From 7a6774576cb2f74ef04db07c9f757ba80fab0286 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Mon, 6 May 2024 15:32:59 -0400 Subject: [PATCH 1/8] generate export maps in package.json on build --- src/compiler/build/write-build.ts | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/compiler/build/write-build.ts b/src/compiler/build/write-build.ts index 076657a7239..acfc40cfe2f 100644 --- a/src/compiler/build/write-build.ts +++ b/src/compiler/build/write-build.ts @@ -36,6 +36,43 @@ export const writeBuild = async ( buildCtx.debug(`in-memory-fs: ${compilerCtx.fs.getMemoryStats()}`); buildCtx.debug(`cache: ${compilerCtx.cache.getMemoryStats()}`); + // TODO: this would need to account for other config values like output directory + // and checking which output targets are used in the build to know which exports to create + const namespace = buildCtx.config.fsNamespace; + const packageJsonExports: any = { + '.': { + import: `./dist/${namespace}/${namespace}.esm.js`, + require: `./dist/${namespace}/${namespace}.cjs.js`, + }, + './loader': { + import: './loader/index.js', + require: './loader/index.cjs', + types: './loader/index.d.ts', + }, + }; + buildCtx.components.forEach((cmp) => { + packageJsonExports[`./${cmp.tagName}`] = { + import: `./dist/components/${cmp.tagName}.js`, + types: `./dist/components/${cmp.tagName}.d.ts`, + }; + }); + + // Write updated `package.json` file + await compilerCtx.fs.writeFile( + config.packageJsonFilePath, + JSON.stringify( + { + ...buildCtx.packageJson, + exports: packageJsonExports, + }, + null, + 2, + ), + { + immediateWrite: true, + }, + ); + await outputServiceWorkers(config, buildCtx); await validateBuildFiles(config, compilerCtx, buildCtx); } catch (e: any) { From 97885773cc3195dfec7583a331c908407eb9ab4f Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Fri, 10 May 2024 11:57:02 -0400 Subject: [PATCH 2/8] npm cli commands --- src/compiler/build/write-build.ts | 73 ++++++++++++++++++------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/src/compiler/build/write-build.ts b/src/compiler/build/write-build.ts index acfc40cfe2f..65a3b87bfc0 100644 --- a/src/compiler/build/write-build.ts +++ b/src/compiler/build/write-build.ts @@ -1,4 +1,5 @@ import { catchError } from '@utils'; +import { execSync } from 'child_process'; import type * as d from '../../declarations'; import { outputServiceWorkers } from '../output-targets/output-service-workers'; @@ -39,39 +40,51 @@ export const writeBuild = async ( // TODO: this would need to account for other config values like output directory // and checking which output targets are used in the build to know which exports to create const namespace = buildCtx.config.fsNamespace; - const packageJsonExports: any = { - '.': { - import: `./dist/${namespace}/${namespace}.esm.js`, - require: `./dist/${namespace}/${namespace}.cjs.js`, - }, - './loader': { - import: './loader/index.js', - require: './loader/index.cjs', - types: './loader/index.d.ts', - }, - }; + execSync(`npm pkg set "exports[.][import]"="./dist/${namespace}/${namespace}.esm.js"`); + execSync(`npm pkg set "exports[.][require]"="./dist/${namespace}/${namespace}.cjs.js"`); + + execSync(`npm pkg set "exports[./loader][import]"="./loader/index.js"`); + execSync(`npm pkg set "exports[./loader][require]"="./loader/index.cjs"`); + execSync(`npm pkg set "exports[./loader][types]"="./loader/index.d.ts"`); + buildCtx.components.forEach((cmp) => { - packageJsonExports[`./${cmp.tagName}`] = { - import: `./dist/components/${cmp.tagName}.js`, - types: `./dist/components/${cmp.tagName}.d.ts`, - }; + execSync(`npm pkg set "exports[./${cmp.tagName}][import]"="./dist/components/${cmp.tagName}.js"`); + execSync(`npm pkg set "exports[./${cmp.tagName}][types]"="./dist/components/${cmp.tagName}.d.ts"`); }); - // Write updated `package.json` file - await compilerCtx.fs.writeFile( - config.packageJsonFilePath, - JSON.stringify( - { - ...buildCtx.packageJson, - exports: packageJsonExports, - }, - null, - 2, - ), - { - immediateWrite: true, - }, - ); + // const packageJsonExports: any = { + // '.': { + // import: `./dist/${namespace}/${namespace}.esm.js`, + // require: `./dist/${namespace}/${namespace}.cjs.js`, + // }, + // './loader': { + // import: './loader/index.js', + // require: './loader/index.cjs', + // types: './loader/index.d.ts', + // }, + // }; + // buildCtx.components.forEach((cmp) => { + // packageJsonExports[`./${cmp.tagName}`] = { + // import: `./dist/components/${cmp.tagName}.js`, + // types: `./dist/components/${cmp.tagName}.d.ts`, + // }; + // }); + + // // Write updated `package.json` file + // await compilerCtx.fs.writeFile( + // config.packageJsonFilePath, + // JSON.stringify( + // { + // ...buildCtx.packageJson, + // exports: packageJsonExports, + // }, + // null, + // 2, + // ), + // { + // immediateWrite: true, + // }, + // ); await outputServiceWorkers(config, buildCtx); await validateBuildFiles(config, compilerCtx, buildCtx); From 47111a73af1e3261202a8eb6429537dc9c7e5935 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Tue, 28 May 2024 16:38:41 -0400 Subject: [PATCH 3/8] add config flag & generation conditionals --- src/compiler/build/write-build.ts | 102 ++++++++++---------- src/compiler/config/validate-config.ts | 1 + src/declarations/stencil-public-compiler.ts | 8 ++ 3 files changed, 61 insertions(+), 50 deletions(-) diff --git a/src/compiler/build/write-build.ts b/src/compiler/build/write-build.ts index 65a3b87bfc0..bd33d0ad0b8 100644 --- a/src/compiler/build/write-build.ts +++ b/src/compiler/build/write-build.ts @@ -1,7 +1,8 @@ -import { catchError } from '@utils'; +import { catchError, isOutputTargetDistCustomElements, isOutputTargetDistLazyLoader } from '@utils'; +import { relative } from '@utils'; import { execSync } from 'child_process'; -import type * as d from '../../declarations'; +import * as d from '../../declarations'; import { outputServiceWorkers } from '../output-targets/output-service-workers'; import { validateBuildFiles } from './validate-files'; @@ -37,54 +38,9 @@ export const writeBuild = async ( buildCtx.debug(`in-memory-fs: ${compilerCtx.fs.getMemoryStats()}`); buildCtx.debug(`cache: ${compilerCtx.cache.getMemoryStats()}`); - // TODO: this would need to account for other config values like output directory - // and checking which output targets are used in the build to know which exports to create - const namespace = buildCtx.config.fsNamespace; - execSync(`npm pkg set "exports[.][import]"="./dist/${namespace}/${namespace}.esm.js"`); - execSync(`npm pkg set "exports[.][require]"="./dist/${namespace}/${namespace}.cjs.js"`); - - execSync(`npm pkg set "exports[./loader][import]"="./loader/index.js"`); - execSync(`npm pkg set "exports[./loader][require]"="./loader/index.cjs"`); - execSync(`npm pkg set "exports[./loader][types]"="./loader/index.d.ts"`); - - buildCtx.components.forEach((cmp) => { - execSync(`npm pkg set "exports[./${cmp.tagName}][import]"="./dist/components/${cmp.tagName}.js"`); - execSync(`npm pkg set "exports[./${cmp.tagName}][types]"="./dist/components/${cmp.tagName}.d.ts"`); - }); - - // const packageJsonExports: any = { - // '.': { - // import: `./dist/${namespace}/${namespace}.esm.js`, - // require: `./dist/${namespace}/${namespace}.cjs.js`, - // }, - // './loader': { - // import: './loader/index.js', - // require: './loader/index.cjs', - // types: './loader/index.d.ts', - // }, - // }; - // buildCtx.components.forEach((cmp) => { - // packageJsonExports[`./${cmp.tagName}`] = { - // import: `./dist/components/${cmp.tagName}.js`, - // types: `./dist/components/${cmp.tagName}.d.ts`, - // }; - // }); - - // // Write updated `package.json` file - // await compilerCtx.fs.writeFile( - // config.packageJsonFilePath, - // JSON.stringify( - // { - // ...buildCtx.packageJson, - // exports: packageJsonExports, - // }, - // null, - // 2, - // ), - // { - // immediateWrite: true, - // }, - // ); + if (config.generateExportMaps) { + writeExportMaps(config, buildCtx); + } await outputServiceWorkers(config, buildCtx); await validateBuildFiles(config, compilerCtx, buildCtx); @@ -94,3 +50,49 @@ export const writeBuild = async ( timeSpan.finish(`writeBuildFiles finished, files wrote: ${totalFilesWrote}`); }; + +/** + * Create export map entry point definitions for the `package.json` file using the npm CLI. + * This will generate a root entry point for the package, as well as entry points for each component and + * the lazy loader (if applicable). + * + * @param config The validated Stencil config + * @param buildCtx The build context containing the components to generate export maps for + */ +const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx) => { + const namespace = buildCtx.config.fsNamespace; + + execSync(`npm pkg set "exports[.][import]"="./dist/${namespace}/${namespace}.esm.js"`); + execSync(`npm pkg set "exports[.][require]"="./dist/${namespace}/${namespace}.cjs.js"`); + + const distLazyLoader = config.outputTargets.find(isOutputTargetDistLazyLoader); + if (distLazyLoader != null) { + // Calculate relative path from project root to lazy-loader output directory + let outDir = relative(config.rootDir, distLazyLoader.dir); + if (!outDir.startsWith('.')) { + outDir = './' + outDir; + } + + execSync(`npm pkg set "exports[./loader][import]"="${outDir}/index.js"`); + execSync(`npm pkg set "exports[./loader][require]"="${outDir}/index.cjs"`); + execSync(`npm pkg set "exports[./loader][types]"="${outDir}/index.d.ts"`); + } + + const distCustomElements = config.outputTargets.find(isOutputTargetDistCustomElements); + if (distCustomElements != null) { + // Calculate relative path from project root to custom elements output directory + let outDir = relative(config.rootDir, distCustomElements.dir); + if (!outDir.startsWith('.')) { + outDir = './' + outDir; + } + + buildCtx.components.forEach((cmp) => { + console.log('path', cmp.jsFilePath); + execSync(`npm pkg set "exports[./${cmp.tagName}][import]"="${outDir}/${cmp.tagName}.js"`); + + if (distCustomElements.generateTypeDeclarations) { + execSync(`npm pkg set "exports[./${cmp.tagName}][types]"="${outDir}/${cmp.tagName}.d.ts"`); + } + }); + } +}; diff --git a/src/compiler/config/validate-config.ts b/src/compiler/config/validate-config.ts index 65f31af59c0..ae2c6314a97 100644 --- a/src/compiler/config/validate-config.ts +++ b/src/compiler/config/validate-config.ts @@ -124,6 +124,7 @@ export const validateConfig = ( devMode, extras: config.extras || {}, flags, + generateExportMaps: isBoolean(config.generateExportMaps) ? config.generateExportMaps : false, hashFileNames, hashedFileNameLength: config.hashedFileNameLength ?? DEFAULT_HASHED_FILENAME_LENGTH, hydratedFlag: validateHydrated(config), diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index 7d1c3d0cd63..2ad83f44b5e 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -84,6 +84,14 @@ export interface StencilConfig { */ globalStyle?: string; + /** + * Will generate {@link https://nodejs.org/api/packages.html#packages_exports export map} entry points + * for each component in the build when `true`. + * + * @default false + */ + generateExportMaps?: boolean; + /** * When the hashFileNames config is set to true, and it is a production build, * the hashedFileNameLength config is used to determine how many characters the file name's hash should be. From 6bbc699ef559cce99fe4e9e7bba33f85cd4ccf13 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Thu, 30 May 2024 11:48:57 -0400 Subject: [PATCH 4/8] wip(tests) --- .../build/test/write-export-maps.spec.ts | 39 ++++++++++++++ src/compiler/build/write-build.ts | 51 +----------------- src/compiler/build/write-export-maps.ts | 53 +++++++++++++++++++ 3 files changed, 94 insertions(+), 49 deletions(-) create mode 100644 src/compiler/build/test/write-export-maps.spec.ts create mode 100644 src/compiler/build/write-export-maps.ts diff --git a/src/compiler/build/test/write-export-maps.spec.ts b/src/compiler/build/test/write-export-maps.spec.ts new file mode 100644 index 00000000000..30f157834b6 --- /dev/null +++ b/src/compiler/build/test/write-export-maps.spec.ts @@ -0,0 +1,39 @@ +import { mockBuildCtx, mockValidatedConfig } from '@stencil/core/testing'; + +import * as d from '../../../declarations'; +import { writeExportMaps } from '../write-export-maps'; + +const execSyncMock = jest.fn(); +jest.mock('child_process', () => ({ + execSync: execSyncMock, +})); + +describe('writeExportMaps', () => { + let config: d.ValidatedConfig; + let buildCtx: d.BuildCtx; + + beforeEach(() => { + config = mockValidatedConfig(); + buildCtx = mockBuildCtx(config); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it('should always generate the default exports', () => { + writeExportMaps(config, buildCtx); + + expect(execSyncMock).toHaveBeenCalledTimes(2); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[.][import]"="./dist/${config.fsNamespace}/${config.fsNamespace}.esm.js"`, + ); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[.][require]"="./dist/${config.fsNamespace}/${config.fsNamespace}.cjs.js"`, + ); + }); + + // it('should generate the lazy loader exports if the output target is present', () => {}); + + // it('should generate the custom elements exports if the output target is present', () => {}); +}); diff --git a/src/compiler/build/write-build.ts b/src/compiler/build/write-build.ts index bd33d0ad0b8..447e4940289 100644 --- a/src/compiler/build/write-build.ts +++ b/src/compiler/build/write-build.ts @@ -1,10 +1,9 @@ -import { catchError, isOutputTargetDistCustomElements, isOutputTargetDistLazyLoader } from '@utils'; -import { relative } from '@utils'; -import { execSync } from 'child_process'; +import { catchError } from '@utils'; import * as d from '../../declarations'; import { outputServiceWorkers } from '../output-targets/output-service-workers'; import { validateBuildFiles } from './validate-files'; +import { writeExportMaps } from './write-export-maps'; /** * Writes files to disk as a result of compilation @@ -50,49 +49,3 @@ export const writeBuild = async ( timeSpan.finish(`writeBuildFiles finished, files wrote: ${totalFilesWrote}`); }; - -/** - * Create export map entry point definitions for the `package.json` file using the npm CLI. - * This will generate a root entry point for the package, as well as entry points for each component and - * the lazy loader (if applicable). - * - * @param config The validated Stencil config - * @param buildCtx The build context containing the components to generate export maps for - */ -const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx) => { - const namespace = buildCtx.config.fsNamespace; - - execSync(`npm pkg set "exports[.][import]"="./dist/${namespace}/${namespace}.esm.js"`); - execSync(`npm pkg set "exports[.][require]"="./dist/${namespace}/${namespace}.cjs.js"`); - - const distLazyLoader = config.outputTargets.find(isOutputTargetDistLazyLoader); - if (distLazyLoader != null) { - // Calculate relative path from project root to lazy-loader output directory - let outDir = relative(config.rootDir, distLazyLoader.dir); - if (!outDir.startsWith('.')) { - outDir = './' + outDir; - } - - execSync(`npm pkg set "exports[./loader][import]"="${outDir}/index.js"`); - execSync(`npm pkg set "exports[./loader][require]"="${outDir}/index.cjs"`); - execSync(`npm pkg set "exports[./loader][types]"="${outDir}/index.d.ts"`); - } - - const distCustomElements = config.outputTargets.find(isOutputTargetDistCustomElements); - if (distCustomElements != null) { - // Calculate relative path from project root to custom elements output directory - let outDir = relative(config.rootDir, distCustomElements.dir); - if (!outDir.startsWith('.')) { - outDir = './' + outDir; - } - - buildCtx.components.forEach((cmp) => { - console.log('path', cmp.jsFilePath); - execSync(`npm pkg set "exports[./${cmp.tagName}][import]"="${outDir}/${cmp.tagName}.js"`); - - if (distCustomElements.generateTypeDeclarations) { - execSync(`npm pkg set "exports[./${cmp.tagName}][types]"="${outDir}/${cmp.tagName}.d.ts"`); - } - }); - } -}; diff --git a/src/compiler/build/write-export-maps.ts b/src/compiler/build/write-export-maps.ts new file mode 100644 index 00000000000..951cd7bb7f0 --- /dev/null +++ b/src/compiler/build/write-export-maps.ts @@ -0,0 +1,53 @@ +import { isOutputTargetDistCustomElements, isOutputTargetDistLazyLoader } from '@utils'; +import { relative } from '@utils'; +import { execSync } from 'child_process'; + +import * as d from '../../declarations'; + +/** + * Create export map entry point definitions for the `package.json` file using the npm CLI. + * This will generate a root entry point for the package, as well as entry points for each component and + * the lazy loader (if applicable). + * + * @param config The validated Stencil config + * @param buildCtx The build context containing the components to generate export maps for + */ +export const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx) => { + const namespace = buildCtx.config.fsNamespace; + + execSync(`npm pkg set "exports[.][import]"="./dist/${namespace}/${namespace}.esm.js"`); + execSync(`npm pkg set "exports[.][require]"="./dist/${namespace}/${namespace}.cjs.js"`); + + console.log('HERE'); + + const distLazyLoader = config.outputTargets.find(isOutputTargetDistLazyLoader); + if (distLazyLoader != null) { + // Calculate relative path from project root to lazy-loader output directory + let outDir = relative(config.rootDir, distLazyLoader.dir); + if (!outDir.startsWith('.')) { + outDir = './' + outDir; + } + + execSync(`npm pkg set "exports[./loader][import]"="${outDir}/index.js"`); + execSync(`npm pkg set "exports[./loader][require]"="${outDir}/index.cjs"`); + execSync(`npm pkg set "exports[./loader][types]"="${outDir}/index.d.ts"`); + } + + const distCustomElements = config.outputTargets.find(isOutputTargetDistCustomElements); + if (distCustomElements != null) { + // Calculate relative path from project root to custom elements output directory + let outDir = relative(config.rootDir, distCustomElements.dir); + if (!outDir.startsWith('.')) { + outDir = './' + outDir; + } + + buildCtx.components.forEach((cmp) => { + console.log('path', cmp.jsFilePath); + execSync(`npm pkg set "exports[./${cmp.tagName}][import]"="${outDir}/${cmp.tagName}.js"`); + + if (distCustomElements.generateTypeDeclarations) { + execSync(`npm pkg set "exports[./${cmp.tagName}][types]"="${outDir}/${cmp.tagName}.d.ts"`); + } + }); + } +}; From f55258233d8749fd15a7f761c2616d9233dbbe1f Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Tue, 4 Jun 2024 11:08:34 -0400 Subject: [PATCH 5/8] add tests --- .../build/test/write-export-maps.spec.ts | 119 ++++++++++++++++-- src/compiler/build/write-export-maps.ts | 2 - 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/src/compiler/build/test/write-export-maps.spec.ts b/src/compiler/build/test/write-export-maps.spec.ts index 30f157834b6..bb24008e254 100644 --- a/src/compiler/build/test/write-export-maps.spec.ts +++ b/src/compiler/build/test/write-export-maps.spec.ts @@ -1,39 +1,134 @@ import { mockBuildCtx, mockValidatedConfig } from '@stencil/core/testing'; +import childProcess from 'child_process'; import * as d from '../../../declarations'; +import { stubComponentCompilerMeta } from '../../types/tests/ComponentCompilerMeta.stub'; import { writeExportMaps } from '../write-export-maps'; -const execSyncMock = jest.fn(); -jest.mock('child_process', () => ({ - execSync: execSyncMock, -})); - describe('writeExportMaps', () => { let config: d.ValidatedConfig; let buildCtx: d.BuildCtx; + let execSyncSpy: jest.SpyInstance; beforeEach(() => { config = mockValidatedConfig(); buildCtx = mockBuildCtx(config); + + execSyncSpy = jest.spyOn(childProcess, 'execSync').mockImplementation(() => ''); }); - afterAll(() => { - jest.restoreAllMocks(); + afterEach(() => { + jest.clearAllMocks(); }); it('should always generate the default exports', () => { writeExportMaps(config, buildCtx); - expect(execSyncMock).toHaveBeenCalledTimes(2); - expect(execSyncMock).toHaveBeenCalledWith( + expect(execSyncSpy).toHaveBeenCalledTimes(2); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[.][import]"="./dist/${config.fsNamespace}/${config.fsNamespace}.esm.js"`, + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[.][require]"="./dist/${config.fsNamespace}/${config.fsNamespace}.cjs.js"`, + ); + }); + + it('should generate the lazy loader exports if the output target is present', () => { + config.rootDir = '/'; + config.outputTargets.push({ + type: 'dist-lazy-loader', + dir: '/dist/lazy-loader', + empty: true, + esmDir: '/dist/esm', + cjsDir: '/dist/cjs', + componentDts: '/dist/components.d.ts', + }); + + writeExportMaps(config, buildCtx); + + expect(execSyncSpy).toHaveBeenCalledTimes(5); + expect(execSyncSpy).toHaveBeenCalledWith( `npm pkg set "exports[.][import]"="./dist/${config.fsNamespace}/${config.fsNamespace}.esm.js"`, ); - expect(execSyncMock).toHaveBeenCalledWith( + expect(execSyncSpy).toHaveBeenCalledWith( `npm pkg set "exports[.][require]"="./dist/${config.fsNamespace}/${config.fsNamespace}.cjs.js"`, ); + expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[./loader][import]"="./dist/lazy-loader/index.js"`); + expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[./loader][require]"="./dist/lazy-loader/index.cjs"`); + expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[./loader][types]"="./dist/lazy-loader/index.d.ts"`); }); - // it('should generate the lazy loader exports if the output target is present', () => {}); + it('should generate the custom elements exports if the output target is present', () => { + config.rootDir = '/'; + config.outputTargets.push({ + type: 'dist-custom-elements', + dir: '/dist/components', + generateTypeDeclarations: true, + }); + + buildCtx.components = [ + stubComponentCompilerMeta({ + tagName: 'my-component', + componentClassName: 'MyComponent', + }), + ]; + + writeExportMaps(config, buildCtx); - // it('should generate the custom elements exports if the output target is present', () => {}); + expect(execSyncSpy).toHaveBeenCalledTimes(4); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[.][import]"="./dist/${config.fsNamespace}/${config.fsNamespace}.esm.js"`, + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[.][require]"="./dist/${config.fsNamespace}/${config.fsNamespace}.cjs.js"`, + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[./my-component][import]"="./dist/components/my-component.js"`, + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[./my-component][types]"="./dist/components/my-component.d.ts"`, + ); + }); + + it('should generate the custom elements exports for multiple components', () => { + config.rootDir = '/'; + config.outputTargets.push({ + type: 'dist-custom-elements', + dir: '/dist/components', + generateTypeDeclarations: true, + }); + + buildCtx.components = [ + stubComponentCompilerMeta({ + tagName: 'my-component', + componentClassName: 'MyComponent', + }), + stubComponentCompilerMeta({ + tagName: 'my-other-component', + componentClassName: 'MyOtherComponent', + }), + ]; + + writeExportMaps(config, buildCtx); + + expect(execSyncSpy).toHaveBeenCalledTimes(6); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[.][import]"="./dist/${config.fsNamespace}/${config.fsNamespace}.esm.js"`, + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[.][require]"="./dist/${config.fsNamespace}/${config.fsNamespace}.cjs.js"`, + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[./my-component][import]"="./dist/components/my-component.js"`, + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[./my-component][types]"="./dist/components/my-component.d.ts"`, + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[./my-other-component][import]"="./dist/components/my-other-component.js"`, + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `npm pkg set "exports[./my-other-component][types]"="./dist/components/my-other-component.d.ts"`, + ); + }); }); diff --git a/src/compiler/build/write-export-maps.ts b/src/compiler/build/write-export-maps.ts index 951cd7bb7f0..531d9ee9f7f 100644 --- a/src/compiler/build/write-export-maps.ts +++ b/src/compiler/build/write-export-maps.ts @@ -18,8 +18,6 @@ export const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx) execSync(`npm pkg set "exports[.][import]"="./dist/${namespace}/${namespace}.esm.js"`); execSync(`npm pkg set "exports[.][require]"="./dist/${namespace}/${namespace}.cjs.js"`); - console.log('HERE'); - const distLazyLoader = config.outputTargets.find(isOutputTargetDistLazyLoader); if (distLazyLoader != null) { // Calculate relative path from project root to lazy-loader output directory From 7ce1ff2b6919cba0d4a724d86e337e8099ae00bd Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Tue, 4 Jun 2024 12:33:52 -0400 Subject: [PATCH 6/8] remove log --- src/compiler/build/write-export-maps.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/build/write-export-maps.ts b/src/compiler/build/write-export-maps.ts index 531d9ee9f7f..275fd1a646c 100644 --- a/src/compiler/build/write-export-maps.ts +++ b/src/compiler/build/write-export-maps.ts @@ -40,7 +40,6 @@ export const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx) } buildCtx.components.forEach((cmp) => { - console.log('path', cmp.jsFilePath); execSync(`npm pkg set "exports[./${cmp.tagName}][import]"="${outDir}/${cmp.tagName}.js"`); if (distCustomElements.generateTypeDeclarations) { From 2aec10702d638063c292ec0b2d4c9f72864aa37f Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Thu, 6 Jun 2024 09:20:30 -0400 Subject: [PATCH 7/8] account for primary output target --- .../build/test/write-export-maps.spec.ts | 61 +++++++++++-------- src/compiler/build/write-export-maps.ts | 39 ++++++++++-- .../validate-primary-package-output-target.ts | 28 +++++++-- 3 files changed, 93 insertions(+), 35 deletions(-) diff --git a/src/compiler/build/test/write-export-maps.spec.ts b/src/compiler/build/test/write-export-maps.spec.ts index bb24008e254..17c49e91435 100644 --- a/src/compiler/build/test/write-export-maps.spec.ts +++ b/src/compiler/build/test/write-export-maps.spec.ts @@ -21,16 +21,43 @@ describe('writeExportMaps', () => { jest.clearAllMocks(); }); - it('should always generate the default exports', () => { + it('should not generate any exports if there are no output targets', () => { + writeExportMaps(config, buildCtx); + + expect(execSyncSpy).toHaveBeenCalledTimes(0); + }); + + it('should generate the default exports for the lazy build if present', () => { + config.outputTargets = [ + { + type: 'dist', + dir: '/dist', + typesDir: '/dist/types', + }, + ]; + + writeExportMaps(config, buildCtx); + + expect(execSyncSpy).toHaveBeenCalledTimes(3); + expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[.][import]"="./dist/index.js"`); + expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[.][require]"="./dist/index.cjs.js"`); + expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[.][types]"="./dist/types/index.d.ts"`); + }); + + it('should generate the default exports for the custom elements build if present', () => { + config.outputTargets = [ + { + type: 'dist-custom-elements', + dir: '/dist/components', + generateTypeDeclarations: true, + }, + ]; + writeExportMaps(config, buildCtx); expect(execSyncSpy).toHaveBeenCalledTimes(2); - expect(execSyncSpy).toHaveBeenCalledWith( - `npm pkg set "exports[.][import]"="./dist/${config.fsNamespace}/${config.fsNamespace}.esm.js"`, - ); - expect(execSyncSpy).toHaveBeenCalledWith( - `npm pkg set "exports[.][require]"="./dist/${config.fsNamespace}/${config.fsNamespace}.cjs.js"`, - ); + expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[.][import]"="./dist/components/index.js"`); + expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[.][types]"="./dist/components/index.d.ts"`); }); it('should generate the lazy loader exports if the output target is present', () => { @@ -46,13 +73,7 @@ describe('writeExportMaps', () => { writeExportMaps(config, buildCtx); - expect(execSyncSpy).toHaveBeenCalledTimes(5); - expect(execSyncSpy).toHaveBeenCalledWith( - `npm pkg set "exports[.][import]"="./dist/${config.fsNamespace}/${config.fsNamespace}.esm.js"`, - ); - expect(execSyncSpy).toHaveBeenCalledWith( - `npm pkg set "exports[.][require]"="./dist/${config.fsNamespace}/${config.fsNamespace}.cjs.js"`, - ); + expect(execSyncSpy).toHaveBeenCalledTimes(3); expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[./loader][import]"="./dist/lazy-loader/index.js"`); expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[./loader][require]"="./dist/lazy-loader/index.cjs"`); expect(execSyncSpy).toHaveBeenCalledWith(`npm pkg set "exports[./loader][types]"="./dist/lazy-loader/index.d.ts"`); @@ -76,12 +97,6 @@ describe('writeExportMaps', () => { writeExportMaps(config, buildCtx); expect(execSyncSpy).toHaveBeenCalledTimes(4); - expect(execSyncSpy).toHaveBeenCalledWith( - `npm pkg set "exports[.][import]"="./dist/${config.fsNamespace}/${config.fsNamespace}.esm.js"`, - ); - expect(execSyncSpy).toHaveBeenCalledWith( - `npm pkg set "exports[.][require]"="./dist/${config.fsNamespace}/${config.fsNamespace}.cjs.js"`, - ); expect(execSyncSpy).toHaveBeenCalledWith( `npm pkg set "exports[./my-component][import]"="./dist/components/my-component.js"`, ); @@ -112,12 +127,6 @@ describe('writeExportMaps', () => { writeExportMaps(config, buildCtx); expect(execSyncSpy).toHaveBeenCalledTimes(6); - expect(execSyncSpy).toHaveBeenCalledWith( - `npm pkg set "exports[.][import]"="./dist/${config.fsNamespace}/${config.fsNamespace}.esm.js"`, - ); - expect(execSyncSpy).toHaveBeenCalledWith( - `npm pkg set "exports[.][require]"="./dist/${config.fsNamespace}/${config.fsNamespace}.cjs.js"`, - ); expect(execSyncSpy).toHaveBeenCalledWith( `npm pkg set "exports[./my-component][import]"="./dist/components/my-component.js"`, ); diff --git a/src/compiler/build/write-export-maps.ts b/src/compiler/build/write-export-maps.ts index 275fd1a646c..d420293ef2b 100644 --- a/src/compiler/build/write-export-maps.ts +++ b/src/compiler/build/write-export-maps.ts @@ -1,8 +1,13 @@ -import { isOutputTargetDistCustomElements, isOutputTargetDistLazyLoader } from '@utils'; +import { + isEligiblePrimaryPackageOutputTarget, + isOutputTargetDistCustomElements, + isOutputTargetDistLazyLoader, +} from '@utils'; import { relative } from '@utils'; import { execSync } from 'child_process'; import * as d from '../../declarations'; +import { PRIMARY_PACKAGE_TARGET_CONFIGS } from '../types/validate-primary-package-output-target'; /** * Create export map entry point definitions for the `package.json` file using the npm CLI. @@ -13,10 +18,36 @@ import * as d from '../../declarations'; * @param buildCtx The build context containing the components to generate export maps for */ export const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx) => { - const namespace = buildCtx.config.fsNamespace; + const eligiblePrimaryTargets = config.outputTargets.filter(isEligiblePrimaryPackageOutputTarget); + if (eligiblePrimaryTargets.length > 0) { + const primaryTarget = + eligiblePrimaryTargets.find((o) => o.isPrimaryPackageOutputTarget) ?? eligiblePrimaryTargets[0]; + const outputTargetConfig = PRIMARY_PACKAGE_TARGET_CONFIGS[primaryTarget.type]; - execSync(`npm pkg set "exports[.][import]"="./dist/${namespace}/${namespace}.esm.js"`); - execSync(`npm pkg set "exports[.][require]"="./dist/${namespace}/${namespace}.cjs.js"`); + if (outputTargetConfig.getModulePath) { + const importPath = outputTargetConfig.getModulePath(config.rootDir, primaryTarget.dir); + + if (importPath) { + execSync(`npm pkg set "exports[.][import]"="${importPath}"`); + } + } + + if (outputTargetConfig.getMainPath) { + const requirePath = outputTargetConfig.getMainPath(config.rootDir, primaryTarget.dir); + + if (requirePath) { + execSync(`npm pkg set "exports[.][require]"="${requirePath}"`); + } + } + + if (outputTargetConfig.getTypesPath) { + const typesPath = outputTargetConfig.getTypesPath(config.rootDir, primaryTarget); + + if (typesPath) { + execSync(`npm pkg set "exports[.][types]"="${typesPath}"`); + } + } + } const distLazyLoader = config.outputTargets.find(isOutputTargetDistLazyLoader); if (distLazyLoader != null) { diff --git a/src/compiler/types/validate-primary-package-output-target.ts b/src/compiler/types/validate-primary-package-output-target.ts index 9fa6b97f22d..778e2a8352c 100644 --- a/src/compiler/types/validate-primary-package-output-target.ts +++ b/src/compiler/types/validate-primary-package-output-target.ts @@ -17,7 +17,7 @@ export type PrimaryPackageOutputTargetRecommendedConfig = { * @param outputTargetDir The output directory for the output target's compiled code. * @returns The recommended path for the `module` property in a project's `package.json` */ - getModulePath?: (rootDir: string, outputTargetDir: string) => string | null; + getModulePath: (rootDir: string, outputTargetDir: string) => string | null; /** * Generates the recommended path for the `types` property based on the output target type, * the project's root directory, and the output target's configuration. @@ -26,7 +26,18 @@ export type PrimaryPackageOutputTargetRecommendedConfig = { * @param outputTargetConfig The output target's config. * @returns The recommended path for the `types` property in a project's `package.json` */ - getTypesPath?: (rootDir: string, outputTargetConfig: any) => string | null; + getTypesPath: (rootDir: string, outputTargetConfig: any) => string | null; + /** + * Generates the recommended path for the `main` property based on the output target type, + * the project's root directory, and the output target's designated output location. + * + * Only used for generate export maps. + * + * @param rootDir The Stencil project's root directory pulled from the validated config. + * @param outputTargetDir The output directory for the output target's compiled code. + * @returns The recommended path for the `main` property in a project's `package.json` + */ + getMainPath: (rootDir: string, outputTargetDir: string) => string | null; }; /** @@ -38,25 +49,32 @@ export const PRIMARY_PACKAGE_TARGET_CONFIGS = { dist: { getModulePath: (rootDir: string, outputTargetDir: string) => normalizePath(relative(rootDir, join(outputTargetDir, 'index.js'))), - getTypesPath: (rootDir: string, outputTargetConfig: d.OutputTargetDist) => + getTypesPath: (rootDir: string, outputTargetConfig: any) => normalizePath(relative(rootDir, join(outputTargetConfig.typesDir!, 'index.d.ts'))), + getMainPath: (rootDir: string, outputTargetDir: string) => + normalizePath(relative(rootDir, join(outputTargetDir, 'index.cjs.js'))), }, 'dist-collection': { getModulePath: (rootDir: string, outputTargetDir: string) => normalizePath(relative(rootDir, join(outputTargetDir, 'index.js'))), + getTypesPath: () => null, + getMainPath: () => null, }, 'dist-custom-elements': { getModulePath: (rootDir: string, outputTargetDir: string) => normalizePath(relative(rootDir, join(outputTargetDir, 'index.js'))), - getTypesPath: (rootDir: string, outputTargetConfig: d.OutputTargetDistCustomElements) => { + getTypesPath: (rootDir: string, outputTargetConfig: any) => { return outputTargetConfig.generateTypeDeclarations ? normalizePath(relative(rootDir, join(outputTargetConfig.dir!, 'index.d.ts'))) : null; }, + getMainPath: () => null, }, 'dist-types': { - getTypesPath: (rootDir: string, outputTargetConfig: d.OutputTargetDistTypes) => + getModulePath: () => null, + getTypesPath: (rootDir: string, outputTargetConfig: any) => normalizePath(relative(rootDir, join(outputTargetConfig.typesDir, 'index.d.ts'))), + getMainPath: () => null, }, } satisfies Record; From bfb89ec1b830f56e4fe5538e68ae18b107cf30c3 Mon Sep 17 00:00:00 2001 From: Tanner Reits Date: Tue, 11 Jun 2024 14:52:42 -0400 Subject: [PATCH 8/8] type comment & SNCs --- src/compiler/build/write-export-maps.ts | 6 +++--- .../types/validate-primary-package-output-target.ts | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/compiler/build/write-export-maps.ts b/src/compiler/build/write-export-maps.ts index d420293ef2b..ab8588a3844 100644 --- a/src/compiler/build/write-export-maps.ts +++ b/src/compiler/build/write-export-maps.ts @@ -25,7 +25,7 @@ export const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx) const outputTargetConfig = PRIMARY_PACKAGE_TARGET_CONFIGS[primaryTarget.type]; if (outputTargetConfig.getModulePath) { - const importPath = outputTargetConfig.getModulePath(config.rootDir, primaryTarget.dir); + const importPath = outputTargetConfig.getModulePath(config.rootDir, primaryTarget.dir!); if (importPath) { execSync(`npm pkg set "exports[.][import]"="${importPath}"`); @@ -33,7 +33,7 @@ export const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx) } if (outputTargetConfig.getMainPath) { - const requirePath = outputTargetConfig.getMainPath(config.rootDir, primaryTarget.dir); + const requirePath = outputTargetConfig.getMainPath(config.rootDir, primaryTarget.dir!); if (requirePath) { execSync(`npm pkg set "exports[.][require]"="${requirePath}"`); @@ -65,7 +65,7 @@ export const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx) const distCustomElements = config.outputTargets.find(isOutputTargetDistCustomElements); if (distCustomElements != null) { // Calculate relative path from project root to custom elements output directory - let outDir = relative(config.rootDir, distCustomElements.dir); + let outDir = relative(config.rootDir, distCustomElements.dir!); if (!outDir.startsWith('.')) { outDir = './' + outDir; } diff --git a/src/compiler/types/validate-primary-package-output-target.ts b/src/compiler/types/validate-primary-package-output-target.ts index 778e2a8352c..df6e0609efc 100644 --- a/src/compiler/types/validate-primary-package-output-target.ts +++ b/src/compiler/types/validate-primary-package-output-target.ts @@ -22,6 +22,9 @@ export type PrimaryPackageOutputTargetRecommendedConfig = { * Generates the recommended path for the `types` property based on the output target type, * the project's root directory, and the output target's configuration. * + * `outputTargetConfig` is typed as `any` because downstream consumers may run into type conflicts + * with the `type` property of all the different "eligible" output targets. + * * @param rootDir The Stencil project's root directory pulled from the validated config. * @param outputTargetConfig The output target's config. * @returns The recommended path for the `types` property in a project's `package.json`