CJS 模块Lexer
A 类 非常快的,我马上回来 JS CommunityJS模块的语法用词法来检测一个 CommunityJS模块最有可能的被命名导出列表.
输出命名的导出列表( E)exports.name = ...)和可能的模块再出口(module.exports = require('...')包括这些案件的常见变异.
从 https://github.com/guybedford/es-module-lexer 页面存档备份,存于互联网档案馆。 。 。 .
综合处理JS语言语法,同时保持小而快. - JS冷的每MB为90ms;JS热的每MB为15m, 参见基准 需要更多信息.
项目状况
该项目在Node.js核心中用于检测在将CJS模块导入无害环境管理时可用的指定出口,并为此目的加以维护.
PR会被接受并上游,仅用于解析器错误,性能改进或新的语法支持.
该项目的检测模式是 冻结。这是因为添加任何新的出口检测模式都会导致向后兼容性支离破碎。 具体地说,很难理解为什么一个名为Export for CommunityJS的ES模块可能会在更新的Node.js版本中工作,但不会在更旧的版本中工作. 这个问题只能在模块作者的下游发现,然后模块作者的固定方法必须了解该项目中哪些模式可以完全反向兼容。 相反,通过完全冻结所发现的图案,如果在任何节点.js版本中有效,它将在任何其他版本中有效。 构建工具也可以可靠地将这个项目所支持的语法作为其确保语法支持的产出目标的一部分.
使用量
npm install cjs-module-lexer
用于通用JS:
const { parse } = require('cjs-module-lexer');
// `init` return a promise for parity with the ESM API, but you do not have to call it
const { exports, reexports } = parse(`
// named exports detection
module.exports.a = 'a';
(function () {
exports.b = 'b';
})();
Object.defineProperty(exports, 'c', { value: 'c' });
/* exports.d = 'not detected'; */
// reexports detection
if (maybe) module.exports = require('./dep1.js');
if (another) module.exports = require('./dep2.js');
// literal exports assignments
module.exports = { a, b: c, d, 'e': f }
// __esModule detection
Object.defineProperty(module.exports, '__esModule', { value: true })
`);
// exports === ['a', 'b', 'c', '__esModule']
// reexports === ['./dep1.js', './dep2.js']
在使用无害环境管理版本时,瓦斯姆得到支持:
import { parse, init } from 'cjs-module-lexer';
// In browsers, initialize the Wasm before parsing.
await init();
const { exports, reexports } = parse(source);
parse 电话 initSync() 如果初始化尚未完成,则在第一次使用时使用.
打电话 initSync() 直接选择屏蔽初始化运行时.
浏览器主线程可以限制同步 Wasm 编译, 所以等待
init() 在浏览器中解析之前.
瓦斯姆的建筑速度在1.5x左右,没有冰冷起步.
语法
CommunityJS导出匹配项运行于源令牌流.
符号语法是:
IDENTIFIER: As defined by ECMA-262, without support for identifier `\` escapes, filtered to remove strict reserved words:
"implements", "interface", "let", "package", "private", "protected", "public", "static", "yield", "enum"
STRING_LITERAL: A `"` or `'` bounded ECMA-262 string literal.
MODULE_EXPORTS: `module` `.` `exports`
EXPORTS_IDENTIFIER: MODULE_EXPORTS_IDENTIFIER | `exports`
EXPORTS_DOT_ASSIGN: EXPORTS_IDENTIFIER `.` IDENTIFIER `=`
EXPORTS_LITERAL_COMPUTED_ASSIGN: EXPORTS_IDENTIFIER `[` STRING_LITERAL `]` `=`
EXPORTS_LITERAL_PROP: (IDENTIFIER (`:` IDENTIFIER)?) | (STRING_LITERAL `:` IDENTIFIER)
EXPORTS_SPREAD: `...` (IDENTIFIER | REQUIRE)
EXPORTS_MEMBER: EXPORTS_DOT_ASSIGN | EXPORTS_LITERAL_COMPUTED_ASSIGN
EXPORTS_DEFINE: `Object` `.` `defineProperty `(` EXPORTS_IDENFITIER `,` STRING_LITERAL
EXPORTS_DEFINE_VALUE: EXPORTS_DEFINE `, {`
(`enumerable: true,`)?
(
`value:` |
`get` (`: function` IDENTIFIER? )? `() {` return IDENTIFIER (`.` IDENTIFIER | `[` STRING_LITERAL `]`)? `;`? `}` `,`?
)
`})`
EXPORTS_LITERAL: MODULE_EXPORTS `=` `{` (EXPORTS_LITERAL_PROP | EXPORTS_SPREAD) `,`)+ `}`
REQUIRE: `require` `(` STRING_LITERAL `)`
EXPORTS_ASSIGN: (`var` | `const` | `let`) IDENTIFIER `=` (`_interopRequireWildcard (`)? REQUIRE
MODULE_EXPORTS_ASSIGN: MODULE_EXPORTS `=` REQUIRE
EXPORT_STAR: (`__export` | `__exportStar`) `(` REQUIRE
EXPORT_STAR_LIB: `Object.keys(` IDENTIFIER$1 `).forEach(function (` IDENTIFIER$2 `) {`
(
(
`if (` IDENTIFIER$2 `===` ( `'default'` | `"default"` ) `||` IDENTIFIER$2 `===` ( '__esModule' | `"__esModule"` ) `) return` `;`?
(
(`if (Object` `.prototype`? `.hasOwnProperty.call(` IDENTIFIER `, ` IDENTIFIER$2 `)) return` `;`?)?
(`if (` IDENTIFIER$2 `in` EXPORTS_IDENTIFIER `&&` EXPORTS_IDENTIFIER `[` IDENTIFIER$2 `] ===` IDENTIFIER$1 `[` IDENTIFIER$2 `]) return` `;`)?
)?
) |
`if (` IDENTIFIER$2 `!==` ( `'default'` | `"default"` ) (`&& !` (`Object` `.prototype`? `.hasOwnProperty.call(` IDENTIFIER `, ` IDENTIFIER$2 `)` | IDENTIFIER `.hasOwnProperty(` IDENTIFIER$2 `)`))? `)`
)
(
EXPORTS_IDENTIFIER `[` IDENTIFIER$2 `] =` IDENTIFIER$1 `[` IDENTIFIER$2 `]` `;`? |
`Object.defineProperty(` EXPORTS_IDENTIFIER `, ` IDENTIFIER$2 `, { enumerable: true, get` (`: function` IDENTIFIER? )? `() { return ` IDENTIFIER$1 `[` IDENTIFIER$2 `]` `;`? `}` `,`? `})` `;`?
)
`})`
令牌之间的间距取为任意ECMA-262白相间,ECMA-262块注释或ECMA-262行注释.
- 返回的导出名称为:
- 全体
IDENTIFIER财务报告和审定财务报表STRING_LITERAL用于EXPORTS_MEMBER财务报告和审定财务报表EXPORTS_LITERAL火柴。 比赛. - 第一个
STRING_LITERAL全部时间档EXPORTS_DEFINE_VALUE匹配该字符串不是同一字符串的地方EXPORTS_DEFINE匹配这个不是同时EXPORTS_DEFINE_VALUE相匹配的。 匹配的.
- 全体
- 再出口的规格是:
- 这个
REQUIRE匹配最后一个匹配的任意MODULE_EXPORTS_ASSIGN或者说EXPORTS_LITERAL。 。 。 . - 全体 顶级
EXPORT_STARREQUIRE匹配和EXPORTS_ASSIGN匹配其IDENTIFIER也匹配第一个IDENTIFIER内EXPORT_STAR_LIB。 。 。 .
- 这个
解析示例
命名的导出解析
命名出口的基本匹配规则是 exports.name, (中文(简体) ), exports['name'] 或者说 Object.defineProperty(exports, 'name', ...)。这种匹配没有范围分析,也不管表达位置如何:
// DETECTS EXPORTS: a, b
(function (exports) {
exports.a = 'a';
exports['b'] = 'b';
})(exports);
由于没有范围分析,上述检测可能过度分类:
// DETECTS EXPORTS: a, b, c
(function (exports, Object) {
exports.a = 'a';
exports['b'] = 'b';
if (false)
exports.c = 'c';
})(NOT_EXPORTS, NOT_OBJECT);
如果识别资料被重新命名,它又会降低分类:
// DETECTS: NO EXPORTS
(function (e) {
e.a = 'a';
e['b'] = 'b';
})(exports);
Getter 导出分析
Object.defineProperty 检测到特定值和返回标识符或成员表达式的获取表:
// DETECTS: a, b, c, d, __esModule
Object.defineProperty(exports, 'a', {
enumerable: true,
get: function () {
return q.p;
}
});
Object.defineProperty(exports, 'b', {
enumerable: true,
get: function () {
return q['p'];
}
});
Object.defineProperty(exports, 'c', {
enumerable: true,
get () {
return b;
}
});
Object.defineProperty(exports, 'd', { value: 'd' });
Object.defineProperty(exports, '__esModule', { value: true });
还特别检测出价值属性:
Object.defineProperty(exports, 'a', {
value: 'no problem'
});
为了避免匹配具有副作用的获取器,任何不支持上述表单的导出名称的获取器都会 选择退出匹配的获取器 :
// DETECTS: NO EXPORTS
Object.defineProperty(exports, 'a', {
get () {
return 'nope';
}
});
if (false) {
Object.defineProperty(module.exports, 'a', {
get () {
return dynamic();
}
})
}
未检测到其它对象定义结构或获取器函数机构:
// DETECTS: NO EXPORTS
Object.defineProperty(exports, 'a', {
enumerable: false,
get () {
return p;
}
});
Object.defineProperty(exports, 'b', {
configurable: true,
get () {
return p;
}
});
Object.defineProperty(exports, 'c', {
get: () => p
});
Object.defineProperty(exports, 'd', {
enumerable: true,
get: function () {
return dynamic();
}
});
Object.defineProperty(exports, 'e', {
enumerable: true,
get () {
return 'str';
}
});
Object.defineProperties 也不予支持.
导出对象指定
做了最好的检测 module.exports 对象任务,但是由于这不是一个全解析器,任意表达式不会在
对象解析进程.
支持简单的对象定义 :
// DETECTS EXPORTS: a, b, c
module.exports = {
a,
'b': b,
c: c,
...d
};
非标识符或字符串表达式的对象属性会从对象检测中跳出,而扩展被忽略:
// DETECTS EXPORTS: a, b
module.exports = {
a,
...d,
b: require('c'),
c: "not detected since require('c') above bails the object detection"
}
Object.defineProperties 目前也没有支持.
模块
任意 module.exports = require('mod') 任务被检测为再出口, 但只返回最后一个:
// DETECTS REEXPORTS: c
module.exports = require('a');
(module => module.exports = require('b'))(NOT_MODULE);
if (false) module.exports = require('c');
这是为了避免在Webpack包中与外部包件(包括: module.exports = require('external') 在他们的来源 对于每个外部依赖.
在导出对象任务中,任何扩展 require() 检测为多个单独的再出口:
// DETECTS REEXPORTS: a, b
module.exports = require('ignored');
module.exports = {
...require('a'),
...require('b')
};
转接器再出口
对于已命名的出口,转接器产出与上述规则十分相通.
但对于恒星再出口,要特别注意支持从Babel和TypeScript以及RollupJS等捆绑器输出的普通模式. 这些再出口和再出口模式仅限于通过这些工具的直接输出在顶层检测.
譬如说, export * from 'external' 由 Babel 输出为:
"use strict";
exports.__esModule = true;
var _external = require("external");
Object.keys(_external).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
exports[key] = _external[key];
});
这里 var _external = require("external") 专门检测到的以及 Object.keys(_external) 语句, 准确到
包括产出的细微变化。 这个 _external 财务报告和审定财务报表 key 在此仔细匹配标识符
检测.
类似类型脚本, export * from 'external' 输出为:
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("external"));
这里 __export(require("external")) 语句被明确检测为再出口,包括变异 tslib.__export 财务报告和审定财务报表 __exportStar。 。 。 .
环境支助
节点.js 10+,以及 所有有Web Assembly支持的浏览器。 。 。 .
JS 语法支持
- Token状态解析出所有行注释,块注释,字符串,模板字符串,块,parens和活页符.
- 分区操作员/ regex 符号模糊是通过回溯检查处理的,包括关闭牙套或剖面回溯跟踪.
- 总是正确分析有效的JS来源,但可以毫无错误地分析无效的JS来源.
基准
可使用 npm run bench。 。 。 .
目前的结果:
联合来文:
Module load time
> 4ms
Cold Run, All Samples
test/samples/*.js (3635 KiB)
> 299ms
Warm Runs (average of 25 runs)
test/samples/angular.js (1410 KiB)
> 13.96ms
test/samples/angular.min.js (303 KiB)
> 4.72ms
test/samples/d3.js (553 KiB)
> 6.76ms
test/samples/d3.min.js (250 KiB)
> 4ms
test/samples/magic-string.js (34 KiB)
> 0.64ms
test/samples/magic-string.min.js (20 KiB)
> 0ms
test/samples/rollup.js (698 KiB)
> 8.48ms
test/samples/rollup.min.js (367 KiB)
> 5.36ms
Warm Runs, All Samples (average of 25 runs)
test/samples/*.js (3635 KiB)
> 40.28ms
Wasm 构建 :
Module load time
> 10ms
Cold Run, All Samples
test/samples/*.js (3635 KiB)
> 43ms
Warm Runs (average of 25 runs)
test/samples/angular.js (1410 KiB)
> 9.32ms
test/samples/angular.min.js (303 KiB)
> 3.16ms
test/samples/d3.js (553 KiB)
> 5ms
test/samples/d3.min.js (250 KiB)
> 2.32ms
test/samples/magic-string.js (34 KiB)
> 0.16ms
test/samples/magic-string.min.js (20 KiB)
> 0ms
test/samples/rollup.js (698 KiB)
> 6.28ms
test/samples/rollup.min.js (367 KiB)
> 3.6ms
Warm Runs, All Samples (average of 25 runs)
test/samples/*.js (3635 KiB)
> 27.76ms
Wasm 构建步骤
建筑使用道克并制造,必须先安装.
要建立 Lexer 运行 npm run build-wasm建造时没有插头, npm run build-wasm-local
下载相同的被绑定的 idi-sdk 和 二进制版本 build/toolchain 并用这些建房.
最佳通行证是用 二进制 在出版之前减少Web大会足迹.
在建造了 Lexer 之后, 建造最后的分发组件
(lexer.js和lexer.mjs)通过运行 npm run build。 。 。 .
如果您需要建立 lib/lexer.wat (可选) 您必须首先安装
华布 作为此的兄弟姐妹文件夹
项目。 然后通过运行构建 wat 文件 make lib/lexer.wat
创建释放
这些是创建和出版发行品的步骤. 你需要个插头 已安装或已安装 华布 如上所述:
- 确定该发布是否应该根据自此以来的变化进行成片补丁、小补丁或主要补丁 ,然后确定新版本.
- 更新 package.json 版本,并运行完整的构建和测试
- npm 安装
- npm 运行构建
- npm 运行测试
- 提交并标记更改, 推到主和标签
- 举个例子
git tag -a 1.4.2 -m "1.4.2"git push origin tag 1.4.2
- 举个例子
- 创建 GitHub 发布
- 运行 npm 发布有访问权限的账户( 询问有访问权限的人) 节点创建账户是一个选项 如果你没有权限.
许可证
麻省理工学院