Scaffolding Project Development Manual

carbon.png

basic concept

With the concept of front-end engineering becoming more and more popular, scaffolding came into being. Simply put, "front-end scaffolding" refers to a tool for quickly building the basic code of a project by selecting a few options. The front-end scaffolding can effectively avoid the same code framework and basic configuration of our ctrl + C and ctrl + V.

Scaffolding Ideas

Before starting to develop the scaffolding CLI, let’s take a look at the ideas and look at several popular scaffolding in the industry. We will find that although they have different functional richness and complexity, they generally include the following basic functions :

CLI build project

  • Generate configuration files based on user input
  • Download the specified project template
  • Generate a new project in the target directory

CLI run project

  • Local launch preview
  • hot update
  • Grammar and code specification detection

Scaffolding Architecture Diagram

Understand the general workflow of scaffolding through the architecture diagram


Plugin dependencies

Develop common plug-ins for scaffolding

plugin name brief description
commander A complete solution for the node.js command line interface
download-git-repo Remote download git project
figlet WordArt
handlebars template engine
inquirer A collection of common interactive command-line user interfaces
log-symbols Symbols that provide coloring for various log levels
child_process Execute command package
not progress bar waiting state
chalk font coloring

create scaffolding

Initialize the project

Download node, create a new folder, cmd enter the folder directory and run: npm init, generate package.json file

{
    
    
  "name": "@gfe/cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    
    
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "xcc",
  "license": "ISC"
}

enter project

Modify the bin parameter in package.json, specifically place the user's custom command, and specify the location of the executable file. The command in the bin is an executable command. If the module is installed globally, npm will configure it for the bin. The file creates a global soft connection , which can be executed directly in the command line tool

{
    
    
  "name": "@gfe/cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin":{
    
    
  	"gfe-cli": "src/gfe-cli.js"
  },
  "scripts": {
    
    
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "xcc",
  "license": "ISC"
}

Create a new src folder in the project, add gfe-cli.js, the file is a command entry file, the first line of the file must be #!/usr/bin/env node , which means that when executing this script, call /usr/bin The node interpreter.

Handle the command line

We set different commands through commander.
The command method is used to set the name of the command, the description method is used to set the description of the command, the alias method is used to set the command abbreviation [a must for lazy people], and the options are used to set the parameters required by the command. For more detailed documentation of commander, you can go to the official website of commander to view it.
We first add three commands to the scaffolding: project creation, project initialization, and project startup . The source code is in src/gfe-cli.js.

project template

Scaffolding can help us quickly generate a specified project structure and configuration. The most common way is that we prepare a set of general, easy-to-use, and standardized project templates in advance and store them in the specified location. When the scaffolding executes the command to create a project , directly copy the **prepared template** to the target directory.
PS: There are two points here that we need to pay attention to:

Project template storage location method

The first one is packaged together with the scaffolding. When the scaffolding is installed, the project template will be stored in the global directory. In this way, each time a project is created, the speed of copying from the local is very fast, but the project template itself Upgrading is more difficult .
The second is to store the project template in a remote warehouse (such as the gitlab warehouse). In this way, every time a project is created, it is dynamically downloaded through a certain address **, and the project template is easy to update.
Choosing the second option decouples the template and scaffolding for easy update and maintenance.
Code source code:

#!/usr/bin/env node
const program = require("commander"); // 用于捕获命令
const chalk = require("chalk"); // 用于字体加色
const download = require("download-git-repo"); // 用于下载git的包
const inquirer = require("inquirer"); // 用于与用户输入做交互
const ora = require("ora"); // 进度显示
const symbols = require("log-symbols"); // 信息前面加✔或✖

program
  .version(require("../package.json").version, "-v,--version")
  .command("init <name>")
  .description("初始化项目中...")
  .action((name) => {
    
    
    inquirer
      .prompt([{
    
    
        type: "list", 
        name: "templates", 
        message: "templates:", 
        choices: [{
    
    
          name: "vue",
          value: {
    
    
            gitUrl: "http://***/vue-demo.git",
          }
        },
                  {
    
    
                    name: "react",
                    value: {
    
    
                      gitUrl: "http://***/react-demo.git",
                    }
                  }]
      }])
      .then((answers) => {
    
    
        const {
    
     gitUrl } = answers.templates
        download(
          `${
      
      gitUrl}`,
          `./${
      
      name}`,
          {
    
     clone: true },
          function (err) {
    
    
            if (err) {
    
    
              console.log(err);
            } else {
    
    
              console.log(symbols.success, chalk.green("创建项目成功"));
            }
          }
        );
      });
  });

program.parse(process.argv);

If the project is downloaded, you need to execute the shell command

In the node script, execute the specified shell command

const {
    
     spawn } =  require('child_process')
// 执行终端命令的子线程
const terminal = async (...args) => {
    
    
  return new Promise((resolve) => {
    
    
    // 子线程
    const proc = spawn(...args)
    // 子线程 proc --终端的输出转到--> 主线程 process
    proc.stdout.pipe(process.stdout)
    proc.stderr.pipe(process.stderr)
    proc.on('close', () => {
    
    
      resolve()
    })
  })
}
module.exports = {
    
    
  terminal
}
const install = async(name) => {
    
    
  const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
  await utils.terminal(npm, ['install'], {
    
    cwd: `./${
      
      name}`})
}

// 初始化
module.exports = install

Executing the process command npm needs to judge const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'

Configure global CLI commands

After our scaffolding is developed and released to npm, it can be installed by npm install -g gfe-cli **global installation** and then the CLI command can be used directly.
However, in order to **convenient debugging and real-time synchronization** modification during the development process, another way is needed to link the CLI command to the global.
You can execute **npm link ** in the gfe-cli directory, this command can soft link the bin command under gfe-cli to the global and use it directly.
Tips: I encountered a few small pits when npm linking, and I would like to share with you that
during the development process, you may encounter **failure to execute commands**, such as zsh: command not found: gfe-cli.

  • Try to re-link npm link, and if it fails, try to delete the global command npm unlink gfe-cli and then link again. Generally, this can solve the problem.
  • If it still doesn’t work, go to the global directory and delete the gfe-cli folder

publish configuration

{
    
    
  "name": "@gfe/cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin":{
    
    
    "gfe-cli": "src/gfe-cli.js"
  },
  "keywords": [
    "tools",
    "javascript",
    "cli"
  ],
  "scripts": {
    
    
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "xcc",
  "license": "ISC"
}

name is the name of the package, you can directly write the package name, such as cli, or add a domain, similar to @gfe/cli, @ is followed by your npm registered user name. key is the keyword of the package.
.npmrc file, publish configuration file

registry=https://*******/
email=****
always-auth=true
_auth=****

.npmignore file, ignore uploaded files

node_modules/
.babelrc
tsconfig.json
.eslintrc.js

npm publish publish

development note

Brief description of ESM and CJS modules

In the early Javascript language, there was no concept of modularity. It was not until the birth of nodejs that the module system was introduced into js. nodejs uses the CJS (Commonjs) specification, which is the require and module.exports we usually see. The module specification of the js language standard is ESM (Ecmascript Module), which is the import and export syntax that we use extensively in front-end projects. nodejs has gradually supported ESM, and many mainstream browsers have natively supported ESM.

Is the project using ESM or CJS?

Node.js 8.5.0 adds experimental support for ESM. Using the **–experimental-modules** flag and adding the file name suffixed with **.mjs** allows nodejs to execute the import and export modules of the ESM specification. For example:

node --experimental-modules index.mjs

Node.js 12.17.0, removed the **–experimental-modules** flag. Although ESM is still experimental, it has been relatively stable.
In later versions, nodejs judges whether the module system uses ESM or CJS according to the following process:

How to use require and import in the same file

After the release of the ES6 standard, module became the standard. The standard usage is to export the interface with the export command and import the module. However, in our usual node module, we use the CommonJS specification, use require to import modules, and use module.exports to export
Modules usually introduced in the interface node are require syntax, not import syntax. You can write a js file that supports both require syntax and import syntax

export const funA = () => {
    
    } ; // 函数funA 
export const funB = () => {
    
    } ; // 函数funB

changed to

const funA = () => {
    
    } ; // 函数funA 
const funB = () => {
    
    } ; // 函数funB
module.exports = {
    
    
  funA:funA,
  funB:funB,
}

run serve.js

const Utils = require('./util')

Nodejs originally supports the modular specification of commonjs, which is the type of require.
If you want to use the modular specification of es6 export import, start the method:
modify the file to the mjs suffix, or modify the type: module in package.json

Rollup packaging attention

Why Use Rollup Packaging Tool

Rollup.js is a module packaging tool that can help you start from an entry file and package all used module files into a final release file (very suitable for building a tool library, which is also packaged with rollup Reason)
Rollup.js has two important features, one of which is that it uses the ES6 module standard, which means that you can directly use import and export without introducing babel (of course, in current projects, babel can be said to be A must-use tool)
An important feature of Rollup.js is called 'tree-shaking', which can help you remove useless code (that is, code that is not used) from the final generated file. (This feature is based on static analysis of ES6 modules, that is to say, variables with only export but no import will not be packaged into the final code)

Problem 1 reports an error SyntaxError: Unexpected character '!'

The first line of the entry file must add #!/usr/bin/env node to use Rollup to package and report errors. You can configure this code in rollup.config.js.
Two attributes of the rollup configuration file: banner and footer, these two attributes will be generated in the file You can add a new parameter banner by inserting a custom string at the beginning and end of :'#!/usr/bin/env node'

Question 2 error [!] Error: 'default' is not exported by ****

Rollup does not support CommonJS modules by default. You can try to avoid using the syntax of CommonJS modules when writing your own, but some external libraries are cjs or umd (packaged by webpack), so using these external libraries requires support for CommonJS modules that can be
imported rollup-plugin-commonjs processing

Question 3 error (!) Unresolved dependencies

  1. Help Rollup find external modules import rollup-plugin-node-resolve
  2. external attribute: use rollup packaging, we need to use third-party libraries in our own library, such as fs, path, etc., and do not want fs, path to appear in the final generated packaging file

Configure rollup.config.js

import json from 'rollup-plugin-json';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import del from 'rollup-plugin-delete'
import {
    
     dependencies } from './package.json'
const external = Object.keys(dependencies || '')
export default {
    
    
  input: './src/gfe-cli.js',
  output: {
    
    
    file: './dist/gfe-cli.mjs',
    banner: '#!/usr/bin/env node'
  },
  external:[...external, 'fs', 'path'],
  plugins: [
    del({
    
    
      targets: 'dist/*'
    }),
    commonjs(),
    resolve({
    
    
      modulesOnly: true,
      preferBuiltins: true
    }),
    json()
  ],
};

npm publish note

After npm successfully publishes the private library, the download address cannot be found?

Reason: The address of the private library is not configured for download.
For example: the scaffolding is **@gfe/n-cli**, which needs to be installed globally - g needs to be
configured globally: open cmd and automatically locate C:\Users\55409>
in the 55409 folder Configure the .npmrc file in

@gfe:registry=https://****/
registry=https://registry.npm.taobao.org/

@gfe needs to correspond to the address of the private library, and the rest of the internal dependencies go to npm public

Guess you like

Origin blog.csdn.net/gaojinbo0531/article/details/129435594