1. 区分 开发 / 生产环境 webpack-merge
-
遵循不重复原则(DRY)
webpack 的相关配置需要保留一个 common配置、一个dev配置、一个prod配置
通过
webpack-merge
包将其整合 -
开发环境时 一些工具的使用是没有意义的,比如 压缩代码、文件名哈希、分离代码等...
-
项目中 安装、配置如下
npm i webpack-merge@4.1.5 -D 复制代码
// webpack/webpack.common.js const path = require('path'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { app: './src/index.js' }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Production' }) ], output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') } }; 复制代码
// webpack/webpack.dev.js const merge = require('webpack-merge'); const common = require('./webpack.common.js'); module.exports = merge(common, { devtool: 'inline-source-map', devServer: { contentBase: './dist' } }); 复制代码
// webpack/webpack.prod.js const merge = require('webpack-merge'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const common = require('./webpack.common.js'); module.exports = merge(common, { plugins: [ new UglifyJSPlugin() ] }); 复制代码
// package.json 配置 npm script { "scripts": { "start": "webpack-dev-server --open --config webpack/webpack.dev.js", "build": "webpack --config webpack/webpack.prod.js" } } 复制代码
2. 配置 source map
-
source map 反应资源的映射关系,用于 定位代码中的错误
-
开发环境下 建议
{ devtool: "cheap-module-eval-source-map" } 复制代码
-
生产环境下 建议
{ devtool: false } 复制代码
3. 摇树优化 Tree Shaking
-
作用:
- 净化 JS 中无用的代码
-
符合如下条件,自动开启 Tree Shaking:
-
webpack 4.X 生产模式下
-
编码时,遵循 ES6 模块化语法(
require
无效) -
编译时,不要编译 ES6模块
// .babelrc 配置如下 { "presets": [ [ "@babel/preset-env", { "modules": false } ] ] } 复制代码
-
-
生产模式下 ES6 模块化语法 实践:
-
实践一:
// a.js 文件 const dict = 'dict'; const dictMedia = 'dictMedia'; export default { dict, dictMedia }; 复制代码
// 引入 a.js 文件 import dicts from '/a'; console.log(dicts); // a.js 文件中的 dict、dictMedia 都被打包 复制代码
-
实践二:
// a.js 文件 const dict = 'dict'; const dictMedia = 'dictMedia'; export default { dict, dictMedia }; 复制代码
// 引入 a.js 文件 import dicts from '/a'; console.log(dicts.dict); // a.js 文件中的 dict 被打包;dictMedia 不被打包 复制代码
-
实践三:
// a.js 文件 const dict = 'dict'; const dictMedia = 'dictMedia'; export { dict, dictMedia }; 复制代码
// 引入 a.js 文件 import {dict, dictMedia} from '/a'; console.log(dict, dictMedia); // a.js 文件中的 dict 被打包;dictMedia 不被打包 复制代码
-
实践四:
// a.js 文件 const dict = 'dict'; const dictMedia = 'dictMedia'; export { dict, dictMedia }; 复制代码
// 引入 a.js 文件 import {dict} from '/a'; console.log(dict); // a.js 文件中的 dict 被打包;dictMedia 不被打包 复制代码
-
4. 作用域提升 Scope Hoisting
-
概述
-
Scope Hoisting 可以让webpack打包出来的代码文件更小、运行更快
-
webpack4 的生产模式,默认开启 Scope Hoisting
-
webpack3 推出的功能,需要手动开启 粗略讲解 在此
-
-
原理:
-
分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去
-
前提是不能造成代码冗余
-
-
是否开启 Scope Hoisting 的对比
// util.js 文件 export default 'Hello,Webpack'; 复制代码
// main.js 入口文件 import str from './util.js'; console.log(str); 复制代码
// 未开启 Scope Hoisting 打包后如下 [ (function (module, __webpack_exports__, __webpack_require__) { var __WEBPACK_IMPORTED_MODULE_0__util_js__ = __webpack_require__(1); console.log(__WEBPACK_IMPORTED_MODULE_0__util_js__["a"]); }), (function (module, __webpack_exports__, __webpack_require__) { __webpack_exports__["a"] = ('Hello,Webpack'); }) ] 复制代码
// 开启 Scope Hoisting 打包后如下 [ (function (module, __webpack_exports__, __webpack_require__) { var util = ('Hello,Webpack'); console.log(util); }) ] 复制代码
5. 启动开发服务 热替换 HMR
-
全称:
Hot Module Replacement
-
应用场景:开发环境下
-
作用:热替换 HMR,在启动开发服务时,局部加载 页面被修改之处;加快开发编译速度
保留在完全重新加载页面时丢失的应用程序状态
只更新变更内容,以节省宝贵的开发时间
调整样式更加快速:几乎相当于在浏览器调试器中更改样式
-
限制:HMR 是可选功能(只会影响包含 HMR 代码的模块)
举个例子,通过 style-loader 为 style 样式追加补丁。为了运行追加补丁,style-loader 实现了 HMR 接口;当它通过 HMR 接收到更新,它会使用新的样式替换旧的样式
如果一个模块没有 HMR 处理函数,更新就会冒泡(bubble up)。这意味着一个简单的处理函数能够对整个模块树(complete module tree)进行更新
-
配置 HMR
// webpack 配置文件中 const webpack = require('webpack'); module.exports = { devServer: { contentBase: path.resolve(__dirname,'dist'), compress: true, host: 'localhost', port:3000, hot: true // 开启 热替换 }, plugins: [ new webpack.NamedModulesPlugin(), // 必要的配置 new webpack.HotModuleReplacementPlugin() // 必要的配置 ] }; 复制代码
6. 项目开发中 善用 按需加载
-
方式一:使用 ES6 的模块化语法,动态加载模块
import()
import('') 语法目前只是 ECMAScript 提案阶段,还没被正式发布
// .babelrc 中 // babel 7 { "plugins": [ "@babel/plugin-syntax-dynamic-import" ] } 复制代码
-
方式二:使用 webpack 对代码进行分割,按需加载(webpack 的
require.ensure
语法)-
require.ensure
是 webpack 语法:-
参数1:要依赖的模块;类型为 字符串数组;一般为空
-
参数2:加载依赖后,自动执行的回调函数
-
参数3:打包后,js 文件的输出路径、js 文件名(chunk名称)
const router = new VueRouter({ routes: [ // 定义路由信息对象 { path: string, name?: string, component: (resolve) => { require.ensure([], () => { resolve(require('../../view/demo/index.vue')) }, 'demo') } } ] }); 复制代码
-
-
7. 配置 loader 生效范围
-
作用:配置 loader 的生效范围,可提高编译速度
-
以配置
babel-loader
为例// 不推荐 // webpack配置文件中 配置 const path = require('path'); module: { rules: [{ test: /\.js$/, use: ['babel-loader'] }] } 复制代码
// 推荐 // webpack配置文件中 配置 const path = require('path'); module: { rules: [{ test: /\.js$/, use: ['babel-loader'], exclude: '', // 排除不要加载的文件夹 include: [path.resolve(__dirname, 'src'), /node_modules/] // 指定需要加载的文件夹 }] } 复制代码
-
exclude
、include
的值:-
值可以是单独项、可以是数组
-
可以是路径、可以是正则
-
-
项目实战:
项目开发中,针对主要 lodaer 如
babel-loader
、style-loader
、sass-lodaer
等配置生效范围: 除了要转换项目代码,还要转换
node_modules
中代码;否则 针对没有完全转成JS的node包,会报错
8. 配置 如何解析模块 resolve
-
给模块起别名:
resolve.alias
// webpack 配置 module.exports = { //... resolve: { alias: { '@components': '/src/common' } } }; 复制代码
// 项目代码 import a from '@components/utils'; // 等同于 import a from '/src/common/utils'; 复制代码
-
自动添加后缀 规则:
resolve.extensions
当引入模块时不带文件后缀,webpack 会根据配置依次添加后缀 寻找文件
// webpack 配置 module.exports = { //... resolve: { extensions: ['.vue', '.js', '.scss', '.css', '.json'] } }; 复制代码
-
解析目录时要使用的文件名:
resolve.mainFiles
默认寻找
index
命名的文件// webpack 配置 module.exports = { //... resolve: { mainFiles: ["index"] } }; 复制代码
-
指明第三方模块存放的位置,以减少搜索步骤
-
默认值:
[node modules]
-
默认搜索步骤:先去当前目录的
/node modules
目录下去找我们想找的模块,如果没找到,就去上一级目录../node modules
中找,再没有就去../../node modules
中找
// webpack 配置 module.exports = { //... resolve: { modules: [path.resolve(__dirname, 'node_modules')] } }; 复制代码
-
-
引入的模块,寻找规则:
不建议写文件后缀名,就意味着引入的模块路径,路径最后一层有可能是文件;有可能是文件夹
默认将路径的最后一层 视为文件名,依次匹配 配置的后缀名
如果没找到该文件,将路径的最后一层视为文件夹,依次匹配 设置的文件名,找到后再 依次匹配设置的后缀名
如都没找到,就会报错
9. 配置 noParse 让webpack编译时 忽略一些文件
-
场景:
-
一些没有采用模块化的文件,没有必要让 webpack 进行处理编译
-
通过配置
modules.noParse
,可以让 webpack 忽略,提升编译速度
-
-
配置:
module.exports = merge(common, { modules: { // 使用正则表达式 noParse: /jquery|chartjs/ // 或 // 使用函数,从 Webpack 3.0.0 开始支持 noParse: (content)=> { // content 代表一个模块的文件路径 // 返回 true or false return /jquery|chartjs/.test(content); } } }); 复制代码
10. 配置 performance 在生产环境 移除性能警告
-
webpack 打包后,如果文件体积超出默认(250kb)大小,会输出警告
-
开发环境下这是一个不错的提示,但生产环境下完全没必要,可以通过
performance
进行相关配置 -
配置如下
// webpack 配置 module.exports = merge(common, { performance: { hints: false, // 关闭警告 maxEntrypointSize: 400000 // 预警值设置成40kb } }); 复制代码
11. 配置 项目环境变量 webpack.DefinePlugin
-
场景:项目开发过程中,配置 每个文件中都可以使用的 JS 变量
-
直接获取 开发环境变量
process.env.NODE_ENV
使用webpack4.x,webpack 会将环境变量
process.env.NODE_ENV
的值,设置为 webpack配置中 mode 的值在项目代码中 可以直接使用
process.env.NODE_ENV
// 项目代码中 console.log(process.env.NODE_ENV); // development 复制代码
-
设置 / 获取 新的环境变量
使用 webpack 的内置插件 DefinePlugin,可以为项目代码定义环境变量
限制:DefinePlugin 设置的环境变量只能在项目代码中获取,不能再 webpack 的配置文件中获取
// webpack 配置如下 const webpack = require('webpack'); module.exports = merge(common, { plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('ddd'), // 覆盖 process.env.NODE_ENV 'process.env.globalName': JSON.stringify('globalName'), 'globalName': JSON.stringify('eeee'), }) ] }); 复制代码
// 项目代码中 console.log(process.env.NODE_ENV); // ddd console.log(process.env.globalName); // globalName console.log(globalName); // eeee 复制代码
12. npm script 指令传参(三种方式)
-
场景:配置webpack时,可能需要通过 npm script 传参,用来处理不同场景下的不同需求
-
方式一:不同系统存在 兼容问题
-
在脚本命令的配置(package.json 的
script
下) 中传参(window)// window 系统下:传参 "scripts": { "server": "webpack-dev-server --open", "build:dev":"set type=dev&webapck", "build:prod": "set type=prod&webpack" }, 复制代码
-
在脚本命令的配置(package.json 的
script
下) 中传参(mac)// mac 系统下:传参 "scripts": { "server": "webpack-dev-server --open", "build:dev":"export type=dev&&webpack", "build:prod": "export type=prod&&webpack" }, 复制代码
-
接收参数
// node的语法来读取type的值,然后根据type的值用if–else判断 if(process.env.type== "build"){ // 生产环境 var website={ publicPath:"http://192.168.0.104:1717/" } }else{ // 开发环境 var website={ publicPath:"http://cdn.jspang.com/" } } 复制代码
-
-
方式二:要求 webpack 配置项输入函数(无系统兼容问题)
-
在脚本命令的配置(package.json 的
script
下) 中传参// package.json 如下 { "scripts": { "build": "webpack --env.NODE_ENV=local --env.production --progress" }, } 复制代码
-
webpack 配置文件中 获取环境变量
必须对 webpack 配置进行一处修改。通常,module.exports 指向配置对象;要使用 env 变量,你必须将 module.exports 转换成一个函数
module.exports = env => { console.log('NODE_ENV: ', env.NODE_ENV) // 'local' console.log('Production: ', env.production) // true return { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') } } }; 复制代码
-
执行命令
npm run build
,即可在控制台中 看到输出的 环境变量的值这样就可以在 webpack 中通过区分不同的环境变量,来配置不同的webpack
-
-
方式三:无系统兼容问题,无过多要求
-
在脚本命令的配置(package.json 的
script
下) 中传参// package.json 如下 { "scripts": { "build": "webpack --prod" }, } 复制代码
-
webpack 配置文件中 获取参数如下
// webpack 配置文件中 console.log(process.argv); // 输出 [ node 路径, webpack 路径, '--prod'] 复制代码
-
如果只是想传递一个布尔值,获取参数如下
// 安装 minimist npm i minimist@1.2.0 -D 复制代码
// webpack 配置文件中 const processArgv = require('minimist')(process.argv.slice(2)); console.log(processArgv.prod); // true 复制代码
-
附言
-
小伙伴们,有什么问题 可以留言,一起交流哈
-
接下来,我还会发布几篇 webpack4.X 实战文章,敬请关注
-
我是一名热衷于编程的前端开发,WX:ZXvictory66