1 简介
1.1 webpack 五个核心概念
1.1.1 Entry
入口,指示webpack以哪个文件为入口起点开始打包,分析构建内部依赖图
1.1.2 Output
输出,指示webpack打包后的资源bundles输出到哪里去,以及怎么命名
1.1.3 Loader
加载器,可以让webpack能够去处理非js文件(webpack自身之理解javascript)
1.1.4 Plugins
插件,可以用于执行更广范围的任务,插件的范围包括从打包优化和压缩一直到重新定义环境变量等
1.1.5 Mode
模式,指示webpack使用相应模式的配置
- development:能让代码本地调试运行的环境
- production:能让代码优化上线的运行环境
2 webpack 初体验
2.1 创建一个webpack项目
- 创建一个目录,初始化包管理
npm init
- 运行命令
npm install webpack webpack-cli -D
- 新建src文件夹用于存放代码,新建build文件夹用于存放打包好的文件
- 编辑代码
- 运行命令(开发环境)
webpack ./src/index.js -o ./build/build.js --mode=development
默认只能处理js/json文件
2.2 打包css样式资源
- 新建css文件style.css
- 在index.js中引入
import 'style.css'
- 创建webpack.config.js文件来配置webpack(使用commentJS)
const { resolve } = require('path'); module.exports = { mode: 'development', // 模式,不能同时存在 // mode: 'production' entry: './src/index.js', // 入口文件 output: { filename: 'built.js', // 输出文件名 path: resolve(__dirname, 'build') // 输出路径 }, module: { // loader配置 rules: [{ test: /\.css$/,// css结尾的文件 use: [// 执行顺序:从右到左,从下到上,依次执行 'style-loader',// 创建爱你style标签,将js中的样式资源插入,添加到head 'css-loader',// 将css文件变成commentjs模块加载js中,内容是字符串 ] }] }, plugins: [ // 插件配置 ], } 复制代码
- 安装所需要的loader
- 运行命令
webpack
使用less、scss等预编译语法需要下载对应的loader,配置方式同上,再添加一个rule对象
2.3 打包HTML文件
- 安装打包html的插件
npm install html-webpack-plugin-D
- 在webpack.config.js中引入并添加配置
const HtmlWebpackPlugin = require('html-webpack-plugin'); ··· plugins: [ // 插件配置 // html-webpack-plugin // 功能: 创建一个空的html文件引入打包之后的所有资源 new HtmlWebpackPlugin({ // 赋值目标文件并引入打包的资源 template: './src/index.html' }) ], 复制代码
在创建html模板的时候不要引入资源
2.4 打包图片文件
图片添加略过,只给出配置方式
module: { // loader配置 rules: [{ test: /\.(jpg|png|gif|jpeg)$/, // 只有一个loader的时候可以直接loader+loader名 loader: 'url-loader',// 下载时需要下载url-loader和file-loader options: { /** * 限制大小,小于限制就会被base64处理 * 优点减少请求数量,减轻服务器压力 * 缺点图片会更大,文件请求速度会变慢 */ limit: 200 * 1024 } }] }, 复制代码
如果html模板中添加了图片,添加一个额外的loader来解析
{ test: /\.html$/, loader: 'html-withimg-loader' // 处理html中的图片 } 复制代码
但是,因为url-loader默认使用es6模块化解析,而html-loader引入图片是commentjs
解析时会出现[object Module]
解决办法:关闭ur-loader的ES6解析,在url-loader的options中添加
esModule: false
2.5 打包其他资源
{ exclude: /\.(css|html|jpg|png|gif|jpeg)$/, loader: 'file-loader' } 复制代码
除了这匹配到的后缀文件之外,其他的文件用file-loader打包
2.6 使用devServer
开发服务器:用来自动编译,热更新等
在内存中编译打包,不会有任何输出
运行:webpack-dev-server
- 运行
npm install webpack-dev-server -D
安装 - webpack.config.js中添加配置
devServer: { contentBase: resolve(__dirname, 'build'),// 项目构建后的路径 compress: true,// 启动gzip压缩 port: 3000,// 端口号 open: true,// 打开默认浏览器 } 复制代码
- 运行
webpack-dev-server
3 开发环境配置
const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'development', // mode: 'production' entry: './src/index.js', output: { filename: 'built.js', path: resolve(__dirname, 'build') }, module: { rules: [{ test: /\.css$/, use: [ 'style-loader', 'css-loader', ] },{ test: /\.(jpg|png|gif|jpeg)$/, loader: 'url-loader', options: { limit: 200 * 1024, esModule: false } },{ test: /\.html$/, loader: 'html-loader' }] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ], devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true, } } 复制代码
4 生产环境搭建
4.1 提取css成单独文件
- 安装插件
npm install mini-css-extract-plugin -D
- 修改webpack.config.js配置文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); ··· module: { rules: [{ test: /\.css$/, use: [ // 'style-loader', MiniCssExtractPlugin.loader,// 替换style-loader,提取css成单独文件 'css-loader', ] }, ···] }, plugins: [ ··· new MiniCssExtractPlugin({ filename: 'css/style.css' }) ], 复制代码
- 运行打包命令
4.2 css兼容性处理
使用到postcss
- 运行
npm install postcss-loader postcss-preset-env -D
- 修改webpack.config.js的css相关部分配置
{ test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', { loader: 'postcss-loader', options: { ident: 'postcss', plugins: () => [ require('postcss-preset-env')() ] } } ] } 复制代码
- 在package.json中添加浏览器版本控制
"browerslist": { "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safair version" ], "production": [ ">0.2%", "not dead", "not op_mini all" ] } 复制代码
- 运行打包
4.3 压缩css
使用插件 optimize-css-assets-webpack-plugin
- 运行
npm install optimize-css-assets-webpack-plugin -D
- webpack.config.js中引入
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin'); ··· plugins: [ ··· new OptimizeCssAssetsWebpackPlugin() ] 复制代码
4.4 js语法检查
使用eslint,使用airbnb规则检查js语法
- 运行
npm install eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import -D
安装所需的依赖和插件 - 在package.json中设置eslintConfig,继承airbnb
"eslintConfig": { "extends": "airbnb-base" } 复制代码
- webpack.config.js中添加rules
{ test: /\.js$/, exclude: /node_modules/,// 只检查自己的代码,不检查第三方库中的代码 loader: 'eslint-loader', options: { fix: true// 自动修复eslint错误 } } 复制代码
- 使用
// eslint-disable-next-line
控制下一行不进行检查
4.5 js兼容性处理
使用babel-loader进行兼容性处理
- 运行
npm install babel-loader @babel/core @babel/preset-env @babel/polyfill -D
- 添加webpack.config.js的rules
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } }, 复制代码
@babel/polyfill属于高级语法支持,在需要兼容的js文件中引入即可
import '@babel/polyfill'
@babel/polyfill属于暴力支持,回事打包文件变的非常大,所以一般采用按需加载的方式:core.js
- 运行
npm install corejs -D
- 修改之前的兼容性配置规则
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage',// 按需加载 corejs: { version: 3 }, targets: {// 制定兼容浏览器版本 chrome: '60', firefox: '50', ie: '9', safari: '10' } } ] ] } }, 复制代码
4.6 HTML和js压缩
4.6.1 js压缩
将模式调整为production
即可实现js代码压缩
4.6.2 HTML压缩
在html的插件中添加设置
new HtmlWebpackPlugin({ // 复制目标文件并引入打包的资源 template: './src/index.html', minify: { // 移除空格 collapseWhitespace: true, // 移除注释 removeComments: true } }), 复制代码
5 生产环境配置
const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'js/built.js', path: resolve(__dirname, 'build') }, module: { // loader配置 rules: [{ test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', { loader: 'postcss-loader', options: { ident: 'postcss', plugins: () => [ require('postcss-preset-env')() ] } } ] }, { test: /\.(jpg|png|gif|jpeg)$/, loader: 'url-loader', options: { limit: 200 * 1024, esModule: false } }, { test: /\.html$/, loader: 'html-loader' }, { test: /\.js$/, exclude: /node_modules/, enforce: 'pre',// 优先执行 loader: 'eslint-loader', options: { fix: true } }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: { version: 3 }, targets: { chrome: '60', firefox: '50', ie: '9', safari: '10' } } ] ] } }, ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', minify: { collapseWhitespace: true, removeComments: true } }), new MiniCssExtractPlugin({ filename: 'css/style.css' }), new OptimizeCssAssetsWebpackPlugin() ], devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true, }, // mode: 'development', mode: 'production', } 复制代码
babel和eslint可能会产生冲突使用enforce: 'pre'让eslint先执行
6 性能优化篇
6.1 开发环境性能优化
6.1.1 优化打包构建速度
6.1.1.1HMR——hot module replacement(模块热替换)
只会重新打包这一个模块,不会重新打包
配置devServer
devServer: { contentBase: resolve(__dirname, 'build'), compress: true, // 启动gzip压缩 port: 3000, // 端口号 open: true, // 打开默认浏览器 hot: true,// 开启热更新 }, 复制代码
样式文件热更新基于'style-loader';
js文件默认不能使用HMR功能;
HTML文件默认不能使用HMR功能,同时会导致问题,HTML文件不能热更新,解决办法:修改entry入口,将html文件引入(不需要做HMR)
6.1.1.2 oneOf减少loader检索
每个文件在检索的的时候会每个rules都验证一遍,将所有的rules都放到oneOf中就可以验证到所在的规则不再继续验证
这样出现了问题,js可能会有多个loader,解决办法:oneOf中保留一条rule,其他的取出来与oneOf平级
6.1.2 优化代码调试
source-map:提供语言代码构建后代码映射技术(构建代码出错,追踪源代码错误)
添加devtool
devtool: 'source-map', 复制代码
[inline- |hidden- | eval-][nosources-][cheap-[module-]]source-map
分别对应:
- inline-内联:(汇集在一起)错误代码准确信息和源代码错误位置
- hidden-外部:代码错误原因,没有错误位置,不能追踪代码错误
- eval-内联:(分别跟每个文件对应)错误代码准确信息和源代码错误位置(位置信息是哈希值)
- nosources-外部:错误代码准确信息,没有源代码错误信息
- cheap-外部:错误代码准确信息和源代码错误位置(只精确到行,提示整行错误)
- cheap-module-外部:错误代码准确信息和源代码错误位置
- source-map`外部:错误代码准确信息和源代码错误位置:
模式使用
- 开发环境:速度快,开发更友好
- 速度快eval>inline>cheap>···
- eval-cheap-source-map
- eval-source-map
- 调试更友好
- source-map
- cheap-module-source-map
- cheap-source-map
- -->eval-source-map / eval-cheap-module-source-map
- 生产环境:源代码要不要隐藏
- 内联会让代码体积变大,使用外部方式
- source-map
- ···
- 隐藏代码
- nosource-source-map// 全部隐藏
- hidden-source-map// 隐藏源代码
- -->source-map / cheap-module-source-map
6.2生产环境性能优化
缓存机制
babel缓存
修改js打包配置,开启缓存
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', // 按需加载 corejs: { version: 3 }, targets: { // 制定兼容浏览器版本 chrome: '60', firefox: '50', ie: '9', safari: '10' } } ] ], cacheDirectory: true// 开启缓存 } }, 复制代码
强缓存模式,所有的数据都要缓存,从新打包之后不会试用新的文件
文件资源缓存
hash
这里修改输出文件名,每次到打包之后的文件都不一样,这就不加绝了强制缓存带来的不刷新了(取10位hash值)
output: { filename: 'js/built[hash:10].js', // 输出文件名 path: resolve(__dirname, 'build') // 输出路径 }, 复制代码
所有的js和css文件共用一个哈希值,重新打包会导致所有的缓存失效
chunkhash
根据chunk生成的hash值,如果打包源来自同一个chunk,那么hash值相同
此时,js和css的hash值还是相同,因为css是在js中引入的,同属于一个chunk
contenthash
根据文件内容生成hash,不同文件hash值一定不一样
filename: 'js/built[contenthash:10].js' 复制代码
如果单独提取了css,css命名修改为
new MiniCssExtractPlugin({ filename: 'css/style-[contenthash:10].css' }) 复制代码
tree shaking
去除无用代码
- 必须使用ES6模块化
- 开启production环境
打包是会自动摇去没有用的叶子
在package.json中配置"sideEffects": false
所有代码都没有副作用
问题:可能会把css文件干掉,解决"sideEffects": ["*.css"]
code split
文件分割
- 多入口文件分割
- 添加webpack.config.js配置项,将node中的代码单独打包一个chunks,自动分析多入口文件中有没有公共文件,如果有会单独打包成一个chunk
optimization: { splitChunks: { chunks: 'all' } }, 复制代码
- 通过js代码,让某个文件单独打包成一个chunk
懒加载、预加载
懒加载
document.getElementById('btn').onclick = function() { import(/* webpackChunkName: 'test'*/'./js/test.js') .then(({mul}) => { console.log(mul(4, 5)); }) } 复制代码
将引入放在事件的回调函数中,事件触发时才加载js文件
配合eslint语法检查是有问题的,在.eslintrc文件汇总设置
"allowImportExportEverywher": true
,允许在任何地方import
预加载
将引入改为
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./js/test.js') 复制代码
预加载会提前将js文件加载
区别
- 懒加载:文件需要才加载
- 预加载:使用之前加载,浏览器空闲再加载(兼容性较差)
- 正常加载:并行加载
PWA
渐进式网络开发应用程序(离线可访问)
workbox-->workbox-webpack-plugin
- 运行
npm install workbox-webpack-plugin -D
- 修改webpack.config.js配置
const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); ··· plugin: [ ··· new WorkboxWebpackPlugin.GenerateSW({ clientsClaim: true, skipWaiting: true }) ] 复制代码
- 帮助serviceworker快速启动;删除旧的serviceworker
- 在入口文件中注册serviceworker,处理兼容
if('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('./service-worker.js') .then(() => { console.log('sw注册成功') }).catch(() => { console.log('sw注册失败') }) }) } 复制代码
- eslint不识别window,navigator等全局变量,修改package.json配置
"eslintConfig": { "extends": "airbnb-base", "env": { "browser": true// 支持浏览器全局变量 } } 复制代码
- 构建代码
多进程打包
消耗时间长的才需要多进程打包,多进程开始需要时间,进程通信也需要时间
- 运行
npm install thread-loader -D
- 修改webpack.config.js中babel所在的rule
{ test: /\.js$/, exclude: /node_modules/, use: [ { loader: 'thread-loader',// 多进程打包 options: { workers: 2,// 使用两个进程 } }, { loader: 'babel-loader', ··· } ] }, 复制代码
externals
忽略某些包,比如CDN引入的
在webpack.config.js第一级目录下添加
externals: { // 包名: npm库名 jquery: 'jQuery' } 复制代码
dll
- 新建一个配置文件,例如
webpack.dll.js
const {resolve} = require('path'); const webpack = require('webpack'); module.exports = { entry: { // [name]: 要打包的库名 jquery: ['jquery'] }, output: { filename: '[name].js', path: resolve(__dirname, 'dll'), library: '[name]_[hash]'// 打包库里暴露的内容叫什么名 }, plugins: [ // 打包生成一个映射关系 new webpack.DllPlugin({ name: '[name]_[hash]',// 映射库暴露的内容名称 path: resolve(__dirname, 'dll/manifast.json')// 输出文件路径 }) ], mode: 'production' } 复制代码
- 运行命令
webpack --config webpack.dll.js
- 运行
npm install add-asset-html-webpack-plugin -D
- 修改
webpack.config.js
配置
const webpack = require('webpack'); const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); ··· plugins: [ ··· // 告诉webpack那些文件不需要打包,以及映射关系 new webpack.DllReferencePlugin({ manifest: resolve(__dirname, 'dll/manifest') }), // 将文件打包输出,并在html自动引入该文件 new AddAssetHtmlWebpackPlugin({ filepath: resolve(__dirname, 'dll/') }) ]