webpack 打包工具,loader 和 plugin 是什么,是如何实现的
参考原文:https://www.jb51.net/article/148719.htm
webpack.config.js的常见配置
const webpack = require("webpack") const path = require("path") const HtmlWebpackPlugin = require("html-webpack-plugin") module.exports = { // 入口文件 entry: { app: path.join(__dirname, "../src/js/index.js") }, // 输出文件 output: { filename: "[name].bundle.js", path: path.resolve(__dirname, "dist"), publicPath: "/" // 确保文件资源能够在 http://localhost:3000下正确访问 }, // 开发者工具 source-map devtool: 'inline-source-map', // 创建开发者服务器 devServer: { contentBase: './dist', hot: true // 热更新 }, // 环境 mode: "development", // loader 配置 module: { rules: [ { test: /\.scss/, use:[ "style-loader", "css-loader" ] }, { test: /\.(png|svg|jpg|gif)$/, use: [ 'file-loader' ] } …… ] }, // plugins 配置 plugins:[ // 删除 dist 目录 new CleanWebpackPlugin(['dist']), // 热更新模块 new webpack.HotModuleReplacementPlugin(), // 重新创建HTML 文件 new HtmlWebpackPlugin({ title: '首页', finename: 'index.html', template: path.resolve(__dirname, "../src/index.html") }) …… ] }
webpack 打包原理
1. 识别入口文件
2. 通过逐层识别模块依赖。(Commonjs、amd 或者 es6 的 import,webpack 都会对其进行分析,来获取代码的依赖)
3. webpack 做的就是分析代码,转换代码,编译代码,输出代码
4. 最终形成打包后的代码
什么是loader
loader是文件加载器,直译为“记载器”。能够加载资源文件,并对这些文件进行一些处理,诸如 编译、压缩等,最终一起打包到指定的文件中。
1. 处理一个文件可以使用多个 loader,loader 的执行顺序和配置中的顺序是相反的,即最后一个 loader 最先执行,第一个 loader 最后执行。
2. 第一个执行的loader 接收源文件内容作为参数,其他 loader 接收前一个执行的 loader 的返回值作为参数,最后执行的 loader 会返回此模块的 JavaScript 源码。
webpack 将一切文件视为模块,但 webpack 原生是只能解析 JS 文件,如果想将其他文件也打包的话,就会用到 loader。所以 loader 的作用就是让 webpack 拥有了加载和解析 非 JavaScript 文件的能力。
手写一个 loader
需求:1). 处理 .txt 文件;2). 对字符串做反转操作; 3). 首字母大写
例如:abcdefg 转换后为 Gfedcba
1. 首先创建两个 loader(已本地loader 为例)
为什么要创建两个 loader?理由在后面介绍。
// reverse-loader.js module.exports = function(src) { if(src) { console.log('input', src) src = src.split('').reverse().join('') console.log('output', src) } return src }
// uppercase-loader.js module.exports = function(src) { if(src) { console.log('input', src) src = src.chatAt(0).toUpperCase() + src.slice(1) console.log('output', src) } return `module.exports = '${src}'` }
看,loader 结构是不是很简单,接受一个参数,并且return一个内容就可以了。
然后创建一个 txt 文件
2. mytest.txt
abcdefg
3. 开始配置 webpack
module.exports = { entry:{ index: './src/js/index.js' }, plugins:[...], optimization:{...}, output:{...}, module:{ rules: [ ..., { test: /\.txt$/, use:[ './loader/uppercase-loader.js', './loader/reverse-loader.js' ] } ] } }
配置完成。
4. 在入口文件中导入这个脚本 —— 说实话,这步我没理解是导入了什么,谁知道的话还请留言告知。
为什么这里需要导入呢,我们不是配置了webapck处理所有的.txt文件么?
因为webpack会做过滤,如果不引用该文件的话,webpack是不会对该文件进行打包处理的,那么你的loader也不会执行
import _ from 'lodash'; import txt from '../txt/mytest.txt' import '../css/style.css' function component() { var element = document.createElement('div'); var button = document.createElement('button'); var br = document.createElement('br'); button.innerHTML = 'Click me and look at the console!'; element.innerHTML = _.join('【' + txt + '】'); element.className = 'hello' element.appendChild(br); element.appendChild(button); // Note that because a network request is involved, some indication // of loading would need to be shown in a production-level site/app. button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => { var print = module.default; print(); }); return element; } document.body.appendChild(component());
// package.json 配置 { ..., "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config webpack.prod.js", "start": "webpack-dev-server --open --config webpack.dev.js", "server": "node server.js" }, ... }
5. 然后执行命令
npm run build
这样我们的loader就写完了。
现在回答为什么要写两个loader?
看到执行的顺序没,我们的配置的是这样的
use: [ './loader/uppercase-loader.js', './loader/reverse-loader.js' ]
正如前文所说, 处理一个文件可以使用多个loader,loader的执行顺序是和本身的顺序是相反的
我们也可以自己写loader解析自定义模板,像vue-loader是非常复杂的,它内部会写大量的对.vue文件的解析,然后会生成对应的html、js和css。
我们这里只是讲述了一个最基础的用法,如果有更多的需要,可以查看《loader官方文档》
什么是plugin
plugin 直译为“插件”。在webpack 运行的生命周期中会广播出很多事件,plugin 可以监听这些事件,在合适的时机通过webpack 提供的 API 改变输出结果。
loader 和 plugin 的区别
1. 作用不同:
loader:是一个转换器,将 A 文件进行编译形成 B 文件,这里操作的是文件,比如将 A.scss 转换为 A.css,单纯的文件转换过程。
plugin:是一个扩展器,扩展 webpack 的功能,让webpack 具有更多的灵活性。针对的是 loader 结束后,webpack 打包的整个过程,并不是直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行广泛的任务。
class MyPlugin{ constructor(options){ // 构造方法 console.log('11-->', options) } apply(compiler){ // 应用函数 // 绑定钩子事件 compiler.plugin("compilation", compilation => { console.log('222') }) } } module.exports = MyPlugin // webpack.config.js 配置: const MyPlugin = require('./plugins/MyPlugin') module.exports = { ... plugins:[ new MyPlugin({param: 'xxx'}) ] }
使用该plugin后,执行的顺序:
1. webpack 启动后,在读取配置的过程中会执行 new MyPlugin(options) 初始化一个 MyPlugin 获取其实例
2. 在初始化 compiler 对象后,就会通过 compiler.plugin(事件名称,回调函数)监听到 webpack 广播出来的事件
3. 并且可以通过 compiler 对象去操作 webpack
其中Compiler 和 Compilation 的区别在于:
Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译。
2. 用法不同:
loader 在 module.rules 中配置,也就是说作为模块的解析规则而存在。类型为数组,每一项都是一个 object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)
plugin 在 plugins 中单独配置。类型为数组,每一项是一个 plugin 的实例,参数都通过构造函数传入。