diff --git a/src/googmodule.ts b/src/googmodule.ts index bf6e6d91b..c5556a76f 100644 --- a/src/googmodule.ts +++ b/src/googmodule.ts @@ -301,6 +301,88 @@ export function extractModuleMarker( return literalTypeOfSymbol(localSymbol); } +// node -p "require('module').builtinModules" +const NODEJS_BUILTIN_MODULES = new Set([ + '_http_agent', + '_http_client', + '_http_common', + '_http_incoming', + '_http_outgoing', + '_http_server', + '_stream_duplex', + '_stream_passthrough', + '_stream_readable', + '_stream_transform', + '_stream_wrap', + '_stream_writable', + '_tls_common', + '_tls_wrap', + 'assert', + 'assert/strict', + 'async_hooks', + 'buffer', + 'child_process', + 'cluster', + 'console', + 'constants', + 'crypto', + 'dgram', + 'diagnostics_channel', + 'dns', + 'dns/promises', + 'domain', + 'events', + 'fs', + 'fs/promises', + 'http', + 'http2', + 'https', + 'inspector', + 'module', + 'net', + 'os', + 'path', + 'path/posix', + 'path/win32', + 'perf_hooks', + 'process', + 'punycode', + 'querystring', + 'readline', + 'readline/promises', + 'repl', + 'stream', + 'stream/consumers', + 'stream/promises', + 'stream/web', + 'string_decoder', + 'sys', + 'timers', + 'timers/promises', + 'tls', + 'trace_events', + 'tty', + 'url', + 'util', + 'util/types', + 'v8', + 'vm', + 'wasi', + 'worker_threads', + 'zlib', +]); + +// Possible patterns: +// - "fs" (\w+) +// - "node:fs" (node:\w+) +// - "fs/promises" (\w+/\w+) +// - "node:fs/promises" (node:\w+/\w+) +const NODEJS_BUILTIN_PATTERN = /^(?:node:)?(\w+(?:\/\w+)?)$/; +function isNodeJSBuiltin(specifier: string): boolean { + const match = specifier.match(NODEJS_BUILTIN_PATTERN); + return match !== null && NODEJS_BUILTIN_MODULES.has(match[1]); +} + /** Internal TypeScript APIs on ts.Declaration. */ declare interface InternalTsDeclaration { locals?: ts.SymbolTable; @@ -706,6 +788,7 @@ export function commonJsToGoogmoduleTransformer( newIdent: ts.Identifier|undefined): ts.Statement|null { const importedUrl = extractRequire(call); if (!importedUrl) return null; + if (isNodeJSBuiltin(importedUrl.text)) return null; // if importPathToGoogNamespace reports an error, it has already been // reported when originally transforming the file to JS (e.g. to produce // the goog.requireType call). Side-effect imports generate no diff --git a/test/googmodule_test.ts b/test/googmodule_test.ts index a258a7bcf..ded2e1d88 100644 --- a/test/googmodule_test.ts +++ b/test/googmodule_test.ts @@ -501,6 +501,46 @@ describe('convertCommonJsToGoogModule', () => { `)); }); + describe('nodejs builtin imports', () => { + it('converts builtin imports to require (not goog.require)', () => { + const before = ` + import {readFile as a} from 'fs'; + import {readFile as b} from 'fs/promises'; + import {readFile as c} from 'node:fs'; + import {readFile as d} from 'node:fs/promises'; + console.log(a, b, c, d); + `; + + expectCommonJs('a.ts', before, false).toBe(outdent(` + goog.module('a'); + var module = module || { id: 'a.ts' }; + goog.require('tslib'); + const fs_1 = require("fs"); + const promises_1 = require("fs/promises"); + const node_fs_1 = require("node:fs"); + const promises_2 = require("node:fs/promises"); + console.log(fs_1.readFile, promises_1.readFile, node_fs_1.readFile, promises_2.readFile); + `)); + }); + + it('allows a trailing slash as an escape hatch', () => { + const before = ` + import {readFile as a} from 'fs/'; + import {readFile as b} from 'fs/promises/'; + console.log(a, b); + `; + + expectCommonJs('a.ts', before, false).toBe(outdent(` + goog.module('a'); + var module = module || { id: 'a.ts' }; + goog.require('tslib'); + const fs_1 = goog.require('fs'); + const promises_1 = goog.require('fs.promises'); + console.log(fs_1.readFile, promises_1.readFile); + `)); + }); + }); + describe('dynamic import', () => { it('handles dynamic imports', () => { const before = `