【webpack源码1】- 初始化

webpack | 2020-05-26 16:47:52 349次 1次

前言

开始进行 webpack 源码系列文章分析,目前准备边记笔记,边写文章。本次系列以主要流程为线索进行编写,编写的最主要目的是为了自己方便学习,也为了别人更方便学习,当然,如果你能看到这篇文章的话。

先提前看下 webpack 工作大致流程,流程图(来源网络)

1. 校验配置文件 :读取命令行传入或者 webpack.config.js 文件,初始化本次构建的配置参数

2. 生成 Compiler 对象:执行配置文件中的插件实例化语句 new MyWebpackPlugin(),为 webpack 事件流挂上自定义hooks

3. 进入 entryOption 阶段:webpack 开始读取配置的 Entries,递归遍历所有的入口文件

4. run/watch:如果运行在 watch 模式则执行 watch 方法,否则执行 run 方法

5. compilation:创建 Compilation 对象回调 compilation 相关钩子,依次进入每一个入口文件(entry),使用 loader 对文件进行编译。通过 compilation 可以读取到 module 的 resource(资源路径)、loaders(使用的loader)等信息。再将编译好的文件内容使用 acorn 解析生成 AST 静态语法树。然后递归、重复的执行这个过程, 所有模块和和依赖分析完成后,执行 compilation 的 seal 方法对每个 chunk 进行整理、优化、封装 __webpack_require__ 来模拟模块化操作.

6. emit:所有文件的编译及转化都已经完成,包含了最终输出的资源,我们可以在传入事件回调的 compilation.assets 上拿到所需数据,其中包括即将输出的资源、代码块 Chunk 等等信息。


一、命令行入口

当我们输入npm run dev 后,一般都在项目的 package.json 中配置 script 脚本命令,命令向 node_modules .bin 目录查找是否存在 webpack.sh 或者 webpack.cmd 文件,如果存在,就执行,不存在则报错(使用 npx 会自动下载并执行)。其中在 cmd 文件中通过执行 node 命令来启动,因为webpack 源码的 package.json 中配置了  bin: ./bin/webpack.js  ,所以输入完命令后就进入 webpack bin目录,执行webpack.js


二、bin/webpack.js执行

这个文件是命令行输入后,执行的第一个文件,在 webpack 这个包中,判断是否安装了 webpack-cli(官方维护的并推荐使用)或者 webpack-command(社区维护),并且仅仅只能安装一个,如果全部没有安装则会自动提示是否安装并执行,如果安装了一个则 require 进来执行。

我们以 webpack-cli 为例:

//判断是否安装过脚手架模模块
const installedClis = CLIs.filter(cli => cli.installed);
if (installedClis.length === 0){
    //脚手架没有安装则通过readline提示用户是否安装webpack-cli
}else if (installedClis.length === 1){
    const path = require("path");
    // 找到webpack-cli包的package.json文件
    const pkgPath = require.resolve(`${installedClis[0].package}/package.json`);
    const pkg = require(pkgPath);
    /**
     * 找到cli路径并引入cli,执行的入口
     *  D:\WORK\webpack\node_modules\_webpack-cli@3.3.11@webpack-cli\bin\cli.js
     */
    require(path.resolve(
        path.dirname(pkgPath),
        pkg.bin[installedClis[0].binName]
    ));
}else{
    //全部安装则报错,提示只能安装一个脚手架
}

这个文件做的事情很纯粹,就是找到脚手架的目录 webpack-cli/bin/cli.js,并导入执行。


、webpack-cli/bin/cli.js执行

先来看下此文件整体结构:

//不需要编译的参数["init", "migrate", "serve", "generate-loader", "generate-plugin", "info"];
const { NON_COMPILATION_ARGS } = require("./utils/constants");
(function (){
    //引入了import-local 包,这个包的作用在于判断某个包是本地安装的还是全局安装的;
    const importLocal = require("import-local");

    // 判断当前包(webpack-cli)是否全局安装,如果是全局安装,则中断执行。
    if (importLocal(__filename)){ ... }

    //引入V8引擎的代码缓存功能,用于加速实例化。
    require("v8-compile-cache");

    //引入错误处理类
    const ErrorHelpers = require("./errorHelpers");

    //过滤出不需要编译的参数,进行后续的运行,如果没有安装则提示安装并运行
    const NON_COMPILATION_CMD = process.argv.find(arg=>{...})
    if (NON_COMPILATION_CMD){
        return require("./utils/prompt-command")(NON_COMPILATION_CMD, ...process.argv);
    }

    //其他命令 则执行yarns
    const yargs = require("yargs").usage(...);

    //比如 输入 npx webpack -help  会对这里面进行的配置根据分组名称group进行分组显示,describe信息描述
    require("./config-yargs")(yargs);
    
    //yargs.parse解析出相关参数
    yargs.parse(process.argv.slice(2), (err, argv, output) => {...});
})();

主要逻辑都集中在 yargs.parse,首先判断了是否有要输出的内容:

        //  如果解析出错并且有输出内容,则输出相关错误信息并退出执行;
        if (err && output) {
            console.error(output);
            process.exitCode = 1;
            return;
        }
        //  如果有输出内容,就输出它们,并返回; help or version
        if (output) {
            console.log(output);
            return;
        }

接着处理合并 cli 参数与项目配置参数:

    options = require("./utils/convert-argv")(argv);

convert-argv这个文件做的事情是:先找是否在命令行中指定配置文件,如果没有指定,则会主动找到当前 cmd 命令目录中是否有webpack.config.js文件,剩余的工作就是进行命令行参数转换,即对用户配置的 options 合并转换处理。回到 cli 文件中,对配置中的参数进行一些转换处理,然后导入了 webpack:

//以上都是对一些默认参数做处理,如果没有配置webpack会自动预置一些,
//这里正式开始处理逻辑,先导入webpack,因为在webpack的包中package.json中主入口配置的为"main": "lib/webpack.js",
const webpack = require("webpack");

然后通过配置传入 webpack 执行后获取到 Compiler:

compiler = webpack(options);

它暴露了和 webpack 整个生命周期相关的钩子,所有 webpack 可配置的内容,开发插件时,我们可以从 compiler 对象中拿到所有和 webpack 主环境相关的内容。在 cli 这个文件中,主要是做了是否开启监听模式,否则直接执行 run 进行打包处理:

compiler.run((err, stats) => {
    if (compiler.close) {
        compiler.close(err2 => {
            compilerCallback(err || err2, stats);
        });
    } else {
        compilerCallback(err, stats);
    }
});

compilerCallback 回调中主要是打印一些相关文件信息。

入口处 webpack 花了大量的工作进行各种配置的归一和预置处理,所以 webpack 配置是很灵活的。配置文件处理完毕后,重要的逻辑工作都是在 webpack.js 中进行相关处理,下一篇开始进入 webpack.js 中进行分析。

1人赞

分享到: