diff --git a/scripts/bundles/compiler.ts b/scripts/bundles/compiler.ts index 50286eb4de0..040ed7e3edf 100644 --- a/scripts/bundles/compiler.ts +++ b/scripts/bundles/compiler.ts @@ -14,6 +14,7 @@ import { sysModulesPlugin } from './plugins/sys-modules-plugin'; import { writePkgJson } from '../utils/write-pkg-json'; import { BuildOptions } from '../utils/options'; import { RollupOptions, OutputChunk } from 'rollup'; +import { typescriptSourcePlugin } from './plugins/typescript-source-plugin'; import terser from 'terser'; export async function compiler(opts: BuildOptions) { @@ -46,17 +47,23 @@ export async function compiler(opts: BuildOptions) { intro: cjsIntro, outro: cjsOutro, strict: false, - banner: getBanner(opts, 'Stencil Compiler', true), + banner: getBanner(opts, `Stencil Compiler`, true), esModule: false, preferConst: true, + freeze: false, + sourcemap: false, }, plugins: [ + typescriptSourcePlugin(opts), { name: 'compilerMockDocResolvePlugin', resolveId(id) { if (id === '@stencil/core/mock-doc') { return join(opts.buildDir, 'mock-doc', 'index.js'); } + if (id === '@microsoft/typescript-etw' || id === 'inspector') { + return id; + } return null; }, }, @@ -68,7 +75,7 @@ export async function compiler(opts: BuildOptions) { } }, load(id) { - if (id === 'fsevents') { + if (id === 'fsevents' || id === '@microsoft/typescript-etw' || id === 'inspector') { return ''; } if (id === rollupWatchPath) { @@ -107,7 +114,7 @@ export async function compiler(opts: BuildOptions) { if (opts.isProd) { const compilerFilename = Object.keys(bundleFiles).find(f => f.includes('stencil')); const compilerBundle = bundleFiles[compilerFilename] as OutputChunk; - const minified = minifyStencilCompiler(compilerBundle.code); + const minified = minifyStencilCompiler(compilerBundle.code, opts); await fs.writeFile(join(opts.output.compilerDir, compilerFilename.replace('.js', '.min.js')), minified); } }, @@ -118,28 +125,47 @@ export async function compiler(opts: BuildOptions) { propertyReadSideEffects: false, unknownGlobalSideEffects: false, }, + onwarn(warning) { + if (warning.code === `THIS_IS_UNDEFINED`) { + return; + } + console.warn(warning.message || warning); + }, }; + // copy typescript default lib dts files + const dtsFiles = (await fs.readdir(opts.typescriptLibDir)).filter(f => { + return f.startsWith('lib.') && f.endsWith('.d.ts'); + }); + + await Promise.all(dtsFiles.map(f => fs.copy(join(opts.typescriptLibDir, f), join(opts.output.compilerDir, f)))); + return [compilerBundle]; } -function minifyStencilCompiler(code: string) { - const opts: terser.MinifyOptions = { - ecma: 2017, +function minifyStencilCompiler(code: string, opts: BuildOptions) { + const minifyOpts: terser.MinifyOptions = { + ecma: 2018, compress: { + ecma: 2018, passes: 2, - ecma: 2017, + side_effects: false, + unsafe_arrows: true, + unsafe_methods: true, }, output: { - ecma: 2017, + ecma: 2018, + comments: false, }, }; - const minifyResults = terser.minify(code, opts); + const results = terser.minify(code, minifyOpts); - if (minifyResults.error) { - throw minifyResults.error; + if (results.error) { + throw results.error; } - return minifyResults.code; + code = getBanner(opts, `Stencil Compiler`, true) + '\n' + results.code; + + return code; } diff --git a/scripts/bundles/helpers/compiler-cjs-intro.js b/scripts/bundles/helpers/compiler-cjs-intro.js index 4205fe3f563..40df2dee7e0 100644 --- a/scripts/bundles/helpers/compiler-cjs-intro.js +++ b/scripts/bundles/helpers/compiler-cjs-intro.js @@ -32,4 +32,5 @@ if (!process.platform) { } if (!process.version) { process.version = 'v12.0.0'; -} \ No newline at end of file +} +process.browser = !!globalThis.location; diff --git a/scripts/bundles/plugins/sys-modules-plugin.ts b/scripts/bundles/plugins/sys-modules-plugin.ts index 23467b894e0..8bfc505f2d1 100644 --- a/scripts/bundles/plugins/sys-modules-plugin.ts +++ b/scripts/bundles/plugins/sys-modules-plugin.ts @@ -1,7 +1,7 @@ import path from 'path'; import { Plugin } from 'rollup'; -const modules = new Set(['crypto', 'events', 'fs', 'module', 'os', 'path', 'stream', 'typescript', 'url', 'util']); +const modules = new Set(['crypto', 'events', 'fs', 'module', 'os', 'path', 'stream', 'url', 'util']); export function sysModulesPlugin(inputDir: string): Plugin { return { diff --git a/scripts/bundles/plugins/typescript-source-plugin.ts b/scripts/bundles/plugins/typescript-source-plugin.ts new file mode 100644 index 00000000000..b8063152e67 --- /dev/null +++ b/scripts/bundles/plugins/typescript-source-plugin.ts @@ -0,0 +1,105 @@ +import fs from 'fs-extra'; +import { Plugin } from 'rollup'; +import { join } from 'path'; +import { BuildOptions } from '../../utils/options'; +import terser from 'terser'; + +export function typescriptSourcePlugin(opts: BuildOptions): Plugin { + const tsPath = require.resolve('typescript'); + return { + name: 'typescriptSourcePlugin', + resolveId(id) { + if (id === 'typescript') { + return tsPath; + } + return null; + }, + load(id) { + if (id === tsPath) { + return bundleTypeScriptSource(tsPath, opts); + } + return null; + }, + }; +} + +async function bundleTypeScriptSource(tsPath: string, opts: BuildOptions) { + const fileName = `typescript-${opts.typescriptVersion.replace(/\./g, '_')}-bundle-cache${opts.isProd ? '.min' : ''}.js`; + const cacheFile = join(opts.buildDir, fileName); + + try { + // check if we've already cached this bundle + return await fs.readFile(cacheFile, 'utf8'); + } catch (e) {} + + // get the source typescript.js file to modify + let code = await fs.readFile(tsPath, 'utf8'); + + // remove the default ts.getDefaultLibFilePath because it uses some + // node apis and we'll be replacing it withour own anyways and + code = removeFromSource(code, `ts.getDefaultLibFilePath = getDefaultLibFilePath;`); + + // remove the CPUProfiler since it uses node apis + code = removeFromSource(code, `enableCPUProfiler: enableCPUProfiler,`); + code = removeFromSource(code, `disableCPUProfiler: disableCPUProfiler,`); + + // trim off the last part that sets module.exports and polyfills globalThis since + // we don't want typescript to add itself to module.exports when in a node env + const tsEnding = `})(ts || (ts = {}));`; + if (!code.includes(tsEnding)) { + throw new Error(`"${tsEnding}" not found`); + } + const lastEnding = code.lastIndexOf(tsEnding); + code = code.substr(0, lastEnding + tsEnding.length); + + // there's a billion unnecessary "var ts;" for namespaces + // but we'll be using the top level "const ts" instead + code = code.replace(/var ts;/g, ''); + + // minification is crazy better if it doesn't use typescript's + // namespace closures, like (function(ts) {...})(ts = ts || {}); + code = code.replace(/ \|\| \(ts \= \{\}\)/g, ''); + + // make a nice clean default export + // "process.browser" is used by typescript to know if it should use the node sys or not + // this ensures its using our checks. Deno should also use process.browser = true + // because we don't want deno using the node apis + const o: string[] = []; + o.push(`import { IS_NODE_ENV } from '@utils';`); + o.push(`process.browser = !IS_NODE_ENV;`); + o.push(`const ts = {};`); + o.push(code); + o.push(`export default ts;`); + code = o.join('\n'); + + if (opts.isProd) { + const minified = terser.minify(code, { + ecma: 2018, + module: true, + compress: { + ecma: 2018, + passes: 2, + }, + output: { + ecma: 2018, + comments: false, + }, + }); + + if (minified.error) { + throw minified.error; + } + code = minified.code; + } + + await fs.writeFile(cacheFile, code); + + return code; +} + +function removeFromSource(srcCode: string, removeCode: string) { + if (!srcCode.includes(removeCode)) { + throw new Error(`"${removeCode}" not found`); + } + return srcCode.replace(removeCode, `/* commented out: ${removeCode} */`); +} diff --git a/scripts/utils/options.ts b/scripts/utils/options.ts index 93cbd033a35..f405e69b3fe 100644 --- a/scripts/utils/options.ts +++ b/scripts/utils/options.ts @@ -9,6 +9,8 @@ export function getOptions(rootDir: string, inputOpts: BuildOptions = {}) { const packageLockJsonPath = join(rootDir, 'package-lock.json'); const changelogPath = join(rootDir, 'CHANGELOG.md'); const nodeModulesDir = join(rootDir, 'node_modules'); + const typescriptDir = join(nodeModulesDir, 'typescript'); + const typescriptLibDir = join(typescriptDir, 'lib'); const buildDir = join(rootDir, 'build'); const scriptsDir = join(rootDir, 'scripts'); const scriptsBundlesDir = join(scriptsDir, 'bundles'); @@ -23,6 +25,8 @@ export function getOptions(rootDir: string, inputOpts: BuildOptions = {}) { packageLockJsonPath, changelogPath, nodeModulesDir, + typescriptDir, + typescriptLibDir, buildDir, scriptsDir, scriptsBundlesDir, @@ -78,7 +82,7 @@ export function getOptions(rootDir: string, inputOpts: BuildOptions = {}) { export function createReplaceData(opts: BuildOptions) { const CACHE_BUSTER = 6; - const typescriptPkg = require(join(opts.nodeModulesDir, 'typescript', 'package.json')); + const typescriptPkg = require(join(opts.typescriptDir, 'package.json')); opts.typescriptVersion = typescriptPkg.version; const transpileId = typescriptPkg.name + typescriptPkg.version + '_' + CACHE_BUSTER; @@ -95,6 +99,9 @@ export function createReplaceData(opts: BuildOptions) { const optimizeCssId = autoprefixerPkg.name + autoprefixerPkg.version + '_' + postcssPkg.name + postcssPkg.version + '_' + CACHE_BUSTER; + const parse5Pkg = require(join(opts.nodeModulesDir, 'parse5', 'package.json')); + opts.parse5Verion = parse5Pkg.version; + const data = readJSONSync(join(opts.srcDir, 'compiler', 'sys', 'dependencies.json')); data.dependencies[0].version = opts.version; data.dependencies[1].version = typescriptPkg.version; @@ -124,6 +131,8 @@ export interface BuildOptions { rootDir?: string; srcDir?: string; nodeModulesDir?: string; + typescriptDir?: string; + typescriptLibDir?: string; buildDir?: string; scriptsDir?: string; scriptsBundlesDir?: string; @@ -154,6 +163,7 @@ export interface BuildOptions { tag?: string; typescriptVersion?: string; rollupVersion?: string; + parse5Verion?: string; terserVersion?: string; } diff --git a/src/cli/run.ts b/src/cli/run.ts index 468a767c294..40157e102f5 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -74,7 +74,6 @@ export const run = async (init: CliInitOptions) => { configPath: findConfigResults.configPath, logger, sys, - typescriptPath: ensureDepsResults.typescriptPath, }); if (validated.diagnostics.length > 0) { diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index dd5a0b8b299..c60e0637edb 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -7,8 +7,10 @@ import { createSysWorker } from './sys/worker/sys-worker'; import { createWatchBuild } from './build/watch-build'; import { getConfig } from './sys/config'; import { patchFs } from './sys/fs-patch'; -import { patchTypescript } from './sys/typescript/typescript-patch'; +import { patchTypescript } from './sys/typescript/typescript-sys'; import { resolveModuleIdAsync } from './sys/resolve/resolve-module-async'; +import { isFunction } from '@utils'; +import ts from 'typescript'; export const createCompiler = async (config: Config) => { // actual compiler code @@ -19,6 +21,10 @@ export const createCompiler = async (config: Config) => { const sys = config.sys; const compilerCtx = new CompilerContext(); + if (isFunction(config.sys.setupCompiler)) { + config.sys.setupCompiler({ ts }); + } + patchFs(sys); compilerCtx.fs = createInMemoryFs(sys); @@ -32,7 +38,7 @@ export const createCompiler = async (config: Config) => { // Pipe events from sys.events to compilerCtx sys.events.on(compilerCtx.events.emit); } - await patchTypescript(config, diagnostics, compilerCtx.fs); + patchTypescript(config, compilerCtx.fs); const build = () => createFullBuild(config, compilerCtx); diff --git a/src/compiler/config/load-config.ts b/src/compiler/config/load-config.ts index 9ccbeb5bd8f..c8539124d78 100644 --- a/src/compiler/config/load-config.ts +++ b/src/compiler/config/load-config.ts @@ -3,10 +3,9 @@ import { buildError, catchError, isString, normalizePath, hasError, IS_NODE_ENV import { createLogger } from '../sys/logger/console-logger'; import { createSystem } from '../sys/stencil-sys'; import { dirname, resolve } from 'path'; -import { loadTypescript } from '../sys/typescript/typescript-load'; import { validateConfig } from './validate-config'; import { validateTsConfig } from '../sys/typescript/typescript-config'; -import type TypeScript from 'typescript'; +import ts from 'typescript'; export const loadConfig = async (init: LoadConfigInit = {}) => { const results: LoadConfigResults = { @@ -27,9 +26,7 @@ export const loadConfig = async (init: LoadConfigInit = {}) => { const config = init.config || {}; let configPath = init.configPath || config.configPath; - const loadedTs = await loadTypescript(sys, init.typescriptPath, false); - - const loadedConfigFile = await loadConfigFile(loadedTs, sys, results.diagnostics, configPath); + const loadedConfigFile = await loadConfigFile(sys, results.diagnostics, configPath); if (hasError(results.diagnostics)) { return results; } @@ -70,7 +67,7 @@ export const loadConfig = async (init: LoadConfigInit = {}) => { results.config.logger.setLevel(results.config.logLevel); if (!hasError(results.diagnostics)) { - const tsConfigResults = await validateTsConfig(loadedTs, results.config, sys, init); + const tsConfigResults = await validateTsConfig(results.config, sys, init); results.diagnostics.push(...tsConfigResults.diagnostics); results.config.tsconfig = tsConfigResults.path; @@ -83,10 +80,6 @@ export const loadConfig = async (init: LoadConfigInit = {}) => { results.tsconfig.exclude = tsConfigResults.exclude; results.tsconfig.extends = tsConfigResults.extends; } - - if (isString(init.typescriptPath)) { - results.config.typescriptPath = init.typescriptPath; - } } catch (e) { catchError(results.diagnostics, e); } @@ -94,12 +87,12 @@ export const loadConfig = async (init: LoadConfigInit = {}) => { return results; }; -const loadConfigFile = async (loadedTs: typeof TypeScript, sys: CompilerSystem, diagnostics: Diagnostic[], configPath: string) => { +const loadConfigFile = async (sys: CompilerSystem, diagnostics: Diagnostic[], configPath: string) => { let config: Config = null; if (isString(configPath)) { // the passed in config was a string, so it's probably a path to the config we need to load - const configFileData = await evaluateConfigFile(loadedTs, sys, diagnostics, configPath); + const configFileData = await evaluateConfigFile(sys, diagnostics, configPath); if (hasError(diagnostics)) { return config; } @@ -117,7 +110,7 @@ const loadConfigFile = async (loadedTs: typeof TypeScript, sys: CompilerSystem, return config; }; -const evaluateConfigFile = async (loadedTs: typeof TypeScript, sys: CompilerSystem, diagnostics: Diagnostic[], configFilePath: string) => { +const evaluateConfigFile = async (sys: CompilerSystem, diagnostics: Diagnostic[], configFilePath: string) => { let configFileData: { config?: Config } = null; try { @@ -133,7 +126,7 @@ const evaluateConfigFile = async (loadedTs: typeof TypeScript, sys: CompilerSyst if (configFilePath.endsWith('.ts')) { // looks like we've got a typed config file // let's transpile it to .js quick - sourceText = transpileTypedConfig(loadedTs, diagnostics, sourceText, configFilePath); + sourceText = transpileTypedConfig(diagnostics, sourceText, configFilePath); } else { // quick hack to turn a modern es module // into and old school commonjs module @@ -151,7 +144,7 @@ const evaluateConfigFile = async (loadedTs: typeof TypeScript, sys: CompilerSyst } else { // browser environment, can't use node's require() to evaluate let sourceText = await sys.readFile(configFilePath); - sourceText = transpileTypedConfig(loadedTs, diagnostics, sourceText, configFilePath); + sourceText = transpileTypedConfig(diagnostics, sourceText, configFilePath); if (hasError(diagnostics)) { return configFileData; } @@ -166,26 +159,26 @@ const evaluateConfigFile = async (loadedTs: typeof TypeScript, sys: CompilerSyst return configFileData; }; -const transpileTypedConfig = (loadedTs: typeof TypeScript, diagnostics: Diagnostic[], sourceText: string, filePath: string) => { +const transpileTypedConfig = (diagnostics: Diagnostic[], sourceText: string, filePath: string) => { // let's transpile an awesome stencil.config.ts file into // a boring stencil.config.js file if (hasError(diagnostics)) { return sourceText; } - const opts: TypeScript.TranspileOptions = { + const opts: ts.TranspileOptions = { fileName: filePath, compilerOptions: { - module: loadedTs.ModuleKind.CommonJS, - moduleResolution: loadedTs.ModuleResolutionKind.NodeJs, + module: ts.ModuleKind.CommonJS, + moduleResolution: ts.ModuleResolutionKind.NodeJs, esModuleInterop: true, - target: loadedTs.ScriptTarget.ES2015, + target: ts.ScriptTarget.ES2015, allowJs: true, }, reportDiagnostics: false, }; - const output = loadedTs.transpileModule(sourceText, opts); + const output = ts.transpileModule(sourceText, opts); return output.outputText; }; diff --git a/src/compiler/config/transpile-options.ts b/src/compiler/config/transpile-options.ts index 9b45ab48a07..4b77aefbbca 100644 --- a/src/compiler/config/transpile-options.ts +++ b/src/compiler/config/transpile-options.ts @@ -35,7 +35,7 @@ export const getTranspileConfig = (input: TranspileOptions) => { transpileCtx.sys = input.sys; } else if (!transpileCtx.sys) { if (IS_NODE_ENV) { - transpileCtx.sys = requireFunc('../sys/node/index.js').createNodeSysNoWatch(); + transpileCtx.sys = requireFunc('../sys/node/index.js').createNodeSys(); } else if (IS_DENO_ENV) { throw new Error(`"sys" must be provided in options`); } else { diff --git a/src/compiler/index.ts b/src/compiler/index.ts index df2ee3701fa..119424cac03 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -2,6 +2,7 @@ import { IS_WEB_WORKER_ENV } from '@utils'; import { createSystem } from './sys/stencil-sys'; import { createWorkerMessageHandler } from './worker/worker-thread'; import { initWebWorkerThread } from './sys/worker/web-worker-thread'; +import ts from 'typescript'; if (IS_WEB_WORKER_ENV) { initWebWorkerThread(createWorkerMessageHandler(createSystem())); @@ -19,3 +20,4 @@ export { optimizeCss } from './optimize/optimize-css'; export { optimizeJs } from './optimize/optimize-js'; export { path } from './sys/modules/path'; export { version, versions, vermoji, buildId } from '../version'; +export { ts }; diff --git a/src/compiler/optimize/optimize-module.ts b/src/compiler/optimize/optimize-module.ts index 6b8841c16f0..55e2fc718c2 100644 --- a/src/compiler/optimize/optimize-module.ts +++ b/src/compiler/optimize/optimize-module.ts @@ -1,8 +1,8 @@ -import { loadTypescript } from '../sys/typescript/typescript-load'; import { minfyJsId } from '../../version'; import { minifyJs } from './minify-js'; -import type { CompilerCtx, CompilerSystem, Config, Diagnostic, SourceTarget } from '../../declarations'; +import type { CompilerCtx, Config, Diagnostic, SourceTarget } from '../../declarations'; import type { CompressOptions, MangleOptions, MinifyOptions } from 'terser'; +import ts from 'typescript'; interface OptimizeModuleOptions { input: string; @@ -59,7 +59,7 @@ export const optimizeModule = async (config: Config, compilerCtx: CompilerCtx, o } const shouldTranspile = opts.sourceTarget === 'es5'; - const results = await compilerCtx.worker.prepareModule(config.typescriptPath, opts.input, minifyOpts, shouldTranspile, opts.inlineHelpers); + const results = await compilerCtx.worker.prepareModule(opts.input, minifyOpts, shouldTranspile, opts.inlineHelpers); if (results != null && typeof results.output === 'string' && results.diagnostics.length === 0 && compilerCtx != null) { if (opts.isCore) { results.output = results.output.replace(/disconnectedCallback\(\)\{\},/g, ''); @@ -116,14 +116,13 @@ export const getTerserOptions = (config: Config, sourceTarget: SourceTarget, pre return opts; }; -export const prepareModule = async (sys: CompilerSystem, typescriptPath: string, input: string, minifyOpts: MinifyOptions, transpileToEs5: boolean, inlineHelpers: boolean) => { +export const prepareModule = async (input: string, minifyOpts: MinifyOptions, transpileToEs5: boolean, inlineHelpers: boolean) => { const results = { output: input, diagnostics: [] as Diagnostic[], }; if (transpileToEs5) { - const ts = await loadTypescript(sys, typescriptPath, false); const tsResults = ts.transpileModule(input, { fileName: 'module.ts', compilerOptions: { diff --git a/src/compiler/sys/modules/index.ts b/src/compiler/sys/modules/index.ts index e4848aaba46..da19246cd7f 100644 --- a/src/compiler/sys/modules/index.ts +++ b/src/compiler/sys/modules/index.ts @@ -8,6 +8,5 @@ export * from './module'; export * from './os'; export * from './path'; export * from './stream'; -export * from './typescript'; export * from './url'; export * from './util'; diff --git a/src/compiler/sys/modules/typescript.ts b/src/compiler/sys/modules/typescript.ts deleted file mode 100644 index 07b82a571ed..00000000000 --- a/src/compiler/sys/modules/typescript.ts +++ /dev/null @@ -1,2 +0,0 @@ -// this "ts" object is patched by the dynamically loaded typescript -export default {}; diff --git a/src/compiler/sys/typescript/typescript-config.ts b/src/compiler/sys/typescript/typescript-config.ts index 4226de72efc..1319738cd3d 100644 --- a/src/compiler/sys/typescript/typescript-config.ts +++ b/src/compiler/sys/typescript/typescript-config.ts @@ -1,9 +1,9 @@ import * as d from '../../../declarations'; import { buildError, buildWarn, catchError, isString, loadTypeScriptDiagnostic, normalizePath } from '@utils'; import { isAbsolute, join, relative } from 'path'; -import type TypeScript from 'typescript'; +import ts from 'typescript'; -export const validateTsConfig = async (ts: typeof TypeScript, config: d.Config, sys: d.CompilerSystem, init: d.LoadConfigInit) => { +export const validateTsConfig = async (config: d.Config, sys: d.CompilerSystem, init: d.LoadConfigInit) => { const tsconfig = { path: null as string, compilerOptions: null as any, @@ -22,7 +22,7 @@ export const validateTsConfig = async (ts: typeof TypeScript, config: d.Config, diagnostic.messageText = `Unable to load TypeScript config file. Please create a "tsconfig.json" file within the "${config.rootDir}" directory.`; } else { tsconfig.path = readTsConfig.path; - const host: TypeScript.ParseConfigFileHost = { + const host: ts.ParseConfigFileHost = { ...ts.sys, readFile: p => { if (p === tsconfig.path) { diff --git a/src/compiler/sys/typescript/typescript-load.ts b/src/compiler/sys/typescript/typescript-load.ts deleted file mode 100644 index 067b205db96..00000000000 --- a/src/compiler/sys/typescript/typescript-load.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type * as d from '../../../declarations'; -import { denoLoadTypeScript } from '../../../sys/deno/deno-load-typescript'; -import { dependencies } from '../dependencies.json'; -import { isFunction, IS_NODE_ENV, IS_BROWSER_ENV, IS_WEB_WORKER_ENV, IS_DENO_ENV } from '@utils'; -import { nodeLoadTypeScript } from '../../../sys/node/node-load-typescript'; -import { patchRemoteTsSys } from './typescript-patch'; -import ts from 'typescript'; - -const importedTs: ImportTypeScriptModule = { - p: null, -}; - -export const loadTypescript = (sys: d.CompilerSystem, typescriptPath: string, sync: boolean): typeof ts | Promise => { - // try sync load typescript methods first - if (exports.ts) { - // already loaded - return exports.ts; - } - - // check if the global object has "ts" on it - // could be browser main thread, browser web worker, or nodejs global - const globalThisTs = getLoadedTs((globalThis as any).ts); - if (globalThisTs) { - return globalThisTs; - } - - if (IS_NODE_ENV) { - const nodeTs = getLoadedTs(nodeLoadTypeScript(typescriptPath)); - if (nodeTs) { - return nodeTs; - } - } - - const tsUrl = getTsUrl(sys, typescriptPath); - - if (IS_WEB_WORKER_ENV) { - const webWorkerTs = getLoadedTs(webWorkerLoadTypeScript(tsUrl)); - if (webWorkerTs) { - patchRemoteTsSys(webWorkerTs, tsUrl); - return webWorkerTs; - } - } - - if (sync) { - throw new Error(`TypeScript "ts" must already be available on the global scope`); - } - - // async at this point - if (!importedTs.p) { - if (IS_DENO_ENV) { - importedTs.p = denoLoadTypeScript(sys, typescriptPath); - } else if (IS_BROWSER_ENV) { - importedTs.p = browserMainLoadTypeScript(tsUrl); - } else { - throw new Error(`Unable to load TypeScript`); - } - } - - return importedTs.p; -}; - -const webWorkerLoadTypeScript = (tsUrl: string) => { - // browser web worker - // doing this before the globalThis check cuz we'd - // rather ensure we're using a valid typescript version - // importScripts() will be synchronous within a web worker - (self as any).importScripts(tsUrl); - return (self as any).ts; -}; - -const browserMainLoadTypeScript = (tsUrl: string): any => - // browser main thread - new Promise((resolve, reject) => { - const scriptElm = document.createElement('script'); - scriptElm.onload = () => { - const browserTs = getLoadedTs((globalThis as any).ts); - if (browserTs) { - patchRemoteTsSys(browserTs, tsUrl); - resolve(browserTs); - } else { - reject(`Unable to load TypeScript via browser script`); - } - }; - scriptElm.onerror = ev => reject(ev); - scriptElm.src = tsUrl; - document.head.appendChild(scriptElm); - }); - -const getTsUrl = (sys: d.CompilerSystem, typeScriptPath: string) => { - if (typeScriptPath) { - return typeScriptPath; - } - const typecriptDep = dependencies.find(dep => dep.name === 'typescript'); - return sys.getRemoteModuleUrl({ moduleId: typecriptDep.name, version: typecriptDep.version, path: typecriptDep.main }); -}; - -const getLoadedTs = (loadedTs: typeof ts) => { - if (loadedTs != null && isFunction(loadedTs.transpileModule)) { - Object.assign(ts, loadedTs); - return (exports.ts = loadedTs); - } - return null; -}; - -interface ImportTypeScriptModule { - p: Promise; -} - -declare const exports: any; diff --git a/src/compiler/sys/typescript/typescript-patch.ts b/src/compiler/sys/typescript/typescript-patch.ts deleted file mode 100644 index 6a0404e10a8..00000000000 --- a/src/compiler/sys/typescript/typescript-patch.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type * as d from '../../../declarations'; -import { exit, getCurrentDirectory, hasError, isBoolean, noop } from '@utils'; -import { loadTypescript } from './typescript-load'; -import { patchTypeScriptResolveModule } from './typescript-resolve-module'; -import { patchTypeScriptSys, patchTypeScriptGetParsedCommandLineOfConfigFile } from './typescript-sys'; -import { resolve } from 'path'; -import ts from 'typescript'; - -export const patchTypescript = async (config: d.Config, diagnostics: d.Diagnostic[], inMemoryFs: d.InMemoryFileSystem) => { - // dynamically load the typescript dependency - const loadedTs = await loadTypescript(config.sys, config.typescriptPath, false); - patchTypescriptModule(config, diagnostics, inMemoryFs, loadedTs); -}; - -export const patchTypescriptSync = (config: d.Config, diagnostics: d.Diagnostic[], inMemoryFs: d.InMemoryFileSystem) => { - const loadedTs = loadTypescript(config.sys, config.typescriptPath, true) as typeof ts; - patchTypescriptModule(config, diagnostics, inMemoryFs, loadedTs); -}; - -const patchTypescriptModule = async (config: d.Config, diagnostics: d.Diagnostic[], inMemoryFs: d.InMemoryFileSystem, loadedTs: typeof ts) => { - if (loadedTs && !hasError(diagnostics)) { - // override some properties on the original imported ts object - patchTypeScriptSys(loadedTs, config, inMemoryFs); - patchTypeScriptResolveModule(loadedTs, config, inMemoryFs); - patchTypeScriptGetParsedCommandLineOfConfigFile(loadedTs); - - // the ts object you see imported here is actually a bogus {} object right now - // so assign the loaded ts object to our project's imported "ts" object - // our "ts" object is the one the rest of the compiler imports and uses - Object.assign(ts, loadedTs); - } -}; - -export const patchRemoteTsSys = (loadedTs: typeof ts, tsUrl: string) => { - // patches just the bare minimum - const tsSys: ts.System = (loadedTs.sys = loadedTs.sys || ({} as any)); - tsSys.getExecutingFilePath = () => tsUrl; - - if (!tsSys.getCurrentDirectory) { - tsSys.getCurrentDirectory = getCurrentDirectory; - } - if (!tsSys.exit) { - tsSys.exit = exit; - } - if (!tsSys.args) { - tsSys.args = []; - } - if (!tsSys.newLine) { - tsSys.newLine = '\n'; - } - if (!isBoolean(tsSys.useCaseSensitiveFileNames)) { - tsSys.useCaseSensitiveFileNames = false; - } - if (!tsSys.resolvePath) { - tsSys.resolvePath = resolve; - } - if (!tsSys.write) { - tsSys.write = noop; - } -}; diff --git a/src/compiler/sys/typescript/typescript-resolve-module.ts b/src/compiler/sys/typescript/typescript-resolve-module.ts index a22023ddb72..c9d6b359b8e 100644 --- a/src/compiler/sys/typescript/typescript-resolve-module.ts +++ b/src/compiler/sys/typescript/typescript-resolve-module.ts @@ -1,13 +1,13 @@ import * as d from '../../../declarations'; import { basename, dirname, isAbsolute, join, resolve } from 'path'; import { isDtsFile, isJsFile, isJsxFile, isLocalModule, isStencilCoreImport, isTsxFile, isTsFile, isJsonFile } from '../resolve/resolve-utils'; -import { isRemoteUrl, isString, IS_BROWSER_ENV, IS_NODE_ENV, IS_WEB_WORKER_ENV, normalizePath } from '@utils'; +import { isRemoteUrl, isString, IS_BROWSER_ENV, IS_NODE_ENV, normalizePath } from '@utils'; import { patchTsSystemFileSystem } from './typescript-sys'; import { resolveRemoteModuleIdSync } from '../resolve/resolve-module-sync'; import { version } from '../../../version'; import ts from 'typescript'; -export const patchTypeScriptResolveModule = (loadedTs: typeof ts, config: d.Config, inMemoryFs: d.InMemoryFileSystem) => { +export const patchTypeScriptResolveModule = (config: d.Config, inMemoryFs: d.InMemoryFileSystem) => { let compilerExe: string; if (config.sys) { compilerExe = config.sys.getCompilerExecutingPath(); @@ -16,9 +16,9 @@ export const patchTypeScriptResolveModule = (loadedTs: typeof ts, config: d.Conf } if (shouldPatchRemoteTypeScript(compilerExe)) { - const resolveModuleName = ((loadedTs as any).__resolveModuleName = loadedTs.resolveModuleName); + const resolveModuleName = ((ts as any).__resolveModuleName = ts.resolveModuleName); - loadedTs.resolveModuleName = (moduleName, containingFile, compilerOptions, host, cache, redirectedReference) => { + ts.resolveModuleName = (moduleName, containingFile, compilerOptions, host, cache, redirectedReference) => { const resolvedModule = patchedTsResolveModule(config, inMemoryFs, moduleName, containingFile); if (resolvedModule) { return resolvedModule; @@ -32,7 +32,7 @@ export const tsResolveModuleName = (config: d.Config, compilerCtx: d.CompilerCtx const resolveModuleName: typeof ts.resolveModuleName = (ts as any).__resolveModuleName || ts.resolveModuleName; if (moduleName && resolveModuleName && config.tsCompilerOptions) { - const host: ts.ModuleResolutionHost = patchTsSystemFileSystem(config, config.sys, compilerCtx.fs, ts, {} as any); + const host: ts.ModuleResolutionHost = patchTsSystemFileSystem(config, config.sys, compilerCtx.fs, ts.sys); const compilerOptions: ts.CompilerOptions = { ...config.tsCompilerOptions }; compilerOptions.resolveJsonModule = true; @@ -184,4 +184,4 @@ const getTsResolveExtension = (p: string) => { return ts.Extension.Ts; }; -const shouldPatchRemoteTypeScript = (compilerExe: string) => !IS_NODE_ENV && IS_WEB_WORKER_ENV && isRemoteUrl(compilerExe); +const shouldPatchRemoteTypeScript = (compilerExe: string) => !IS_NODE_ENV && isRemoteUrl(compilerExe); diff --git a/src/compiler/sys/typescript/typescript-sys.ts b/src/compiler/sys/typescript/typescript-sys.ts index 2b77f88dda7..fc1b6367277 100644 --- a/src/compiler/sys/typescript/typescript-sys.ts +++ b/src/compiler/sys/typescript/typescript-sys.ts @@ -1,19 +1,11 @@ import type * as d from '../../../declarations'; -import { basename } from 'path'; +import { basename, resolve } from 'path'; +import { exit, getCurrentDirectory, getExecutingFilePath, IS_CASE_SENSITIVE_FILE_NAMES, IS_WEB_WORKER_ENV, isRemoteUrl, isString, normalizePath, noop } from '@utils'; import { fetchUrlSync } from '../fetch/fetch-module-sync'; -import { IS_CASE_SENSITIVE_FILE_NAMES, IS_WEB_WORKER_ENV, isRemoteUrl, isString, normalizePath } from '@utils'; -import type ts from 'typescript'; +import { patchTypeScriptResolveModule } from './typescript-resolve-module'; +import ts from 'typescript'; -export const patchTypeScriptSys = (loadedTs: typeof ts, config: d.Config, inMemoryFs: d.InMemoryFileSystem) => { - loadedTs.sys = loadedTs.sys || ({} as ts.System); - - if (config.sys) { - patchTsSystemFileSystem(config, config.sys, inMemoryFs, loadedTs, loadedTs.sys); - patchTsSystemWatch(config.sys, loadedTs, loadedTs.sys); - } -}; - -export const patchTsSystemFileSystem = (config: d.Config, stencilSys: d.CompilerSystem, inMemoryFs: d.InMemoryFileSystem, loadedTs: typeof ts, tsSys: ts.System) => { +export const patchTsSystemFileSystem = (config: d.Config, stencilSys: d.CompilerSystem, inMemoryFs: d.InMemoryFileSystem, tsSys: ts.System) => { const realpath = (path: string) => { const rp = stencilSys.realpathSync(path); if (isString(rp)) { @@ -83,7 +75,7 @@ export const patchTsSystemFileSystem = (config: d.Config, stencilSys: d.Compiler tsSys.readDirectory = (path, extensions, exclude, include, depth) => { const cwd = stencilSys.getCurrentDirectory(); - return (loadedTs as any).matchFiles(path, extensions, exclude, include, IS_CASE_SENSITIVE_FILE_NAMES, cwd, depth, getAccessibleFileSystemEntries, realpath); + return (ts as any).matchFiles(path, extensions, exclude, include, IS_CASE_SENSITIVE_FILE_NAMES, cwd, depth, getAccessibleFileSystemEntries, realpath); }; tsSys.readFile = p => { @@ -115,7 +107,7 @@ export const patchTsSystemFileSystem = (config: d.Config, stencilSys: d.Compiler return tsSys; }; -const patchTsSystemWatch = (stencilSys: d.CompilerSystem, loadedTs: typeof ts, tsSys: ts.System) => { +const patchTsSystemWatch = (stencilSys: d.CompilerSystem, tsSys: ts.System) => { tsSys.watchDirectory = (p, cb, recursive) => { const watcher = stencilSys.watchDirectory( p, @@ -134,11 +126,11 @@ const patchTsSystemWatch = (stencilSys: d.CompilerSystem, loadedTs: typeof ts, t tsSys.watchFile = (p, cb) => { const watcher = stencilSys.watchFile(p, (filePath, eventKind) => { if (eventKind === 'fileAdd') { - cb(filePath, loadedTs.FileWatcherEventKind.Created); + cb(filePath, ts.FileWatcherEventKind.Created); } else if (eventKind === 'fileUpdate') { - cb(filePath, loadedTs.FileWatcherEventKind.Changed); + cb(filePath, ts.FileWatcherEventKind.Changed); } else if (eventKind === 'fileDelete') { - cb(filePath, loadedTs.FileWatcherEventKind.Deleted); + cb(filePath, ts.FileWatcherEventKind.Deleted); } }); return { @@ -149,6 +141,45 @@ const patchTsSystemWatch = (stencilSys: d.CompilerSystem, loadedTs: typeof ts, t }; }; +export const patchTypescript = (config: d.Config, inMemoryFs: d.InMemoryFileSystem) => { + if (!(ts as any).__patched) { + if (config.sys) { + patchTsSystemFileSystem(config, config.sys, inMemoryFs, ts.sys); + patchTsSystemWatch(config.sys, ts.sys); + } + patchTypeScriptResolveModule(config, inMemoryFs); + patchTypeScriptGetParsedCommandLineOfConfigFile(); + (ts as any).__patched = true; + } +}; + +const patchTypeScriptSysMinimum = () => { + if (!ts.sys) { + // patches just the bare minimum + // if ts.sys already exists then it must be node ts.sys + // otherwise we're either browser or deno + // will be updated later on with the stencil sys + ts.sys = { + args: [], + createDirectory: noop, + directoryExists: () => false, + exit, + fileExists: () => false, + getCurrentDirectory, + getDirectories: () => [], + getExecutingFilePath, + readDirectory: () => [], + readFile: noop, + newLine: '\n', + resolvePath: resolve, + useCaseSensitiveFileNames: false, + write: noop, + writeFile: noop, + }; + } +}; +patchTypeScriptSysMinimum(); + export const getTypescriptPathFromUrl = (config: d.Config, tsExecutingUrl: string, url: string) => { const tsBaseUrl = new URL('..', tsExecutingUrl).href; if (url.startsWith(tsBaseUrl)) { @@ -159,10 +190,10 @@ export const getTypescriptPathFromUrl = (config: d.Config, tsExecutingUrl: strin return url; }; -export const patchTypeScriptGetParsedCommandLineOfConfigFile = (loadedTs: typeof ts) => { - const orgGetParsedCommandLineOfConfigFile = loadedTs.getParsedCommandLineOfConfigFile; +export const patchTypeScriptGetParsedCommandLineOfConfigFile = () => { + const orgGetParsedCommandLineOfConfigFile = ts.getParsedCommandLineOfConfigFile; - loadedTs.getParsedCommandLineOfConfigFile = (configFileName, optionsToExtend, host, extendedConfigCache) => { + ts.getParsedCommandLineOfConfigFile = (configFileName, optionsToExtend, host, extendedConfigCache) => { const results = orgGetParsedCommandLineOfConfigFile(configFileName, optionsToExtend, host, extendedConfigCache); // manually filter out any .spec or .e2e files diff --git a/src/compiler/transpile.ts b/src/compiler/transpile.ts index f2c8a23698a..3f9f686ab39 100644 --- a/src/compiler/transpile.ts +++ b/src/compiler/transpile.ts @@ -1,8 +1,8 @@ import { TranspileOptions, TranspileResults, Config, TransformOptions, TransformCssToEsmInput } from '../declarations'; import { catchError, isString } from '@utils'; -import { getTranspileCssConfig, getTranspileConfig, getTranspileResults } from './config/transpile-options'; import { getPublicCompilerMeta } from './transformers/add-component-meta-static'; -import { patchTypescript, patchTypescriptSync } from './sys/typescript/typescript-patch'; +import { getTranspileCssConfig, getTranspileConfig, getTranspileResults } from './config/transpile-options'; +import { patchTypescript } from './sys/typescript/typescript-sys'; import { rollupPluginUtils } from '@compiler-deps'; import { transformCssToEsm, transformCssToEsmSync } from './style/css-to-esm'; import { transpileModule } from './transpile/transpile-module'; @@ -13,7 +13,7 @@ export const transpile = async (code: string, opts: TranspileOptions = {}) => { try { if (shouldTranspileModule(results.inputFileExtension)) { const { config, compileOpts, transformOpts } = getTranspileConfig(opts); - await patchTypescript(config, results.diagnostics, null); + patchTypescript(config, null); transpileCode(config, compileOpts, transformOpts, results); } else if (results.inputFileExtension === 'd.ts') { results.code = ''; @@ -36,7 +36,7 @@ export const transpileSync = (code: string, opts: TranspileOptions = {}) => { try { if (shouldTranspileModule(results.inputFileExtension)) { const { config, compileOpts, transformOpts } = getTranspileConfig(opts); - patchTypescriptSync(config, results.diagnostics, null); + patchTypescript(config, null); transpileCode(config, compileOpts, transformOpts, results); } else if (results.inputFileExtension === 'd.ts') { results.code = ''; diff --git a/src/compiler/worker/worker-thread.ts b/src/compiler/worker/worker-thread.ts index 9c28c7bd02c..0b729dfd106 100644 --- a/src/compiler/worker/worker-thread.ts +++ b/src/compiler/worker/worker-thread.ts @@ -6,7 +6,7 @@ import { transformCssToEsm } from '../style/css-to-esm'; export const createWorkerContext = (sys: d.CompilerSystem): d.CompilerWorkerContext => ({ transformCssToEsm, - prepareModule: (typescriptPath, input, minifyOpts, transpile, inlineHelpers) => prepareModule(sys, typescriptPath, input, minifyOpts, transpile, inlineHelpers), + prepareModule, optimizeCss, prerenderWorker: prerenderRequest => prerenderWorker(sys, prerenderRequest), }); diff --git a/src/declarations/stencil-private.ts b/src/declarations/stencil-private.ts index 1d019ddf9df..bf8d5fba11b 100644 --- a/src/declarations/stencil-private.ts +++ b/src/declarations/stencil-private.ts @@ -2445,7 +2445,7 @@ export interface VNodeProdData { export interface CompilerWorkerContext { optimizeCss(inputOpts: OptimizeCssInput): Promise; - prepareModule(typescriptPath: string, input: string, minifyOpts: any, transpile: boolean, inlineHelpers: boolean): Promise<{ output: string; diagnostics: Diagnostic[] }>; + prepareModule(input: string, minifyOpts: any, transpile: boolean, inlineHelpers: boolean): Promise<{ output: string; diagnostics: Diagnostic[] }>; prerenderWorker(prerenderRequest: PrerenderUrlRequest): Promise; transformCssToEsm(input: TransformCssToEsmInput): Promise; } diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index e9f26931220..6bcbcee95bb 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -248,7 +248,6 @@ export interface StencilConfig { validateTypes?: boolean; watchIgnoredRegex?: RegExp; excludeUnusedDependencies?: boolean; - typescriptPath?: string; stencilCoreResolvedId?: string; } @@ -841,11 +840,7 @@ export interface CompilerSystem { */ createWorkerController?(maxConcurrentWorkers: number): WorkerMainController; encodeToBase64(str: string): string; - ensureDependencies?(opts: { - rootDir: string; - logger: Logger; - dependencies: CompilerDependency[]; - }): Promise<{ stencilPath: string; typescriptPath: string; diagnostics: Diagnostic[] }>; + ensureDependencies?(opts: { rootDir: string; logger: Logger; dependencies: CompilerDependency[] }): Promise<{ stencilPath: string; diagnostics: Diagnostic[] }>; ensureResources?(opts: { rootDir: string; logger: Logger; dependencies: CompilerDependency[] }): Promise; /** * process.exit() @@ -952,6 +947,7 @@ export interface CompilerSystem { * SYNC! Does not throw. */ rmdirSync(p: string, opts?: CompilerSystemRemoveDirectoryOptions): CompilerSystemRemoveDirectoryResults; + setupCompiler?: (c: { ts: any }) => void; /** * Returns undefined if stat not found. Does not throw. */ @@ -1990,7 +1986,6 @@ export interface LoadConfigInit { * within the root directory. */ initTsConfig?: boolean; - typescriptPath?: string; } export interface LoadConfigResults { @@ -2242,7 +2237,6 @@ export interface TranspileOptions { * `latest`, `esnext`, `es2017`, `es2015`, or `es5`. Defaults to `latest`. */ target?: CompileTarget; - typescriptPath?: string; /** * Create a source map. Using `inline` will inline the source map into the * code, otherwise the source map will be in the returned `map` property. diff --git a/src/sys/node/index.ts b/src/sys/node/index.ts index dc0f285b705..cc0fbce50d0 100644 --- a/src/sys/node/index.ts +++ b/src/sys/node/index.ts @@ -1,3 +1,3 @@ export { createNodeLogger } from './node-logger'; -export { createNodeSys, createNodeSysNoWatch } from './node-sys'; +export { createNodeSys } from './node-sys'; export { setupNodeProcess } from './node-setup-process'; diff --git a/src/sys/node/node-sys.ts b/src/sys/node/node-sys.ts index 6a2ed0a1cb2..9a50327a18b 100644 --- a/src/sys/node/node-sys.ts +++ b/src/sys/node/node-sys.ts @@ -4,7 +4,6 @@ import type { CompilerSystemRealpathResults, CompilerSystemUnlinkResults, CompilerSystemWriteFileResults, - Diagnostic, } from '../../declarations'; import { asyncGlob, nodeCopyTasks } from './node-copy-tasks'; import { buildEvents } from '../../compiler/events'; @@ -16,75 +15,11 @@ import fs from 'graceful-fs'; import { NodeLazyRequire } from './node-lazy-require'; import { NodeResolveModule } from './node-resolve-module'; import { NodeWorkerController } from './node-worker-controller'; -import { normalizePath, requireFunc, buildError, isFunction } from '@utils'; +import { normalizePath, isFunction } from '@utils'; import path from 'path'; import type TypeScript from 'typescript'; export function createNodeSys(c: { process?: any } = {}) { - const sys = createNodeSysNoWatch(c); - const ts = require('typescript') as typeof TypeScript; - const tsWatchFile = ts.sys.watchFile; - const tsWatchDirectory = ts.sys.watchDirectory; - - sys.watchTimeout = 80; - sys.events = buildEvents(); - - sys.watchDirectory = (p, callback, recursive) => { - const tsFileWatcher = tsWatchDirectory( - p, - fileName => { - fileName = normalizePath(fileName); - callback(fileName, null); - }, - recursive, - ); - - const close = () => { - tsFileWatcher.close(); - }; - - sys.addDestory(close); - - return { - close() { - sys.removeDestory(close); - tsFileWatcher.close(); - }, - }; - }; - - sys.watchFile = (p, callback) => { - const tsFileWatcher = tsWatchFile(p, (fileName, tsEventKind) => { - fileName = normalizePath(fileName); - if (tsEventKind === ts.FileWatcherEventKind.Created) { - callback(fileName, 'fileAdd'); - sys.events.emit('fileAdd', fileName); - } else if (tsEventKind === ts.FileWatcherEventKind.Changed) { - callback(fileName, 'fileUpdate'); - sys.events.emit('fileUpdate', fileName); - } else if (tsEventKind === ts.FileWatcherEventKind.Deleted) { - callback(fileName, 'fileDelete'); - sys.events.emit('fileDelete', fileName); - } - }); - - const close = () => { - tsFileWatcher.close(); - }; - sys.addDestory(close); - - return { - close() { - sys.removeDestory(close); - tsFileWatcher.close(); - }, - }; - }; - - return sys; -} - -export function createNodeSysNoWatch(c: { process?: any } = {}) { const prcs: NodeJS.Process = c.process || global.process; const destroys = new Set<() => Promise | void>(); const onInterruptsCallbacks: (() => void)[] = []; @@ -93,6 +28,9 @@ export function createNodeSysNoWatch(c: { process?: any } = {}) { const hardwareConcurrency = sysCpus.length; const osPlatform = platform(); + const compilerExecutingPath = path.join(__dirname, '..', '..', 'compiler', 'stencil.js'); + const devServerExecutingPath = path.join(__dirname, '..', '..', 'dev-server', 'index.js'); + const sys: CompilerSystem = { name: 'node', version: prcs.versions.node, @@ -176,26 +114,15 @@ export function createNodeSysNoWatch(c: { process?: any } = {}) { destroys.clear(); }, dynamicImport(p) { - return Promise.resolve(requireFunc(p)); + return Promise.resolve(require(p)); }, encodeToBase64(str) { return Buffer.from(str).toString('base64'); }, async ensureDependencies() { - const diagnostics: Diagnostic[] = []; - let typescriptPath: string = null; - try { - typescriptPath = require.resolve('typescript'); - } catch (e) { - const diagnostic = buildError(diagnostics); - diagnostic.header = `Unable to find TypeScript`; - diagnostic.messageText = `Please ensure you install the dependencies first, for example: "npm install"`; - } - return { stencilPath: sys.getCompilerExecutingPath(), - typescriptPath, - diagnostics, + diagnostics: [], }; }, async ensureResources() {}, @@ -204,10 +131,10 @@ export function createNodeSysNoWatch(c: { process?: any } = {}) { return normalizePath(prcs.cwd()); }, getCompilerExecutingPath() { - return path.join(__dirname, '..', '..', 'compiler', 'stencil.js'); + return compilerExecutingPath; }, getDevServerExecutingPath() { - return path.join(__dirname, '..', '..', 'dev-server', 'index.js'); + return devServerExecutingPath; }, getEnvironmentVar(key) { return process.env[key]; @@ -378,6 +305,67 @@ export function createNodeSysNoWatch(c: { process?: any } = {}) { return { basename: path.basename(p), dirname: path.dirname(p), path: p, removedDirs: [], removedFiles: [], error: e }; } }, + setupCompiler(c) { + const ts: typeof TypeScript = c.ts; + const tsSysWatchDirectory = ts.sys.watchDirectory; + const tsSysWatchFile = ts.sys.watchFile; + + sys.watchTimeout = 80; + + sys.events = buildEvents(); + + sys.watchDirectory = (p, callback, recursive) => { + const tsFileWatcher = tsSysWatchDirectory( + p, + fileName => { + fileName = normalizePath(fileName); + callback(fileName, null); + }, + recursive, + ); + + const close = () => { + tsFileWatcher.close(); + }; + + sys.addDestory(close); + + return { + close() { + sys.removeDestory(close); + tsFileWatcher.close(); + }, + }; + }; + + sys.watchFile = (p, callback) => { + const tsFileWatcher = tsSysWatchFile(p, (fileName, tsEventKind) => { + fileName = normalizePath(fileName); + if (tsEventKind === ts.FileWatcherEventKind.Created) { + callback(fileName, 'fileAdd'); + sys.events.emit('fileAdd', fileName); + } else if (tsEventKind === ts.FileWatcherEventKind.Changed) { + callback(fileName, 'fileUpdate'); + sys.events.emit('fileUpdate', fileName); + } else if (tsEventKind === ts.FileWatcherEventKind.Deleted) { + callback(fileName, 'fileDelete'); + sys.events.emit('fileDelete', fileName); + } + }); + + const close = () => { + tsFileWatcher.close(); + }; + sys.addDestory(close); + + return { + close() { + sys.removeDestory(close); + tsFileWatcher.close(); + }, + }; + }; + }, stat(p) { return new Promise(resolve => { fs.stat(p, (err, s) => { diff --git a/src/utils/environment.ts b/src/utils/environment.ts index 450efdce98c..ee423a56dfa 100644 --- a/src/utils/environment.ts +++ b/src/utils/environment.ts @@ -14,9 +14,7 @@ export const OS_PLATFORM = IS_NODE_ENV ? global.process.platform : IS_DENO_ENV ? export const IS_DENO_WINDOWS_ENV = IS_DENO_ENV && Deno.build.os === 'windows'; -export const IS_NODE_WINDOWS_ENV = IS_NODE_ENV && global.process.platform === 'win32'; - -export const IS_WINDOWS_ENV = IS_DENO_WINDOWS_ENV || IS_NODE_WINDOWS_ENV; +export const IS_WINDOWS_ENV = OS_PLATFORM === 'win32' || OS_PLATFORM === 'windows'; export const IS_CASE_SENSITIVE_FILE_NAMES = !IS_WINDOWS_ENV; @@ -29,12 +27,12 @@ export const HAS_WEB_WORKER = IS_BROWSER_ENV && typeof Worker === 'function'; export const IS_FETCH_ENV = typeof fetch === 'function'; -export const requireFunc = (path: string) => (typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require)(path); +export const requireFunc = IS_NODE_ENV ? require : noop; + +export const getCurrentDirectory: () => string = IS_NODE_ENV ? process.cwd : IS_DENO_ENV ? (globalThis as any).Deno.cwd : () => '/'; -export const getCurrentDirectory: () => string = IS_NODE_ENV ? process.cwd : IS_DENO_ENV ? Deno.cwd : () => '/'; +export const getExecutingFilePath = () => (IS_NODE_ENV ? __filename : IS_BROWSER_ENV ? location.href : './'); -export const exit: (exitCode?: number) => void = IS_NODE_ENV ? process.exit : IS_DENO_ENV ? Deno.exit : noop; +export const exit: (exitCode?: number) => void = IS_NODE_ENV ? global.process.exit : IS_DENO_ENV ? (globalThis as any).Deno.exit : noop; -declare const __webpack_require__: (path: string) => any; -declare const __non_webpack_require__: (path: string) => any; declare const Deno: any;