preface
At present, the front end is developing day by day, and the engineering is becoming more and more mature. A lot of great frameworks and tools have emerged during this time. At the same time, with the use of scaffolding with the frame is also coming. The development of front-end scaffolding tools, such as VUE-CLI, create-React-app, etc., are commonly used in vue and React development projects. Xiaobian in the vuE-CLI3, VUE-CLI2 scaffolding implementation, whim to achieve a simple version of their own scaffolding, below we come to learn about the scaffolding implementation process.
Implementation approach
I think the implementation differences between VUE-CLI3 and VUe-cli2 are as follows
- is
vue-cli3
Not fromgit
The repository downloads the templates, instead generating the code itself and creating files and folders. vue-cli3
thewebpack
The configuration is built in, not exposed, and provides user-defined configuration files from defining your own configuration; whilevue-cli2
The configuration is completely exposed and can be modified at will.
This article is based on vuE-CLI2 implementation ideas to implement a simple react version scaffolding step by step. Below is a small series of overall implementation process
2. Use the COMMANDER tool to add parsing parameters to your LBS command, parse parameters, and add custom commands. 3. Use the Inquirer command line to interact with the user (user input, selection). 5. Modify the files in the template (package.json, index.html, etc.). 6
Start coding
This article implements a LBS init [projectName] –force command
ProjectName: the entered projectName –force: the defined option (whether to force overwrite if the entered [projectName] folder exists in the current directory)
Add Scaffolding command (LBS)
In package.json, we can add the following code
"bin":{
"lbs": "./bin/lbs.js"
},
Copy the code
Then create the bin/lbs.js file and add the test code:
#! /usr/bin/env node console.log("hello lbs-cli")Copy the code
The first line, which must be added, specifies that node is used here to parse the script. The default directory is /usr/bin. If the directory cannot be found, go to the system environment variable.
Then we open CMD window in any directory, type LBS command, you will find no command. In fact, we still need to do a step, is to install the local project globally, under the current project run NPM install. -g, and then run the LBS command under CMD, you will find the output of the string we printed.
Now that we’ve added our own LBS commands to the system, how do we add init, create, –version, and so on to LBS?
Use Commander to enrich our LBS command
For those unfamiliar with the use of Commander, see the Commander documentation
We’ll start by installing the plug-in, and then take a first stab at adding version viewing options to our LBS command
const { program } = require("commander") const pkg = require("./.. /package.json") program.version(pkg.version,'-v --version') program.parse(process.argv)Copy the code
If you run LBS -v or LBS –version on any command line, you can see the version output on the console
Next add a command to the LBS command:
Program.mand ('init [projectName]').description(" Initializes the project ") // Add an option.option ('-f --force',' force to delete the project directory if there is an input project directory '). Action ((projectName, CMD)=>{// projectName is the argument we entered, Console. log(projectName) // CMD is the Command object console.log(cmd.force)})Copy the code
Here we have added an init command that supports an optional argument and an optional -f option so let’s do this
lbs init test -f
Copy the code
The test and CMD objects we entered can be viewed on the console. The force attribute can be found in CMD.
If LBS init is executed, the output is as follows
If LBS init test is executed, the following output is displayed
We’ll focus on these two data points here, but you can extend other parameters and options if your command has other complex functions.
Here are justcommand
A way to use when we arecommand
Adding a second description argument means using a separate executable as a subcommand, such as your command isinit
Then you need to create onelbs-init
Script file, this file is responsible for executing the command you specify, according tolbs-${command}
The way we created the scriptlbs-init.js
file
Modify the command as follows, adding a second parameter to the command method
Program.mand ('init [projectName]','init project').description(" Initialize the project ") // Add an option.option ('-f --force',' force to delete project directory if entered ').action((projectName, CMD)=>{console.log(projectName) // projectName is the argument we entered, Console. log(cmd.force) // CMD is the Command object})Copy the code
Execute LBS init and you will find nothing output. Because the action method will not be executed, the empty lbS-init. js file we created will be executed. So it doesn’t output anything. Lbs.js only needs to define the init command. Program.mand (‘init [projectName]’,’init project’)
Then add the parsing code to lbS-init.js
const { program } = require("commander") let projectName; let force; Description (" initializes the project ").option('-f --force',' If there is an input project directory, ').action((name, CMD)=>{projectName = name; force = cmd.force; }); program.parse(process.argv); console.log(projectName,force)Copy the code
Re-run LBS init test -f to obtain the data. At this point, we can customize the parameters and options for our LBS init command. What if the user just executes the LBS init command and we don’t get the project name? Please look down
useinquirer
Implementation of command line and user interaction (user input, selection, q&A)
The inquirer plugin Chalk is basically a custom color console output
Create a logger.js utility class that prints console information
const chalk = require('chalk');
exports.warn = function(message){
console.log(chalk.yellow(message));
}
exports.error = function(message){
console.log(chalk.red(message))
}
exports.info = function(message){
console.log(chalk.white(message))
}
exports.infoGreen = function(message){
console.log(chalk.green(message))
}
exports.exit = function(error){
if(error && error instanceof Error){
console.log(chalk.red(error.message))
}
process.exit(-1);
}
Copy the code
This library is a tool that we can use to interact with users; The first problem is to input the project name. The second problem is to ask the user to select a template. The template needs to be ready on Github. This template is based on the react-apps-template project to rebuild a Git repository. The react+ WebPack4 template does not exist
Const questions = [{type: 'input', name:'projectName', message: Yellow (" Enter your project name: ")}, {type:'list', name:'template', message: Yellow (" Please select Create Project template: "), choices:[ {name:"lb-react-apps-template",value:"lb-react-apps-template"}, {name:"template2",value:"tempalte2"}, {name:"template3",value:"tempalte3"} ] } ]; If (projectName){questions.splice(0,1); if(projectName){questions.splice(0,1); } // Execute user interaction command inquirer.prompt(questions). Then (result=>{if(result.projectname) {projectName = result.projectname; } const templateName = result.template; Log (" projectName: "+ projectName) console.log(" templateName:" + templateName) if(! templateName || ! ProjectName){// exit logger.exit(); } // go down checkProjectExits(projectName,templateName); // Check if the directory exists}). Catch (error=>{logger.exit(error); })Copy the code
Here,checkProjectExits
I’m going to implement it, so I can ignore it. At this point we executelbs init
, you can see that the successful acquisitionprojectName
andtemplateName
Next, we need to determine whether the project name entered by the user exists in the current directory. In this case, 1. If the command executed by the user contains –force, then delete the existing directory directly; 2. If the user wants to overwrite, delete the existing folder, but if the user doesn’t allow it, exit
Add the checkProjectExits method to check for the existence of a directory as follows
function checkProjectExits(projectName,templateName){ const currentPath = process.cwd(); const filePath = path.join(currentPath,`${projectName}`); If (force){// Force delete if(fs.existssync (filePath)){// Delete spinner. LogWithSpinner (' delete ${projectName}... `) deletePath(filePath) spinner.stopSpinner(false); } startDownloadTemplate(projectName, templateName) return; } if(fs.existssync (filePath)){// Check whether the file exists and ask whether to continue inquirer. Prompt ({type: 'confirm', name: 'out', message: The '${projectName} folder already exists. Overwrite it? ` }).then(data=>{ if(! Data.out){// the user disagrees with exit(); }else{// delete spinner. LogWithSpinner (' delete ${projectName}... `) deletePath(filePath) spinner.stopSpinner(false); StartDownloadTemplate (projectName, templateName) // Start downloading template}}). Catch (error=>{exit(error); }) }else{ startDownloadTemplate(projectName, }} function startDownloadTemplate(projectName,templateName){ console.log(projectName,templateName) }Copy the code
Here we use a spinner tool class, create lib/spinner. Js, mainly a switch chrysanthemum animation prompt, the code is as follows
const ora = require('ora') const chalk = require('chalk') const spinner = ora() let lastMsg = null exports.logWithSpinner = (symbol, msg) => { if (! Mark) {mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark = mark mark 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 }Copy the code
Create lib/ IO. Js and implement deletePath to delete a directory as follows
function deletePath (filePath){ if(fs.existsSync(filePath)){ const files = fs.readdirSync(filePath); for(let index=0; index<files.length; index++){ const fileNmae = files[index]; const currentPath = path.join(filePath,fileNmae); if(fs.statSync(currentPath).isDirectory()){ deletePath(currentPath) }else{ fs.unlinkSync(currentPath); } } fs.rmdirSync(filePath); }}Copy the code
LBS init my-app -f and LBS init -f to check whether my-app is deleted.
Execute LBS init and enter the existing directory name as the project name according to step by step instructions. Select the template and check if the my-app folder is deleted as follows
Download and unzip the template
To download the template, we need to concatenate the URL of the github repository corresponding to the zip package according to the name of the selected template, and then execute the node download code (note that the downloaded zip package is downloaded to the temporary directory of the system). After downloading the zip package successfully, unzip it to the directory where the user entered the project name. After the package is decompressed, delete the downloaded package. That’s the end of the process
Request plug-in was downloaded and decompressed. Decompress plug-in was used. These two plug-ins need to be installed in advance
Override the startDownloadTemplate method above
Function startDownloadTemplate(projectName,templateName){// Start downloading template (projectName,templateName, (error)=>{ if(error){ logger.exit(error); return; } // Replace the extracted template package.json, ReplaceFileContent (projectName,templateName)})} function replaceFileContent(projectName,templateName){ console.log(projectName,templateName); }Copy the code
Create lib/download.js to download template. The code is as follows
const request = require("request") const fs = require("fs") const path = require("path") const currentPath = process.cwd(); const spinner = require("./spinner") const os = require("os") const { deletePath , unzipFile } = require("./io") exports.downloadTemplate = function (templateName,projectName,callBack){ // According to the templateName joining together making the corresponding package const url = {templateName} ` https://github.com/liuboshuo/$url/archive/master. Zip `; Const tempProjectPath = fs.mkdtempsync (path.join(os.tmpdir(), '${projectName}-')); Const file = path.join(tempProjectPath, '${templateName}.zip'); If (fs.existssync (file)){fs.unlinksync (file); } spinner. LogWithSpinner (" Download from template...") ) let stream = fs.createWriteStream(file); request(url,).pipe(stream).on("close",function(err){ spinner.stopSpinner(false) if(err){ callBack(err); return; Const destPath = path.join(currentPath, '${projectName}'); UnzipFile (file,destPath,(error)=>{deletePath(tempProjectPath); unzipFile(file,destPath,(error)=>{deletePath(tempProjectPath); callBack(error); }); })}Copy the code
Add the following method to decompress the zip package in lib/io.js
const decompress = require("decompress"); exports.unzipFile = function(file,destPath,callBack){ decompress(file,destPath,{ map: File => {// Here you can change the decompression location of the file, // For example, the file path in the compressed package is ${destPath}/lb-react-apps-template/ SRC /index.js const outPath = file.path.substr(file.path.indexOf('/') + 1) file.path = outPath return file }} ).then(files => { callBack() }).catch(error=>{ callBack(error) }) }Copy the code
Here you can perform the LBS init my-app test
Modify template files in your project (package.json, index.html, etc.)
Override the replaceFileContent method to change the contents of some files in the template, such as package.json’s name and index.html’s title value
function replaceFileContent(projectName,templateName){ const currentPath = process.cwd(); Json const pkgPath = path.join(currentPath, '${projectName}/package.json'); Const PKG = require(pkgPath); // change the name attribute of package.json to projectName pkg.name = projectName; fs.writeFileSync(pkgPath,JSON.stringify(pkg,null,2)); const indexPath = path.join(currentPath, `${projectName}/index.html`); let html = fs.readFileSync(indexPath).toString(); / templates/modify the title for the project name HTML = HTML. Replace (/ < title > (. *) < \ / title > / g, ` < title > ${projectName} < / title > `) fs.writeFileSync(indexPath,html); } function install(projectName){console.log(projectName)} function install(projectName){console.log(projectName)}Copy the code
Install dependencies
Rewrite the install method to create a node child process using the child_process package to perform the NPM install task. CMD on Windows, NPM. CMD on Linux, and NPM on MAC. Child_process is a nodejs package. The spawn method is used to execute system commands, the execFileSync method is used to execute files, and so on
const currentPath = process.cwd(); const npm = process.platform === 'win32' ? CMD ':' NPM '// Create a child process to execute NPM install const nodeJob = child_process.spawn(NPM, ['install'], {stdio: 'inherit', // specify the parent process communication mode CWD: path.join(currentPath,projectName)}); Nodejob.on ("close",()=>{logger.info(' created successfully! ${projectName} project is located at ${path.join(currentPath,projectName)} ') logger.info(") logger.info(' You can run the development environment by executing the following command ') logger.infoGreen(` cd ${projectName} `); logger.infoGreen(` npm run dev `); })Copy the code
Test it by performing LBS init
So here’s a simple version of scaffolding!
What doubt can concern public number private letter oh ~