likes
comments
collection
share

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

作者站长头像
站长
· 阅读数 69

本文 webpack 版本 ^5.73.0、webpack-cli 版本 ^4.10.0

简介

前面我们说了webpack处理css、js、ts、图片文本等,今天我们来说说webpack提升开发效率的几个常用配置。

ProvidePlugin

ProvidePluginwebpack 的内置插件,作用就是不需要 importrequire 就可以在项目中到处使用配置好的变量。简单理解就是自动导入功能。

什么意思呢?来看个例子你就明白了

实例

假如我们的项目需要使用到jquery

首先我们安装 jquery

npm i jquery

然后在需要使用的地方导入使用

import $ from "jquery";

// 模拟使用jquery
console.log($);

我们编译看下效果,页面正常输出 jquery 函数。

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

如果项目用到jquery的地方不多的话,这种使用方式没什么问题,但是,如果项目里面很多页面都需要用到jquery,那我们每个页面都需要import $ from "jquery";,这无疑是一个重复劳动。

那可以不引入就能使用吗?我们来试试。

下面在我们js文件里面不引入jquery,然后直接输出$

console.log($)

编译,看下结果,不出所料,报错了,提示找不到$

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

那有什么办法能不引入就能直接使用呢?嘿,有,那就是使用 ProvidePlugin 插件。

接下来在webpack.config.js里面配置我们 ProvidePlugin 插件。

plugins: [
  // ...
  // 自动引入 jquery
  new webpack.ProvidePlugin({
    $: "jquery",
  }),
]

再来编译看下结果,成功,$正确输出。

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

注意 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") },
}),

在我们的代码中打印看下结果

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

输出的结果是否和你想的一致呢?

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

和上面类似,如果你项目有使用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

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

npm run staging

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

npm run build

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

可以看到,我们通过不同的命令就能获取到不同的MODE值啦。有了不同的MODE值我们就可以进行不同环境的配置啦。

比如,我们需要不同的环境定义不同的mode,开发环境设置modedevelopment,测试和生产环境我们设置modeproduction,进行不同的优化。或者根据不同的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的值,并且可以在我们的开发代码里面获取到。

如果modedevelopmentprocess.env.NODE_ENV 的值为 development,如果modeproductionprocess.env.NODE_ENV 的值为 production

我们来测试下,定义入口文件index.js,在里面输出process.env.NODE_ENV的值。

// index.js
console.log(process.env.NODE_ENV);

我们分别构建,然后运行构建后的js

npm run dev

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

npm run staging

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

npm run build

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

可以看到,开发环境process.env.NODE_ENV的值为development,测试和生产环境的值是production

这个没问题,那么我们上面设置的process.env.MODE的值也能在开发代码里面获取到吗?

我们再来试试

// index.js
console.log(process.env.NODE_ENV);
console.log(process.env.MODE);

再次构建,发现居然是undefined

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

这就有问题啦,如果我们代码中需要根据不同环境做不同处理那应该怎么办?其实我们可以用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

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

npm run staging

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

npm run build

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

可以看到,process.env.NODE_ENVprocess.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 属性设置为 webworkerweb 或者没有指定,它的默认值是

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 属性解析文件。因为我们默认的targetweb。如果我们的target设置成node,那么它会从 module 字段中解析文件。

externals

externals 配置用来排除依赖,什么意思呢?就是配置好后,webpack 会排除这些模块的构建。

实例

比如上面我们安装并引入了jquery,正常情况下 jquery 是会被打包到我们的目标 js 里面的。

打包完后,目标文件有282kb

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

如果不想打包进去我们就可以配置externals,将它排除。

module.exports = {
  externals: {
    jquery: "$",
  },
};

我们配置好,再来打包测试下,可以发现,它只有3.71kb了,因为jquery并没有构建进来

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

并且页面也报错了

webpack入门之提升开发效率的几个配置(ProvidePlugin、DefinePlugin、resolve、externals)

那有些小伙伴就会有疑问了,既然排除构建会报错,那么这个配置又有什么用呢?

那就是我们希望某个包不构建到我们的目标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 的构建速度,并且构建出来产物的体积也会减小很多,页面性能会提升很多。

总结

ProvidePluginDefinePlugin 是 webpack 的内置插件,我们无需安装就能直接使用。ProvidePlugin能帮助我们完成模块的自动导入功能。DefinePlugin能帮我们定义全局变量(环境变量)。

resolveexternalswebpack 的配置项,使用的好的话能大大提升我们的开发效率和系统性能。

系列文章

webpack入门之css处理(css预处理器和css后置处理器)

webpack入门之图片、字体、文本、数据文件处理

webpack入门之js处理(babel、babel polyfill)

webpack入门之ts处理(ts-loadr和babel-loader的选择)

webpack入门之开发环境(mode、dev-server、devtool)

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!

转载自:https://juejin.cn/post/7241424021128364087
评论
请登录