preface
Front-end scaffolding front-end engineering is a important tool to enhance the efficiency of the team, thus building a scaffold for the front-end engineer is a cannot obtain the skill, and the industry for the deployment of the scaffold is relatively small, generally are related to business related engineering scaffold building templates, This paper aims to provide some scaffolding practices related to front-end deployment, hoping to help students who build engineering links.
architecture
For a scaffold, not only to achieve a deployment scheme, so in scaffolding architecture design, the use of plug-in plug-in model, through the plug-in mechanism will be required to provide functional parts
directory
- packages
- @pnw
- cli
- cli-service
- cli-shared-utils
- cli-ui
- test
- @pnw
- scripts
- bootstrap.js
- dev.js
- prod.js
- release.js
- env.json
case
Use @pnw/ CLI scaffolding to build, use the command PNW deploy to build the deployment directory, follow the CI/CD process, where default.conf is mainly used for nginx build, Dockerfile is used for image build, yamL file is mainly used for K8S-related build
The source code
cli
The core of the deployment part is an invocation of deployFn in deploy.js, which is implemented 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)
}
Copy the code
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 executes')
const service = newService(); service.apply(deployFn); service.run(... args); }async function deploy(options) {
// Customize the deploy content TODO
info('deploy executes')
const targetDir = path.resolve(process.cwd(), '. ');
return await create('deploy', targetDir, fetchDeploy)
}
module.exports = (. args) = > {
returndeploy(... args).catch(err= > {
stopSpinner(false)
error(err)
})
};
Copy the code
cli-plugin-deploy
Implement the core part of the deployment part, where Build, nginx, Docker, YAML, etc., are mainly used to generate template content files
build.js
exports.build = config= > {
return `npm install
npm run build`
};
Copy the code
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`
}
Copy the code
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; }} `
}
Copy the code
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`
}
Copy the code
index.js
// const { fs, path } = require('@pnw/cli-shared-utils');
const { TEMPLATES } = require('.. /constant');
/ * * * *@param {*} Template Template path *@param {*} Config Specifies the parameter */ used to inject templates
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`}}Copy the code
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 target folder *@param {*} FileName fileName *@param {*} FileExt file extension *@param {*} Data file contents */
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}Successful `))
.catch(err= > {
if (err) {
error(err);
returnerr; }}); }/ * * * *@param {*} TargetDir Target path address * for the directory to be created@param {*} ProjectName Name of the directory you want to create *@param {*} Configuration string * in templateStr directory@param {*} The argument */ obtained from the answers 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);
// Get the various utility functions corresponding to the template
const {
yaml,
nginx,
build,
docker
} = require('./__template__') [`${templateKey}`];
// Get the 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 the project directory${projectName}Successful `);
return true
})
.then((flag) = > {
// console.log('flag', flag);
// Get build Options
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);
returnerr; }}); ENV.forEach(env= > {
fs.promises.mkdir(path.join(targetDir, projectName, env))
.then(() = > {
done('Create the project directory${projectName}/${env}Successful `);
return true;
})
.then(flag= > {
// Get docker Options
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 yamL Options
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 nginx Options
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);
returnerr; }}); }); }/ * * * *@param {*} ProjectName Name of the generated directory *@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 want to deploy'.name: 'template_name'.choices: options
},
{
type: 'checkbox'.message: 'Please select the environment you want 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 abbreviation of the current Git repository as the mirror name'.filter: function (v) {
return v.match(REPO_REG).join(' ')}}, {type: 'input'.name: 'forward_name'.message: 'Please use a name that matches the URL 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 the 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); })};Copy the code
cli-service
Service and Creator are two base classes that provide basic related services, respectively
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)
/ /}
// }
awaitfetch(projectName, targetDir); }}module.exports = Creator;
Copy the code
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;
Copy the code
cli-shared-utils
Common tool library, the third party and node related core module convergence to this inside, unified output and magic change
is.js
exports.isFunction= fn= > typeof fn === 'function';
Copy the code
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)
}
}
Copy the code
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)
}
Copy the code
conclusion
Front-end engineering link is not only the front application program of building, at the same time also need to pay attention to the construction of the entire front-end upstream and downstream related, including but not limited to: UI build, test, building, deploying, building, etc., for the front-end engineering, all things should be able to abstract into templated can be engineered, such ability authors efficiency, promote the development experience and efficiency, ‘!!!!!!
reference
- Vue – cli source code
- Leo: Open source scaffolding tools from an engineering perspective