From b09f233ca611b269a28bc70a370aca0dd78726d2 Mon Sep 17 00:00:00 2001 From: Alice Pote Date: Tue, 20 Feb 2024 13:17:01 -0500 Subject: [PATCH] chore(esbuild): build the internal module with esbuild (#5276) This adds the ability to build the modules within `internal/` with Esbuild, replicating the existing Rollup build. STENCIL-990 --- scripts/bundles/internal.ts | 2 +- scripts/bundles/plugins/alias-plugin.ts | 3 + scripts/esbuild/build.ts | 2 + scripts/esbuild/internal-app-data.ts | 56 +++++++++ scripts/esbuild/internal-platform-client.ts | 111 ++++++++++++++++++ scripts/esbuild/internal-platform-hydrate.ts | 89 ++++++++++++++ scripts/esbuild/internal-platform-testing.ts | 54 +++++++++ scripts/esbuild/internal.ts | 100 ++++++++++++++++ scripts/esbuild/testing.ts | 26 +--- scripts/esbuild/util.ts | 23 +++- scripts/test/validate-build.ts | 2 + src/client/client-load-module.ts | 11 +- src/compiler/style/css-to-esm.ts | 2 +- src/compiler/transformers/add-static-style.ts | 2 +- .../component-native/native-static-style.ts | 2 +- src/runtime/initialize-component.ts | 2 +- tsconfig.json | 3 +- 17 files changed, 462 insertions(+), 28 deletions(-) create mode 100644 scripts/esbuild/internal-app-data.ts create mode 100644 scripts/esbuild/internal-platform-client.ts create mode 100644 scripts/esbuild/internal-platform-hydrate.ts create mode 100644 scripts/esbuild/internal-platform-testing.ts create mode 100644 scripts/esbuild/internal.ts diff --git a/scripts/bundles/internal.ts b/scripts/bundles/internal.ts index f43350e181b..e0913b3670b 100644 --- a/scripts/bundles/internal.ts +++ b/scripts/bundles/internal.ts @@ -40,7 +40,7 @@ export async function internal(opts: BuildOptions) { return [...clientPlatformBundle, ...hydratePlatformBundles, ...testingPlatform, await internalAppData(opts)]; } -async function copyStencilInternalDts(opts: BuildOptions, outputInternalDir: string) { +export async function copyStencilInternalDts(opts: BuildOptions, outputInternalDir: string) { const declarationsInputDir = join(opts.buildDir, 'declarations'); // copy to @stencil/core/internal diff --git a/scripts/bundles/plugins/alias-plugin.ts b/scripts/bundles/plugins/alias-plugin.ts index 39d781a6ab4..d04bfacfa90 100644 --- a/scripts/bundles/plugins/alias-plugin.ts +++ b/scripts/bundles/plugins/alias-plugin.ts @@ -50,6 +50,9 @@ export function aliasPlugin(opts: BuildOptions): Plugin { if (id === '@utils') { return join(opts.buildDir, 'utils', 'index.js'); } + if (id === '@utils/shadow-css') { + return join(opts.buildDir, 'utils', 'shadow-css.js'); + } if (id === '@environment') { return join(opts.buildDir, 'compiler', 'sys', 'environment.js'); } diff --git a/scripts/esbuild/build.ts b/scripts/esbuild/build.ts index 34ea5616601..468aad0d826 100644 --- a/scripts/esbuild/build.ts +++ b/scripts/esbuild/build.ts @@ -2,6 +2,7 @@ import { getOptions } from '../utils/options'; import { buildCli } from './cli'; import { buildCompiler } from './compiler'; import { buildDevServer } from './dev-server'; +import { buildInternal } from './internal'; import { buildMockDoc } from './mock-doc'; import { buildScreenshot } from './screenshot'; import { buildSysNode } from './sys-node'; @@ -23,6 +24,7 @@ async function main() { buildScreenshot(opts), buildSysNode(opts), buildTesting(opts), + buildInternal(opts), ]); } diff --git a/scripts/esbuild/internal-app-data.ts b/scripts/esbuild/internal-app-data.ts new file mode 100644 index 00000000000..f6be6d8517d --- /dev/null +++ b/scripts/esbuild/internal-app-data.ts @@ -0,0 +1,56 @@ +import type { BuildOptions as ESBuildOptions } from 'esbuild'; +import fs from 'fs-extra'; +import { join } from 'path'; + +import { BuildOptions } from '../utils/options'; +import { writePkgJson } from '../utils/write-pkg-json'; +import { getBaseEsbuildOptions } from './util'; + +/** + * Get an object containing ESbuild options to build the internal app data + * file. This function also performs relevant side-effects, like writing a + * `package.json` file to disk. + * + * @param opts build options + * @returns a Promise wrapping an array of ESbuild option objects + */ +export async function getInternalAppDataBundles(opts: BuildOptions): Promise { + const appDataBuildDir = join(opts.buildDir, 'app-data'); + const appDataSrcDir = join(opts.srcDir, 'app-data'); + const outputInternalAppDataDir = join(opts.output.internalDir, 'app-data'); + + await fs.emptyDir(outputInternalAppDataDir); + + // copy @stencil/core/internal/app-data/index.d.ts + await fs.copyFile(join(appDataBuildDir, 'index.d.ts'), join(outputInternalAppDataDir, 'index.d.ts')); + + // write @stencil/core/internal/app-data/package.json + writePkgJson(opts, outputInternalAppDataDir, { + name: '@stencil/core/internal/app-data', + description: 'Used for default app data and build conditionals within builds.', + main: 'index.cjs', + module: 'index.js', + types: 'index.d.ts', + sideEffects: false, + }); + + const appDataBaseOptions: ESBuildOptions = { + ...getBaseEsbuildOptions(), + entryPoints: [join(appDataSrcDir, 'index.ts')], + platform: 'node', + }; + + const appDataESM: ESBuildOptions = { + ...appDataBaseOptions, + format: 'esm', + outfile: join(outputInternalAppDataDir, 'index.js'), + }; + + const appDataCJS: ESBuildOptions = { + ...appDataBaseOptions, + format: 'cjs', + outfile: join(outputInternalAppDataDir, 'index.cjs'), + }; + + return [appDataESM, appDataCJS]; +} diff --git a/scripts/esbuild/internal-platform-client.ts b/scripts/esbuild/internal-platform-client.ts new file mode 100644 index 00000000000..a94402d54ba --- /dev/null +++ b/scripts/esbuild/internal-platform-client.ts @@ -0,0 +1,111 @@ +import type { BuildOptions as ESBuildOptions } from 'esbuild'; +import { replace } from 'esbuild-plugin-replace'; +import fs from 'fs-extra'; +import glob from 'glob'; +import { join } from 'path'; + +import { getBanner } from '../utils/banner'; +import { BuildOptions, createReplaceData } from '../utils/options'; +import { writePkgJson } from '../utils/write-pkg-json'; +import { externalAlias, getBaseEsbuildOptions, getEsbuildAliases, getEsbuildExternalModules } from './util'; + +/** + * Create objects containing ESbuild options for the two bundles which need to + * be written to `internal/client`. This also performs relevant side-effects, + * like clearing out the directory and writing a `package.json` script to disk. + * + * @param opts build options + * @returns an array of ESBuild option objects + */ +export async function getInternalClientBundle(opts: BuildOptions): Promise { + const inputClientDir = join(opts.srcDir, 'client'); + const outputInternalClientDir = join(opts.output.internalDir, 'client'); + const outputInternalClientPolyfillsDir = join(outputInternalClientDir, 'polyfills'); + + await fs.emptyDir(outputInternalClientDir); + await fs.emptyDir(outputInternalClientPolyfillsDir); + + await copyPolyfills(opts, outputInternalClientPolyfillsDir); + + // write @stencil/core/internal/client/package.json + writePkgJson(opts, outputInternalClientDir, { + name: '@stencil/core/internal/client', + description: + 'Stencil internal client platform to be imported by the Stencil Compiler and internal runtime. Breaking changes can and will happen at any time.', + main: 'index.js', + sideEffects: false, + }); + + const internalClientAliases = getEsbuildAliases(); + internalClientAliases['@platform'] = join(inputClientDir, 'index.ts'); + + const clientExternal = getEsbuildExternalModules(opts, opts.output.internalDir); + + const internalClientBundle: ESBuildOptions = { + ...getBaseEsbuildOptions(), + entryPoints: [join(inputClientDir, 'index.ts')], + format: 'esm', + outfile: join(outputInternalClientDir, 'index.js'), + platform: 'node', + external: clientExternal, + alias: internalClientAliases, + banner: { + js: getBanner(opts, 'Stencil Client Platform'), + }, + plugins: [ + replace(createReplaceData(opts)), + externalAlias('@app-data', '@stencil/core/internal/app-data'), + externalAlias('@utils/shadow-css', './shadow-css.js'), + // we want to get the esm, not the cjs, since we're creating an esm + // bundle here + externalAlias('@stencil/core/mock-doc', '../../mock-doc/index.js'), + ], + }; + + const patchBrowserAliases = getEsbuildAliases(); + + const polyfills = await fs.readdir(join(opts.srcDir, 'client', 'polyfills')); + for (const polyFillFile of polyfills) { + patchBrowserAliases[join('./polyfills', polyFillFile)] = join(opts.srcDir, 'client', 'polyfills'); + } + + const patchBrowserExternal = [ + ...getEsbuildExternalModules(opts, opts.output.internalDir), + '@stencil/core', + '@stencil/core/mock-doc', + ]; + + const internalClientPatchBrowserBundle: ESBuildOptions = { + ...getBaseEsbuildOptions(), + entryPoints: [join(inputClientDir, 'client-patch-browser.ts')], + format: 'esm', + outfile: join(outputInternalClientDir, 'patch-browser.js'), + platform: 'node', + external: patchBrowserExternal, + alias: patchBrowserAliases, + banner: { + js: getBanner(opts, 'Stencil Client Patch Browser'), + }, + plugins: [ + replace(createReplaceData(opts)), + externalAlias('@platform', '@stencil/core'), + externalAlias('@app-data', '@stencil/core/internal/app-data'), + ], + }; + + return [internalClientBundle, internalClientPatchBrowserBundle]; +} + +async function copyPolyfills(opts: BuildOptions, outputInternalClientPolyfillsDir: string) { + const srcPolyfillsDir = join(opts.srcDir, 'client', 'polyfills'); + + const srcPolyfillFiles = glob.sync('*.js', { cwd: srcPolyfillsDir }); + + await Promise.all( + srcPolyfillFiles.map(async (fileName) => { + const src = join(srcPolyfillsDir, fileName); + const dest = join(outputInternalClientPolyfillsDir, fileName); + await fs.copyFile(src, dest); + }), + ); +} diff --git a/scripts/esbuild/internal-platform-hydrate.ts b/scripts/esbuild/internal-platform-hydrate.ts new file mode 100644 index 00000000000..0ce61ebd5b9 --- /dev/null +++ b/scripts/esbuild/internal-platform-hydrate.ts @@ -0,0 +1,89 @@ +import type { BuildOptions as ESBuildOptions } from 'esbuild'; +import fs from 'fs-extra'; +import { join } from 'path'; + +import { getBanner } from '../utils/banner'; +import { bundleDts } from '../utils/bundle-dts'; +import { BuildOptions } from '../utils/options'; +import { writePkgJson } from '../utils/write-pkg-json'; +import { externalAlias, getBaseEsbuildOptions, getEsbuildAliases, getEsbuildExternalModules } from './util'; + +/** + * Create objects containing ESbuild options for the two bundles comprising + * the hydrate platform. This also performs relevant side-effects, like + * clearing out a directory and writing a `package.json` script to disk. + * + * @param opts build options + * @returns an array of ESBuild option objects + */ +export async function getInternalPlatformHydrateBundles(opts: BuildOptions): Promise { + const inputHydrateDir = join(opts.buildDir, 'hydrate'); + const hydrateSrcDir = join(opts.srcDir, 'hydrate'); + const outputInternalHydrateDir = join(opts.output.internalDir, 'hydrate'); + + await fs.emptyDir(outputInternalHydrateDir); + + // write @stencil/core/internal/hydrate/package.json + writePkgJson(opts, outputInternalHydrateDir, { + name: '@stencil/core/internal/hydrate', + description: + 'Stencil internal hydrate platform to be imported by the Stencil Compiler. Breaking changes can and will happen at any time.', + main: 'index.js', + }); + + await createHydrateRunnerDtsBundle(opts, inputHydrateDir, outputInternalHydrateDir); + + const hydratePlatformInput = join(hydrateSrcDir, 'platform', 'index.js'); + + const external = [...getEsbuildExternalModules(opts, outputInternalHydrateDir), '@stencil/core/mock-doc']; + + const internalHydrateAliases = getEsbuildAliases(); + internalHydrateAliases['@platform'] = hydratePlatformInput; + + const internalHydratePlatformBundle: ESBuildOptions = { + ...getBaseEsbuildOptions(), + entryPoints: [hydratePlatformInput], + format: 'esm', + platform: 'node', + outfile: join(outputInternalHydrateDir, 'index.js'), + external, + alias: internalHydrateAliases, + banner: { + js: getBanner(opts, 'Stencil Hydrate Platform'), + }, + plugins: [ + externalAlias('@utils/shadow-css', '../client/shadow-css.js'), + externalAlias('@app-data', '@stencil/core/internal/app-data'), + // this needs to be externalized and also pointed at the esm version + externalAlias('@stencil/core/mock-doc', '../../mock-doc/index.js'), + ], + }; + + const internalHydrateRunnerBundle: ESBuildOptions = { + ...getBaseEsbuildOptions(), + entryPoints: [join(hydrateSrcDir, 'runner', 'index.js')], + external, + format: 'esm', + platform: 'node', + outfile: join(outputInternalHydrateDir, 'runner.js'), + banner: { + js: getBanner(opts, 'Stencil Hydrate Runner'), + }, + plugins: [ + externalAlias('@utils/shadow-css', '../client/shadow-css.js'), + externalAlias('@app-data', '@stencil/core/internal/app-data'), + externalAlias('@hydrate-factory', '@stencil/core/hydrate-factory'), + ], + }; + + return [internalHydratePlatformBundle, internalHydrateRunnerBundle]; +} + +async function createHydrateRunnerDtsBundle(opts: BuildOptions, inputHydrateDir: string, outputDir: string) { + // bundle @stencil/core/internal/hydrate/runner.d.ts + const dtsEntry = join(inputHydrateDir, 'runner', 'index.d.ts'); + const dtsContent = await bundleDts(opts, dtsEntry); + + const outputPath = join(outputDir, 'runner.d.ts'); + await fs.writeFile(outputPath, dtsContent); +} diff --git a/scripts/esbuild/internal-platform-testing.ts b/scripts/esbuild/internal-platform-testing.ts new file mode 100644 index 00000000000..17c2ca1d9ca --- /dev/null +++ b/scripts/esbuild/internal-platform-testing.ts @@ -0,0 +1,54 @@ +import type { BuildOptions as ESBuildOptions } from 'esbuild'; +import fs from 'fs-extra'; +import { join } from 'path'; + +import { BuildOptions } from '../utils/options'; +import { writePkgJson } from '../utils/write-pkg-json'; +import { externalAlias, getBaseEsbuildOptions, getEsbuildAliases, getEsbuildExternalModules } from './util'; + +/** + * Get an ESBuild configuration object for the internal testing bundle. This + * function also has side-effects which set things up for the bundle to be built + * correctly, like writing a `package.json` file to disk. + * + * @param opts build options + * @returns a promise wrapping an object holding options for ESBuild + */ +export async function getInternalTestingBundle(opts: BuildOptions): Promise { + const inputTestingPlatform = join(opts.srcDir, 'testing', 'platform', 'index.ts'); + const outputTestingPlatformDir = join(opts.output.internalDir, 'testing'); + + await fs.emptyDir(outputTestingPlatformDir); + + // write @stencil/core/internal/testing/package.json + writePkgJson(opts, outputTestingPlatformDir, { + name: '@stencil/core/internal/testing', + description: + 'Stencil internal testing platform to be imported by the Stencil Compiler. Breaking changes can and will happen at any time.', + main: 'index.js', + }); + + const internalTestingAliases = { + ...getEsbuildAliases(), + '@platform': inputTestingPlatform, + }; + + const external = [...getEsbuildExternalModules(opts, opts.output.internalDir), '@stencil/core/mock-doc']; + + const internalTestingBuildOptions: ESBuildOptions = { + ...getBaseEsbuildOptions(), + entryPoints: [inputTestingPlatform], + bundle: true, + format: 'cjs', + outfile: join(outputTestingPlatformDir, 'index.js'), + platform: 'node', + logLevel: 'info', + external, + alias: internalTestingAliases, + plugins: [ + externalAlias('@app-data', '@stencil/core/internal/app-data'), + externalAlias('@utils/shadow-css', '../client/shadow-css.js'), + ], + }; + return internalTestingBuildOptions; +} diff --git a/scripts/esbuild/internal.ts b/scripts/esbuild/internal.ts new file mode 100644 index 00000000000..9e33fa933cb --- /dev/null +++ b/scripts/esbuild/internal.ts @@ -0,0 +1,100 @@ +import type { BuildOptions as ESBuildOptions } from 'esbuild'; +import fs from 'fs-extra'; +import { join } from 'path'; + +import { copyStencilInternalDts } from '../bundles/internal'; +import type { BuildOptions } from '../utils/options'; +import { writePkgJson } from '../utils/write-pkg-json'; +import { getInternalAppDataBundles } from './internal-app-data'; +import { getInternalClientBundle } from './internal-platform-client'; +import { getInternalPlatformHydrateBundles } from './internal-platform-hydrate'; +import { getInternalTestingBundle } from './internal-platform-testing'; +import { getBaseEsbuildOptions, runBuilds } from './util'; + +/** + * Run the build for the `internal/` directory, copying and modifying files + * as-needed while also creating and then building the various bundles that need + * to be written to `internal/`. + * + * @param opts Build options for the current build + * @returns a Promise wrapping the state of the build + */ +export async function buildInternal(opts: BuildOptions) { + const inputInternalDir = join(opts.buildDir, 'internal'); + + await fs.emptyDir(opts.output.internalDir); + + await copyStencilInternalDts(opts, opts.output.internalDir); + + await copyUtilsDtsFiles(opts); + + await copyStencilCoreEntry(opts); + + // copy @stencil/core/internal default entry, which defaults to client + // but we're not exposing all of Stencil's internal code (only the types) + await fs.copyFile(join(inputInternalDir, 'default.js'), join(opts.output.internalDir, 'index.js')); + + // write @stencil/core/internal/package.json + writePkgJson(opts, opts.output.internalDir, { + name: '@stencil/core/internal', + description: + 'Stencil internals only to be imported by the Stencil Compiler. Breaking changes can and will happen at any time.', + main: 'index.js', + types: 'index.d.ts', + sideEffects: false, + }); + + // this is used in several of our bundles, so we bundle it here in one spot + const shadowCSSBundle: ESBuildOptions = { + ...getBaseEsbuildOptions(), + entryPoints: [join(opts.srcDir, 'utils', 'shadow-css.ts')], + format: 'esm', + outfile: join(opts.output.internalDir, 'client', 'shadow-css.js'), + platform: 'node', + }; + + const clientPlatformBundle = await getInternalClientBundle(opts); + const hydratePlatformBundles = await getInternalPlatformHydrateBundles(opts); + const appDataBundles = await getInternalAppDataBundles(opts); + const internalTestingBundle = await getInternalTestingBundle(opts); + + return runBuilds( + [shadowCSSBundle, ...clientPlatformBundle, ...hydratePlatformBundles, internalTestingBundle, ...appDataBundles], + opts, + ); +} + +async function copyStencilCoreEntry(opts: BuildOptions) { + // write @stencil/core entry + const stencilCoreSrcDir = join(opts.srcDir, 'internal', 'stencil-core'); + const stencilCoreDstDir = join(opts.output.internalDir, 'stencil-core'); + await fs.ensureDir(stencilCoreDstDir); + await fs.copy(stencilCoreSrcDir, stencilCoreDstDir); +} + +/** + * Copy `.d.ts` files built from `src/utils` to `internal/utils` so that types + * exported from utility modules can be referenced by other typedefs (in + * particular by our declarations). + * + * Some modules within `@utils` incorporate external types which aren't bundled + * so we selectively copy only `.d.ts` files which are 1) standalone and 2) export + * a type that other modules in the codebase (in, for instance, `src/compiler/` + * or `src/cli/`) depend on. + * + * @param opts options for the rollup build + */ +const copyUtilsDtsFiles = async (opts: BuildOptions) => { + const outputDirPath = join(opts.output.internalDir, 'utils'); + await fs.ensureDir(outputDirPath); + + // copy the `.d.ts` file corresponding to `src/utils/result.ts` + const resultDtsFilePath = join(opts.buildDir, 'utils', 'result.d.ts'); + const resultDtsOutputFilePath = join(opts.output.internalDir, 'utils', 'result.d.ts'); + await fs.copyFile(resultDtsFilePath, resultDtsOutputFilePath); + + const utilsIndexDtsPath = join(opts.output.internalDir, 'utils', 'index.d.ts'); + // here we write a simple module that re-exports `./result` so that imports + // elsewhere like `import { result } from '@utils'` will resolve correctly + await fs.writeFile(utilsIndexDtsPath, `export * as result from "./result"`); +}; diff --git a/scripts/esbuild/testing.ts b/scripts/esbuild/testing.ts index d321f18249e..935bc5633f1 100644 --- a/scripts/esbuild/testing.ts +++ b/scripts/esbuild/testing.ts @@ -6,7 +6,7 @@ import { copyTestingInternalDts } from '../bundles/testing'; import { getBanner } from '../utils/banner'; import type { BuildOptions } from '../utils/options'; import { writePkgJson } from '../utils/write-pkg-json'; -import { getBaseEsbuildOptions, getEsbuildAliases, getEsbuildExternalModules, runBuilds } from './util'; +import { externalAlias, getBaseEsbuildOptions, getEsbuildAliases, getEsbuildExternalModules, runBuilds } from './util'; const EXTERNAL_TESTING_MODULES = [ 'constants', @@ -59,11 +59,11 @@ export async function buildTesting(opts: BuildOptions) { alias: getEsbuildAliases(), banner: { js: getBanner(opts, `Stencil Testing`, true) }, plugins: [ - externalAliases('@app-data', '@stencil/core/internal/app-data'), - externalAliases('@platform', '@stencil/core/internal/testing'), - externalAliases('../internal/testing/index.js', '@stencil/core/internal/testing'), - externalAliases('@stencil/core/dev-server', '../dev-server/index.js'), - externalAliases('@stencil/core/mock-doc', '../mock-doc/index.cjs'), + externalAlias('@app-data', '@stencil/core/internal/app-data'), + externalAlias('@platform', '@stencil/core/internal/testing'), + externalAlias('../internal/testing/index.js', '@stencil/core/internal/testing'), + externalAlias('@stencil/core/dev-server', '../dev-server/index.js'), + externalAlias('@stencil/core/mock-doc', '../mock-doc/index.cjs'), lazyRequirePlugin(opts, [ '@stencil/core/internal/app-data', '@stencil/core/internal/testing', @@ -81,20 +81,6 @@ function getLazyRequireFn(opts: BuildOptions) { return fs.readFileSync(join(opts.bundleHelpersDir, 'lazy-require.js'), 'utf8').trim(); } -function externalAliases(moduleId: string, resolveToPath: string): Plugin { - return { - name: 'externalAliases', - setup(build) { - build.onResolve({ filter: new RegExp(`^${moduleId}$`) }, () => { - return { - path: resolveToPath, - external: true, - }; - }); - }, - }; -} - function lazyRequirePlugin(opts: BuildOptions, moduleIds: string[]): Plugin { return { name: 'lazyRequirePlugin', diff --git a/scripts/esbuild/util.ts b/scripts/esbuild/util.ts index 4af8d9dfa19..c52e681a259 100644 --- a/scripts/esbuild/util.ts +++ b/scripts/esbuild/util.ts @@ -1,4 +1,4 @@ -import type { BuildOptions as ESBuildOptions, BuildResult as ESBuildResult } from 'esbuild'; +import type { BuildOptions as ESBuildOptions, BuildResult as ESBuildResult, Plugin } from 'esbuild'; import * as esbuild from 'esbuild'; import { join } from 'path'; @@ -125,3 +125,24 @@ export function getBaseEsbuildOptions(): ESBuildOptions { export function getEsbuildTargets(): string[] { return ['node16', 'chrome79', 'edge79', 'firefox70', 'safari14']; } + +/** + * Alias and mark a module as external at the same time + * + * @param moduleId the module ID to alias and externalize + * @param resolveToPath the path to which imports of the module should be rewritten + * @returns an Esbuild plugin + */ +export function externalAlias(moduleId: string, resolveToPath: string): Plugin { + return { + name: 'externalAliases', + setup(build) { + build.onResolve({ filter: new RegExp(`^${moduleId}$`) }, () => { + return { + path: resolveToPath, + external: true, + }; + }); + }, + }; +} diff --git a/scripts/test/validate-build.ts b/scripts/test/validate-build.ts index b3e17771601..ac4b0528f4e 100644 --- a/scripts/test/validate-build.ts +++ b/scripts/test/validate-build.ts @@ -119,6 +119,8 @@ const pkgs: TestPackage[] = [ ]; /** + * Validate that certain files were written to disk during the build, and that + * these files tree-shake correctly. * * @param rootDir the root of the Stencil repository */ diff --git a/src/client/client-load-module.ts b/src/client/client-load-module.ts index e568b14126c..f9686e2f4e0 100644 --- a/src/client/client-load-module.ts +++ b/src/client/client-load-module.ts @@ -5,6 +5,15 @@ import { consoleDevError, consoleError } from './client-log'; export const cmpModules = /*@__PURE__*/ new Map(); +/** + * We need to separate out this prefix so that Esbuild doesn't try to resolve + * the below, but instead retains a dynamic `import()` statement in the + * emitted code. + * + * See here for details https://esbuild.github.io/api/#glob + */ +const MODULE_IMPORT_PREFIX = './'; + export const loadModule = ( cmpMeta: d.ComponentRuntimeMeta, hostRef: d.HostRef, @@ -29,7 +38,7 @@ export const loadModule = ( /* webpackInclude: /\.entry\.js$/ */ /* webpackExclude: /\.system\.entry\.js$/ */ /* webpackMode: "lazy" */ - `./${bundleId}.entry.js${BUILD.hotModuleReplacement && hmrVersionId ? '?s-hmr=' + hmrVersionId : ''}` + `${MODULE_IMPORT_PREFIX}${bundleId}.entry.js${BUILD.hotModuleReplacement && hmrVersionId ? '?s-hmr=' + hmrVersionId : ''}` ).then((importedModule) => { if (!BUILD.hotModuleReplacement) { cmpModules.set(bundleId, importedModule); diff --git a/src/compiler/style/css-to-esm.ts b/src/compiler/style/css-to-esm.ts index 405291b2c7b..250067a2347 100644 --- a/src/compiler/style/css-to-esm.ts +++ b/src/compiler/style/css-to-esm.ts @@ -1,9 +1,9 @@ import { catchError, createJsVarName, DEFAULT_STYLE_MODE, hasError, isString, normalizePath, resolve } from '@utils'; +import { scopeCss } from '@utils/shadow-css'; import MagicString from 'magic-string'; import path from 'path'; import type * as d from '../../declarations'; -import { scopeCss } from '../../utils/shadow-css'; import { parseStyleDocs } from '../docs/style-docs'; import { optimizeCss } from '../optimize/optimize-css'; import { serializeImportPath } from '../transformers/stencil-import-path'; diff --git a/src/compiler/transformers/add-static-style.ts b/src/compiler/transformers/add-static-style.ts index 4c8d26ba2a8..cfd8718288a 100644 --- a/src/compiler/transformers/add-static-style.ts +++ b/src/compiler/transformers/add-static-style.ts @@ -1,8 +1,8 @@ import { dashToPascalCase, DEFAULT_STYLE_MODE } from '@utils'; +import { scopeCss } from '@utils/shadow-css'; import ts from 'typescript'; import type * as d from '../../declarations'; -import { scopeCss } from '../../utils/shadow-css'; import { getScopeId } from '../style/scope-css'; import { createStaticGetter, getExternalStyles } from './transform-utils'; diff --git a/src/compiler/transformers/component-native/native-static-style.ts b/src/compiler/transformers/component-native/native-static-style.ts index 4a89232f6a3..c5ea1f3eca1 100644 --- a/src/compiler/transformers/component-native/native-static-style.ts +++ b/src/compiler/transformers/component-native/native-static-style.ts @@ -1,8 +1,8 @@ import { DEFAULT_STYLE_MODE } from '@utils'; +import { scopeCss } from '@utils/shadow-css'; import ts from 'typescript'; import type * as d from '../../../declarations'; -import { scopeCss } from '../../../utils/shadow-css'; import { getScopeId } from '../../style/scope-css'; import { createStyleIdentifier } from '../add-static-style'; import { createStaticGetter } from '../transform-utils'; diff --git a/src/runtime/initialize-component.ts b/src/runtime/initialize-component.ts index 4a1ef05a7fc..e80b5e94884 100644 --- a/src/runtime/initialize-component.ts +++ b/src/runtime/initialize-component.ts @@ -116,7 +116,7 @@ export const initializeComponent = async ( BUILD.shadowDomShim && cmpMeta.$flags$ & CMP_FLAGS.needsShadowDomShim ) { - style = await import('../utils/shadow-css').then((m) => m.scopeCss(style, scopeId, false)); + style = await import('@utils/shadow-css').then((m) => m.scopeCss(style, scopeId, false)); } registerStyle(scopeId, style, !!(cmpMeta.$flags$ & CMP_FLAGS.shadowDomEncapsulation)); diff --git a/tsconfig.json b/tsconfig.json index 971a3d511c0..cd1e40c08ab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -38,7 +38,8 @@ "@stencil/core/mock-doc": ["src/mock-doc/index.ts"], "@stencil/core/testing": ["src/testing/index.ts"], "@sys-api-node": ["src/sys/node/index.ts"], - "@utils": ["src/utils/index.ts"] + "@utils": ["src/utils/index.ts"], + "@utils/shadow-css": ["src/utils/shadow-css"] }, "pretty": true, "resolveJsonModule": true,