如何在Closure Compiler中将node_modules定义为外部模块?

12

我有一个Node.js项目,想要使用Closure Compiler进行编译。我不想在浏览器中运行/使用browserify。我主要想要类型检查的效用。我最初使用以下方式使编译器正常工作:

java -jar compiler.jar -W VERBOSE 
                       --language_in ECMASCRIPT5_STRICT 
                       --externs closure-externs.js 
                       --js="lib/**.js"

closure-externs.js 中手动定义了变量和函数,我以相对粗糙的方式从 Node.js 中使用它们:

// closure-externs.js

/** @constructor */function Buffer(something){}
function require(path){}
var process = {};
[...]

结果发现这只是纯粹的运气。文件之间没有依赖追踪,所以有些情况下你可能返回一个类型{Foo},编译器会抱怨它不存在(取决于机器,取决于编译顺序)。后来我发现自己全部做错了,应该使用--process_common_js_modules,这样编译器就会在需要时进行依赖追踪,比如我require("foo")。我当前这样调用编译器:

java -jar compiler.jar -W VERBOSE 
                       --language_in ECMASCRIPT5_STRICT 
                       --externs externs/fs.js 
                       --js="lib/**.js"
                       --process_common_js_modules 
                       --common_js_entry_module app.js

但是出现了以下错误:

 ERROR - required entry point "module$crypto" never provided
 ERROR - required entry point "module$dgram" never provided
 ERROR - required entry point "module$extend" never provided
 ERROR - required entry point "module$fs" never provided
 ERROR - required entry point "module$net" never provided
 ERROR - required entry point "module$q" never provided
一些模块是 Node.js 本地模块 (例如 fs),而其他模块包含在 node_modules 中,如 q。我不想通过编译器运行这些外部模块,所以我知道需要为它们设置 externs 文件。我知道有一个 https://github.com/dcodeIO/node.js-closure-compiler-externs 用于常见的 Node.js externs,并且我知道如何在编译器上调用它们,但出现了像 --externs externs/fs.js 的错误,module$fs仍然存在问题。我做错了什么?
我知道还有其他标志,如 --module--common_js_module_path_prefix,但我不确定是否需要使用它们才能使其正常工作。我无法通过搜索获得任何正确的答案。 :(

2
你需要的许多externs都是在编译器项目中官方维护的:https://github.com/google/closure-compiler/tree/master/contrib/nodejs。contrib externs现在也作为编译器官方npm包的一部分进行分发: https://www.npmjs.com/package/google-closure-compiler。 - Chad Killingsworth
我知道这些文件存在。我的问题是,如何与compiler.jar正确使用这些文件,因为--externs似乎并没有像我想的那样工作。 - Dororo
这就是为什么我把它发布为评论 - 我也对答案感兴趣。 - Chad Killingsworth
2个回答

6
问题在于您希望编译器能够识别某些require调用是内部的,即所需的模块应该被编译器处理为源代码,而其他一些则是外部的,因此应该保持不变。目前还没有很好地解决这种情况的方法。
解决方法:
使用后期处理添加外部Require语句
在这种情况下,您将完全省略对外部模块的任何require语句。编译器只会处理具有内部要求语句和模块的代码。编译后,您将添加外部require语句的前缀:
需要添加的Header JS
var crypto = require('crypto');

待编译的源代码

console.log(crypto);

因为crypto在外部声明,编译器会正确识别类型和符号名称。

别名Require调用

当指定--process_common_js_modules时,编译器会识别require语句,并以类似于其他语言中宏的方式扩展它们。通过为应保持外部的require语句设置别名,编译器将无法识别它们,因此不会进行扩展。

需要编译的源代码

var externalRequire = require;
/** @suppress {duplicate} this is already defined in externs */
var crypto = externalRequire('crypto');
console.log(crypto)

1
谢谢Chad,这个回答解决了问题,并为想要在未来使其工作的任何人提供了一个不错的解决方案。相关的来回讨论:https://github.com/google/closure-compiler/issues/954 - Dororo
使用任何这些解决方法都会抛出“警告 - 在外部定义中未定义名称模块。”,因为对于每个定义模块(例如“path”)的官方外部文件,Node.js都会使用“module.exports = path;”导出公共定义。为什么如果我将“process_common_js_modules”添加到编译器标志中会失败? - Alvaro Fuentes Zurita
官方的externs似乎需要一个自定义的运行器 - 这不是官方repo的一部分。我们现在的nodejs支持还没有达到我们想要的水平。虽然正在努力改进,但这是一个缓慢的过程。如果您在nodejs externs中删除module.exports行,则此处的解决方法应该开始起作用。 - Chad Killingsworth
我很想看到这个答案的一个工作示例,因为它对我来说并不是很清楚。 - Bnaya
这些对我的源代码非常有效,但不幸的是无法解决需要内置模块的npms的问题。 - cpcallen

0

如果你只是使用 Closure Compiler 进行类型检查,即使用 --checks-only 选项,则有另一种解决方法(优于 Chad's answer 中提到的那些),它可以正确地处理未修改的第三方 NPM 模块,而这些模块反过来又导入了内置模块。

使用存根

诀窍是创建存根 NPM 模块来替代内置模块。这些可以很小;它们只需要声明你实际使用的 API 部分。

以下是内置模块 path 的示例。

externs/path/path.js 中,我拥有所需的 path 部分的“外部”声明(实际上不是外部,因此例如无法使用 @nosideeffects):

/** @const */
var path = {};

/**
 * @param {string} path
 * @return {string}
 */
path.dirname = function(path) {};

/**
 * @param {string} path
 * @return {string}
 */
path.extname = function(path) {};

/**
 * @param {...string} var_args
 * @return {string}
 */
path.join = function(var_args) {};

module.exports = path;

在“externs/path/package.json”中,我有一个最小的NPM包配置:
{
  "description": "Fake package.json for require('path')",
  "main": "path.js",
  "name": "path",
}

然后从 node_modules/path 创建一个符号链接到 externs/path,并将以下内容添加到我的编译器标志中:

node_modules/path/package.json
node_modules/path/path.js

你可以直接将存根实现放在node_modules中,但我更喜欢保持我的存根与由npm管理的真正模块分开。我只需要记得手动添加符号链接到我的Git repo中,因为它已经配置为忽略node_modules


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接