google / closure-compiler

A JavaScript checker and optimizer.
https://developers.google.com/closure/compiler/
Apache License 2.0
7.41k stars 1.15k forks source link

Allow ESM imports of externs files #3740

Open ctjlewis opened 3 years ago

ctjlewis commented 3 years ago

Sample source:

/** moduleTest.js */
import fs from 'fs';
console.log(fs);

Command:

google-closure-compiler \
  -O ADVANCED \
  --module_resolution NODE \
  --process_common_js_modules \
  --externs ./node_modules/google-closure-compiler/contrib/nodejs/fs.js \
  moduleTest.js

The compiler should not try to import an extern. Expected output should be something like (leaving import statement intact):

import fs from 'fs';console.log(fs);

Actual output:

moduleTest.js:1: ERROR - [JSC_JS_MODULE_LOAD_WARNING] Failed to load module "fs"
  1| import fs from 'fs';
     ^

1 error(s), 0 warning(s)

I recognize that this is a low-priority issue, but I want to file it anyway so it's documented. It is highly valuable to be able to declare imports as external, especially NodeJS builtins.

Ideally, --jscomp_off moduleLoad --jscomp_off undefinedVars would produce useful output, but it generates:

console.log(default$$module$fs);

Where default$$module$fs is naturally undefined. This is a hacky suggestion, but for -O ADVANCED, it might be appropriate to replace default$$module$fs with require('fs'), such that the generated output is:

console.log(require('fs'));

Which produces the expected output.

lauraharker commented 3 years ago

There are two related aspects of this feature request: 1) make the compiler imports of extern file paths - would be relatively easy 1) preserve those imports in the compiled output - hard because Closure Compiler currently rewrites all imports/exports and bundles all modules into a single script.

@ChadKillingsworth 's work on dynamic imports may partially solve (2).

https://docs.google.com/document/d/1thSbGV5i96jltVwn76KYrPkWoNc-yj_KU3HtBGUCgMc/edit#heading=h.c037si364tjn is the design doc. I can imagine you being able to access extern files via dynamic import, although I haven't read through the proposal in enough detail to know whether that's in scope.

ChadKillingsworth commented 3 years ago

So we first need to support extern files as modules. That in and of itself won't be too hard for ES Modules. For built in modules from node, the hardest part will be figuring out how to indicate the module name in the extern file since it's likely the extern file itself won't live at the proper path.

rconnamacher commented 2 years ago

I just ran into this today as well. I'm using Closure Compiler with (mostly) pure-esm projects, and if we could import extern files and use them for type annotations, that'd save a lot of boilerplate code.

react.ext.js

/** @externs */

const React = {};
export default React;

/**
 * @param ...
 */
React.createElement = function(type, props, children) {};

Component.js

import ReactType from "../extern/react.ext.js";
const React = /** @type {!ReactType} */ (require("react"));
corrideat commented 1 year ago

I need this too for a certain workflow. Idieally, import and export shoud be left alone, although there's a dirty workaround for exports.

I'd be happy to submit a patch if I'm pointed at the right direction.

What I'm looking for is:

import {foo as bar} from 'baz'
const qux = foo('quz')

export default qux

Some other changes may be applied of course, but the point is that the import remains without some black-magic to hide the export into something else. Bonus points for preserving the export at the end.3

The ideal output would be something like:

import {foo as test} from 'bar';
const a = b('qux')

export default baz

This looks line an easy-ish fix, if CC wouldn't be clever about bundling imports / requires.