diff --git a/src/compiler/config/load-config.ts b/src/compiler/config/load-config.ts index 104012df14d..43f49498feb 100644 --- a/src/compiler/config/load-config.ts +++ b/src/compiler/config/load-config.ts @@ -1,9 +1,10 @@ import type { CompilerSystem, Config, Diagnostic, LoadConfigInit, LoadConfigResults } from '../../declarations'; -import { buildError, catchError, isString, normalizePath, hasError } from '@utils'; +import { buildError, catchError, hasError, isString, normalizePath } from '@utils'; import { createLogger } from '../sys/logger/console-logger'; import { createSystem } from '../sys/stencil-sys'; -import { dirname, resolve } from 'path'; +import { dirname } from 'path'; import { IS_NODE_ENV } from '../sys/environment'; +import { nodeRequire } from '../sys/node-require'; import { validateConfig } from './validate-config'; import { validateTsConfig } from '../sys/typescript/typescript-config'; import ts from 'typescript'; @@ -116,32 +117,9 @@ const evaluateConfigFile = async (sys: CompilerSystem, diagnostics: Diagnostic[] try { if (IS_NODE_ENV) { - // ensure we cleared out node's internal require() cache for this file - delete require.cache[resolve(configFilePath)]; - - // let's override node's require for a second - // don't worry, we'll revert this when we're done - require.extensions['.ts'] = (module: NodeModuleWithCompile, filename: string) => { - let sourceText = sys.readFileSync(filename, 'utf8'); - - if (configFilePath.endsWith('.ts')) { - // looks like we've got a typed config file - // let's transpile it to .js quick - sourceText = transpileTypedConfig(diagnostics, sourceText, configFilePath); - } else { - // quick hack to turn a modern es module - // into and old school commonjs module - sourceText = sourceText.replace(/export\s+\w+\s+(\w+)/gm, 'exports.$1'); - } - - module._compile(sourceText, filename); - }; - - // let's do this! - configFileData = require(configFilePath); - - // all set, let's go ahead and reset the require back to the default - require.extensions['.ts'] = undefined; + const results = nodeRequire(configFilePath); + diagnostics.push(...results.diagnostics); + configFileData = results.module; } else { // browser environment, can't use node's require() to evaluate let sourceText = await sys.readFile(configFilePath); @@ -183,7 +161,3 @@ const transpileTypedConfig = (diagnostics: Diagnostic[], sourceText: string, fil return output.outputText; }; - -interface NodeModuleWithCompile extends NodeModule { - _compile(code: string, filename: string): any; -} diff --git a/src/compiler/index.ts b/src/compiler/index.ts index 83e00b5b973..7b2400e0e65 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -15,6 +15,7 @@ export { createWorkerContext } from './worker/worker-thread'; export { createWorkerMessageHandler } from './worker/worker-thread'; export { dependencies } from './sys/dependencies.json'; export { loadConfig } from './config/load-config'; +export { nodeRequire } from './sys/node-require'; export { optimizeCss } from './optimize/optimize-css'; export { optimizeJs } from './optimize/optimize-js'; export { path } from './sys/modules/path'; diff --git a/src/compiler/sys/modules/module.ts b/src/compiler/sys/modules/module.ts index 1a8d4ae6b1d..074619e9d63 100644 --- a/src/compiler/sys/modules/module.ts +++ b/src/compiler/sys/modules/module.ts @@ -1,62 +1,66 @@ +/** + * Node builtin modules as of v14.5.0 + */ +export const NODE_BUILTINS = [ + '_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', + 'async_hooks', + 'buffer', + 'child_process', + 'cluster', + 'console', + 'constants', + 'crypto', + 'dgram', + 'dns', + 'domain', + 'events', + 'fs', + 'fs/promises', + 'http', + 'http2', + 'https', + 'inspector', + 'module', + 'net', + 'os', + 'path', + 'perf_hooks', + 'process', + 'punycode', + 'querystring', + 'readline', + 'repl', + 'stream', + 'string_decoder', + 'sys', + 'timers', + 'tls', + 'trace_events', + 'tty', + 'url', + 'util', + 'v8', + 'vm', + 'worker_threads', + 'zlib', +]; + export default class Module { static get builtinModules() { - // as of node v14.2.0 - return [ - '_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', - 'async_hooks', - 'buffer', - 'child_process', - 'cluster', - 'console', - 'constants', - 'crypto', - 'dgram', - 'dns', - 'domain', - 'events', - 'fs', - 'fs/promises', - 'http', - 'http2', - 'https', - 'inspector', - 'module', - 'net', - 'os', - 'path', - 'perf_hooks', - 'process', - 'punycode', - 'querystring', - 'readline', - 'repl', - 'stream', - 'string_decoder', - 'sys', - 'timers', - 'tls', - 'trace_events', - 'tty', - 'url', - 'util', - 'v8', - 'vm', - 'worker_threads', - 'zlib', - ]; + return NODE_BUILTINS; } } diff --git a/src/compiler/sys/node-require.ts b/src/compiler/sys/node-require.ts new file mode 100644 index 00000000000..9fd4df8bd00 --- /dev/null +++ b/src/compiler/sys/node-require.ts @@ -0,0 +1,72 @@ +import type { Diagnostic } from '../../declarations'; +import { IS_NODE_ENV } from './environment'; +import { loadTypeScriptDiagnostic, catchError } from '@utils'; +import ts from 'typescript'; + +export const nodeRequire = (id: string) => { + const results = { + module: undefined as any, + id, + diagnostics: [] as Diagnostic[], + }; + + if (IS_NODE_ENV) { + try { + const fs: typeof import('fs') = require('fs'); + const path: typeof import('path') = require('path'); + + results.id = path.resolve(id); + + // ensure we cleared out node's internal require() cache for this file + delete require.cache[results.id]; + + // let's override node's require for a second + // don't worry, we'll revert this when we're done + require.extensions['.ts'] = (module: NodeModuleWithCompile, fileName: string) => { + let sourceText = fs.readFileSync(fileName, 'utf8'); + + if (fileName.endsWith('.ts')) { + // looks like we've got a typed config file + // let's transpile it to .js quick + const tsResults = ts.transpileModule(sourceText, { + fileName, + compilerOptions: { + module: ts.ModuleKind.CommonJS, + moduleResolution: ts.ModuleResolutionKind.NodeJs, + esModuleInterop: true, + target: ts.ScriptTarget.ES2017, + allowJs: true, + }, + }); + sourceText = tsResults.outputText; + + results.diagnostics.push(...tsResults.diagnostics.map(loadTypeScriptDiagnostic)); + } else { + // quick hack to turn a modern es module + // into and old school commonjs module + sourceText = sourceText.replace(/export\s+\w+\s+(\w+)/gm, 'exports.$1'); + } + + try { + module._compile(sourceText, fileName); + } catch (e) { + catchError(results.diagnostics, e); + } + }; + + // let's do this! + results.module = require(results.id); + + // all set, let's go ahead and reset the require back to the default + require.extensions['.ts'] = undefined; + } catch (e) { + catchError(results.diagnostics, e); + } + } + + return results; +}; + +interface NodeModuleWithCompile extends NodeModule { + _compile(code: string, filename: string): any; +} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 00955ad2f6d..e926ea32a3e 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -83,7 +83,7 @@ export const pluck = (obj: { [key: string]: any }, keys: string[]) => { export const isBoolean = (v: any): v is boolean => typeof v === 'boolean'; export const isDefined = (v: any) => v !== null && v !== undefined; export const isUndefined = (v: any) => v === null || v === undefined; -export const isFunction = (v: any): v is boolean => typeof v === 'function'; +export const isFunction = (v: any): v is Function => typeof v === 'function'; export const isNumber = (v: any): v is boolean => typeof v === 'number'; export const isObject = (val: Object) => val != null && typeof val === 'object' && Array.isArray(val) === false; export const isString = (v: any): v is string => typeof v === 'string';