diff --git a/jest.config.js b/jest.config.js index 1c6440a0a27..60ccb546ac6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,13 +3,13 @@ module.exports = { moduleNameMapper: { '@app-data': '/internal/app-data/index.cjs', '@app-globals': '/internal/app-globals/index.cjs', - '@compiler-deps': '/src/compiler/sys/modules/compiler-deps.ts', '@platform': '/internal/testing/index.js', '@runtime': '/internal/testing/index.js', '@stencil/core/cli': '/cli/index.js', '@stencil/core/compiler': '/compiler/stencil.js', '@stencil/core/mock-doc': '/mock-doc/index.cjs', '@stencil/core/testing': '/testing/index.js', + '@sys-api-node': '/sys/node/index.js', '@utils': '/src/utils', '^typescript$': '/scripts/build/typescript-modified-for-jest.js', }, diff --git a/package-lock.json b/package-lock.json index 17a123af08d..ddf81d59d75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@types/inquirer": "^7.3.1", "@types/jest": "^27.0.3", "@types/listr": "^0.14.4", + "@types/mock-fs": "^4.13.1", "@types/node": "^20.1.1", "@types/pixelmatch": "^5.2.4", "@types/pngjs": "^6.0.1", @@ -62,6 +63,7 @@ "merge-source-map": "^1.1.0", "mime-db": "^1.46.0", "minimatch": "5.1.6", + "mock-fs": "^5.2.0", "node-fetch": "3.3.1", "open": "^9.0.0", "open-in-editor": "2.2.0", @@ -1786,6 +1788,15 @@ "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", "dev": true }, + "node_modules/@types/mock-fs": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz", + "integrity": "sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "20.2.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", @@ -8227,6 +8238,15 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "node_modules/mock-fs": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.2.0.tgz", + "integrity": "sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -12481,6 +12501,15 @@ "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", "dev": true }, + "@types/mock-fs": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz", + "integrity": "sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "20.2.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", @@ -17317,6 +17346,12 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "mock-fs": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.2.0.tgz", + "integrity": "sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==", + "dev": true + }, "modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", diff --git a/package.json b/package.json index 88cd900533e..abbdaf92fc8 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@types/inquirer": "^7.3.1", "@types/jest": "^27.0.3", "@types/listr": "^0.14.4", + "@types/mock-fs": "^4.13.1", "@types/node": "^20.1.1", "@types/pixelmatch": "^5.2.4", "@types/pngjs": "^6.0.1", @@ -106,6 +107,7 @@ "merge-source-map": "^1.1.0", "mime-db": "^1.46.0", "minimatch": "5.1.6", + "mock-fs": "^5.2.0", "node-fetch": "3.3.1", "open": "^9.0.0", "open-in-editor": "2.2.0", diff --git a/scripts/bundles/compiler.ts b/scripts/bundles/compiler.ts index e3fb4c48860..06e814c427a 100644 --- a/scripts/bundles/compiler.ts +++ b/scripts/bundles/compiler.ts @@ -8,14 +8,13 @@ import type { OutputChunk, RollupOptions, RollupWarning, TransformResult } from import sourcemaps from 'rollup-plugin-sourcemaps'; import { getBanner } from '../utils/banner'; +import { NODE_BUILTINS } from '../utils/constants'; import type { BuildOptions } from '../utils/options'; import { writePkgJson } from '../utils/write-pkg-json'; import { aliasPlugin } from './plugins/alias-plugin'; -import { inlinedCompilerDepsPlugin } from './plugins/inlined-compiler-deps-plugin'; import { parse5Plugin } from './plugins/parse5-plugin'; import { replacePlugin } from './plugins/replace-plugin'; import { sizzlePlugin } from './plugins/sizzle-plugin'; -import { sysModulesPlugin } from './plugins/sys-modules-plugin'; import { terserPlugin } from './plugins/terser-plugin'; import { typescriptSourcePlugin } from './plugins/typescript-source-plugin'; @@ -67,6 +66,7 @@ export async function compiler(opts: BuildOptions) { const rollupWatchPath = join(opts.nodeModulesDir, 'rollup', 'dist', 'es', 'shared', 'watch.js'); const compilerBundle: RollupOptions = { input: join(inputDir, 'index.js'), + external: NODE_BUILTINS, output: { format: 'cjs', file: join(opts.output.compilerDir, compilerFileName), @@ -154,14 +154,11 @@ export async function compiler(opts: BuildOptions) { }; }, }, - inlinedCompilerDepsPlugin(opts, inputDir), parse5Plugin(opts), sizzlePlugin(opts), aliasPlugin(opts), - sysModulesPlugin(inputDir), rollupNodeResolve({ mainFields: ['module', 'main'], - preferBuiltins: false, }), rollupCommonjs({ transformMixedEsModules: false, diff --git a/scripts/bundles/plugins/alias-plugin.ts b/scripts/bundles/plugins/alias-plugin.ts index d698c089a44..39d781a6ab4 100644 --- a/scripts/bundles/plugins/alias-plugin.ts +++ b/scripts/bundles/plugins/alias-plugin.ts @@ -15,7 +15,6 @@ export function aliasPlugin(opts: BuildOptions): Plugin { ['@hydrate-factory', '@stencil/core/hydrate-factory'], ['@stencil/core/mock-doc', '@stencil/core/mock-doc'], ['@stencil/core/testing', '@stencil/core/testing'], - ['@sys-api-node', './index.js'], ['@dev-server-process', './server-process.js'], ]); @@ -54,6 +53,9 @@ export function aliasPlugin(opts: BuildOptions): Plugin { if (id === '@environment') { return join(opts.buildDir, 'compiler', 'sys', 'environment.js'); } + if (id === '@sys-api-node') { + return join(opts.buildDir, 'sys', 'node', 'index.js'); + } if (helperResolvers.has(id)) { return join(opts.bundleHelpersDir, `${id}.js`); } diff --git a/scripts/bundles/plugins/inlined-compiler-deps-plugin.ts b/scripts/bundles/plugins/inlined-compiler-deps-plugin.ts deleted file mode 100644 index f6dfcd9ab4b..00000000000 --- a/scripts/bundles/plugins/inlined-compiler-deps-plugin.ts +++ /dev/null @@ -1,77 +0,0 @@ -import rollupCommonjs from '@rollup/plugin-commonjs'; -import rollupJson from '@rollup/plugin-json'; -import rollupNodeResolve from '@rollup/plugin-node-resolve'; -import fs from 'fs-extra'; -import { join } from 'path'; -import { Plugin, rollup } from 'rollup'; - -import type { BuildOptions } from '../../utils/options'; - -/** - * Generates a rollup configuration for loading external, third party scripts that are required by the Stencil compiler - * into the core package. - * @param opts the options being used during a build of the Stencil compiler - * @param inputDir the directory from which modules should be resolved. Care should be taken to observe this value at - * build to verify where modules are being resolved from. - * @returns a rollup plugin for bundling compiler dependencies - */ -export function inlinedCompilerDepsPlugin(opts: BuildOptions, inputDir: string): Plugin { - return { - name: 'inlinedCompilerDepsPlugin', - resolveId(id: string): string | null { - if (id === '@compiler-deps') { - return id; - } - return null; - }, - load(id: string): Promise { - if (id === '@compiler-deps') { - return bundleCompilerDeps(opts, inputDir); - } - return null; - }, - }; -} - -/** - * Bundles various compiler dependencies into the compiler. For a list of those dependencies, refer to the `input` - * field of the rollup build invocation in this function as the source of truth. - * @param opts the options being used during a build of the Stencil compiler - * @param inputDir the directory from which modules should be resolved - * @returns the bundled dependencies - */ -async function bundleCompilerDeps(opts: BuildOptions, inputDir: string): Promise { - const cacheFile = join(opts.buildDir, 'compiler-deps-bundle-cache.js'); - - if (!opts.isProd) { - try { - return await fs.readFile(cacheFile, 'utf8'); - } catch (e) {} - } - - const build = await rollup({ - input: join(inputDir, 'sys', 'modules', 'compiler-deps.js'), - external: ['fs', 'module', 'path', 'util', 'resolve'], - plugins: [ - rollupNodeResolve({ - preferBuiltins: false, - }), - rollupCommonjs(), - rollupJson({ - preferConst: true, - }), - ], - treeshake: { - moduleSideEffects: false, - }, - }); - - await build.write({ - format: 'es', - file: cacheFile, - preferConst: true, - banner: `// Rollup ${opts.rollupVersion}`, - }); - - return await fs.readFile(cacheFile, 'utf8'); -} diff --git a/scripts/bundles/plugins/sys-modules-plugin.ts b/scripts/bundles/plugins/sys-modules-plugin.ts deleted file mode 100644 index 8fa1bb245a1..00000000000 --- a/scripts/bundles/plugins/sys-modules-plugin.ts +++ /dev/null @@ -1,31 +0,0 @@ -import path from 'path'; -import type { Plugin } from 'rollup'; - -/** - * Modules that are polyfilled by Stencil, rather than using the default Node implementation. - */ -const modules = new Set(['crypto', 'events', 'fs', 'module', 'os', 'path', 'stream', 'url', 'util']); - -/** - * Rollup plugin that aids in resolving various system level (sys) modules properly. - * @param inputDir the directory from which modules should be resolved. Care should be taken to observe this value at - * build to verify where modules are being resolved from. - * @returns the rollup plugin that resolves Stencil `sys` modules - */ -export function sysModulesPlugin(inputDir: string): Plugin { - return { - name: 'sysModulesPlugin', - /** - * A rollup build hook for resolving Stencil's environment agnostic packages - * [Source](https://rollupjs.org/guide/en/#resolveid) - * @param importee the importee exactly as it is written in an import statement in the source code - * @returns a string that resolves an import to some id, null otherwise - */ - resolveId(importee: string): string | null { - if (modules.has(importee)) { - return path.join(inputDir, 'sys', 'modules', `${importee}.js`); - } - return null; - }, - }; -} diff --git a/scripts/bundles/sys-node.ts b/scripts/bundles/sys-node.ts index d90ff6b80e5..c1e2d6d32d6 100644 --- a/scripts/bundles/sys-node.ts +++ b/scripts/bundles/sys-node.ts @@ -6,6 +6,7 @@ import type { RollupOptions } from 'rollup'; import webpack, { Configuration } from 'webpack'; import { getBanner } from '../utils/banner'; +import { NODE_BUILTINS } from '../utils/constants'; import type { BuildOptions } from '../utils/options'; import { writePkgJson } from '../utils/write-pkg-json'; import { aliasPlugin } from './plugins/alias-plugin'; @@ -38,7 +39,7 @@ export async function sysNode(opts: BuildOptions) { preferConst: true, freeze: false, }, - external: ['child_process', 'crypto', 'events', 'https', 'path', 'readline', 'os', 'util'], + external: NODE_BUILTINS, plugins: [ relativePathPlugin('glob', './glob.js'), relativePathPlugin('graceful-fs', './graceful-fs.js'), @@ -69,7 +70,7 @@ export async function sysNode(opts: BuildOptions) { preferConst: true, freeze: false, }, - external: ['child_process', 'crypto', 'events', 'https', 'path', 'readline', 'os', 'util'], + external: NODE_BUILTINS, plugins: [ { name: 'sysNodeWorkerAlias', @@ -82,6 +83,7 @@ export async function sysNode(opts: BuildOptions) { } }, }, + relativePathPlugin('@sys-api-node', './index.js'), rollupResolve({ preferBuiltins: true, }), diff --git a/scripts/test/validate-build.ts b/scripts/test/validate-build.ts index 30b68e653c0..a615e440baa 100644 --- a/scripts/test/validate-build.ts +++ b/scripts/test/validate-build.ts @@ -3,6 +3,7 @@ import { dirname, join, relative } from 'path'; import { rollup } from 'rollup'; import ts, { ModuleResolutionKind, ScriptTarget } from 'typescript'; +import { NODE_BUILTINS } from '../utils/constants'; import { BuildOptions, getOptions } from '../utils/options'; import { PackageData } from '../utils/write-pkg-json'; @@ -335,8 +336,11 @@ async function validateModuleTreeshake(opts: BuildOptions, moduleName: string, e const outputFile = join(opts.scriptsBuildDir, `treeshake_${moduleName}.js`); const bundle = await rollup({ + external: NODE_BUILTINS, input: virtualInputId, - treeshake: true, + treeshake: { + moduleSideEffects: false, + }, plugins: [ { name: 'stencilResolver', diff --git a/src/compiler/sys/modules/module.ts b/scripts/utils/constants.ts similarity index 86% rename from src/compiler/sys/modules/module.ts rename to scripts/utils/constants.ts index 074619e9d63..ea8c73168ff 100644 --- a/src/compiler/sys/modules/module.ts +++ b/scripts/utils/constants.ts @@ -1,5 +1,5 @@ /** - * Node builtin modules as of v14.5.0 + * Node built-ins that we mark as external when building Stencil */ export const NODE_BUILTINS = [ '_http_agent', @@ -58,9 +58,3 @@ export const NODE_BUILTINS = [ 'worker_threads', 'zlib', ]; - -export default class Module { - static get builtinModules() { - return NODE_BUILTINS; - } -} diff --git a/scripts/utils/test/options.spec.ts b/scripts/utils/test/options.spec.ts index 4bf62c53f62..9b876b8530d 100644 --- a/scripts/utils/test/options.spec.ts +++ b/scripts/utils/test/options.spec.ts @@ -1,4 +1,5 @@ -import { path } from '../../../compiler'; +import path from 'path'; + import { BuildOptions, getOptions } from '../options'; import * as Vermoji from '../vermoji'; diff --git a/src/cli/run.ts b/src/cli/run.ts index 5983e1c1b02..d7ea04edc35 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -1,6 +1,5 @@ import { hasError, isFunction, shouldIgnoreError } from '@utils'; -import { createLogger } from '../compiler/sys/logger/console-logger'; import type * as d from '../declarations'; import { ValidatedConfig } from '../declarations'; import { createConfigFlags } from './config-flags'; @@ -114,13 +113,14 @@ export const runTask = async ( coreCompiler: CoreCompiler, config: d.Config, task: d.TaskCommand, - sys?: d.CompilerSystem + sys: d.CompilerSystem ): Promise => { - const logger = config.logger ?? createLogger(); const flags = createConfigFlags(config.flags ?? { task }); - config.logger = logger; config.flags = flags; - config.sys = sys ?? config.sys ?? coreCompiler.createSystem({ logger }); + + if (!config.sys) { + config.sys = sys; + } const strictConfig: ValidatedConfig = coreCompiler.validateConfig(config, {}).config; switch (task) { @@ -134,7 +134,7 @@ export const runTask = async ( case 'generate': case 'g': - await taskGenerate(coreCompiler, strictConfig); + await taskGenerate(strictConfig); break; case 'help': diff --git a/src/cli/task-generate.ts b/src/cli/task-generate.ts index 35c4834bec5..6eed7a7e078 100644 --- a/src/cli/task-generate.ts +++ b/src/cli/task-generate.ts @@ -1,8 +1,8 @@ -import { validateComponentTag } from '@utils'; +import { normalizePath, validateComponentTag } from '@utils'; +import { join, parse, relative } from 'path'; import { IS_NODE_ENV } from '../compiler/sys/environment'; import type { ValidatedConfig } from '../declarations'; -import type { CoreCompiler } from './load-compiler'; /** * Task to generate component boilerplate and write it to disk. This task can @@ -10,19 +10,15 @@ import type { CoreCompiler } from './load-compiler'; * being called in an inappropriate place, being asked to overwrite files that * already exist, etc. * - * @param coreCompiler the CoreCompiler we're using currently, here we're - * mainly accessing the `path` module * @param config the user-supplied config, which we need here to access `.sys`. * @returns a void promise */ -export const taskGenerate = async (coreCompiler: CoreCompiler, config: ValidatedConfig): Promise => { +export const taskGenerate = async (config: ValidatedConfig): Promise => { if (!IS_NODE_ENV) { config.logger.error(`"generate" command is currently only implemented for a NodeJS environment`); return config.sys.exit(1); } - const path = coreCompiler.path; - if (!config.configPath) { config.logger.error('Please run this command in your root directory (i. e. the one containing stencil.config.ts).'); return config.sys.exit(1); @@ -46,7 +42,7 @@ export const taskGenerate = async (coreCompiler: CoreCompiler, config: Validated // explicitly return here to avoid printing the error message. return; } - const { dir, base: componentName } = path.parse(input); + const { dir, base: componentName } = parse(input); const tagError = validateComponentTag(componentName); if (tagError) { @@ -63,12 +59,12 @@ export const taskGenerate = async (coreCompiler: CoreCompiler, config: Validated const testFolder = extensionsToGenerate.some(isTest) ? 'test' : ''; - const outDir = path.join(absoluteSrcDir, 'components', dir, componentName); - await config.sys.createDir(path.join(outDir, testFolder), { recursive: true }); + const outDir = join(absoluteSrcDir, 'components', dir, componentName); + await config.sys.createDir(normalizePath(join(outDir, testFolder)), { recursive: true }); const filesToGenerate: readonly BoilerplateFile[] = extensionsToGenerate.map((extension) => ({ extension, - path: getFilepathForFile(coreCompiler, outDir, componentName, extension), + path: getFilepathForFile(outDir, componentName, extension), })); await checkForOverwrite(filesToGenerate, config); @@ -92,7 +88,7 @@ export const taskGenerate = async (coreCompiler: CoreCompiler, config: Validated console.log(config.logger.bold('The following files have been generated:')); const absoluteRootDir = config.rootDir; - writtenFiles.map((file) => console.log(` - ${path.relative(absoluteRootDir, file)}`)); + writtenFiles.map((file) => console.log(` - ${relative(absoluteRootDir, file)}`)); }; /** @@ -123,22 +119,16 @@ const chooseFilesToGenerate = async (): Promise +const getFilepathForFile = (filePath: string, componentName: string, extension: GenerableExtension): string => isTest(extension) - ? coreCompiler.path.join(path, 'test', `${componentName}.${extension}`) - : coreCompiler.path.join(path, `${componentName}.${extension}`); + ? normalizePath(join(filePath, 'test', `${componentName}.${extension}`)) + : normalizePath(join(filePath, `${componentName}.${extension}`)); /** * Get the boilerplate for a file and write it to disk @@ -157,7 +147,7 @@ const getBoilerplateAndWriteFile = async ( file: BoilerplateFile ): Promise => { const boilerplate = getBoilerplateByExtension(componentName, file.extension, withCss); - await config.sys.writeFile(file.path, boilerplate); + await config.sys.writeFile(normalizePath(file.path), boilerplate); return file.path; }; @@ -187,7 +177,7 @@ const checkForOverwrite = async (files: readonly BoilerplateFile[], config: Vali if (alreadyPresent.length > 0) { config.logger.error( 'Generating code would overwrite the following files:', - ...alreadyPresent.map((path) => '\t' + path) + ...alreadyPresent.map((path) => '\t' + normalizePath(path)) ); await config.sys.exit(1); } diff --git a/src/cli/test/run.spec.ts b/src/cli/test/run.spec.ts index d87a358a0d1..746bb6ba908 100644 --- a/src/cli/test/run.spec.ts +++ b/src/cli/test/run.spec.ts @@ -210,19 +210,6 @@ describe('run', () => { const compilerSystemUsed: d.CompilerSystem = taskBuildSpy.mock.calls[0][1].sys; expect(compilerSystemUsed).toBe(sys); }); - - it('uses the sys field on the config if no sys argument is provided', async () => { - // if the optional `sys` argument isn't provided, attempt to default to the one on the config - await runTask(coreCompiler, unvalidatedConfig, 'build'); - const validated = coreCompiler.validateConfig(unvalidatedConfig, {}); - - // first validate there was one call, and that call had two arguments - expect(taskBuildSpy).toHaveBeenCalledTimes(1); - expect(taskBuildSpy).toHaveBeenCalledWith(coreCompiler, validated.config); - - const compilerSystemUsed: d.CompilerSystem = taskBuildSpy.mock.calls[0][1].sys; - expect(compilerSystemUsed).toBe(unvalidatedConfig.sys); - }); }); }); @@ -248,7 +235,7 @@ describe('run', () => { const validated = coreCompiler.validateConfig(unvalidatedConfig, {}); expect(taskGenerateSpy).toHaveBeenCalledTimes(1); - expect(taskGenerateSpy).toHaveBeenCalledWith(coreCompiler, validated.config); + expect(taskGenerateSpy).toHaveBeenCalledWith(validated.config); }); it("calls the generate task for the argument 'g'", async () => { @@ -256,7 +243,7 @@ describe('run', () => { const validated = coreCompiler.validateConfig(unvalidatedConfig, {}); expect(taskGenerateSpy).toHaveBeenCalledTimes(1); - expect(taskGenerateSpy).toHaveBeenCalledWith(coreCompiler, validated.config); + expect(taskGenerateSpy).toHaveBeenCalledWith(validated.config); }); }); diff --git a/src/cli/test/task-generate.spec.ts b/src/cli/test/task-generate.spec.ts index 4b5383240fe..520a071f02c 100644 --- a/src/cli/test/task-generate.spec.ts +++ b/src/cli/test/task-generate.spec.ts @@ -1,10 +1,8 @@ -import * as coreCompiler from '@stencil/core/compiler'; import { mockCompilerSystem, mockValidatedConfig } from '@stencil/core/testing'; import type * as d from '../../declarations'; import * as utils from '../../utils/validation'; import { createConfigFlags } from '../config-flags'; -import { CoreCompiler } from '../load-compiler'; import { BoilerplateFile, getBoilerplateByExtension, taskGenerate } from '../task-generate'; const promptMock = jest.fn().mockResolvedValue('my-component'); @@ -41,13 +39,12 @@ const setup = async () => { /** * Little test helper function which just temporarily silences * console.log calls, so we can avoid spewing a bunch of stuff. - * @param coreCompiler the core compiler instance to forward to `taskGenerate` * @param config the user-supplied config to forward to `taskGenerate` */ -async function silentGenerate(coreCompiler: CoreCompiler, config: d.ValidatedConfig): Promise { +async function silentGenerate(config: d.ValidatedConfig): Promise { const tmp = console.log; console.log = jest.fn(); - await taskGenerate(coreCompiler, config); + await taskGenerate(config); console.log = tmp; } @@ -65,7 +62,7 @@ describe('generate task', () => { it('should exit with an error if no `configPath` is supplied', async () => { const { config, errorSpy } = await setup(); config.configPath = undefined; - await taskGenerate(coreCompiler, config); + await taskGenerate(config); expect(config.sys.exit).toHaveBeenCalledWith(1); expect(errorSpy).toHaveBeenCalledWith( 'Please run this command in your root directory (i. e. the one containing stencil.config.ts).' @@ -75,7 +72,7 @@ describe('generate task', () => { it('should exit with an error if no `srcDir` is supplied', async () => { const { config, errorSpy } = await setup(); config.srcDir = undefined; - await taskGenerate(coreCompiler, config); + await taskGenerate(config); expect(config.sys.exit).toHaveBeenCalledWith(1); expect(errorSpy).toHaveBeenCalledWith("Stencil's srcDir was not specified."); }); @@ -83,7 +80,7 @@ describe('generate task', () => { it('should exit with an error if the component name does not validate', async () => { const { config, errorSpy, validateTagSpy } = await setup(); validateTagSpy.mockReturnValue('error error error'); - await taskGenerate(coreCompiler, config); + await taskGenerate(config); expect(config.sys.exit).toHaveBeenCalledWith(1); expect(errorSpy).toHaveBeenCalledWith('error error error'); }); @@ -99,7 +96,7 @@ describe('generate task', () => { } const createDirSpy = jest.spyOn(config.sys, 'createDir'); - await silentGenerate(coreCompiler, config); + await silentGenerate(config); expect(createDirSpy).toHaveBeenCalledWith( includeTests ? `${config.srcDir}/components/my-component/test` : `${config.srcDir}/components/my-component`, { recursive: true } @@ -109,7 +106,7 @@ describe('generate task', () => { it('should generate the files the user picked', async () => { const { config } = await setup(); const writeFileSpy = jest.spyOn(config.sys, 'writeFile'); - await silentGenerate(coreCompiler, config); + await silentGenerate(config); const userChoices: ReadonlyArray = [ { extension: 'tsx', path: '/src/components/my-component/my-component.tsx' }, { extension: 'css', path: '/src/components/my-component/my-component.css' }, @@ -128,7 +125,7 @@ describe('generate task', () => { it('should error without writing anything if a to-be-generated file is already present', async () => { const { config, errorSpy } = await setup(); jest.spyOn(config.sys, 'readFile').mockResolvedValue('some file contents'); - await silentGenerate(coreCompiler, config); + await silentGenerate(config); expect(errorSpy).toHaveBeenCalledWith( 'Generating code would overwrite the following files:', '\t/src/components/my-component/my-component.tsx', diff --git a/src/compiler/bundle/bundle-output.ts b/src/compiler/bundle/bundle-output.ts index a3885aea8a4..528c5dd4839 100644 --- a/src/compiler/bundle/bundle-output.ts +++ b/src/compiler/bundle/bundle-output.ts @@ -1,4 +1,7 @@ -import { rollupCommonjsPlugin, rollupJsonPlugin, rollupNodeResolvePlugin, rollupReplacePlugin } from '@compiler-deps'; +import rollupCommonjsPlugin from '@rollup/plugin-commonjs'; +import rollupJsonPlugin from '@rollup/plugin-json'; +import rollupNodeResolvePlugin from '@rollup/plugin-node-resolve'; +import rollupReplacePlugin from '@rollup/plugin-replace'; import { createOnWarnFn, isString, loadRollupDiagnostics } from '@utils'; import { rollup, RollupOptions, TreeshakingOptions } from 'rollup'; diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 3d2b01809b6..228c4c13bfc 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -7,7 +7,6 @@ import { createFullBuild } from './build/full-build'; import { createWatchBuild } from './build/watch-build'; import { Cache } from './cache'; import { getConfig } from './sys/config'; -import { patchFs } from './sys/fs-patch'; import { createInMemoryFs } from './sys/in-memory-fs'; import { resolveModuleIdAsync } from './sys/resolve/resolve-module-async'; import { patchTypescript } from './sys/typescript/typescript-sys'; @@ -21,8 +20,6 @@ import { createSysWorker } from './sys/worker/sys-worker'; */ export const createCompiler = async (userConfig: Config): Promise => { // actual compiler code - // could be in a web worker on the browser - // or the main thread in node const config: ValidatedConfig = getConfig(userConfig); const diagnostics: Diagnostic[] = []; const sys = config.sys; @@ -32,8 +29,6 @@ export const createCompiler = async (userConfig: Config): Promise => { config.sys.setupCompiler({ ts }); } - patchFs(sys); - compilerCtx.fs = createInMemoryFs(sys); compilerCtx.cache = new Cache(config, createInMemoryFs(sys)); await compilerCtx.cache.initCacheDir(); diff --git a/src/compiler/config/load-config.ts b/src/compiler/config/load-config.ts index 2dbff9766fd..46e3040d810 100644 --- a/src/compiler/config/load-config.ts +++ b/src/compiler/config/load-config.ts @@ -1,3 +1,4 @@ +import { createNodeSys } from '@sys-api-node'; import { buildError, catchError, hasError, isString, normalizePath } from '@utils'; import { dirname } from 'path'; import ts from 'typescript'; @@ -11,7 +12,6 @@ import type { } from '../../declarations'; import { IS_NODE_ENV } from '../sys/environment'; import { nodeRequire } from '../sys/node-require'; -import { createSystem } from '../sys/stencil-sys'; import { validateTsConfig } from '../sys/typescript/typescript-config'; import { validateConfig } from './validate-config'; @@ -53,7 +53,7 @@ export const loadConfig = async (init: LoadConfigInit = {}): Promise { const output: d.OutputTargetHydrate[] = []; @@ -59,7 +58,9 @@ export const validateHydrateScript = (config: d.ValidatedConfig, userOutputs: d. outputTarget.external = outputTarget.external || []; - outputTarget.external.push(...NODE_BUILTINS); + outputTarget.external.push('fs'); + outputTarget.external.push('path'); + outputTarget.external.push('crypto'); output.push(outputTarget); }); diff --git a/src/compiler/config/test/load-config.spec.ts b/src/compiler/config/test/load-config.spec.ts index 51620a95ecd..e21b30bca03 100644 --- a/src/compiler/config/test/load-config.spec.ts +++ b/src/compiler/config/test/load-config.spec.ts @@ -1,26 +1,53 @@ +import { getMockFSPatch } from '@stencil/core/testing'; +import { createNodeSys } from '@sys-api-node'; +import fs from 'fs'; +import mock from 'mock-fs'; import path from 'path'; import { ConfigFlags } from '../../../cli/config-flags'; -import { createSystem } from '../../../compiler/sys/stencil-sys'; import type * as d from '../../../declarations'; import { normalizePath } from '../../../utils'; import { loadConfig } from '../load-config'; describe('load config', () => { - const configPath = require.resolve('./fixtures/stencil.config.ts'); - const configPath2 = require.resolve('./fixtures/stencil.config2.ts'); - const fixturesPath = path.dirname(configPath); - const srcPath = path.join(fixturesPath, 'src'); - const indexPath = path.join(srcPath, 'index.ts'); + const configPath = 'fixtures/stencil.config.ts'; + const configPath2 = 'fixtures/stencil.config2.ts'; let sys: d.CompilerSystem; + const noTsConfigPath = 'no-ts-dir/stencil.config.ts'; beforeEach(() => { - sys = createSystem(); - sys.writeFileSync(configPath, ``); - sys.writeFileSync(configPath2, ``); - sys.createDirSync(fixturesPath); - sys.createDirSync(srcPath); - sys.writeFileSync(indexPath, `console.log('fixture');`); + sys = createNodeSys(); + + const tsconfig = JSON.stringify( + { + compilerOptions: { + allowSyntheticDefaultImports: true, + experimentalDecorators: true, + lib: ['dom', 'es2015'], + moduleResolution: 'node', + module: 'esnext', + target: 'es2017', + jsx: 'react', + jsxFactory: 'h', + jsxFragmentFactory: 'Fragment', + }, + include: ['src'], + }, + null, + 2 + ); + + mock({ + [configPath]: mock.load(path.resolve(__dirname, configPath)), + [configPath2]: mock.load(path.resolve(__dirname, configPath2)), + 'fixtures/tsconfig.json': tsconfig, + [noTsConfigPath]: mock.load(path.resolve(__dirname, configPath)), + ...getMockFSPatch(mock), + }); + }); + + afterEach(() => { + mock.restore(); }); it("merges a user's configuration with a stencil.config file on disk", async () => { @@ -75,36 +102,25 @@ describe('load config', () => { expect(actualConfig.configPath).toBe(null); }); - it('warns that a tsconfig file could not be found when "initTsConfig" set', async () => { - const loadedConfig = await loadConfig({ initTsConfig: true }); - - expect(loadedConfig.diagnostics).toHaveLength(1); - expect(loadedConfig.diagnostics[0]).toEqual({ - absFilePath: '/tsconfig.json', - code: '18003', - columnNumber: undefined, - header: 'TypeScript', - language: 'typescript', - level: 'warn', - lineNumber: undefined, - lines: [], - messageText: - "No inputs were found in config file '/tsconfig.json'. Specified 'include' paths were '[\"src\"]' and 'exclude' paths were '[]'.", - relFilePath: undefined, - type: 'typescript', - }); + it('creates a tsconfig file when "initTsConfig" set', async () => { + const tsconfigPath = path.resolve(path.dirname(noTsConfigPath), 'tsconfig.json'); + expect(fs.existsSync(tsconfigPath)).toBe(false); + const loadedConfig = await loadConfig({ initTsConfig: true, configPath: noTsConfigPath }); + expect(fs.existsSync(tsconfigPath)).toBe(true); + expect(loadedConfig.diagnostics).toHaveLength(0); }); it('errors that a tsconfig file could not be created when "initTsConfig" isn\'t present', async () => { - const loadedConfig = await loadConfig({}); + const loadedConfig = await loadConfig({ configPath: noTsConfigPath }); expect(loadedConfig.diagnostics).toHaveLength(1); expect(loadedConfig.diagnostics[0]).toEqual({ absFilePath: null, header: 'Missing tsconfig.json', level: 'error', lines: [], - messageText: - 'Unable to load TypeScript config file. Please create a "tsconfig.json" file within the "/" directory.', + messageText: `Unable to load TypeScript config file. Please create a "tsconfig.json" file within the "./${path.dirname( + noTsConfigPath + )}" directory.`, relFilePath: null, type: 'build', }); @@ -112,7 +128,7 @@ describe('load config', () => { }); describe('no initialization argument', () => { - it('errors that a tsconfig file could not be created', async () => { + it('errors that a tsconfig file cannot be found', async () => { const loadConfigResults = await loadConfig(); expect(loadConfigResults.diagnostics).toHaveLength(1); expect(loadConfigResults.diagnostics[0]).toEqual({ @@ -120,8 +136,9 @@ describe('load config', () => { header: 'Missing tsconfig.json', level: 'error', lines: [], - messageText: - 'Unable to load TypeScript config file. Please create a "tsconfig.json" file within the "/" directory.', + messageText: expect.stringMatching( + `Unable to load TypeScript config file. Please create a "tsconfig.json" file within the` + ), relFilePath: null, type: 'build', }); diff --git a/src/compiler/config/test/validate-config-sourcemap.spec.ts b/src/compiler/config/test/validate-config-sourcemap.spec.ts index 1028f0d0e27..2e6120ff81f 100644 --- a/src/compiler/config/test/validate-config-sourcemap.spec.ts +++ b/src/compiler/config/test/validate-config-sourcemap.spec.ts @@ -1,13 +1,15 @@ import { mockLoadConfigInit } from '@stencil/core/testing'; +import { getMockFSPatch } from '@stencil/core/testing'; +import { createNodeSys } from '@sys-api-node'; +import mock from 'mock-fs'; import path from 'path'; -import { createSystem } from '../../../compiler/sys/stencil-sys'; import type * as d from '../../../declarations'; import { loadConfig } from '../load-config'; describe('stencil config - sourceMap option', () => { - const configPath = require.resolve('./fixtures/stencil.config.ts'); - const fixturesPath = path.dirname(configPath); + const fixturesDir = 'fixtures'; + const configPath = path.join(fixturesDir, 'stencil.config.ts'); let sys: d.CompilerSystem; /** @@ -29,9 +31,16 @@ describe('stencil config - sourceMap option', () => { }; beforeEach(() => { - sys = createSystem(); - sys.writeFileSync(configPath, ``); - sys.createDirSync(fixturesPath); + sys = createNodeSys(); + + mock({ + [configPath]: mock.load(path.resolve(__dirname, configPath)), + ...getMockFSPatch(mock), + }); + }); + + afterEach(() => { + mock.restore(); }); it('sets sourceMap options to true in tsconfig', async () => { diff --git a/src/compiler/config/transpile-options.ts b/src/compiler/config/transpile-options.ts index a77b83c5ddf..db5d7c4c6e5 100644 --- a/src/compiler/config/transpile-options.ts +++ b/src/compiler/config/transpile-options.ts @@ -11,8 +11,7 @@ import type { TranspileResults, } from '../../declarations'; import { STENCIL_INTERNAL_CLIENT_ID } from '../bundle/entry-alias-ids'; -import { IS_NODE_ENV, requireFunc } from '../sys/environment'; -import { createSystem } from '../sys/stencil-sys'; +import { requireFunc } from '../sys/environment'; import { parseImportPath } from '../transformers/stencil-import-path'; export const getTranspileResults = (code: string, input: TranspileOptions) => { @@ -60,11 +59,7 @@ export const getTranspileConfig = (input: TranspileOptions): TranspileConfig => if (input.sys) { transpileCtx.sys = input.sys; } else if (!transpileCtx.sys) { - if (IS_NODE_ENV) { - transpileCtx.sys = requireFunc('../sys/node/index.js').createNodeSys(); - } else { - transpileCtx.sys = createSystem(); - } + transpileCtx.sys = requireFunc('../sys/node/index.js').createNodeSys(); } const compileOpts: TranspileOptions = { diff --git a/src/compiler/config/validate-config.ts b/src/compiler/config/validate-config.ts index 2f93f15a89c..6a9f189de5a 100644 --- a/src/compiler/config/validate-config.ts +++ b/src/compiler/config/validate-config.ts @@ -1,8 +1,7 @@ +import { createNodeLogger, createNodeSys } from '@sys-api-node'; import { buildError, isBoolean, isNumber, isString, sortBy } from '@utils'; import { ConfigBundle, Diagnostic, LoadConfigInit, UnvalidatedConfig, ValidatedConfig } from '../../declarations'; -import { createLogger } from '../sys/logger/console-logger'; -import { createSystem } from '../sys/stencil-sys'; import { setBooleanConfig } from './config-utils'; import { validateOutputTargets } from './outputs'; import { validateDevServer } from './validate-dev-server'; @@ -75,7 +74,12 @@ export const validateConfig = ( const config = Object.assign({}, userConfig); - const logger = bootstrapConfig.logger || config.logger || createLogger(); + const logger = + bootstrapConfig.logger || + config.logger || + createNodeLogger({ + process, + }); const validatedConfig: ValidatedConfig = { devServer: {}, // assign `devServer` before spreading `config`, in the event 'devServer' is not a key on `config` @@ -85,7 +89,7 @@ export const validateConfig = ( hydratedFlag: validateHydrated(config), logger, outputTargets: config.outputTargets ?? [], - sys: config.sys ?? bootstrapConfig.sys ?? createSystem({ logger }), + sys: config.sys ?? bootstrapConfig.sys ?? createNodeSys({ logger }), testing: config.testing ?? {}, transformAliasedImportPaths: userConfig.transformAliasedImportPaths ?? false, rollupConfig: validateRollupConfig(config), diff --git a/src/compiler/index.ts b/src/compiler/index.ts index fc2a325f06e..d6af5da098b 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -1,22 +1,12 @@ import ts from 'typescript'; -import { IS_WEB_WORKER_ENV } from './sys/environment'; -import { createSystem } from './sys/stencil-sys'; -import { initWebWorkerThread } from './sys/worker/web-worker-thread'; -import { createWorkerMessageHandler } from './worker/worker-thread'; -export { FsWriteResults } from './sys/in-memory-fs'; - -if (IS_WEB_WORKER_ENV) { - initWebWorkerThread(createWorkerMessageHandler(createSystem())); -} - export { buildId, vermoji, version, versions } from '../version'; export { createCompiler } from './compiler'; export { loadConfig } from './config/load-config'; export { optimizeCss } from './optimize/optimize-css'; export { optimizeJs } from './optimize/optimize-js'; export { createPrerenderer } from './prerender/prerender-main'; -export { path } from './sys/modules/path'; +export { FsWriteResults } from './sys/in-memory-fs'; export { nodeRequire } from './sys/node-require'; export { createSystem } from './sys/stencil-sys'; export { transpile, transpileSync } from './transpile'; diff --git a/src/compiler/output-targets/test/custom-elements-types.spec.ts b/src/compiler/output-targets/test/custom-elements-types.spec.ts index c9681dc5c8e..5529672d1ba 100644 --- a/src/compiler/output-targets/test/custom-elements-types.spec.ts +++ b/src/compiler/output-targets/test/custom-elements-types.spec.ts @@ -1,4 +1,3 @@ -import { path } from '@stencil/core/compiler'; import { mockBuildCtx, mockCompilerCtx, @@ -7,6 +6,7 @@ import { mockValidatedConfig, } from '@stencil/core/testing'; import { DIST_CUSTOM_ELEMENTS } from '@utils'; +import path from 'path'; import { join, relative } from 'path'; import type * as d from '../../../declarations'; diff --git a/src/compiler/output-targets/test/output-targets-dist-custom-elements.spec.ts b/src/compiler/output-targets/test/output-targets-dist-custom-elements.spec.ts index 764de74a237..231e4716a73 100644 --- a/src/compiler/output-targets/test/output-targets-dist-custom-elements.spec.ts +++ b/src/compiler/output-targets/test/output-targets-dist-custom-elements.spec.ts @@ -1,4 +1,3 @@ -import { path } from '@stencil/core/compiler'; import { mockBuildCtx, mockCompilerCtx, @@ -7,6 +6,7 @@ import { mockValidatedConfig, } from '@stencil/core/testing'; import { DIST_CUSTOM_ELEMENTS } from '@utils'; +import path from 'path'; import type * as d from '../../../declarations'; import { OutputTargetDistCustomElements } from '../../../declarations'; diff --git a/src/compiler/sys/config.ts b/src/compiler/sys/config.ts index 7b3b6ef927a..2b06d4147e3 100644 --- a/src/compiler/sys/config.ts +++ b/src/compiler/sys/config.ts @@ -1,7 +1,6 @@ import { createConfigFlags } from '../../cli/config-flags'; import type * as d from '../../declarations'; import { validateConfig } from '../config/validate-config'; -import { setPlatformPath } from '../sys/modules/path'; import { createLogger } from './logger/console-logger'; export const getConfig = (userConfig: d.Config): d.ValidatedConfig => { @@ -11,8 +10,6 @@ export const getConfig = (userConfig: d.Config): d.ValidatedConfig => { userConfig.flags = flags; const config: d.ValidatedConfig = validateConfig(userConfig, {}).config; - setPlatformPath(config.sys.platformPath); - if (config.flags.debug || config.flags.verbose) { config.logLevel = 'debug'; } else if (config.flags.logLevel) { diff --git a/src/compiler/sys/fs-patch.ts b/src/compiler/sys/fs-patch.ts deleted file mode 100644 index aec1b278976..00000000000 --- a/src/compiler/sys/fs-patch.ts +++ /dev/null @@ -1,9 +0,0 @@ -import fs from 'fs'; - -import type * as d from '../../declarations'; -import { FsObj } from './modules/fs'; - -export const patchFs = (userSys: d.CompilerSystem) => { - const fsObj = fs as any as FsObj; - Object.assign(fsObj.__sys, userSys); -}; diff --git a/src/compiler/sys/modules/compiler-deps.ts b/src/compiler/sys/modules/compiler-deps.ts deleted file mode 100644 index 849bf602ba2..00000000000 --- a/src/compiler/sys/modules/compiler-deps.ts +++ /dev/null @@ -1,11 +0,0 @@ -import rollupCommonjsPlugin from '@rollup/plugin-commonjs'; -import rollupJsonPlugin from '@rollup/plugin-json'; -import rollupNodeResolvePlugin from '@rollup/plugin-node-resolve'; -import rollupReplacePlugin from '@rollup/plugin-replace'; -import rollupPluginUtils from '@rollup/pluginutils'; - -export { rollupCommonjsPlugin }; -export { rollupJsonPlugin }; -export { rollupNodeResolvePlugin }; -export { rollupPluginUtils }; -export { rollupReplacePlugin }; diff --git a/src/compiler/sys/modules/crypto.ts b/src/compiler/sys/modules/crypto.ts deleted file mode 100644 index 21e9dc48217..00000000000 --- a/src/compiler/sys/modules/crypto.ts +++ /dev/null @@ -1,3 +0,0 @@ -//@ts-ignore -import sha256 from 'hash.js/lib/hash/sha/256'; -export const createHash = () => sha256(); diff --git a/src/compiler/sys/modules/dts-core.ts b/src/compiler/sys/modules/dts-core.ts deleted file mode 100644 index e83fd82d919..00000000000 --- a/src/compiler/sys/modules/dts-core.ts +++ /dev/null @@ -1,2 +0,0 @@ -const coreDts = `/* core dts placeholder */`; -export default coreDts; diff --git a/src/compiler/sys/modules/dts-internal.ts b/src/compiler/sys/modules/dts-internal.ts deleted file mode 100644 index 9eecb3959be..00000000000 --- a/src/compiler/sys/modules/dts-internal.ts +++ /dev/null @@ -1,2 +0,0 @@ -const internalDts = `/* internal dts placeholder */`; -export default internalDts; diff --git a/src/compiler/sys/modules/events.ts b/src/compiler/sys/modules/events.ts deleted file mode 100644 index fca2890cd83..00000000000 --- a/src/compiler/sys/modules/events.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class EventEmitter {} - -export default { - EventEmitter, -}; diff --git a/src/compiler/sys/modules/fs.ts b/src/compiler/sys/modules/fs.ts deleted file mode 100644 index fc903f0c4f0..00000000000 --- a/src/compiler/sys/modules/fs.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { normalizePath } from '@utils'; -import { basename } from 'path'; - -import type * as d from '../../../declarations'; -import { promisify } from './util'; - -export interface FsObj { - __sys: d.CompilerSystem; - [key: string]: any; -} - -class FsError extends Error { - constructor(public syscall: string, public path: string, public code: string = 'ENOENT', public errno: number = -2) { - super(`ENOENT: no such file or directory, ${syscall} '${path}'`); - } -} - -const fs: FsObj = { - __sys: {} as any, -}; - -export const exists = (fs.exists = (p: string, cb: any) => { - fs.__sys - .access(p) - .then(cb) - .catch(() => cb(false)); -}); - -// https://nodejs.org/api/util.html#util_custom_promisified_functions -(exists as any)[promisify.custom] = (p: string) => fs.__sys.access(p); - -export const existsSync = (fs.existsSync = (p: string) => { - // https://nodejs.org/api/fs.html#fs_fs_existssync_path - return fs.__sys.accessSync(p); -}); - -export const mkdir = (fs.mkdir = (p: string, opts: any, cb: any) => { - cb = typeof cb === 'function' ? cb : typeof opts === 'function' ? opts : null; - opts = typeof opts === 'function' ? undefined : opts; - fs.__sys - .createDir(p, opts) - .then((results) => { - if (cb) { - if (results.error) { - cb(new FsError('mkdir', p)); - } else { - cb(null); - } - } - }) - .catch((e) => { - cb && cb(e); - }); -}); - -export const mkdirSync = (fs.mkdirSync = (p: string, opts: any) => { - const results = fs.__sys.createDirSync(p, opts); - if (results.error) { - throw new FsError('mkdir', p); - } -}); - -export const readdirSync = (fs.readdirSync = (p: string) => { - // sys.readdirSync includes full paths - // but if fs.readdirSync was called, the expected - // nodejs results are of just the basename for each dir item - const dirItems = fs.__sys.readDirSync(p); - return dirItems.map((dirItem) => basename(dirItem)); -}); - -export const readFile = (fs.readFile = async (p: string, opts: any, cb: (err: any, data?: string) => void) => { - const encoding = typeof opts === 'object' ? opts.encoding : typeof opts === 'string' ? opts : 'utf-8'; - cb = typeof cb === 'function' ? cb : typeof opts === 'function' ? opts : null; - fs.__sys - .readFile(p, encoding) - .then((data) => { - if (cb) { - if (typeof data === 'string') { - cb(null, data); - } else { - cb(new FsError('open', p), data); - } - } - }) - .catch((e) => { - cb && cb(e); - }); -}); - -export const readFileSync = (fs.readFileSync = (p: string, opts: any) => { - const encoding = typeof opts === 'object' ? opts.encoding : typeof opts === 'string' ? opts : 'utf-8'; - const data = fs.__sys.readFileSync(p, encoding); - if (typeof data !== 'string') { - throw new FsError('open', p); - } - return data; -}); - -export const realpath = (fs.realpath = (p: string, opts: any, cb: (err: any, data?: string) => void) => { - cb = typeof cb === 'function' ? cb : typeof opts === 'function' ? opts : null; - fs.__sys - .realpath(p) - .then((results) => { - cb && cb(results.error, results.path); - }) - .catch((e) => { - cb && cb(e); - }); -}); - -export const realpathSync = (fs.realpathSync = (p: string) => { - const results = fs.__sys.realpathSync(p); - if (results.error) { - throw results.error; - } - return normalizePath(results.path); -}); - -export const statSync = (fs.statSync = (p: string) => { - const fsStats = fs.__sys.statSync(p); - if (fsStats.error) { - throw new FsError('statSync', p); - } - return { - isDirectory: () => fsStats.isDirectory, - isFile: () => fsStats.isFile, - isSymbolicLink: () => fsStats.isSymbolicLink, - size: fsStats.size, - mtimeMs: fsStats.mtimeMs, - }; -}); - -export const lstatSync = (fs.lstatSync = statSync); - -export const stat = (fs.stat = (p: string, opts: any, cb: any) => { - cb = typeof cb === 'function' ? cb : typeof opts === 'function' ? opts : null; - fs.__sys - .stat(p) - .then((fsStats) => { - if (cb) { - if (fsStats.error) { - cb(new FsError('stat', p)); - } else { - cb({ - isDirectory: () => fsStats.isDirectory, - isFile: () => fsStats.isFile, - isSymbolicLink: () => fsStats.isSymbolicLink, - size: fsStats.size, - mtimeMs: fsStats.mtimeMs, - }); - } - } - }) - .catch((e) => { - cb && cb(e); - }); -}); - -export const watch = (fs.watch = () => { - throw new Error(`fs.watch() not implemented`); -}); - -export const writeFile = (fs.writeFile = (p: string, data: string, opts: any, cb: any) => { - cb = typeof cb === 'function' ? cb : typeof opts === 'function' ? opts : null; - fs.__sys - .writeFile(p, data) - .then((writeResults) => { - if (cb) { - if (writeResults.error) { - cb(new FsError('writeFile', p)); - } else { - cb(null); - } - } - }) - .catch((e) => { - cb && cb(e); - }); -}); - -export default fs; diff --git a/src/compiler/sys/modules/index.ts b/src/compiler/sys/modules/index.ts deleted file mode 100644 index 870c9a20118..00000000000 --- a/src/compiler/sys/modules/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export * from './compiler-deps'; -export * from './crypto'; -export * from './dts-core'; -export * from './dts-internal'; -export * from './events'; -export * from './fs'; -export * from './module'; -export * from './os'; -export * from './path'; -export * from './stream'; -export * from './url'; -export * from './util'; diff --git a/src/compiler/sys/modules/os.ts b/src/compiler/sys/modules/os.ts deleted file mode 100644 index 9e234488a56..00000000000 --- a/src/compiler/sys/modules/os.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { OS_PLATFORM } from '../environment'; - -export const EOL = '\n'; - -export const platform = () => OS_PLATFORM; - -export default { - EOL, - platform, -}; diff --git a/src/compiler/sys/modules/path.ts b/src/compiler/sys/modules/path.ts deleted file mode 100644 index e6a623f888e..00000000000 --- a/src/compiler/sys/modules/path.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { normalizePath } from '@utils'; -import pathBrowserify from 'path-browserify'; - -import type * as d from '../../../declarations'; -import { IS_NODE_ENV, requireFunc } from '../environment'; - -export let basename: any; -export let dirname: any; -export let extname: any; -export let isAbsolute: any; -export let join: any; -export let normalize: any; -export let parse: any; -export let relative: any; -export let resolve: any; -export let sep: any; -export let delimiter: any; -export let posix: any; -export let win32: any; - -export const path: d.PlatformPath = {} as any; - -export const setPlatformPath = (platformPath: d.PlatformPath) => { - if (!platformPath) { - platformPath = pathBrowserify; - } - - Object.assign(path, platformPath); - - const normalizeOrg = path.normalize; - const joinOrg = path.join; - const relativeOrg = path.relative; - const resolveOrg = path.resolve; - - normalize = path.normalize = (...args: string[]) => normalizePath(normalizeOrg.apply(path, args)); - join = path.join = (...args: string[]) => normalizePath(joinOrg.apply(path, args)); - relative = path.relative = (...args: string[]) => normalizePath(relativeOrg.apply(path, args)); - resolve = path.resolve = (...args: string[]) => normalizePath(resolveOrg.apply(path, args)); - - basename = path.basename; - dirname = path.dirname; - extname = path.extname; - isAbsolute = path.isAbsolute; - parse = path.parse; - sep = path.sep; - delimiter = path.delimiter; - posix = path.posix; - if (path.win32) { - win32 = path.win32; - } else { - win32 = { ...posix }; - win32.sep = '\\'; - } -}; - -setPlatformPath(IS_NODE_ENV ? requireFunc('path') : pathBrowserify); - -export default path; diff --git a/src/compiler/sys/modules/stream.ts b/src/compiler/sys/modules/stream.ts deleted file mode 100644 index 26047624057..00000000000 --- a/src/compiler/sys/modules/stream.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const Stream = function () { - /**/ -}; - -export default { - Stream, -}; diff --git a/src/compiler/sys/modules/url.ts b/src/compiler/sys/modules/url.ts deleted file mode 100644 index cb794322f85..00000000000 --- a/src/compiler/sys/modules/url.ts +++ /dev/null @@ -1 +0,0 @@ -export const URL = globalThis.URL as any; diff --git a/src/compiler/sys/modules/util.ts b/src/compiler/sys/modules/util.ts deleted file mode 100644 index 640090c1c8f..00000000000 --- a/src/compiler/sys/modules/util.ts +++ /dev/null @@ -1,45 +0,0 @@ -export const inherits = (ctor: any, superCtor: any) => { - if (superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true, - }, - }); - } -}; - -export const inspect = (...args: any[]) => args.forEach((arg) => console.log(arg)); - -export const promisify = (fn: Function): (() => Promise) => { - if (typeof (fn as any)[promisify.custom] === 'function') { - // https://nodejs.org/api/util.html#util_custom_promisified_functions - return function (...args: any[]) { - return (fn as any)[promisify.custom].apply(this, args); - }; - } - - return function (...args: any[]) { - return new Promise((resolve, reject) => { - args.push((err: any, result: any) => { - if (err != null) { - reject(err); - } else { - resolve(result); - } - }); - fn.apply(this, args); - }); - }; -}; - -promisify.custom = Symbol('promisify.custom'); - -export default { - inherits, - inspect, - promisify, -}; diff --git a/src/compiler/sys/stencil-sys.ts b/src/compiler/sys/stencil-sys.ts index 08a6c7c1ebb..36ad4973d9b 100644 --- a/src/compiler/sys/stencil-sys.ts +++ b/src/compiler/sys/stencil-sys.ts @@ -22,7 +22,7 @@ import type { } from '../../declarations'; import { version } from '../../version'; import { buildEvents } from '../events'; -import { HAS_WEB_WORKER, IS_BROWSER_ENV, IS_WEB_WORKER_ENV } from './environment'; +import { HAS_WEB_WORKER, IS_BROWSER_ENV } from './environment'; import { createLogger } from './logger/console-logger'; import { resolveModuleIdAsync } from './resolve/resolve-module-async'; import { createWebWorkerMainController } from './worker/web-worker-main'; @@ -146,9 +146,6 @@ export const createSystem = (c?: { logger?: Logger }): CompilerSystem => { const getCurrentDirectory = () => '/'; const getCompilerExecutingPath = () => { - if (IS_WEB_WORKER_ENV) { - return location.href; - } return sys.getRemoteModuleUrl({ moduleId: '@stencil/core', path: 'compiler/stencil.min.js' }); }; diff --git a/src/compiler/transpile.ts b/src/compiler/transpile.ts index 859e07c6363..7ed1e1b39a1 100644 --- a/src/compiler/transpile.ts +++ b/src/compiler/transpile.ts @@ -1,4 +1,4 @@ -import { rollupPluginUtils } from '@compiler-deps'; +import rollupPluginUtils from '@rollup/pluginutils'; import type { Config, TransformCssToEsmInput, diff --git a/src/compiler/transpile/run-program.ts b/src/compiler/transpile/run-program.ts index 7a691895eb7..3506167bd6e 100644 --- a/src/compiler/transpile/run-program.ts +++ b/src/compiler/transpile/run-program.ts @@ -54,7 +54,7 @@ export const runTsProgram = async ( emittedDts.push(srcDtsPath); typesOutputTarget.forEach((o) => { - const distPath = join(o.typesDir, relativeEmitFilepath); + const distPath = normalizePath(join(normalizePath(o.typesDir), normalizePath(relativeEmitFilepath))); data = updateStencilTypesImports(o.typesDir, distPath, data); compilerCtx.fs.writeFile(distPath, data); }); @@ -147,17 +147,40 @@ export const runTsProgram = async ( return false; }; -const getRelativeDts = (config: d.Config, srcPath: string, emitDtsPath: string): string => { +/** + * Calculate a relative path for a `.d.ts` file, giving the location within + * the typedef output directory where we'd like to write it to disk. + * + * The correct relative path for a `.d.ts` file is basically given by the + * relative location of the _source_ file associated with the `.d.ts` file + * within the Stencil project's source directory. + * + * Thus, in order to calculate this, we take the path to the source file, the + * emit path calculated by typescript (which is going to be right next to the + * emit location for the JavaScript that the compiler emits for the source file) + * and we do a pairwise walk up the two paths, assembling path components as + * we go, until the source file path is equal to the configured source + * directory. Then the path components from the `emitDtsPath` can be reversed + * and re-assembled into a suitable relative path. + * + * @param config a Stencil configuration object + * @param srcPath the path to the source file for the `.d.ts` file of interest + * @param emitDtsPath the emit path for the `.d.ts` file calculated by + * TypeScript + * @returns a relative path to a suitable location where the typedef file can be + * written + */ +export const getRelativeDts = (config: d.Config, srcPath: string, emitDtsPath: string): string => { const parts: string[] = []; for (let i = 0; i < 30; i++) { - if (config.srcDir === srcPath) { + if (normalizePath(config.srcDir) === srcPath) { break; } const b = basename(emitDtsPath); parts.push(b); - emitDtsPath = join(emitDtsPath, '..'); - srcPath = normalizePath(join(srcPath, '..')); + emitDtsPath = normalizePath(join(emitDtsPath, '..')); + srcPath = normalizePath(join(normalizePath(srcPath), '..')); } - return join(...parts.reverse()); + return normalizePath(join(...parts.reverse())); }; diff --git a/src/compiler/transpile/test/run-program.spec.ts b/src/compiler/transpile/test/run-program.spec.ts new file mode 100644 index 00000000000..0227247338c --- /dev/null +++ b/src/compiler/transpile/test/run-program.spec.ts @@ -0,0 +1,47 @@ +import { mockValidatedConfig } from '@stencil/core/testing'; + +import { getRelativeDts } from '../run-program'; + +describe('run-program.ts', () => { + describe('getRelativeDts', () => { + it('should find the relative path to write a dts file to', () => { + const config = mockValidatedConfig({ srcDir: '/Testuser/stencil-project/src' }); + const foo = getRelativeDts( + config, + '/Testuser/stencil-project/src/index.ts', + '/Testuser/stencil-project/.stencil/index.d.ts' + ); + expect(foo).toBe('index.d.ts'); + }); + + it('should find the relative path to write a nested dts file to', () => { + const config = mockValidatedConfig({ srcDir: '/Testuser/stencil-project/src' }); + const foo = getRelativeDts( + config, + '/Testuser/stencil-project/src/components/index.ts', + '/Testuser/stencil-project/.stencil/components/index.d.ts' + ); + expect(foo).toBe('./components/index.d.ts'); + }); + + it('should find the relative path to write a dts file to (windows)', () => { + const config = mockValidatedConfig({ srcDir: 'C:\\Testuser\\stencil-project\\src' }); + const foo = getRelativeDts( + config, + 'C:/Testuser/stencil-project/src/index.ts', + 'C:/Testuser/stencil-project/.stencil/index.d.ts' + ); + expect(foo).toBe('index.d.ts'); + }); + + it('should find the relative path to write a nested dts file to (windows)', () => { + const config = mockValidatedConfig({ srcDir: 'C:\\Testuser\\stencil-project\\src' }); + const foo = getRelativeDts( + config, + 'C:/Testuser/stencil-project/src/components/index.ts', + 'C:/Testuser/stencil-project/.stencil/components/index.d.ts' + ); + expect(foo).toBe('./components/index.d.ts'); + }); + }); +}); diff --git a/src/testing/index.ts b/src/testing/index.ts index e42d7d8e316..b3956beb9f6 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -27,5 +27,5 @@ export { E2EElement, E2EPage, newE2EPage } from './puppeteer'; export { newSpecPage } from './spec-page'; export { transpile } from './test-transpile'; export { createTesting } from './testing'; -export { shuffleArray } from './testing-utils'; +export { getMockFSPatch, shuffleArray } from './testing-utils'; export type { EventSpy, SpecPage, Testing } from '@stencil/core/internal'; diff --git a/src/testing/testing-utils.ts b/src/testing/testing-utils.ts index 866c58a7540..fb786a942c9 100644 --- a/src/testing/testing-utils.ts +++ b/src/testing/testing-utils.ts @@ -1,6 +1,6 @@ import type * as d from '@stencil/core/internal'; import { isOutputTargetDistLazy, isOutputTargetWww } from '@utils'; -import { join, relative } from 'path'; +import { join, relative, resolve } from 'path'; import { InMemoryFileSystem } from '../compiler/sys/in-memory-fs'; @@ -191,3 +191,27 @@ export async function withSilentWarn(cb: SilentWarnFunc): Promise { console.warn = realWarn; return retVal; } + +/** + * This is a helper for using `mock-fs` in Jest. + * + * `mock-fs` replaces the node.js implementation of `fs` with a separate one + * which does filesystem operations against an in-memory filesystem instead of + * against the disk. + * + * This 'patch' consists of adding files to the in-memory filesystem from the + * disk (via `mock.load`) which are sometimes required by Jest between the + * `beforeEach` and `afterEach` calls in a test suite -- without adding these + * files to the in-memory filesystem the runtime require by Jest will fail. + * + * @param mock a `mock-fs` module, as imported in the particular test file + * where you want to mock the filesystem. + * @returns a filesystem manifest suitable for mocking out runtime + * dependencies of Jest + */ +export const getMockFSPatch = (mock: any) => ({ + 'node_modules/write-file-atomic': mock.load(resolve(process.cwd(), 'node_modules', 'write-file-atomic')), + 'node_modules/imurmurhash': mock.load(resolve(process.cwd(), 'node_modules', 'imurmurhash')), + 'node_modules/is-typedarray': mock.load(resolve(process.cwd(), 'node_modules', 'is-typedarray')), + 'node_modules/typedarray-to-buffer': mock.load(resolve(process.cwd(), 'node_modules', 'typedarray-to-buffer')), +}); diff --git a/tsconfig.json b/tsconfig.json index 6d4b37da759..21136ecf2c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,7 +28,6 @@ "paths": { "@app-data": ["src/app-data/index.ts"], "@app-globals": ["src/app-globals/index.ts"], - "@compiler-deps": ["src/compiler/sys/modules/compiler-deps.ts"], "@dev-server-process": ["src/dev-server/server-process.ts"], "@hydrate-factory": ["src/hydrate/runner/hydrate-factory.ts"], "@platform": ["src/client/index.ts"], @@ -57,7 +56,6 @@ "src/client/client-patch-browser.ts", "src/compiler/index.ts", "src/compiler/public.ts", - "src/compiler/sys/modules/index.ts", "src/dev-server/index.ts", "src/dev-server/client/index.ts", "src/dev-server/dev-server-client/index.ts",