Take notes from the MOOCs Web Front-end Architecture course.

Introduction to Scaffolding

What is scaffolding?

Scaffolding is essentially an operating system client that executes from the command line, such as:

vue create vue-test-app
Copy the code

The command above consists of three parts:

  • Lord commands: vue
  • command:create
  • The command param: vue – test – app

The execution principle of scaffolding

The execution principle of scaffolding is as follows:

  • Enter vue create vue-test-app on the terminal
  • The terminal parses the Vue command
  • The terminal finds the vue command in the environment variable
  • Finally, link to the actual file vue.js according to the vue command
  • The terminal uses Node to execute vue.js
  • Vue. Js parsing the command/options
  • Vue. Js, execute the command
  • Exit after the command is executed

The implementation principle of scaffolding

Why global installation@vue/cliThe command to be added isvue?

Vue -> vue -> vue -> vue -> vue -> vue -> vue -> vue -> vue -> vue -> vue /lib/node_modules/@vue/cli/bin/vue.js,vue soft connects to the global installation directory to execute vue.js, where is this binding determined? We enter… /lib/node_modules/@vue/cli/, check package.json, we can see “bin”: {“vue”: “bin/vue

Global installation@vue/cliWhat happened when?

npm install -g @vue/cli
Copy the code

NPM first downloads our package to node_modules. If it is a globally installed node, it may be stored in /uer/lib/. When the package is completely downloaded, it will parse the package in package.json. It will create a soft connection in the bin directory of our Node installation directory.

performvueWhat happened when you ordered it? whyvuePoints to thejsDocuments, we can just go throughvueCommand to execute it?

The first problem: when executing a vue, our operating system will use which vue to find the file in the bin directory to execute, that is to say, go to the environment variable to find whether the vue is registered, and execute it once it is registered.

Second question: because it was added at the top of the file! /usr/bin/env node environment variable, which allows us to execute it with a fixed name

To expand on the differences between the following two ways:

  #!/usr/bin/env node
  #!/usr/bin/node
Copy the code

The first is to look for node in an environment variable

The second option is to execute node directly from the /usr/bin/directory

The role of scaffolding

The core goal of scaffolding development is to improve front-end development efficiency

The core value of

  • Automation: duplicate project code copy/Git operation/publish online operation
  • Standardization: project creation/Git flow/ release process/rollback process
  • Datalization: the research and development process is systematized and datalized, making the research and development process quantifiable

Scaffolding development difficulties analysis

  • Subcontracting: The division of a complex system into modules
  • Command registration:
vue create
vue add
vue invoke
Copy the code
  • Parameter analysis:
    • The options for:--version,--help
    • The options abbreviations:-V,-h
    • Options with params: -path XXXX
  • Help document
  • Command line interaction
  • Log print
  • Command line text changes color
  • Network communication :HTTP/WebSocket
  • File processing

And so on…

Scaffolding local link standard procedure

Link to local scaffolding:

cd your-cli-dir
npm link
Copy the code

Link local library files:

cd your-cli-dir
npm link
cd your-cli-dir
npm link your-lib
Copy the code

Unlink local library files:

CD your-cli-dir NPM unlink CD your-cli-dir # link NPM unlink your-lib # link no rm -rf node_modules NPM install -s your-libCopy the code

Understand the NPM link:

  • npm link your-lib: Add to the current projectnode_modulesUnder the specified library file link tonodeglobalnode_modulesUnder the library file
  • npm link: links the current project tonodeglobalnode_modulesAs a library file, and parsebinConfigure to create executable files

Understand the NPM unlink:

  • npm unlink: Removes the current project fromnodeglobalnode_modulesRemove the
  • npm unlink your-lib: Removes library file dependencies from the current project

Analysis of the pain points of native scaffold development

  • Pain point 1: repeat operation
    • Multi-package local link
    • Multiple packages depend on installation
    • Multiple Package unit tests
    • Multiple Package code commits
    • Multiple Package code releases
  • Pain point 2: version consistency issues
    • Release consistency
    • Interdependent versions are upgraded after release

Analyze the pain points, and there will be a solution, which is to manage multiple packages through Lerna.

Lerna profile

Lerna is an optimized git+ NPM based multi-package project management tool. Lerna is used to manage large projects such as Babel, VUe-CLI, craette-react-app, etc.

Realize the principle of

  • The local lerna command is preferentially invoked through import-local
  • Scaffolding is generated through Yargs, global properties are registered, commands are registered, and parameters are parsed through the parse method
  • Lerna command registration requires two methods: Builder and handler. The Builder method is used to register the options exclusive to the command, and the handler is used to process the logic of command services
  • Lerna implements local development by configuring NPM local dependencies by writing to package.json dependencies:file:your-local-module-pathIn thelerna publicWill automatically replace the path

advantage

  • Significantly reduce repetitive operations
  • Improve standardization of operations

Learn Develop the scaffolding process

Scaffolding development

Before developing scaffolding, we first understand the flow chart of scaffolding development.

Scaffolding diagram

Scaffolding unpacking strategy

  • Core process: Core
  • Command: commands
    • Initialize the
    • release
    • Clear the cache
  • The model layer: models
    • The Command Command
    • The Project Project
    • Component components
    • Npm module
    • Git repository
  • Supported module :utils
    • Git operation
    • Cloud build
    • Utility methods
    • API request
    • Git API

Command Execution Process

  • Preparation stage

  • The command register

  • Command execution

Preparation stage

  • Checking the Version number
// Check the version
function checkPkgVersion() {
    log.info('cli', pkg.version);
}
Copy the code
  • Checking the Node Version
// Check the node version
checkNodeVersion() {
    // First, get the current Node version number
    const currentVersion = process.version;
    const lastVersion = LOWEST_NODE_VERSION;
    // Step 2, compare the lowest version number
    if(! semver.gte(currentVersion, lastVersion)) {throw new Error(colors.red('roy-cli-dev needs to install v${lastVersion}Previous versions of Node.js)); }}Copy the code
  • Checking root Permissions
// Check root startup
function checkRoot() {
    // The root account will be demoted to a user account
    const rootCheck = require('root-check');
    rootCheck();
}
Copy the code
  • Check the user home directory
// Check the user home directory
function checkUserHome() {
    if(! userHome || ! pathExists(userHome)) {throw new Error(colors.red('Current login user home directory does not exist!! ')); }}Copy the code
  • Check into the reference
// Check the input parameter
function checkInputArgs() {
    const minimist = require('minimist');
    args = minimist(process.argv.slice(2));
    checkArgs();
}

function checkArgs() {
    if (args.debug) {
        process.env.LOG_LEVEL = 'verbose';
    } else {
        process.env.LOG_LEVEL = 'info';
    }
    log.level = process.env.LOG_LEVEL;
}
Copy the code
  • Checking Environment Variables
// Check environment variables
function checkEnv() {
    const dotenv = require('dotenv');
    const dotenvPath = path.resolve(userHome, '.env');
    if (pathExists(dotenvPath)) {
        config = dotenv.config({
            path: dotenvPath
        });
    }
    createDefaultConfig();
    log.verbose('Environment variables', process.env.CLI_HOME_PATH);
}

function createDefaultConfig() {
    const cliConfig = {
        home: userHome
    }
    if (process.env.CLI_HOME) {
        cliConfig['cliHome'] = path.join(userHome, process.env.CLI_HOME);
    } else {
        cliConfig['cliHome'] = path.join(userHome, constants.DEFAULT_CLI_HOME);
    }
    process.env.CLI_HOME_PATH = cliConfig.cliHome;
}
Copy the code
  • Check whether it is the latest version
// Check if it is the latest version and needs to be updated
async function checkGlobalUpdate() {
    //1. Obtain the current version and module name
    const currentVersion = pkg.version;
    const npmName = pkg.name;
    //2. Call NPM API to get all version numbers
    const { getNpmSemverVersion } = require('@roy-cli-dev/get-npm-info');
    //3. Extract all version numbers and compare which version numbers are greater than the current version
    const lastVersion = await getNpmSemverVersion(currentVersion, npmName);
    if (lastVersion && semver.gt(lastVersion, currentVersion)) {
        //4. Obtain the latest version number and prompt the user to update the version
        log.warn(colors.yellow('Please update manually${npmName}, current version:${currentVersion}, latest version:${lastVersion}Update command: NPM install -g${npmName}`))}}Copy the code

The command register

Register init phase

// Registration of names
function registerCommand() {
    program
        .name(Object.keys(pkg.bin)[0])
        .usage('<command> [options]')
        .version(pkg.version)
        .option('-d, --debug'.'Debug mode enabled'.false)
        .option('-tp, --targetPath <targetPath>'.'Whether to specify a local debug file path'.' ');

    program
        .command('init [projectName]')
        .option('-f, --force'.'Force initialization of the project')
        .action(init); //init parses a single command exec dynamically loads the module


    // Enable the debug mode
    program.on('option:debug'.function () {
        if (program.debug) {
            process.env.LOG_LEVEL = 'verbose';
        } else {
            process.env.LOG_LEVEL = 'info';
        }
        log.level = process.env.LOG_LEVEL;
        log.verbose('test');
    });

    / / specified targetPath
    program.on('option:targetPath'.function () {
        process.env.CLI_TARGET_PATH = program.targetPath;
    });

    // Listen for unknown commands
    program.on('command:*'.function (obj) {
        const availabelCommands = program.commands.map(cmd= > cmd.name());
        log.verbose(colors.red('Unknown command :' + obj[0]));
        if (availabelCommands.length > 0) {
            log.verbose(colors.blue('Available commands :' + availabelCommands.join(', ')));
        }
    })

    program.parse(process.argv);
    // When the user does not enter a command
    if (program.args && program.args.length < 1) {
        program.outputHelp();
        console.log(); }}Copy the code

Current Architecture Diagram

Through the preparation phase and command initialization init phase, we create the following packages:

This architecture can meet the requirements of general scaffolding, but there are two problems:

1. Slow CLI installation: All packages are integrated in the CLI. Therefore, the CLI installation speed is slowed down when there are too many commands

2. Poor flexibility: the init command can only use the @roy-cli-dev/init package. For group companies, the init command of each BU may be different, and the init command may need to be dynamic, for example:

  • Team A uses @roy-cli-dev/init as the initialization template
  • Team B uses its own @roy-cli-dev/my-init initialization template
  • Team C uses its own @roy-cli-dev/your-init initialization template

This presents a challenge to our architectural design, requiring us to be able to dynamically load the init module, which increases the complexity of the architecture but greatly increases the scalability of the scaffolding, decoupling the scaffolding framework from the business logic

Scaffolding architecture optimization

Command execution phase

const SETTINGS = {
    init: "@roy-cli-dev/init",}const CACHE_DIR = 'dependencies/';

async function exec() {
    let targetPath = process.env.CLI_TARGET_PATH;
    const homePath = process.env.CLI_HOME_PATH;
    let storeDir = ' ';
    let pkg;
    log.verbose('targetPath', targetPath);
    log.verbose('homePath', homePath);
    const cmdObj = arguments[arguments.length - 1];
    const cmdName = cmdObj.name();
    const packageName = SETTINGS[cmdName];
    const packageVersion = 'latest';

    if(! targetPath) {// Whether to execute native code
        // Generate the cache path
        targetPath = path.resolve(homePath, CACHE_DIR);
        storeDir = path.resolve(targetPath, 'node_modules');
        log.verbose(targetPath, storeDir);
        // Initialize the Package object
        pkg = new Package({
            targetPath,
            storeDir,
            packageName,
            packageVersion
        });
        // Check whether Package exists
        if (await pkg.exists()) {
            / / update the package
            await pkg.update()
        } else {
            / / install package
            awaitpkg.install(); }}else {
        pkg = new Package({
            targetPath,
            packageName,
            packageVersion
        });
    }
    // Get the entry file
    const rootFile = pkg.getRootFile();
    if (rootFile) {// Check whether the entry file exists
        try {
            // called in the current process
            // require(rootFile).call(null, Array.from(arguments));
            // called in the node child process
            const args = Array.from(arguments);
            const cmd = args[args.length - 1];
            const o = Object.create(null);
            Object.keys(cmd).forEach(key= >{
                if(cmd.hasOwnProperty(key) && ! key.startsWith('_') && key ! = ='parent') {
                    o[key] = cmd[key];
                }
            })
            args[args.length - 1] = o;
            const code = `require('${rootFile}').call(null, The ${JSON.stringify(args)}) `;
            const child = spawn('node'['-e',code],{
                cwd:process.cwd(),
                stdio:'inherit'
            });
            // Execution generates an exception
            child.on('error'.e= >{
                log.error(e.message);
                process.exit(1);
            });
            // Exit after the command is executed
            child.on('exit'.e= >{
                log.verbose('Command executed successfully :'+e); process.exit(e); })}catch(e) { log.error(e.message); }}//1.targetPath -> modulePath
    //2. ModulePath -> Package(NPM module)
    //3.Package.getRootFile
    //4.Package.update/Package.install
}
Copy the code

Scaffolding project creation functional design

First of all, we need to think about what scaffolding projects are created for:

  • Scalability: Ability to quickly reuse to different teams and adapt to the differences between different teams
  • Low cost: The ability to add templates without changing the scaffolding source code, and the cost of adding templates is very low
  • High performance: Controls storage space and makes full use of Node multiple processes to improve installation performance

Create functional architecture plans for the project

The whole process is divided into three stages:

  • Preparation stage

  • Download the module

  • Install the module

Preparation stage

The core work of the preparation phase is:

  • Ensure the installation environment for the project
  • Confirm basic project information

Download the module

Downloading modules is a quick way to implement functionality using the packaged Package class

Install the module

Modules can be installed in standard mode and custom mode:

  • In standard mode, module rendering is implemented through EJS, and dependencies are automatically installed and projects are started
  • In custom mode, users are allowed to take the initiative to implement the module installation process and subsequent startup process

The core code is as follows:

class InitCommand extends Command {
    init() {
        this.projectName = this._argv[0] | |' ';
        this.force = this._cmd.force;
        log.verbose(this._argv);
        log.verbose('projectName'.this.projectName);
        log.verbose('force'.this.force);
    }
    async exec() {
        try {
            //1. Preparation stage
            const projectInfo = await this.prepare();
            if (projectInfo) {
                //2. Download the template
                log.verbose('projectInfo', projectInfo);
                this.projectInfo = projectInfo
                await this.downloadTemplate();
                //3. Install the template
                await this.installTemplate(); }}catch (e) {
            log.error(e.message);
            if (process.env.LOG_LEVEL === 'verbose') {
                console.log(e); }}}async installTemplate() {
        log.verbose('templateInfo'.this.templateInfo);
        if (this.templateInfo) {
            if (!this.templateInfo.type) {
                this.templateInfo.type = TEMPLATE_TYPE_NORMAL
            }
            if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
                // Standard installation
                await this.installNormalTemplate();
            } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
                // Custom installation
                await this.installCustomTemplate();
            } else {
                throw new Error('Cannot fail project Template Class'); }}else {
            throw new Error('Project template information does not exist'); }}checkCommand(cmd) {
        if (WHITE_COMMAND.includes(cmd)) {
            return cmd;
        }
        return null;
    }

    async execCommand(command, errMsg) {
        let ret;
        if (command) {
            const cmdArray = command.split(' ');
            const cmd = this.checkCommand(cmdArray[0]);
            if(! cmd) {throw new Error('Command does not exist! Command: ' + command);
            }
            const args = cmdArray.slice(1);
            ret = await execAsync(cmd, args, {
                stdio: 'inherit'.cwd: process.cwd(),
            })
        }
        if(ret ! = =0) {
            throw new Error(errMsg)
        }
    }

    async ejsRender(options) {
        const dir = process.cwd();
        const projectInfo = this.projectInfo;
        return new Promise((resolve, reject) = > {
            glob('* *', {
                cwd: dir,
                ignore: options.ignore || ' '.nodir: true,},(err, files) = > {
                if (err) {
                    reject(err);
                }
                Promise.all(files.map(file= > {
                    const filePath = path.join(dir, file);
                    return new Promise((resolve1, reject1) = > {
                        ejs.renderFile(filePath, projectInfo, {}, (err, result) = > {
                            console.log(result);
                            if (err) {
                                reject1(err);
                            } else{ fse.writeFileSync(filePath, result); resolve1(result); }})}); })).then(() = > {
                    resolve();
                }).catch(err= >{ reject(err); }); })})}async installNormalTemplate() {
        // Copy the template code to the current directory
        let spinner = spinnerStart('Installing template');
        log.verbose('templateNpm'.this.templateNpm)
        try {
            const templatePath = path.resolve(this.templateNpm.cachFilePath, 'template');
            const targetPath = process.cwd();
            fse.ensureDirSync(templatePath);// Ensure that the current file does not exist, otherwise it will be created
            fse.ensureDirSync(targetPath);
            fse.copySync(templatePath, targetPath);// Copy the template from the cache directory to the current directory
        } catch (e) {
            throw e;
        } finally {
            spinner.stop(true);
            log.success('Template installed successfully');
        }
        const templateIgnore = this.templateInfo.ignore || [];
        const ignore = ['**/node_modules/**'. templateIgnore];await this.ejsRender({ ignore });
        // Depends on the installation
        const { installCommand, startCommand } = this.templateInfo
        await this.execCommand(installCommand, 'Dependency setup failed');
        // Start command execution
        await this.execCommand(startCommand, 'Failed to start execution command');
    }
    async installCustomTemplate() {
        // Query the entry file of the user-defined template
        if (await this.templateNpm.exists()) {
            const rootFile = this.templateNpm.getRootFile();
            if (fs.existsSync(rootFile)) {
                log.notice('Start executing custom templates');
                constoptions = { ... this.options,cwd:process.cwd(),
                }
                const code = `require('${rootFile}'),The ${JSON.stringify(options)}) `;
                log.verbose('code',code);
                await execAsync('node'['-e', code], { stdio: 'inherit'.cwd: process.cwd()});
                log.success('Custom template installed successfully');
            } else {
                throw new Error('Custom template entry file does not exist'); }}}async downloadTemplate() {
        //1. Obtain project template information from the project template API
        //1.1 Build a back-end system through egg.js
        //1.2 Store project templates through NPM
        //1.3 Store project template information to mongodb database
        //1.4 Get mongodb data from egg.js and return it via API
        const { projectTemplate } = this.projectInfo;
        const templateInfo = this.template.find(item= > item.npmName === projectTemplate);
        const targetPath = path.resolve(userHome, '.roy-cli-dev'.'template');
        const storeDir = path.resolve(userHome, '.roy-cli-dev'.'template'.'node_modules');
        const { npmName, version } = templateInfo;
        this.templateInfo = templateInfo;
        const templateNpm = new Package({
            targetPath,
            storeDir,
            packageName: npmName,
            packageVersion: version
        })
        if (! await templateNpm.exists()) {
            const spinner = spinnerStart('Downloading templates... ');
            await sleep();
            try {
                await templateNpm.install();
            } catch (e) {
                throw e;
            } finally {
                spinner.stop(true);
                if (templateNpm.exists()) {
                    log.success('Template download successful');
                    this.templateNpm = templateNpm; }}}else {
            const spinner = spinnerStart('Updating the template... ');
            await sleep();
            try {
                await templateNpm.update();
            } catch (e) {
                throw e;
            } finally {
                spinner.stop(true);
                if (templateNpm.exists()) {
                    log.success('Template updated successfully');
                    this.templateNpm = templateNpm; }}}}async prepare() {
        // Check whether the project template exists
        const template = await getProjectTemplate();
        if(! template || template.length ===0) {
            throw new Error('Project template does not exist');
        }
        this.template = template;
        //1. Check whether the current directory is empty
        const localPath = process.cwd();
        if (!this.isDirEmpty(localPath)) {
            let ifContinue = false;
            if (!this.force) {
                // Ask if you want to continue
                ifContinue = (await inquirer.prompt({
                    type: 'confirm'.name: 'ifContinue'.default: false.message: 'The current folder is not empty. Do you want to continue creating the project? '
                })).ifContinue;
                if(! ifContinue) {return; }}//2. Determine whether to enable forcible update
            if (ifContinue || this.force) {
                // Give the user a second confirmation
                const { confirmDelete } = await inquirer.prompt({
                    type: 'confirm'.name: 'confirmDelete'.default: false.message: 'Are you sure you want to clear the files in the current directory? ',})if (confirmDelete) {
                    // Clear the current directory
                    fse.emptyDirSync(localPath)
                }
            }
        }
        return this.getProjectInfo();

        //3. Select create project or component
        //4. Obtain the basic information of the project

    }
    async getProjectInfo() {

        function isValidName(v) {
            return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v);
        }

        let projectInfo = {};
        let isProjectInfoValid = false;
        if (isValidName(this.projectName)) {
            isProjectInfoValid = true;
            projectInfo.projectName = this.projectName;
        }
        
        //1. Select create project or component
        const { type } = await inquirer.prompt({
            type: 'list'.name: 'type'.message: 'Please select an initialization type'.default: TYPE_PROJECT,
            choices: [{
                name: 'the project'.value: TYPE_PROJECT
            }, {
                name: 'components'.value: TYPE_COMPONENT
            }]
        });
        log.verbose('type', type);
        this.template = this.template.filter(template= > {
            return template.tag.includes(type);
        })
        const title = type === TYPE_PROJECT ? 'the project' : 'components';
        //2. Obtain basic project information
        const projectNamePrompt = {
            type: 'input'.name: 'projectName'.message: ` please enter${title}The name of the `.default: ' '.validate: function (v) {
                const done = this.async();
                setTimeout(function () {
                    //1. The first character must be an English character
                    //2. The last character must be a number or an English character
                    //3. Characters run only "-_"
                    //\w = a-za-z0-9 * indicates zero or more entries
                    if(! isValidName(v)) { done('Please enter a valid one${title}The name `);
                        return;
                    }
                    done(null.true);
                }, 0);
            },
            filter: function (v) {
                returnv; }}let projectPrompt = [];
        if(! isProjectInfoValid) { projectPrompt.push(projectNamePrompt); } projectPrompt.push({input: 'input'.name: 'projectVersion'.message: ` please enter${title}The version number `.default: '1.0.0'.validate: function (v) {
                const done = this.async();
                setTimeout(function () {
                    //1. The first character must be an English character
                    //2. The last character must be a number or an English character
                    //3. Characters run only "-_"
                    //\w = a-za-z0-9 * indicates zero or more entries
                    if(! (!!!!! semver.valid(v))) { done('Please enter a valid version number');
                        return;
                    }
                    done(null.true);
                }, 0);
            },
            filter: function (v) {
                if(!!!!! semver.valid(v)) {return semver.valid(v);
                } else {
                    returnv; }}}, {type: 'list'.name: 'projectTemplate'.message: ` please select${title}Template `.choices: this.createTemplateChoices()
        });
        if (type === TYPE_PROJECT) {
            const project = awaitinquirer.prompt(projectPrompt); projectInfo = { ... projectInfo, type, ... project } }else if (type === TYPE_COMPONENT) {
            const descriptionPrompt = {
                input: 'input'.name: 'componentDescription'.message: 'Please enter component description information'.default: ' '.validate: function (v) {
                    const done = this.async();
                    setTimeout(function () {
                        //1. The first character must be an English character
                        //2. The last character must be a number or an English character
                        //3. Characters run only "-_"
                        //\w = a-za-z0-9 * indicates zero or more entries
                        if(! v) { done('Please enter component description information');
                            return;
                        }
                        done(null.true);
                    }, 0);
                }
            }
            projectPrompt.push(descriptionPrompt);
            const component = awaitinquirer.prompt(projectPrompt); projectInfo = { ... projectInfo, type, ... component } }//return basic information of the project (object)
        if (projectInfo.projectName) {
            projectInfo.className = require('kebab-case')(projectInfo.projectName).replace(/ ^ - /.' ');
        }
        if (projectInfo.projectVersion) {
            projectInfo.version = projectInfo.projectVersion;
        }
        if (projectInfo.componentDescription) {
            projectInfo.description = projectInfo.componentDescription;
        }
        return projectInfo;
    }

    isDirEmpty(localPath) {
        let fileList = fs.readdirSync(localPath);
        // File filtering logic
        fileList = fileList.filter(file= > (
            !file.startsWith('. ') && ['node_modules'].indexOf(file) < 0
        ));

        return! fileList || fileList.length <=0;
    }
    createTemplateChoices() {
        return this.template.map(item= > ({
            value: item.npmName,
            name: item.name
        }))
    }
}

function init(argv) {
    // console.log('init',projectName,cmdObj.force,process.env.CLI_TARGET_PATH);
    return new InitCommand(argv);
}


module.exports = init;
module.exports.InitCommand = InitCommand;
Copy the code

At this point we are done with scaffolding development and creating projects through scaffolding.

The attached

How to develop scaffolding with Yargs?

  • Scaffolding is divided into three parts (Vue Create Vuex)

    • Bin: The main command configates the bin property in package.json. NPM link is installed locally
    • Command: a command
    • Options: parameters (Boolean/string/number)
    • File top increment#! /usr/bin/env nodeThe purpose of this command is to tell the operating system to query the node command in the environment variable to execute the file
  • Scaffolding initialization process

    • Constructor :Yargs() (generate a scaffold by calling the Yargs constructor)
    • Common methods:
      • Yargs.options (Properties for registration scaffolding)
      • Yargs.option
      • Yargs.group (group scaffold properties)
      • Yargs. DemandCommand (specify the minimum number of commands to be sent)
      • Fargs. shamecommands (recommend the closest correct command for you after typing the incorrect command)
      • Yargs.strict (error message can be displayed after this function is enabled)
      • Yargs.fail (Listen for scaffolding anomalies)
      • Yargs.alias
      • Yargs.wrapper (width of command line tool)
      • Yargs.epilogus (tip at the bottom of the command line tool)
  • Scaffolding parameter parsing method

    • hideBin(process.argv)
    • Yargs.parse(argv, options)
  • Command registration method

    • Yargs.command(command,describe, builder, handler)
    • Yargs.command({command,describe, builder, handler})

Node.js module path resolution process

  • Node.js project module path resolution is passedrequire.resolveMethod to implement
  • require.resolveIt is throughModule._resolveFileNameMethod implemented
  • require.resolveImplementation principle:
    • Module._resolveFileNameThe core process of the method has three points:
      • Check whether it is a built-in module
      • throughModule._resolveLookupPathsThe node_modules () method generates possible paths to node_modules
      • throughModule._findPathQuery the real path of the module
    • Module._findPathThe core process has four points:
      • Query cache (pass request and Paths\x00(space) merged into cacheKey.)
      • Walk through paths to form path and Request into file path basePath
      • Called if basePath existsfs.realPathSyncObtain the real file path
      • Cache the real file path toModule._pathCache(Key is the cacheKey generated earlier)
    • fs.realPathSyncThe core process has three points:
      • Query cache (the cache key is P, i.eModule._findPathFile path generated in)
      • Traverses the path string from left to right/, split the path to determine whether the path is a soft connection. If so, query the real link and generate a new path P, and then continue to traverse. Here is one detail that needs to be paid attention to:
        • The subpath base generated during traversal is cached in knownHard and cache to avoid repeated query
      • After traversal, the real path corresponding to the module is obtained. At this time, the original path is used as key and the real path is used as value, which is saved in the cache
  • require.resolve.pathsIs equivalent toModule._resolveLoopupPathsThis method is used to get the possible paths of all node_modules
  • require.resolve.pathsImplementation principle: