Skip to content

Commit feba98c

Browse files
authored
fix(compiler): custom elements relative typedef import paths (#4633)
* fix(compiler): ensure typedef import paths are relative in custom elements * add test for top-level path generation * use `afterEach` for spy restore
1 parent 4d7b138 commit feba98c

File tree

2 files changed

+123
-63
lines changed

2 files changed

+123
-63
lines changed

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ const generateCustomElementsTypesOutput = async (
8282
const localComponentTypeDefFilePath = `./${component.tagName}`;
8383

8484
return [
85-
`export { ${importName} as ${exportName} } from '${componentDTSPath}';`,
85+
`export { ${importName} as ${exportName} } from '${
86+
componentDTSPath.startsWith('.') ? componentDTSPath : './' + componentDTSPath
87+
}';`,
8688
// We need to alias each `defineCustomElement` function typedef to match the aliased name given to the
8789
// function in the `index.js`
8890
`export { defineCustomElement as ${defineFunctionExportName} } from '${localComponentTypeDefFilePath}';`,
@@ -206,8 +208,6 @@ const generateCustomElementType = (componentsDtsRelPath: string, cmp: d.Componen
206208
*/
207209
const relDts = (fromPath: string, dtsPath: string): string => {
208210
dtsPath = relative(fromPath, dtsPath);
209-
if (!dtsPath.startsWith('.')) {
210-
dtsPath = '.' + dtsPath;
211-
}
212-
return normalizePath(dtsPath.replace('.d.ts', ''));
211+
212+
return normalizePath(dtsPath.replace('.d.ts', ''), true);
213213
};

src/compiler/output-targets/test/custom-elements-types.spec.ts

+118-58
Original file line numberDiff line numberDiff line change
@@ -39,71 +39,131 @@ const setup = () => {
3939
};
4040

4141
describe('Custom Elements Typedef generation', () => {
42-
it('should generate an index.d.ts file corresponding to the index.js file when barrel export behavior is enabled', async () => {
43-
// this component tests the 'happy path' of a component's filename coinciding with its
44-
// tag name
45-
const componentOne = stubComponentCompilerMeta({
46-
tagName: 'my-component',
47-
sourceFilePath: '/src/components/my-component/my-component.tsx',
48-
});
49-
// this component tests that we correctly resolve its path when the component tag does
50-
// not match its filename
51-
const componentTwo = stubComponentCompilerMeta({
52-
sourceFilePath: '/src/components/the-other-component/my-real-best-component.tsx',
53-
componentClassName: 'MyBestComponent',
54-
tagName: 'my-best-component',
42+
describe('export behavior: single-export-module', () => {
43+
let config: d.ValidatedConfig;
44+
let compilerCtx: d.CompilerCtx;
45+
let buildCtx: d.BuildCtx;
46+
let writeFileSpy: jest.SpyInstance;
47+
48+
beforeEach(() => {
49+
// this component tests the 'happy path' of a component's filename coinciding with its
50+
// tag name
51+
const componentOne = stubComponentCompilerMeta({
52+
tagName: 'my-component',
53+
sourceFilePath: '/src/components/my-component/my-component.tsx',
54+
});
55+
// this component tests that we correctly resolve its path when the component tag does
56+
// not match its filename
57+
const componentTwo = stubComponentCompilerMeta({
58+
sourceFilePath: '/src/components/the-other-component/my-real-best-component.tsx',
59+
componentClassName: 'MyBestComponent',
60+
tagName: 'my-best-component',
61+
});
62+
({ config, compilerCtx, buildCtx } = setup());
63+
(config.outputTargets[0] as d.OutputTargetDistCustomElements).customElementsExportBehavior =
64+
'single-export-module';
65+
buildCtx.components = [componentOne, componentTwo];
66+
67+
writeFileSpy = jest.spyOn(compilerCtx.fs, 'writeFile');
5568
});
56-
const { config, compilerCtx, buildCtx } = setup();
57-
(config.outputTargets[0] as d.OutputTargetDistCustomElements).customElementsExportBehavior = 'single-export-module';
58-
buildCtx.components = [componentOne, componentTwo];
5969

60-
const writeFileSpy = jest.spyOn(compilerCtx.fs, 'writeFile');
70+
afterEach(() => {
71+
writeFileSpy.mockRestore();
72+
});
6173

62-
await generateCustomElementsTypes(config, compilerCtx, buildCtx, 'types_dir');
74+
it('should generate an index.d.ts file corresponding to the index.js file when outputting to a sub-dir of dist', async () => {
75+
await generateCustomElementsTypes(config, compilerCtx, buildCtx, 'types_dir');
6376

64-
const expectedTypedefOutput = [
65-
'/* TestApp custom elements */',
66-
`export { StubCmp as MyComponent } from '../types_dir/components/my-component/my-component';`,
67-
`export { defineCustomElement as defineCustomElementMyComponent } from './my-component';`,
68-
`export { MyBestComponent as MyBestComponent } from '../types_dir/components/the-other-component/my-real-best-component';`,
69-
`export { defineCustomElement as defineCustomElementMyBestComponent } from './my-best-component';`,
70-
'',
71-
'/**',
72-
' * Used to manually set the base path where assets can be found.',
73-
' * If the script is used as "module", it\'s recommended to use "import.meta.url",',
74-
' * such as "setAssetPath(import.meta.url)". Other options include',
75-
' * "setAssetPath(document.currentScript.src)", or using a bundler\'s replace plugin to',
76-
' * dynamically set the path at build time, such as "setAssetPath(process.env.ASSET_PATH)".',
77-
' * But do note that this configuration depends on how your script is bundled, or lack of',
78-
' * bundling, and where your assets can be loaded from. Additionally custom bundling',
79-
' * will have to ensure the static assets are copied to its build directory.',
80-
' */',
81-
'export declare const setAssetPath: (path: string) => void;',
82-
'',
83-
'/**',
84-
` * Used to specify a nonce value that corresponds with an application's CSP.`,
85-
' * When set, the nonce will be added to all dynamically created script and style tags at runtime.',
86-
' * Alternatively, the nonce value can be set on a meta tag in the DOM head',
87-
' * (<meta name="csp-nonce" content="{ nonce value here }" />) which',
88-
' * will result in the same behavior.',
89-
' */',
90-
'export declare const setNonce: (nonce: string) => void',
91-
'',
92-
'export interface SetPlatformOptions {',
93-
' raf?: (c: FrameRequestCallback) => number;',
94-
' ael?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;',
95-
' rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;',
96-
'}',
97-
'export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;',
98-
"export * from '../types_dir/components';",
99-
'',
100-
].join('\n');
77+
const expectedTypedefOutput = [
78+
'/* TestApp custom elements */',
79+
`export { StubCmp as MyComponent } from '../types_dir/components/my-component/my-component';`,
80+
`export { defineCustomElement as defineCustomElementMyComponent } from './my-component';`,
81+
`export { MyBestComponent as MyBestComponent } from '../types_dir/components/the-other-component/my-real-best-component';`,
82+
`export { defineCustomElement as defineCustomElementMyBestComponent } from './my-best-component';`,
83+
'',
84+
'/**',
85+
' * Used to manually set the base path where assets can be found.',
86+
' * If the script is used as "module", it\'s recommended to use "import.meta.url",',
87+
' * such as "setAssetPath(import.meta.url)". Other options include',
88+
' * "setAssetPath(document.currentScript.src)", or using a bundler\'s replace plugin to',
89+
' * dynamically set the path at build time, such as "setAssetPath(process.env.ASSET_PATH)".',
90+
' * But do note that this configuration depends on how your script is bundled, or lack of',
91+
' * bundling, and where your assets can be loaded from. Additionally custom bundling',
92+
' * will have to ensure the static assets are copied to its build directory.',
93+
' */',
94+
'export declare const setAssetPath: (path: string) => void;',
95+
'',
96+
'/**',
97+
` * Used to specify a nonce value that corresponds with an application's CSP.`,
98+
' * When set, the nonce will be added to all dynamically created script and style tags at runtime.',
99+
' * Alternatively, the nonce value can be set on a meta tag in the DOM head',
100+
' * (<meta name="csp-nonce" content="{ nonce value here }" />) which',
101+
' * will result in the same behavior.',
102+
' */',
103+
'export declare const setNonce: (nonce: string) => void',
104+
'',
105+
'export interface SetPlatformOptions {',
106+
' raf?: (c: FrameRequestCallback) => number;',
107+
' ael?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;',
108+
' rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;',
109+
'}',
110+
'export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;',
111+
"export * from '../types_dir/components';",
112+
'',
113+
].join('\n');
101114

102-
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith('my-best-dir/index.d.ts', expectedTypedefOutput, {
103-
outputTargetType: DIST_CUSTOM_ELEMENTS,
115+
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith('my-best-dir/index.d.ts', expectedTypedefOutput, {
116+
outputTargetType: DIST_CUSTOM_ELEMENTS,
117+
});
104118
});
105119

106-
writeFileSpy.mockRestore();
120+
it('should generate an index.d.ts file corresponding to the index.js file when outputting to top-level of dist', async () => {
121+
(config.outputTargets[0] as d.OutputTargetDistCustomElements).dir = 'dist';
122+
123+
await generateCustomElementsTypes(config, compilerCtx, buildCtx, 'dist/types_dir');
124+
125+
const expectedTypedefOutput = [
126+
'/* TestApp custom elements */',
127+
`export { StubCmp as MyComponent } from './types_dir/components/my-component/my-component';`,
128+
`export { defineCustomElement as defineCustomElementMyComponent } from './my-component';`,
129+
`export { MyBestComponent as MyBestComponent } from './types_dir/components/the-other-component/my-real-best-component';`,
130+
`export { defineCustomElement as defineCustomElementMyBestComponent } from './my-best-component';`,
131+
'',
132+
'/**',
133+
' * Used to manually set the base path where assets can be found.',
134+
' * If the script is used as "module", it\'s recommended to use "import.meta.url",',
135+
' * such as "setAssetPath(import.meta.url)". Other options include',
136+
' * "setAssetPath(document.currentScript.src)", or using a bundler\'s replace plugin to',
137+
' * dynamically set the path at build time, such as "setAssetPath(process.env.ASSET_PATH)".',
138+
' * But do note that this configuration depends on how your script is bundled, or lack of',
139+
' * bundling, and where your assets can be loaded from. Additionally custom bundling',
140+
' * will have to ensure the static assets are copied to its build directory.',
141+
' */',
142+
'export declare const setAssetPath: (path: string) => void;',
143+
'',
144+
'/**',
145+
` * Used to specify a nonce value that corresponds with an application's CSP.`,
146+
' * When set, the nonce will be added to all dynamically created script and style tags at runtime.',
147+
' * Alternatively, the nonce value can be set on a meta tag in the DOM head',
148+
' * (<meta name="csp-nonce" content="{ nonce value here }" />) which',
149+
' * will result in the same behavior.',
150+
' */',
151+
'export declare const setNonce: (nonce: string) => void',
152+
'',
153+
'export interface SetPlatformOptions {',
154+
' raf?: (c: FrameRequestCallback) => number;',
155+
' ael?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;',
156+
' rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;',
157+
'}',
158+
'export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;',
159+
"export * from './types_dir/components';",
160+
'',
161+
].join('\n');
162+
163+
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith('dist/index.d.ts', expectedTypedefOutput, {
164+
outputTargetType: DIST_CUSTOM_ELEMENTS,
165+
});
166+
});
107167
});
108168

109169
it('should generate an index.d.ts file corresponding to the index.js file when barrel export behavior is disabled', async () => {

0 commit comments

Comments
 (0)