Front-end engineering-making a company's scaffolding

Front-end engineering-making a company's scaffolding

Effectiveness

introduction

As the concept of front-end engineering continues to deepen, more and more people choose to use scaffolding to build their own projects from zero to one. The one that everyone is most familiar with is

create-react-app
with
vue-cli
, They can help us initialize the configuration and generate the project structure, automatically install dependencies, and finally we can run the project with a line of instructions to start development, or to build the project.

These scaffolds provide best practices in a general sense, but in actual development, it is found that with the continuous development of the business, there will inevitably be adjustments to the actual situation of business development. E.g:

  • Realized by adjusting the plug-in and configuration

    Webpack
    After packaging performance optimization

  • Project structure adjustment

  • Coding style

  • User access control

  • Fusion company's infrastructure

  • ...

All in all, as the business develops, we tend to precipitate a more "personalized" business plan. At this time, our most direct approach is to develop a scaffolding of the scheme so that these best practices and schemes can be reused in the future.

1. How does scaffolding work?

Scaffolds with different functions are naturally not the same in complexity. But in general, the work of scaffolding generally involves several steps:

  • Initialization, generally at this time, the environment will be initialized and some pre-checks, such as version updates, will be done.
  • User input and interaction, such as using
    vue-cli
    When, it will "ask" you a lot of configuration options
  • Generate template
  • Generate configuration file
  • Installation dependencies
  • Finishing work such as cleaning, verification, etc.

In the enterprise, we generally just want to be lightweight and quickly create a scaffolding for a specific scene (may not need to be

vue-cli
So complete). All the following will demonstrate the basic process of production, from simple demo debugging to complex function development.

2. Scaffolding overall structure design and basic process

Basic process

3. Develop the three-party library we need to use for scaffolding

Library namedescription
commanderProcessing console commands
chalkColorful console
latest-versionThe latest npm package for activities
inquirerConsole query
download-git-repogit remote warehouse pull
figletChalk
globMatch the specified path file
oraLoading effect of the command line environment
clearClear the information output from the console
log-symbolsColorful symbols for various diary levels
metalsmithProcessing template

4. Project actual combat

  • Directory Structure
|-- .gitignore |-- .npmignore |-- package-lock.json |-- package.json |-- README.md |-- bin | |-- cunw | |-- cunw-init | |-- cunw-list |-- conf | |-- const .js | |-- index.js | |-- template.json |-- lib | |-- download.js | |-- generator.js | |-- projectConstruction.js |-- utils |-- log.js Copy code
1. Project Initialization

Create a new one

cunw
Folder, initialize project
npm init --y
And install the required dependencies

{ "name": "cunw-cli", "version": "0.0.1", "description": "Quickly generate project scaffolding", "main": "index.js", "scripts": { "test": "echo/"Error: no test specified\" && exit 1" }, "author": "forestxiecode", "license": "ISC", "dependencies": { "chalk": "^4.1.1", "clear": "^0.1.0", "commander": "^6.1.0", "download-git-repo": "^3.0.2", "ejs": "^2.7.4", "figlet": "^1.5.0", "git-clone": "^0.1.0", "glob": "^7.1.6", "handlebars": "^4.7.6", "inquirer": "^6.5.2", "latest-version": "^5.1.0", "log-symbols": "^4.0.0", "metalsmith": "^2.3.0", "minimatch": "^3.0.4", "ora": "^5.1.0", "rimraf": "^3.0.2", "wrap-ansi": "^7.0.0" }, "devDependencies": {}, } Copy code
2. Test environment

In the lib folder in the root directory, create a new

cunw-init.js
File, write test code.

+ #! /usr/ bin/env node + Console .log ( 'Test' ) copying the code

And modify the pack.json file, global link instructions, test environment, debugging code, such as.

+ "bin": { + "cunw-init": "bin/cunw-init" + } Copy code
  • Under the root directory global link
    npm link
  • Command test

3. Write the program code

According to the input, get the project name

//Obtain the project name according to the input let projectName = program.args[ 0 ]; if (!projectName) { //Equivalent to the --help option of executing the command, displaying the help information, which is a built-in command option of commander program.help(); return ; } //returns the current working directory Node.js process the let rootName = path.basename (process.cwd ()); Copy the code

Execute the main function, use here

figlet
Tool to print uppercase chalk font

const Data = the await figlet ( 'the WELCOM CuNW the CLI' ) Console .log (chalk.green (Data)) to copy the code

Enter version check, get local

package.json
The version number under the file, use
latest-version
Module gets the last version number for comparison

function checkVersion () { return new Promise ( async (resolve, reject) => { const spinner = ora( `Check version....`); spinner.start(); let webVersion = await latestVersion( ` ${CONST.CLI_NAME} ` ); let localVersion = require ( "../package.json" ).version; spinner.succeed(); console.log(` ${localVersion}, ${webVersion}\n`); let webVersionArr = webVersion.split("."); let localVersionArr = localVersion.split("."); let isNew = webVersionArr.some((item, index) => { return Number(item) > Number(localVersionArr[index]); }); if (isNew) { log.warn(` , npm install @cunw/cunw-cli -g/n`) setTimeout(() => { resolve(isNew); }, 2000) } else { setTimeout(() => { resolve(isNew); }, 1000) } }); }

// function checkDir() { return new Promise(async (resolve, reject) => { const list = glob.sync("*"); // if (list.length) { if ( list.filter(name => { const fileName = path.resolve(process.cwd(), path.join(".", name)); const isDir = fs.statSync(fileName).isDirectory(); return name.indexOf(projectName) !== -1 && isDir; }).length !== 0 ) { log.error(` ${projectName} `) reject(` ${projectName} `); } resolve(projectName); } else if (rootName === projectName) { let answer = await inquirer.prompt([ { name: "buildInCurrent", message: " ", type: "confirm", default: true } ]); resolve(answer.buildInCurrent ? "." : projectName); } else { resolve(projectName); } }); } //Create the file function makeDir ( projectRoot ) { if (projectRoot !== "." ) { fs.mkdirSync(projectName); } } Copy code

Use inquiry.js to handle the command line interaction, allowing users to render the templates they need now.

function selectTemplate () { return new Promise ( ( resolve, reject ) => { let choices = [] Object .values(templateConfig).forEach( item => { if (item.enable) { choices.push({ name : item.name, value : item.value }); } }); let config = { //type:'checkbox', type : "list" , message : "Please choose the type of project to create" , name : "select" , choices : [ new inquirer.Separator( "template type" ), .. .choices] }; inquirer.prompt(config).then( data => { let {select} = data; let {name, git, value} = templateConfig[select]; resolve({ git, name, value }); }); }); } Copy code

Get the user's template address, use

download-git-repo
Module download template.

module .exports = function ( target, url ) { const spinner = ora( `The project template is being downloaded, the source address: ${url} ` ) target = path.join(CONST.TEMPLATE_NAME) spinner.start() return new Promise ( ( resolve, reject ) => { download( `direct: ${url} ` , target, { clone : true }, ( err ) => { if (err) { spinner.fail() console .log(logSymbols.fail, chalk.red( "Template download failed" )); reject(err) } else { spinner.succeed() console .log(logSymbols.success, chalk.green( "Template download completed" )); resolve(target) } }) }) } Copy code

use

metalsmith
Processing template

Quoting the introduction of the official website:

An extremely simple, pluggable static site generator.

It is a static website generator that can be used in batch processing template scenarios. Similar toolkits also

Wintersmith
,
Assemble
,
Hexo
. One of its biggest features is
EVERYTHING IS PLUGIN
,and so,
metalsmith
It is essentially a glue frame, which completes the production work by gluing various plug-ins.

Add variable placeholders to the project template.

module .exports = function ( config ) { let {metadata, src, dest} = config; if (!src) { return Promise .reject( new Error ( `Invalid source: ${src} ` )); } //Official template return new Promise ( ( resolve, reject ) => { const metalsmith = Metalsmith(process.cwd()) .metadata(metadata) .clean( false ) .source(src) .destination(dest); const ignoreFile = path.resolve(process.cwd(), src, CONST.FILE_IGNORE); if (fs.existsSync(ignoreFile)) { //Define a metalsmith plugin metalsmith.use( ( files, metalsmith, done ) => { const meta = metalsmith.metadata(); //First render the ignore file, and then cut the content of the ignore file by line to get the ignored list const ignores = ejs .render(fs.readFileSync(ignoreFile).toString(), meta) .split( "\n" ) .filter( item => !!item.length); Object .keys(files).forEach( fileName => { //Remove ignored files ignores.forEach( ignorePattern => { if (minimatch(fileName, ignorePattern) ) { delete files[fileName]; } }); }); done(); }); } metalsmith .use( ( files, metalsmith, done ) => { const meta = metalsmith.metadata(); //compile template Object .keys(files).forEach( fileName => { try { const t = files[fileName].contents .toString(); if ( /(<%.*%>)/g .test(t)) { files[fileName].contents = new Buffer.from(ejs.render(t, meta)); } } catch (err) { //console.log("fileName------------", fileName); //console.log("er ----------- --", err); } }); done(); }) .build( err => { rm(src); err? reject(err): resolve(); }); }); }; Copy code

package.json
of
name
,
version
,
description
The content of the field is replaced with
handlebar
Placeholders for syntax, similar replacements are also done elsewhere in the template, and resubmit the template update after completion.

Call this function to delete some useless files and do some cleanup work.

function deleteCusomizePrompt ( target ) { //Custom option template path const cusomizePrompt = path.join(process.cwd(), target, CONST.CUSTOMIZE_PROMPT) if (fs.existsSync(cusomizePrompt)) { rm(cusomizePrompt) } //Ignore the document path const fileIgnore = path.join(process.cwd(), target, CONST.FILE_IGNORE) if (fs.existsSync(fileIgnore)) { rm(fileIgnore) } } Copy code

Finally, the finished callback is executed and the project is initialized.

clear() log.succes( "Created successfully" ) //Initialize the project await initProject(projectRoot) //Run the project console .log(chalk.green( `=================== =================\n Run the project...\n cd ./demmo\n npm run serve\n =================================== ` )) Copy code

5. How to publish an npm package

1. Register an npm account
2. Under the project root directory
npm login
Sign in
npm
Account number, final execution
npm publish
release

summary

To imitate, to refer to, in fact, the realization of a scaffolding is not particularly complicated.

  • Some engineering problems can be solved well through node
  • In fact, npm has a good node module library such as:
    • Process downloads through download-git-repo
    • Handle terminal interaction through inquiry.js
    • Insert interactive input items into project templates through metalsmith and template engine

Through this development of their own scaffolding. I also think I have many shortcomings, and I am imitating and learning.