From c05fafe84b81ace7dc20cb5446b396b70da897d0 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Thu, 15 Sep 2022 16:09:48 -0400 Subject: [PATCH 1/9] feat(compiler): moves `autoDefineCustomElements` to an export behavior This commit moves `autoDefineCustomElements` from a config flag to a `customElementsExportBehavior` option on the `dist-custom-elements` output target. This prevents treeshaking issues that were possible when barrel exporting and this option were both enabled. --- src/cli/telemetry/test/telemetry.spec.ts | 4 ++-- .../add-define-custom-element-function.ts | 2 +- src/declarations/stencil-public-compiler.ts | 15 ++++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/cli/telemetry/test/telemetry.spec.ts b/src/cli/telemetry/test/telemetry.spec.ts index 0d2c97ea3fc..7060c73c8c7 100644 --- a/src/cli/telemetry/test/telemetry.spec.ts +++ b/src/cli/telemetry/test/telemetry.spec.ts @@ -261,7 +261,7 @@ describe('anonymizeConfigForTelemetry', () => { outputTargets: [ { type: WWW, baseUrl: 'https://example.com' }, { type: DIST_HYDRATE_SCRIPT, external: ['beep', 'boop'], dir: 'shoud/go/away' }, - { type: DIST_CUSTOM_ELEMENTS, autoDefineCustomElements: false }, + { type: DIST_CUSTOM_ELEMENTS }, { type: DIST_CUSTOM_ELEMENTS, generateTypeDeclarations: true }, { type: DIST, typesDir: 'my-types' }, ], @@ -270,7 +270,7 @@ describe('anonymizeConfigForTelemetry', () => { expect(anonymizedConfig.outputTargets).toEqual([ { type: WWW, baseUrl: 'omitted' }, { type: DIST_HYDRATE_SCRIPT, external: ['beep', 'boop'], dir: 'omitted' }, - { type: DIST_CUSTOM_ELEMENTS, autoDefineCustomElements: false }, + { type: DIST_CUSTOM_ELEMENTS }, { type: DIST_CUSTOM_ELEMENTS, generateTypeDeclarations: true }, { type: DIST, typesDir: 'omitted' }, ]); diff --git a/src/compiler/transformers/component-native/add-define-custom-element-function.ts b/src/compiler/transformers/component-native/add-define-custom-element-function.ts index 1cab0fe88ab..482c070af55 100644 --- a/src/compiler/transformers/component-native/add-define-custom-element-function.ts +++ b/src/compiler/transformers/component-native/add-define-custom-element-function.ts @@ -41,7 +41,7 @@ export const addDefineCustomElementFunctions = ( setupComponentDependencies(moduleFile, components, newStatements, caseStatements, tagNames); addDefineCustomElementFunction(tagNames, newStatements, caseStatements); - if (outputTarget.autoDefineCustomElements) { + if (outputTarget.customElementsExportBehavior === 'auto-define-custom-elements') { const conditionalDefineCustomElementCall = createAutoDefinitionExpression( principalComponent.componentClassName ); diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index bc10975dff1..95fde53dc51 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -2028,8 +2028,15 @@ export interface OutputTargetBaseNext { * * - `default`: No additional export or definition behavior will happen. * - `single-export-module`: All components will be re-exported from the specified directory's root `index.js` file. + * - `auto-define-custom-elements`: Enables the auto-definition of a component and its children (recursively) in the custom elements registry. This + * functionality allows consumers to bypass the explicit call to define a component, its children, its children's + * children, etc. Users of this flag should be aware that enabling this functionality may increase bundle size. */ -export const CustomElementsExportBehaviorOptions = ['default', 'single-export-module'] as const; +export const CustomElementsExportBehaviorOptions = [ + 'default', + 'single-export-module', + 'auto-define-custom-elements', +] as const; /** * This type is auto-generated based on the values in `CustomElementsExportBehaviorOptions` array. @@ -2045,12 +2052,6 @@ export interface OutputTargetDistCustomElements extends OutputTargetBaseNext { inlineDynamicImports?: boolean; includeGlobalScripts?: boolean; minify?: boolean; - /** - * Enables the auto-definition of a component and its children (recursively) in the custom elements registry. This - * functionality allows consumers to bypass the explicit call to define a component, its children, its children's - * children, etc. Users of this flag should be aware that enabling this functionality may increase bundle size. - */ - autoDefineCustomElements?: boolean; /** * Enables the generation of type definition files for the output target. */ From afeab08e8c28011276f6fc4642fe1978dded3425 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Sat, 17 Sep 2022 17:47:34 -0400 Subject: [PATCH 2/9] feat(compiler): add `defineCustomElements` method & signature typedef This commit adds a `defineCustomElements` function to the `dist-custom-elements` output target that can be used to define all custom elements at once. TBD on if this will always be available, for certain export behaviors, or as a dedicated export behavior --- .../custom-elements-types.ts | 12 +++++++++ .../dist-custom-elements/index.ts | 26 ++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts b/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts index cace9d7f43b..0f889c21e49 100644 --- a/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts +++ b/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts @@ -96,6 +96,18 @@ const generateCustomElementsTypesOutput = async ( ` rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;`, `}`, `export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;`, + ``, + `/**`, + ` * Utility to define all custom elements within this package using the tag name provided in the component's source. `, + ` * When defining each custom element, it will also check it's safe to define by:`, + ` *`, + ` * 1. Ensuring the "customElements" registry is available in the global context (window).`, + ` * 2. The component tag name is not already defined.`, + ` *`, + ` * Use the standard [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) `, + ` * method instead to define custom elements individually, or to provide a different tag name.`, + ` */`, + `export declare const defineCustomElements: (opts?: any) => void;`, ]; const componentsDtsRelPath = relDts(outputTarget.dir!, join(typesDir, 'components.d.ts')); diff --git a/src/compiler/output-targets/dist-custom-elements/index.ts b/src/compiler/output-targets/dist-custom-elements/index.ts index 1bd8ae34b20..a6733ef8e38 100644 --- a/src/compiler/output-targets/dist-custom-elements/index.ts +++ b/src/compiler/output-targets/dist-custom-elements/index.ts @@ -190,6 +190,8 @@ export const addCustomElementInputs = ( const components = buildCtx.components; // an array to store the imports of these modules that we're going to add to our entry chunk const indexImports: string[] = []; + const indexExports: string[] = []; + const exportNames: string[] = []; components.forEach((cmp) => { const exp: string[] = []; @@ -200,7 +202,7 @@ export const addCustomElementInputs = ( if (cmp.isPlain) { exp.push(`export { ${importName} as ${exportName} } from '${cmp.sourceFilePath}';`); - indexImports.push(`export { {${exportName} } from '${coreKey}';`); + indexExports.push(`export { {${exportName} } from '${coreKey}';`); } else { // the `importName` may collide with the `exportName`, alias it just in case it does with `importAs` exp.push( @@ -215,19 +217,37 @@ export const addCustomElementInputs = ( // correct virtual module, if we instead referenced, for instance, // `cmp.sourceFilePath`, we would end up with duplicated modules in our // output. - indexImports.push( + indexExports.push( `export { ${exportName}, defineCustomElement as defineCustomElement${exportName} } from '${coreKey}';` ); } + indexImports.push(`import { ${exportName} } from '${coreKey}';`); + exportNames.push(exportName); + bundleOpts.inputs[cmp.tagName] = coreKey; bundleOpts.loader![coreKey] = exp.join('\n'); }); + bundleOpts.loader!['\0core'] += indexImports.join('\n'); + // Only re-export component definitions if the barrel export behavior is set if (outputTarget.customElementsExportBehavior === 'single-export-module') { - bundleOpts.loader!['\0core'] += indexImports.join('\n'); + bundleOpts.loader!['\0core'] += indexExports.join('\n'); } + + bundleOpts.loader!['\0core'] += ` +export const defineCustomElements = (opts) => { + if (typeof customElements !== 'undefined') { + [ + ${exportNames.join(',\n ')} + ].forEach(cmp => { + if (!customElements.get(cmp.is)) { + customElements.define(cmp.is, cmp, opts); + } + }); + } +};`; }; /** From 99d5d0daa61f06dd4b6aaf49ba024c0e80da5d53 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Mon, 26 Sep 2022 12:19:53 -0400 Subject: [PATCH 3/9] feat(compiler): add export behavior for custom elements `defineCustomElements` This commit wraps the `defineCustomElements()` function for `dist-custom-elements` into a new export behavior so this becomes opt-in behavior rather than behavior that always exists --- .../custom-elements-types.ts | 29 +++++++++++-------- .../dist-custom-elements/index.ts | 8 +++-- src/declarations/stencil-public-compiler.ts | 3 ++ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts b/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts index 41bbdf9c305..794a8074664 100644 --- a/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts +++ b/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts @@ -44,6 +44,7 @@ const generateCustomElementsTypesOutput = async ( outputTarget: d.OutputTargetDistCustomElements ) => { const isBarrelExport = outputTarget.customElementsExportBehavior === 'single-export-module'; + const isBundleExport = outputTarget.customElementsExportBehavior === 'bundle'; // the path where we're going to write the typedef for the whole dist-custom-elements output const customElementsDtsPath = join(outputTarget.dir!, 'index.d.ts'); @@ -97,18 +98,22 @@ const generateCustomElementsTypesOutput = async ( ` rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;`, `}`, `export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;`, - ``, - `/**`, - ` * Utility to define all custom elements within this package using the tag name provided in the component's source. `, - ` * When defining each custom element, it will also check it's safe to define by:`, - ` *`, - ` * 1. Ensuring the "customElements" registry is available in the global context (window).`, - ` * 2. The component tag name is not already defined.`, - ` *`, - ` * Use the standard [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) `, - ` * method instead to define custom elements individually, or to provide a different tag name.`, - ` */`, - `export declare const defineCustomElements: (opts?: any) => void;`, + ...(isBundleExport + ? [ + ``, + `/**`, + ` * Utility to define all custom elements within this package using the tag name provided in the component's source. `, + ` * When defining each custom element, it will also check it's safe to define by:`, + ` *`, + ` * 1. Ensuring the "customElements" registry is available in the global context (window).`, + ` * 2. The component tag name is not already defined.`, + ` *`, + ` * Use the standard [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) `, + ` * method instead to define custom elements individually, or to provide a different tag name.`, + ` */`, + `export declare const defineCustomElements: (opts?: any) => void;`, + ] + : []), ]; const componentsDtsRelPath = relDts(outputTarget.dir!, join(typesDir, 'components.d.ts')); diff --git a/src/compiler/output-targets/dist-custom-elements/index.ts b/src/compiler/output-targets/dist-custom-elements/index.ts index 9647d6b9f98..2f2e3207497 100644 --- a/src/compiler/output-targets/dist-custom-elements/index.ts +++ b/src/compiler/output-targets/dist-custom-elements/index.ts @@ -230,14 +230,17 @@ export const addCustomElementInputs = ( bundleOpts.loader![coreKey] = exp.join('\n'); }); - bundleOpts.loader!['\0core'] += indexImports.join('\n'); + if (outputTarget.customElementsExportBehavior === 'bundle') { + bundleOpts.loader!['\0core'] += indexImports.join('\n'); + } // Only re-export component definitions if the barrel export behavior is set if (outputTarget.customElementsExportBehavior === 'single-export-module') { bundleOpts.loader!['\0core'] += indexExports.join('\n'); } - bundleOpts.loader!['\0core'] += ` + if (outputTarget.customElementsExportBehavior === 'bundle') { + bundleOpts.loader!['\0core'] += ` export const defineCustomElements = (opts) => { if (typeof customElements !== 'undefined') { [ @@ -249,6 +252,7 @@ export const defineCustomElements = (opts) => { }); } };`; + } }; /** diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index c7d3d1d1928..3c480464062 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -2035,6 +2035,9 @@ export interface OutputTargetBaseNext { export const CustomElementsExportBehaviorOptions = [ 'default', 'auto-define-custom-elements', + // TODO evaluate if this should be an export behavior, or just a flag + // on the output target config + 'bundle', 'single-export-module', ] as const; From 2cb215988319c1281acc8667af08dc795f52ec21 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Wed, 28 Sep 2022 15:01:49 -0400 Subject: [PATCH 4/9] test(compiler): tests for custom elements `bundle` export behavior This commit adds test cases for the new `bundle` export behavior on `dist-custom-elements` that will serve as a quick port-over for projects currently using `dist-custom-elements-bundle` --- .../custom-elements-types.ts | 4 +- .../test/custom-elements-types.spec.ts | 59 +++++++++++++++++++ ...utput-targets-dist-custom-elements.spec.ts | 46 ++++++++++++++- src/declarations/stencil-public-compiler.ts | 4 +- 4 files changed, 108 insertions(+), 5 deletions(-) diff --git a/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts b/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts index 794a8074664..de447844c20 100644 --- a/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts +++ b/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts @@ -102,13 +102,13 @@ const generateCustomElementsTypesOutput = async ( ? [ ``, `/**`, - ` * Utility to define all custom elements within this package using the tag name provided in the component's source. `, + ` * Utility to define all custom elements within this package using the tag name provided in the component's source.`, ` * When defining each custom element, it will also check it's safe to define by:`, ` *`, ` * 1. Ensuring the "customElements" registry is available in the global context (window).`, ` * 2. The component tag name is not already defined.`, ` *`, - ` * Use the standard [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) `, + ` * Use the standard [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)`, ` * method instead to define custom elements individually, or to provide a different tag name.`, ` */`, `export declare const defineCustomElements: (opts?: any) => void;`, 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 bdea89aa1ef..163ff220dae 100644 --- a/src/compiler/output-targets/test/custom-elements-types.spec.ts +++ b/src/compiler/output-targets/test/custom-elements-types.spec.ts @@ -151,4 +151,63 @@ describe('Custom Elements Typedef generation', () => { writeFileSpy.mockRestore(); }); + + it('should generate a type signature for the `defineCustomElements` function when `bundle` export behavior is set', async () => { + const componentOne = stubComponentCompilerMeta({ + tagName: 'my-component', + sourceFilePath: '/src/components/my-component/my-component.tsx', + }); + const componentTwo = stubComponentCompilerMeta({ + sourceFilePath: '/src/components/the-other-component/my-real-best-component.tsx', + componentClassName: 'MyBestComponent', + tagName: 'my-best-component', + }); + const { config, compilerCtx, buildCtx } = setup(); + (config.outputTargets[0] as d.OutputTargetDistCustomElements).customElementsExportBehavior = 'bundle'; + buildCtx.components = [componentOne, componentTwo]; + + const writeFileSpy = jest.spyOn(compilerCtx.fs, 'writeFile'); + + await generateCustomElementsTypes(config, compilerCtx, buildCtx, 'types_dir'); + + const expectedTypedefOutput = [ + '/**', + ' * Used to manually set the base path where assets can be found.', + ' * If the script is used as "module", it\'s recommended to use "import.meta.url",', + ' * such as "setAssetPath(import.meta.url)". Other options include', + ' * "setAssetPath(document.currentScript.src)", or using a bundler\'s replace plugin to', + ' * dynamically set the path at build time, such as "setAssetPath(process.env.ASSET_PATH)".', + ' * But do note that this configuration depends on how your script is bundled, or lack of', + ' * bundling, and where your assets can be loaded from. Additionally custom bundling', + ' * will have to ensure the static assets are copied to its build directory.', + ' */', + 'export declare const setAssetPath: (path: string) => void;', + '', + 'export interface SetPlatformOptions {', + ' raf?: (c: FrameRequestCallback) => number;', + ' ael?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;', + ' rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;', + '}', + 'export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;', + '', + '/**', + ` * Utility to define all custom elements within this package using the tag name provided in the component's source.`, + ` * When defining each custom element, it will also check it's safe to define by:`, + ' *', + ' * 1. Ensuring the "customElements" registry is available in the global context (window).', + ' * 2. The component tag name is not already defined.', + ' *', + ' * Use the standard [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)', + ' * method instead to define custom elements individually, or to provide a different tag name.', + ' */', + 'export declare const defineCustomElements: (opts?: any) => void;', + '', + ].join('\n'); + + expect(compilerCtx.fs.writeFile).toBeCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, { + outputTargetType: DIST_CUSTOM_ELEMENTS, + }); + + writeFileSpy.mockRestore(); + }); }); 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 991057a05e3..0974769bc82 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 @@ -11,6 +11,7 @@ import type * as d from '../../../declarations'; import { OutputTargetDistCustomElements } from '../../../declarations'; import { STENCIL_APP_GLOBALS_ID, STENCIL_INTERNAL_CLIENT_ID, USER_INDEX_ENTRY_ID } from '../../bundle/entry-alias-ids'; import { stubComponentCompilerMeta } from '../../types/tests/ComponentCompilerMeta.stub'; +import * as outputCustomElementsMod from '../dist-custom-elements'; import { addCustomElementInputs, bundleCustomElements, @@ -18,7 +19,6 @@ import { getBundleOptions, outputCustomElements, } from '../dist-custom-elements'; -import * as outputCustomElementsMod from '../dist-custom-elements'; // TODO(STENCIL-561): fully delete dist-custom-elements-bundle code import { DIST_CUSTOM_ELEMENTS, DIST_CUSTOM_ELEMENTS_BUNDLE } from '../output-utils'; @@ -223,5 +223,49 @@ export { ComponentWithJsx, defineCustomElement as defineCustomElementComponentWi ); }); }); + + describe('CustomElementsExportBehavior.BUNDLE', () => { + beforeEach(() => { + (config.outputTargets[0] as OutputTargetDistCustomElements).customElementsExportBehavior = 'bundle'; + }); + + it('should add a `defineCustomElements` function to the index.js file', () => { + const componentOne = stubComponentCompilerMeta(); + const componentTwo = stubComponentCompilerMeta({ + componentClassName: 'MyBestComponent', + tagName: 'my-best-component', + }); + + buildCtx.components = [componentOne, componentTwo]; + + const bundleOptions = getBundleOptions( + config, + buildCtx, + compilerCtx, + config.outputTargets[0] as OutputTargetDistCustomElements + ); + addCustomElementInputs(buildCtx, bundleOptions, config.outputTargets[0] as OutputTargetDistCustomElements); + expect(bundleOptions.loader['\0core']).toEqual( + `export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; +export * from '${USER_INDEX_ENTRY_ID}'; +import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; +globalScripts(); +import { StubCmp } from '\0StubCmp'; +import { MyBestComponent } from '\0MyBestComponent'; +export const defineCustomElements = (opts) => { + if (typeof customElements !== 'undefined') { + [ + StubCmp, + MyBestComponent + ].forEach(cmp => { + if (!customElements.get(cmp.is)) { + customElements.define(cmp.is, cmp, opts); + } + }); + } +};` + ); + }); + }); }); }); diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index 3c480464062..7277a3819a4 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -2030,13 +2030,13 @@ export interface OutputTargetBaseNext { * - `auto-define-custom-elements`: Enables the auto-definition of a component and its children (recursively) in the custom elements registry. This * functionality allows consumers to bypass the explicit call to define a component, its children, its children's * children, etc. Users of this flag should be aware that enabling this functionality may increase bundle size. + * - `bundle`: A `defineCustomElements` function will be exported from the distribution directory. This behavior was added to allow easy migration + * from `dist-custom-elements-bundle` to `dist-custom-elements`. * - `single-export-module`: All components will be re-exported from the specified directory's root `index.js` file. */ export const CustomElementsExportBehaviorOptions = [ 'default', 'auto-define-custom-elements', - // TODO evaluate if this should be an export behavior, or just a flag - // on the output target config 'bundle', 'single-export-module', ] as const; From 247aaf5b2f0dfe4d03b8590a73add13b7097cc4b Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Wed, 28 Sep 2022 15:42:55 -0400 Subject: [PATCH 5/9] misc(): fix jest alias lint error --- .../output-targets/test/custom-elements-types.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 a294444eb1d..f77bf9f00be 100644 --- a/src/compiler/output-targets/test/custom-elements-types.spec.ts +++ b/src/compiler/output-targets/test/custom-elements-types.spec.ts @@ -145,7 +145,7 @@ describe('Custom Elements Typedef generation', () => { '', ].join('\n'); - expect(compilerCtx.fs.writeFile).toBeCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, { + expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, { outputTargetType: DIST_CUSTOM_ELEMENTS, }); @@ -204,7 +204,7 @@ describe('Custom Elements Typedef generation', () => { '', ].join('\n'); - expect(compilerCtx.fs.writeFile).toBeCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, { + expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, { outputTargetType: DIST_CUSTOM_ELEMENTS, }); From 2e87712d97a2b0f7776266e52a456b35e0b66493 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Mon, 3 Oct 2022 16:24:09 -0400 Subject: [PATCH 6/9] fix(): PR feedback --- .../dist-custom-elements/custom-elements-types.ts | 2 +- src/compiler/output-targets/dist-custom-elements/index.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts b/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts index de447844c20..54525c62c8c 100644 --- a/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts +++ b/src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts @@ -106,7 +106,7 @@ const generateCustomElementsTypesOutput = async ( ` * When defining each custom element, it will also check it's safe to define by:`, ` *`, ` * 1. Ensuring the "customElements" registry is available in the global context (window).`, - ` * 2. The component tag name is not already defined.`, + ` * 2. Ensuring that the component tag name is not already defined.`, ` *`, ` * Use the standard [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)`, ` * method instead to define custom elements individually, or to provide a different tag name.`, diff --git a/src/compiler/output-targets/dist-custom-elements/index.ts b/src/compiler/output-targets/dist-custom-elements/index.ts index 2f2e3207497..5ee9b855ffe 100644 --- a/src/compiler/output-targets/dist-custom-elements/index.ts +++ b/src/compiler/output-targets/dist-custom-elements/index.ts @@ -189,9 +189,12 @@ export const addCustomElementInputs = ( outputTarget: d.OutputTargetDistCustomElements ): void => { const components = buildCtx.components; - // an array to store the imports of these modules that we're going to add to our entry chunk + // An array to store the imports of these modules that we're going to add to our entry chunk const indexImports: string[] = []; + // An array to store the export declarations that we're going to add to our entry chunk const indexExports: string[] = []; + // An array to store the exported component names that will be used for the `defineCustomElements` + // function on the `bundle` export behavior option const exportNames: string[] = []; components.forEach((cmp) => { From c6fa5042f5b861e2568b3ba6c47e7377eba66ee3 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Tue, 4 Oct 2022 13:28:41 -0400 Subject: [PATCH 7/9] refactor(compiler): move export behavior conditionals to entry point generation This commit moves the logic for `dist-custom-elements` export behavior to the `generateEntryPoint()` function. This keeps all the logic responsible for generating entry-point code in the same place and will make it easier to add/remove export behaviors in the future --- .../dist-custom-elements/index.ts | 87 ++++++++++++------- .../test/custom-elements-types.spec.ts | 2 +- ...utput-targets-dist-custom-elements.spec.ts | 60 ++++++++----- 3 files changed, 95 insertions(+), 54 deletions(-) diff --git a/src/compiler/output-targets/dist-custom-elements/index.ts b/src/compiler/output-targets/dist-custom-elements/index.ts index 5ee9b855ffe..e455146c9ce 100644 --- a/src/compiler/output-targets/dist-custom-elements/index.ts +++ b/src/compiler/output-targets/dist-custom-elements/index.ts @@ -87,9 +87,7 @@ export const getBundleOptions = ( // @see {@link https://rollupjs.org/guide/en/#conventions} for more info. index: '\0core', }, - loader: { - '\0core': generateEntryPoint(outputTarget), - }, + loader: {}, inlineDynamicImports: outputTarget.inlineDynamicImports, preserveEntrySignatures: 'allow-extension', }); @@ -233,49 +231,76 @@ export const addCustomElementInputs = ( bundleOpts.loader![coreKey] = exp.join('\n'); }); - if (outputTarget.customElementsExportBehavior === 'bundle') { - bundleOpts.loader!['\0core'] += indexImports.join('\n'); - } - - // Only re-export component definitions if the barrel export behavior is set - if (outputTarget.customElementsExportBehavior === 'single-export-module') { - bundleOpts.loader!['\0core'] += indexExports.join('\n'); - } - - if (outputTarget.customElementsExportBehavior === 'bundle') { - bundleOpts.loader!['\0core'] += ` -export const defineCustomElements = (opts) => { - if (typeof customElements !== 'undefined') { - [ - ${exportNames.join(',\n ')} - ].forEach(cmp => { - if (!customElements.get(cmp.is)) { - customElements.define(cmp.is, cmp, opts); - } - }); - } -};`; - } + // Generate the contents of the entry file to be created by the bundler + bundleOpts.loader!['\0core'] = generateEntryPoint(outputTarget, indexImports, indexExports, exportNames); }; /** * Generate the entrypoint (`index.ts` file) contents for the `dist-custom-elements` output target * @param outputTarget the output target's configuration + * @param cmpImports The import declarations for local component modules. + * @param cmpExports The export declarations for local component modules. + * @param cmpNames The exported component names (could be aliased) from local component modules. * @returns the stringified contents to be placed in the entrypoint */ -export const generateEntryPoint = (outputTarget: d.OutputTargetDistCustomElements): string => { - const imp: string[] = []; +export const generateEntryPoint = ( + outputTarget: d.OutputTargetDistCustomElements, + cmpImports: string[] = [], + cmpExports: string[] = [], + cmpNames: string[] = [] +): string => { + const body: string[] = []; + const imports: string[] = []; + const exports: string[] = []; - imp.push( + // Exports that are always present + exports.push( `export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';`, `export * from '${USER_INDEX_ENTRY_ID}';` ); + // Content related to global scripts if (outputTarget.includeGlobalScripts !== false) { - imp.push(`import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';`, `globalScripts();`); + imports.push(`import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';`); + body.push(`globalScripts();`); } - return imp.join('\n') + '\n'; + // Content related to the `bundle` export behavior + if (outputTarget.customElementsExportBehavior === 'bundle') { + imports.push(...cmpImports); + body.push( + 'export const defineCustomElements = (opts) => {', + " if (typeof customElements !== 'undefined') {", + ' [', + ...cmpNames.map((cmp) => ` ${cmp},`), + ' ].forEach(cmp => {', + ' if (!customElements.get(cmp.is)) {', + ' customElements.define(cmp.is, cmp, opts);', + ' }', + ' });', + ' }', + '};' + ); + } + + // Content related to the `single-export-module` export behavior + if (outputTarget.customElementsExportBehavior === 'single-export-module') { + exports.push(...cmpExports); + } + + // Generate the contents of the file based on the parts + // defined above. This keeps the file structure consistent as + // new export behaviors may be added + let content = ''; + + // Add imports to file content + content += imports.length ? imports.join('\n') + '\n' : ''; + // Add exports to file content + content += exports.length ? exports.join('\n') + '\n' : ''; + // Add body to file content + content += body.length ? body.join('\n') + '\n' : ''; + + return content; }; /** 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 f77bf9f00be..d2e64341f78 100644 --- a/src/compiler/output-targets/test/custom-elements-types.spec.ts +++ b/src/compiler/output-targets/test/custom-elements-types.spec.ts @@ -195,7 +195,7 @@ describe('Custom Elements Typedef generation', () => { ` * When defining each custom element, it will also check it's safe to define by:`, ' *', ' * 1. Ensuring the "customElements" registry is available in the global context (window).', - ' * 2. The component tag name is not already defined.', + ' * 2. Ensuring that the component tag name is not already defined.', ' *', ' * Use the standard [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)', ' * method instead to define custom elements individually, or to provide a different tag name.', 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 0974769bc82..c1164b58e79 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 @@ -67,13 +67,28 @@ describe('Custom Elements output target', () => { }); describe('generateEntryPoint', () => { - it.each([true, false])('should include globalScripts if the right option is set', (includeGlobalScripts) => { + it('should include global scripts when flag is `true`', () => { const entryPoint = generateEntryPoint({ type: DIST_CUSTOM_ELEMENTS, - includeGlobalScripts, + includeGlobalScripts: true, }); - const globalScriptsBoilerplate = `import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';\nglobalScripts();`; - expect(entryPoint.includes(globalScriptsBoilerplate)).toBe(includeGlobalScripts); + + expect(entryPoint).toEqual(`import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; +export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; +export * from '${USER_INDEX_ENTRY_ID}'; +globalScripts(); +`); + }); + + it('should not include global scripts when flag is `false`', () => { + const entryPoint = generateEntryPoint({ + type: DIST_CUSTOM_ELEMENTS, + includeGlobalScripts: false, + }); + + expect(entryPoint).toEqual(`export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; +export * from '${USER_INDEX_ENTRY_ID}'; +`); }); }); @@ -87,9 +102,7 @@ describe('Custom Elements output target', () => { expect(options.inputs).toEqual({ index: '\0core', }); - expect(options.loader).toEqual({ - '\0core': generateEntryPoint({ type: DIST_CUSTOM_ELEMENTS }), - }); + expect(options.loader).toEqual({}); expect(options.preserveEntrySignatures).toEqual('allow-extension'); }); @@ -158,9 +171,9 @@ describe('Custom Elements output target', () => { ); addCustomElementInputs(buildCtx, bundleOptions, config.outputTargets[0] as OutputTargetDistCustomElements); expect(bundleOptions.loader['\0core']).toEqual( - `export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; + `import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; +export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; export * from '${USER_INDEX_ENTRY_ID}'; -import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; globalScripts(); ` ); @@ -190,12 +203,13 @@ globalScripts(); ); addCustomElementInputs(buildCtx, bundleOptions, config.outputTargets[0] as OutputTargetDistCustomElements); expect(bundleOptions.loader['\0core']).toEqual( - `export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; + `import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; +export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; export * from '${USER_INDEX_ENTRY_ID}'; -import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; -globalScripts(); export { StubCmp, defineCustomElement as defineCustomElementStubCmp } from '\0StubCmp'; -export { MyBestComponent, defineCustomElement as defineCustomElementMyBestComponent } from '\0MyBestComponent';` +export { MyBestComponent, defineCustomElement as defineCustomElementMyBestComponent } from '\0MyBestComponent'; +globalScripts(); +` ); }); @@ -215,11 +229,12 @@ export { MyBestComponent, defineCustomElement as defineCustomElementMyBestCompon ); addCustomElementInputs(buildCtx, bundleOptions, config.outputTargets[0] as OutputTargetDistCustomElements); expect(bundleOptions.loader['\0core']).toEqual( - `export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; + `import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; +export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; export * from '${USER_INDEX_ENTRY_ID}'; -import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; +export { ComponentWithJsx, defineCustomElement as defineCustomElementComponentWithJsx } from '\0ComponentWithJsx'; globalScripts(); -export { ComponentWithJsx, defineCustomElement as defineCustomElementComponentWithJsx } from '\0ComponentWithJsx';` +` ); }); }); @@ -246,24 +261,25 @@ export { ComponentWithJsx, defineCustomElement as defineCustomElementComponentWi ); addCustomElementInputs(buildCtx, bundleOptions, config.outputTargets[0] as OutputTargetDistCustomElements); expect(bundleOptions.loader['\0core']).toEqual( - `export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; -export * from '${USER_INDEX_ENTRY_ID}'; -import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; -globalScripts(); + `import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; import { StubCmp } from '\0StubCmp'; import { MyBestComponent } from '\0MyBestComponent'; +export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; +export * from '${USER_INDEX_ENTRY_ID}'; +globalScripts(); export const defineCustomElements = (opts) => { if (typeof customElements !== 'undefined') { [ StubCmp, - MyBestComponent + MyBestComponent, ].forEach(cmp => { if (!customElements.get(cmp.is)) { customElements.define(cmp.is, cmp, opts); } }); } -};` +}; +` ); }); }); From 45942cc429dea256285cc5926f64e3236264a7c5 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Thu, 6 Oct 2022 10:03:23 -0400 Subject: [PATCH 8/9] misc(compiler): add line break before body content in custom element output --- src/compiler/output-targets/dist-custom-elements/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/output-targets/dist-custom-elements/index.ts b/src/compiler/output-targets/dist-custom-elements/index.ts index e455146c9ce..5d0da1ba37f 100644 --- a/src/compiler/output-targets/dist-custom-elements/index.ts +++ b/src/compiler/output-targets/dist-custom-elements/index.ts @@ -298,7 +298,7 @@ export const generateEntryPoint = ( // Add exports to file content content += exports.length ? exports.join('\n') + '\n' : ''; // Add body to file content - content += body.length ? body.join('\n') + '\n' : ''; + content += body.length ? '\n' + body.join('\n') + '\n' : ''; return content; }; From 4db4009e28fd15db87fb57ce57e40232204a7362 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Thu, 13 Oct 2022 10:49:22 -0400 Subject: [PATCH 9/9] fix(): failing tests due to code format output change --- .../test/output-targets-dist-custom-elements.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) 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 c1164b58e79..0dad9e8d945 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 @@ -76,6 +76,7 @@ describe('Custom Elements output target', () => { expect(entryPoint).toEqual(`import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; export * from '${USER_INDEX_ENTRY_ID}'; + globalScripts(); `); }); @@ -174,6 +175,7 @@ export * from '${USER_INDEX_ENTRY_ID}'; `import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}'; export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; export * from '${USER_INDEX_ENTRY_ID}'; + globalScripts(); ` ); @@ -208,6 +210,7 @@ export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}' export * from '${USER_INDEX_ENTRY_ID}'; export { StubCmp, defineCustomElement as defineCustomElementStubCmp } from '\0StubCmp'; export { MyBestComponent, defineCustomElement as defineCustomElementMyBestComponent } from '\0MyBestComponent'; + globalScripts(); ` ); @@ -233,6 +236,7 @@ globalScripts(); export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; export * from '${USER_INDEX_ENTRY_ID}'; export { ComponentWithJsx, defineCustomElement as defineCustomElementComponentWithJsx } from '\0ComponentWithJsx'; + globalScripts(); ` ); @@ -266,6 +270,7 @@ import { StubCmp } from '\0StubCmp'; import { MyBestComponent } from '\0MyBestComponent'; export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}'; export * from '${USER_INDEX_ENTRY_ID}'; + globalScripts(); export const defineCustomElements = (opts) => { if (typeof customElements !== 'undefined') {