externals

externals 表示外部扩展的意思,使用这个属性可以防止将指定的依赖打包到 bundle.js 中,而是在运行时(runtime)从用户环境(例如从 cdn 或 node_modules)获取这些扩展依赖。

使用这个的目的是减少我们的代码包体积,从而可以提高首屏渲染速度。

cnd使用介绍:
当我们使用一个cnd资源时,只需要使用 script 标签加载对应的cnd路径就可以。
这样,就会在全局下(window)挂载一个属性或方法,使用时就会从全局对象下查找。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. <!-- 使用cnd的方式加载lodash三方库 -->
  9. <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
  10. </head>
  11. <body>
  12. <script>
  13. console.log(window);
  14. console.log(window._.isEmpty({}))
  15. </script>
  16. </body>
  17. </html>

可以看到,lodash 已经成为了 window 的一个属性,我们已经可以使用window._进行访问 lodash 的方法了。
image.png
image.png
externals的使用:
比如用户环境域名为 https://xxx.jituan.com,cdn 域名为 https://statics.jituan.com ,cdn 上放了一些公用的静态资源,如 loadsh,moment 之类的工具库。

那在项目中,可如下配置
index.html

<!-- 使用cdn资源 -->
<script src="https://statics.jituan.com/lodash/4.17.11/index.min.js,moment/2.22.2/locale/zh-cn.js,react/16.8.6/index.min.js,react-dom/16.8.6/index.min.js"></script>
const externals = {
  // key: value,    key 表示npm包名,value 表示挂载在全局下的属性或方法
  lodash: '_',
  moment: 'moment',
  react: 'React',
  'react-dom': 'ReactDOM',
}

module.exports = {
  //...
  externals,
};

我们已经知道了 cdn 的资源,可以通过window来访问。但现在都是使用模块化的开发,而不是传统开发。我们会使用 import 导入我们需要的资源

import moment from 'moment'

from 后面的值可以是路径,也可以是模块名。当它是一个模块名时,那就需要通过配置,告诉 JS 引擎模块的位置。

我们可以通过 babel-plugin-const-replace-import解决这个问题。

babelOptions

借助 babel-plugin-const-replace-import,可以在编译时,将通过 import 的方式引入的指定类库转成 const 的方式。

webpack.config.js

const externals = {
  lodash: '_',
  moment: 'moment',
  react: 'React',
  'react-dom': 'ReactDOM',
}

module.exports = {
  //...
  externals,
  babelOptions: {
     plugins: [
        [
          require.resolve("babel-plugin-const-replace-import"),
          { libraries: externals }
        ]
     ]
  }
};

源码:

import React from 'react'
import { isEmpty } from 'lodash'

转换后:

const React = window.React
const { isEmpty } = window._

react、lodash的值将会检索一个全局的 React、_变量,import 的声明方式转换为 const 的声明方式。

转换的目的是为了防止从本地node_modules中去查找依赖。import 的声明,会从node_modules 查找依赖,配置babel-plugin-const-replace-import后,就变成了 const 的方式,也就是说不会去node_mudules 中查找。

未配置babel-plugin-const-replace-import 导致的bug

以下是我们公司的子项目配置
index.html

<script src="//statics.jituancaiyun.com/cdn/js/??react/16.6.3/index.js,prop-types/15.6.2/index.js,react-dom/16.6.3/index.js,redux/4.0.1/index.js,react-redux/5.1.1/index.js,react-router-dom/4.3.1/index.js,redux-actions/2.6.4/index.min.js,moment/2.22.2/index.min.js,moment/2.22.2/locale/zh-cn.js,antd/3.16.2/index.min.js,axios/0.18.0/index.min.js"></script>

package.json

"scripts": {
  "local": "cross-env NODE_ENV=development abc dev",
  "dev": "node ./script/update && cross-env RUN_ENV=net cross-env NODE_ENV=development abc dev",
  "build": "node ./script/update && cross-env RUN_ENV=net cross-env NODE_ENV=production abc build",
  "releaseIndex": "cross-env NODE_ENV=production abc build",
  "releaseApp": "cross-env NODE_ENV=production cross-env RUN_ENV=com abc build --config=abc.library.js",
  "release": "npm run releaseIndex && npm run releaseApp",
  "eslint-fix": "node_modules/.bin/eslint --fix src/"
},

webpack.config.js

const isDev = process.env.NODE_ENV === 'development'

const externals = {
  lodash: '_',
  moment: 'moment',
  react: 'React',
  'react-dom': 'ReactDOM',
}

const babelOptions = {}

if (!isDev) {
  babelOptions.plugins = [
    [
      require.resolve('babel-plugin-const-replace-import'),
      { libraries: externals },
    ],
  ]
}

module.exports = {
  // ...,
  externals,
  babelOptions,
}

cdn 上的 react 版本为 16.6.3,本地安装的版本为16.8,有一个同学开发时使用了hooks(16.8及以上的版本才支持)进行开发,本地开发是没有问题的,线上环境却出了问题,就是因为没有配置babel-plugin-const-replace-import 导致的。如果12行去掉,在本地开发时就会及时发现这个问题。