webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)
本文 webpack 版本 ^5.73.0、webpack-cli 版本 ^4.10.0
简介
前面我们说了webpack
处理css、js、ts、图片文本等,今天我们来说说webpack
提升开发效率的几个常用配置。
ProvidePlugin
ProvidePlugin
是 webpack
的内置插件,作用就是不需要 import
或 require
就可以在项目中到处使用配置好的变量。简单理解就是自动导入功能。
什么意思呢?来看个例子你就明白了
实例
假如我们的项目需要使用到jquery
。
首先我们安装 jquery
npm i jquery
然后在需要使用的地方导入使用
import $ from "jquery";
// 模拟使用jquery
console.log($);
我们编译看下效果,页面正常输出 jquery
函数。
如果项目用到jquery
的地方不多的话,这种使用方式没什么问题,但是,如果项目里面很多页面都需要用到jquery
,那我们每个页面都需要import $ from "jquery";
,这无疑是一个重复劳动。
那可以不引入就能使用吗?我们来试试。
下面在我们js
文件里面不引入jquery
,然后直接输出$
。
console.log($)
编译,看下结果,不出所料,报错了,提示找不到$
那有什么办法能不引入就能直接使用呢?嘿,有,那就是使用 ProvidePlugin
插件。
接下来在webpack.config.js
里面配置我们 ProvidePlugin
插件。
plugins: [
// ...
// 自动引入 jquery
new webpack.ProvidePlugin({
$: "jquery",
}),
]
再来编译看下结果,成功,$
正确输出。
注意 ProvidePlugin
插件默认寻找路径是当前文件夹 ./**
和 node_modules
,当然啦,你可以指定全路径。
同理你还可以应用在其他库上,比如 React
,我们再使用的时候,需要在每个文件中引入 React
,如果你不想在每个文件引入,你就可以在 ProvidePlugin
里面配置。
plugins: [
// ...
// 自动引入 react
new webpack.ProvidePlugin({
React: "react",
}),
],
这样你就能直接使用 React
了。
注意,笔者项目里没有配置eslint
,如果你项目有使用eslint
的话这些全局变量会报错。因为配置的全局变量在eslint
中并不知道。所以我们还需要在eslint
的配置文件对globals
项进行配置。
// .eslintrc.js
module.exports = {
// ...
globals: { "React": true, "$": true, //.... }
}
如果对eslint
还不熟悉的朋友,可以看看笔者之前写的 Eslint通关指南
当然啦,这个配置虽然可以提升我们的开发效率,但是笔者不建议多使用。毕竟全局变量多也不是什么好事。
DefinePlugin
DefinePlugin
是 webpack
的内置插件。用来定义全局变量。
传递给 DefinePlugin
的每个键都是一个标识符或多个以 .
连接的标识符。并且有以下特点
- 如果该值为字符串,它将被作为代码片段来使用。
- 如果该值不是字符串,则将被转换成字符串(包括函数方法)。
- 如果值是一个对象,则它所有的键将使用相同方法定义。
- 如果键添加
typeof
作为前缀,它会被定义为 typeof 调用。
实例1
下面笔者举一个简单的例子,假设我们系统配置了如下全局变量
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("哈哈"),
"process.env.TEST": { name: JSON.stringify("test") },
}),
在我们的代码中打印看下结果
输出的结果是否和你想的一致呢?
和上面类似,如果你项目有使用eslint
的话这些全局变量会报错。因为配置的全局变量在eslint
中并不知道。所以我们还需要在eslint
的配置文件对globals
项进行配置。
// .eslintrc.js
module.exports = {
// ...
globals: { "PRODUCTION": true, "VERSION": true, //.... }
}
如果对eslint
还不熟悉的朋友,可以看看笔者之前写的 Eslint通关指南
通常我们使用 DefinePlugin
来定义环境变量。而不是写死的全局变量。
那什么是环境变量呢?简单来说就是同一个变量在不同的环境下有不同的值的变量就是环境变量。
下面笔者再举个实际开发中用到的例子
实例2
一般我们项目开发至少会设置三个环境。开发、测试、生产环境
下面三个命令分别传递不同的自定义变量MODE
,构建各自环境的代码。dev
构建开发环境,staging
构建测试环境,build
构建生产环境。
{
"scripts": {
"dev": "cross-env MODE=development webpack",
"staging": "cross-env MODE=staging webpack",
"build": "cross-env MODE=production webpack",
}
}
三个环境传递三个不同的MODE
值,我们可以在webpack.config.js
中通过process.env.MODE
获取到。
// webpack.config.js
console.log("process.env.MODE=", process.env.MODE);
const config = {
// ...webpack相关配置
}
module.exports = (env, argv) => {}
我们来编译试试看
npm run dev
npm run staging
npm run build
可以看到,我们通过不同的命令就能获取到不同的MODE
值啦。有了不同的MODE
值我们就可以进行不同环境的配置啦。
比如,我们需要不同的环境定义不同的mode
,开发环境设置mode
为development
,测试和生产环境我们设置mode
为production
,进行不同的优化。或者根据不同的MODE
值来加载不同的配置文件,也是可以的。
// webpack.config.js
console.log("process.env.MODE=", process.env.MODE);
const config = {
// ...webpack相关配置
}
module.exports = (env, argv) => {
if (process.env.MODE === "development") {
config.mode = "development";
} else {
config.mode = "production";
}
return config;
};
前面说了,mode
定义后,会同步设置process.env.NODE_ENV
的值,并且可以在我们的开发代码里面获取到。
如果mode
为development
则process.env.NODE_ENV
的值为 development
,如果mode
为production
则process.env.NODE_ENV
的值为 production
。
我们来测试下,定义入口文件index.js
,在里面输出process.env.NODE_ENV
的值。
// index.js
console.log(process.env.NODE_ENV);
我们分别构建,然后运行构建后的js
。
npm run dev
npm run staging
npm run build
可以看到,开发环境process.env.NODE_ENV
的值为development
,测试和生产环境的值是production
。
这个没问题,那么我们上面设置的process.env.MODE
的值也能在开发代码里面获取到吗?
我们再来试试
// index.js
console.log(process.env.NODE_ENV);
console.log(process.env.MODE);
再次构建,发现居然是undefined
这就有问题啦,如果我们代码中需要根据不同环境做不同处理那应该怎么办?其实我们可以用process.env.NODE_ENV
的思路,它就是根据mode
的不同使用DefinePlugin
插件来动态设置值的,所以我们也使用这个插件处理下。
const webpack = require("webpack");
// ...
module.exports = (env, argv) => {
if (process.env.MODE === "development") {
config.mode = "development";
} else {
config.mode = "production";
}
// 根据参数,重新定义三种模式
config.plugins.push(
new webpack.DefinePlugin({
"process.env.MODE": JSON.stringify(process.env.MODE),
})
);
return config;
};
我们分别构建,然后运行构建后的js
。
npm run dev
npm run staging
npm run build
可以看到,process.env.NODE_ENV
和process.env.MODE
的值都能在我们的代码中正确获取到了,这样我们不仅能根据不同环境配置我们的webpack.config.js
,还能在我们的开发代码中根据不同的环境进行不同的处理。
这就是 DefinePlugin
插件的魅力。
resolve 配置
resolve
用来配置 webpack
如何寻找模块所对应的文件。下面笔者说说在resolve
下面常用的几个配置。
modules
resolve.modules
配置 webpack
去哪些目录下寻找第三方模块,默认情况下,只会去 node_modules
下寻找,如果你我们项目中某个文件夹下的模块经常被导入,不希望写很长的路径,那么就可以通过配置 resolve.modules
来简化。
//webpack.config.js
module.exports = {
//....
resolve: {
modules: ['./src/components', 'node_modules'] //从左到右依次查找
}
}
这样配置之后,我们可以直接 import CustomComponent from 'CustomComponent'
导入我们自己写的组件。它首先会在 ./src/components
下查找,找不到的话,再会到 node_modules
下寻找。
alias
resolve.alias
配置项通过别名把原导入路径映射成一个新的导入路径。
假如在当前目录下,有个component
文件夹,里面是我们的组件。
按以往的做法,引入文件是这样的
import { name } from "./component/test";
console.log(name);
如果配置好别名后
//webpack.config.js
module.exports = {
//....
resolve: {
alias: {
"@": path.resolve(__dirname, "jssrc/component"),
}
}
}
我们只需要这样引入
import { name } from "@/test";
console.log(name);
这里的@
其实就是上面我们配置的path.resolve(__dirname, "jssrc/component")
绝对路径。
这个例子我们只是简单的演示,实际开发中这么简单的路径不会这么配置,通常层级很深、使用频率很高的路径我们才会配置别名。
比如在vue
中,我们就能直接使用@
,其实在vue-cli
里面就是配置了"@": path.resolve(__dirname, "src")
,代表src
目录。
extensions
extensions
用来配置我们的文件后缀名。
比如我们上面的例子,导入test.js
的时候并没有写后缀.js
为什么能导入成功呢?
import { name } from "./component/test";
console.log(name);
这里其实就是我们的extensions
配置起到了作用。extensions
的默认值是['.js', '.json', '.wasm']
,所以不写文件后缀的话,它会依次匹配'.js', '.json', '.wasm'
,如果都没匹配上的话才会报错。
假如我们现在有个.txt
文件想不写后缀就能导入成功我们可以这样配置
module.exports = {
// '.txt'
resolve: {
extensions: ['.txt'],
},
};
以上这样使用 resolve.extensions
会 覆盖默认数组,这就意味着 webpack
将不再尝试使用默认扩展来解析模块。然而你可以使用 '...'
访问默认拓展名:
module.exports = {
// '.js', '.json', '.wasm', '.txt'
resolve: {
extensions: ['...', '.txt'],
},
};
extensions
的配置要将高频的后缀放在前面,并且数组不要太长,减少尝试次数。
enforceExtension
如果配置了 resolve.enforceExtension
为 true
,那么导入语句不能缺省文件后缀。
mainFields
当从 npm 包中导入模块时(例如,import * as D3 from 'd3'
),此选项将决定在 package.json
中使用哪个字段导入模块。
根据 webpack
配置中指定的 target
不同,默认值也会有所不同。
当 target
属性设置为 webworker
、web
或者没有指定,它的默认值是
webpack.config.js
module.exports = {
//...
resolve: {
mainFields: ['browser', 'module', 'main'],
},
};
对于其他任意的 target(包括 node
),默认值为:
webpack.config.js
module.exports = {
//...
resolve: {
mainFields: ['module', 'main'],
},
};
什么意思呢?
假如,我们的项目引入了一个名为 lodash
的类库,它的 package.json
包含以下字段:
{
"browser": "build/index.js",
"module": "index.js",
"main": "dist/index.js"
}
在我们 import _ from 'lodash'
时,它会从 browser
属性解析文件。因为我们默认的target
是web
。如果我们的target
设置成node
,那么它会从 module
字段中解析文件。
externals
externals
配置用来排除依赖,什么意思呢?就是配置好后,webpack
会排除这些模块的构建。
实例
比如上面我们安装并引入了jquery
,正常情况下 jquery
是会被打包到我们的目标 js
里面的。
打包完后,目标文件有282kb
。
如果不想打包进去我们就可以配置externals
,将它排除。
module.exports = {
externals: {
jquery: "$",
},
};
我们配置好,再来打包测试下,可以发现,它只有3.71kb
了,因为jquery
并没有构建进来
并且页面也报错了
那有些小伙伴就会有疑问了,既然排除构建会报错,那么这个配置又有什么用呢?
那就是我们希望某个包不构建到我们的目标js
里面,而是使用cdn
的模式导入使用,并且还能通过 import
的方式去引用(如 import $ from 'jquery'
)此时就可以配置 externals
。
比如上面的例子,我们的jquery
不再使用npm
安装,而是直接在index.html
使用cdn
引入。
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
然后代码里面的使用方式还是使用import
的方式导入
import $ from "jquery";
console.log($);
这样的优势就是能大大提升我们 webpack
的构建速度,并且构建出来产物的体积也会减小很多,页面性能会提升很多。
总结
ProvidePlugin
和 DefinePlugin
是 webpack
的内置插件,我们无需安装就能直接使用。ProvidePlugin
能帮助我们完成模块的自动导入功能。DefinePlugin
能帮我们定义全局变量(环境变量)。
resolve
和 externals
是 webpack
的配置项,使用的好的话能大大提升我们的开发效率和系统性能。
系列文章
webpack入门之css处理(css预处理器和css后置处理器)
webpack入门之js处理(babel、babel polyfill)
webpack入门之ts处理(ts-loadr和babel-loader的选择)
webpack入门之开发环境(mode、dev-server、devtool)
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!
转载自:https://juejin.cn/post/7241424021128364087