创作不易 拒绝白嫖 点个赞呗
文章目录
前言
本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
主要概念
- 入口(entry)
- 输出(output)
- loader
- 插件(plugin)
- 模式(mode)
1. 基本使用
1.1 拆分配置和merge
初始化package.json
npm init -y
1.1.1 安装webpack
yarn add -D webpack webpack-cli
1.1.2 安装webpack-merge
yarn add -D webpack-merge
1.1.3 新建build文件夹以及配置文件
- build\webpack.common.js
- build\webpack.dev.js
- build\webpack.prod.js
webpack.common.js
公共配置
const path = require('path') module.exports ={ entry:path.join(__dirname, '..', 'src/index') }
webpack.dev.js
开发时
const webpackCommon = require('./webpack.common.js') const { merge } = require('webpack-merge') module.exports = merge(webpackCommon, { mode: 'development' })
webpack.prod.js
打包时
const path = require('path') const webpack = require('webpack') const webpackCommon = require('./webpack.common.js') const { merge } = require('webpack-merge') const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = merge(webpackCommon, { mode: 'production', output: { filename: '[name].[contenthash:8].js', path: path.join(__dirname, '..', 'dist'), }, plugins: [new CleanWebpackPlugin()] })
1.1.4 Scripts
"scripts": { // 本地服务 "dev": "webpack-dev-server --config build/webpack.dev.js", "build": "webpack --config build/webpack.prod.js" },
1.1.5 测试
新建src/index
执行 yarn build
1.2 启动本地服务
1.2.1 安装webpack-dev-server
yarn add -D webpack-dev-server
1.2.2 配置devServer
webpack.dev.js
devServer: { port: 8080, progress: true, // 显示打包的进度条 contentBase:path.join(__dirname, '..', 'dist'), // 根目录 open: true, // 自动打开浏览器 compress: true, // 启动 gzip 压缩 // 设置代理 proxy: { } }
1.2.3 启动
yarn dev
1.2.4 测试
安装HtmlWebpackPlugin,自动生成html
yarn add -D html-webpack-plugin
配置HtmlWebpackPlugin
webpack.common.js
plugins:[ new HtmlWebpackPlugin({ template: path.join(__dirname, '..', 'src/index.html'), filename: 'index.html' }) ]
开发环境,会在内存中生成一个html文件
打包环境,会在dist下生成一个html文件
html文件会自动引入main.js
1.3 处理es6(配置babel)
13.1. 安装
babel-loader
yarn add -D babel-loader
@babel/core
yarn add -D @babel/core
@babel/preset-env
yarn add -D @babel/preset-env
1.3.2. 配置
.babelrc
{ "presets": ["@babel/preset-env"], "plugins": [] }
build\webpack.common.js
module: { rules: [ { test: /\.js$/, // loader: 'babel-loader', //loader 是单个加载器,use是加载器数组 use: [ 'babel-loader', ], include: path.join(__dirname, '..', 'src'), exclude: /node_modules/ } ] },
1.4 处理css
1.4.1 安装
style-loader
css插入到页面的style标签
yarn add -D style-loader
css-loader
yarn add -D css-loader
postcss-loader + autoprefixer
自动添加前缀
yarn add -D postcss-loader autoprefixer
1.4.2 配置
build\webpack.common.js
{ test: /\.css$/, // loader 的执行顺序是:从后往前 use: ['style-loader', 'css-loader', 'postcss-loader'] },
postcss.config.js
module.exports = { plugins: [require('autoprefixer')] }
1.5 处理图片
1.5.1 一般文件中引入图片有三种方式:
js文件中通过 import imgSrc from ‘./photo’; img.src = imgSrc引入
在css文件中作为背景图引入
在html文件中直接引入
1.5.2 安装
file-loader
yarn add -D file-loader
url-loader
yarn add -D url-loader
1.5.3 配置
build-base-conf\webpack.dev.js
// 直接引入图片 url { test: /\.(png|jpg|jpeg|gif)$/, use: 'file-loader' }
build-base-conf\webpack.prod.js
// 图片 - 考虑 base64 编码的情况 { test: /\.(png|jpg|jpeg|gif)$/, use: { loader: 'url-loader', options: { // 小于 5kb 的图片用 base64 格式产出 // 否则,依然延用 file-loader 的形式,产出 url 格式 limit: 5 * 1024, // 打包到 img 目录下 outputPath: '/img1/', // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源) // publicPath: 'http://cdn.abc.com' } } },
2 高级特性
2.1 配置多入口
2.1.1 配置entry
entry: { index: path.join(srcPath, 'index.js'), other: path.join(srcPath, 'other.js') },
这里的index与other就是chunk名称
2.1.2. 配置对应的出口
build-base-conf\webpack.prod.js
output: { filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳 path: distPath, // publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到 },
注意这里只需要在prod里面加入配置,
dev不需要
2.1.3 配置对应的模板
new HtmlWebpackPlugin({ template: path.join(__dirname, '..', 'src/index.html'), filename: 'index.html', // chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用 chunks: ['index'] // 只引用 index.js }), // 多入口 - 生成 other.html new HtmlWebpackPlugin({ template: path.join(__dirname, '..', 'src/other.html'), filename: 'other.html', chunks: ['other'] // 只引用 other.js })
2.2 抽离css(打包)
2.2.1 安装
mini-css-extract-plugin
yarn add -D mini-css-extract-plugin
2.2.2 配置
build\webpack.prod.js
loader
// 抽离 css { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader 'css-loader', 'postcss-loader' ] },
plugins
new MiniCssExtractPlugin({ filename: 'css/main.[contenthash:8].css' })
2.3 压缩css(打包)
2.3.1 安装
optimize-css-assets-webpack-plugin
yarn add optimize-css-assets-webpack-plugin
2.3.2 配置
build-min-extract-css\webpack.prod.js
optimization: { // 压缩 css minimizer: [ new OptimizeCSSAssetsPlugin({})], }
2.4 抽离公共代码
抽离公共代码
我们在开发多个页面的项目的时候,有时候会在几个页面中引用某些公共的模块,这些公共模块多次被下载会造成资源浪费,如果把这些公共模块抽离出来只需下载一次之后便缓存起来了,这样就可以避免因重复下载而浪费资源,那么怎么在webpack中抽离出公共部分呢?方法如下:
2.4.1 公共模块抽离
举例:
项目中分别有a.js, b.js, page1.js, page2.js这四个JS文件,
page1.js 和 page2.js中同时都引用了a.js, b.js,
这时候想把a.js, b.js抽离出来合并成一个公共的js,然后在page1, page2中自动引入这个公共的js,
splitChunks: { cacheGroups: { //公用模块抽离 common: { chunks: 'initial', minSize: 0, //大于0个字节 minChunks: 2 //抽离公共代码时,这个代码块最小被引用的次数 } } }
2.4.2 第三方模块抽离
页面中有时会引入第三方模块,比如import $ from ‘jquery’; page1中需要引用,page2中也需要引用,这时候就可以用vendor把jquery抽离出来,方法如下:
optimization: { // 分割代码块 splitChunks: { chunks: 'all', /** * initial 入口 chunk,对于异步导入的文件不处理 async 异步 chunk,只对异步导入的文件处理 all 全部 chunk */ // 缓存分组 cacheGroups: { // 第三方模块 vendor: { name: 'vendor', // chunk 名称 priority: 1, // 权限更高,优先抽离,重要!!! test: /node_modules/, minSize: 0, // 大小限制 minChunks: 1 // 最少复用过几次 }, // 公共的模块 common: { name: 'common', // chunk 名称 priority: 0, // 优先级 minSize: 0, // 公共模块的大小限制 minChunks: 2 // 公共模块最少复用过几次 } } } }
注意:这里需要配置权重 priority,因为抽离的时候会执行第一个common配置,入口处看到jquery也被公用了就一起抽离了,不会再执行wendor的配置了,所以加了权重之后会先抽离第三方模块,然后再抽离公共common的,这样就实现了第三方和公用的都被抽离了。
3 性能优化
3.1 优化babal-loader(缩小构建目标)
3.1.1 原因
Loader处理文件的转换操作是很耗时的,所以需要让尽可能少的文件被Loader处理
3.1.2 配置
{ test: /\.js$/, use: [ 'babel-loader?cacheDirectory',//开启转换结果缓存 ], include: path.resolve(__dirname, 'src'),//只对src目录中文件采用babel-loader exclude: path.resolve(__dirname,' ./node_modules'),//排除node_modules目录下的文件 },
3.2 happyPack 多进程打包
3.2.1 安装
yarn add -D happypack
3.2.2 配置
Plugins
new HappyPack({ // 用唯一ID来代表当前HappyPack是用来处理一类特定文件的,与rules中的use对应 id: "babel", loaders: ["babel-loader?cacheDirectory"], //默认设置loader处理 threads: 5, //使用共享池处理 }), new HappyPack({ id: 'styles', loaders: ['css-loader', 'postcss-loader'], threads: 5, //代表开启几个子进程去处理这一类型的文件 verbose: true //是否允许输出日子 }),
rules
{ test: /\.(js|jsx)$/, use: [MiniCssExtractPlugin.loader, "HappyPack/loader?id=babel"], exclude: path.resolve(__dirname, " ./node_modules"), }, { test: /\.css$/, use: 'happypack/loader?id=styles', include: path.join(__dirname, '..', 'src') },
3.3 ParallelUglifyPlugin优化压缩
webpack默认提供了UglifyJS插件来压缩JS代码,但是它使用的是单线程压缩代码,也就是说多个js文件需要被压缩,它需要一个个文件进行压缩。
所以说在正式环境打包压缩代码速度非常慢(因为压缩JS代码需要先把代码解析成用Object抽象表示的AST语法树,再去应用各种规则分析和处理AST,导致这个过程耗时非常大)。
3.3.1 安装
yarn add -D webpack-parallel-uglify-plugin
3.3.2 配置
new ParallelUglifyPlugin({ // 传递给 UglifyJS的参数如下: uglifyJS: { output: { /* 是否输出可读性较强的代码,即会保留空格和制表符,默认为输出,为了达到更好的压缩效果, 可以设置为false */ beautify: false, /* 是否保留代码中的注释,默认为保留,为了达到更好的压缩效果,可以设置为false */ comments: false }, compress: { /* 是否删除代码中所有的console语句,默认为不删除,开启后,会删除所有的console语句 */ drop_console: true, /* 是否内嵌虽然已经定义了,但是只用到一次的变量,比如将 var x = 1; y = x, 转换成 y = 5, 默认为不 转换,为了达到更好的压缩效果,可以设置为false */ collapse_vars: true, /* 是否提取出现了多次但是没有定义成变量去引用的静态值,比如将 x = 'xxx'; y = 'xxx' 转换成 var a = 'xxxx'; x = a; y = a; 默认为不转换,为了达到更好的压缩效果,可以设置为false */ reduce_vars: true } } } )
3.4 自动刷新
借助自动化的手段,在监听到本地源码文件发生变化时,自动重新构建出可运行的代码后再控制浏览器刷新。Webpack将这些功能都内置了,并且提供了多种方案供我们选择。
3.4.1 项目中自动刷新的配置:
module.export = { watch: true, watchOptions: { // 不监听的文件或文件夹 ignored: /node_modules/, // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高 aggregateTimeout: 300, // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的 poll: 1000 } }
3.4.2 原理
- 在 Webpack 中监听一个文件发生变化的原理是定时(可在watchOptions.poll中设置)的去获取这个文件的最后编辑时间,每次都存下最新的最后编辑时间,如果发现当前获取的和最后一次保存的最后编辑时间不一致,就认为该文件发生了变化
- 当发现某个文件发生了变化时,并不会立刻告诉监听者,而是先缓存起来,收集一段时间(可在watchOptions.aggregateTimeout中设置)的变化后,再一次性告诉监听者。防止在编辑代码的过程中可能会高频的输入文字导致文件变化的事件高频的发生
3.5 模块热更新
_ _DevServer 还支持一种叫做模块热替换( Hot Module Replacement )的技术可在不刷新整个网页的情况下做到超灵敏实时预览。原理是在一个源码发生变化时,只需重新编译发生变化的模块,再用新输出的模块替换掉浏览器中对应的老模块 。模块热替换技术在很大程度上提升了开发效率和体验 。
项目中模块热替换的配置:
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin'); devServer:{ host:'localhost', port:'8080', open:true//自动拉起浏览器 // 只设置hot只有cssHMR生效 hot:true,//热加载 //hotOnly:true }, plugins:[ //热更新插件 new webpack.HotModuleReplacementPlugin() ] HotModuleReplacementPlugin生成一个mainifest(一个json结构描述了发生变化的modules列表) 和update file(一个js文件包含修改后的代码内容)
JS文件中
if (module.hot) { module.hot.accept('./print.js', function() { //告诉 webpack 接受热替换的模块 console.log('Accepting the updated printMe module!'); printMe(); }) } // 对Js生效
3.6 性能分析
3.6.1 webpack-bundle-analyzer
]
// config/webpack.common.js const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const commonConfig = { // ... plugins: [ new BundleAnalyzerPlugin({ analyzerPort: 8889, // 指定端口号 openAnalyzer: false, }), ] // ... }
3.6.2 speed-measure-webpack-plugin
// config/webpack.common.js const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); const smp = new SpeedMeasurePlugin(); // ... module.exports = (production) => { if (production) { const endProdConfig = merge(commonConfig, prodConfig); return smp.wrap(endProdConfig); } else { const endDevConfig = merge(commonConfig, devConfig); return smp.wrap(endDevConfig); } };
后记
2020-10-10 webpack 5.0.0 发布了,但这并不意味着它已经完成了,没有 bug,甚至功能完整。 就像 webpack 4
一样,我们通过修复问题以及增加新特性来延续开发。 在接下来的日子里,可能会有很多 bug 修复。新特性可能也会出现。
尝试用持久性缓存来提高构建性能。 尝试用更好的算法和默认值来改进长期缓存。 尝试用更好的 Tree Shaking 和代码生成来改善包大小。
尝试改善与网络平台的兼容性。 尝试在不引入任何破坏性变化的情况下,清理那些在实现 v4 功能时处于奇怪状态的内部结构。
试图通过现在引入突破性的变化来为未来的功能做准备,使其能够尽可能长时间地保持在 v5 版本上。 迁移指南