前端基础 | Webpack 基础加面试题收集

Webpack 基础加面试题收集

webpack基础,自己配置 webpack 进行资源打包

这只是我个人学习整理的个人笔记,可以直接跳过前面去看文章中的「参考文章」

重要提示

纸上得来终觉浅,绝知此事要躬行

请一定动手敲一敲代码,看一看效果

请一定动手敲一敲代码,看一看效果

请一定动手敲一敲代码,看一看效果

Webpack介绍

一个现代 JavaScript 应用程序的静态模块打包器,当 webpack 处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle

Webpack 仅能理解 JavascriptJSON 文件,需要通过 Loader 来转换

Webpack核心概念

  • **Entry**(入口)
  • **Output**(输出)
  • **Loader**(允许 Webpack 处理他类型的文件,并将其转换为可被您的应用程序使用并添加到依赖关系图的有效模块。)
  • **Plugins**(插件是 Webpack的基础,可用来处理任何 Loader 不能处理的事情)
  • **Mode**(配置编译环境,可选: developmentproductionnone, 默认值production
  • **Browser Compatibility**(浏览器兼容性,支持所有兼容 ES5 的浏览器「IE8及以下版本除外」,若要支持旧版浏览器,需要先加载一个polyfill

Webpack 安装与项目初始化

1. 项目初始化

创建文件夹 webpack-demo,进入文件夹执行

1
npm init -y

从 webpack 4 版本开始,webpack-cli 分离成一个单独的模块,安装 webpack 时还需要单独安装 webpack-cli

不建议全局安装 webpack,采用本地安装的方式

1
npm install webpack webpack-cli --save-dev

webpack-demo 目录下新建 webpack.config.js

entry 配置

Simple rule: one entry point per HTML page. SPA: one entry point, MPA: multiple entry points.

一个HTML页面一个入口,单页面一个入口,多页面多个入口

官方地址:entry-context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
entry: './src/index.js' //webpack的默认配置
}
module.exports = {
entry: {
app: './src/index.js' // app是输出的文件名,output中配置了filename后,这个名字无效
} //
}
module.exports = {
entry: [
'./src/index.js',
'./src/index2.js'
]
}

entry 的配置可以是 字符串数组对象

output 配置

1
2
3
4
5
6
7
8
9
10
const path = require('path'); // node提供
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'), //输出到的文件夹
filename: 'bundle.js', // 编译后输出的文件名
publicPath: '/' //通常是CDN地址
}
}

Webpack 打包 js 文件

本文所使用的 webpack 版本:

1
2
webpack@4.42.1
webpack-cli@3.3.11

Webpack v4 开始,不引入任何配置文件的情况下也可以使用。

webpack-demo 下创建 src/index.js,在其中写点内容

1
2
let arr = [1,2,3]
arr.map((item) => {console.log(item)})

执行 npx webpack --mode=development 进行打包。

执行完后,在 webpack-demo 下可看到一个 dist 文件夹,其中的 main.js 文件,即为默认打包后的文件。

查看 main.js 文件

1
2
3
4
5
6
7
8
9
10
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! no static exports found */
/***/ (function(module, exports) {

eval("let arr = [1,2,3]\narr.map((item) => {console.log(item)})\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })

代码还是箭头函数,没有被打包成低版本代码,这不是我们所需要的。此时需要使用 webpack 的 babel-loader 来将代码转换到低版本。

安装 babel-loader

1
npm install babel-loader --save-dev

安装 babel 依赖:推荐读下:不容错过的 Babel7 知识

1
2
3
npm install @babel/core @babel/preset-env @babel/plugin-transform-runtime --save-dev

npm install @babel/runtime @babel/runtime-corejs3

配置 loader

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module: {
rules:[
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/
}
]
}
}

创建 .babelrc 文件,配置 babel

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
38
39
// 配置babel方式一
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}

// 配置 babel 方式二:在webpack.config.js中配置

module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
},
exclude: /node_modules/
}
]
}
}

loader 是从右向左(或者从下至上)执行的

比如要配置 less-loader,还需要配置 css-loaderstyle-loader,代码如下

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module: {
rules: [
{
test: /\.(le|c)ss$/,
use: ['style-loader', 'css-loader', 'less-loader'],
exclude: /node_modules/
}
]
}
}

webpack 使用 loader 的方式建议阅读官方文档:Webpack Using Loaders

设置 mode

告知 webpack 使用相应的模式进行优化打包,打包出的文件有所不同。

默认值: production

可设置: noneproductiondevelopment

Option Description
development Sets process.env.NODE_ENV on DefinePlugin to value development . Enables NamedChunksPlugin and NamedModulesPlugin .
production Sets process.env.NODE_ENV on DefinePlugin to value production . Enables FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin , OccurrenceOrderPlugin , SideEffectsFlagPlugin and TerserPlugin .
none Opts out of any default optimization options

将打包后的 js 文件自动添加到 html 中

需要用到插件 html-webpack-plugin

1
npm install html-webpack-plugin --save-dev

webpack-demo 目录下新建 public/index.html

修改 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
...,
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html', // 打包后的文件名
})
]
}

安装 cross-env,用来提供一个兼容性好的 scripts 来使用环境变量

1
npm install crosee-env --save-dev

具体用法,在 packge.json 中的 scripts 中添加内容

1
"start": "cross-env NODE_ENV=development webpack",

之前编译执行的是 npx webpack --mode=development,现在只需要执行 npm run start

如何在浏览器中实时展示效果

使用到的插件 webpack-dev-server

1
npm install webpack-dev-server --save-dev

修改 package.json

1
"start": "cross-env NODE_ENV=development webpack-dev-server",

配置 webpack-dev-server

官方文档:dev-server

1
2
3
4
5
6
7
8
9
10
11
12
13
// webpack.config.js
module.expots= {
...,
devServer: {
port: '8080', //默认是8080
quiet: false, //默认不启用,如果开启了,控制台不会看到除了初始启动信息外的任何console信息,包括错误提示
inline: true, //默认开启 inline 模式,如果设置为false,开启 iframe 模式
stats: 'errors-only', //终端仅打印 error
overlay: true, //默认不启用,是否全屏显示编译的错误信息
clientLogLevel: "silent", //日志等级
compress: true //是否启用 gzip 压缩
}
}

DevTool配置

方便我们在控制台看到我们在代码中的console信息或者错误信息的实际行数,方便定位问题,否则控制台会显示编译后的位置,和实际位置打不一样

官方可选参数:devtool配置

修改 webpack.config.js

1
2
3
4
module.exports = {
...,
devtool: 'cheap-module-eval-source-map' //开发环境下使用,线上设置为 none 或者 source-map
}

编译 less 文件

需要用到插件 less-loadercss-loaderstyle-loaderpostcss-loaderautoprefixer,后两个是自动添加兼容性前缀。

1
npm install style-loader less-loader css-loader postcss-loader autoprefixer --save-dev

新建文件 src/index.less

1
2
3
4
@color: red;
body{
background-color: @color;
}

修改 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
module.exports = {
...,
module: {
rules:[
...,
{
test: /\.(le|c)ss$/,
use: ['style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: function () {
return [
require('autoprefixer')({
"overrideBrowserslist": [
">0.25%",
"not dead"
]
})
]
}
}
}, 'less-loader'],
exclude: /node_modules/
}
]
}
}

推荐在根目录新建 .browserslistrc 文件来配置 postcss-loader

处理图片/字体文件等

需要用到插件 url-loaderfile-loader

url-loader 处理资源时,将配置的limit限制大小以内的资源以 DataURL 返回

官方文档:url-loaderfile-loader

1
npm install url-loader file-loader --save-dev

配置 webpack.config.js,参考 vue-cli2.x 构建的项目配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
...,
module: {
rules:[
...,
{
test: /\.(woff2?|eot|ttf|otf|png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10240, // 10k,大于 10k 将使用 file-loader
esModule: false, // file-loader的配置,默认使用ES Module,否则使用 CommonJS
}
}
]
}
}

清空 dist 文件夹

每次打包时,旧的打包文件不是基本不是我们需要的,所以需要清空文件夹,懒得手动去删除文件夹,需要使用插件 clean-webpack-plugin

每次执行打包后都会清空文件夹中的内容重新生成

1
npm install clean-webpack-plugin -D

修改 webpack.config.jsplugins

1
2
3
4
5
6
7
8
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
...,
plugins: [
...,
new CleanWebpackPlugin()
]
}

最后

一个基础的webpack就配置完了

参考文章

官方文档地址,建议随时查阅巩固:Webpack官网

祭出掘金大佬的三篇文章,从基础到进阶都有,跟着做完肯定有收获

一些 webpack 相关面试题

Webpack 的热更新原理

Webpack 的热更新又称热替换 (Hot Module Replacement),缩写为 HMR。 这个机制可以做到 「不用刷新浏览器」 而将新变更的模块替换掉旧的模块。

  1. webpack 使用 webpack-dev-server 启动一个本地服务
  2. 启动的本地服务与浏览器之间维护了一个 Websocket
  3. webpack 监听源文件的变化,当开发者保存文件时触发webpack的重新编译,编译完成后通过socket向客户端推送当前编译的hash值
  4. 客户端通过websocket接收到推送过来的hash值
  5. 客户端接收到 hash值 后,通过 Ajax 向服务端发送请求,服务端返回包含所有需要更新模块的的hash值构成的的一个json
  6. 获取到 json 后,再通过 jsonp 获取到最新的模块代码。
  7. 通过 HotModulePlugin 进行更新处理,

细节参考:Webpack HMR 原理解析

webpack的构建流程

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,开始执行编译;
  3. 确定入口:根据配置中的 entry 找出所有的入口文件;
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

如何提高webpack的构建速度

  1. 多入口情况下,使用 CommonsChunkPlugin 来提取公共代码
  2. 通过 externals 配置来提取常用库
  3. 利用 DllPluginDllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。
  4. 使用 Happypack 实现多线程加速编译
  5. 使用 webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采用了多核并行压缩来提升压缩速度
  6. 使用 Tree-shakingScope Hoisting 来剔除多余代码

题目后续还会再收集添加一部分