gatsby-starter-theme-antv 查错历险记

405 阅读5分钟

问题描述

最近在学习 G2 源码项目,项目是以 gatsby + @antv/gatsby-theme-antv 来构建的。

想着要运行源码项目边调试边学习。可是在启动项目的时候却遇到了问题。

$ npm run start

报错信息:

"@antv/gatsby-theme-antv" threw an error while running the createPages lifecycle:

Error: [BABEL] /C:/Users/violetjack/github/G2-4/examples/bar/basic/demo/bsic.ts: The decorators plugin, when .version is '2018-09' or not specified, requires a 'decoratorsBeforeExport' option, whose value must be a boolean. (While pro cessing: "base333$inherits")

我同样试了使用 gatsby-cli 来构建新项目,也存在同样的问题。

$ yarn global add gatsby-cli
$ gatsby new mysite https://github.com/antvis/gatsby-starter-theme-antv
$ cd mysite
$ yarn start

解决过程全纪录

从报错信息上来看,就是 babel 的一个配置项传输上出现了问题。那么想一些解决方法。

查找 @antv/gatsby-theme-antv 库的 babel

首先,从报错信息来看问题是出在 @antv/gatsby-theme-antvcreatePages() 函数的 babel 编译上,于是就去找代码~

const { transform } = require('@babel/standalone');

exports.createPages = async ({ actions, graphql, reporter, store }) => {
    ...
    const allExamples = allDemos.map((item) => {
        const source = fs.readFileSync(item.absolutePath, 'utf8');
        const { code } = transform(source, {
          filename: item.absolutePath,
          presets: ['react', 'typescript', 'es2015', 'stage-3'],
          plugins: ['transform-modules-umd'],
          babelrc: false,
        });
        return {
          ...item,
          source,
          babeledSource: code,
        };
    });
    ...
};

然后,我去找了 @babel/standalone 的文档,发现它用于单独使用来转译代码。

在文档中提到,由于它单独使用的特性,.babelrc 这类 配置文件 是对 transform() 函数无效,所以需要在函数中去定义配置项。即上面代码的 babel 配置就是函数中的第二个参数:

{
    filename: item.absolutePath,
    presets: ['react', 'typescript', 'es2015', 'stage-3'],
    plugins: ['transform-modules-umd'],
    babelrc: false,
}

到这里线索就断了,先试试其他方案。

搜集网上解决方案

搜索关键字 @antv/gatsby-theme-antv" threw an error while running the createPages lifecycle 发现并没有什么有用的结果。

搜索关键字 requires a 'decoratorsBeforeExport' option, whose value must be a boolean. 果然发现有类似的问题。

找了很多文章,基本上解决方案都差不多,是在 babel 配置文件中加上如下代码:

{
    "plugins": [
      ["@babel/plugin-proposal-decorators", { "legacy": true }],
      ["@babel/plugin-proposal-class-properties", { "loose" : true }]
    ]
}

于是在项目中试着创建 .babelrc 文件 来配置。发现还是原来的报错。

结合第一步查看源码的经历,babel 配置文件并没有在 transform() 函数中生效。

查询报错路径

突然想看看报错的地方发生了什么,于是我全局搜索了 when .version is '2018-09' or not specified 发现有两处地方有这个报错。分别是:

  • node_modules@babel\plugin-syntax-decorators\lib\index.js
  • node_modules@babel\standalone\babel.js

我就用笨办法,在错误信息上加一些标识,来查找到底是哪里发出的报错。结果是 node_modules\@babel\standalone\babel.js,这也符合了我一开始的猜测。

  var syntaxDecorators = declare(function (api, options) {
    api.assertVersion(7);
    var version = options.version;
    {
      var legacy = options.legacy;
      if (legacy !== undefined) {
        if (typeof legacy !== "boolean") {
          throw new Error(".legacy must be a boolean.");
        }
        if (version !== undefined) {
          throw new Error("You can either use the .legacy or the .version option, not both.");
        }
      }
      if (version === undefined) {
        version = legacy ? "legacy" : "2018-09";
      } else if (version !== "2023-01" && version !== "2022-03" && version !== "2021-12" && version !== "2018-09" && version !== "legacy") {
        throw new Error("Unsupported decorators version: " + version);
      }
      var decoratorsBeforeExport = options.decoratorsBeforeExport;
      if (decoratorsBeforeExport === undefined) {
        if (version === "2021-12" || version === "2022-03") {
          decoratorsBeforeExport = false;
        } else if (version === "2018-09") {
          throw new Error("The decorators plugin, when .version is '2018-09' or not specified," + " requires a 'decoratorsBeforeExport' option, whose value must be a boolean. 222222" // 在这里做了标记
        }
      } else {
        if (version === "legacy" || version === "2022-03" || version === "2023-01") {
          throw new Error("'decoratorsBeforeExport' can't be used with " + version + " decorators.");
        }
        if (typeof decoratorsBeforeExport !== "boolean") {
          throw new Error("'decoratorsBeforeExport' must be a boolean.");
        }
      }
    }
    return {
      name: "syntax-decorators",
      manipulateOptions: function manipulateOptions(_ref, parserOpts) {
        var generatorOpts = _ref.generatorOpts;
        if (version === "legacy") {
          parserOpts.plugins.push("decorators-legacy");
        } else {
          if (version === "2023-01") {
            parserOpts.plugins.push(["decorators", {
              allowCallParenthesized: false
            }], "decoratorAutoAccessors");
          } else if (version === "2022-03") {
            parserOpts.plugins.push(["decorators", {
              decoratorsBeforeExport: false,
              allowCallParenthesized: false
            }], "decoratorAutoAccessors");
          } else if (version === "2021-12") {
            parserOpts.plugins.push(["decorators", {
              decoratorsBeforeExport: decoratorsBeforeExport
            }], "decoratorAutoAccessors");
            generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
          } else if (version === "2018-09") {
            parserOpts.plugins.push(["decorators", {
              decoratorsBeforeExport: decoratorsBeforeExport
            }]);
            generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
          }
        }
      }
    };
  });

可以看到,它的配置项有 versionlegacydecoratorsBeforeExport 这三个,也就是官方文档中所描述的那样(babel-plugin-proposal-decorators 和 babel-plugin-syntax-decorators 的配置项是一样的)。

是谁动了 babel-plugin-syntax-decorators?

那到底是谁触发了报错呢?自然而然想到了 @babel/standalone 的 transform() 函数。那么我们就照着网上的方案修改函数中的 babel 配置项。

    const { code } = transform(source, {
      filename: item.absolutePath,
      presets: ['react', 'typescript', 'es2015', 'stage-3'],
      plugins: [
        'transform-modules-umd',
        [
          "proposal-decorators", 
          { 
            version: "legacy" 
          }
        ],
        [
          "proposal-class-properties",
          {
            loose: true
          }
        ]
      ],
      babelrc: false,
    });

结果还是报错(薅头发抓狂……)。想了好久后突然想到:既然是在 node 环境下运行的,我直接打 log 找找是谁在调用 babel-plugin-syntax-decorators 就可以啦。

打印后发现:

  • 在 transform 中写的 babel 配置项生效了,逻辑是正确的。
  • 除了我们自定义的 babel 配置项,还有一个地方也在调用 babel-plugin-syntax-decorators 且配置项异常。

继续找呗,最终找到了元凶。

  var presetStage3 = (function (_, opts) {
    if (opts === void 0) {
      opts = {};
    }
    var _opts = opts,
      _opts$loose = _opts.loose,
      loose = _opts$loose === void 0 ? false : _opts$loose,
      _opts$decoratorsLegac = _opts.decoratorsLegacy,
      decoratorsLegacy = _opts$decoratorsLegac === void 0 ? false : _opts$decoratorsLegac,
      _opts$decoratorsVersi = _opts.decoratorsVersion,
      decoratorsVersion = _opts$decoratorsVersi === void 0 ? "2018-09" : _opts$decoratorsVersi,
      decoratorsBeforeExport = _opts.decoratorsBeforeExport;
    var plugins = [
      _syntaxImportAssertions, 
      proposalUnicodeSetsRegex, 
      proposalDuplicateNamedCapturingGroupsRegex, 
      // 这里这里这里
      [
        proposalDecorators,
        {
          version: decoratorsLegacy ? "legacy" : decoratorsVersion,
          decoratorsBeforeExport: decoratorsBeforeExport
        }
      ], 
      // 这里这里这里
      proposalRegexpModifiers
    ].concat(_toConsumableArray([proposalExportNamespaceFrom, proposalLogicalAssignmentOperators, [proposalOptionalChaining, {
      loose: loose
    }], [proposalNullishCoalescingOperator, {
      loose: loose
    }], [proposalClassProperties, {
      loose: loose
    }], proposalJsonStrings, proposalNumericSeparator, [proposalPrivateMethods, {
      loose: loose
    }], proposalPrivatePropertyInObject, proposalClassStaticBlock]));
    return {
      plugins: plugins
    };
  });

所以真相只有一个!就是 stage-3 这个浓眉大眼的家伙在捣鬼!它自己偷偷搞了一套 plugins 的配置,导致了这场大案。

既然找到了凶手,解决起来也就很简单了。在 node_modules\@antv\gatsby-theme-antv\gatsby-node.js 中修改 stage-3 的配置项来修复 babel-plugin-syntax-decorators 的问题即可。而且之前的解决方案也并没有派上用场。

const { code } = transform(source, {
    filename: item.absolutePath,
    presets: ['react', 'typescript', 'es2015', [
      'stage-3', 
      {
        decoratorsLegacy: true,
      }
    ]],
    plugins: [
      'transform-modules-umd'
    ],
    babelrc: false,
 });

总结

这个问题困扰了我一整天,主要也是怪我对前端工程这块儿知识不扎实。这么一步步查证,真的有种查案的感觉。下面是一些心得和教训:

  • 遇到问题千万别玄学,什么重启电脑、重启项目、换个电脑、重装 dependences 这些,这些尝试一来效率很低且无用,很浪费时间。二来会养成惰性,不愿意去吭硬骨头。
  • 基础知识非常重要,基础越扎实定位和解决问题的能力就越强。平时要多积累这方面的知识。
  • 找问题要有条理性,就像是查案一样层层递进。不能东一榔头西一棒子,反复横跳也是不愿意吭硬骨头的表现啦。(当然要有调理的钻研,而不是钻牛角尖)
  • 前端工程项目的 node_module 是可以编辑和调试的!别把 node_module 想的太神秘,直接去里面调试就能解决一些问题。不用每次都去 github 上拉源码。

本文正在参加「金石计划」