前言
webpack几乎是目前前端开发者无人不知的打包框架,毕竟无论使用什么开发库,都会想到要使用webpack打包,包括各种脚手架cli工具,大部分也采用了webpack作为其打包工具。本文试图用最简单的代码(仅仅使用命令行工具,代码足够少)来编写自己的loader与plugin。
初始化项目
// 运行npm init初始化package.json,一路回车
$ npm init
// 安装两个必须的依赖,前者为webpack核心,后者为命令行工具
$ npm install webpack webpack-cli
// 安装babel依赖
$ npm install @babel/core @babel/preset-env -D
简单来说,以上依赖已经足够去实现打包了,下面初始化一个待打包的工程文件。
// 项目根目录创建并编辑一个待打包的文件
$ vim main.js
// 加入以下待打包的代码
let foo = Math.random()
// 下边这个todo是模仿源代码中的todo,表示你还有未完成的功能。
// todo
console.log(foo)
接着,编辑webpack的默认配置文件,保证可以打包源代码。
// 接着,编辑webpack的默认配置文件
$ vim webpack.config.js
// 添加一下的配置
module.exports = {
entry: './main.js',
module: {
rules: [
{
test: /.js$/,
use: ['babel-loader']
}
]
}
}
// 打包,如果你没有全局安装webpack,是无法直接运行webpack的,因为没有加入环境变量。而npx可以自动去当前node_modules的bin目录中去寻找
$ npx webpack
如上,看到了吧,就是这样简单,接下来,我们以此为基础,添加loader和plugin。
需求如下:
- 如果代码中出现
// todo
则替换为一个警告。这样代码执行的之后,可以发现问题。
- 将最终打包出的代码,移动到
cdn
目录,来模拟静态资源上传CDN的过程。
正戏开始
首先介绍一下loader与plugin的区别。loader用于编译代码,大部分情况下是把原来无法直接在浏览器运行的代码编译为可执行的代码,比如对ES6,Sass的编译。而plugin则是在特定的时间点对编译的资源进行操作,比如压缩代码,或将打包出的文件打入到Html中。具体看看下边的实战,想必你会有更深的理解。
todo-loader
对于第一个需求,首先编写loader。一个loader就是一个npm包,所以操作如下:
// 创建loader的文件夹
$ mkdir todo-loader
// 初始化一个npm包,之后一路回车
$ npm init
// 创建并编辑入口文件
$ vim index.js
// 这就是核心
module.exports = function(resource){
console.log('加载todo-loader啦')
const result = resource.replace(/\/\/\stodo/, 'console.warn("请完成todo")')
return result;
}
简单来说,一个loader的实现就是去实现一个函数,传入文件源代码resource
然后进行你想要的修改。之后再把它返回出来,以便下一个loader继续处理。可以看一下输出的结果:
// 源代码
let foo = Math.random()
// todo
console.log(foo)
// 打包输出
(()=>{var o=Math.random();console.warn("请完成todo"),console.log(o)})();
以上可以看出已经完成了对todo
注释的替换。支持已经实现了一个loader ^ ^
cdn-plugin
下面来实现上传CDN的plugin,事实上loader和plugin其实就是按照webpack的规矩办事即可。在webpack的构建过程中,会在特定的时机广播事件。这就好像是Vue/React的生命周期钩子函数。plugin只需要在对应的钩子(事件回调)函数中完成想要的操作,比如在最终打包完成的钩子函数中上传文件到CDN,简单来说过程如下:
- 初始化一个plugin的npm包
- 编辑index.js文件,创建一个构造函数,函数内部实现一个apply方法(按规矩办事)
- 监听打包成功的事件,并完成上传CDN的操作。
const child_process = require('child_process')
class UploadCDNPlugin {
constructor(options){
// 根据用户参数去定制
}
// 核心方法
apply(compiler) {
// 监听回调方法
compiler.hooks.afterEmit.tap(
'UploadCDNPlugin',
(compilation) => {
// 原路径
const src = compilation.compiler.outputPath;
// 目标路径
const cdn = src.replace(/\/\w+$/,'/cdn');
// 模拟上传,实际可能是调用网络IO接口而不是磁盘IO的接口
child_process.exec(`cp -r ${src} ${cdn}`, err=>{console.log(err)} );
}
);
}
}
module.exports = UploadCDNPlugin;
接着只要在webpack中引入该插件即可
// 这里引入有三种方式
1. npm link 本文使用这种方式
2. 直接require一个js文件
3. 发布到私服或者npm
const UploadCDNPlugin = require('cdn-plugin');
...
plugins: [
new UploadCDNPlugin()
]
结束语
weback有两个核心部分需要了解到
- 构建机制
- 生成的bundle.js在浏览器的运行机制
本文描述了其构建机制,从上述loader编译到plugin的钩子函数,不难看出,webpack的构建流程就像流水线一样,各个loader与plugin各司其职。将你的代码"交给各个工人去操作",最终完成完整的打包流程,简单而优雅。
参考
- webpack官方文档 https://webpack.js.org/api/