webpack打包文件常见问题
1.打包完成后引入文件的地址是哪个?
在打包输出的结果中: Entrypoint main = main.bundle.js
生成的其他代码文件会被入口函数添加到 header 的尾部。例如下例:
<script charset="utf-8" src="/Users/stone/Downloads/webpack4-learn-master/demo05/dist/vendors.15224047.js"></script>
2.代码分模块打包方法。
如下例子:
const path = require('path')
const CleanWebpackPlugin = require('clean-webpack-plugin')//会对打包信息进行清除。
module.exports = {
entry: {
main: './src/index.js'
},
output: {
publicPath: __dirname + '/dist/', // js 引用的路径或者 CDN 地址
path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录
filename: '[name].bundle.js', // 代码打包后的文件名
chunkFilename: '[name].js' // 代码拆分后的文件名
},
optimization: {//对打包进行优化,用于大的模块进行分割
splitChunks: {
chunks: 'all',//共有三种,all,async,inital
automaticNameDelimiter: '.', //名字分割的办法
cacheGroups: {//希望的分割模式
vendors: {//这个cache分组名一点用也没,只是个命名
test: /[\\/]node_modules[\\/]/,//文件路径正则
priority:-10,
name: 'vendors',
minSize: 40, //模块的最小分割大小
maxSize: 50, //模块的最大分割大小,如果超过那个就可能会被分割。
},
lodash: {//对这个库单独打包
minSize: 40,
maxSize: 50,
name: 'lodash',
test: /[\\/]node_modules[\\/]lodash[\\/]/,
priority: 5 // 优先级要大于 vendors 不然会被打包进 vendors
},
commons: {//也是自己定义的名字
name: 'commons',
minSize: 0, //表示在压缩前的最小模块大小,默认值是 30kb
minChunks: 2, // 最小公用次数
priority: 5, // 优先级
reuseExistingChunk: true // 公共模块必开启
},
default: {//其他的默认情况下的分组方式
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
name:"xxxxxx"//没有作用
}
}
}
},
plugins: [new CleanWebpackPlugin()]
}
说明:主要用来对大的模块进行分解打包。
minSize
:用来描述最小的分包大小。
maxSize
:如果包超过了这个大小限制将会被再次分解,分解后的包是带了 hashid 的。同时他是尽可能的去分,如果不能分了,大小会存在超过限定的情况。
3.代码懒加载方法
document.addEventListener('click', function() {
import(/* webpackChunkName: 'use-lodash'*/ 'lodash').then(function(_) {
console.log(_.join(['3', '4']))
})
})
懒加载的含义是,在触发某些事件的时候,才去动态的加载代码,然后执行代码。
上面的代码只有触发的时候,才会从后端将代码加载进来,然后执行对应的模块。
懒加载能加快网页的加载速度,如果你把详情页、弹窗等页面全部打包到一个 js 文件中,用户如果只是访问首页,只需要首页的代码,不需要其他页面的代码,加入多余的代码只会使加载时间变长,所以我们可以对路由进行懒加载,只有当用户访问到对应路由的时候,再去加载对应模块。
懒加载并不是 webpack 里的概念,而是 ES6 中的 import 语法,webpack 只是能够识别 import 语法,能进行代码分割而已。
可以使用浏览器的coverage
对代码的使用率进行分析。
页面刚加载的时候,异步的代码根本就不会执行,但是我们却把它下载下来,实际上就会浪费页面执行性能,webpack 就希望像这样交互的功能,应该把它放到一个异步加载的模块去写,例如下面的书写方法:
单独书写一个click.js
文件
function handleClick() {
const element = document.createElement('div')
element.innerHTML = 'Dell Lee'
document.body.appendChild(element)
}
export default handleClick
将 index.js 文件写为异步的加载模块:
document.addEventListener('click', () => {
import('./click.js').then(({ default: func }) => {
func()
})
})
这么去写代码,才是使页面加载最快的一种方式,写高性能前端代码的时候,不光是考虑缓存,还要考虑代码使用率。
所以 webpack 在打包过程中,是希望我们多写这种异步的代码,才能提升网站的性能,这也是为什么 webpack 的 splitChunks 中的 chunks 默认是 async,异步的。
异步能提高你网页打开的性能,而同步代码是增加一个缓存,对性能的提升是非常有限的,因为缓存一般是第二次打开网页或者刷新页面的时候,缓存很有用,但是网页的性能一般是用户第一次打开网页,看首屏的时候。
当然,这也会出现另一个问题,就是当用户点击的时候,才去加载业务模块,如果业务模块比较大的时候,用户点击后并没有立马看到效果,而是要等待几秒,这样体验上也不好,怎么去解决这种问题?
4.代码懒加载方法用的太多也会变慢的---预加载方式
采用异步,如果用懒加载的方式,在用户点击触发事件后,才进行下载相关代码,然后执行相关代码,这个下载执行的过程可能也很消耗时间。
如果访问首页的时候,不需要加载详情页的逻辑,等用户首页加载完了以后,页面展示出来了,页面的带宽被释放出来了,网络空闲了,再「偷偷」的去加载详情页的内容,而不是等用户去点击的时候再去加载
这个解决方案就是依赖 webpack 的 Prefetching/Preloading
特性
document.addEventListener('click', () => {
import(/* webpackPrefetch: true */ './click.js').then(({ default: func }) => {
func()
})
})
webpackPrefetch: true
会等你主要的 JS 都加载完了之后,网络带宽空闲的时候,它就会预先帮你加载好.
当网页打开的时候,main.bundle.js 被加载完了,网络空闲了,就会预先加载 1.js 耗时 14ms(注意文本格式Type
为text/javascript
,是个缓存),等我去点击页面的时候,Network 又多了一个 1.js(注意文本格式Type
为script
,是从缓存中读出来的),耗时 2ms,这是因为第一次加载完了 1.js,被浏览器给缓存起来了,等我点击的时候,浏览器直接从缓存中取,响应速度非常快。
这里我们使用的是 webpackPrefetch
,还有一种是 webpackPreload
区别:
与 prefetch 相比,Preload 指令有很多不同之处:
Prefetch 会等待核心代码加载完之后,有空闲之后再去加载。Preload 会和核心的代码并行加载,还是不推荐
总结:
针对优化,不仅仅是局限于缓存,缓存能带来的代码性能提升是非常有限的,而是如何让代码的使用率最高,有一些交互后才用的代码,可以写到异步组件里面去,通过懒加载的形式,去把代码逻辑加载进来,这样会使得页面访问速度变的更快,如果你觉得懒加载会影响用户体验,可以使用 Prefetch 这种方式来预加载,不过在某些游览器不兼容,会有兼容性的问题,重点不是在 Prefetch 怎么去用,而是在做前端代码性能优化的时候,缓存不是最重要的点,最重要的是代码使用的覆盖率上(coverage)
5.解决因打包文件名称变动导致引入麻烦---html-webpack-plugin
经过上面几个小节的操作,有没有觉得每次要去更改 index.html 中引入 js 文件很麻烦,一旦打包的名字变更后,也要对应的去修改 index.html 引入的 js 名称,这个时候就要使用一个插件来帮助我们,打包完之后自动生成 HTML 文件,并自动引入打包后的 js 文件,安装的方法如下:
npm i html-webpack-plugin html-loader --save-dev
插件使用的配置方法如下:
module.exports = {
plugins: [
new HtmlWebpackPlugin({
// 打包输出HTML
title: '自动生成 HTML',
minify: {
// 压缩 HTML 文件
removeComments: true, // 移除 HTML 中的注释
collapseWhitespace: true, // 删除空白符与换行符
minifyCSS: true // 压缩内联 css
},
filename: 'index.html', // 生成后的文件名
template: 'index.html' // 根据此模版生成 HTML 文件
})
]
}
title
: 打包后生成 html 的 title
filename
:打包后的 html 文件名称
template
:模板文件(例子源码中根目录下的 index.html)
chunks
:和 entry
配置中相匹配,支持多页面、多入口
minify
:压缩选项
由于使用了 title
选项,则需要在 template
选项对应的 html 的 title 中加入 <%= htmlWebpackPlugin.options.title %>
然后入口函数就在 html 中就不需要引入了。
6.处理 css/scss 文件
CSS 在 HTML 中的常用引入方法有 <link>
标签和 <style>
标签两种,所以这次就是结合 webpack 特点实现以下功能:
- 将 css 通过 link 标签引入
- 将 css 放在 style 标签里
这次我们需要用到 css-loader
,style-loader
等 loader,跟 babel 一样,webpack 不知道将 CSS 提取到文件中。需要使用 loader 来加载对应的文件
css-loader
:负责解析 CSS 代码,主要是为了处理 CSS 中的依赖,例如 @import
和 url()
等引用外部文件的声明。
style-loader
会将 css-loader
解析的结果转变成 JS 代码,运行时动态插入 style 标签来让 CSS 代码生效。
安装的依赖
npm i css-loader style-loader --save-dev
同时需要增加如下的配置文件:
module: {
rules: [
{
test: /\.css$/, // 针对 .css 后缀的文件设置 loader
use: ['style-loader', 'css-loader']
}
},
打包完成访问后发现样式表在 index 中内置了(可以发现是通过 <style>
标签注入的 css)。 这是因为:
style-loader
, css-loader
两个 loader
的处理后,CSS 代码会转变为 JS,和 index.js 一起打包。
如果需要单独把 CSS 文件分离出来,我们需要使用 mini-css-extract-plugin
插件。
使用的配置方法如下:
module: {
rules: [
{
test: /\.css$/, // 针对 .css 后缀的文件设置 loader
use: [
{//使用loader方式解析
loader: MiniCssExtractPlugin.loader
},
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
})
]
说明:use
是对匹配的资源进从后到前的执行(这个顺序很诡异)。plugins
字段相当于配置文件。使用上面的配置文件后,将会把 css 单独的打包到文件中。 但是这样的文件并没有压缩,需要,引入 optimize-css-assets-webpack-plugin
插件来实现 css 压缩
npm install optimize-css-assets-webpack-plugin --save-dev
在打包文件的 plugins 中加入如下的配置:
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano'), //用于优化\最小化 CSS 的 CSS 处理器,默认为 cssnano
cssProcessorOptions: { safe: true, discardComments: { removeAll: true } }, //传递给 cssProcessor 的选项,默认为{}
canPrint: true //布尔值,指示插件是否可以将消息打印到控制台,默认为 true
})
打开配置后发现 css 文件也已经被压缩了。
处理 scss 文件使用的依赖为
npm i node-sass sass-loader --save-dev
这里就不多介绍了。
注意!!!
module.rules.use
数组中,loader
的位置。根据 webpack 规则:放在最后的 loader
首先被执行,从上往下写的话是下面先执行,从左往右写的话是右边先执行。
['style-loader', 'css-loader', 'sass-loader']
执行顺序为 sass-loader --> css-loader --> style-loader
首先应该利用 sass-loader 将 scss 编译为 css,剩下的配置和处理 css 文件相同。
这个顺序确实是很诡异的!!!!!!!!!!
为 CSS 自动 加上浏览器前缀
安装 postcss-loader 和 autoprefixer 依赖
npm install postcss-loader autoprefixer --save-dev
7.JS Tree Shaking
字面意思是摇树,项目中没有使用的代码会在打包的时候丢掉。JS 的 Tree Shaking 依赖的是 ES6 的模块系统(比如:import 和 export)
这个在打包的时候已经被优化了。我们写代码的时候要求按需加载引入模块。
- 如何处理第三方 JS 库?
对于经常使用的第三方库(例如 jQuery、lodash 等等),如何实现 Tree Shaking ?
开头讲过,js tree shaking 利用的是 ES 的模块系统。而 lodash.js 使用的是 CommonJS 而不是 ES6 的写法。所以,安装对应的模块系统即可。
安装 lodash.js 的 ES 写法的版本:npm install lodash-es --save
友情提示:
在一些对加载速度敏感的项目中使用第三方库,请注意库的写法是否符合 ES 模板系统规范,以方便 webpack 进行 tree shaking。
8.CSS Tree Shaking
能够将没有用到的 CSS 文件给清除掉。
使用的库如下:
npm i glob-all purify-css purifycss-webpack --save-dev
警告!!!
如果项目中有引入第三方 css 库的话,谨慎使用!!!
9.图片处理汇总
1.图片处理和 base64 编码
使用如下的依赖:
npm install url-loader file-loader --save-dev
在 webpack.config.js 中的 module.rules 选项中进行配置,以实现让 loader 识别图片后缀名,并且进行指定的处理操作。
rules: [
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
name: '[name]-[hash:5].min.[ext]',
outputPath: 'images/', //输出到 images 文件夹
limit: 20000 //把小于 20kb 的文件转成 Base64 的格式
}
}
]
}
]
总结:
如果图片较多,会发很多 http 请求,会降低页面性能。
url-loader 会将引入的图片编码,转为 base64 字符串。再把这串字符打包到文件中,最终只需要引入这个文件就能访问图片了,节省了图片请求。
但是,如果图片较大,编码会消耗性能。因此 url-loader 提供了一个 limit 参数,小于 limit 字节的文件会被转为 base64,大于 limit 的使用 file-loader 进行处理,单独打包。
url-loader
依赖 file-loader
,url-loader
可以看作是增强版的 file-loader
图片压缩
图片压缩需要使用 img-loader 插件,除此之外,针对不同的图片类型,还要引用不同的插件。比如,我们项目中使用的是 png 图片,因此,需要引入 imagemin-pngquant,并且指定压缩率。压缩 jpg/jpeg 图片为 imagemin-mozjpeg 插件
10.字体处理
对字体的处理和对 css 的样式极像。
11.处理第三方 JS 库
如何使用和管理第三方 JS 库?
项目做大之后,开发者会更多专注在业务逻辑上,其他方面则尽力使用第三方 JS 库来实现。
由于 js 变化实在太快,所以出现了多种引入和管理第三方库的方法,常用的有 3 中:
1.CDN:<script></script>
标签引入即可 2.npm
包管理:目前最常用和最推荐的方法 3.本地 js 文件:一些库由于历史原因,没有提供 ES6 版本,需要手动下载,放入项目目录中,再手动引入。
针对第三种方法,如果没有 webpack,则需要手动引入 import 或者 require 来加载文件;但是,webpack 提供了 alias 的配置,配合 webpack.ProvidePlugin 这款插件,可以跳过手动入,直接使用!