【面试题】Webpack
1. webpack的五大核心
entry
(入口):指示 Webpack 从哪个文件开始打包output
(输出):指示 Webpack 打包完的文件输出到哪里去,如何命名等loader
(加载器):webpack 本身只能处理 js、json 等资源,其他资源需要借助 loaderplugins
(插件):扩展Webpack功能mode
(模式):主要由两种模式:development
、production
2. webpack的构建流程
- 读取参数:从配置文件和shell中读取合并参数
- 开始编译:用参数初始化Compiler对象,加载所有的配置插件,执行Compiler对象的run方法开始编译
- 确认入口:从entry中找到所有的入口文件
- 编译模块:从入口出发,调用配置的loader对模块进行编译,找到该模块依赖的模块,再递归进行本步骤
- 完成编译:得到编译后内容以及依赖关系
- 输出资源:根据入口与模块关系,组装成一个个包含多模块的chunk,再把每个chunk转为一个单独文件输出到列表
- 输出完成:在确定输出内容后,根据配置确定输出的路径和文件名,写入到文件系统
3. 什么是loader?原理是什么?手写一个loader
loader作用:
帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块
loader原理:
loader本质是一个函数,用来处理源代码
loader参数:
content
源文件的内容map
SourceMap 数据meta
数据,可以是任何内容
module.exports = function (content, map, meta) {
// 传递map,让source-map不中断
// 传递meta,让下一个loader接收到其他参数
this.callback(null, content, map, meta);
};
loader分类:
- 同步loader
- 异步loader(如果有异步代码需要使用异步loader)
- Raw loader
- Pitching loader
手写loader例子:
// 用来清理 js 代码中的console.log
module.exports = function cleanLogLoader(content) {
// 将console.log替换为空
return content.replace(/console\.log\(.*\);?/g, "");
};
# 先需要安装依赖
npm i @babel/core @babel/preset-env -D
// loaders/banner-loader/schema.json
{
"type": "object",
"properties": {
"presets": {
"type": "array"
}
},
"additionalProperties": true
}
// 编译 js 代码,将 ES6+语法编译成 ES5-语法。
const schema = require("./schema.json");
const babel = require("@babel/core");
module.exports = function (content) {
const options = this.getOptions(schema);
// 使用异步loader
const callback = this.async();
// 使用babel对js代码进行编译
babel.transform(content, options, function (err, result) {
callback(err, result.code);
});
};
# 先需要安装依赖
npm i loader-utils -D
// 编译 js 代码,将 ES6+语法编译成 ES5-语法。
const schema = require("./schema.json");
const babel = require("@babel/core");
module.exports = function (content) {
const options = this.getOptions(schema);
// 使用异步loader
const callback = this.async();
// 使用babel对js代码进行编译
babel.transform(content, options, function (err, result) {
callback(err, result.code);
});
};
// webpack使用这个loader的配置
{
test: /\.(png|jpe?g|gif)$/,
loader: "./loaders/file-loader.js",
type: "javascript/auto", // 解决图片重复打包问题
},
// 动态创建 style 标签,插入 js 中的样式代码,使样式生效。
const styleLoader = () => {};
styleLoader.pitch = function (remainingRequest) {
/*
remainingRequest: C:\Users\86176\Desktop\source\node_modules\css-loader\dist\cjs.js!C:\Users\86176\Desktop\source\src\css\index.css
这里是inline loader用法,代表后面还有一个css-loader等待处理
最终我们需要将remainingRequest中的路径转化成相对路径,webpack才能处理
希望得到:../../node_modules/css-loader/dist/cjs.js!./index.css
所以:需要将绝对路径转化成相对路径
要求:
1. 必须是相对路径
2. 相对路径必须以 ./ 或 ../ 开头
3. 相对路径的路径分隔符必须是 / ,不能是 \
*/
const relativeRequest = remainingRequest
.split("!")
.map((part) => {
// 将路径转化为相对路径
const relativePath = this.utils.contextify(this.context, part);
return relativePath;
})
.join("!");
/*
!!${relativeRequest}
relativeRequest:../../node_modules/css-loader/dist/cjs.js!./index.css
relativeRequest是inline loader用法,代表要处理的index.css资源, 使用css-loader处理
!!代表禁用所有配置的loader,只使用inline loader。(也就是外面我们style-loader和css-loader),它们被禁用了,只是用我们指定的inline loader,也就是css-loader
import style from "!!${relativeRequest}"
引入css-loader处理后的css文件
为什么需要css-loader处理css文件,不是我们直接读取css文件使用呢?
因为可能存在@import导入css语法,这些语法就要通过css-loader解析才能变成一个css文件,否则我们引入的css资源会缺少
const styleEl = document.createElement('style')
动态创建style标签
styleEl.innerHTML = style
将style标签内容设置为处理后的css代码
document.head.appendChild(styleEl)
添加到head中生效
*/
const script = `
import style from "!!${relativeRequest}"
const styleEl = document.createElement('style')
styleEl.innerHTML = style
document.head.appendChild(styleEl)
`;
// style-loader是第一个loader, 由于return导致熔断,所以其他loader不执行了(不管是normal还是pitch)
return script;
};
module.exports = styleLoader;
4. 常见的loader有哪些
css-loader, scss-loader, less-loader
:将 Css, less, scss 文件编译成 Webpack 能识别的模块style-loader
:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容babel-loader
:解决ES6语法兼容性问题postcss-loader
:解决CSS兼容性问题vue-loader
:加载并编译 Vue 组件react-hot-loader
:实时调整 react 组件
5. 什么是plugin?原理是什么?
plugin作用:
通过插件我们可以扩展 webpack,加入自定义的构建行为,使 webpack 可以执行更广泛的任务,拥有更强的构建能力。
plugin原理:
webpack 在编译代码过程中,会触发一系列 Tapable 钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当 webpack 构建的时候,插件注册的事件就会随着钩子的触发而执行了。
Webpack内部的钩子:
手写plugin例子:
- 作用:分析 webpack 打包资源大小,并输出分析文件。
- 开发思路:
- 在哪做?
compiler.hooks.emit
, 它是在打包输出前触发,我们需要分析资源大小同时添加上分析后的 md 文件。
// 一个最简单的插件
class TestPlugin {
constructor() {
console.log("TestPlugin constructor()");
}
// 1. webpack读取配置时,new TestPlugin() ,会执行插件 constructor 方法
// 2. webpack创建 compiler 对象
// 3. 遍历所有插件,调用插件的 apply 方法
apply(compiler) {
console.log("TestPlugin apply()");
}
}
module.exports = TestPlugin;
// plugins/analyze-webpack-plugin.js
class AnalyzeWebpackPlugin {
apply(compiler) {
// emit是异步串行钩子
compiler.hooks.emit.tap("AnalyzeWebpackPlugin", (compilation) => {
// Object.entries将对象变成二维数组。二维数组中第一项值是key,第二项值是value
const assets = Object.entries(compilation.assets);
let source = "# 分析打包资源大小 \n| 名称 | 大小 |\n| --- | --- |";
assets.forEach(([filename, file]) => {
source += `\n| ${filename} | ${file.size()} |`;
});
// 添加资源
compilation.assets["analyze.md"] = {
source() {
return source;
},
size() {
return source.length;
},
};
});
}
}
module.exports = AnalyzeWebpackPlugin;
6. 常见的plugin有哪些?
html-webpack-plugin
:简化 html 文件创建mini-css-extract-plugin
:将css提取成单独文件css-minimizer-webpack-plugin
:css压缩eslint-webpack-plugin
:使用 eslint来检查js代码terser-webpack-plugin
: 使用 terser来压缩 JavaScript
7. webpack有哪些可以提高效率的插件
HotModuleReplacementPlugin:
热模块更新替换,提高重新打包速度。webpack-merge:
提取公共配置,减少重复配置代码。webpack-dashboard:
可以更有好的展示相关打包信息。size-plugin:
监控资源体积变化,尽早发现问题。speed-measure-webpack-plugin:
简称SMP,分析出 webpack 打包过程中 loader 和 plugin的耗时,有助于找到构建过程中的性能瓶颈。
8. 如何提高webpack的构建速度
- HMR:在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。
- OneOf:顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了。
- Include/Exclude:包含,只处理 xxx 文件,排除某些文件(eslint检查去掉node_modules)
- cache:对 Eslint 检查 和 Babel 编译结果进行缓存。
- thead:多进程打包:开启电脑的多个进程同时干一件事,速度更快。
9. 如何减少webpack的打包体积
- Tree shaking:用于移除 JavaScript 中的没有使用上的代码。
- Image Minimizer:
image-minimizer-webpack-plugin
用来压缩图片的插件 - Code Split:代码分割,需要哪个加载哪个
10. 如何利用webpack优化性能
- OneOf
- Include/Exclude
- Image Minimizer
- Network Cache:使用contenthash来命令文件,当文件内容改了hash才会变