Teach you to write a scaffold hand in hand

After three months, I finally have time to write the second article of scaffold series. Working in Beijing is really much busier than Tianjin. I don't have time to fish. If you haven't read the first article in this series Teach you to write a scaffold hand in hand , it is suggested to read this article again first, and the effect is better.

Mini cli project GitHub address: https://github.com/woai3c/mini-cli

The code of v3 version is in v3 branch, and the code of v4 version is in v4 branch.

Third version v3

The third version mainly adds two functions:

  1. How the project is organized into monorepo.
  2. Add command. You can add plug-ins by mvc add xxx command.

monorepo

First, let's take a brief look at monorepo and multirepo. They are all a way of project management. Multirepo is to maintain different projects in different git warehouses, while monorepo maintains multiple projects in the same git warehouse. In v3, I want to transform Mini cli into monorepo, and maintain different plug-ins as independent projects.

After transforming the project into monorepo, the directory is as follows:

├─packages
│  ├─@mvc
│  │  ├─cli # Core plug-in
│  │  ├─cli-plugin-babel # babel plugin
│  │  ├─cli-plugin-linter # Linker plug-in
│  │  ├─cli-plugin-router # router plug-in
│  │  ├─cli-plugin-vue # vue plug-in
│  │  ├─cli-plugin-vuex # vuex plugin
│  │  └─cli-plugin-webpack # webpack plugin
└─scripts # The commit message validation script has nothing to do with the project and needs no attention
│─lerna.json
|─package.json

monorepo transformation process

Global install lerna

npm install -g lerna

Create project

git init mini-cli

initialization

cd mini-cli
lerna init

Create package

lerna create xxx

Since cli is the core code of scaffold, you need to call other plug-ins here, because you need to add other plug-ins to the dependency of @ mvc/cli

# If it is added to devDependencies, you need to add -- dev after it
# Downloading third-party dependencies is the same command
lerna add @mvc/cli-plugin-babel --scope=@mvc/cli

There is no difference between the second version of repo and the subsequent version of pomon plug-in, which can be transformed into a separate plug-in.

Advantages of using monorepo

  1. If it is developed in the way of multirepo, if you need to call other plug-ins during local debugging, you need to execute npm i installation before using it. Using monorepo has no such trouble. You can directly call other plug-ins in the packages directory to facilitate development and debugging.
  2. If multiple plug-ins have been modified, the modified plug-ins can be published at the same time when lerna publish is executed, instead of each being published separately.

add command

The purpose of transforming the project into monorepo is to facilitate subsequent expansion. For example, the generated project originally does not support router. If you suddenly want to add router function halfway, you can execute the command mvc add router to add Vue router dependency and related template code.

Let's first look at the code of the add command:

const path = require('path')
const inquirer = require('inquirer')
const Generator = require('./Generator')
const clearConsole = require('./utils/clearConsole')
const PackageManager = require('./PackageManager')
const getPackage = require('./utils/getPackage')
const readFiles = require('./utils/readFiles')

async function add(name) {
    const targetDir = process.cwd()
    const pkg = getPackage(targetDir)
    // Empty console
    clearConsole()

    let answers = {}
    try {
        const pluginPrompts = require(`@mvc/cli-plugin-${name}/prompts`)
        answers = await inquirer.prompt(pluginPrompts)
    } catch (error) {
        console.log(error)
    }

    const generator = new Generator(pkg, targetDir, await readFiles(targetDir))
    const pm = new PackageManager(targetDir, answers.packageManager)
    require(`@mvc/cli-plugin-${name}/generator`)(generator, answers)

    await generator.generate()
    // Download dependency
    await pm.install()
}

module.exports = add

Since v3 version is still developed locally, relevant plug-ins are not released to npm, because plug-ins can be directly referenced without npm i installation. When the v2 version executes the create command to create a project, all interactive prompts are placed under the cli plug-in, but the add command adds a plug-in separately, so you need to add a prompt under each plug-in JS file (if not needed, it can be omitted), which contains some statements that interact with users. For example, when adding a router plug-in with the add command, you will be asked whether to select the history mode.

const chalk = require('chalk')

module.exports = [
    {
        name: 'historyMode',
        type: 'confirm',
        message: `Use history mode for router? ${chalk.yellow(`(Requires proper server setup for index fallback in production)`)}`,
        description: `By using the HTML5 History API, the URLs don't need the '#' character anymore.`,
    },
]

It can be seen from the code logic of the add command that if the newly added plug-in has prompts JS file will read the code and pop up interactive statements. Otherwise, skip and download directly.

Fourth version v4

v4 version mainly makes the dev and build functions of webpack dynamic. The original scaffold generated project has a build directory, which contains some configuration codes of webpack. The project generated by scaffold in v4 version does not have a build directory.

This function is realized through the new MVC cli service plug-in. The generated project will have the following two script commands:

scripts: {
    serve: 'mvc-cli-service serve',
    build: 'mvc-cli-service build',
},

When npm run serve is run, the command MVC cli service serve is executed. The code of this block is as follows:

#!/usr/bin/env node
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const devConfig = require('../lib/dev.config')
const buildConfig = require('../lib/pro.config')

const args = process.argv.slice(2)
if (args[0] === 'serve') {
    const compiler = webpack(devConfig)
    const server = new WebpackDevServer(compiler)

    server.listen(8080, '0.0.0.0', err => {
        console.log(err)
    })
} else if (args[0] === 'build') {
    webpack(buildConfig, (err, stats) => {
        if (err) console.log(err)
        if (stats.hasErrors()) {
            console.log(new Error('Build failed with errors.'))
        }
    })
} else {
    console.log('error command')
}

The principle is as follows( npm scripts User Guide):

The principle of npm script is very simple. Whenever npm run is executed, a new Shell will be automatically created and the specified script commands will be executed in this Shell. Therefore, any command that can be run by Shell (generally Bash) can be written in npm script.

In particular, the Shell created by npm run will delete the node of the current directory_ modules/. Add the PATH variable to the bin subdirectory. After execution, restore the PATH variable as it is.

The above code judges the executed command. If it is serve, start the development environment with a new WebpackDevServer instance. If it's build, use webpack to package.

The webpack configuration of Vue cli is dynamic. chainwebpack is used to dynamically add different configurations. My demo is written directly, mainly because I don't have time, so I don't study it further.

After publishing to npm

Download Mini cli scaffold, in fact, only the core plug-in MVC cli is downloaded. If this plug-in needs to reference other plug-ins, it needs to be installed before calling. Therefore, some modifications need to be made to the create add command. Let's take a look at the changes to the create command code:

answers.features.forEach(feature => {
    if (feature !== 'service') {
        pkg.devDependencies[`mvc-cli-plugin-${feature}`] = '~1.0.0'
    } else {
        pkg.devDependencies['mvc-cli-service'] = '~1.0.0'
    }
})

await writeFileTree(targetDir, {
    'package.json': JSON.stringify(pkg, null, 2),
})

await pm.install()

// According to the options selected by the user, load the corresponding module in package JSON writes the corresponding dependency
// And render the corresponding template module
answers.features.forEach(feature => {
    if (feature !== 'service') {
        require(`mvc-cli-plugin-${feature}/generator`)(generator, answers)
    } else {
        require(`mvc-cli-service/generator`)(generator, answers)
    }
})

await generator.generate()

// Download dependency
await pm.install()

The above code is the newly added logic. After the user selects the required plug-ins, write these plug-ins to the pkg object, and then generate package JSON file, and then execute npm install to install dependencies. After installing the plug-ins, read the generator directory / file code of each plug-in to generate templates or add different dependencies again. Then perform the installation again.

Publishing encountered pits

The plug-in of v3 version has a prefix @ mvc. Since the npm package with @ prefix will be used as a private package by default, some pitfalls have been encountered. It took a long time. Later, I was too lazy to do it. I simply changed the prefix name of all plug-ins to the prefix beginning with mvc.

reference material

Keywords: Front-end

Added by themire on Tue, 08 Feb 2022 16:17:08 +0200