# 什么是 webpack
webpack
是个静态模块打包工具。在 webpack
看来,项目里所有资源皆模块,利用资源依赖关系,把各模块之间关联起来。
简单讲就: webpack
对有依赖关系的多个模块文件进行打包处理后,生成浏览器可以直接 高效运行的资源。
通过 入口文件
开始,利用 递归
找到直接依赖或间接依赖的所有模块,并在内部构建一个能映射出项目所需的所有模块的 依赖图
,并进行 webpack
打包生成一个或多个 bundle
文件。
打包是用的 webpack
命令即:npm webpack
从 webpack 4.x
开始,把 webpack
拆成 webpack
和 webpack-cli
两部分,分工如下:
webpack:
负责 Js 的打包工作
webpack-cli:
解析 webpack
命令,命令内部使用 webpack
的功能
并且 webpack
能解析打包各种模块规范的 Js
代码,包括:ES6、Commonjs、AND/Requirejs 以及 CMD/Seajs
。
优点
- 专注于处理模块化的项目,能做到开箱即用,一步到位
- 可通过
plugin
扩展,完整好用又不失灵活 - 使用场景不局限于
web
开发 - 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展
- 良好的开发体验
缺点
只能用于采用模块化开发的项目
# 什么是 loader ? 什么是 plugin ?
loader:模块转换器,webpack
将一切文件视为模块,但 webpack
只能解析 JavaScript
文件,而 loader 作用是让 webpack
拥有了加载 和 解析非 JavaScript
文件的能力。
plugin:在 webpack
构建流程中的特定时机注入扩展逻辑,让它具有更多的灵活性。在 webpack
运行的生命周期中会广播出许多事件,plugin
可以监听这些事件,在合适的时机通过 webpack
提供的 API 改变输出结果。
用法的区别:
Loader
在 module.rules
中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)
Plugin
在 plugins
中单独配置。 类型为数组,每一项是一个 plugin
的实例,参数都通过构造函数传入。
# 有哪些常见的 Loader ?他们是解决什么问题的?
file-loader
:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
url-loader
:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
source-map-loader
:加载额外的 Source Map 文件,以方便断点调试
image-loader
:加载并且压缩图片文件
babel-loader
:把 ES6 转换成 ES5
css-loader
:加载 CSS,支持模块化、压缩、文件导入等特性
style-loader
:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
eslint-loader
:通过 ESLint 检查 JavaScript 代码
svg-inline-loader
:将压缩后的 SVG 内容注入代码中
json-loader
: 加载 JSON 文件(默认包含)
ts-loader
: 将 TypeScript 转换成 JavaScript
awesome-typescript-loader
:将 TypeScript 转换成 JavaScript,性能优于 ts-loader
sass-loader
:将 CSS 代码注入 JavaScript 中,通过 DOM 操作去加载 CSS
postcss-loader
:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
tslint-loader
:通过 TSLint检查 TypeScript 代码
vue-loader
:加载 Vue.js 单文件组件
# 有哪些常见的 Plugin?他们是解决什么问题的?
define-plugin
:定义环境变量
commons-chunk-plugin
:提取公共代码
terser-webpack-plugin
: 支持压缩 ES6 (Webpack4)
ignore-plugin
:忽略部分文件
html-webpack-plugin
:简化 HTML 文件创建 (依赖于 html-loader)
web-webpack-plugin
:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用
mini-css-extract-plugin
: 分离样式文件,CSS 提取为独立文件,支持按需加载
serviceworker-webpack-plugin
:为网页应用增加离线缓存功能
clean-webpack-plugin
: 删除打包文件
happypack
:实现多线程加速编译
# 如何利用 webpack 来优化前端性能?
用 webpack
优化前端性能是指优化 webpack
的输出结果,让打包的最终结果在浏览器运行快速高效。
1.压缩代码。删除多余的代码、注释、简化代码的写法等等方式。
用 UglifyJsPlugin
和ParallelUglifyPlugin
压缩JS文件
用 mini-css-extract-plugin
压缩 CSS
- 利用 CDN 加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用
webpack
对于output
参数和各loader
的publicPath
参数来修改资源路径 - 删除死代码。JS 用
Tree Shaking
,CSS 需要使用Purify-CSS
- 提取公共代码。用
CommonsChunkPlugin
插件
# 分别介绍 bundle,chunk,module 是什么
bundle
:是由 webpack
打包出来的文件,
chunk
:代码块,一个 chunk
由多个模块组合而成,用于代码的合并和分割。
module
:是开发中的单个模块,在 webpack
的世界,一切皆模块,一个模块对应一个文件,webpack
会从配置的 entry
中递归开始找出所有依赖的模块。
.说一下 webpack 的热更新原理吧
热更新又称热替换(Hot Module Replacement),缩写为 HMR
,基于 webpack-dev-server
。
当你对代码修改并保存后,将会对代码进行重新打包,并将改动的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,去实现局部更新页面而非整体刷新页面。
# webpack 的构建流程是什么?
webpack
的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数:从配置文件 和
Shell
语句中读取与合并参数,得出最终的参数; - 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的
run
方法开始执行编译; - 确定入口:根据配置中的
entry
找出所有的入口文件; - 编译模块:从入口文件出发,调用所有配置的
Loader
对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
完成模块编译:在经过第4步使用Loader
翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系; - 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
Chunk
,再把每个Chunk
转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会; - 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,webpack
会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用webpack
提供的API
改变webpack
的运行结果。
# 如何提高 webpack 的构建速度?
- 多入口情况下,使用
CommonsChunkPlugin
来提取公共代码 - 通过externals配置来提取常用库
- 利用
DllPlugin
和DllReferencePlugin
预编译资源模块 通过DllPlugin
来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin
将预编译的模块加载进来。 - 使用
Happypack
实现多线程加速编译 - 使用
webpack-uglify-parallel
来提升uglifyPlugin
的压缩速度。 原理上webpack-uglify-parallel
采用了多核并行压缩来提升压缩速度 - 使用
Tree-shaking
和Scope Hoisting
来剔除多余代码
# webpack 文件之间相互依赖如何处理?
.npm 打包时需要注意哪些?如何利用 webpack 来更好的构建?
npm 是目前最大的 JavaScript 模块仓库,里面有来自全世界开发者上传的可复用模块。你可能只是 JS 模块的使用者,但是有些情况你也会去选择上传自己开发的模块。 关于 NPM 模块上传的方法可以去官网上进行学习,这里只讲解如何利用webpack来构建。
NPM模块需要注意以下问题:
要支持 CommonJS 模块化规范,所以要求打包后的最后结果也遵守该规则。
Npm模块使用者的环境是不确定的,很有可能并不支持ES6,所以打包的最后结果应该是采用ES5编写的。并且如果ES5是经过转换的,请最好连同SourceMap一同上传。
Npm包大小应该是尽量小(有些仓库会限制包大小)
发布的模块不能将依赖的模块也一同打包,应该让用户选择性的去自行安装。这样可以避免模块应用者再次打包时出现底层模块被重复打包的情况。
UI组件类的模块应该将依赖的其它资源文件,例如.css文件也需要包含在发布的模块里。
基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化:
CommonJS模块化规范的解决方案: 设置output.libraryTarget='commonjs2'使输出的代码符合CommonJS2 模块化规范,以供给其它模块导入使用
输出ES5代码的解决方案:使用babel-loader把 ES6 代码转换成 ES5 的代码。再通过开启devtool: 'source-map'输出SourceMap以发布调试。
Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件
不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。
对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现
.文件指纹是什么?怎么用?
文件指纹是打包后输出的文件名的后缀。
Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改
Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash
Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变
JS的文件指纹设置
设置 output 的 filename,用 chunkhash。
module.exports = {
entry: {
app: './scr/app.js',
search: './src/search.js'
},
output: {
filename: '[name][chunkhash:8].js',
path:__dirname + '/dist'
}
}
CSS的文件指纹设置
设置 MiniCssExtractPlugin 的 filename,使用 contenthash。
module.exports = {
entry: {
app: './scr/app.js',
search: './src/search.js'
},
output: {
filename: '[name][chunkhash:8].js',
path:__dirname + '/dist'
},
plugins:[
new MiniCssExtractPlugin({
filename: `[name][contenthash:8].css`
})
]
}
图片的文件指纹设置
设置file-loader的name,使用hash。
占位符名称及含义
ext 资源后缀名
name 文件名称
path 文件的相对路径
folder 文件所在的文件夹
contenthash 文件的内容hash,默认是md5生成
hash 文件内容的hash,默认是md5生成
emoji 一个随机的指代文件内容的emoji
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename:'bundle.js',
path:path.resolve(__dirname, 'dist')
},
module:{
rules:[{
test:/\.(png|svg|jpg|gif)$/,
use:[{
loader:'file-loader',
options:{
name:'img/[name][hash:8].[ext]'
}
}]
}]
}
}
.在实际工程中,配置文件上百行乃是常事,如何保证各个loader按照预想方式工作?
可以使用 enforce 强制执行 loader 的作用顺序,pre 代表在所有正常 loader 之前执行,post 是所有 loader 之后执行。(inline 官方不推荐使用)