前言 嘛,这是这个系列的第二篇,上篇随缘介绍了Webpack的基本配置和打包的基本过程和原理,这篇文章我们来介绍Webpack中两个很重要的扩展功能,loader
和plugin
Loader Loader简介 还记得这张图嘛,Webpack支持对各种文件进行打包
但是Webpack本身只支持对js文件进行解析打包,因为AST抽象语法树的解析只能针对js
那对其他的文件怎么处理呢,其实很简单,我们对其他文件使用loader进行解析和处理,把它转化成一个可以转化成AST的代码就可以了
Loader的配置 新建一个src/index.css
1 2 3 4 5 6 :root , body { height : 100% ; } body { background : darkseagreen; }
修改src/index.js
1 2 3 import add from "./cal.js" ;import "./index.css" ;console .log(add(Math .random(),Math .random()));
运行命令
非常快乐的报错了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [webpack-cli] Compilation finished asset bundle.js 6.43 KiB [emitted] (name: main) runtime modules 931 bytes 4 modules cacheable modules 752 bytes modules by path ./src/*.js 260 bytes ./src/index.js 98 bytes [built] [code generated] ./src/cal.js 162 bytes [built] [code generated] ./src/index.css 77 bytes [built] [code generated] [1 error] ./src/util/index.js 415 bytes [built] [code generated] ERROR in ./src/index.css 1:0 Module parse failed: Unexpected token (1:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js .org/concepts#loaders > :root, body { | height: 100%; | } @ ./src/index.js 2:0-21
很明显,报错信息告诉我们,要用一个loader进行解析css这个文件类型,那我们可以给它加一个loader
新建src/loaders/style-loader
1 2 3 4 5 6 7 module .exports = function (sourceCode ) { var code = `var style = document.createElement("style"); style.innerHTML = \`${sourceCode} \`; document.head.appendChild(style); module.exports = \`${sourceCode} \`` ; return code; }
修改webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let path = require ("path" );module .exports = { entry: "./src/index.js" , mode: "development" , output: { path: path.resolve(__dirname, "dist" ), filename: "bundle.js" }, devServer: { contentBase: path.resolve(__dirname, 'dist' ), port: 9000 }, module : { rules: [{ test: /\.css$/ , use: ["./src/loaders/style-loader" ] }] } }
运行命令
打包成功√
1 2 3 4 5 6 7 8 E:\Workspaces\Project\WebPro\WebpackTest>webpack [webpack-cli] Compilation finished asset bundle.js 669 bytes [emitted] [minimized] (name: main) orphan modules 577 bytes [orphan] 2 modules cacheable modules 961 bytes ./src/index.js + 2 modules 675 bytes [built] [code generated] ./src/index.css 286 bytes [built] [code generated] webpack 5.10.1 compiled successfully in 250 ms
看看打包结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ({ "./src/add.js" : ((__unused_webpack_module, __webpack_exports__, __webpack_require__ ) => "use strict" ; eval ("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => /* binding */ add\n/* harmony export */ });\n/* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util */ \"./src/util/index.js\");\n\r\n\r\nfunction add(a, b) {\r\n return a + b;\r\n}\r\n\r\n\n\n//# sourceURL=webpack://webpacktest/./src/add.js?" ); }), "./src/index.js" : ((__unused_webpack_module, __webpack_exports__, __webpack_require__ ) => { "use strict" ; eval ("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _add_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./add.js */ \"./src/add.js\");\n/* harmony import */ var _index_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.css */ \"./src/index.css\");\n/* harmony import */ var _index_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_index_css__WEBPACK_IMPORTED_MODULE_1__);\n\r\n\r\nconsole.log((0,_add_js__WEBPACK_IMPORTED_MODULE_0__.default)(1,2));\n\n//# sourceURL=webpack://webpacktest/./src/index.js?" ); }), "./src/index.css" : ((module ) => { eval ("var style = document.createElement(\"style\");\n style.innerHTML = `:root, body {\r\n height: 100%;\r\n}\r\nbody {\r\n background: darkseagreen;\r\n}`;\n document.head.appendChild(style);\n module.exports = `:root, body {\r\n height: 100%;\r\n}\r\nbody {\r\n background: darkseagreen;\r\n}`\n\n//# sourceURL=webpack://webpacktest/./src/index.css?" ); }), "./src/util/index.js" : ((__unused_webpack_module, __webpack_exports__, __webpack_require__ ) => { "use strict" ; eval ("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\nfunction randomNum(minNum, maxNum) {\r\n switch (arguments.length) {\r\n case 1:\r\n return parseInt(Math.random() * minNum + 1, 10);\r\n break;\r\n case 2:\r\n return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);\r\n break;\r\n default:\r\n return 0;\r\n break;\r\n }\r\n}\r\nconsole.log(randomNum(1,2));\r\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (randomNum);\n\n//# sourceURL=webpack://webpacktest/./src/util/index.js?" ); }) });
然后把对应的内容拿出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 "./src/index.css" : ((module ) => { var style = document .createElement("style" ); style.innerHTML = `:root, body { height: 100%; } body { background: darkseagreen; }` ; document .head.appendChild(style); module .exports = `:root, body { height: 100%; } body { background: darkseagreen; }` }),
看到了吗,这就是我们通过loader处理过的css代码
所以loader本质上就是一个函数,接收原文件并且转化为Webpack可以识别处理的代码
更多的配置参见:webpack-loader
练习一下 嘛,这次我们试一下通过loader处理图片文件
新建src/loaders/img-loader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 let loaderUtil = require ("loader-utils" )function loader (buffer ) { let { limit = 1000 , filename = "[contenthash].[ext]" } = loaderUtil.getOptions(this ); let content = "" ; if (buffer.byteLength >= limit) { content = getFilePath.call(this , buffer, filename); } else { content = getBase64(buffer) } return `module.exports = \`${content} \`` ; } loader.raw = true ; module .exports = loader;function getBase64 (buffer ) { return "data:image/png;base64," + buffer.toString("base64" ); } function getFilePath (buffer, name ) { let filename = loaderUtil.interpolateName(this , name, { content: buffer }); this .emitFile(filename, buffer); return filename; }
修改index.js
1 2 3 4 5 6 7 import add from "./add.js" ;import "./index.css" ;import src from "./assets/img.png" ;console .log(add(1 ,2 ));let img = document .createElement("img" );img.src = src; document .body.appendChild(img);
修改webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 let path = require ("path" );module .exports = { entry: "./src/index.js" , mode: "development" , output: { path: path.resolve(__dirname, "dist" ), filename: "bundle.js" }, devServer: { contentBase: path.resolve(__dirname, 'dist' ), port: 9000 }, module : { rules: [{ test: /\.css$/ , use: ["./src/loaders/style-loader" ] }, { test: /\.(png)|(jpg)|(gif)$/ , use: [{ loader: "./src/loaders/img-loader.js" , options: { limit: 3000 , filename: "img-[contenthash:5].[ext]" } }] }] } }
运行webpack命令打包
1 2 3 4 5 6 7 8 9 10 11 12 13 E:\Workspaces\Project\WebPro\WebpackTest>webpack [webpack-cli] Compilation finished asset img-e7819.png 189 KiB [emitted] (auxiliary name: main) asset bundle.js 7.05 KiB [emitted] (name: main) runtime modules 931 bytes 4 modules cacheable modules 1.01 KiB modules by path ./src/*.js 298 bytes ./src/index.js 202 bytes [built] [code generated] ./src/add.js 96 bytes [built] [code generated] ./src/index.css 286 bytes [built] [code generated] ./src/assets/img.png 32 bytes [built] [code generated] ./src/util/index.js 415 bytes [built] [code generated] webpack 5.10.1 compiled successfully in 114 ms
打开index.html
1 2 3 4 5 6 7 8 9 10 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <script src ="./bundle.js" > </script > </body > </html >
啊哈打包成功
Plugin Plugin简介 loader的功能定位是转换代码,而有的时候我们需要在打包流程里执行一些操作,这时候就要用到我们的plugin了
那么plugin是什么呢,其实plugin本质上就是一个带有apply方法的对象
1 2 3 4 5 let plugin = { apply: function (compiler ) { } }
但是我们一般会写成构造函数的形式,在需要时通过new来创建
1 2 3 4 5 6 7 class MyPlugin { apply (compiler ) { console .log("apply方法运行" ); } } module .exports = MyPlugin
如果要在打包时应用我们的插件,可以在webpack.config.js
里进行下面的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 let path = require ("path" );let MyPlugin = require ("./src/plugins/MyPlugin" );module .exports = { entry: "./src/index.js" , mode: "development" , output: { path: path.resolve(__dirname, "dist" ), filename: "bundle.js" }, devServer: { contentBase: path.resolve(__dirname, 'dist' ), port: 9000 }, module : { rules: [{ test: /\.css$/ , use: ["./src/loaders/style-loader" ] }, { test: /\.(png)|(jpg)|(gif)$/ , use: [{ loader: "./src/loaders/img-loader.js" , options: { limit: 3000 , filename: "img-[contenthash:5].[ext]" } }] }] }, plugins: [ new MyPlugin() ] }
get √
1 2 3 4 5 6 7 8 9 10 11 12 13 14 E:\Workspaces\Project\WebPro\WebpackTest>webpack apply方法运行 [webpack-cli] Compilation finished asset img-e7819.png 189 KiB [compared for emit] (auxiliary name: main) asset bundle.js 7.05 KiB [compared for emit] (name: main) runtime modules 931 bytes 4 modules cacheable modules 1.01 KiB modules by path ./src/*.js 298 bytes ./src/index.js 202 bytes [built] [code generated] ./src/add.js 96 bytes [built] [code generated] ./src/index.css 286 bytes [built] [code generated] ./src/assets/img.png 32 bytes [built] [code generated] ./src/util/index.js 415 bytes [built] [code generated] webpack 5.10.1 compiled successfully in 259 ms
插件的apply函数会在初始化阶段,创建好compiler对象后运行,并且把compiler对象传入,我们可以在apply函数里给compiler注册一些hook,这些hook就类似于webpack的生命周期函数,会在特定的时机被执行
注册方法如下
1 2 3 4 5 6 7 class MyPlugin { apply (compiler ) { compiler.hooks.事件名称.事件类型(name, function (compilation ) { }) } }
事件名称
即要监听的事件名,即钩子名,compiler所有的钩子
事件类型
这部分使用了一个库:Tapable Api
如果想详细了解各个事件类型可以看这篇文章:Webpack Tappable使用研究
在注册事件时,你需要传入两个参数,一个是事件的名称,我们一般直接使用插件名就好了,另一个是一个回调函数,这个回调函数会在触发事件时执行,传入一个compilation参数,这个参数代表了这次的编译打包结果,也可以在这上面挂载一些钩子
详情请看comliation
练习一下 在这里我们编写一个可以输出打包出来的资源到文件的插件
新建src/plugins/FileListPlugin.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class FileListPlugin { constructor (filename = "fileList.txt" ) { this .filename = filename; } apply (compiler ) { compiler.hooks.emit.tap("FileListPlugin" , compilation => { let fileList = []; Object .keys(compilation.assets).forEach((key ) => { let content = `【${key} 】 大小:${compilation.assets[key].size()/1000 } KB` ; fileList.push(content); }) let str = fileList.join("\n\n" ); compilation.assets[this .filename] = { source ( ) { return str }, size ( ) { return str.length; } } }) } } module .exports = FileListPlugin;
更新webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 let path = require ("path" );let MyPlugin = require ("./src/plugins/MyPlugin" );let FileListPlugin = require ("./src/plugins/FileListPlugin" );module .exports = { entry: "./src/index.js" , mode: "development" , output: { path: path.resolve(__dirname, "dist" ), filename: "bundle.js" }, devServer: { contentBase: path.resolve(__dirname, 'dist' ), port: 9000 }, module : { rules: [{ test: /\.css$/ , use: ["./src/loaders/style-loader" ] }, { test: /\.(png)|(jpg)|(gif)$/ , use: [{ loader: "./src/loaders/img-loader.js" , options: { limit: 3000 , filename: "img-[contenthash:5].[ext]" } }] }] }, plugins: [ new MyPlugin(), new FileListPlugin() ] }
运行命令
dist目录下生成了一个fileList.txt
1 2 3 4 5 【img-e7819.png】 大小:193.314KB 【bundle.js】 大小:7.222KB
这样我们的插件就运行成功了