Webpack External with Path Transform

Build

在实际开发的过程中,可能会遇到这样的 Webpack 打包场景:某一个目录内的文件不需要被打包,而是通过 require 的形式在运行时单独加载。对于一般的包来说,只需要简单的配置 Webpack 的 externals 字段就可以了;但是如果引用的位置是一个相对路径,那么在配置 externals 的时候就会相对复杂一些。至少有以下的潜在问题:

  • 不需要被打包的目录是固定的,但是引用的位置可能不是固定的。这导致在相对路径引用的时候,引用的地址不是一个固定的字符串,需要根据引用位置以及引用路径才能确定一个引用是否指向了不需要被打包的文件目录。换句话说,是否 externals 需要动态分析

举个例子来说,假设目录结构如下:

- A
  - should-exclude.js
- B
  - A
    - should-include.js
  - index.js

其中,只有顶层的 A 目录不需要被打包,B 目录下的 A 子目录依然需要被打包。假设 index.js 中的内容如下:

import shouldExclude from '../../A/should-exclude';
import shouldInclude from '../A/should-include';

那么符合预期的编译结果应该大致如下:

const shouldExclude = require('./A/should-exclude').default;
const shouldInclude = __webpack__require('../A/should-include');

在这种场景下,单纯判断引用路径是否包含 A/ 就不够了,需要具体问题具体分析。

  • 另一个问题是打包后的引用路径很可能需要发生变化。还是用上面这个例子来说明。B 目录被打包后会生成一个文件,此时 B 目录内的文件层级关系就不复存在了。这时候生成文件和 A 的目录关系也就固定下来了。原先在 B 目录内各种情况的相对引用,到了打包之后需要根据最终确认的层级关系固定成一个具体的表示。因此,引用的路径也需要转义

针对上面提到的这两点,Webpack 的 externals 也提供了函数回调的配置方案,可以用于灵活的配置。一个参考代码如下;

const path = require('path');
function external(context, request, callback) {
  // 当 request 是相对引用的时候,根据使用文件的绝对路径(context),计算出具体被引用的文件地址
  const requestedFilePath = path.resolve(context, request);
  // 判断被引用的地址是否需要被打包
  if (shouldExternal(requestedFilePath)) {
    // 重新计算引用的层级关系,比如将 ../../A/xx.js 转化成 ../A/xx.js
    const transformedRequest = transformRequest(request);
    return callback(null, `commonjs2 ${transformedRequest}`);
  }
  return null;
}

module.exports = {
  // ...
  externals: [
    external,
  ],
  // ...
};

注:关于 commonjs2commonjs 的区别,可以参考这个 issue