深入webpack打包原理及loader和plugin的实现
一、Webpack的打包原理
Webpack 是一个现代化的 JavaScript 应用程序打包器。Webpack 会读取你的应用程序并构建一个依赖关系图,然后根据这个图创建一个打包文件。在打包的过程中,Webpack 的核心功能包括模块解析器、依赖关系解析器、代码生成器和打包器等。Webpack 的打包流程大体可以分为如下几个阶段:
-
初始化阶段:读取配置文件,创建Compiler对象,加载所有的插件,等等;
-
编译阶段:通过调用 Compiler 对象提供的 run 方法来进行编译,主要是如下几个过程:
-
根据 entry 和其依赖的模块,构建成 AST 树,然后经过转换器进行语法转换、附加前置、后置、中间模块等操作转换器;
-
根据不同类型的模块,对模块进行相应的处理(姑且称之为 load),例如: 解析模块依赖(package.json, CommonJS, ES6 等),处理样式文件、图片等文件引用等;
-
对模块进行封装,生成完成的 Chunk 对象;
-
输出阶段:对 Chunk 进行优化、压缩、最终的输出文件等操作。
下面我们重点关注在编译阶段中,webpack是如何对模块进行处理的。
二、Loader的实现
在Webpack中,Loader是一个文件转换器,它专门用于处理资源文件,将入口文件和依赖文件都作为参数传入。Webpack 会通过读取配置文件的module.rules属性,来决定用哪个Loader进行处理。其实现基本流程可以简述为:
-
读取配置文件,解析出module.rules下的所有正则表达式对象,并通过正则对象来匹配module下的模块文件;
-
根据匹配的Loader对文件进行相应的转换处理等操作;
-
返回转换好的内容。
有关 Loader 实现的两个示例:
1. 常用的文件转换
常用 Loader 中的一种是处理样式文件的 style-loader 和 css-loader,它们的作用是将 css 样式文件打包到 JavaScript 文件中,目的是为了提高浏览器的加载速度。
例如 css-loader 可以解析 CSS 文件,并将 CSS 文件转换成一个字符串,然后交给 style-loader 处理,style-loader 可以在 HTML 文件中插入一段 style 标签,然后将 CSS 字符串放入 style 标签中。
// css-loader.js
module.exports = function(source) {
return JSON.stringify(source); // 将 css 转为字符串
}
// style-loader.js
module.exports = function(cssString) {
const style = document.createElement('style');
style.innerText = cssString; // 内联CSS
document.head.appendChild(style); // 将style标签插入HTML
}
2. Babel转换器
常见的一个Loader是 Bable-loader,用于将 ES6 或更高的语法或其他语言转成浏览器支持的语法。
例如,将 ES6 代码转换成 ES5 代码:
// babel-loader.js
const babelCore = require('@babel/core');
module.exports = function(source) {
const { code } = babelCore.transform(source, {
presets: [
['@babel/preset-env', { targets: 'last 2 versions, >1%, not dead' }]
]
});
return code;
}
三、Plugin的实现
Plugin是可以影响Webpack的构建流程,并将在整个构建流程中“挂载”在Webpack的钩子上。Webpack的运行过程被定义为一个生命周期,而 Plugin 就是在 Webpack 生命周期中,将要被执行的代码。
常见的Plugin有:html-webpack-plugin
插件可以在打包完成后,将打包生成的 js 自动引入到 html 页面中;clean-webpack-plugin
插件可以在打包前自动清理输出目录;copy-webpack-plugin
插件可以将需要转换成 ES5 代码的 js 文件从 dist 目录中COPY 到 dist/es5 目录中等等。
Plugin的实现基本流程可以简述为:
-
在Webpack生命周期中通过生命周期钩子来获得Webpack中要处理的‘资源’。
-
通过‘资源’创建新的‘资源’,并将这些‘资源’添加到Webpack构建流程中。
-
完成插件(Plugin)的生命周期
有关 Plugin 实现的两个示例:
1. html-webpack-plugin插件
样式通常会以 link 标签或者 style 标签的形式插入到 HTML 中。针对这点,我们可以实现一个插件,自动向 HTML 文件中添加 link 或 style 标签,这样在浏览器中访问时,就可以自动加载样式。
const HtmlWebpackPlugin = require('html-webpack-plugin');
class AutoInsertStylePlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('AutoInsertStylePlugin', (compliation, callback) => {
const htmlFileName = 'index.html'; // 设置需要插入 link 或 style 标签的HTML 文件名
let htmlFileContent = null;
for (const asset of compliation.assets) {
const [fileName, fileContent] = Object.entries(asset)[0];
if (fileName === htmlFileName) {
htmlFileContent = fileContent.source(); // 读取 HTML 的文件内容
break;
}
}
if (!htmlFileContent) {
callback();
return;
}
const styleContent = ''; // 通过某种方式获得样式内容
const styles = `<style>${styleContent}</style>`;
const updatedFileContent = htmlFileContent.replace(/<\/head>/, + styles + '</head>'); // 通过正则将新的样式内容添加到 HTML 中
compliation.assets[htmlFileName] = {
source: () => updatedFileContent,
size: () => updatedFileContent.length
}
callback();
});
}
}
// 使用
module.exports = {
plugins: [
new HtmlWebpackPlugin(),
new AutoInsertStylePlugin()
]
};
2. clean-webpack-plugin插件
const fs = require('fs');
class CleanWebpackPlugin {
apply(compiler) {
compiler.hooks.beforeRun.tapAsync('clean-webpack-plugin', (compiler, callback) => {
try {
fs.rmdirSync('./dist', { recursive: true }); // 递归删除 dist 目录以及其下的所有文件
} catch (e) {
console.error(`Error while deleting dist directory : ${e}`);
}
callback();
});
}
}
// 使用
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
};
这个插件会在 Webpack 打包开始之前,将 dist 目录删除,防止不必要的文件增量构建,并在下一次构建开始时清空整个 dist 目录。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:深入webpack打包原理及loader和plugin的实现 - Python技术站