preface
Front end scaffolding is an important tool to improve team efficiency in front-end engineering. Therefore, building scaffolding is an unavailable skill for front-end engineers, while there are relatively few scaffolds in the industry for deployment. Generally speaking, it is related to the relevant templates of the business, This paper aims to provide some scaffolding practice schemes related to front-end deployment, hoping to be helpful to students related to building engineering links.
framework
For a scaffold, it is not just a deployment scheme. Therefore, in the scaffold architecture design, the plug-in mode is adopted, and the required functional parts are provided through the plug-in mechanism
catalogue
packages
- cli
- cli-service
- cli-shared-utils
- cli-ui
- test
scripts
- bootstrap.js
- dev.js
- prod.js
- release.js
- env.json
case
The @ pnw/cli scaffold is used to build the deployment directory, and the command pnw deploy is used to build the deployment directory. The ci/cd process is followed, where default Conf is mainly used for nginx construction, Dockerfile is used for image construction, and yaml file is mainly used for k8s related construction
Source code
cli
The core of the deployment part is deploy JS, and this part implements specific functions in cli plugin deploy
create.js
const { isDev } = require('../index'); console.log('isDev', isDev) const { Creator } = isDev ? require('../../cli-service') : require('@pnw/cli-service'); const creator = new Creator(); module.exports = (name, targetDir, fetch) => { return creator.create(name, targetDir, fetch) }
deploy.js
const { isDev } = require('../index'); const { path, stopSpinner, error, info } = require('@pnw/cli-shared-utils'); const create = require('./create'); const { Service } = isDev ? require('../../cli-service') : require('@pnw/cli-service'); const { deployFn } = isDev ? require('../../cli-plugin-deploy') :require('@pnw/cli-plugin-deploy'); // console.log('deployFn', deployFn); async function fetchDeploy(...args) { info('fetchDeploy Yes') const service = new Service(); service.apply(deployFn); service.run(...args); } async function deploy(options) { // Customize the deploy ment content TODO info('deploy Yes') const targetDir = path.resolve(process.cwd(), '.'); return await create('deploy', targetDir, fetchDeploy) } module.exports = (...args) => { return deploy(...args).catch(err => { stopSpinner(false) error(err) }) };
cli-plugin-deploy
Implement the core part of the deployment part, in which build, nginx, docker and yaml are mainly used to generate template content files
build.js
exports.build = config => { return `npm install npm run build` };
docker.js
exports.docker = config => { const { forward_name, env_directory } = config; return `FROM harbor.dcos.ncmp.unicom.local/platpublic/nginx:1.20.1 COPY ./dist /usr/share/nginx/html/${forward_name}/ COPY ./deploy/${env_directory}/default.conf /etc/nginx/conf.d/ EXPOSE 80` }
nginx.js
exports.nginx = config => { return `client_max_body_size 1000m; server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; gzip_static on; } }` }
yaml.js
exports.yaml = config => { const { git_name } = config; return `apiVersion: apps/v1 kind: Deployment metadata: name: ${git_name} spec: replicas: 1 selector: matchLabels: app: ${git_name} template: metadata: labels: app: ${git_name} spec: containers: - name: ${git_name} image: harbor.dcos.ncmp.unicom.local/custom/${git_name}:1.0 imagePullPolicy: Always resources: limits: cpu: 5 memory: 10G requests: cpu: 1 memory: 1G ports: - containerPort: 80` }
index.js
// const { fs, path } = require('@pnw/cli-shared-utils'); const { TEMPLATES } = require('../constant'); /** * * @param {*} template Template path * @param {*} config Parameters of injection template */ function generate(template, config) { // console.log('template', template); return require(`./${template}`).generateJSON(config); } function isExitTemplate(template) { return TEMPLATES.includes(template) } TEMPLATES.forEach(m => { Object.assign(exports, { [`${m}`]: require(`./${m}`) }) }) exports.createTemplate = (tempalteName, env_dirs) => { if( isExitTemplate(tempalteName) ) { return generate(tempalteName, { // git_name: 'fescreenrj', // forward_name: 'rj', env_dirs }); } else { return `${tempalteName} is NOT A Template, Please SELECT A correct TEMPLATE` } }
main.js
const { createTemplate } = require('./__template__'); const { isDev } = require('../index'); console.log('isDev', isDev); const { REPO_REG, PATH_REG } = require('./reg'); const { TEMPLATES } = require('./constant'); const { path, fs, inquirer, done, error } = isDev ? require('../../cli-shared-utils') : require('@pnw/cli-shared-utils'); /** * * @param {*} targetDir Absolute path to the destination folder * @param {*} fileName File name * @param {*} fileExt File extension * @param {*} data File content */ const createFile = async (targetDir, fileName, fileExt, data) => { // console.log('fileName', fileName); // console.log('fileExt', fileExt); let file = fileName + '.' + fileExt; if (!fileExt) file = fileName; // console.log('file', file) await fs.promises.writeFile(path.join(targetDir, file), data) .then(() => done(`create a file ${file}success`)) .catch(err => { if (err) { error(err); return err; } }); } /** * * @param {*} targetDir The destination path address of the directory that needs to be created * @param {*} projectName Directory name to be created * @param {*} templateStr Configuration string in directory * @param {*} answers Parameters obtained from the command line */ const createCatalogue = async (targetDir, projectName, templateMap, answers) => { const templateKey = Object.keys(templateMap)[0], templateValue = Object.values(templateMap)[0]; // console.log('templateKey', templateKey); // console.log('templateValue', templateValue); // Obtain various tool functions corresponding to the template const { yaml, nginx, build, docker } = require('./__template__')[`${templateKey}`]; // Get environment folder const ENV = templateValue.ENV; console.log('path.join(targetDir, projectName)', targetDir, projectName) // 1. Create a folder await fs.promises.mkdir(path.join(targetDir, projectName)).then(() => { done(`Create project catalog ${projectName}success`); return true }) .then((flag) => { // console.log('flag', flag); // Get Options for build const buildOptions = templateValue.FILE.filter(f => f.KEY == 'build')[0]; // console.log('buildOptions', buildOptions); flag && createFile(path.join(targetDir, projectName), buildOptions[`NAME`], buildOptions[`EXT`], build()); }) .catch(err => { if (err) { error(err); return err; } }); ENV.forEach(env => { fs.promises.mkdir(path.join(targetDir, projectName, env)) .then(() => { done(`Create project catalog ${projectName}/${env}success`); return true; }) .then(flag => { // Get Options for docker const dockerOptions = templateValue.FILE.filter(f => f.KEY == 'docker')[0]; flag && createFile(path.join(targetDir, projectName, env), dockerOptions[`NAME`], dockerOptions[`EXT`], docker({ forward_name: answers[`forward_name`], env_directory: env })); // Get Options for yaml const yamlOptions = templateValue.FILE.filter(f => f.KEY == 'yaml')[0]; flag && createFile(path.join(targetDir, projectName, env), yamlOptions[`NAME`], yamlOptions[`EXT`], yaml({ git_name: answers[`repo_name`] })); // Get Options for nginx const nginxOptions = templateValue.FILE.filter(f => f.KEY == 'nginx')[0]; flag && createFile(path.join(targetDir, projectName, env), nginxOptions[`NAME`], nginxOptions[`EXT`], nginx()); }) .catch(err => { if (err) { error(err); return err; } }); }); } /** * * @param {*} projectName Generated directory name * @param {*} targetDir Absolute path */ module.exports = async (projectName, targetDir) => { let options = []; async function getOptions() { return fs.promises.readdir(path.resolve(__dirname, './__template__')).then(files => files.filter(f => TEMPLATES.includes(f))) } options = await getOptions(); console.log('options', options); const promptList = [{ type: 'list', message: 'Please select the application template you need to deploy', name: 'template_name', choices: options }, { type: 'checkbox', message: 'Please select the environment you need to deploy', name: 'env_dirs', choices: [{ value: 'dev', name: 'development environment ' }, { value: 'demo', name: 'Demo environment' }, { value: 'production', name: 'production environment ' }, ] }, { type: 'input', name: 'repo_name', message: 'It is recommended to use the current git The abbreviation of the warehouse is used as the image name', filter: function (v) { return v.match(REPO_REG).join('') } }, { type: 'input', name: 'forward_name', message: 'Please use url Name of the path rule', filter: function (v) { return v.match(PATH_REG).join('') } }, ]; inquirer.prompt(promptList).then(answers => { console.log('answers', answers); const { template_name } = answers; // console.log('templateName', templateName) // Get template string const templateStr = createTemplate(template_name, answers.env_dirs); // console.log('template', JSON.parse(templateStr)); const templateMap = { [`${template_name}`]: JSON.parse(templateStr) } createCatalogue(targetDir, projectName, templateMap, answers); }) };
cli-service
Service and Creator are two base classes respectively, which are used to provide basic related services
Creator.js
const { path, fs, chalk, stopSpinner, inquirer, error } = require('@pnw/cli-shared-utils'); class Creator { constructor() { } async create(projectName, targetDir, fetch) { // const cwd = process.cwd(); // const inCurrent = projectName === '.'; // const name = inCurrent ? path.relative('../', cwd) : projectName; // targetDir = path.resolve(cwd, name || '.'); // if (fs.existsSync(targetDir)) { // const { // action // } = await inquirer.prompt([{ // name: 'action', // type: 'list', // message: `Target directory ${chalk.cyan(targetDir)} already exists. Pick an action:`, // choices: [{ // name: 'Overwrite', // value: 'overwrite' // }, // { // name: 'Merge', // value: 'merge' // }, // { // name: 'Cancel', // value: false // } // ] // }]) // if (!action) { // return // } else if (action === 'overwrite') { // console.log(`\nRemoving ${chalk.cyan(targetDir)}...`) // await fs.remove(targetDir) // } // } await fetch(projectName, targetDir); } } module.exports = Creator;
Service.js
const { isFunction } = require('@pnw/cli-shared-utils') class Service { constructor() { this.plugins = []; } apply(fn) { if(isFunction(fn)) this.plugins.push(fn) } run(...args) { if( this.plugins.length > 0 ) { this.plugins.forEach(plugin => plugin(...args)) } } } module.exports = Service;
cli-shared-utils
Share the tool library, converge the third-party and node related core modules to this, and unify the output and magic change
is.js
exports.isFunction= fn => typeof fn === 'function';
logger.js
const chalk = require('chalk'); const { stopSpinner } = require('./spinner'); const format = (label, msg) => { return msg.split('\n').map((line, i) => { return i === 0 ? `${label} ${line}` : line.padStart(stripAnsi(label).length + line.length + 1) }).join('\n') }; exports.log = (msg = '', tag = null) => { tag ? console.log(format(chalkTag(tag), msg)) : console.log(msg) }; exports.info = (msg, tag = null) => { console.log(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ''), msg)) }; exports.done = (msg, tag = null) => { console.log(format(chalk.bgGreen.black(' DONE ') + (tag ? chalkTag(tag) : ''), msg)) }; exports.warn = (msg, tag = null) => { console.warn(format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ''), chalk.yellow(msg))) }; exports.error = (msg, tag = null) => { stopSpinner() console.error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ''), chalk.red(msg))) if (msg instanceof Error) { console.error(msg.stack) } }
spinner.js
const ora = require('ora') const chalk = require('chalk') const spinner = ora() let lastMsg = null let isPaused = false exports.logWithSpinner = (symbol, msg) => { if (!msg) { msg = symbol symbol = chalk.green('✔') } if (lastMsg) { spinner.stopAndPersist({ symbol: lastMsg.symbol, text: lastMsg.text }) } spinner.text = ' ' + msg lastMsg = { symbol: symbol + ' ', text: msg } spinner.start() } exports.stopSpinner = (persist) => { if (!spinner.isSpinning) { return } if (lastMsg && persist !== false) { spinner.stopAndPersist({ symbol: lastMsg.symbol, text: lastMsg.text }) } else { spinner.stop() } lastMsg = null } exports.pauseSpinner = () => { if (spinner.isSpinning) { spinner.stop() isPaused = true } } exports.resumeSpinner = () => { if (isPaused) { spinner.start() isPaused = false } } exports.failSpinner = (text) => { spinner.fail(text) }
summary
The front-end engineering link is not only the construction of front-end application projects, but also needs to pay attention to the upstream and downstream related construction of the whole front-end, including but not limited to ui construction, test construction, deployment construction, etc. for front-end engineering, all things that can be abstracted into templates should be engineered, so as to reduce costs and increase efficiency and improve development experience and efficiency, Encourage each other!!!