背景

有时候我们需要在每个模块都使用一个模块,但是又不想在每个模块都引入一遍,这时就可以把模块暴露的全局。

例子

我们以 jquery 为例,将它暴露到每个模块。
先安装 jquery: yarn add jquery
不暴露时使用方法,我们必须在每个文件中通过 import 引入才能使用。如果没有 import,毫无意外会报错。

  1. /** ./src/index.js **/
  2. import $ from 'jquery';
  3. console.log($);

所以我们需要暴露到全局。

eslint 配置

在暴露到全局时,我们需要先配置一下.eslintrc.js,因为eslint并不知道我们配置了全局变量,会导致变量为定义的错误,如下:
image.png
解决方法如下,在 globals 中添加 jquery 的’$’变量为 true,后续使用’$’时就不会报 eslint 错误了。

  1. /** ./eslintrc.js **/
  2. module.exports = {
  3. env: {
  4. browser: true,
  5. es2021: true,
  6. },
  7. extends: [
  8. 'airbnb-base',
  9. ],
  10. parserOptions: {
  11. ecmaVersion: 'latest',
  12. sourceType: 'module',
  13. },
  14. rules: {
  15. "eol-last": "off",
  16. "no-console": "off"
  17. },
  18. globals:{
  19. '$':true
  20. }
  21. };

暴露的方法

暴露到全局的方法有三种。注意这三种方式不能混合使用。

使用 cdn

通过 cdn 可以直接在 window 中添加一个 $。
还记得我们最初配置打包参数时引入的 public 下 html 模版吗?我们打开那个模版,如下:

  1. <!-- ./public/index.html -->
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  7. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  8. <title>Document</title>
  9. </head>
  10. <body>
  11. <div id="app"></div>
  12. <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
  13. </body>
  14. </html>

在11 行添加通过 script 标签引入 cdn 上的 js 文件。
接下来,我们在 index.js 文件中试一下,不用 import ,直接打印

  1. /** ./src/index.js **/
  2. console.log($);
  3. console.log(window.$);

运行看结果,如下:
image.png
可以正常打印,说明暴露成功。

使用 ProvidePlugin

ProvidePlugin 插件被集成在 webpack 包中,通过这个插件可以在每个模块中注入 jquery。
但是注意的是,这边是在每个模块中注入,而不是真正意义上的暴露全局变量,并不会把模块挂到 window 上。
这种方式需要配置 webpack,如下:

  1. /** ./webpack.config.js **/
  2. let webpack = require('webpack');
  3. module.exports = {
  4. plugins:[
  5. //...
  6. new webpack.ProvidePlugin({
  7. $:'jQuery'
  8. })
  9. ]
  10. }

例子还是刚才的例子

  1. /** ./src/index.js **/
  2. console.log($);
  3. console.log(window.$);

打包后看结果
image.png
我们发现 “$” 还是能获取到的,但是 “window.$” 是获取不到的,说明他是单独注入每个模块,而不是把它声明到全局。

使用 expose-loader

最后一张是通过 expose-loader 的方式,在模块被第一次引入时暴露到全局。
expose-loader 有两种使用方式,详情见 expose,我这边简单介绍下。

  • 内联
  • 使用配置文件

    内联

    使用刚才的例子 ```javascript / ./src/index.js / / eslint-disable-next-line import/no-webpack-loader-syntax, import/no-unresolved / require(‘expose-loader?exposes=$!jquery’);

console.log($); console.log(window.$);

  1. 如例子所示 我们通过 require 引入,并且使用了特殊的语法<br />?exposes= 是后面加要声明的全局变量名<br />! 是后面加包名<br />而且因为我们配置了eslint,而eslint是不识别这种语法的,所以我们要添加:<br />/* eslint-disable-next-line import/no-webpack-loader-syntax, import/no-unresolved */<br />来忽略 eslint 检测。<br />接下来我们看打包运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1584826/1656045473827-8caa1776-1e3f-4005-84c1-a4d678b53462.png#clientId=u4d6efbca-2f56-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=301&id=u3b56d576&margin=%5Bobject%20Object%5D&name=image.png&originHeight=602&originWidth=2344&originalType=binary&ratio=1&rotation=0&showTitle=false&size=336173&status=done&style=none&taskId=ua4ca3f90-270a-4937-adac-ccbfffe4bac&title=&width=1172)<br />"$" 和 "window.$" 都能被获取所以暴露成功。
  2. <a name="PXJsk"></a>
  3. #### 使用配置文件
  4. 除了使用内联的方式,还可以使用配置文件的方式,效果一摸一样,我这边展示下配置方式。<br />在文件中引入
  5. ```javascript
  6. require('jquery');
  7. console.log($);
  8. console.log(window.$);

在 webpack 配置文件中修改

  1. /** ./webpack.config.js **/
  2. //...
  3. module.exports = {
  4. module: {
  5. rules: [
  6. {
  7. test:require.resolve('jquery'),
  8. use:[
  9. {
  10. loader: 'expose-loader',
  11. options: {
  12. exposes:['$']
  13. }
  14. }
  15. ]
  16. }
  17. ]
  18. }
  19. }
  20. //...

打包结果
image.png
和之前一样,可以在全局访问”$”,并且被挂载到了 window 上。

剥离第三方库(externals)

有时候我们需要既可以在全局使用变量,又可以在本地模块引入,而且不会重复打包,这就需要用到 webpack 的 externals 属性。
首先我们来构造 暴露到全局的属性和本地引入的包。
这个需要使用到之前提过的 cdn 和 安装 jquery 包。我们就会有疑惑这不就重复了,没错就是重复了,所以这个方法我感觉很捞。不管,我们先实现一下:
现在 html 模版中,引入 cdn 文件,确保能全局调用。

  1. <!-- ./public/index.html -->
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  7. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  8. <title>Document</title>
  9. </head>
  10. <body>
  11. <div id="app"></div>
  12. <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
  13. </body>
  14. </html>

然后安装 jquery npm 包

yarn add jquery

然后在 index 文件中实战使用一下

import $ from 'jquery';

console.log($);
console.log(window.$);

为了方便观察,我这边cdn引入的版本和npm安装的版本,jquery是不同的,
然后我们打包运行一下看结果:
image.png
很明显他把 jquery 也打包进去了,但是我们是通过 cdn 的,不需要把本地的包打进去。
再看页面:
image.png
我们发现 import 引入的 “$” 和 “window.$” 并不是一个东西,所以这很容易让人搞错。而且很难排查包版本的问题。
所以有两个问题:

  • 重复打包
  • 调用错乱

解决方法如下,我们在 webpack 中忽略 npm 中的包。

/** ./webpack.config.js **/
//...
module.exports = {
  externals: {
    'jquery':'$' // 外部的变量 不需要打包
  }
}
//...

再执行打包
image.png
这时候就发现没有多余的打包了
再看页面
image.png
全局变量都被暴露出来,并且都是从 cdn 引入的。
总结一下,这个功能主要作用是将第三方库剥离出webpack的打包,提高打包效率。