问题描述
最近在学习 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: "base3$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-antv
的 createPages()
函数的 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;
}
}
}
};
});
可以看到,它的配置项有 version
、legacy
、decoratorsBeforeExport
这三个,也就是官方文档中所描述的那样(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 上拉源码。
本文正在参加「金石计划」