diff --git a/.eslintrc.js b/.eslintrc.js index dfbc3f517..ba87c94fd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,5 +8,7 @@ module.exports = { rules: { // Doesnt work with `package.json` exports 'import/no-unresolved': 'off', + // Very buggy? + 'node/no-unpublished-import': 'off', }, }; diff --git a/package.json b/package.json index 060670ca6..cfa01c883 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "prerelease": "yarn run clean && yarn run setup && yarn run pack && yarn run check", "release": "yarn prerelease && lerna-release", "version": "yarn install && git add yarn.lock", - "pack": "NODE_ENV=production yarn run packemon build-workspace --addEngines --addExports --declaration", + "pack": "yarn run packemon build-workspace --addEngines --addExports --declaration", "packemon": "node ./packages/packemon/cjs/bin.cjs" }, "workspaces": [ diff --git a/packages/babel-plugin-cjs-esm-interop/package.json b/packages/babel-plugin-cjs-esm-interop/package.json index 072f4779f..ee47a6afb 100644 --- a/packages/babel-plugin-cjs-esm-interop/package.json +++ b/packages/babel-plugin-cjs-esm-interop/package.json @@ -19,11 +19,10 @@ "author": "Miles Johnson", "license": "MIT", "main": "./lib/index.js", - "types": "./dts/index.d.ts", + "types": "./lib/index.d.ts", "files": [ - "dts/**/*.d.ts", - "lib/**/*.{js,map}", - "src/**/*.{ts,tsx,json}" + "lib/**/*", + "src/**/*" ], "engines": { "node": ">=14.15.0", @@ -46,13 +45,17 @@ "exports": { "./package.json": "./package.json", "./*": { - "types": "./dts/*.d.ts", - "node": "./lib/*.js", + "node": { + "types": "./lib/*.d.ts", + "default": "./lib/*.js" + }, "default": "./lib/*.js" }, ".": { - "types": "./dts/index.d.ts", - "node": "./lib/index.js", + "node": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + }, "default": "./lib/index.js" } } diff --git a/packages/babel-plugin-conditional-invariant/package.json b/packages/babel-plugin-conditional-invariant/package.json index 538eb783b..a10ddde22 100644 --- a/packages/babel-plugin-conditional-invariant/package.json +++ b/packages/babel-plugin-conditional-invariant/package.json @@ -19,11 +19,10 @@ "author": "Miles Johnson", "license": "MIT", "main": "./lib/index.js", - "types": "./dts/index.d.ts", + "types": "./lib/index.d.ts", "files": [ - "dts/**/*.d.ts", - "lib/**/*.{js,map}", - "src/**/*.{ts,tsx,json}" + "lib/**/*", + "src/**/*" ], "engines": { "node": ">=14.15.0", @@ -43,13 +42,17 @@ "exports": { "./package.json": "./package.json", "./*": { - "types": "./dts/*.d.ts", - "node": "./lib/*.js", + "node": { + "types": "./lib/*.d.ts", + "default": "./lib/*.js" + }, "default": "./lib/*.js" }, ".": { - "types": "./dts/index.d.ts", - "node": "./lib/index.js", + "node": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + }, "default": "./lib/index.js" } } diff --git a/packages/babel-plugin-env-constants/package.json b/packages/babel-plugin-env-constants/package.json index 26ea227ca..81bb8cd66 100644 --- a/packages/babel-plugin-env-constants/package.json +++ b/packages/babel-plugin-env-constants/package.json @@ -19,11 +19,10 @@ "author": "Miles Johnson", "license": "MIT", "main": "./lib/index.js", - "types": "./dts/index.d.ts", + "types": "./lib/index.d.ts", "files": [ - "dts/**/*.d.ts", - "lib/**/*.{js,map}", - "src/**/*.{ts,tsx,json}" + "lib/**/*", + "src/**/*" ], "engines": { "node": ">=14.15.0", @@ -43,13 +42,17 @@ "exports": { "./package.json": "./package.json", "./*": { - "types": "./dts/*.d.ts", - "node": "./lib/*.js", + "node": { + "types": "./lib/*.d.ts", + "default": "./lib/*.js" + }, "default": "./lib/*.js" }, ".": { - "types": "./dts/index.d.ts", - "node": "./lib/index.js", + "node": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + }, "default": "./lib/index.js" } } diff --git a/packages/packemon/package.json b/packages/packemon/package.json index 6b4837163..aeb28872e 100644 --- a/packages/packemon/package.json +++ b/packages/packemon/package.json @@ -17,35 +17,49 @@ "author": "Miles Johnson", "license": "MIT", "main": "./cjs/index.cjs", - "types": "./dts/index.d.ts", + "types": "./cjs/index.d.ts", "bin": "./cjs/bin.cjs", "files": [ - "cjs/**/*.{cjs,mjs,map}", - "dts/**/*.d.ts", - "src/**/*.{ts,tsx,json}", + "cjs/**/*", + "src/**/*", "templates/**/*" ], "exports": { "./package.json": "./package.json", "./babel": { - "types": "./dts/babel.d.ts", "node": { - "import": "./cjs/babel-wrapper.mjs", - "require": "./cjs/babel.cjs" + "import": { + "types": "./cjs/babel.d.ts", + "default": "./cjs/babel-wrapper.mjs" + }, + "require": { + "types": "./cjs/babel.d.ts", + "default": "./cjs/babel.cjs" + } } }, "./bin": { - "types": "./dts/bin.d.ts", "node": { - "import": "./cjs/bin-wrapper.mjs", - "require": "./cjs/bin.cjs" + "import": { + "types": "./cjs/bin.d.ts", + "default": "./cjs/bin-wrapper.mjs" + }, + "require": { + "types": "./cjs/bin.d.ts", + "default": "./cjs/bin.cjs" + } } }, ".": { - "types": "./dts/index.d.ts", "node": { - "import": "./cjs/index-wrapper.mjs", - "require": "./cjs/index.cjs" + "import": { + "types": "./cjs/index.d.ts", + "default": "./cjs/index-wrapper.mjs" + }, + "require": { + "types": "./cjs/index.d.ts", + "default": "./cjs/index.cjs" + } } } }, diff --git a/packages/packemon/src-old/CodeArtifact.ts b/packages/packemon/src-old/CodeArtifact.ts deleted file mode 100644 index 3169847b4..000000000 --- a/packages/packemon/src-old/CodeArtifact.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { rollup, RollupCache } from 'rollup'; -import { toArray, VirtualPath } from '@boost/common'; -import { Artifact } from './Artifact'; -import { removeSourcePath } from './helpers/removeSourcePath'; -import { getRollupConfig } from './rollup/config'; -import { - BuildOptions, - BuildResultFiles, - CodeBuild, - ConfigFile, - Format, - InputMap, - PackageExportPaths, - PackageExports, - Platform, - Support, -} from './types'; - -export class CodeArtifact extends Artifact { - getPackageExports(): PackageExports { - const exportMap: PackageExports = {}; - - if (this.api === 'private' || this.bundle) { - Object.keys(this.inputs).forEach((outputName) => { - this.mapPackageExportsFromBuilds(outputName, exportMap); - }); - } else { - // Use subpath export patterns when not bundling - // https://nodejs.org/api/packages.html#subpath-patterns - this.mapPackageExportsFromBuilds('*', exportMap); - this.mapPackageExportsFromBuilds('index', exportMap); - } - - return exportMap; - } - - // eslint-disable-next-line complexity - protected mapPackageExportsFromBuilds(outputName: string, exportMap: PackageExports) { - const defaultEntry = this.findEntryPoint(['lib'], outputName); - let paths: PackageExportPaths = {}; - - switch (this.platform) { - case 'browser': { - const moduleEntry = this.findEntryPoint(['esm'], outputName); - - paths = { - module: moduleEntry, // Bundlers - import: moduleEntry, - default: this.findEntryPoint(['umd', 'lib'], outputName), // Node.js tooling - }; - break; - } - - case 'node': { - paths = { - import: this.findEntryPoint(['mjs'], outputName), - require: this.findEntryPoint(['cjs'], outputName), - }; - - // Automatically apply the mjs wrapper for cjs - if (!paths.import && outputName !== '*' && paths.require) { - paths.import = (paths.require as string).replace('.cjs', '-wrapper.mjs'); - } - - if (!paths.require && defaultEntry) { - paths.default = defaultEntry; - } - break; - } - - case 'native': - paths.default = defaultEntry; - break; - - default: - break; - } - - // Remove undefined values - for (const key in paths) { - if (paths[key as keyof typeof paths] === undefined) { - delete paths[key as keyof typeof paths]; - } - } - - const pathsCount = Object.keys(paths).length; - const pathsMap = { - [this.platform === 'native' ? 'react-native' : this.platform]: - // eslint-disable-next-line no-nested-ternary - pathsCount === 0 && defaultEntry - ? defaultEntry - : pathsCount === 1 && paths.default - ? paths.default - : paths, - }; - - // Provide fallbacks if condition above is not - if (defaultEntry) { - pathsMap.default = defaultEntry; - } - - // eslint-disable-next-line no-param-reassign - exportMap[outputName === 'index' ? '.' : `./${outputName}`] = pathsMap; - } -} diff --git a/packages/packemon/src-old/Package.ts b/packages/packemon/src-old/Package.ts deleted file mode 100644 index dc24cf0ed..000000000 --- a/packages/packemon/src-old/Package.ts +++ /dev/null @@ -1,192 +0,0 @@ -import glob from 'fast-glob'; -import fs from 'fs-extra'; -import semver from 'semver'; -import { deepMerge, isObject, Memoize, PackageStructure, Path, toArray } from '@boost/common'; -import { optimal } from '@boost/common/optimal'; -import { createDebugger, Debugger } from '@boost/debug'; -import { Artifact } from './Artifact'; -import { CodeArtifact } from './CodeArtifact'; -import { - DEFAULT_FORMATS, - EXCLUDE, - EXTENSIONS, - FORMATS_BROWSER, - FORMATS_NATIVE, - FORMATS_NODE, - NODE_SUPPORTED_VERSIONS, - NPM_SUPPORTED_VERSIONS, - SUPPORT_PRIORITY, -} from './constants'; -import { loadModule } from './helpers/loadModule'; -import { sortExports } from './helpers/sortExports'; -import { Project } from './Project'; -import { packemonBlueprint } from './schemas'; -import { - BuildOptions, - ConfigFile, - FeatureFlags, - InputMap, - PackageConfig, - PackageExportPaths, - PackageExports, - PackemonPackage, - PackemonPackageConfig, - TSConfigStructure, -} from './types'; -import { TypesArtifact } from './TypesArtifact'; - -export class Package { - isComplete(): boolean { - return this.artifacts.every((artifact) => artifact.isComplete()); - } - - isRunning(): boolean { - return this.artifacts.some((artifact) => artifact.isRunning()); - } - - protected addEngines() { - const artifact = (this.artifacts as CodeArtifact[]) - .filter((art) => art instanceof CodeArtifact) - .filter((art) => art.platform === 'node') - .reduce( - (oldest, art) => - !oldest || SUPPORT_PRIORITY[art.support] < SUPPORT_PRIORITY[oldest.support] - ? art - : oldest, - null, - ); - - if (!artifact) { - return; - } - - this.debug('Adding `engines` to `package.json`'); - - const pkg = this.packageJson; - - if (!pkg.engines) { - pkg.engines = {}; - } - - Object.assign(pkg.engines, { - node: `>=${NODE_SUPPORTED_VERSIONS[artifact.support]}`, - npm: toArray(NPM_SUPPORTED_VERSIONS[artifact.support]) - .map((v) => `>=${v}`) - .join(' || '), - }); - } - - protected addEntryPoints() { - this.debug('Adding entry points to `package.json`'); - - let mainEntry: string | undefined; - let moduleEntry: string | undefined; - let browserEntry: string | undefined; - let buildCount = 0; - - // eslint-disable-next-line complexity - this.artifacts.forEach((artifact) => { - // Build files - if (artifact instanceof CodeArtifact) { - const mainEntryName = artifact.inputs.index ? 'index' : Object.keys(artifact.inputs)[0]; - - buildCount += artifact.builds.length; - - // Generate `main`, `module`, and `browser` fields - if (!mainEntry || (artifact.platform === 'node' && mainEntryName === 'index')) { - mainEntry = artifact.findEntryPoint(['lib', 'cjs', 'mjs', 'esm'], mainEntryName); - } - - if (!moduleEntry || (artifact.platform === 'browser' && mainEntryName === 'index')) { - moduleEntry = artifact.findEntryPoint(['esm'], mainEntryName); - } - - // Only include when we share a lib with another platform - if (!browserEntry && artifact.platform === 'browser') { - browserEntry = artifact.findEntryPoint( - artifact.sharedLib ? ['lib', 'umd'] : ['umd'], - mainEntryName, - ); - } - - // Generate `bin` field - if ( - artifact.inputs.bin && - artifact.platform === 'node' && - !isObject(this.packageJson.bin) - ) { - this.packageJson.bin = artifact.findEntryPoint(['lib', 'cjs', 'mjs'], 'bin'); - } - } - - // Type declarations - if (artifact instanceof TypesArtifact) { - const mainEntryName = artifact.builds.some((build) => build.outputName === 'index') - ? 'index' - : artifact.builds[0].outputName; - - this.packageJson.types = artifact.findEntryPoint(mainEntryName); - } - }); - - if (mainEntry) { - this.packageJson.main = mainEntry; - - // Only set when we have 1 build, otherwise its confusing - if (buildCount === 1) { - if (mainEntry.includes('mjs/') || mainEntry.includes('esm/')) { - this.packageJson.type = 'module'; - } else if (mainEntry.includes('cjs/')) { - this.packageJson.type = 'commonjs'; - } - } - } - - if (moduleEntry) { - this.packageJson.module = moduleEntry; - } - - if (browserEntry && !isObject(this.packageJson.browser)) { - this.packageJson.browser = browserEntry; - } - } - - protected addExports() { - this.debug('Adding `exports` to `package.json`'); - - let exportMap: PackageExports = { - './package.json': './package.json', - }; - - this.artifacts.forEach((artifact) => { - Object.entries(artifact.getPackageExports()).forEach(([path, conditions]) => { - if (!conditions) { - return; - } - - if (!exportMap[path]) { - exportMap[path] = conditions; - return; - } - - if (typeof exportMap[path] === 'string') { - exportMap[path] = { default: exportMap[path] }; - } - - exportMap[path] = deepMerge( - exportMap[path] as PackageExportPaths, - typeof conditions === 'string' ? { default: conditions } : conditions, - ); - }); - }); - - exportMap = sortExports(exportMap); - - if (isObject(this.packageJson.exports)) { - Object.assign(this.packageJson.exports, exportMap); - } else { - this.packageJson.exports = exportMap as PackageStructure['exports']; - } - } - -} diff --git a/packages/packemon/src-old/TypesArtifact.ts b/packages/packemon/src-old/TypesArtifact.ts deleted file mode 100644 index b0c0908c8..000000000 --- a/packages/packemon/src-old/TypesArtifact.ts +++ /dev/null @@ -1,66 +0,0 @@ -import path from 'path'; -import execa from 'execa'; -import { VirtualPath } from '@boost/common'; -import { createDebugger, Debugger } from '@boost/debug'; -import { Artifact } from './Artifact'; -import { removeSourcePath } from './helpers/removeSourcePath'; -import { BuildOptions, PackageExports, TSConfigStructure, TypesBuild } from './types'; - -export class TypesArtifact extends Artifact { - findEntryPoint(outputName: string): string | undefined { - const output = this.builds.find((build) => build.outputName === outputName); - - if (!output) { - return undefined; - } - - return `./${new VirtualPath( - 'dts', - removeSourcePath(output.inputFile), - )}.${this.getDeclExtFromInput(output.inputFile)}`; - } - - getDeclExt(): string { - const baseInputExt = path.extname(this.builds[0].inputFile); - const isAllSameExt = this.builds.every((build) => build.inputFile.endsWith(baseInputExt)); - - if (!isAllSameExt) { - throw new Error( - 'All inputs must share the same extension. Cannot determine a TypeScript declaration format.', - ); - } - - return this.getDeclExtFromInput(baseInputExt); - } - - getDeclExtFromInput(inputFile: string): string { - if (inputFile.endsWith('.cts')) { - return 'd.cts'; - } - - if (inputFile.endsWith('.mts')) { - return 'd.mts'; - } - - return 'd.ts'; - } - - getPackageExports(): PackageExports { - const exportMap: PackageExports = {}; - - if (this.api === 'private' || this.bundle) { - this.builds.forEach(({ outputName }) => { - exportMap[outputName === 'index' ? '.' : `./${outputName}`] = { - types: this.findEntryPoint(outputName), - }; - }); - } else { - const ext = this.getDeclExt(); - - exportMap['./*'] = { types: `./dts/*.${ext}` }; - exportMap['.'] = { types: `./dts/index.${ext}` }; - } - - return exportMap; - } -} diff --git a/packages/packemon/src/Artifact.ts b/packages/packemon/src/Artifact.ts index 597e4c918..10acaeb45 100644 --- a/packages/packemon/src/Artifact.ts +++ b/packages/packemon/src/Artifact.ts @@ -18,6 +18,8 @@ import type { FeatureFlags, Format, InputMap, + PackageExportPaths, + PackageExports, Platform, Support, } from './types'; @@ -197,39 +199,56 @@ export class Artifact { ); } - findEntryPoint(formats: Format[], outputName: string): string | undefined { + findEntryPoint(formats: Format[], outputName: string) { for (const format of formats) { - if (this.builds.some((build) => build.format === format)) { - return this.getBuildOutput(format, outputName).path; + const build = this.builds.find((build) => build.format === format); + + if (build) { + return this.getBuildOutput(format, outputName, build.declaration); } } return undefined; } - getBuildOutput(format: Format, outputName: string = '') { + // eslint-disable-next-line complexity + getBuildOutput(format: Format, outputName: string, declaration: boolean = false) { + const inputFile = this.inputs[outputName]; let name = outputName; // When using a public API, we do not create output files based on the input map. // Instead files mirror the source file structure, so we need to take that into account! - if (this.api === 'public' && this.inputs[outputName]) { - name = removeSourcePath(this.inputs[outputName]); + if (this.api === 'public' && inputFile) { + name = removeSourcePath(inputFile); } - const ext = format === 'cjs' || format === 'mjs' ? format : 'js'; - const extGroup = format === 'cjs' ? 'cjs,mjs,map' : `${ext},map`; const folder = format === 'lib' && this.sharedLib ? `lib/${this.platform}` : format; - const file = `${name}.${ext}`; + const entryExt = format === 'cjs' || format === 'mjs' ? format : 'js'; + let declExt: string | undefined; + + if (declaration) { + if (!inputFile || inputFile.endsWith('.ts')) { + declExt = 'd.ts'; + } else if (inputFile.endsWith('.cts')) { + declExt = 'd.cts'; + } else if (inputFile.endsWith('.mts')) { + declExt = 'd.mts'; + } + } return { - ext, - extGroup, - file, + declExt, + declPath: declExt ? `./${new VirtualPath(folder, `${name}.${declExt}`)}` : undefined, + entryExt, + entryPath: `./${new VirtualPath(folder, `${name}.${entryExt}`)}`, folder, - path: `./${new VirtualPath(folder, file)}`, }; } + getIndexInput(): string { + return this.inputs.index ? 'index' : Object.keys(this.inputs)[0]; + } + getInputPaths(): InputMap { return Object.fromEntries( Object.entries(this.inputs).map(([outputName, inputFile]) => [ @@ -244,6 +263,23 @@ export class Artifact { return `${this.platform}:${this.support}:${this.builds.map((build) => build.format).join(',')}`; } + getPackageExports(): PackageExports { + const exportMap: PackageExports = {}; + + if (this.api === 'private' || this.bundle) { + Object.keys(this.inputs).forEach((outputName) => { + this.mapPackageExportsFromBuilds(outputName, exportMap); + }); + } else { + // Use subpath export patterns when not bundling + // https://nodejs.org/api/packages.html#subpath-patterns + this.mapPackageExportsFromBuilds('*', exportMap); + this.mapPackageExportsFromBuilds(this.getIndexInput(), exportMap, true); + } + + return exportMap; + } + isComplete(): boolean { return this.state === 'passed' || this.state === 'failed'; } @@ -256,6 +292,98 @@ export class Artifact { return this.getLabel(); } + // eslint-disable-next-line complexity + protected mapPackageExportsFromBuilds( + outputName: string, + exportMap: PackageExports, + index: boolean = false, + ) { + const defaultEntry = this.findEntryPoint(['lib'], outputName); + let paths: PackageExportPaths = {}; + + switch (this.platform) { + case 'browser': { + const esmEntry = this.findEntryPoint(['esm'], outputName); + + if (esmEntry) { + paths = { + types: esmEntry.declPath, + module: esmEntry.entryPath, // Bundlers + import: esmEntry.entryPath, + }; + } + + // Node.js tooling + const libEntry = this.findEntryPoint(['umd', 'lib'], outputName); + + paths.types ??= libEntry?.declPath; + paths.default = libEntry?.entryPath; + + break; + } + + case 'node': { + const mjsEntry = this.findEntryPoint(['mjs'], outputName); + const cjsEntry = this.findEntryPoint(['cjs'], outputName); + + if (mjsEntry || cjsEntry) { + paths = { + import: mjsEntry + ? { + types: mjsEntry.declPath, + default: mjsEntry.entryPath, + } + : undefined, + require: cjsEntry + ? { + types: cjsEntry.declPath, + default: cjsEntry.entryPath, + } + : undefined, + }; + + // Automatically apply the mjs wrapper for cjs + if (!paths.import && outputName !== '*' && cjsEntry) { + paths.import = { + types: cjsEntry.declPath, + default: cjsEntry.entryPath.replace('.cjs', '-wrapper.mjs'), + }; + } + + if (!paths.require && defaultEntry) { + paths.default = defaultEntry.entryPath; + } + } else { + paths.types = defaultEntry?.declPath; + paths.default = defaultEntry?.entryPath; + } + + break; + } + + case 'native': + paths.default = defaultEntry?.entryPath; + break; + + default: + break; + } + + const pathsMap = { + [this.platform === 'native' ? 'react-native' : this.platform]: paths, + }; + + // Provide fallbacks if condition above is not + if (defaultEntry) { + Object.assign(pathsMap, { + default: defaultEntry.entryPath, + }); + } + + // eslint-disable-next-line no-param-reassign + exportMap[index || outputName === 'index' ? '.' : `./${outputName}`] = pathsMap; + } + protected logWithSource( message: string, level: 'error' | 'info' | 'warn', diff --git a/packages/packemon/src/Package.ts b/packages/packemon/src/Package.ts index f0e6bc701..474ca3447 100644 --- a/packages/packemon/src/Package.ts +++ b/packages/packemon/src/Package.ts @@ -5,7 +5,7 @@ import glob from 'fast-glob'; import fs from 'fs-extra'; import semver from 'semver'; -import { isObject, Memoize, Path, toArray } from '@boost/common'; +import { isObject, Memoize, PackageStructure, Path, toArray } from '@boost/common'; import { optimal } from '@boost/common/optimal'; import { createDebugger, Debugger } from '@boost/debug'; import { Artifact } from './Artifact'; @@ -21,6 +21,8 @@ import { } from './constants'; import { loadTsconfigJson } from './helpers/loadTsconfigJson'; import { matchesPattern } from './helpers/matchesPattern'; +import { mergeExports } from './helpers/mergeExports'; +import { sortExports } from './helpers/sortExports'; import { packemonBlueprint } from './schemas'; import { ApiType, @@ -28,6 +30,7 @@ import { ConfigFile, FeatureFlags, PackageConfig, + PackageExports, PackemonPackage, PackemonPackageConfig, Platform, @@ -89,7 +92,7 @@ export class Package { ); // Add package entry points based on artifacts - // this.addEntryPoints(); + this.addEntryPoints(); // Add package `engines` based on artifacts if (options.addEngines) { @@ -98,7 +101,7 @@ export class Package { // Add package `exports` based on artifacts if (options.addExports) { - // this.addExports(); + this.addExports(); } // Add package `files` whitelist @@ -409,6 +412,88 @@ export class Package { }); } + protected addEntryPoints() { + this.debug('Adding entry points to `package.json`'); + + let mainEntry: string | undefined; + let typesEntry: string | undefined; + let moduleEntry: string | undefined; + let browserEntry: string | undefined; + + // eslint-disable-next-line complexity + this.artifacts.forEach((artifact) => { + const mainEntryName = artifact.getIndexInput(); + + // Generate `main`, `module`, and `browser` fields + if (!mainEntry || (artifact.platform === 'node' && mainEntryName === 'index')) { + const entry = artifact.findEntryPoint(['lib', 'cjs', 'mjs', 'esm'], mainEntryName); + + if (entry) { + mainEntry = entry.entryPath; + typesEntry = entry.declPath; + } + } + + if (!moduleEntry || (artifact.platform === 'browser' && mainEntryName === 'index')) { + moduleEntry = artifact.findEntryPoint(['esm'], mainEntryName)?.entryPath; + } + + // Only include when we share a lib with another platform + if (!browserEntry && artifact.platform === 'browser') { + browserEntry = artifact.findEntryPoint( + artifact.sharedLib ? ['lib', 'umd'] : ['umd'], + mainEntryName, + )?.entryPath; + } + + // Generate `bin` field + if (artifact.inputs.bin && artifact.platform === 'node' && !isObject(this.json.bin)) { + this.json.bin = artifact.findEntryPoint(['lib', 'cjs', 'mjs'], 'bin')?.entryPath; + } + }); + + if (mainEntry) { + this.json.main = mainEntry; + } + + if (typesEntry) { + this.json.types = typesEntry; + } + + if (moduleEntry) { + this.json.module = moduleEntry; + } + + if (browserEntry && !isObject(this.json.browser)) { + this.json.browser = browserEntry; + } + } + + protected addExports() { + this.debug('Adding `exports` to `package.json`'); + + let exportMap: PackageExports = { + './package.json': './package.json', + }; + + this.artifacts.forEach((artifact) => { + Object.entries(artifact.getPackageExports()).forEach(([path, conditions]) => { + if (conditions) { + exportMap[path] = mergeExports(exportMap[path] ?? {}, conditions); + } + }); + }); + + // Sort and flatten exports + exportMap = sortExports(exportMap); + + if (isObject(this.json.exports)) { + Object.assign(this.json.exports, exportMap); + } else { + this.json.exports = exportMap as PackageStructure['exports']; + } + } + protected addFiles() { this.debug('Adding files to `package.json`'); diff --git a/packages/packemon/src/helpers/mergeExports.ts b/packages/packemon/src/helpers/mergeExports.ts new file mode 100644 index 000000000..6af035699 --- /dev/null +++ b/packages/packemon/src/helpers/mergeExports.ts @@ -0,0 +1,27 @@ +/* eslint-disable no-param-reassign */ + +import { PackageExportConditions, PackageExportPaths } from '../types'; + +export function mergeExports( + prev: PackageExportPaths | string, + base: PackageExportPaths | string, +): PackageExportPaths | undefined { + const next = typeof base === 'string' ? { default: base } : base; + + if (typeof prev === 'string') { + return next; + } + + Object.entries(next).forEach(([origKey, nextValue]) => { + const key = origKey as PackageExportConditions; + const prevValue = prev[key]; + + if (!prevValue) { + prev[key] = nextValue; + } else if (nextValue) { + prev[key] = mergeExports(prevValue, nextValue); + } + }); + + return prev; +} diff --git a/packages/packemon/src/helpers/sortExportConditions.ts b/packages/packemon/src/helpers/sortExportConditions.ts index 193cdfd05..2a0eb19e9 100644 --- a/packages/packemon/src/helpers/sortExportConditions.ts +++ b/packages/packemon/src/helpers/sortExportConditions.ts @@ -12,6 +12,40 @@ const WEIGHTS = { default: 100, // Default must be last }; +export function flattenExportConditions(paths: PackageExportPaths): PackageExportPaths | string { + const map: PackageExportPaths = {}; + let count = 0; + + Object.entries(paths).forEach(([path, condition]) => { + // Remove undefined and empty values + if (!condition) { + return; + } + + const key = path as keyof PackageExportPaths; + + if (typeof condition === 'string') { + map[key] = condition; + } else { + map[key] = flattenExportConditions(condition); + } + + count += 1; + }); + + if (count === 1) { + if (map.default) { + return map.default; + } + + if (map.require) { + return map.require; + } + } + + return map; +} + export function sortExportConditions( paths: T, ): T { @@ -22,6 +56,10 @@ export function sortExportConditions { + if (!value) { + return; + } + pathsList.push({ key, value: sortExportConditions(value), @@ -35,5 +73,12 @@ export function sortExportConditions [path.key, path.value])) as T; + const map = Object.fromEntries( + pathsList.map((path) => [ + path.key, + typeof path.value === 'string' ? path.value : flattenExportConditions(path.value), + ]), + ) as PackageExportPaths; + + return map as T; } diff --git a/packages/packemon/src/rollup/config.ts b/packages/packemon/src/rollup/config.ts index c943f81db..47d4f7623 100644 --- a/packages/packemon/src/rollup/config.ts +++ b/packages/packemon/src/rollup/config.ts @@ -88,7 +88,7 @@ export function getRollupOutputConfig( packemonConfig: ConfigFile = {}, ): OutputOptions { const { platform, support } = artifact; - const { ext, folder } = artifact.getBuildOutput(format); + const { entryExt, folder } = artifact.getBuildOutput(format, 'index'); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const isSwc = packemonConfig.swc || !!process.env.PACKEMON_SWC; const isEsm = format === 'esm' || format === 'mjs'; @@ -99,11 +99,11 @@ export function getRollupOutputConfig( originalFormat: format, interop: 'auto', // Map our externals to local paths with trailing extension - paths: getRollupPaths(artifact, ext), + paths: getRollupPaths(artifact, entryExt), // Use our extension for file names assetFileNames: 'assets/[name].[ext]', - chunkFileNames: `${artifact.bundle ? 'bundle' : '[name]'}-[hash].${ext}`, - entryFileNames: `[name].${ext}`, + chunkFileNames: `${artifact.bundle ? 'bundle' : '[name]'}-[hash].${entryExt}`, + entryFileNames: `[name].${entryExt}`, preserveModules: !artifact.bundle, // Use ESM features when not supporting old targets generatedCode: { diff --git a/packages/packemon/tests-old/CodeArtifact.test.tsold b/packages/packemon/tests-old/CodeArtifact.test.tsold deleted file mode 100644 index b973a4477..000000000 --- a/packages/packemon/tests-old/CodeArtifact.test.tsold +++ /dev/null @@ -1,129 +0,0 @@ -import { rollup } from 'rollup'; -import { Path } from '@boost/common'; -import { getFixturePath } from '@boost/test-utils'; -import { CodeArtifact } from '../src/CodeArtifact'; -import { Package } from '../src/Package'; -import { Project } from '../src/Project'; -import { getRollupConfig } from '../src/rollup/config'; -import { mockSpy } from './helpers'; - -jest.mock('../src/rollup/config', () => ({ - getRollupConfig: jest.fn(() => ({ - input: true, - output: [ - { originalFormat: 'lib', a: true }, - { originalFormat: 'esm', b: true }, - { originalFormat: 'mjs', c: true }, - ], - })), -})); - -jest.mock('fs-extra'); -jest.mock('rollup', () => ({ rollup: jest.fn() })); - -describe('CodeArtifact', () => { - const fixturePath = new Path(getFixturePath('project')); - const packageJson = { - name: 'project', - version: '0.0.0', - packemon: {}, - }; - let artifact: CodeArtifact; - - beforeEach(() => { - artifact = new CodeArtifact( - new Package(new Project(fixturePath), fixturePath, { ...packageJson }), - [], - ); - artifact.startup(); - }); - - describe('getPackageExports()', () => { - it('adds exports based on input file and output name', () => { - artifact.builds.push({ format: 'lib' }); - - expect(artifact.getPackageExports()).toEqual({ - '.': { - node: './lib/index.js', - default: './lib/index.js', - }, - }); - }); - - it('adds exports based on input file and output name when shared lib required', () => { - artifact.sharedLib = true; - artifact.builds.push({ format: 'lib' }); - - expect(artifact.getPackageExports()).toEqual({ - '.': { - node: './lib/node/index.js', - default: './lib/node/index.js', - }, - }); - }); - - it('supports subpath file exports when output name is not "index"', () => { - artifact.inputs = { sub: './src/sub.ts' }; - artifact.builds.push({ format: 'lib' }); - - expect(artifact.getPackageExports()).toEqual({ - './sub': { - node: './lib/sub.js', - default: './lib/sub.js', - }, - }); - }); - - it('supports conditional exports when there are multiple builds', () => { - artifact.builds.push({ format: 'lib' }, { format: 'mjs' }, { format: 'cjs' }); - - expect(artifact.getPackageExports()).toEqual({ - '.': { - node: { - import: './mjs/index.mjs', - require: './cjs/index.cjs', - }, - default: './lib/index.js', - }, - }); - }); - - it('skips `default` export when there is no `lib` build', () => { - artifact.inputs = { sub: './src/sub.ts' }; - artifact.builds.push({ format: 'mjs' }, { format: 'cjs' }); - - expect(artifact.getPackageExports()).toEqual({ - './sub': { - node: { - import: './mjs/sub.mjs', - require: './cjs/sub.cjs', - }, - }, - }); - }); - - it('changes export namespace to "browser" when a `browser` platform', () => { - artifact.platform = 'browser'; - artifact.builds.push({ format: 'lib' }); - - expect(artifact.getPackageExports()).toEqual({ - '.': { - browser: './lib/index.js', - default: './lib/index.js', - }, - }); - }); - - it('changes export namespace to "react-native" when a `native` platform', () => { - artifact.platform = 'native'; - artifact.builds.push({ format: 'lib' }); - - expect(artifact.getPackageExports()).toEqual({ - '.': { - 'react-native': './lib/index.js', - default: './lib/index.js', - }, - }); - }); - }); -}); diff --git a/packages/packemon/tests-old/TypesArtifact.test.tsold b/packages/packemon/tests-old/TypesArtifact.test.tsold deleted file mode 100644 index b83c69abb..000000000 --- a/packages/packemon/tests-old/TypesArtifact.test.tsold +++ /dev/null @@ -1,137 +0,0 @@ -import execa from 'execa'; -import { Path } from '@boost/common'; -import { getFixturePath } from '@boost/test-utils'; -import { Package } from '../src/Package'; -import { Project } from '../src/Project'; -import { TypesArtifact } from '../src/TypesArtifact'; - -jest.mock('fs-extra'); -jest.mock('execa'); - -describe('TypesArtifact', () => { - const fixturePath = new Path(getFixturePath('project')); - let artifact: TypesArtifact; - let tsconfigSpy: jest.SpyInstance; - let warnSpy: jest.SpyInstance; - - beforeEach(() => { - artifact = new TypesArtifact( - new Package(new Project(fixturePath), fixturePath, { - name: 'project', - version: '0.0.0', - packemon: {}, - }), - [ - { inputFile: 'src/index.ts', outputName: 'index' }, - { inputFile: 'src/sub/test.ts', outputName: 'test' }, - ], - ); - artifact.startup(); - - tsconfigSpy = jest - .spyOn(artifact, 'loadTsconfigJson') - .mockImplementation(() => ({ options: {} } as any)); - - warnSpy = jest.spyOn(console, 'warn').mockImplementation(); - }); - - afterEach(() => { - tsconfigSpy.mockRestore(); - warnSpy.mockRestore(); - }); - - it('sets correct metadata', () => { - expect(artifact.getLabel()).toBe('dts'); - expect(artifact.getBuildTargets()).toEqual(['dts']); - expect(artifact.toString()).toBe('types (dts)'); - }); - - describe('findEntryPoint()', () => { - it('returns mirrored source path', () => { - expect(artifact.findEntryPoint('index')).toBe('./dts/index.d.ts'); - expect(artifact.findEntryPoint('test')).toBe('./dts/sub/test.d.ts'); - }); - - it('supports .d.cts', () => { - artifact.builds[0].inputFile = 'src/index.cts'; - - expect(artifact.findEntryPoint('index')).toBe('./dts/index.d.cts'); - }); - - it('supports .d.mts', () => { - artifact.builds[0].inputFile = 'src/index.mts'; - - expect(artifact.findEntryPoint('index')).toBe('./dts/index.d.mts'); - }); - }); - - describe('getDeclExt()', () => { - it('defaults to .d.ts', () => { - expect(artifact.getDeclExt()).toBe('d.ts'); - }); - - it('supports .d.cts', () => { - artifact.builds[0].inputFile = 'src/index.cts'; - artifact.builds[1].inputFile = 'src/sub/test.cts'; - - expect(artifact.getDeclExt()).toBe('d.cts'); - }); - - it('supports .d.mts', () => { - artifact.builds[0].inputFile = 'src/index.mts'; - artifact.builds[1].inputFile = 'src/sub/test.mts'; - - expect(artifact.getDeclExt()).toBe('d.mts'); - }); - - it('errors if multiple source formats', () => { - artifact.builds[0].inputFile = 'src/index.cts'; - artifact.builds[1].inputFile = 'src/sub/test.mts'; - - expect(() => artifact.getDeclExt()).toThrow( - 'All inputs must share the same extension. Cannot determine a TypeScript declaration format.', - ); - }); - }); - - describe('getPackageExports()', () => { - it('adds exports based on input file and output name builds', () => { - expect(artifact.getPackageExports()).toEqual({ - '.': { - types: './dts/index.d.ts', - }, - './test': { - types: './dts/sub/test.d.ts', - }, - }); - }); - - it('supports .d.cts', () => { - artifact.builds[0].inputFile = 'src/index.cts'; - artifact.builds[1].inputFile = 'src/sub/test.cts'; - - expect(artifact.getPackageExports()).toEqual({ - '.': { - types: './dts/index.d.cts', - }, - './test': { - types: './dts/sub/test.d.cts', - }, - }); - }); - - it('supports .d.mts', () => { - artifact.builds[0].inputFile = 'src/index.mts'; - artifact.builds[1].inputFile = 'src/sub/test.mts'; - - expect(artifact.getPackageExports()).toEqual({ - '.': { - types: './dts/index.d.mts', - }, - './test': { - types: './dts/sub/test.d.mts', - }, - }); - }); - }); -}); diff --git a/packages/packemon/tests/Artifact.test.ts b/packages/packemon/tests/Artifact.test.ts index 98599d5d0..2deca5212 100644 --- a/packages/packemon/tests/Artifact.test.ts +++ b/packages/packemon/tests/Artifact.test.ts @@ -194,51 +194,51 @@ describe('Artifact', () => { it('returns metadata for `lib` format', () => { expect(artifact.getBuildOutput('lib', 'index')).toEqual({ - ext: 'js', - extGroup: 'js,map', - file: 'index.js', + declExt: undefined, + declPath: undefined, + entryExt: 'js', + entryPath: './lib/index.js', folder: 'lib', - path: './lib/index.js', }); }); it('returns metadata for `esm` format', () => { expect(artifact.getBuildOutput('esm', 'index')).toEqual({ - ext: 'js', - extGroup: 'js,map', - file: 'index.js', + declExt: undefined, + declPath: undefined, + entryExt: 'js', + entryPath: './esm/index.js', folder: 'esm', - path: './esm/index.js', }); }); it('returns metadata for `umd` format', () => { expect(artifact.getBuildOutput('umd', 'index')).toEqual({ - ext: 'js', - extGroup: 'js,map', - file: 'index.js', + declExt: undefined, + declPath: undefined, + entryExt: 'js', + entryPath: './umd/index.js', folder: 'umd', - path: './umd/index.js', }); }); it('returns metadata for `cjs` format', () => { expect(artifact.getBuildOutput('cjs', 'index')).toEqual({ - ext: 'cjs', - extGroup: 'cjs,mjs,map', - file: 'index.cjs', + declExt: undefined, + declPath: undefined, + entryExt: 'cjs', + entryPath: './cjs/index.cjs', folder: 'cjs', - path: './cjs/index.cjs', }); }); it('returns metadata for `mjs` format', () => { expect(artifact.getBuildOutput('mjs', 'index')).toEqual({ - ext: 'mjs', - extGroup: 'mjs,map', - file: 'index.mjs', + declExt: undefined, + declPath: undefined, + entryExt: 'mjs', + entryPath: './mjs/index.mjs', folder: 'mjs', - path: './mjs/index.mjs', }); }); @@ -247,11 +247,11 @@ describe('Artifact', () => { artifact.sharedLib = true; expect(artifact.getBuildOutput('lib', 'index')).toEqual({ - ext: 'js', - extGroup: 'js,map', - file: 'index.js', + declExt: undefined, + declPath: undefined, + entryExt: 'js', + entryPath: './lib/node/index.js', folder: 'lib/node', - path: './lib/node/index.js', }); }); @@ -259,11 +259,11 @@ describe('Artifact', () => { artifact.sharedLib = true; expect(artifact.getBuildOutput('esm', 'index')).toEqual({ - ext: 'js', - extGroup: 'js,map', - file: 'index.js', + declExt: undefined, + declPath: undefined, + entryExt: 'js', + entryPath: './esm/index.js', folder: 'esm', - path: './esm/index.js', }); }); }); @@ -273,11 +273,49 @@ describe('Artifact', () => { artifact.inputs = { index: 'src/some/other/file.ts' }; expect(artifact.getBuildOutput('mjs', 'index')).toEqual({ - ext: 'mjs', - extGroup: 'mjs,map', - file: 'some/other/file.mjs', + declExt: undefined, + declPath: undefined, + entryExt: 'mjs', + entryPath: './mjs/some/other/file.mjs', folder: 'mjs', - path: './mjs/some/other/file.mjs', + }); + }); + + describe('types', () => { + it('returns declaration fields', () => { + expect(artifact.getBuildOutput('lib', 'index', true)).toEqual({ + declExt: 'd.ts', + declPath: './lib/index.d.ts', + entryExt: 'js', + entryPath: './lib/index.js', + folder: 'lib', + }); + }); + + it('supports cts', () => { + artifact.inputs = { index: 'src/index.cts' }; + artifact.builds = [{ format: 'cjs' }]; + + expect(artifact.getBuildOutput('cjs', 'index', true)).toEqual({ + declExt: 'd.cts', + declPath: './cjs/index.d.cts', + entryExt: 'cjs', + entryPath: './cjs/index.cjs', + folder: 'cjs', + }); + }); + + it('supports mts', () => { + artifact.inputs = { index: 'src/index.mts' }; + artifact.builds = [{ format: 'mjs' }]; + + expect(artifact.getBuildOutput('mjs', 'index', true)).toEqual({ + declExt: 'd.mts', + declPath: './mjs/index.d.mts', + entryExt: 'mjs', + entryPath: './mjs/index.mjs', + folder: 'mjs', + }); }); }); }); @@ -290,6 +328,284 @@ describe('Artifact', () => { }); }); + describe('getPackageExports()', () => { + beforeEach(() => { + artifact.builds = []; + }); + + it('adds exports based on input file and output name', () => { + artifact.builds.push({ format: 'lib' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + types: undefined, + default: './lib/index.js', + }, + default: './lib/index.js', + }, + }); + }); + + it('adds exports based on input file and output name when shared lib required', () => { + artifact.sharedLib = true; + artifact.builds.push({ format: 'lib' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + types: undefined, + default: './lib/node/index.js', + }, + default: './lib/node/index.js', + }, + }); + }); + + it('supports subpath file exports when output name is not "index"', () => { + artifact.inputs = { sub: './src/sub.ts' }; + artifact.builds.push({ format: 'lib' }); + + expect(artifact.getPackageExports()).toEqual({ + './sub': { + node: { + types: undefined, + default: './lib/sub.js', + }, + default: './lib/sub.js', + }, + }); + }); + + it('supports conditional exports when there are multiple builds', () => { + artifact.builds.push({ format: 'lib' }, { format: 'mjs' }, { format: 'cjs' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + import: { + types: undefined, + default: './mjs/index.mjs', + }, + require: { + types: undefined, + default: './cjs/index.cjs', + }, + }, + default: './lib/index.js', + }, + }); + }); + + it('supports conditional exports with types when there are multiple builds', () => { + artifact.builds.push( + { declaration: true, format: 'lib' }, + { declaration: true, format: 'mjs' }, + { declaration: true, format: 'cjs' }, + ); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + import: { + types: './mjs/index.d.ts', + default: './mjs/index.mjs', + }, + require: { + types: './cjs/index.d.ts', + default: './cjs/index.cjs', + }, + }, + default: './lib/index.js', + }, + }); + }); + + it('skips `default` export when there is no `lib` build', () => { + artifact.inputs = { sub: './src/sub.ts' }; + artifact.builds.push({ format: 'mjs' }, { format: 'cjs' }); + + expect(artifact.getPackageExports()).toEqual({ + './sub': { + node: { + import: { + types: undefined, + default: './mjs/sub.mjs', + }, + require: { + types: undefined, + default: './cjs/sub.cjs', + }, + }, + }, + }); + }); + + it('changes export namespace to "browser" when a `browser` platform', () => { + artifact.platform = 'browser'; + artifact.builds.push({ format: 'lib' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + browser: { + types: undefined, + default: './lib/index.js', + }, + default: './lib/index.js', + }, + }); + }); + + it('changes export namespace to "react-native" when a `native` platform', () => { + artifact.platform = 'native'; + artifact.builds.push({ format: 'lib' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + 'react-native': { + default: './lib/index.js', + }, + default: './lib/index.js', + }, + }); + }); + + it('supports lib', () => { + artifact.builds.push({ format: 'lib' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + types: undefined, + default: './lib/index.js', + }, + default: './lib/index.js', + }, + }); + }); + + it('supports lib with types', () => { + artifact.builds.push({ declaration: true, format: 'lib' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + types: './lib/index.d.ts', + default: './lib/index.js', + }, + default: './lib/index.js', + }, + }); + }); + + it('supports cjs', () => { + artifact.builds.push({ format: 'cjs' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + import: { + types: undefined, + default: './cjs/index-wrapper.mjs', + }, + require: { + types: undefined, + default: './cjs/index.cjs', + }, + }, + }, + }); + }); + + it('supports cjs with types', () => { + artifact.builds.push({ declaration: true, format: 'cjs' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + import: { + types: './cjs/index.d.ts', + default: './cjs/index-wrapper.mjs', + }, + require: { + types: './cjs/index.d.ts', + default: './cjs/index.cjs', + }, + }, + }, + }); + }); + + it('supports .d.cts', () => { + artifact.builds.push({ declaration: true, format: 'cjs' }); + artifact.inputs = { index: 'src/index.cts' }; + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + import: { + types: './cjs/index.d.cts', + default: './cjs/index-wrapper.mjs', + }, + require: { + types: './cjs/index.d.cts', + default: './cjs/index.cjs', + }, + }, + }, + }); + }); + + it('supports mjs', () => { + artifact.builds.push({ format: 'mjs' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + import: { + types: undefined, + default: './mjs/index.mjs', + }, + require: undefined, + }, + }, + }); + }); + + it('supports mjs with types', () => { + artifact.builds.push({ declaration: true, format: 'mjs' }); + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + import: { + types: './mjs/index.d.ts', + default: './mjs/index.mjs', + }, + require: undefined, + }, + }, + }); + }); + + it('supports .d.mts', () => { + artifact.builds.push({ declaration: true, format: 'mjs' }); + artifact.inputs = { index: 'src/index.mts' }; + + expect(artifact.getPackageExports()).toEqual({ + '.': { + node: { + import: { + types: './mjs/index.d.mts', + default: './mjs/index.mjs', + }, + require: undefined, + }, + }, + }); + }); + }); + describe('logWithSource()', () => { it('logs a message to level', () => { const spy = jest.spyOn(console, 'info').mockImplementation(); diff --git a/packages/packemon/tests/Package.test.ts b/packages/packemon/tests/Package.test.ts index 72d81b600..859293fca 100644 --- a/packages/packemon/tests/Package.test.ts +++ b/packages/packemon/tests/Package.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable jest/no-disabled-tests */ import fsx from 'fs-extra'; import { Path } from '@boost/common'; import { mockNormalizedFilePath } from '@boost/common/test'; @@ -199,7 +198,7 @@ describe('Packemon', () => { }); }); - describe.skip('entries', () => { + describe('entries', () => { describe('main', () => { it('adds "main" for node `lib` format', async () => { pkg.artifacts.push(createCodeArtifact([{ format: 'lib' }])); @@ -221,7 +220,6 @@ describe('Packemon', () => { expect(pkg.json).toEqual( expect.objectContaining({ main: './cjs/index.cjs', - type: 'commonjs', }), ); }); @@ -234,7 +232,6 @@ describe('Packemon', () => { expect(pkg.json).toEqual( expect.objectContaining({ main: './mjs/index.mjs', - type: 'module', }), ); }); @@ -260,7 +257,6 @@ describe('Packemon', () => { expect(pkg.json).toEqual( expect.objectContaining({ main: './esm/index.js', - type: 'module', }), ); }); @@ -335,7 +331,6 @@ describe('Packemon', () => { expect(pkg.json).toEqual( expect.objectContaining({ module: './esm/index.js', - type: 'module', }), ); }); @@ -393,16 +388,13 @@ describe('Packemon', () => { describe('types', () => { it('adds "types" when a types artifact exists', async () => { - // TODO - // pkg.artifacts.push( - // createTypesArtifact([{ outputName: 'index', inputFile: 'src/some/path.ts' }]), - // ); + pkg.artifacts.push(createCodeArtifact([{ declaration: true, format: 'lib' }])); await pkg.build({}, config); expect(pkg.json).toEqual( expect.objectContaining({ - types: './dts/some/path.d.ts', + types: './lib/index.d.ts', }), ); }); @@ -465,7 +457,7 @@ describe('Packemon', () => { }); }); - describe.skip('exports', () => { + describe('exports', () => { it('does nothing if no builds', async () => { pkg.artifacts.push(new Artifact(pkg, [])); @@ -623,7 +615,13 @@ describe('Packemon', () => { await pkg.build({ addExports: true }, config); expect(pkg.json.exports).toEqual({ - '.': { node: './lib/index.js', types: './dts/index.d.ts', default: './lib/index.js' }, + '.': { + node: { + types: './lib/index.d.ts', + default: './lib/index.js', + }, + default: './lib/index.js', + }, './package.json': './package.json', }); }); @@ -730,7 +728,7 @@ describe('Packemon', () => { }); // https://github.com/milesj/packemon/issues/42#issuecomment-808793241 - it.skip('private api: uses inputs as subpath imports', async () => { + it('private api: uses inputs as subpath imports', async () => { const a = createCodeArtifact([{ format: 'cjs' }]); a.api = 'private'; a.inputs = { index: 'src/node.ts' }; @@ -773,7 +771,7 @@ describe('Packemon', () => { ); }); - it.skip('public api + bundle: uses inputs as subpath imports (non-deep imports)', async () => { + it('public api + bundle: uses inputs as subpath imports (non-deep imports)', async () => { const a = createCodeArtifact([{ format: 'cjs' }]); a.api = 'public'; a.bundle = true; @@ -816,7 +814,7 @@ describe('Packemon', () => { ); }); - it.skip('public api + no bundle: uses patterns as subpath imports (deep imports)', async () => { + it('public api + no bundle: uses patterns as subpath imports (deep imports)', async () => { const a = createCodeArtifact([{ format: 'cjs' }]); a.api = 'public'; a.bundle = false; @@ -844,22 +842,23 @@ describe('Packemon', () => { expect(pkg.json).toEqual( expect.objectContaining({ main: './cjs/node.cjs', + module: './esm/web.js', bin: './lib/cli.js', exports: { './package.json': './package.json', './*': { browser: { import: './esm/*.js', module: './esm/*.js', default: './lib/*.js' }, - node: { import: './mjs/*.mjs' }, + node: { import: './mjs/*.mjs', require: './cjs/*.cjs', default: './lib/*.js' }, default: './lib/*.js', }, '.': { browser: { - import: './esm/index.js', - module: './esm/index.js', - default: './lib/index.js', + import: './esm/web.js', + module: './esm/web.js', + default: './lib/web.js', }, - node: { import: './mjs/index.mjs' }, - default: './lib/index.js', + node: { import: './mjs/web.mjs', require: './cjs/node.cjs', default: './lib/cli.js' }, + default: './lib/web.js', }, }, }), diff --git a/packages/packemon/tests/__snapshots__/outputs.test.ts.snap b/packages/packemon/tests/__snapshots__/outputs.test.ts.snap index d9384150d..466044ddb 100644 --- a/packages/packemon/tests/__snapshots__/outputs.test.ts.snap +++ b/packages/packemon/tests/__snapshots__/outputs.test.ts.snap @@ -156,9 +156,12 @@ exports[`Outputs (babel) artifacts builds all the artifacts with rollup 6`] = ` [ "package.json", { + "browser": "./umd/client.js", "dependencies": { "typescript": "*", }, + "main": "./lib/index.js", + "module": "./esm/client.js", "name": "project-rollup", "packemon": { "inputs": { @@ -264,6 +267,7 @@ exports[`Outputs (babel) bundle bundles all files into a single file with rollup [ "package.json", { + "main": "./lib/index.js", "name": "project-bundle", "packemon": { "bundle": true, @@ -388,6 +392,7 @@ exports[`Outputs (babel) bundle with assets uses same assets across multiple for "package.json", { "main": "./lib/index.js", + "module": "./esm/index.js", "name": "project-assets", "packemon": { "bundle": true, @@ -508,6 +513,7 @@ exports[`Outputs (babel) no bundle creates individual files for every source fil [ "package.json", { + "main": "./lib/index.js", "name": "project-bundle", "packemon": { "bundle": true, @@ -739,9 +745,12 @@ exports[`Outputs (swc) artifacts builds all the artifacts with rollup 6`] = ` [ "package.json", { + "browser": "./umd/client.js", "dependencies": { "typescript": "*", }, + "main": "./lib/index.js", + "module": "./esm/client.js", "name": "project-rollup", "packemon": { "inputs": { @@ -847,6 +856,7 @@ exports[`Outputs (swc) bundle bundles all files into a single file with rollup 2 [ "package.json", { + "main": "./lib/index.js", "name": "project-bundle", "packemon": { "bundle": true, @@ -971,6 +981,7 @@ exports[`Outputs (swc) bundle with assets uses same assets across multiple forma "package.json", { "main": "./lib/index.js", + "module": "./esm/index.js", "name": "project-assets", "packemon": { "bundle": true, @@ -1091,6 +1102,7 @@ exports[`Outputs (swc) no bundle creates individual files for every source file [ "package.json", { + "main": "./lib/index.js", "name": "project-bundle", "packemon": { "bundle": true, @@ -1195,7 +1207,7 @@ exports[`Special formats cts supports .cts -> .cjs / .d.cts 2`] = ` }, }, "type": "commonjs", - "types": "./dts/index.d.cts", + "types": "./cjs/index.d.cts", }, ] `; @@ -1232,7 +1244,7 @@ exports[`Special formats mts supports .mts -> .mjs / .d.mts 2`] = ` }, }, "type": "module", - "types": "./dts/index.d.mts", + "types": "./mjs/index.d.mts", }, ] `; diff --git a/packages/packemon/tests/helpers/sortExportConditions.test.ts b/packages/packemon/tests/helpers/sortExportConditions.test.ts index 8894bd43d..0d39746e8 100644 --- a/packages/packemon/tests/helpers/sortExportConditions.test.ts +++ b/packages/packemon/tests/helpers/sortExportConditions.test.ts @@ -3,13 +3,13 @@ import { sortExportConditions } from '../../src/helpers/sortExportConditions'; describe('sortExportConditions()', () => { it('sorts in the correct order', () => { const map = sortExportConditions({ - default: '', - script: '', - import: '', - node: '', - require: '', - types: '', - browser: '', + default: 'index.js', + script: 'index.js', + import: 'index.js', + node: 'index.js', + require: 'index.js', + types: 'index.js', + browser: 'index.js', }); expect(Object.keys(map)).toStrictEqual([ diff --git a/tests/__fixtures__/project-cts/package.json b/tests/__fixtures__/project-cts/package.json index 3be9a03d2..50eb18d49 100644 --- a/tests/__fixtures__/project-cts/package.json +++ b/tests/__fixtures__/project-cts/package.json @@ -6,7 +6,7 @@ "index": "src/index.cts" } }, - "types": "./dts/index.d.cts", + "types": "./cjs/index.d.cts", "main": "./cjs/index.cjs", "type": "commonjs" } diff --git a/tests/__fixtures__/project-mts/package.json b/tests/__fixtures__/project-mts/package.json index 968585b60..402f37ea4 100644 --- a/tests/__fixtures__/project-mts/package.json +++ b/tests/__fixtures__/project-mts/package.json @@ -6,7 +6,7 @@ "index": "src/index.mts" } }, - "types": "./dts/index.d.mts", + "types": "./mjs/index.d.mts", "main": "./mjs/index.mjs", "type": "module" } diff --git a/website/docs/build.md b/website/docs/build.md index 856863ed1..d85f3e2c3 100644 --- a/website/docs/build.md +++ b/website/docs/build.md @@ -23,8 +23,8 @@ to their configured build targets (platform, formats, etc). Build supports the following command line options. -- `--addEngines` - Add Node.js `engine` versions to `package.json` when `platform` is - `node`. Uses the `support` setting to determine the version range. +- `--addEngines` - Add Node.js `engine` versions to `package.json` when `platform` is `node`. Uses + the `support` setting to determine the version range. - `--addExports` - Add `exports` fields to `package.json`, based on `api`, `bundle`, and `inputs`. This is an experimental Node.js feature and may not work correctly ([more information](https://nodejs.org/api/packages.html#packages_package_entry_points)). @@ -80,11 +80,11 @@ like the following (when also using `--declaration`). ``` / -├── dts/ -| └── index.d.ts ├── esm/ +| ├── index.d.ts | └── index.js ├── lib/ +| ├── index.d.ts | └── browser/index.js | └── node/index.js ├── src/ @@ -105,8 +105,8 @@ and files list, as demonstrated below. This can further be expanded upon using t "name": "package", "main": "./lib/index.js", "module": "./esm/index.js", - "types": "./dts/index.d.ts", - "files": ["dts/", "esm/", "lib/", "src/"], + "types": "./lib/index.d.ts", + "files": ["esm/**/*", "lib/**/*", "src/**/*"], "packemon": { "inputs": { "index": "src/index.ts" }, "platform": ["node", "browser"], @@ -123,7 +123,6 @@ much everything except tests). ``` / -├── dts/ ├── esm/ ├── lib/ ├── src/ diff --git a/website/docs/files.md b/website/docs/files.md index c6c2fd51a..afc354be8 100644 --- a/website/docs/files.md +++ b/website/docs/files.md @@ -24,9 +24,8 @@ Example output using our own package. ### Tree format ``` -┌─ dts -│ └─ index.d.ts -├─ lib +┌─ lib +│ ├─ index.d.ts │ ├─ index.js │ └─ index.js.map ├─ src @@ -39,7 +38,7 @@ Example output using our own package. ### List format ``` -┌─ dts/index.d.ts +┌─ lib/index.d.ts ├─ lib/index.js ├─ lib/index.js.map ├─ src/index.ts diff --git a/website/docs/migrate/3.0.md b/website/docs/migrate/3.0.md index 838dcc07f..098acba5f 100644 --- a/website/docs/migrate/3.0.md +++ b/website/docs/migrate/3.0.md @@ -63,6 +63,31 @@ If using project references, you'll need to manually update `outDir` as we canno } ``` +## Package `exports` have changed + +To support the TypeScript differences mentioned above, the `package.json` `exports` had to also +change. Instead of having a `types` export at the top-level, it will now be nested within `import` +or `require`, depending on the format. This also means that `import` and `require` may have +different type declarations if need be. + +```json +// Before +".": { + "types": "./dts/index.d.ts", + "node": "./lib/index.js", + "default": "./lib/index.js" +} + +// After +".": { + "node": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + }, + "default": "./lib/index.js" +} +``` + ## Publishable files are now lenient When the `--addFiles` option is passed, we update the `files` field in the package's `package.json` @@ -79,6 +104,7 @@ requirement. For example, these are simply now `src/**/*` and `cjs/**/*`. ## Other minor changes - The `pack` and `pack-workspace` commands will automatically set `NODE_ENV=production` now. +- The `type` field in `package.json` will no longer be set. - Removed most of the React/Ink integration for rendering to the terminal, as it was major bottleneck for performance. - Removed `babelrcRoots` support from our internal Babel configuration. diff --git a/website/docs/setup.mdx b/website/docs/setup.mdx index 39d9557c2..2851e7f51 100644 --- a/website/docs/setup.mdx +++ b/website/docs/setup.mdx @@ -84,21 +84,6 @@ Integrating with TypeScript can sometimes be tricky, and with Packemon, that is Since Packemon now handles the build process, TypeScript should be configured for type checking and declaration generation only. -### Update output directories - -Both the `outDir` and `declarationDir` settings should be updated to `dts`, and should _not_ be set -to `lib`, `build`, or some other variant. This is especially true if using project references. - -```json title="tsconfig.json" -{ - "compilerOptions": { - "declaration": true, - "declarationDir": "dts", - "outDir": "dts" - } -} -``` - ### Enable emitting The `noEmit` setting should _only_ be used on the command line (via an npm script) and should not be @@ -131,14 +116,14 @@ guarantees that Packemon needs for handling declarations. ### Supporting project references Alongside the requirements listed above, the `tsconfig.json` within each project reference package -should be updated to only emit declarations to `dts`, like so. +should be updated to only emit declarations to each format, like so. This may require multiple +configs. ```json title="tsconfig.json" { "compilerOptions": { "declaration": true, - "declarationDir": "dts", - "outDir": "dts", + "outDir": "lib", "rootDir": "src", "emitDeclarationOnly": true }