前言
之前我们大多都是用Vue-Cli来创建项目,但是Vue-Cli已经停止更新了,并且Vue-Cli相当于一堆插件的集合体,我们想替换以下,或者想根据我们的项目优化以下,提升编译的性能,这时候可以自己用Webpack来配置项目。
在搭建的时候最头疼的几个问题
- 依赖下载不下来
- 依赖之间不兼容
- 依赖和NodeJS版本不兼容
安装cnpm 可以解决依赖无法下载的问题
1 | npm install -g cnpm --registry=https://registry.npm.taobao.org |
本文是在Node v12.22.6
的版本下测试的。
配置步骤
基本配置
创建项目文件夹 webpack01
进入项目文件夹根目录,运行
1 | npm init |
安装基础依赖
1 | npm i -D webpack@5.74.0 webpack-cli@4.10.0 webpack-dev-server@4.10.0 |
注意
vue-template-compiler要和vue的版本一致
创建以下文件夹及文件
/public/index.html
1 |
|
/src/App.vue
1 | <template> |
/src/main.js
1 | import App from './App'; |
/webpack.config.js
1 | const path = require('path'); |
pacakge.json 中添加 scripts
配置
1 | { |
这时候就能运行了
1 | npm run start |
打包
1 | npm run build |
查看webpack的版本
1 | npx webpack --version |
Vue Loader简介
https://vue-loader.vuejs.org/zh/guide/#vue-cli
Vue Loader 的配置和其它的 loader 不太一样。
除了通过一条规则将 vue-loader
应用到所有扩展名为 .vue
的文件上之外,请确保在你的 webpack 配置中添加 Vue Loader 的插件:
1 | // webpack.config.js |
这个插件是必须的!
它的职责是将你定义过的其它规则复制并应用到 .vue
文件里相应语言的块。
例如,如果你有一条匹配 /\.js$/
的规则,那么它会应用到 .vue
文件里的 <script>
块。
处理HTML
HTML中根据变量取值
1 | const path = require('path'); |
HTML中取值
1 | <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> |
处理JS
安装babel
添加依赖
1 | npm i -D babel-loader@8.2.5 @babel/core@7.18.13 |
@babel/plugin-transform-runtime
有三大作用,其中之一就是自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers
里的辅助函数来替代。这样就减少了我们手动引入的麻烦。
现在我们除了安装@babel/runtime
包提供辅助函数模块,还要安装Babel插件@babel/plugin-transform-runtime
来自动替换辅助函数。
作用
babel-loader:只是和webpack之间的桥梁,并不会把es6语法进行转换。
@babel/preset-env @babel/polyfill是做转换的。
以上babel的配置是官网提供主要用来解决业务代码js语法转译用的,当要生成类库或者组件库时上面这种配置会污染全局变量,需要使用@babel/plugin-transform-runtime
在根目录下创建 babel 配置文件 .babelrc
:
1 | { |
注意:"corejs": 2
, // 这里设置2是因为上面安装的版本是 @babel/runtime-corejs2
配置webpack.config.js设置使用babel的规则
1 | module.exports = { |
缓存
1 | { |
处理CSS
其中less和sass任选其一即可。
处理css文件
添加依赖
1 | npm i -D style-loader@3.3.1 css-loader@6.7.1 |
在webpack.config.js这个配置文件设置匹配css文件处理的插件1
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
处理less
添加依赖
1 | npm i less-loader@11.0.0 less@4.1.3 -D |
在webpack.config.js配置文件设置匹配less文件的处理1
{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
处理sass
安装sass-loader node-sass工具来处理sass文件1
2npm i sass-loader node-sass -D
npm i sass fiber -D
在webpack.config.js配置文件设置匹配scss文件的处理1
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
处理URL
图片
安装url-loader1
npm i url-loader@4.1.1 file-loader@6.2.0 -D
在webpack.config.js中添加处理url路径的loader模块:1
{test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader?esModule=false&limit=500&name=imgs/[hash:8]-[name].[ext]'},
上面这种输入参数的方式还有另一种方式,以对象的键值对方式,如下:1
2
3
4
5
6
7
8
9
10
11{
test: /\.(jpg|png|gif|bmp|jpeg|jfif)$/,
use: [{
loader: 'url-loader',
options: {
esModule: false,
limit: 500, //是把小于500B的文件打成Base64的格式,写入JS
name: 'imgs/[hash:8]-[name].[ext]' // [hash:8] 在名称前面设置8位哈希值,[name] 设置文件的原名, [ext] 设置文件的原后缀
}
}]
},// 处理 图片路径的 loader
对比
file-loader 可以指定要复制和放置资源文件的位置,以及如何使用版本哈希命名以获得更好的缓存。此外,这意味着 你可以就近管理图片文件,可以使用相对路径而不用担心部署时 URL 的问题。使用正确的配置,webpack 将会在打包输出中自动重写文件路径为正确的 URL。
url-loader 允许你有条件地将文件转换为内联的 base-64 URL (当文件小于给定的阈值),这会减少小文件的 HTTP 请求数。如果文件大于该阈值,会自动的交给 file-loader 处理。
字体
不要把字体也用url-loader 来处理,把字体文件转成base64是浏览器无法识别的
1 | { |
音频
1 | { |
静态文件处理
https://www.webpackjs.com/plugins/copy-webpack-plugin/#install
https://github.com/webpack-contrib/copy-webpack-plugin/tree/v9.1.0
1 | npm install copy-webpack-plugin@9 -D |
配置
1 | //webpack.config.js |
注意:
版本不同,配置也不一样。
to配置的相对路径是相对于发布目录的。
如果from所在目录中排除文件后没有文件的时候会报错。
我的配置
package.json
1 | { |
webpack.config.js
1 | const path = require('path'); |
.babelrc
1 | { |
/public/index.html
1 |
|
/src/App.vue
1 | <template> |
/src/main.js
1 | import App from './App'; |
优化
优化构建速度
耗时分析
首先安装一下
1 | npm i -D speed-measure-webpack-plugin |
修改我们的配置文件 webpack.config.js
1 | // 费时分析 |
范围优化
resolve
1、alias
alias 用的创建 import
或 require
的别名,用来简化模块引用,项目中基本都需要进行配置。
1 | const path = require('path') |
配置完成之后,我们在项目中就可以
1 | // 使用 src 别名 ~ |
2、extensions
webpack 默认配置
1 | module.exports = { |
如果用户引入模块时不带扩展名,例如
1 | import file from '../path/to/file'; |
那么 webpack 就会按照 extensions 配置的数组从左到右的顺序去尝试解析模块
需要注意的是:
- 高频文件后缀名放前面;
- 手动配置后,默认配置会被覆盖
如果想保留默认配置,可以用 ...
扩展运算符代表默认配置,例如
1 | module.exports = { |
3、modules
告诉 webpack 解析模块时应该搜索的目录,常见配置如下
1 | const path = require('path'); |
告诉 webpack 优先 src 目录下查找需要解析的文件,会大大节省查找时间。
4、resolveLoader
resolveLoader 与上面的 resolve 对象的属性集合相同, 但仅用于解析 webpack 的 loader 包。
一般情况下保持默认配置就可以了,但如果你有自定义的 Loader 就需要配置一下,不配可能会因为找不到 loader 报错。例如:我们在 loader 文件夹下面,放着我们自己写的 loader。我们就可以怎么配置
1 | const path = require('path'); |
externals
externals
配置选项提供了「从输出的 bundle 中排除依赖」的方法。此功能通常对 library 开发人员来说是最有用的,然而也会有各种各样的应用程序用到它。例如,从 CDN 引入 jQuery,而不是把它打包:
1、引入链接
1 | <script |
2、配置 externals
1 | module.exports = { |
3、使用 jQuery
1 | import $ from 'jquery'; |
我们可以用这样的方法来剥离不需要改动的一些依赖,大大节省打包构建的时间。
缩小范围
在配置 loader 的时候,我们需要更精确的去指定 loader 的作用目录或者需要排除的目录,通过使用 include
和 exclude
两个配置项,可以实现这个功能,常见的例如:
include
:符合条件的模块进行解析exclude
:排除符合条件的模块,不解析exclude
优先级更高
例如在配置 babel 的时候
1 | const path = require('path'); |
noParse
- 不需要解析依赖的第三方大型类库等,可以通过这个字段进行配置,以提高构建速度
- 使用 noParse 进行忽略的模块文件中不会解析
import
、require
等语法
1 | module.exports = { |
IgnorePlugin
防止在 import
或 require
调用时,生成以下正则表达式匹配的模块:
requestRegExp
匹配(test)资源请求路径的正则表达式。contextRegExp
匹配(test)资源上下文(目录)的正则表达式
1 | new webpack.IgnorePlugin({ resourceRegExp, contextRegExp }); |
以下示例演示了此插件的用法。
1、安装 moment 插件(时间处理库)
1 | npm i -S moment |
2、配置 IgnorePlugin
1 | // 引入 webpack |
目的是将插件中的非中文语音排除掉,这样就可以大大节省打包的体积了
多进程
配置在 thread-loader 之后的 loader 都会在一个单独的 worker 池(worker pool)中运行
1、安装
1 | npm i -D thread-loader@3.0.4 |
2、配置
1 | const path = require('path'); |
缓存
利用缓存可以大幅提升重复构建的速度
JS缓存
babel-loader 开启缓存
- babel 在转译 js 过程中时间开销比价大,将 babel-loader 的执行结果缓存起来,重新打包的时候,直接读取缓存
- 缓存位置:
node_modules/.cache/babel-loader
配置
1 | const path = require('path'); |
CSS缓存
cache-loader
- 缓存一些性能开销比较大的 loader 的处理结果
- 缓存位置:
node_modules/.cache/cache-loader
1、安装
1 | npm i -D cache-loader@4.1.0 |
2、配置 cache-loader
1 | module.exports = { |
less
1 | {test: /\.less$/, use: ['style-loader','cache-loader', 'css-loader', 'less-loader']}, |
其他
hard-source-webpack-plugin
hard-source-webpack-plugin 为模块提供了中间缓存,重复构建时间大约可以减少 80%,但是在 webpack5 中已经内置了模块缓存,不需要再使用此插件
持久化缓存
通过配置cache缓存生成的 webpack 模块和 chunk,来改善构建速度。
1 | module.exports = { |
优化构建结果
优化构建结果是为了让打包出来的文件尽可能小,这样势必会增加构建时间。
结果分析
借助插件webpack-bundle-analyzer我们可以直观的看到打包结果中,文件的体积大小、各模块依赖关系、文件是够重复等问题,极大的方便我们在进行项目优化的时候,进行问题诊断。
1、安装
1 | npm i -D webpack-bundle-analyzer |
2、配置插件
1 | // 引入插件 |
3、修改启动命令
1 | "scripts": { |
4、执行编译命令 npm run analyzer
打包结束后,会自行启动地址为 http://127.0.0.1:8888
的 web 服务
如果,我们只想保留数据不想启动 web 服务,这个时候,我们可以加上两个配置
1 | new BundleAnalyzerPlugin({ |
这样再次执行打包的时候就只会产生 state.json 的文件了
压缩 CSS
1、安装 optimize-css-assets-webpack-plugin
1 | npm install -D optimize-css-assets-webpack-plugin |
2、修改 webapck.config.js
配置
1 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') |
压缩 JS
在生成环境下打包默认会开启 js 压缩,但是当我们手动配置
optimization
选项之后,就不再默认对 js 进行压缩,需要我们手动去配置。
因为 webpack5 内置了terser-webpack-plugin插件,所以我们不需重复安装,直接引用就可以了,具体配置如下
1 | const TerserPlugin = require('terser-webpack-plugin'); |
清除无用的 CSS
purgecss-webpack-plugin 会单独提取 CSS 并清除用不到的 CSS
1、安装插件
1 | $ npm i -D purgecss-webpack-plugin |
2、添加配置
1 | // ... |
3、index.html 新增节点
1 | <head> |
4、在 sass.scss 中添加样式
1 | .used { |
5、执行一下打包
我们可以看到只有 .used
被保存下来
如何证明是这个插件的作用呢?注释掉再打包就可以看到,.unused
也会被打包进去,由此可证…
Tree-shaking
Tree-shaking 作用是剔除没有使用的代码,以降低包的体积
了解更多 Tree-shaking 知识,推荐阅读 从过去到现在,聊聊 Tree-shaking
webpack5
中 tree-shaking
中的配置
打开项目下 package.json
, 加入配置 "sideEffects"
sideEffects
有三种情况
sideEffects:true
所有文件都有副作用,全都不可tree-shaking
sideEffects:false
有这些文件有副作用,所有其他文件都可以tree-shaking
,但会保留这些文件sideEffects:[]
部分tree-shaking
, 除了数组外都tree-shaking
所谓 副作用
指的是 在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。
举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
对于某些代码,可能没有被导出和使用,但是却不能删除。
因为仅仅是引入这个文件(比如import './index.less'
),或者执行了某个表达式(比如Array.prototype.slice = null
),都会对结果造成影响,所以不能被轻易删除。
webpack认为这些代码是有“副作用(Side Effects)”的。
Scope Hoisting
Scope Hoisting 即作用域提升,原理是将多个模块放在同一个作用域下,并重命名防止命名冲突,通过这种方式可以减少函数声明和内存开销。
- webpack 默认支持,在生产环境下默认开启
- 只支持 es6 代码
优化运行时体验
运行时优化的核心就是提升首屏的加载速度,主要的方式就是:降低首屏加载文件体积,首屏不需要的文件进行预加载或者按需加载
splitChunks 分包配置
optimization.splitChunks 是基于 SplitChunksPlugin 插件实现的。默认情况下,它只会影响到按需加载的 chunks,因为修改 initial chunks 会影响到项目的 HTML 文件中的脚本标签。
webpack 将根据以下条件自动拆分 chunks:
- 新的 chunk 可以被共享,或者模块来自于
node_modules
文件夹 - 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
- 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
- 当加载初始化页面时,并发请求的最大数量小于或等于 30
1、默认配置介绍
1 | module.exports = { |
2、项目中的使用
1 | module.exports = { |
代码懒加载
针对首屏加载不太需要的一些资源,我们可以通过懒加载的方式去实现。
下面看一个小需求:点击图片给图片加一个描述
1、新建图片描述信息 desc.js
1 | const ele = document.createElement('div') |
2、点击图片引入描述 index.js
1 | import './main.css'; |
prefetch 与 preload
上面我们使用异步加载的方式引入图片的描述,但是如果需要异步加载的文件比较大时,在点击的时候去加载也会影响到我们的体验,这个时候我们就可以考虑使用 prefetch 来进行预拉取
prefetch
prefetch (预获取):浏览器空闲的时候进行资源的拉取
改造一下上面的代码
1 | // 按需加载 |
preload
- preload (预加载):提前加载后面会用到的关键资源
- 因为会提前拉取资源,如果不是特殊需要,谨慎使用
官网示例:
1 | import(/* webpackPreload: true */ 'ChartingLibrary'); |
其他插件
构建进度条插件
1 | npm i -D webpackbar@5.0.2 |
配置
1 | const WebpackBar = require('webpackbar'); |
当然里面还有一个属性就是reporters还没有写上,可以在里面注册事件,也可以理解为各种钩子函数。
如下:
1 | { |
当然多数情况下,我们并不会使用这些,基本默认就足够了。
常见问题
IE白板 Chrome正常
这是典型的ES6不支持的情况,最简单的方法是
1 | npm install --save babel-polyfill |
main.js中添加
1 | import 'babel-polyfill'; |
或者
1 | require('babel-polyfill'); |
详解
Babel默认只转换语法:(默认情况下Babel可以将箭头函数,class等语法转换为ES5兼容的形式)。\
而不转换新的API,如需使用新的API,比如
Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise
等全局对象,以及一些定义在全局对象上的方法(比如Object.assign
)都不会转码,还需要使用对应的转换插件或者polyfill
。
举例来说:
ES6在Array对象上新增了Array.from
方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill
,为当前环境提供一个垫片。