webpack中的打包优化,压缩配置
webpack概念:现代javaScript应用程序的静态模块打包器。当webpack处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包城一个或多个bundle。
1.入口:入口起点指示webpack应该使用哪个模块,来作为构建其内部依赖图的开始。单页面配置一个入口,多页面配置多个入口
2.出口output告诉webpack在哪里输出它所创建的bundles,以及如何命名这些文件。
3.loader让webpack能够去处理那些非javaScript文件(webpack自身只理解javaScript)。loader一般是开发时的依赖。loader可以将所有类型的文件转换为webpack能够处理的有效模块。
如同:less,sass,jsx,vue等文件,如果没有打包工具进行转换,则浏览器无法识别。
const path = require('path');
const config = {
output: {
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
}
};
module.exports = config;
“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先使用 raw-loader 转换一下。”
4.插件plugins, 插件可以用于执行范围更广的任务。打包优化,压缩
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
const config = {
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
module.exports = config;
5.模式:通过不同的模式打包的效果不同,其内部有一些默认的优化配置。
module.exports = {
mode: 'production'
};
多页面应用 //根据经验:每个 HTML 文档只使用一个入口起点
- 使用
CommonsChunkPlugin
为每个页面间的应用程序共享代码创建 bundle。由于入口起点增多,多页应用能够复用入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。
const config = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
};
输出:即使可以存在多个入口
起点,但只指定一个输出
配置。
{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}
// 写入到硬盘:./dist/app.js, ./dist/search.js
模式:
1
loader用于对模块的源代码进行转换。loader可以使你在import或加载模块时预处理文件。因此,loader类似于其他构建工具中的"task",loader可以将文件从不同的语言(如TypeScript)转换为JavaScript
loader常用的调用形式,调用顺序是从右到左,从下到上。
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
url-loader和file-loader的区别
1、url-loader依赖file-loader
2、当使用url-loader加载图片,图片大小小于上限值,则将图片转base64字符串,否则使用file-loader加载图片,都是为了提高浏览器加载图片速度。
3、使用url-loader加载图片比file-loader更优秀
优化:
dev开发阶段:
1.HMR,热模块重载,提高打包速度,提升开发速度
2.不要放过第三方插件库,CommonsChunkPlugin 每次构建时都会重新构建一次 vendor,DllPlugin这个依赖库不会跟着你的业务代码一起被重新打包,只有当依赖自身发生版本变化时才会重新打包。
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 依赖的库数组
vendor: [
'prop-types',
'babel-polyfill',
'react',
'react-dom',
'react-router-dom',
]
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
library: '[name]_[hash]',
},
plugins: [
new webpack.DllPlugin({
// DllPlugin的name属性需要和libary保持一致
name: '[name]_[hash]',
path: path.join(__dirname, 'dist', '[name]-manifest.json'),
// context需要和webpack.config.js保持一致
context: __dirname,
}),
],
}
vendor.js 不必解释,是我们第三方库打包的结果。这个多出来的 vendor-manifest.json,则用于描述每个第三方库对应的具体路径。
3.Happypack——将 loader 由单进程转为多进程
const HappyPack = require('happypack')
// 手动创建进程池
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
module.exports = {
module: {
rules: [
...
{
test: /\.js$/,
// 问号后面的查询参数指定了处理这类文件的HappyPack实例的名字
loader: 'happypack/loader?id=happyBabel',
...
},
],
},
plugins: [
...
new HappyPack({
// 这个HappyPack的“名字”就叫做happyBabel,和楼上的查询参数遥相呼应
id: 'happyBabel',
// 指定进程池
threadPool: happyThreadPool,
loaders: ['babel-loader?cacheDirectory']
})
],
}
4.删除冗余代码,Tree-Shaking
UglifyJsPlugin ,压缩过程中对碎片化的冗余代码(如 console 语句、注释等)进行自动化删除:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
plugins: [
new UglifyJsPlugin({
// 允许并发
parallel: true,
// 开启缓存
cache: true,
compress: {
// 删除所有的console语句
drop_console: true,
// 把使用多次的静态值自动定义为变量
reduce_vars: true,
},
output: {
// 不保留注释
comment: false,
// 使输出的代码尽可能紧凑
beautify: false
}
})
]
}
5.按需加载
如果我把这整个项目打一个包,用户打开我的网站时,会发生什么?有很大机率会卡死,对不对?更好的做法肯定是先给用户展示主页,其它页面等请求到了再加载。
开启gzip压缩
具体的做法非常简单,只需要你在你的 request headers 中加上这么一句:
accept-encoding:gzip
压缩 Gzip,服务端要花时间;解压 Gzip,浏览器要花时间。中间节省出来的传输时间,真的那么可观吗?
答案是肯定的。如果你手上的项目是 1k、2k 的小文件,那确实有点高射炮打蚊子的意思,不值当。但更多的时候,我们处理的都是具备一定规模的项目文件。实践证明,这种情况下压缩和解压带来的时间开销相对于传输过程中节省下的时间开销来说,可以说是微不足道的
webpack 的 Gzip 和服务端的 Gzip
既然存在着这样的交换,那么就要求我们学会权衡。服务器的 CPU 性能不是无限的,如果存在大量的压缩需求,服务器也扛不住的。服务器一旦因此慢下来了,用户还是要等。Webpack 中 Gzip 压缩操作的存在,事实上就是为了在构建过程中去做一部分服务器的工作,为服务器分压。
开发环境的配置
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: { filename: 'js/built.js', path: resolve(__dirname, 'build') },
module: {
rules: [
// loader 的配置
{ // 处理 less 资源
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{ // 处理 css 资源
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{ // 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: { limit: 8 * 1024,
name: '[hash:10].[ext]',
// 关闭 es6 模块化
esModule: false,
outputPath: 'imgs' }
},
{ // 处理 html 中 img 资源
test: /\.html$/,
loader: 'html-loader'
},
{ // 处理其他资源
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media' }
}
]
},
plugins: [ // plugins 的配置
new HtmlWebpackPlugin({ template: './src/index.html' })
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
//开启gzip压缩
compress: true,
port: 3000,
open: true
}
};
优化
-
提取css为单独文件(MiniCssExtractPlugin), css兼容性处理(postcss-loader'), css压缩(OptimizeCssAssetsWebpackPlugin)
-
js语法检测(eslint), js兼容性处理(bable), js压缩(生产模式默认压缩js代码) //同一个匹配规则,需要指定先后顺序,比如优先度eslint>bable
-
html压缩(HtmlWebpackPlugin)
-
HMR
-
source-map(devtool: 'eval-source-map')
-
缓存
// 开启 babel 缓存 // 第二次构建时,会读取之前的缓存 cacheDirectory: true
-
tree-shaking(OptimizeCssAssetsWebpackPlugin)
-
code split
/*1. 可以将 node_modules 中代码单独打包一个 chunk 最终输出 2. 自动分析多入口 chunk 中,有没有公共的文件。如果有会打包成单独一个 chunk */ optimization: { splitChunks: { chunks: 'all' } },
-
lazy loading
-
pwa
new WorkboxWebpackPlugin.GenerateSW({ /*1. 帮助 serviceworker 快速启动 2. 删除旧的 serviceworker 生成一个 serviceworker 配置文件~ */ clientsClaim: true, skipWaiting: true })
-
多进程打包
-
externals
externals: { // 拒绝 jQuery 被打包进来 jquery: 'jQuery' }
-
optimization
// 将当前模块的记录其他模块的 hash 单独打包为一个文件 runtime // 解决:修改 a 文件导致 b 文件的 contenthash 变化 runtimeChunk: { name: entrypoint => `runtime-${entrypoint.name}` },