备注:本文是总结他人资料,供面试背诵使用
1.说说你对webpack的理解
开发时,我们会使用框架 (React、Vue) ,ES6 模块化语法,Less/Sass 等 CSS 预处理器等语法进行开发,这样的代码要想在浏览器运行必须经过编译成浏览器能识别的 JS、CSS语法才能运行。所以我们需要打包工具帮我们做完这些事。除此之外,打包还能压缩代码、做兼容性处理、提升代码性能等。
webpack 是一个静态模块的打包工具。它会在内部从一个或多个入口点构建一个依赖图,然后将项目中所需的每一个模块组合成一个或多个 bundles 进行输出,它们均为静态资源。输出的文件已经编译好了,可以在浏览器运行。 webpack 具有打包压缩、编译兼容、能力扩展等功能。其最初的目标是实现前端项目的模块化,也就是如何更高效地管理和维护项目中的每一个资源。
2.说说webpack的构建流程
初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
确定入口:根据配置中的 entry 找出所有的入口文件
编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表
输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
在以上过程中,webpack会在特定的时间点广播出特定的事件,插件在监听到事件后会执行特定的逻辑,并且插件可以调用webpack提供的API改变webpack的运行结果
3.Loader是什么?
其作用是让 Webpack 能够去处理那些非 JavaScript 文件。由于 Webpack 自身只理解 JavaScript、JSON ,其他类型/后缀的文件都需要经过 loader 处理,并将它们转换为有效模块。loader 可以是同步的,也可以是异步的;而且支持链式调用,链中的每个 loader 会处理之前已处理过的资源。
loader的两个属性:
1.test,识别出哪些文件会被转换
2.use,在进行转换时,使用的loader
多loader的执行顺序,从右到左(从下到上)
4.常用的Loader
babel-loader 将ES6代码转换成ES5版本
ts-loader ts转js 如果没有babel,使用ts-loader,有babel用@babel/preset-typescript
sass-loader sass转换css
style-loader 创建
css-loader 将css转为js模块导出
url-loader url-loader可以在图片大小小于设定的limit的时候返回的是base编码的图片,大于limit时会调用file-loader对图片进行处理。
file-loader 其实就是将文件拷贝到输出目录下,使用file-loader中配置的新名字
postcss-loader (1)将css解析成js可以操作的AST,(2)调用插件来处理AST并得到结果。
经常配合autoprefixer自动为CSS添加浏览器前缀
html-loader 将html代码转换为js模块导出
eslint-loader 检查代码是否符合eslint规范,只检查错误,不修改代码
5.手写Loader
const loaderUtils = require('loader-utils');
module.exports = function(source){
// 知识点一 如果为每个构建执行重复的转换操作,这样webpack的构建可能会变得非常慢
// 开启缓存
this.cacheable && this.cacheable();
// 知识点二 使用loaderUtils 获取loader的参数
const options = loaderUtils.getOptions(this);
console.log('options ->', options);
// 知识点三 告诉 Webpack 本次转换是异步的,Loader 会在 callback 中回调结果
// 异步用this.axync() 同步用this.callback()
var callback = this.async()
// someAsyncOperation 代表一些异步的方法
someAsyncOperation(source).then(res => {
// 通过 callback 返回异步执行后的结果
callback(err, result, sourceMaps, ast)
})
// 真正的手写代码
const reg = /(console.log((.*)))/g;
source = source.replace(reg, '');
this.callback(null, source);
// return undefined的作用是让webpack知道loader返回的结果应该在this.callback当中,而不是return中
return;
}
/**
this.callback(
// 当无法转换原内容时,给 Webpack 返回一个 Error
err: Error | null,
// 原内容转换后的内容
content: string | Buffer,
// 用于把转换后的内容得出原内容的 Source Map,方便调试
sourceMap?: SourceMap,
// 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
abstractSyntaxTree?: AST
);
*/
6.Plugins是什么?
由于webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的时机执行自己的插件任务,webpack有两个核心对象:
compiler:包含了 webpack 环境的所有的配置信息,包括 options,loader 和 plugin,和 webpack 整个生命周期相关的钩子
compilation:作为 plugin 内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化,一次新的 Compilation 将被创建
1.插件必须是一个带有apply方法的对象
2.不建议修改compiler和compilation对象,因为传入的同一个对象的引用
class CopyrightWebpackPlugin {
// 编写一个构造器
// 参数初始化
constructor(options) {
console.log(options)
}
apply(compiler) {
//遇到同步时刻 同步用tap
compiler.hooks.compile.tap('CopyrightWebpackPlugin',() => {
console.log('compiler');
});
//遇到异步时刻 异步用tapAsync
// emit 输出asset到output目录之前执行
//Compilation存放打包的所有内容,Compilation.assets放置生成的内容
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (Compilation, callback) => {
debugger;
// 往代码中增加一个文件,copyright.txt
Compilation.assets['copyright.txt'] = {
source: function() {
return 'copyright by monday';
},
size: function() {
return 19;
}
};
callback();
})
}
}
module.exports = CopyrightWebpackPlugin;
7.常用的Plugin
1.HtmlWebpackPlugin
2.CleanWebpackPlugin
3.CopyWebpackPlugin
4.Webpack.DefinePlugin 定义全局变量
5.MiniCssExtractPlugin 将css抽取成单独的文件
6.PurgeCSSPlugin 删除没用到的CSS样式,需要判断引用文件,起到css tree-shaking的效果
7.webpack.ProvidePlugin 配置全局模块,避免多次引入的麻烦
8.BundleAnalyzerPlugin 分析打包后资源的依赖以及大小
9.ImageminPlugin 压缩图片
10.CompressionPlugin 启用传输压缩,gzip压缩,需要服务端配合
11.webpack.NoEmitOnErrorsPlugin 遇到编译报错不输出
12.OptimizeCssAssetsPlugin 压缩css,去除重复的类名样式,去除无用的空格
13.UglifyJSPlugin 压缩JS
14.webpack.HotModuleReplacementPlugin 热更新插件
15.TerserPlugin 压缩es6,相比UglifyJsPlugin插件,能更好的处理ES6以上的语法
8.实现一个Plugin
由于webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的时机执行自己的插件任务,webpack有两个核心对象:
compiler:包含了 webpack 环境的所有的配置信息,包括 options,loader 和 plugin,和 webpack 整个生命周期相关的钩子
compilation:作为 plugin 内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化,一次新的 Compilation 将被创建
在这里插入代码片class CopyrightWebpackPlugin {
// 编写一个构造器
// 参数初始化
constructor(options) {
console.log(options)
}
apply(compiler) {
//遇到同步时刻 同步用tap
compiler.hooks.compile.tap('CopyrightWebpackPlugin',() => {
console.log('compiler');
});
//遇到异步时刻 异步用tapAsync
// emit 输出asset到output目录之前执行
//Compilation存放打包的所有内容,Compilation.assets放置生成的内容
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (Compilation, callback) => {
debugger;
// 往代码中增加一个文件,copyright.txt
Compilation.assets['copyright.txt'] = {
source: function() {
return 'copyright by monday';
},
size: function() {
return 19;
}
};
callback();
})
}
}
module.exports = CopyrightWebpackPlugin;
9.Source map是什么?
source map是将编译、打包、压缩后的代码映射和回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试代码就需要source-map,提高开发效率
10.文件指纹是什么?
文件指纹是打包文件的唯一标识,文件指纹通常有两个用途:
-
版本管理: 在发布版本时,通过文件指纹来区分 修改的文件 和 未修改的文件。
-
使用缓存: 未修改的文件,文件指纹保持不变,浏览器继续使用缓存访问。
Hash 是和整个项目的构建相关,
compilation
实例的变化就会触发 Hash 的变化。Chunkhash 是和 webpack 打包的模块相关,每一个 entry 作为一个模块,会产生不同的 Chunkhash 值,所以他们之间的变化是互不影响的。
Contenthash 是和根据文件内容相关,比如一个页面内的 JS 内容、CSS 内容都会拥有自己的 Contenthash,可以保持各自的独立更新。
11.说说webpack热更新的原理
HMR全称Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换,添加,删除模块,而无需重新刷新整个应用。
例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失如果使用的是 HMR
,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用
webpack的原理:
-
启动阶段
1-2-A-B 在编写为经webpack打包的源代码后,webpacl compiler将源代码和HMR Runtime一起编译成bundle文件,放在webpack Dev Server,当浏览器请求bundle.js文件时,bundle.js就返回给浏览器,运行在浏览器上
(socket.js在服务器和浏览器建立了一个websocket长链接) -
更新阶段
1-2-3-4 当某一个文件或者模块发生变化时,webpack监听到文件变化对文件重新编译打包,编译生成唯一的hash值,本次生成的hash值会作为下一次热更新的标识,也就是本次热更新使用的hash值,是上一次热更新生成文件的hash(此次的hash值是上次热更新中manifest中h的值)由于
socket
服务器在HMR Runtime
和HMR Server
之间建立websocket
链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的hash
值,如下图的h
属性,作为下一次热更细的标识
浏览器根据hash标识,去创建ajax去获取此次的mainfest(h:build生成的hash值,c:变化的模块 )文件,使用jsonp获取此次的修改内容,浏览器拿到这两个文件后,通过HMR runtime机制,加载这两个文件,触发render流程,实现局部刷新
12.说说webpack proxy工作原理
在开发阶段, webpack-dev-server
会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost
的一个端口上,而后端服务又是运行在另外一个地址上
所以在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题
通过设置webpack proxy
实现代理请求后,相当于浏览器与服务端中添加一个代理者
当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地
在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常接收数据
注意:服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制
13.webpack打包体积优化
一、提取公共模块
假如现在有一个MPA(多页面应用)的react项目,每个页面的入口文件及其依赖的组件中都会引入一份react和react-dom等,那最终打包后的每个页面中同样也会有一份以上的公共包的代码,可以将这个包单独抽离出来,最终在每个打包后的页面入口文件引入,从而减少打包后的总体积
module.exports = {
optimization: {
splitChunks: {
minSize: 20000,
cacheGroups: {
react: {
test: /(react|react-dom)/,
name: 'vendors',
chunks: 'all',
},
},
},
}
};
二、配置CDN服务器
1.可以修改output:{publicPath: ‘’} 的值,打包时添加上自己的CDN地址
2.在CDN上部署自己依赖的第三方资源,
3.在externals中加入不进行打包的资源,在html模版中加入CDN服务器地址
4.如果没有自己配置CDN服务器,可以使用其他人放置在cdn服务器上的资源
三、代码压缩(此处的代码压缩并非真正的压缩,而是删掉代码中无意义的部分,例如空格)
1.压缩js,使用插件TerserPlugin
2.压缩css,使用CSSMinimizerPlugin
css压缩通常是去除无用的空格等,因为很难去修改选择器,属性和名称、值等
3.压缩html,使用HtmlWebpackPlugin的minify配置
new HtmlWebpackPlugin({
template: path.join(__dirname, './public/index.html'),
filename: 'index.html',
minify: { // minify配置可以压缩html文件,设置了minify,实际上会使用另一个插件html-minifier-terser
minifyCSS: false, //是否压缩css
collapseWhitespace: true, // 是否折叠空格
removeComments: true, // 是否移除注释
}
}),
4.压缩图片,使用image-webpack-loader
这个不太理解,压缩后图片会不会失真或者像素变低?打包的时候压缩,使用的时候进行解压缩?
四、使用TreeShaking
tree shaking是一个术语,在计算机中标识消除死代码,尽量使用纯函数编程,用于消除未调用的代码
1.JS实现Tree Shaking
(1)usedExports,配置方法很简单,只需将usedExports设置为true
module.exports = {
...
optimization:{
usedExports
}
}
使用后,没被用上的代码在webpack打包中会添加 unused harmony export … 注释,用来告知Terser在优化时,可以删掉这段代码
(2)sideEffects实现
sideEffects用于指定哪些模块是有副作用的,副作用指的是这里面的代码有执行一些特殊的任务,不能仅仅依靠export来判断这段代码的意义
sideEffect设置为false,就是告知webpack可以安全的删除未用到的exports
sideEffect设置为数组,则将代码中的数据进行保留
总结:
- 在optimization中配置usedExports为true,来帮助Terser进行优化;
- 在package.json中配置sideEffects,直接对模块进行优化;
2.CSS实现Tree Shaking
css主要使用的purgecss-webpack-plugin
五、文件大小压缩(真正意义的压缩,gzip算法)
compression-webpack-plugin 插件
对文件的大小进行压缩,将前端打包好的资源文件进一步压缩,生成指定的、体积更小的压缩文件,减小http传输过程中带宽的损耗
new ComepressionPlugin({
test:/\.(css|js)$/, // 哪些文件需要压缩
threshold:500, // 设置文件多大开始压缩
minRatio:0.7, // 至少压缩的比例
algorithm:"gzip", // 采用的压缩算法
})
使用npm run build,会生成许多以.gz格式的文件
生成压缩后的文件,不能直接使用,需要服务端配置才可以使用,而且发现打包生成的“dist/index.html”首页内,也没有直接引用这些“.gz”格式的文件。
而实现的关键,其实就是让服务端向浏览器发送“Content-Encoding=gzip”这个响应头,并把对应的“.gz”格式文件发送给浏览器,让浏览器通过“gzip”编码格式来解析资源。
const path = require('path');
const fs = require('fs');
const express = require('express');
const app = express();
app.use((request, response, next) => {
const fullPath = path.join(__dirname, `${request.originalUrl}.gz`);
// 检测是否存在同名.gz压缩文件
if (fs.existsSync(fullPath)) {
// 存在就告诉浏览器用gzip编码格式来解析,并把对应的“.gz”格式文件发送给浏览器。
response.setHeader('Content-Encoding', 'gzip')
response.sendFile(fullPath);
} else {
next()
}
})
app.use(express.static('./'));
app.listen(1055, _ => {
console.log('1055服务器已经启动');
});
六、antd按需导入
import {Col,Row} from 'antd'
14.优化构建速度
这个问题个人认为比较宽泛,顺着聊几点就好了,答不上来也没关系,重点是上面的压缩打包后的体积
可以使用speed-measure-webpack-plugin统计总打包耗时以及每个plugin和loader的打包耗时
一、缩小构建范围、优化Loader配置
在使用loader时,可通过配置include、exclude、test属性来匹配文件
module.exports = {
module: {
rules: [
{
// 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
test: /\.js$/,
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
use: ['babel-loader?cacheDirectory'],
// 只对项目根目录下的 src 目录中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
]
},
};
二、优化resolve.alias
alias
给一些常用的路径起一个别名,特别当我们的项目目录结构比较深的时候,一个文件的路径可能是./../../
的形式
通过配置alias
以减少查找过程
module.exports = {
...
resolve:{
alias:{
"@":path.resolve(__dirname,'./src')
}
}
}
三、多进程构建
对于耗时较长的模块,同时开启多个 nodejs 进程进行构建,可以有效地提升打包的速度。terser启动多线程
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
};
四、合理使用source-map
打包生成sourceMap的时候,信息越详细,打包速度就越慢
五、cache-loader
在一些性能开销较大的 loader
之前添加 cache-loader
,以将结果缓存到磁盘里,显著提升二次构建速度文章来源:https://www.toymoban.com/news/detail-648729.html
保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader
使用此loader
文章来源地址https://www.toymoban.com/news/detail-648729.html
module.exports = {
module: {
rules: [
{
test: /\.ext$/,
use: ['cache-loader', ...loaders],
include: path.resolve('src'),
},
],
},
};
到了这里,关于前端Webpack面试题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!