# 什么是 Webpack
webpack是个静态模块打包工具,在webpack看来项目里所有资源皆模块,利用资源依赖关系把各模块之间联系起来。
简单讲:webpack对有依赖关系的多个模块文件进行打包处理后,生成浏览器可以直接高效运行的资源。通过入口文件开始,利用递归找到直接或间接依赖的所有模块,并在内部构建一个能映射出项目所有模块的依赖图,并进行webpack打包生成一个或多个bundle文件。
打包的命令:npm webpack
从webpack 4.x开始,把webpack拆分成webpack和webpack-cli两部分:
webpack:负责JS的打包工作webpack-cli:解析webpack命令,命令内部使用webpack的功能并且webpack能解析打包各模块的JS代码,包括ES6、Commonjs、AND/Requiresjs和Seajs等。
优点
- 专注于处理模块化的项目,能做到开箱即用,一步到位
- 可通过
plugin扩展,完整好用又不失灵活 - 使用场景不局限于
web开发 - 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展
- 良好的开发体验
缺点
只能用于采用模块化开发的项目
# 运行 npm run xxx 的时候发生了什么?
运行npm run xxx的时候,首先会去项目的package.json文件里找scripts中对应的xxx并执行xxx的命令:例如启动Vue项目运行npm run serve时实际上就是执行了vue-cli-service serve这条命令。
疑问1:为什么不直接执行
vue-cli-service serve命令?
因为系统中没有vue-cli-service这条命令,所以会报错
疑问2:既然
vue-cli-service这条指令不存在操作系统中,为什么执行npm run serve时,也就是相当于执行vue-cli-service serve命令时就能成功,而且还不报指令不存在的错误呢?
其实也很简单:在安装依赖时,是通过npm i xxx来执行的,例如npm i @vue/cli-service。npm在安装这个依赖时,会在node_modules/.bin/目录中创建好vue-cli-service为名的几个可执行文件:vue-cli-service、vue-cli-service.cmd、vue-cli-service.ps1
.bin目录不是任何一个npm包,目录下的文件表示这是一个个软链接,打开文件可看到文件顶部写着#!/bin/sh,表示这是个脚本。
由此可知:当使用npm run serve执行vue-cli-service serve时,虽然没有安装vue-cli-service的全局命令,但npm会到./node_modules/.bin中找到vue-cli-service文件作为脚本来执行,则相当于执行了./node_modules/.bin/vue-cli-service serve(最后的serve作为参数传入)。
疑问3:
.bin目录下的文件表示软连接,那软连接文件是哪里来的?又是怎么知道这条软连接是执行哪里的?
当我们执行npm i命令为Vue项目安装依赖包时,npm将bin/vue-cli-service.js作为bin在package-lock.json文件中声明了。
所以在npm i时,npm读到该配置后,就将该文件软链接到./node_modules/.bin目录下,而npm还会自动把node_modules/.bin加入$PATH,这样就可以直接作为命令运行依赖程序和开发依赖程序,不用全局安装了。
例如使用npm install -g xxx来安装某个包时,会将其中的bin文件加入到全局,比如create-react-app和vue-cli,在全局安装后,就可以直接使用如vue-cli projectName这样的命令来创建项目了。
问题4:
node_modules/bin中为什么会有三个vue-cli-service文件呢?
在cmd里运行时,windows一般是调用了vue-cli-service.cmd这个文件,即当我们运行vue-cli-service serve这条命令时相当于运行 node_modules/.bin/vue-cli-service.cmd serve。然后这个脚本会使用node去运行vue-cli-service.js这个js文件
由于node中可以使用一系列系统相关的api,所以在这个js中可以做很多事情,例如读取并分析运行这条命令的目录下的文件,根据模板生成文件等。
unix 系默认的可执行文件,必须输入完整文件名
vue-cli-service
# windows cmd 中默认可执行文件,不添加后缀名时,自动根据 pathext 查找文件
vue-cli-service.cmd
# Windows PowerShell 中可执行文件,可以跨平台
vue-cli-service.ps1
综上简单将就是:npm i时npm就帮我们把软连接配置好了,其实软连接相当于一种映射,执行npm run xxx的时候,就会到node_modules/.bin中找对应的映射文件,然后再找到相应的js文件来执行,总结记起来有三步:
- 运行
npm run xxx时npm会先在当前目录的node_modules/.bin查找要执行的程序,如果找到则运行; - 没有找到则从全局的
node_modules/.bin中查找(npm i -g xxx就是安装到到全局目录); - 如果全局目录还是没找到,那么就从
path环境变量中查找有没有其他同名的可执行程序。
# 如何利用 webpack 来优化前端性能?
用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。
- 压缩代码。删除多余的代码、注释、简化代码的写法等等方式。
用
UglifyJsPlugin和ParallelUglifyPlugin压缩JS文件 用mini-css-extract-plugin压缩CSS - 利用
CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径。 - 删除死代码。
JS用Tree Shaking,CSS需要使用Purify-CSS - 提取公共代码。用
CommonsChunkPlugin插件
# 什么是 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 转换成 ES5css-loader:加载 CSS,支持模块化、压缩、文件导入等特性style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。eslint-loader:通过 ESLint 检查 JavaScript 代码svg-inline-loader:将压缩后的 SVG 内容注入代码中json-loader: 加载 JSON 文件(默认包含)ts-loader: 将 TypeScript 转换成 JavaScriptawesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loadersass-loader:将 CSS 代码注入 JavaScript 中,通过 DOM 操作去加载 CSSpostcss-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 的热更新原理
热更新又称热替换(Hot Module Replacement)缩写为 HMR,基于 webpack-dev-server。当你对代码修改并保存后,将会对代码进行重新打包,并将改动的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,去实现局部更新页面而非整体刷新页面。
# webpack 的构建流程是什么?
webpack的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
(1)初始化参数 (2)开始编译 (3)确定入口 (4)编译模块 (5) 输出资源 (6)输出完成
# 如何提高 webpack 的构建速度?
- 多入口情况下,使用
CommonsChunkPlugin来提取公共代码 - 通过externals配置来提取常用库
- 利用
DllPlugin和DllReferencePlugin预编译资源模块通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。 - 使用
Happypack实现多线程加速编译 - 使用
webpack-uglify-parallel来提升uglifyPlugin的压缩速度。原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度 - 使用
Tree-shaking和Scope Hoisting来剔除多余代码
# 文件指纹是什么?怎么用?
文件指纹是打包后输出的文件名的后缀。
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`
})
]
}