前言
本文主要针对 webpack 中的 laoder 和 plugins 进行学习,不涉及如何使用和配置 webpack,因为这些基础在官方文档中已经很明确了,重点在于如何去实现属于自定义的 laoder 和 plugins。那么在开始前,先简单的介绍下什么叫构建工具。
构建工具
在 web 应用程序中,除了 HTML 文件之外,往往还需要用的很多的其他静态资源加以修饰,比如在 HTML 中使用的 图片、css 样式、js文件等等,但是浏览器并不能识别所有的文件资源,并正确的加载。
因此,开发者需要对不同的文件资源进行对应的处理操作,目的是为了能够正确的加载和使用对应的文件资源。比如:
- 图片除了常用的一些格式能被正常加载和显示之外,一些特殊的格式就无法直接使用;
- css 样式我们可能会使用 less / scss / css in js 等方式去使用;
- js 文件中可能使用了比较新的语法,如 ES6 、ES7 以至于更新的特性,需要对应的编译工具去做转换等等。
由于需要针对不同的文件资源做不同的处理,并且还要考这些用于处理文件资源工具的维护问题,因此就诞生了构建工具。
构建工具就是包含了处理大多数以上提及到问题的解决方案,意味着原本我们需要不同的小工具去处理不同的文件内容,但是现在只需要关注构建工具本身如何使用即可。
webpack
webpack 是什么?
webpack 是众多构建工具中的一种,它也是一个用于现代 JavaScript 应用程序的 静态模块打包工具。
当 webpack 处理应用程序时,它会在内部从 一个 或 多个 入口点去构建一个 依赖图,然后将项目中所需的每一个模块组合成一个或多个 bundles,它们均为 静态资源,用于展示你的内容。
其中涉及到的 chunk 和 bundles 的概念,可以根据下图来辅助理解:
- 根据引入的各种文件资源,形成对应的 依赖图,其中包含了要处理的 代码块 chunk
- 将 代码块 chunk 进行对应的处理,也称之为打包,输出之后就得到了需要的 bundles
五大核心
mode
- 可选值:
development
,production
,none
- 设置
mode
参数,可以启用 webpack 内置在相应环境下的默认优化 - mode 参数默认值为
production
entry
入口起点(entry point) 指示 webpack 应该使用哪个文件作为入口模块,用来作为构建其内部依赖图,可以拥有多个入口文件。
output
output 负责告诉 webpack 需要在哪里输出它所创建的 bundle,以及怎么去命名这些文件.
- 默认输出目录:
./dist
- 默认主要输出文件名: ./dist/main.js
- 其他生成文件默认放在
./dist
下
loader
webpack 只能理解 JavaScript 和 JSON 文件,开箱的 webapck 没办法识别其他文件类型。loader 就能够把这些文件类型转换成 weback 能识别的资源,并将它们转换为有效 模块,以便于在应用程序中去使用,同时也会被添加到依赖图中。
plugin
loader 用于转换某些类型的模块,而 plugin 则可以用于执行包括 loader 在内的、范围更广的任务。比如:打包优化,资源管理,注入环境变量等。
- 可以通过 require 引入对应plugin 插件,并在选项配置
plugins
的数组中 实例化调用 new PluginName(opt) - 可以自定义 webpack 插件实现具体场景的需求
loader
在 webpack 中 loader 是什么?
loader 本质就是一个函数,这个函数会接收三个参数:
content
:对应模块的内容map
:对应模块的 sourcemapmeta
:对应模块的元数据
loader 执行顺序
通常 loader 的书写结构决定了对执行顺序的描述:
- 左右结构 ——> 执行顺序为 从右往左
- 上下结构 ——> 执行顺序为 从下往上
为了更清晰和直观,下面列出了一个在 webpack 配置中和样式相关的常见配置:
module: { rules: [ { test: /\.css$/, // 左右结构 use: ['style-loader', 'css-loader'], // 或 // 上下结构 use: [ 'style-loader', 'css-loader' ] } ] } 复制代码
无论是 左右结构 还是 上下结构,都可以统一理解为 从后往前 的顺序去执行.
自定义 loader
- 新建 loader1.js 和 loader2.js 作为自定义 loader,注意除了向外暴露的函数方法以外,还给这个函数对象上添加了一个 pitch 方法,内容具体如下:
pitch 方法执行顺序和 loader 是相反的,也就是说 pitch 是 从前往后 的顺序去执行.
// loader1.js module.exports = function(content, map, meta) { console.log('loader1 ...'); return content; } module.exports.pitch = function (){ console.log('loader1 pitch...'); } // loader2.js module.exports = function(content, map, meta) { console.log('loader2 ...'); return content; } module.exports.pitch = function (){ console.log('loader2 pitch...'); } 复制代码
- 并在 webpack.config.js 中进行配置,内容如下:
// webpack.config.js const { resolve } = require('path'); module.exports = { mode: 'production', module: { rules: [ { test: /\.js$/, use: [ resolve(__dirname, 'loaders/loader1.js'), resolve(__dirname, 'loaders/loader2.js'), ] }, ] } } 复制代码
- 为了简化每次引入自定义 loader 时,都要写完整路径,如:
resolve(__dirname, 'loaders/xxx.js)
,因此可以通过配置 resolveLoader 选项统一指定 loader 要查找的路径,具体如下:
// webpack.config.js const { resolve } = require('path'); module.exports = { mode: 'production', module: { rules: [ { test: /\.js$/, use: [ 'loader1', 'loader2', ] }, ] }, resolveLoader: { modules: [ resolve(__dirname, 'loaders'), 'node_modules' ], } } 复制代码
- 当在编辑器终端输入 webpack 指令进行打包时,控制台输出结果如下:
loader 的同步和异步
同步 loader
在 自定义 loader 中书写 loader 的方式就属于同步 loader,当然还有另一种写法,那就是通过调用 this.callback() 方法,可以将上述 自定义 loader 中的写法进行改写,具体如下:
this.callback(error, content, map, meta),其中 error 表示错误内容,当没有错误时,可将其执行为 null. 使用这样的方式,就不需要在显式的进行 return.
// loader1.js module.exports = function(content, map, meta) { console.log('loader1 ...'); this.callback(null, content, map, meta); } module.exports.pitch = function (){ console.log('loader1 pitch...'); } // loader2.js module.exports = function(content, map, meta) { console.log('loader1 ...'); this.callback(null, content, map, meta); } module.exports.pitch = function (){ console.log('loader1 pitch...'); } 复制代码
异步 loader
异步 loader 需要通过 const callBack = this.async();
方法进行指定,然后通过调用 callBack() 方法表明异步执行完成.
可以将 loader2.js 变为异步 loader,改造内容和运行结果如下:
// loader2.js module.exports = function(content, map, meta) { console.log('loader2 ...'); const callback = this.async(); setTimeout(()=>{ callback(null,content, map, meta); },1000); } module.exports.pitch = function (){ console.log('loader2 pitch...'); } 复制代码
PS: 当执行到 loader2 时,会先等待 1s 左右,然后在执行 loader1 . 同时 compiled successfully 的时间明显比之前更多.