Skip to content

Commit 61b554d

Browse files
feat(compiler): update generation of type declaration file w/ dist-custom-elements
This updates the code to generate typedefs for the dist-custom-elements output target so that it works correctly with the updated index.js export introduced in #3368. STENCIL-332 Investigate re-exporting components via index.js
1 parent 0a4881d commit 61b554d

File tree

3 files changed

+116
-22
lines changed

3 files changed

+116
-22
lines changed

src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts

+26-18
Original file line numberDiff line numberDiff line change
@@ -9,47 +9,57 @@ import { normalizePath, dashToPascalCase } from '@utils';
99
* @param config the Stencil configuration associated with the project being compiled
1010
* @param compilerCtx the current compiler context
1111
* @param buildCtx the context associated with the current build
12-
* @param distDtsFilePath the path to a type declaration file (.d.ts) that is being generated for the output target.
13-
* This path is not necessarily the `components.d.ts` file that is found in the root of a project's `src` directory.
12+
* @param typesDir the path to the directory where type declarations are saved
1413
*/
1514
export const generateCustomElementsTypes = async (
1615
config: d.Config,
1716
compilerCtx: d.CompilerCtx,
1817
buildCtx: d.BuildCtx,
19-
distDtsFilePath: string
18+
typesDir: string
2019
): Promise<void> => {
21-
const outputTargets = config.outputTargets.filter(isOutputTargetDistCustomElements);
20+
const outputTargets = (config.outputTargets ?? []).filter(isOutputTargetDistCustomElements);
2221

2322
await Promise.all(
2423
outputTargets.map((outputTarget) =>
25-
generateCustomElementsTypesOutput(config, compilerCtx, buildCtx, distDtsFilePath, outputTarget)
24+
generateCustomElementsTypesOutput(config, compilerCtx, buildCtx, typesDir, outputTarget)
2625
)
2726
);
2827
};
2928

3029
/**
3130
* Generates types for a single `dist-custom-elements` output target definition in a Stencil project's configuration
31+
*
3232
* @param config the Stencil configuration associated with the project being compiled
3333
* @param compilerCtx the current compiler context
3434
* @param buildCtx the context associated with the current build
35-
* @param distDtsFilePath the path to a type declaration file (.d.ts) that is being generated for the output target.
36-
* This path is not necessarily the `components.d.ts` file that is found in the root of a project's `src` directory.
35+
* @param typesDir path to the directory where type declarations are saved
3736
* @param outputTarget the output target for which types are being currently generated
3837
*/
39-
export const generateCustomElementsTypesOutput = async (
38+
const generateCustomElementsTypesOutput = async (
4039
config: d.Config,
4140
compilerCtx: d.CompilerCtx,
4241
buildCtx: d.BuildCtx,
43-
distDtsFilePath: string,
42+
typesDir: string,
4443
outputTarget: d.OutputTargetDistCustomElements
4544
) => {
46-
const customElementsDtsPath = join(outputTarget.dir, 'index.d.ts');
47-
const componentsDtsRelPath = relDts(outputTarget.dir, distDtsFilePath);
45+
// the path where we're going to write the typedef for the whole dist-custom-elements output
46+
const customElementsDtsPath = join(outputTarget.dir!, 'index.d.ts');
47+
// the directory where types for the individual components are written
48+
const componentsTypeDirectoryPath = relative(outputTarget.dir!, join(typesDir, 'components'));
49+
50+
const components = buildCtx.components.filter((m) => !m.isCollectionDependency);
4851

4952
const code = [
5053
`/* ${config.namespace} custom elements */`,
51-
``,
52-
`import type { Components, JSX } from "${componentsDtsRelPath}";`,
54+
...components.map((component) => {
55+
const exportName = dashToPascalCase(component.tagName);
56+
const importName = component.componentClassName;
57+
// typedefs for individual components can be found under paths like
58+
// $TYPES_DIR/components/my-component/my-component.d.ts
59+
const componentDTSPath = join(componentsTypeDirectoryPath, component.tagName, component.tagName);
60+
61+
return `export { ${importName} as ${exportName} } from '${componentDTSPath}';`;
62+
}),
5363
``,
5464
`/**`,
5565
` * Used to manually set the base path where assets can be found.`,
@@ -69,11 +79,10 @@ export const generateCustomElementsTypesOutput = async (
6979
` rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;`,
7080
`}`,
7181
`export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;`,
72-
``,
73-
`export type { Components, JSX };`,
74-
``,
7582
];
7683

84+
const componentsDtsRelPath = relDts(outputTarget.dir!, join(typesDir, 'components.d.ts'));
85+
7786
const usersIndexJsPath = join(config.srcDir, 'index.ts');
7887
const hasUserIndex = await compilerCtx.fs.access(usersIndexJsPath);
7988
if (hasUserIndex) {
@@ -87,12 +96,11 @@ export const generateCustomElementsTypesOutput = async (
8796
outputTargetType: outputTarget.type,
8897
});
8998

90-
const components = buildCtx.components.filter((m) => !m.isCollectionDependency);
9199
await Promise.all(
92100
components.map(async (cmp) => {
93101
const dtsCode = generateCustomElementType(componentsDtsRelPath, cmp);
94102
const fileName = `${cmp.tagName}.d.ts`;
95-
const filePath = join(outputTarget.dir, fileName);
103+
const filePath = join(outputTarget.dir!, fileName);
96104
await compilerCtx.fs.writeFile(filePath, dtsCode, { outputTargetType: outputTarget.type });
97105
})
98106
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { path } from '@stencil/core/compiler';
2+
import { mockConfig, mockStencilSystem, mockBuildCtx, mockCompilerCtx, mockModule } from '@stencil/core/testing';
3+
import type * as d from '../../../declarations';
4+
import * as outputCustomElementsMod from '../dist-custom-elements';
5+
import { stubComponentCompilerMeta } from '../../types/tests/ComponentCompilerMeta.stub';
6+
import { generateCustomElementsTypes } from '../dist-custom-elements/custom-elements-types';
7+
import { DIST_CUSTOM_ELEMENTS } from '../output-utils';
8+
import { join, relative } from 'path';
9+
10+
const setup = () => {
11+
const sys = mockStencilSystem();
12+
const config: d.Config = mockConfig(sys);
13+
const compilerCtx = mockCompilerCtx(config);
14+
const buildCtx = mockBuildCtx(config, compilerCtx);
15+
const root = config.rootDir;
16+
config.configPath = '/testing-path';
17+
config.srcDir = '/src';
18+
config.buildAppCore = true;
19+
config.rootDir = path.join(root, 'User', 'testing', '/');
20+
config.namespace = 'TestApp';
21+
config.buildEs5 = true;
22+
config.globalScript = path.join(root, 'User', 'testing', 'src', 'global.ts');
23+
config.outputTargets = [{ type: DIST_CUSTOM_ELEMENTS, dir: 'my-best-dir' }];
24+
25+
const bundleCustomElementsSpy = jest.spyOn(outputCustomElementsMod, 'bundleCustomElements');
26+
27+
compilerCtx.moduleMap.set('test', mockModule());
28+
29+
return { config, compilerCtx, buildCtx, bundleCustomElementsSpy };
30+
};
31+
32+
describe('Custom Elements Typedef generation', () => {
33+
it('should generate an index.d.ts file corresponding to the index.js file', async () => {
34+
const componentOne = stubComponentCompilerMeta();
35+
const componentTwo = stubComponentCompilerMeta({
36+
componentClassName: 'MyBestComponent',
37+
tagName: 'my-best-component',
38+
});
39+
const { config, compilerCtx, buildCtx } = setup();
40+
buildCtx.components = [componentOne, componentTwo];
41+
42+
const writeFileSpy = jest.spyOn(compilerCtx.fs, 'writeFile');
43+
44+
await generateCustomElementsTypes(config, compilerCtx, buildCtx, 'types_dir');
45+
46+
const componentsTypeDirectoryPath = relative('my-best-dir', join('types_dir', 'components'));
47+
48+
const expectedTypedefOutput = [
49+
'/* TestApp custom elements */',
50+
`export { StubCmp as StubCmp } from '${join(componentsTypeDirectoryPath, 'stub-cmp', 'stub-cmp')}';`,
51+
`export { MyBestComponent as MyBestComponent } from '${join(
52+
componentsTypeDirectoryPath,
53+
'my-best-component',
54+
'my-best-component'
55+
)}';`,
56+
'',
57+
'/**',
58+
' * Used to manually set the base path where assets can be found.',
59+
' * If the script is used as "module", it\'s recommended to use "import.meta.url",',
60+
' * such as "setAssetPath(import.meta.url)". Other options include',
61+
' * "setAssetPath(document.currentScript.src)", or using a bundler\'s replace plugin to',
62+
' * dynamically set the path at build time, such as "setAssetPath(process.env.ASSET_PATH)".',
63+
' * But do note that this configuration depends on how your script is bundled, or lack of',
64+
' * bundling, and where your assets can be loaded from. Additionally custom bundling',
65+
' * will have to ensure the static assets are copied to its build directory.',
66+
' */',
67+
'export declare const setAssetPath: (path: string) => void;',
68+
'',
69+
'export interface SetPlatformOptions {',
70+
' raf?: (c: FrameRequestCallback) => number;',
71+
' ael?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;',
72+
' rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;',
73+
'}',
74+
'export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;',
75+
"export * from '../types_dir/components';",
76+
'',
77+
].join('\n');
78+
79+
expect(compilerCtx.fs.writeFile).toBeCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, {
80+
outputTargetType: DIST_CUSTOM_ELEMENTS,
81+
});
82+
83+
writeFileSpy.mockRestore();
84+
});
85+
});

src/compiler/types/generate-types.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ const generateTypesOutput = async (
4444

4545
// Copy .d.ts files from src to dist
4646
// In addition, all references to @stencil/core are replaced
47-
let distDtsFilePath: string;
48-
await Promise.all(
47+
const copiedDTSFilePaths = await Promise.all(
4948
srcDtsFiles.map(async (srcDtsFile) => {
5049
const relPath = relative(config.srcDir, srcDtsFile.absPath);
5150
const distPath = join(outputTarget.typesDir, relPath);
@@ -54,15 +53,17 @@ const generateTypesOutput = async (
5453
const distDtsContent = updateStencilTypesImports(outputTarget.typesDir, distPath, originalDtsContent);
5554

5655
await compilerCtx.fs.writeFile(distPath, distDtsContent);
57-
distDtsFilePath = distPath;
56+
return distPath;
5857
})
5958
);
59+
const distDtsFilePath = copiedDTSFilePaths.slice(-1)[0];
6060

6161
const distPath = outputTarget.typesDir;
6262
await generateAppTypes(config, compilerCtx, buildCtx, distPath);
63+
const { typesDir } = outputTarget;
6364

6465
if (distDtsFilePath) {
65-
await generateCustomElementsTypes(config, compilerCtx, buildCtx, distDtsFilePath);
6666
await generateCustomElementsBundleTypes(config, compilerCtx, buildCtx, distDtsFilePath);
67+
await generateCustomElementsTypes(config, compilerCtx, buildCtx, typesDir);
6768
}
6869
};

0 commit comments

Comments
 (0)