记2021年底-2022年初各互联网大厂裁员风波内的个人感受,也是计划写这个专题的初衷。笔者作为一名前端开发,也担心哪天突然收到离职大礼包,上午收到通知,下午就要走人的尴尬境遇。作为底层的开发人员,我深切感受到前路的迷茫和不确定,但是与其惶惶度日,不如静下心来沉淀自己的技术,有技术在身总会让人觉得心安。
准备工作
开始进入正题,本次的webpack分享不会停留在webpack配置层,将会从执行入口、构建前准备阶段、编译构建阶段、构建后优化阶段、文件输出阶段这五部分进行介绍。这里特别感谢“极客时间”的 《玩转webpack》 作者 程柳锋,笔者是在借鉴视频课程的基础上学习webpack4.0源码进行的总结分享,另外也想借此机会安利一波该课程,视频内容从浅入深,很适合入门学习。
执行入口
通常我们构建打包过程可以简化为以下三步:
- 在项目根目录配置webpack.config.js文件,文件内对webpack进行各种配置(entry output module plugins devtool env等等)
- 在package.json中添加脚本执行指令
"scripts": {
"build": "webpack"
}
- 在终端运行
npm run build
即可开始打包构建
但是黑盒内到底隐藏着怎样的真相?
当运行npm run build
指令时,npm会让命令行工具进入node_modules目录下的.bin目录查找是否存在webpack.sh或者webpack.cmd,所以实际执行的文件是node_modules/.bin/webpack
本着求真的原则,这里稍微扩展一下node_modules的这个.bin目录是怎么生成的,其实npm这个包管理工具,会根据每个依赖包中json对象的bin字段,在.bin目录下创建该字段指向文件的镜像。所以可以看看webpack依赖包里的package.json文件,发现"bin": {"webpack": "bin/webpack.js"}
这块代码。
到这一步,我们就找到了webpack运行时的真正入口:node_modules/webpack/bin/webpack.js
后面就开始针对实际代码进行解析分享,这部分建议大家对照着webpack4.x的源码来看,文章中会对主流程代码进行提取讲解,不过具体的代码处理过程需要大家观看源码。
个人读 “这部分源码” 时感觉受益匪浅,主流程脉络清晰,即使没有注释可以知道每个代码块负责怎样的功能,和webpack相关的其他代码文件不同,webpack/bin/webpack.js作为入口文件相当有含金量!
webpack/bin/webpack.js
process.exitCode = 0;
const runCommand = (command, args) => {};
const isInstalled = packageName => {};
const CLIs = [];
const installedClis = CLIs.filter(cli => cli.installed);
if (installedClis.length === 0) {}
else if (installedClis.length === 1) {}
else {}
- 设置进程退出时的状态码为0,表示构建进程正常结束;
- 分别定义
runCommand
——运行指定命令的函数、isInstalled
——判断某个依赖是否被引入的函数、CLIs
——webpack命令行工具的枚举值; - 获取用户已安装的命令行工具数组
installedClis
; - 如果用户未安装webpack命令行工具则提示用户安装,如果用户安装了一个命令行工具则正常运行
require('webpack-cli/bin/cli.js')
,如果用户安装了两个命令行工具则提示用户删掉一个webpack命令行工具或者直接用指定的命令行工具去运行
看到这里,其实可以发现webpack/bin/webpack.js这个入口文件的作用就是为了引入webpack-cli这个命令行工具。如果用户安装了webpack-cli,最后会执行require('webpack-cli/bin/cli.js')
这行代码,引入cli.js这个文件进行后续处理。
webpack-cli/bin/cli.js
const { NON_COMPILATION_ARGS } = require("./utils/constants");
(function() {
const NON_COMPILATION_CMD = process.argv.find();
if (NON_COMPILATION_CMD) {
return require("./utils/prompt-command")(NON_COMPILATION_CMD, ...process.argv);
}
const yargs = require("yargs").usage(`webpack-cli ${require("../package.json").version};
yargs.parse(process.argv.slice(2), (err, argv, output) => {
options = require("./utils/convert-argv")(argv);
const webpack = require("webpack");
compiler = webpack(options);
if (firstOptions.watch || options.watch) {
compiler.watch(watchOptions, compilerCallback);
} else {
compiler.run();
}
});
})()
这部分处理流程稍微多一点,不过关键节点比较清晰,可以先将主流程一分为二,一个是用户运行的指令不需要编译;另一个是需要编译的指令。
如何判断用户输入的指令是否需要编译?webpack-cli事先定义了不需要编译指令的枚举值数组,在./utils/constants.js文件中的NON_COMPILATION_ARGS数组,const NON_COMPILATION_ARGS = ["init", "migrate", "serve", "generate-loader", "generate-plugin", "info"]
,对于不需要编译的指令直接调用promptForInstallation(packages, ...args)方法去运行。
对于需要进行编译构建的指令,首先引入yarg对“npm run”后面的指令进行解析,然后根据解析后的argv去生成webpack配制项参数options,再使用配制项参数实例化webpack,然后判断构建模式是watch还是run并开始运行。
本专题的主线是webpack构建打包的源码分析,后续将主要讲解compiler.run()对应的源码处理。