Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(compiler): custom elements relative typedef import paths #4633

Merged
merged 3 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ const generateCustomElementsTypesOutput = async (
const localComponentTypeDefFilePath = `./${component.tagName}`;

return [
`export { ${importName} as ${exportName} } from '${componentDTSPath}';`,
`export { ${importName} as ${exportName} } from '${
componentDTSPath.startsWith('.') ? componentDTSPath : './' + componentDTSPath
}';`,
// We need to alias each `defineCustomElement` function typedef to match the aliased name given to the
// function in the `index.js`
`export { defineCustomElement as ${defineFunctionExportName} } from '${localComponentTypeDefFilePath}';`,
Expand Down Expand Up @@ -206,8 +208,6 @@ const generateCustomElementType = (componentsDtsRelPath: string, cmp: d.Componen
*/
const relDts = (fromPath: string, dtsPath: string): string => {
dtsPath = relative(fromPath, dtsPath);
if (!dtsPath.startsWith('.')) {
dtsPath = '.' + dtsPath;
}
return normalizePath(dtsPath.replace('.d.ts', ''));

return normalizePath(dtsPath.replace('.d.ts', ''), true);
};
176 changes: 118 additions & 58 deletions src/compiler/output-targets/test/custom-elements-types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,71 +39,131 @@ const setup = () => {
};

describe('Custom Elements Typedef generation', () => {
it('should generate an index.d.ts file corresponding to the index.js file when barrel export behavior is enabled', async () => {
// this component tests the 'happy path' of a component's filename coinciding with its
// tag name
const componentOne = stubComponentCompilerMeta({
tagName: 'my-component',
sourceFilePath: '/src/components/my-component/my-component.tsx',
});
// this component tests that we correctly resolve its path when the component tag does
// not match its filename
const componentTwo = stubComponentCompilerMeta({
sourceFilePath: '/src/components/the-other-component/my-real-best-component.tsx',
componentClassName: 'MyBestComponent',
tagName: 'my-best-component',
describe('export behavior: single-export-module', () => {
let config: d.ValidatedConfig;
let compilerCtx: d.CompilerCtx;
let buildCtx: d.BuildCtx;
let writeFileSpy: jest.SpyInstance;

beforeEach(() => {
// this component tests the 'happy path' of a component's filename coinciding with its
// tag name
const componentOne = stubComponentCompilerMeta({
tagName: 'my-component',
sourceFilePath: '/src/components/my-component/my-component.tsx',
});
// this component tests that we correctly resolve its path when the component tag does
// not match its filename
const componentTwo = stubComponentCompilerMeta({
sourceFilePath: '/src/components/the-other-component/my-real-best-component.tsx',
componentClassName: 'MyBestComponent',
tagName: 'my-best-component',
});
({ config, compilerCtx, buildCtx } = setup());
(config.outputTargets[0] as d.OutputTargetDistCustomElements).customElementsExportBehavior =
'single-export-module';
buildCtx.components = [componentOne, componentTwo];

writeFileSpy = jest.spyOn(compilerCtx.fs, 'writeFile');
});
tanner-reits marked this conversation as resolved.
Show resolved Hide resolved
const { config, compilerCtx, buildCtx } = setup();
(config.outputTargets[0] as d.OutputTargetDistCustomElements).customElementsExportBehavior = 'single-export-module';
buildCtx.components = [componentOne, componentTwo];

const writeFileSpy = jest.spyOn(compilerCtx.fs, 'writeFile');
it('should generate an index.d.ts file corresponding to the index.js file when outputting to a sub-dir of dist', async () => {
await generateCustomElementsTypes(config, compilerCtx, buildCtx, 'types_dir');

await generateCustomElementsTypes(config, compilerCtx, buildCtx, 'types_dir');
const expectedTypedefOutput = [
'/* TestApp custom elements */',
`export { StubCmp as MyComponent } from '../types_dir/components/my-component/my-component';`,
`export { defineCustomElement as defineCustomElementMyComponent } from './my-component';`,
`export { MyBestComponent as MyBestComponent } from '../types_dir/components/the-other-component/my-real-best-component';`,
`export { defineCustomElement as defineCustomElementMyBestComponent } from './my-best-component';`,
'',
'/**',
' * 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;',
'',
'/**',
` * Used to specify a nonce value that corresponds with an application's CSP.`,
' * When set, the nonce will be added to all dynamically created script and style tags at runtime.',
' * Alternatively, the nonce value can be set on a meta tag in the DOM head',
' * (<meta name="csp-nonce" content="{ nonce value here }" />) which',
' * will result in the same behavior.',
' */',
'export declare const setNonce: (nonce: 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;',
"export * from '../types_dir/components';",
'',
].join('\n');

const expectedTypedefOutput = [
'/* TestApp custom elements */',
`export { StubCmp as MyComponent } from '../types_dir/components/my-component/my-component';`,
`export { defineCustomElement as defineCustomElementMyComponent } from './my-component';`,
`export { MyBestComponent as MyBestComponent } from '../types_dir/components/the-other-component/my-real-best-component';`,
`export { defineCustomElement as defineCustomElementMyBestComponent } from './my-best-component';`,
'',
'/**',
' * 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;',
'',
'/**',
` * Used to specify a nonce value that corresponds with an application's CSP.`,
' * When set, the nonce will be added to all dynamically created script and style tags at runtime.',
' * Alternatively, the nonce value can be set on a meta tag in the DOM head',
' * (<meta name="csp-nonce" content="{ nonce value here }" />) which',
' * will result in the same behavior.',
' */',
'export declare const setNonce: (nonce: 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;',
"export * from '../types_dir/components';",
'',
].join('\n');
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith('my-best-dir/index.d.ts', expectedTypedefOutput, {
outputTargetType: DIST_CUSTOM_ELEMENTS,
});

expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith('my-best-dir/index.d.ts', expectedTypedefOutput, {
outputTargetType: DIST_CUSTOM_ELEMENTS,
writeFileSpy.mockRestore();
});

writeFileSpy.mockRestore();
it('should generate an index.d.ts file corresponding to the index.js file when outputting to top-level of dist', async () => {
(config.outputTargets[0] as d.OutputTargetDistCustomElements).dir = 'dist';

await generateCustomElementsTypes(config, compilerCtx, buildCtx, 'dist/types_dir');

const expectedTypedefOutput = [
'/* TestApp custom elements */',
`export { StubCmp as MyComponent } from './types_dir/components/my-component/my-component';`,
`export { defineCustomElement as defineCustomElementMyComponent } from './my-component';`,
`export { MyBestComponent as MyBestComponent } from './types_dir/components/the-other-component/my-real-best-component';`,
`export { defineCustomElement as defineCustomElementMyBestComponent } from './my-best-component';`,
'',
'/**',
' * 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;',
'',
'/**',
` * Used to specify a nonce value that corresponds with an application's CSP.`,
' * When set, the nonce will be added to all dynamically created script and style tags at runtime.',
' * Alternatively, the nonce value can be set on a meta tag in the DOM head',
' * (<meta name="csp-nonce" content="{ nonce value here }" />) which',
' * will result in the same behavior.',
' */',
'export declare const setNonce: (nonce: 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;',
"export * from './types_dir/components';",
'',
].join('\n');

expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith('dist/index.d.ts', expectedTypedefOutput, {
outputTargetType: DIST_CUSTOM_ELEMENTS,
});

writeFileSpy.mockRestore();
});
});

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