webpack | 2020-05-26 16:47:52 600次 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人赞