Based on Webpack 4.x project actual combat 1- simple use

Based on Webpack 4.x project actual combat 2 – configuration once, multiple projects run

Based on Webpack 4.x project combat 3 – handwriting a CLI


preface

Having written about webpack-Multi configuration, CSS preprocessor uses less, today we will continue this series of articles by writing a CLI tool. So this article isn’t really webpack, it’s about writing your own CLI tool, but it’s based on the previous two articles, so it’s in this series.

Let’s refer to vue-CLI to learn how to write cli tools.

Let’s take a look at vue-CLI:

  1. npm install -g vue-cli
  2. vue init webpack testTest is your project name and then there will be some configuration questions and answers
Project name test: Project name, if not changed, the originaltestName Project Description A vue. js Project: indicates the Project description. Alternatively, click Enter and use the default name Author: Author Vue build standalone: Install vue-router? Install vue-router Use ESLint to lint your code? Pick an ESLint management code Pick an ESLint Standard: Choose an ESLint preset, the code style for writing vue projects Set up unit tests Yes: Install unit tests Pick atestrunner jest Setup e2e tests with Nightwatch? Should we run NPM installforyou after the project has been created? (recommended) NPM: Whether to help you 'NPM install'Copy the code

Referring to vue-CLI, our scaffolding is called Webpack-multi-CLI and is built based on webpack4.x project Live 2 – configuration once, multiple projects running this demo.

When done, our command is similar to vue-CLI

  1. The installationnpm install -g webpack-multi-cli
  2. usenpm init project-name, the following configuration choices appear
Project name: Project description A webpack-Multi Project: Project description A webpack-multi Project: Author Pick a CSS preprocessor? Select a CSS preprocessor. The value can be less or sass Should we run NPM installforyou after the project has been created? (recommended) NPM: whether to help you 'NPM install', if you enter NPM command, then help you to execute NPM installCopy the code

Begin to build

Before we can start building, we need to install some NPM packages:

Chalk: color output figlet: generate character pattern inquirer: Inquirer command line user interface commander Custom command fs-extra file operation ORA Make loop effect Promise-exec execute subprocess

Write a package.json file

Take a look at our final package.json file

{
  "name": "webpack-multi-cli"."version": "1.0.0"."description": "create webpack-multi cli"."main": "index.js"."bin": {
    "webpack-multi-cli": "bin/index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git"."url": "git+https://github.com/xianyulaodi/webpack-multi-cli.git"
  },
  "author": "xianyulaodi"."license": "MIT"."bugs": {
    "url": "https://github.com/xianyulaodi/webpack-multi-cli/issues"
  },
  "dependencies": {
    "chalk": "^" 2.4.2."commander": "^ 2.20.0"."figlet": "^ 1.2.1." "."fs-extra": "^" 7.0.1."inquirer": "^ 5.2.0." "."ora": "^ 3.4.0"."promise-exec": "^ 0.1.0 from"
  },
  "homepage": "https://github.com/xianyulaodi/webpack-multi-cli#readme"
}
Copy the code

Our entry file is bin/index.js, which is similar to vue-cli init project name. Our initial command is webpack-multi-cli init project name, so the bin of package.json needs to be written like this

."bin": {
   "webpack-multi-cli": "bin/index.js"}..Copy the code

Writing the init command

Next, write a custom init command that relies on the commander library, lib/cmd.js

#! /usr/bin/env node

const program = require("commander");
const path = require('path');
const currentPath = process.cwd();  // Current directory path
const fs = require('fs-extra');
let projectName = null;


// Define directives
program
  .version(require('.. /package.json').version)
  .command('init <name>')
  .action(function (name) {
    projectName = name;
  });
  program.parse(process.argv);
  program.projectName = projectName;

if(! projectName) {console.error('no project name was given, eg: webpack-multi-cli init myProject');
  process.exit(1);
}

if (fs.pathExistsSync(path.resolve(currentPath, `${projectName}`))) {
  console.error('The name of the project you created:${projectName}It already exists, failed to create, please modify the project name and try again);
  process.exit(1);
}

module.exports = program;

Copy the code

If no project name is passed in, or if one already exists, an exception is thrown and the program ends, saving the project name as a default name. Remember our orders? No,

Project name test:// The project name is the same as the original test name
Copy the code

This is where the default project name comes from

Writing interaction questions

This relies on the inquirer library

lib/inquirer.ja

const inquirer = require('inquirer');
const cmd = require('./cmd');
const projectName = cmd.projectName;

module.exports = {

    getQuestions: (a)= > {

        const questions = [
            {
                name: 'projectName'.type: 'input'.message: 'Project name'.default: projectName
            },
            {
                name: 'description'.type: 'input'.message: `Project description`.default: 'A webpack-multi project'
            },
            {
                name: 'author'.type: 'input'.message: `Author`}, {name: 'cssPreprocessor'.type: 'list'.message: 'Pick an css preprocessor'.choices: [
                    "less"."sass"] {},name: 'isNpmInstall'.type: 'input'.message: 'Should we run `npm install` for you after the project has been create? 
      
       '
      ,}];returninquirer.prompt(questions); }},Copy the code

The main is some of our interaction questions, the specific use of this library, you can Google

Master file

bin/index.js

The idea here is to create a new template directory to store our WebPack configuration template, write the questions you entered, such as project name, author, description, choose less or sass, etc., into these configuration files, and then download them to the root directory where you executed the command

Another idea is to grab the interaction problem you came back to, grab the file from Github, and download it to the root directory where you executed the command

Once you know the idea, it’s a little bit easier

Start by getting the current path where you typed the command

const currentPath = process.cwd();  // Current directory path
Copy the code

Interaction problem to get input

const config = await inquirer.getQuestions();
Copy the code

The next step is to write the answers to the interaction questions into our template

  • Write package. Json
function handlePackageJson(config) {
    const spinner = ora('Writing package.json... ').start();
    const promise = new Promise((resolve, reject) = > {
        const packageJsonPath = path.resolve(`${currentPath}/${config.projectName}`.'package.json');
        fs.readJson(packageJsonPath, (err, json) => {
            if (err) {
                console.error(err);
            }
            json.name = config.projectName;
            json.description = config.description;
            json.author = config.author;
            if(config.cssPreprocessor == 'less') {
                json.devDependencies = Object.assign(json.devDependencies, { 
                    "less": "^ 3.9.0"."less-loader": "^ 4.1.0." "
                });
            } else {
                json.devDependencies = Object.assign(json.devDependencies, { 
                    "sass-loader": "^ 7.1.0"."node-sass": "^ 4.11.0"
                });
            }
            fs.writeJSON(path.resolve(`${currentPath}/${config.projectName}/package.json`), json, err => {
                if (err) {
                    return console.error(err)
                }
                spinner.stop();
                ora(chalk.green('Package. json write success')).succeed();
                resolve();
            });
        });
    });
    return promise;
}
Copy the code
  • Write the WebPack configuration

The interaction problem with Webpack is simple: choose less or sass, which defaults to less

function handleWebpackBase(config) {
    const spinner = ora('Writing to webpack... ').start();
    const promise = new Promise((resolve, reject) = > {
        const webpackBasePath = path.resolve(`${currentPath}/${config.projectName}`.'_webpack/webpack.base.conf.js');
        fs.readFile(webpackBasePath, 'utf8'.function(err, data) {
            if (err) {
                return console.error(err)
            }
            if(config.cssPreprocessor == 'scss') {
                data = data.replace("less-loader"."sass-loader");
            }
            fs.writeFile(path.resolve(`${currentPath}/${config.projectName}/_webpack/webpack.base.conf.js`), data, (err,result) => {
                if (err) {
                    return console.error(err)
                }
                spinner.stop();
                ora(chalk.green('Webpack write successful')).succeed(); resolve(); })})});return promise;
}
Copy the code

The complete main file bin/index.js

#! /usr/bin/env node

const inquirer = require('.. /lib/inquirer');
const path = require('path');
const fs = require('fs-extra');
const ora = require('ora'); // The terminal displays the wheel loading
const chalk = require('chalk');
const figlet = require('figlet');
const exec = require('promise-exec');
const currentPath = process.cwd();  // Current directory path
const templatePath = path.resolve(__dirname, '.. /template\/');

function handlePackageJson(config) {
    const spinner = ora('Writing package.json... ').start();
    const promise = new Promise((resolve, reject) = > {
        const packageJsonPath = path.resolve(`${currentPath}/${config.projectName}`.'package.json');
        fs.readJson(packageJsonPath, (err, json) => {
            if (err) {
                console.error(err);
            }
            json.name = config.projectName;
            json.description = config.description;
            json.author = config.author;
            if(config.cssPreprocessor == 'less') {
                json.devDependencies = Object.assign(json.devDependencies, { 
                    "less": "^ 3.9.0"."less-loader": "^ 4.1.0." "
                });
            } else {
                json.devDependencies = Object.assign(json.devDependencies, { 
                    "sass-loader": "^ 7.1.0"."node-sass": "^ 4.11.0"
                });
            }
            fs.writeJSON(path.resolve(`${currentPath}/${config.projectName}/package.json`), json, err => {
                if (err) {
                    return console.error(err)
                }
                spinner.stop();
                ora(chalk.green('Package. json write success')).succeed();
                resolve();
            });
        });
    });
    return promise;
}

function handleWebpackBase(config) {
    const spinner = ora('Writing to webpack... ').start();
    const promise = new Promise((resolve, reject) = > {
        const webpackBasePath = path.resolve(`${currentPath}/${config.projectName}`.'_webpack/webpack.base.conf.js');
        fs.readFile(webpackBasePath, 'utf8'.function(err, data) {
            if (err) {
                return console.error(err)
            }
            if(config.cssPreprocessor == 'scss') {
                data = data.replace("less-loader"."sass-loader");
            }
            fs.writeFile(path.resolve(`${currentPath}/${config.projectName}/_webpack/webpack.base.conf.js`), data, (err,result) => {
                if (err) {
                    return console.error(err)
                }
                spinner.stop();
                ora(chalk.green('Webpack write successful')).succeed(); resolve(); })})});return promise;
}

function successConsole(config) {
    console.log(' ');
    const projectName = config.projectName;
    console.log(`${chalk.gray('Project path:')} ${path.resolve(`${currentPath}/${projectName}`)}`);
    console.log(chalk.gray('Next, execute:'));
    console.log(' ');
    console.log(' ' + chalk.green('cd ') + projectName);
    if(config.isNpmInstall ! ='npm') {
        console.log(' ' + chalk.green('npm install'));
    }
    console.log(' ' + chalk.green('npm run dev --dirname=demo'));
    console.log(' ');
    console.log(chalk.green('enjoy coding ... '));
    console.log(
        chalk.green(figlet.textSync("webpack multi cli"))); }function createTemplate(config) {
    const projectName = config.projectName;
    const spinner = ora('Generating... ').start();
    fs.copy(path.resolve(templatePath), path.resolve(`${currentPath}/${projectName}`))
    .then((a)= > {
        spinner.stop();
        ora(chalk.green('Directory generated successfully! ')).succeed();
        return handlePackageJson(config);
    })
    .then((a)= > {
        return handleWebpackBase(config);
    })
    .then((a)= > {
        if(config.isNpmInstall == 'npm') {
            const spinnerInstall = ora('Install dependencies in... ').start();
            if(config.cssPreprocessor == 'sass') {
                console.log('if the node - sass installation failed, please check: https://github.com/sass/node-sass');
            }
            exec('npm install', {
                cwd: `${currentPath}/${projectName}`
            }).then(function(){
                console.log(' ')
                spinnerInstall.stop();
                ora(chalk.green('Phase-dependent installation successful! ')).succeed();
                successConsole(config);
            }).catch(function(err) {
                console.error(err);
            });
        } else {
            successConsole(config);
        }
    })
    .catch(err= > console.error(err))
}

const launch = async() = > {const config = await inquirer.getQuestions();
    createTemplate(config);
}
launch();
Copy the code

Finally, we can publish our scaffolding to NPM. About NPM package publishing, we can click to see how to write a NPM package

Our package has been released successful www.npmjs.com/package/web…

NPM install webpack-multi-CLI-g

Use webpack Init myTest as shown below:

After installing the dependencies, run NPM run dev –dirname=demo to see what happens

At this point, our Webpack-multi-CLI scaffolding is complete. It is relatively simple, certainly not comparable to vue-CLI, but the basic idea is there. If you want to do more complicated scaffolding, it is just an extension.

Through this article, I hope you can learn how to write a scaffolding of your own, understand the basic ideas similar to vue-CLI, hope you can have some harvest

Click here for the source code