Skip to content

Commit

Permalink
feat(compiler): update generation of type declaration file w/ dist-cu…
Browse files Browse the repository at this point in the history
…stom-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
  • Loading branch information
alicewriteswrongs committed Jun 9, 2022
1 parent 0a4881d commit 4cc6b38
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,57 @@ import { normalizePath, dashToPascalCase } from '@utils';
* @param config the Stencil configuration associated with the project being compiled
* @param compilerCtx the current compiler context
* @param buildCtx the context associated with the current build
* @param distDtsFilePath the path to a type declaration file (.d.ts) that is being generated for the output target.
* This path is not necessarily the `components.d.ts` file that is found in the root of a project's `src` directory.
* @param typesDir the path to the directory where type declarations are saved
*/
export const generateCustomElementsTypes = async (
config: d.Config,
compilerCtx: d.CompilerCtx,
buildCtx: d.BuildCtx,
distDtsFilePath: string
typesDir: string
): Promise<void> => {
const outputTargets = config.outputTargets.filter(isOutputTargetDistCustomElements);

await Promise.all(
outputTargets.map((outputTarget) =>
generateCustomElementsTypesOutput(config, compilerCtx, buildCtx, distDtsFilePath, outputTarget)
generateCustomElementsTypesOutput(config, compilerCtx, buildCtx, typesDir, outputTarget)
)
);
};

/**
* Generates types for a single `dist-custom-elements` output target definition in a Stencil project's configuration
*
* @param config the Stencil configuration associated with the project being compiled
* @param compilerCtx the current compiler context
* @param buildCtx the context associated with the current build
* @param distDtsFilePath the path to a type declaration file (.d.ts) that is being generated for the output target.
* This path is not necessarily the `components.d.ts` file that is found in the root of a project's `src` directory.
* @param typesDir path to the directory where type declarations are saved
* @param outputTarget the output target for which types are being currently generated
*/
export const generateCustomElementsTypesOutput = async (
const generateCustomElementsTypesOutput = async (
config: d.Config,
compilerCtx: d.CompilerCtx,
buildCtx: d.BuildCtx,
distDtsFilePath: string,
typesDir: string,
outputTarget: d.OutputTargetDistCustomElements
) => {
// 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');
const componentsDtsRelPath = relDts(outputTarget.dir, distDtsFilePath);
// the directory where types for the individual components are written
const componentsTypeDirectoryPath = relative(outputTarget.dir, join(typesDir, 'components'));

const components = buildCtx.components.filter((m) => !m.isCollectionDependency);

const code = [
`/* ${config.namespace} custom elements */`,
``,
`import type { Components, JSX } from "${componentsDtsRelPath}";`,
...components.map((component) => {
const exportName = dashToPascalCase(component.tagName);
const importName = component.componentClassName;
// typedefs for individual components can be found under paths like
// $TYPES_DIR/components/my-component/my-component.d.ts
const componentDTSPath = join(componentsTypeDirectoryPath, component.tagName, component.tagName);

return `export { ${importName} as ${exportName} } from '${componentDTSPath}';`;
}),
``,
`/**`,
` * Used to manually set the base path where assets can be found.`,
Expand All @@ -69,11 +79,10 @@ export const generateCustomElementsTypesOutput = async (
` rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;`,
`}`,
`export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;`,
``,
`export type { Components, JSX };`,
``,
];

const componentsDtsRelPath = relDts(outputTarget.dir, join(typesDir, 'components.d.ts'));

const usersIndexJsPath = join(config.srcDir, 'index.ts');
const hasUserIndex = await compilerCtx.fs.access(usersIndexJsPath);
if (hasUserIndex) {
Expand All @@ -87,7 +96,6 @@ export const generateCustomElementsTypesOutput = async (
outputTargetType: outputTarget.type,
});

const components = buildCtx.components.filter((m) => !m.isCollectionDependency);
await Promise.all(
components.map(async (cmp) => {
const dtsCode = generateCustomElementType(componentsDtsRelPath, cmp);
Expand Down
85 changes: 85 additions & 0 deletions src/compiler/output-targets/test/custom-elements-types.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { path } from '@stencil/core/compiler';
import { mockConfig, mockStencilSystem, mockBuildCtx, mockCompilerCtx, mockModule } from '@stencil/core/testing';
import type * as d from '../../../declarations';
import * as outputCustomElementsMod from '../dist-custom-elements';
import { stubComponentCompilerMeta } from '../../types/tests/ComponentCompilerMeta.stub';
import { generateCustomElementsTypes } from '../dist-custom-elements/custom-elements-types';
import { DIST_CUSTOM_ELEMENTS } from '../output-utils';
import { join, relative } from 'path';

const setup = () => {
const sys = mockStencilSystem();
const config: d.Config = mockConfig(sys);
const compilerCtx = mockCompilerCtx(config);
const buildCtx = mockBuildCtx(config, compilerCtx);
const root = config.rootDir;
config.configPath = '/testing-path';
config.srcDir = '/src';
config.buildAppCore = true;
config.rootDir = path.join(root, 'User', 'testing', '/');
config.namespace = 'TestApp';
config.buildEs5 = true;
config.globalScript = path.join(root, 'User', 'testing', 'src', 'global.ts');
config.outputTargets = [{ type: DIST_CUSTOM_ELEMENTS, dir: 'my-best-dir' }];

const bundleCustomElementsSpy = jest.spyOn(outputCustomElementsMod, 'bundleCustomElements');

compilerCtx.moduleMap.set('test', mockModule());

return { config, compilerCtx, buildCtx, bundleCustomElementsSpy };
};

describe('Custom Elements Typedef generation', () => {
it('should generate an index.d.ts file corresponding to the index.js file', async () => {
const componentOne = stubComponentCompilerMeta();
const componentTwo = stubComponentCompilerMeta({
componentClassName: 'MyBestComponent',
tagName: 'my-best-component',
});
const { config, compilerCtx, buildCtx } = setup();
buildCtx.components = [componentOne, componentTwo];

const writeFileSpy = jest.spyOn(compilerCtx.fs, 'writeFile');

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

const componentsTypeDirectoryPath = relative('my-best-dir', join('types_dir', 'components'));

const expectedTypedefOutput = [
'/* TestApp custom elements */',
`export { StubCmp as StubCmp } from '${join(componentsTypeDirectoryPath, 'stub-cmp', 'stub-cmp')}';`,
`export { MyBestComponent as MyBestComponent } from '${join(
componentsTypeDirectoryPath,
'my-best-component',
'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;',
'',
'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).toBeCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, {
outputTargetType: DIST_CUSTOM_ELEMENTS,
});

writeFileSpy.mockRestore();
});
});
9 changes: 5 additions & 4 deletions src/compiler/types/generate-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ const generateTypesOutput = async (

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

await compilerCtx.fs.writeFile(distPath, distDtsContent);
distDtsFilePath = distPath;
return distPath;
})
);
const distDtsFilePath = copiedDTSFilePaths.slice(-1)[0];

const distPath = outputTarget.typesDir;
await generateAppTypes(config, compilerCtx, buildCtx, distPath);
const { typesDir } = outputTarget;

if (distDtsFilePath) {
await generateCustomElementsTypes(config, compilerCtx, buildCtx, distDtsFilePath);
await generateCustomElementsBundleTypes(config, compilerCtx, buildCtx, distDtsFilePath);
await generateCustomElementsTypes(config, compilerCtx, buildCtx, typesDir);
}
};

0 comments on commit 4cc6b38

Please sign in to comment.