木匣子

Web/Game/Programming/Life etc.

给 Apollo-Link 打补丁 II

继上回给 Apollo Link 打补丁过去了几个月,本想翻看一下之前的那个 Pull Request 是否已经被 Merge,结果没想到原来的 Apollo Link 库已经被弃用了。新版的 Apollo Link 被重构后集成到了 @apollo/client 包中。不过并没有修复前面 PR 提到的问题。更糟糕的是之前使用的打补丁的方式无法起作用了。新的 selectHttpOptionsAndBody() 函数被专门放置到一个独立的 module 文件中。并且在提供的 cjs bundle 里,它直接被 createHttpLink 函数引用。所以之前的替换方法没办法在函数被引用前进行「狸猫换太子」。

那么是否有其它成本比较低的方式进行补救?于是我简单的搜索了一下 Webpack 的官方文档,很快找到了一个叫 NormalModuleReplacementPlugin 的内置插件。使用它可以非常方便地替换指定的模块文件。

new webpack.NormalModuleReplacementPlugin(
  resourceRegExp,
  newResource
);

于是我们将 @apollo/client 包中的 selectHttpOptionsAndBody.js 复制一份,并作以下修改:

  1. 在头部导入 stripIgnoredCharacters ;
  2. 在生成 body.query 的地方使用 stripIgnoredCharacters 压缩 graphql 查询;

Diff 结果如下:

3d2
< import { stripIgnoredCharacters } from "graphql";
38c37
<         body.query = stripIgnoredCharacters(print(query));
---
>         body.query = print(query);

将修改后的文件放到 /path/to/project/patch/selectHttpOptionsAndBody.js 然后在 Webpack 配置中增加一个插件:

plugins: {[
    ..., 
    new webpack.NormalModuleReplacementPlugin(
      /\/node_modules\/@apollo\/client\/link\/http\/selectHttpOptionsAndBody\.js/,
      path.resolve(process.cwd(), './patch/selectHttpOptionsAndBody.js'),
    ),
]},

重新运行 Webpack 构建,但发现并未能如愿将补丁打上。检查 Sourcemap 发现 Webpack 引入的是 @apollo/client 打包好的 commonjs 模块(*.cjs.js)而非实际的源码。

经过一番排查,发现 @apollo/client/link/httppackage.json 描述如下:

{
  "name": "@apollo/client/link/http",
  "main": "http.cjs.js",
  "module": "index.js",
  "types": "index.d.ts"
}

可见当前 Webpack 使用的是 main 字段的入口,而非 module 字段的入口。根据 Webpack 的 resolve 配置说明,我们可以使用 #resolvemainfields 来修改入口顺序。于是在 Webpack 配置中找到 mainFields 并作如下调整:

resolve: {
    mainFields: ['module', 'browser', 'main', 'jsnext:main'],
    ...,
}

调整的依据:

  1. 优先使用 es module,虽然会稍微增加一些构建时间,但可以利用 tree-shaking 减小包的体积;
  2. 对于不支持 es module 的库,尝试使用 browser 或 main 入口,即预编译的浏览器包或者 commonjs 的入口;

再次构建,检查 Sourcemap 后发现 selectHttpOptionsAndBody.js 已经变成我们打个补丁的版本了。

最后为了保证依赖的一致性,将 @apollo/client 的版本锁定。下次升级时需要检查补丁是否需要修订。