第 1 章:webpack 简介
1.1 webpack 是什么
webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。
在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。
它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。
1.2 webpack 五个核心概念
1.2.1 Entry
入口(Entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。
1.2.2 Output
输出(Output)指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。
1.2.3 Loader
Loader 让 webpack 能 够去处理 那些非 JavaScript 文件 (webpack 自身只理解
JavaScript)
1.2.4 Plugins
插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,
一直到重新定义环境中的变量等。
1.2.5 Mode
模式(Mode)指示 webpack 使用相应模式的配置。
第 2 章:webpack 的初体验
2.1 初始化配置
1. 初始化 package.json
输入指令:
npm init
2. 下载并安装 webpack
输入指令:
npm install webpack webpack-cli -g
npm install webpack webpack-cli -D
2.2 编译打包应用
1. 创建文件
2. 运行指令
开发环境指令:webpack src/js/index.js -o build/js/built.js --mode=development
功能:webpack 能够编译打包 js 和 json 文件,并且能将 es6 的模块化语法转换成
浏览器能识别的语法。
生产环境指令:webpack src/js/index.js -o build/js/built.js --mode=production
功能:在开发配置功能上多一个功能,压缩代码。
3. 结论
webpack 能够编译打包 js 和 json 文件。
能将 es6 的模块化语法转换成浏览器能识别的语法。
生产环境和开发环境将 ES6 模块化编译成浏览器能识别的模块化,但是不能处理 ES6 的基本语法转化为 ES5(需要借助 loader)
生产环境比开发环境多一个压缩 js 代码的功能
4. 问题
不能编译打包 css、img 等文件。
不能将 js 的 es6 基本语法转化为 es5 以下语法。
第 3 章: Webpack 的基本配置
3.1 基本结构
1. 创建文件 webpack.config.js
2. 配置内容如下
1 module.exports = { 2 entry: './src/js/index.js', // 入口文件 3 output: { // 输出配置 4 filename: './built.js', // 输出文件名 5 path: resolve(__dirname, 'build/js') // 输出文件路径配置 6 }, 7 module:{ //匹配对应的文件,并将文件内容通过对应的loader转变为webpack能够识别的内容(相当于翻译官) 8 rules:[ 9 //详细loader配置 10 //不同文件必须配置不同的loader处理 11 ... 12 ] 13 }, 14 plugins:[ //通过插件处理相应的功能 15 ... 16 ], 17 mode: 'development' //开发环境 18 };
3. 运行指令: webpack
第 4 章:Webpack 开发环境的基本配置
webpack.config.js 是 webpack 的配置文件。
作用: 指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
所有构建工具都是基于 nodejs 平台运行的,模块化默认采用 commonjs。
开发环境配置主要是为了能让代码运行。主要考虑以下几个方面:
- 打包样式资源
- 打包 html 资源
- 打包图片资源
- 打包其他资源
- devServer
下面是一个简单的开发环境webpack.config.js配置文件
1 // resolve用来拼接绝对路径的方法 2 const { resolve } = require('path') // node 内置核心模块,用来处理路径问题。 3 const HtmlWebpackPlugin = require('html-webpack-plugin') // 引用plugin 4 5 module.exports = { 6 // webpack配置 7 entry: './src/js/index.js', // 入口起点 8 output: { 9 // 输出 10 // 输出文件名 输出文件为js 目录下的build.js 11 filename: 'js/built.js', 12 // __dirname是nodejs的变量,代表当前文件的目录绝对路径 13 path: resolve(__dirname, 'build'), // 输出路径,所有资源打包都会输出到这个文件夹下 14 }, 15 // loader配置 16 module: { 17 rules: [ 18 // 详细的loader配置 19 // 不同文件必须配置不同loader处理 20 { 21 // 匹配哪些文件 22 test: /.less$/, 23 // 使用哪些loader进行处理 24 use: [ 25 // use数组中loader执行顺序:从右到左,从下到上,依次执行(先执行less-loader) 26 // style-loader:创建style标签,将js中的样式资源插入进去,添加到head中生效 27 'style-loader', 28 // css-loader:将css文件变成commonjs模块加载到js中,里面内容是样式字符串 29 'css-loader', 30 // less-loader:将less文件编译成css文件,需要下载less-loader和less 31 'less-loader' 32 ], 33 }, 34 { 35 test: /.css$/, 36 use: ['style-loader', 'css-loader'], 37 }, 38 { 39 // url-loader:处理图片资源,问题:默认处理不了html中的img图片 40 test: /.(jpg|png|gif)$/, 41 // 需要下载 url-loader file-loader 42 loader: 'url-loader', 43 options: { 44 // 图片大小小于8kb,就会被base64处理,优点:减少请求数量(减轻服务器压力), 45 // 缺点:图片体积会更大(文件请求速度更慢) 46 // base64在客户端本地解码所以会减少服务器压力,如果图片过大还采用base64编码会导致cpu调用率上升,网页加载时变卡 47 limit: 8 * 1024, 48 // 给图片重命名,[hash:10]:取图片的hash的前10位,[ext]:取文件原来扩展名 49 name: '[hash:10].[ext]', 50 // 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是conmonjs,解析时会出问题:[object Module] 51 // 解决:关闭url-loader的es6模块化,使用commonjs解析 52 esModule: false, 53 outputPath: 'imgs', //将打包后的图片资源放到imgs文件中(为了让打包文件目录与src目录一致) 54 }, 55 }, 56 { 57 test: /.html$/, 58 // 处理html文件的img图片(负责引入img,从而能被url-loader进行处理) 59 loader: 'html-loader', 60 }, 61 // 打包其他资源(除了html/js/css资源以外的资源) 62 { 63 // 排除html|js|css|less|jpg|png|gif文件 64 exclude: /.(html|js|css|less|jpg|png|gif)/, 65 // file-loader:处理其他文件 66 loader: 'file-loader', 67 options: { 68 name: '[hash:10].[ext]', 69 outputPath: 'media', //将打包后的图片资源放到media文件中 70 }, 71 }, 72 ], 73 }, 74 // plugin的配置 75 plugins: [ 76 // 下载html-webpack-plugin 77 // html-webpack-plugin:默认会创建一个空的html文件,自动引入打包输出的所有资源(JS/CSS) 78 // 需要有结构的HTML文件可以加一个template 79 new HtmlWebpackPlugin({ 80 // 复制这个./src/index.html文件,并自动引入打包输出的所有资源(JS/CSS) 81 template: './src/index.html', 82 }), 83 ], 84 // 模式 85 mode: 'development', // 开发模式 生产模式值为: 'production' 86 // 开发服务器 devServer:用来自动化,不用每次修改后都重新输入webpack打包一遍(自动编译,自动打开浏览器,自动刷新浏览器) 87 // 特点:只会在内存中编译打包,不会有任何输出(不会像之前那样在外面看到打包输出的build包,而是在内存中,关闭后会自动删除) 88 // 安装: npm i webpack-dev-server -D 89 // 启动devServer指令为:npx webpack-dev-server 90 devServer: { 91 // 项目构建后路径 92 contentBase: resolve(__dirname, 'build'), 93 // 启动gzip压缩 94 compress: true, 95 // 端口号 96 port: 3000, 97 // 自动打开浏览器 98 open: true, 99 //是否项目编译后自动打开浏览器 100 autoOpenBrowser: false, //true为自动打开 false不自动打开 101 }, 102 }
第 5 章:Webpack 生产环境的基本配置
而生产环境的配置需要考虑以下几个方面:
- 提取 css 成单独文件
- css 兼容性处理
- 压缩 css
- js 语法检查
- js 兼容性处理
- js 压缩
- html 压缩
下面是一个基本的生产环境下的webpack.config.js配置
5.1 提取css成单独文件
1.下载插件:npm install --save-dev mini-css-extract-plugin
2.修改配置文件
1 const { resolve } = require('path'); 2 const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 module.exports = { 5 entry: './src/js/index.js', 6 output: { 7 filename: 'js/built.js', 8 path: resolve(__dirname, 'build') }, 9 module: { 10 rules: [ 11 { 12 test: /.css$/, 13 use: [ 14 // 创建 style 标签,将样式放入head中 15 //'style-loader', 16 // 这个 loader 取代 style-loader。作用:提取 js 中的 css 成单独文件 17 MiniCssExtractPlugin.loader, // 将 css 文件整合到 js 文件中 18 'css-loader' 19 ] 20 } 21 ] 22 }, 23 plugins: [ 24 new HtmlWebpackPlugin({ template: './src/index.html' }), 25 new MiniCssExtractPlugin({ 26 // 对输出的 css 文件进行重命名 filename: 'css/built.css' }) 27 ], 28 mode: 'development' 29 };
5.2 css 兼容性处理
1.下载 loader
npm install --save-dev postcss-loader postcss-preset-env
2.修改配置文件webpack.config.js
1 const { resolve } = require('path'); 2 const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 5 // 设置 nodejs 环境变量 6 // process.env.NODE_ENV = 'development'; 7 module.exports = { 8 entry: './src/js/index.js', 9 output: { 10 filename: 'js/built.js', 11 path: resolve(__dirname, 'build') 12 }, 13 module: { 14 rules: [ 15 { 16 test: /.css$/, 17 use: [ 18 MiniCssExtractPlugin.loader, 19 'css-loader', 20 /* 21 css兼容性处理: postcss --> postcss-loader postcss-preset-env 22 注意:安装postcss-loader 3.0.0 版本,否则会报错 23 24 帮postcss找到package.json 中browserlist里面的配置 通过配置加载指定的css兼容性样式 25 26 "browserslist": { 27 //开发环境 ---》设置环境变量:process.env.NODE_ENV = 'development' 28 "development" : [ 29 "last 1 chrome version", 30 "last 1 firefox version", 31 "last 1 safari version" 32 ], 33 //生产环境 默认是看生产环境 34 "production" : [ 35 ">0.2%", 36 "not dead", 37 "not op_mini all" 38 ] 39 } 40 */ 41 //使用loader的默认配置 42 // postcss-loader 43 //修改loader的配置 44 { 45 loader: 'postcss-loader', 46 options: { 47 ident: 'postcss', //固定写法 48 plugins: () => [ // postcss 的插件 49 require('postcss-preset-env')() 50 ] 51 } 52 } 53 ] 54 } 55 ] 56 }, 57 plugins: [ 58 new HtmlWebpackPlugin({ 59 template: './src/index.html' 60 }), 61 new MiniCssExtractPlugin({ 62 filename: 'css/built.css' 63 }) 64 ], 65 mode: 'development' 66 };
3.修改 package.json
1 "browserslist": { 2 "development": [ 3 "last 1 chrome version", 4 "last 1 firefox version", 5 "last 1 safari version" 6 ], 7 "production": [ 8 ">0.2%", 9 "not dead", 10 "not op_mini all" 11 ] 12 }
5.3 压缩 css
1. 下载安装包
npm install --save-dev optimize-css-assets-webpack-plugin
2.修改配置文件
1 const { resolve } = require('path'); 2 const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin' ) 5 6 module.exports = { 7 entry: './src/js/index.js', 8 output: { 9 filename: 'js/built.js', 10 path: resolve(__dirname, 'build') 11 }, 12 module: { 13 rules: [ 14 { 15 test: /.css$/, 16 use: [ 17 MiniCssExtractPlugin.loader, 18 'css-loader', 19 { 20 loader: 'postcss-loader', 21 options: { 22 ident: 'postcss', //固定写法 23 plugins: () => [ // postcss 的插件 24 require('postcss-preset-env')() 25 ] 26 } 27 } 28 ] 29 } 30 ] 31 }, 32 plugins: [ 33 new HtmlWebpackPlugin({ 34 template: './src/index.html' 35 }), 36 new MiniCssExtractPlugin({ 37 filename: 'css/built.css' 38 }), 39 // 压缩 css 40 new OptimizeCssAssetsWebpackPlugin() 41 ], 42 mode: 'development' 43 };
5.4 js 语法检查
1.下载安装包
npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
2.修改配置文件
1 const { resolve } = require('path'); 2 const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin' ) 5 6 // 设置 nodejs 环境变量 7 // process.env.NODE_ENV = 'development'; 8 9 module.exports = { 10 entry: './src/js/index.js', 11 output: { 12 filename: 'js/built.js', 13 path: resolve(__dirname, 'build') }, 14 module: { 15 rules: [ 16 /* 17 语法检查: eslint-loader eslint 18 注意:只检查自己写的源代码,第三方的库是不用检查的 19 设置检查规则: package.json 中 eslintConfig 中设置~ 20 "eslintConfig": { 21 "extends": "airbnb-base" 22 } 23 对于console.log的检测,eslint会爆出警告 24 可以在console.log前面一行以单行注释的方式忽略 console.log的检测 25 // eslint-disable-next-line 26 airbnb --> eslint-config-airbnb-base eslint-plugin-import eslint 27 */ 28 { 29 test: /.js$/, 30 exclude: /node_modules/, 31 loader: 'eslint-loader', 32 options: { 33 // 自动修复 eslint 的错误 34 fix: true 35 } 36 } 37 ] 38 }, 39 plugins: [ 40 new HtmlWebpackPlugin({ 41 template: './src/index.html' 42 }), 43 new MiniCssExtractPlugin({ filename: 'css/built.css' }), 44 // 压缩 css 45 new OptimizeCssAssetsWebpackPlugin() 46 ], 47 mode: 'development' 48 };
3.配置package.json
1 "eslintConfig": { 2 "extends": "airbnb-base", 3 "env": { 4 "browser": true 5 } 6 }
5.5 js 兼容性处理
1.下载安装包
npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/polyfill
2.修改配置文件
1 const { resolve } = require('path'); 2 const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 4 module.exports = { 5 entry: './src/js/index.js', 6 output: { 7 filename: 'js/built.js', 8 path: resolve(__dirname, 'build') 9 }, 10 module: { 11 rules: [ 12 /* 13 js兼容性处理: babel-loader @babel/preset-env @babel/core 14 下载 babel-loader @babel/preset-env 15 1. 基本js兼容性处理 --》 @babel/preset-env 16 问题: 只能转换基本语法 如promise高级语法不能转换 17 2. 全部js兼容性处理 --》 @babel/polyfill 18 问题: 我只要解决部分兼容性问题 但是将所有兼容性代码全部引入 体积太大了 19 3.需要做兼容性处理的就做 按需加载 --》 core-js 下载core-js 20 21 1 3结合 完成所有的兼容性处理方案 2因为处理完包的体积(built.js)太大了,所以不考虑 22 */ 23 { 24 test: /.js$/, 25 exclude: /node_modules/, 26 loader: 'babel-loader', 27 options: { // 预设:指示 babel 做怎么样的兼容性处理 28 presets: [ 29 [ 30 '@babel/preset-env', 31 { 32 // 按需加载 33 useBuiltIns: 'usage', // 指定 core-js 版本 34 corejs: { 35 version: 3 36 }, 37 // 指定兼容性做到哪个版本浏览器 38 targets: { 39 chrome: '60', 40 firefox: '60', 41 ie: '9', 42 safari: '10', 43 edge: '17' 44 } 45 } 46 ] 47 ] 48 } 49 } 50 ] 51 }, 52 plugins: [ 53 new HtmlWebpackPlugin({ 54 template: './src/index.html' 55 }) 56 ], 57 mode: 'development' 58 }
5.6 js 压缩
生产环境下会自动压缩 js 代码
5.7 HTML 压缩
修改配置文件
1 const { resolve } = require('path'); 2 const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 module.exports = { 4 entry: './src/js/index.js', 5 output: { 6 filename: 'js/built.js', 7 path: resolve(__dirname, 'build') 8 }, 9 plugins: [ 10 new HtmlWebpackPlugin({ 11 template: './src/index.html', 12 // 压缩 html 代码 13 minify: { 14 // 移除空格 15 collapseWhitespace: true, 16 // 移除注释 17 removeComments: true 18 } 19 }) 20 ], 21 mode: 'production' 22 };
5.8生产环境基本配置
配置文件参考代码:
1 const { resolve } = require('path') 2 const MiniCssExtractorPlugin = require('mini-css-extract-plugin') 3 const OptimiziCssAssetsWebpackPlugin = require('optimizi-css-assets-webpack-plugin') 4 const HtmlWebpackPlugin = require('html-webpack-plugin') 5 6 // 定义node.js的环境变量,决定使用browserslist的哪个环境 7 process.env.NODE_ENV = 'production' 8 9 // 复用loader的写法 10 const commonCssLoader = [ 11 // 这个loader取代style-loader。作用:提取js中的css成单独文件然后通过link加载 12 MiniCssExtractPlugin.loader, 13 // css-loader:将css文件整合到js文件中 14 // 经过css-loader处理后,样式文件是在js文件中的 15 // 问题:1.js文件体积会很大2.需要先加载js再动态创建style标签,样式渲染速度就慢,会出现闪屏现象 16 // 解决:用MiniCssExtractPlugin.loader替代style-loader 17 'css-loader', 18 /* 19 postcss-loader:css兼容性处理:postcss --> 需要安装:postcss-loader postcss-preset-env 20 postcss需要通过package.json中browserslist里面的配置加载指定的css兼容性样式 21 在package.json中定义browserslist: 22 "browserslist": { 23 // 开发环境 --> 设置node环境变量:process.env.NODE_ENV = development 24 "development": [ // 只需要可以运行即可 25 "last 1 chrome version", 26 "last 1 firefox version", 27 "last 1 safari version" 28 ], 29 // 生产环境。默认是生产环境 30 "production": [ // 需要满足绝大多数浏览器的兼容 31 ">0.2%", 32 "not dead", 33 "not op_mini all" 34 ] 35 }, 36 */ 37 { 38 loader: 'postcss-loader', 39 options: { 40 ident: 'postcss', // 基本写法 41 plugins: () => [ 42 // postcss的插件 43 require('postcss-preset-env')(), 44 ], 45 }, 46 }, 47 ] 48 49 module.exports = { 50 entry: './src/js/index.js', 51 output: { 52 filename: 'js/built.js', 53 path: resolve(__dirname, 'build'), 54 }, 55 module: { 56 rules: [ 57 { 58 test: /.css$/, 59 use: [...commonCssLoader], 60 }, 61 { 62 test: /.less$/, 63 use: [...commonCssLoader, 'less-loader'], 64 }, 65 /* 66 正常来讲,一个文件只能被一个loader处理 67 当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序 68 先执行eslint再执行babel(用enforce) 69 */ 70 { 71 /* 72 js的语法检查: 需要下载 eslint-loader eslint 73 注意:只检查自己写的源代码,第三方的库是不用检查的 74 airbnb(一个流行的js风格) --> 需要下载 eslint-config-airbnb-base eslint-plugin-import 75 设置检查规则: 76 package.json中eslintConfig中设置 77 "eslintConfig": { 78 "extends": "airbnb-base", // 继承airbnb的风格规范 79 "env": { 80 "browser": true // 可以使用浏览器中的全局变量(使用window不会报错) 81 } 82 } 83 */ 84 test: /.js$/, 85 exclude: /node_modules/, // 忽略node_modules 86 enforce: 'pre', // 优先执行 87 loader: 'eslint-loader', 88 options: { 89 // 自动修复 90 fix: true, 91 }, 92 }, 93 /* 94 js兼容性处理:需要下载 babel-loader @babel/core 95 1. 基本js兼容性处理 --> @babel/preset-env 96 问题:只能转换基本语法,如promise高级语法不能转换 97 2. 全部js兼容性处理 --> @babel/polyfill 98 问题:只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了 99 3. 需要做兼容性处理的就做:按需加载 --> core-js 100 */ 101 { 102 // 第三种方式:按需加载 103 test: /.js$/, 104 exclude: /node_modules/, 105 loader: 'babel-loader', 106 options: { 107 // 预设:指示babel做怎样的兼容性处理 108 presets: [ 109 '@babel/preset-env', // 基本预设 110 { 111 useBuiltIns: 'usage', //按需加载 112 corejs: { version: 3 }, // 指定core-js版本 113 targets: { // 指定兼容到什么版本的浏览器 114 chrome: '60', 115 firefox: '50', 116 ie: '9', 117 safari: '10', 118 edge: '17' 119 }, 120 }, 121 ], 122 }, 123 }, 124 { 125 // 图片处理 126 test: /.(jpg|png|gif)/, 127 loader: 'url-loader', 128 options: { 129 limit: 8 * 1024, 130 name: '[hash:10].[ext]', 131 outputPath: 'imgs', 132 esModule: false, // 关闭url-loader默认使用的es6模块化解析 133 }, 134 }, 135 // html中的图片处理 136 { 137 test: /.html$/, 138 loader: 'html-loader', 139 }, 140 // 处理其他文件 141 { 142 exclude: /.(js|css|less|html|jpg|png|gif)/, 143 loader: 'file-loader', 144 options: { 145 outputPath: 'media', 146 }, 147 }, 148 ], 149 }, 150 plugins: [ 151 new MiniCssExtractPlugin({ 152 // 对输出的css文件进行重命名 153 filename: 'css/built.css', 154 }), 155 // 压缩css 156 new OptimiziCssAssetsWebpackPlugin(), 157 // HtmlWebpackPlugin:html文件的打包和压缩处理 158 // 通过这个插件会自动将单独打包的样式文件通过link标签引入 159 new HtmlWebpackPlugin({ 160 template: './src/index.html', 161 // 压缩html代码 162 minify: { 163 // 移除空格 164 collapseWhitespace: true, 165 // 移除注释 166 removeComments: true, 167 }, 168 }), 169 ], 170 // 生产环境下会自动压缩js代码 171 mode: 'production', 172 }
第 6 章:webpack 优化配置
6.1 开发环境性能优化
6.1.1 HMR(模块热替换)
HMR: hot module replacement 热模块替换 / 模块热替换
作用:一个模块发生变化,只会重新打包构建这一个模块(而不是打包所有模块) ,极大提升构建速度
代码:只需要在 devServer 中设置 hot 为 true,就会自动开启HMR功能(只能在开发模式下使用)
1 /* 2 HRM: hot module replacement 热模块替换/ 模块热替换 3 作用: 一个模块发生变化,只会重新打包这一个模块(而不是打包所有) 4 极大地提升构建速度 5 6 样式文件: 可以使用HMR功能 因为style-loader内部实现了 7 js文件: 默认不能使用HMR 功能--->需要修改js代码 添加支持HRM功能的代码 8 注意:HRM功能对js的处理 只能处理非入口js文件的其他文件 9 CSS文件: 默认不能使用HMR 功能 同时会导致问题:html文件不能热更新了 10 解决: 修改entry入口 将html文件引入 11 */ 12 devServer: { 13 contentBase: resolve(__dirname, 'build'), 14 compress: true, 15 port: 3000, 16 open: true, 17 // 开启HMR功能 18 // 当修改了webpack配置,新配置要想生效,必须重启webpack服务 19 hot: true 20 }
每种文件实现热模块替换的情况:
- 样式文件:可以使用HMR功能,因为开发环境下使用的 style-loader 内部默认实现了热模块替换功能
- js 文件:默认不能使用HMR功能(修改一个 js 模块所有 js 模块都会刷新)
--> 实现 HMR 需要修改 js 代码(添加支持 HMR 功能的代码)
在入口文件中index.js中
1 // 绑定 2 if (module.hot) { 3 // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效 4 module.hot.accept('./print.js', function() { 5 // 方法会监听 print.js 文件的变化,一旦发生变化,只有这个模块会重新打包构建,其他模块不会。 6 // 会执行后面的回调函数 7 print(); 8 }); 9 }
注意:HMR 功能对 js 的处理,只能处理非入口 js 文件的其他文件。
- html 文件: 默认不能使用 HMR 功能(html 不用做 HMR 功能,因为只有一个 html 文件,不需要再优化)
使用 HMR 会导致问题:html 文件不能热更新了(不会自动打包构建)
解决:修改 entry 入口,将 html 文件引入(这样 html 修改整体刷新)
entry: ['./src/js/index.js', './src/index.html']
6.1.2 source-map
source-map:一种提供源代码到构建后代码的映射的技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)
参数:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
代码:
devtool: 'eval-source-map'
可选方案:[生成source-map的位置|给出的错误代码信息]
- source-map:外部,错误代码准确信息 和 源代码的错误位置
- inline-source-map:内联,只生成一个内联 source-map,错误代码准确信息 和 源代码的错误位置
- hidden-source-map:外部,错误代码错误原因,但是没有错误位置(为了隐藏源代码),不能追踪源代码错误,只能提示到构建后代码的错误位置
- eval-source-map:内联,每一个文件都生成对应的 source-map,都在 eval 中,错误代码准确信息 和 源代码的错误位
- nosources-source-map:外部,错误代码准确信息,但是没有任何源代码信息(为了隐藏源代码)
- cheap-source-map:外部,错误代码准确信息 和 源代码的错误位置,只能把错误精确到整行,忽略列
- cheap-module-source-map:外部,错误代码准确信息 和 源代码的错误位置,module 会加入 loader 的 source-map
内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快
开发/生产环境可做的选择:
开发环境:需要考虑速度快,调试更友好
- 速度快( eval > inline > cheap >... )
- eval-cheap-souce-map
- eval-source-map
- 调试更友好
- souce-map
- cheap-module-souce-map
- cheap-souce-map
最终得出最好的两种方案 --> eval-source-map(完整度高,内联速度快) / eval-cheap-module-souce-map(错误提示忽略列但是包含其他信息,内联速度快)
生产环境:需要考虑源代码要不要隐藏,调试要不要更友好
- 内联会让代码体积变大,所以在生产环境不用内联
- 隐藏源代码
- nosources-source-map 全部隐藏
- hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
最终得出最好的两种方案 --> source-map(最完整) / cheap-module-souce-map(错误提示一整行忽略列)
6.2 生产环境性能优化
6.2.1 优化打包构建速度
6.2.1.1 oneOf
oneOf:匹配到 loader 后就不再向后进行匹配,优化生产环境的打包构建速度
代码:
1 module: { 2 rules: [ 3 { 4 // js 语法检查 5 test: /.js$/, 6 exclude: /node_modules/, 7 // 优先执行 8 enforce: 'pre', 9 loader: 'eslint-loader', 10 options: { 11 fix: true 12 } 13 }, 14 { 15 // oneOf 优化生产环境的打包构建速度 16 // 以下loader只会匹配一个(匹配到了后就不会再往下匹配了) 17 // 注意:不能有两个配置处理同一种类型文件(所以把eslint-loader提取出去放外面) 18 oneOf: [ 19 { 20 test: /.css$/, 21 use: [...commonCssLoader] 22 }, 23 { 24 test: /.less$/, 25 use: [...commonCssLoader, 'less-loader'] 26 }, 27 { 28 // js 兼容性处理 29 test: /.js$/, 30 exclude: /node_modules/, 31 loader: 'babel-loader', 32 options: { 33 presets: [ 34 [ 35 '@babel/preset-env', 36 { 37 useBuiltIns: 'usage', 38 corejs: { 39 version: 3 40 }, 41 targets: { 42 chrome: '60', 43 firefox: '50' 44 } 45 } 46 ] 47 ] 48 } 49 }, 50 { 51 test: /.(jpg|png|gif)/, 52 loader: 'url-loader', 53 options: { 54 limit: 8 * 1024, 55 name: '[hash:10].[ext]', 56 outputPath: 'imgs', 57 esModule: false 58 } 59 }, 60 { 61 test: /.html$/, 62 loader: 'html-loader' 63 }, 64 { 65 exclude: /.(js|css|less|html|jpg|png|gif)/, 66 loader: 'file-loader', 67 options: { 68 outputPath: 'media' 69 } 70 } 71 ] 72 } 73 ] 74 }
6.2.1.2 babel 缓存
babel 缓存:类似 HMR,将 babel 处理后的资源缓存起来(哪里的 js 改变就更新哪里,其他 js 还是用之前缓存的资源),让第二次打包构建速度更快
代码:
1 { 2 test: /.js$/, 3 exclude: /node_modules/, 4 loader: 'babel-loader', 5 options: { 6 presets: [ 7 [ 8 '@babel/preset-env', 9 { 10 useBuiltIns: 'usage', 11 corejs: { version: 3 }, 12 targets: { 13 chrome: '60', 14 firefox: '50' 15 } 16 } 17 ] 18 ], 19 // 开启babel缓存 20 // 第二次构建时,会读取之前的缓存 21 cacheDirectory: true 22 } 23 },
文件资源缓存
开启bable缓存:
设置babel-loader的cacheDirectory为true
文件名不变,就不会重新请求,而是再次用之前缓存的资源
1.hash: 每次 wepack 打包时会生成一个唯一的 hash 值。
问题:重新打包,所有文件的 hsah 值都改变,会导致所有缓存失效。(可能只改动了一个文件)
2.chunkhash:根据 chunk 生成的 hash 值。来源于同一个 chunk的 hash 值一样
问题:js 和 css 来自同一个chunk,hash 值是一样的(因为 css-loader 会将 css 文件加载到 js 中,所以同属于一个chunk)
3.contenthash: 根据文件的内容生成 hash 值。不同文件 hash 值一定不一样(文件内容修改,文件名里的 hash 才会改变)
修改 css 文件内容,打包后的 css 文件名 hash 值就改变,而 js 文件没有改变 hash 值就不变,这样 css 和 js 缓存就会分开判断要不要重新请求资源 --> 让代码上线运行缓存更好使用
1 const {resolve } = require("path") 2 const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 3 4 /* 5 缓存: 6 babel缓存 7 cacheDirectory: true 8 ----》 让第二次打包构建速度更快 9 文件资源缓存 10 hash:每次webpack构建时会生成一个唯一的hash值。 11 问题:因为js和css同时使用一个hash值 12 如果重新打包 会导致所有缓存失效(可能我只改动了一个文件) 13 chunkhash: 根据chunk生成的hash值 如果打包来源同一个chunk 那么hash值就一样 14 问题: js和css的hash值还是一样的 15 因为css是在js中被引入的 所以同属于一个chunk 16 contenthash: 根据文件的内容生成hash值 不同文件hash值一定不一样 17 ----》 让代码上线运行缓存更好 18 */ 19 20 //定义nodejs环境变量 决定使用browserslist的那个环境 21 process.env.NODE_ENV = 'production' 22 23 module.exports = { 24 entry: './src/js/index.js', 25 output: { 26 filename: 'js/built.[contenthash:10].js', //文件缓存处理 27 path:resolve(__dirname,'build') 28 }, 29 module:{ 30 ...... 31 { 32 test: /.js$/, 33 exclude: /node_modules/, 34 loader: 'babel-loader', 35 options: { 36 presets: [ 37 [ 38 '@babel/preset-env', 39 { 40 useBuiltIns: 'usage', 41 corejs:{version: 3}, 42 targets: { 43 chrome: '60', 44 firefox: '50' 45 } 46 } 47 ] 48 ], 49 //开启bable缓存 50 //第二次构建是 会读取之前的缓存 51 cacheDirectory: true //设置 52 }, 53 }, 54 ...... 55 }, 56 plugins:[ 57 new MiniCssExtractPlugin({ 58 filename: 'css/built.[contenthash:10].css' //文件缓存处理 59 }), 60 ...... 61 ], 62 mode:'production', 63 }
6.2.1.3 多进程打包
多进程打包:某个任务消耗时间较长会卡顿,多进程可以同一时间干多件事,效率更高。
优点是提升打包速度,缺点是每个进程的开启和交流都会有开销(babel-loader消耗时间最久,所以使用thread-loader针对其进行优化)
1 { 2 test: /.js$/, 3 exclude: /node_modules/, 4 use: [ 5 /* 6 thread-loader会对其后面的loader(这里是babel-loader)开启多进程打包。 7 进程启动大概为600ms,进程通信也有开销。(启动的开销比较昂贵,不要滥用) 8 只有工作消耗时间比较长,才需要多进程打包 9 */ 10 { 11 loader: 'thread-loader', 12 options: { 13 workers: 2 // 进程2个 14 } 15 }, 16 { 17 loader: 'babel-loader', 18 options: { 19 presets: [ 20 [ 21 '@babel/preset-env', 22 { 23 useBuiltIns: 'usage', 24 corejs: { version: 3 }, 25 targets: { 26 chrome: '60', 27 firefox: '50' 28 } 29 } 30 ] 31 ], 32 // 开启babel缓存 33 // 第二次构建时,会读取之前的缓存 34 cacheDirectory: true 35 } 36 } 37 ] 38 },
6.2.1.4 externals
externals:让某些库不打包,通过 cdn 引入
webpack.config.js 中配置:
1 externals: { 2 // 拒绝jQuery被打包进来(通过cdn引入,速度会快一些) 3 // 忽略的库名 -- npm包名 4 jquery: 'jQuery' 5 }
需要在 index.html 中通过 cdn 引入:
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
6.2.1.5 dll
dll:让某些库单独打包,后直接引入到 build 中。可以在 code split 分割出 node_modules 后再用 dll 更细的分割,优化代码运行的性能。
1.下载插件
npm i add-asset-html-webpack-plugin -D
2.webpack.dll.js 配置:(将 jquery 单独打包) ----webpack.dll.js文件也可以是其他js文件名,只是运行的时候徐亚运行对应的配置文件(webpack --config xxx.js)
1 /* 2 node_modules的库会打包到一起,但是很多库的时候打包输出的js文件就太大了 3 使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包 4 当运行webpack时,默认查找webpack.config.js配置文件 5 需求:需要运行webpack.dll.js文件 6 --> webpack --config webpack.dll.js(运行这个指令表示以这个配置文件打包) 7 */ 8 const { resolve } = require('path'); 9 const webpack = require('webpack'); 10 11 module.exports = { 12 entry: { 13 // 最终打包生成的[name] --> jquery 14 // ['jquery] --> 要打包的库是jquery 15 jquery: ['jquery'] 16 }, 17 output: { 18 // 输出出口指定 19 filename: '[name].js', // name就是jquery 20 path: resolve(__dirname, 'dll'), // 打包到dll目录下 21 library: '[name]_[hash]', // 打包的库里面向外暴露出去的内容叫什么名字 22 }, 23 plugins: [ 24 // 打包生成一个manifest.json --> 提供jquery的映射关系(告诉webpack:jquery之后不需要再打包和暴露内容的名称) 25 new webpack.DllPlugin({ 26 name: '[name]_[hash]', // 映射库的暴露的内容名称 27 path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径 28 }) 29 ], 30 mode: 'production' 31 };
3.webpack.config.js 配置:
(告诉 webpack 不需要再打包 jquery,并将之前打包好的 jquery 跟其他打包好的资源一同输出到 build 目录下)
1 // 引入插件 2 const webpack = require('webpack'); 3 const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); 4 // plugins中配置: 5 plugins: [ 6 new HtmlWebpackPlugin({ 7 template: './src/index.html' 8 }), 9 // 告诉webpack哪些库不参与打包,同时使用时的名称也得变 10 new webpack.DllReferencePlugin({ 11 manifest: resolve(__dirname, 'dll/manifest.json') 12 }), 13 // 将某个文件打包输出到build目录下,并在html中自动引入该资源 14 new AddAssetHtmlWebpackPlugin({ 15 filepath: resolve(__dirname, 'dll/jquery.js') 16 }) 17 ],
6.2.2 优化代码运行的性能
6.2.2.1 缓存
6.2.2.2 tree shaking(树摇)
tree shaking:去除无用代码
前提:1. 必须使用 ES6 模块化 2. 开启 production 环境 (这样就自动会把无用代码去掉)
作用:减少代码体积
在 package.json 中配置:
"sideEffects": false
表示所有代码都没有副作用(都可以进行 tree shaking)
这样会导致的问题:可能会把 css / @babel/polyfill 文件干掉(副作用)
所以可以配置:"sideEffects": ["*.css", "*.less"]
不会对css/less文件tree shaking处理
6.2.2.3 code split(代码分割)
代码分割。将打包输出的一个大的 built.js 文件拆分成多个小文件,这样可以并行加载多个文件,比加载一个文件更快。
1.多入口拆分
1 entry: { 2 // 多入口:有一个入口,最终输出就有一个built 3 index: './src/js/index.js', 4 test: './src/js/test.js' 5 }, 6 output: { 7 // [name]:取文件名 (和entry中的属性对应) 8 filename: 'js/[name].[contenthash:10].js', 9 path: resolve(__dirname, 'build') 10 },
2.optimization:
1 optimization: { 2 splitChunks: { 3 chunks: 'all' 4 } 5 },
optimization的功能:
- 将 node_modules 中的代码单独打包(大小超过30kb)
- 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk(比如两个模块中都引入了jquery会被打包成单独的文件)(大小超过30kb)
注意:单入口只能做第一件事,多入口能做以上两件事
3.import 动态导入语法:
1 /* 2 通过js代码,让某个文件被单独打包成一个chunk 3 import动态导入语法:能将某个文件单独打包(test文件不会和index打包在同一个文件而是单独打包) 4 webpackChunkName:指定test单独打包后文件的名字 5 */ 6 import(/* webpackChunkName: 'test' */'./test') 7 .then(({ mul, count }) => { 8 // 文件加载成功~ 9 // eslint-disable-next-line 10 console.log(mul(2, 5)); 11 }) 12 .catch(() => { 13 // eslint-disable-next-line 14 console.log('文件加载失败~'); 15 });
6.2.2.4 lazy loading(懒加载/预加载)
1.懒加载:当文件需要使用时才加载(需要代码分割)。但是如果资源较大,加载时间就会较长,有延迟。--图片懒加载
2.正常加载:可以认为是并行加载(同一时间加载多个文件)没有先后顺序,先加载了不需要的资源就会浪费时间。
3.预加载 prefetch(兼容性很差):会在使用之前,提前加载。等其他资源加载完毕,浏览器空闲了,再偷偷加载这个资源。这样在使用时已经加载好了,速度很快。所以在懒加载的基础上加上预加载会更好。
代码:
1 document.getElementById('btn').onclick = function() { 2 // 将import的内容放在异步回调函数中使用,点击按钮,test.js才会被加载(不会重复加载) 3 // webpackPrefetch: true表示开启预加载 4 import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => { 5 console.log(mul(4, 5)); 6 }); 7 import('./test').then(({ mul }) => { 8 console.log(mul(2, 5)) 9 }) 10 };
6.2.2.5 pwa(离线可访问技术)
pwa:离线可访问技术(渐进式网络开发应用程序),使用 serviceworker 和 workbox 技术。优点是离线也能访问,缺点是兼容性差。
webpack.config.js 中配置:
1 const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); // 引入插件 2 // plugins中加入: 3 new WorkboxWebpackPlugin.GenerateSW({ 4 /* 5 1. 帮助serviceworker快速启动 6 2. 删除旧的 serviceworker 7 生成一个 serviceworker 配置文件 8 */ 9 clientsClaim: true, 10 skipWaiting: true 11 })
index.js 中还需要写一段代码来激活它的使用:
1 /* 2 1. eslint不认识 window、navigator全局变量 3 解决:需要修改package.json中eslintConfig配置 4 "env": { 5 "browser": true // 支持浏览器端全局变量 6 } 7 2. sw代码必须运行在服务器上 8 --> nodejs 9 或--> 10 npm i serve -g 11 serve -s build 启动服务器,将打包输出的build目录下所有资源作为静态资源暴露出去 12 */ 13 if ('serviceWorker' in navigator) { // 处理兼容性问题 14 window.addEventListener('load', () => { 15 navigator.serviceWorker 16 .register('/service-worker.js') // 注册serviceWorker 17 .then(() => { 18 console.log('sw注册成功了~'); 19 }) 20 .catch(() => { 21 console.log('sw注册失败了~'); 22 }); 23 }); 24 }
其他:
webpack官网:https://www.webpackjs.com/concepts/
其他优秀的webpack推荐:Webpack4不求人系列(共5篇)