webpack启动过程分析

201 阅读1分钟

当使用命令行启动项目时,npm会让命令行进入node_modules下的.bin文件,在该文件中查找webpack.sh或者webpack.cmd文件,如果找到就执行, 如果没有找到就报错。

webpack.cmd文件:

@IF EXIST "%~dp0\node.exe" 
(  "%~dp0\node.exe"  "%~dp0\..\webpack\bin\webpack.js" %*) 
ELSE (  @SETLOCAL  @SET PATHEXT=%PATHEXT:;.JS;=;%  node  "%~dp0\..\webpack\bin\webpack.js" %*)

可以看到实际上是执行了node_modules/webpack/bin/webpack.js文件。

webpack.js代码分析:

函数一共有三个:

  • runCommand:创建一个异步的进程(使用nodeJS的child_process模块),使用yarn/npm/pnpm下载webpack-cli。

  • isInstalled:下载webpack-cli

  • runCli:找到node_modules文件下的入口文件,然后执行。

源码分析:

#!/usr/bin/env node/** 
* @param {string} command process to run 
* @param {string[]} args command line arguments 
* @returns {Promise<void>} promise 
*/
const runCommand = (command, args) => {    
    const cp = require("child_process");   
    return new Promise((resolve, reject) => {
   // 创建一个子进程下载webpack-cli,如果错误,直接抛出错误,如果没有错,关闭子进程,并resolve出去。        
        const executedCommand = cp.spawn(command, args, {            
            stdio: "inherit",           
            shell: true        
        });        
        executedCommand.on("error", error => {            
            reject(error);        
        });        
        executedCommand.on("exit", code => {            
            if (code === 0) {                
                resolve();            
            } else {                
                reject();            
            }        
        });    
    });
};

/** 
* @param {string} packageName name of the package 
* @returns {boolean} is the package installed? 
*/
const isInstalled = packageName => {
    // 使用开启pnp    
    if (process.versions.pnp) {        
        return true;    
    }    
    const path = require("path");    
    const fs = require("graceful-fs");    
    let dir = __dirname;    
    do {        
        try {
           // 判断node_modules/webpac/package.json中的依赖是否都下载,如果下载,返回true,没有则报错。            
            if (                
                fs.statSync(path.join(dir, "node_modules", packageName)).isDirectory()            
            ) {                
                return true;            
            }        
          } 
          catch (_error) {            
            // Nothing        
          }    
    } while (dir !== (dir = path.dirname(dir)));   

     return false;
};
/** 
* @param {CliOption} cli options 
* @returns {void} 
*/
const runCli = cli => {    
    const path = require("path");

   // 获取webpack-cli/package.json文件的绝对目录。    
    const pkgPath = require.resolve(`${cli.package}/package.json`);    
  // eslint-disable-next-line node/no-missing-require    
    const pkg = require(pkgPath);

    // 执行webpack-cli/package.json文件和package.json中的bin指向的文件。
    // 这里pkg.bin的值为node_modules/webpack-cli/bin/cli.js
    // eslint-disable-next-line node/no-missing-require    

    require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
};

/** 
* @typedef {Object} CliOption 
* @property {string} name display name 
* @property {string} package npm package name 
* @property {string} binName name of the executable file 
* @property {boolean} installed currently installed? 
* @property {string} url homepage 
*//** @type {CliOption} 
*/
const cli = {    
    name: "webpack-cli",    
    package: "webpack-cli",    
    binName: "webpack-cli",    
    installed: isInstalled("webpack-cli"),    
    url: "https://github.com/webpack/webpack-cli"
};
// 判断webpack-cli是否存在,不存在则下载再执行,如果存在,则直接执行
if (!cli.installed) {    
    const path = require("path");    
    const fs = require("graceful-fs");    
    const readLine = require("readline");    
    const notify =        "CLI for webpack must be installed.\n" + `  ${cli.name} (${cli.url})\n`;    
    console.error(notify);    
    let packageManager;    
    if (fs.existsSync(path.resolve(process.cwd(), "yarn.lock"))) {       
         packageManager = "yarn";    
    } else if (fs.existsSync(path.resolve(process.cwd(), "pnpm-lock.yaml"))) {        
        packageManager = "pnpm";    
    } else {        
        packageManager = "npm";    
    }    
    const installOptions = [packageManager === "yarn" ? "add" : "install", "-D"];    
    console.error(        `We will use "${packageManager}" to install the CLI via "${packageManager} ${installOptions.join(            " "        )} ${cli.package}".`    );    
    const question = `Do you want to install 'webpack-cli' (yes/no): `;    
    const questionInterface = readLine.createInterface({        input: process.stdin,        output: process.stderr    });    
    // In certain scenarios (e.g. when STDIN is not in terminal mode), the callback function will not be   
    // executed. Setting the exit code here to ensure the script exits correctly in those cases. The callback    
    // function is responsible for clearing the exit code if the user wishes to install webpack-cli.    
    process.exitCode = 1;    
    questionInterface.question(question, answer => {        
        questionInterface.close();        
        const normalizedAnswer = answer.toLowerCase().startsWith("y");        
        if (!normalizedAnswer) {            
            console.error(                "You need to install 'webpack-cli' to use webpack via CLI.\n" +                    "You can also install the CLI manually."            );            
            return;        
        }        
        process.exitCode = 0;        
        console.log(            `Installing '${                cli.package            }' (running '${packageManager} ${installOptions.join(" ")} ${                cli.package            }')...`        );        
        runCommand(packageManager, installOptions.concat(cli.package))            
            .then(() => {                
                    runCli(cli);            
            })            
            .catch(error => {                
                console.error(error);                
                process.exitCode = 1;            
            });    
        });
      } else {    runCli(cli);}