From 694b8a82d303b887394419f0cfa5737be3e67366 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Thu, 17 Aug 2023 23:35:56 +0900 Subject: [PATCH 01/20] refac: remove cwd from getExportPaths Co-authored-by: Jiachi Liu --- src/exports.ts | 116 +----------------------- test/lib-unit/exports.test.ts | 163 ++++++++++------------------------ 2 files changed, 47 insertions(+), 232 deletions(-) diff --git a/src/exports.ts b/src/exports.ts index 7366e6aa..1293e0e2 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -1,4 +1,3 @@ -import fs from 'fs' import { join, resolve, dirname, extname } from 'path' import type { PackageMetadata, @@ -8,7 +7,6 @@ import type { ParsedExportCondition, } from './types' import { filenameWithoutExtension } from './utils' -import { availableExtensions, availableExportConventions } from './constants' export function getTypings(pkg: PackageMetadata) { return pkg.types || pkg.typings @@ -82,107 +80,6 @@ function findExport( }) } -// Should exclude all outDirs since they are readable on `fs.readdirSync` -// Example: -// { 'import': './someOutDir/index.js' } => ['someOutDir'] -// { 'import': './importDir/index.js', 'require': './requireDir/index.js' } => ['importDir', 'requireDir'] -function getOutDirs(exportsConditions: ExportCondition) { - return [ - ...new Set( - Object.values(exportsConditions) - .flatMap((value) => Object.values(value)) - .flatMap((innerValue) => - typeof innerValue === 'string' ? [innerValue] : [], - ), - ), - ] - .map((value) => value.split('/')[1]) - .filter(Boolean) -} - -function resolveWildcardEntry( - wildcardEntry: { - [key: string]: ExportCondition - }, - cwd: string, - excludes: string[], -): { - [key: string]: ExportCondition -}[] { - const dirents = fs.readdirSync(cwd, { withFileTypes: true }) - - const allowedExtensions = [ - ...availableExtensions, - ...availableExportConventions, - ].map((ext) => `.${ext}`) - - const resolvedExports = dirents.flatMap((dirent) => { - // Skip outDirs and existing ExportConditions keys - if (excludes.includes(dirent.name)) return - - if (dirent.isDirectory()) { - // Read inside src directory - if (dirent.name === 'src') { - return resolveWildcardEntry(wildcardEntry, `${cwd}/src`, excludes) - } - - const dirName = dirent.name - const hasIndexFile = fs - .readdirSync(`${cwd}/${dirName}`) - .some((file) => file.startsWith('index')) - - if (hasIndexFile) { - return { - [`./${dirName}`]: JSON.parse( - JSON.stringify(wildcardEntry).replace(/\*/g, `${dirName}/index`), - ), - } - } - } - - if (dirent.isFile()) { - const fileName = filenameWithoutExtension(dirent.name)! - // ['.'] is for index file, so skip index - if (fileName === 'index') return - if (allowedExtensions.includes(extname(dirent.name))) { - return { - [`./${fileName}`]: JSON.parse( - JSON.stringify(wildcardEntry).replace(/\*/g, fileName), - ), - } - } - } - - return - }) - - return resolvedExports.filter(Boolean) -} - -function resolveWildcardExports( - exportsConditions: { - [key: string]: ExportCondition - }, - cwd: string, -) { - const outDirs = getOutDirs(exportsConditions) - const existingKeys = [ - ...new Set(Object.keys(exportsConditions).flatMap((key) => key.split('/'))), - ] - const excludes = [...outDirs, ...existingKeys] - - const wildcardEntry = exportsConditions['./*'] as { - [key: string]: ExportCondition - } - - const resolvedEntry = resolveWildcardEntry(wildcardEntry, cwd, excludes) - - const resolvedExports = Object.assign({}, exportsConditions, ...resolvedEntry) - delete resolvedExports['./*'] - - return resolvedExports -} - /** * * Convert package.exports field to paths mapping @@ -271,7 +168,7 @@ function parseExport( * pkg.main and pkg.module will be added to ['.'] if exists */ -export function getExportPaths(pkg: PackageMetadata, cwd: string) { +export function getExportPaths(pkg: PackageMetadata) { const pathsMap: Record = {} const packageType = getPackageType(pkg) const isCjsPackage = packageType === 'commonjs' @@ -279,16 +176,7 @@ export function getExportPaths(pkg: PackageMetadata, cwd: string) { const { exports: exportsConditions } = pkg if (exportsConditions) { - let resolvedExportsConditions = exportsConditions - - if ( - Object.keys(exportsConditions).some((key) => key === './*') && - typeof exportsConditions !== 'string' - ) { - resolvedExportsConditions = resolveWildcardExports(exportsConditions, cwd) - } - - const paths = parseExport(resolvedExportsConditions, packageType) + const paths = parseExport(exportsConditions, packageType) Object.assign(pathsMap, paths) } diff --git a/test/lib-unit/exports.test.ts b/test/lib-unit/exports.test.ts index 0e08b1a6..b8eebe75 100644 --- a/test/lib-unit/exports.test.ts +++ b/test/lib-unit/exports.test.ts @@ -6,8 +6,6 @@ import { getExportTypeDist, } from '../../src/exports' -const cwd = path.resolve(__dirname) - describe('lib exports', () => { describe('getExportPaths', () => { it('should handle the basic main fields paths (cjs)', () => { @@ -15,7 +13,7 @@ describe('lib exports', () => { main: './dist/index.cjs', module: './dist/index.esm.js', } - const result = getExportPaths(pkg, cwd) + const result = getExportPaths(pkg) expect(result).toEqual({ '.': { require: './dist/index.cjs', @@ -26,17 +24,14 @@ describe('lib exports', () => { it('should handle types field', () => { expect( - getExportPaths( - { - exports: { - '.': { - import: './dist/index.mjs', - types: './dist/index.d.ts', - }, + getExportPaths({ + exports: { + '.': { + import: './dist/index.mjs', + types: './dist/index.d.ts', }, }, - cwd, - ), + }), ).toEqual({ '.': { import: './dist/index.mjs', @@ -45,59 +40,6 @@ describe('lib exports', () => { }) }) - it('should handle wildcard exports', () => { - expect( - getExportPaths( - { - exports: { - '.': { - types: './dist/index.d.ts', - import: './dist/index.js', - }, - './server': { - types: './dist/server/index.d.ts', - 'react-server': './dist/server/react-server.mjs', - 'edge-light': './dist/server/edge.mjs', - import: './dist/server/index.mjs', - }, - './*': { - types: './dist/*.d.ts', - import: './dist/*.js', - }, - }, - }, - path.join(__dirname, '../integration/wildcard-exports'), - ), - ).toEqual({ - '.': { - types: './dist/index.d.ts', - import: './dist/index.js', - }, - './server': { - types: './dist/server/index.d.ts', - 'react-server': './dist/server/react-server.mjs', - 'edge-light': './dist/server/edge.mjs', - import: './dist/server/index.mjs', - }, - './button': { - types: './dist/button.d.ts', - import: './dist/button.js', - }, - './input': { - types: './dist/input.d.ts', - import: './dist/input.js', - }, - './layout': { - types: './dist/layout/index.d.ts', - import: './dist/layout/index.js', - }, - './lite': { - types: './dist/lite.d.ts', - import: './dist/lite.js', - }, - }) - }) - describe('type:module', () => { it('should handle the basic main fields paths (esm)', () => { const pkg: PackageMetadata = { @@ -105,7 +47,7 @@ describe('lib exports', () => { main: './dist/index.mjs', module: './dist/index.esm.js', } - const result = getExportPaths(pkg, cwd) + const result = getExportPaths(pkg) expect(result).toEqual({ '.': { import: './dist/index.mjs', @@ -117,18 +59,15 @@ describe('lib exports', () => { it('should handle the exports conditions', () => { expect( - getExportPaths( - { - exports: { - '.': { - require: './dist/index.cjs', - module: './dist/index.esm.js', - default: './dist/index.esm.js', - }, + getExportPaths({ + exports: { + '.': { + require: './dist/index.cjs', + module: './dist/index.esm.js', + default: './dist/index.esm.js', }, }, - cwd, - ), + }), ).toEqual({ '.': { require: './dist/index.cjs', @@ -138,17 +77,14 @@ describe('lib exports', () => { }) expect( - getExportPaths( - { - exports: { - '.': { - import: './dist/index.mjs', - require: './dist/index.cjs', - }, + getExportPaths({ + exports: { + '.': { + import: './dist/index.mjs', + require: './dist/index.cjs', }, }, - cwd, - ), + }), ).toEqual({ '.': { import: './dist/index.mjs', @@ -159,19 +95,16 @@ describe('lib exports', () => { it('should handle the mixed exports conditions', () => { expect( - getExportPaths( - { - main: './dist/index.cjs', - exports: { - '.': { - sub: { - require: './dist/index.cjs', - }, + getExportPaths({ + main: './dist/index.cjs', + exports: { + '.': { + sub: { + require: './dist/index.cjs', }, }, }, - cwd, - ), + }), ).toEqual({ '.': { require: './dist/index.cjs', @@ -182,20 +115,17 @@ describe('lib exports', () => { }) expect( - getExportPaths( - { - main: './dist/index.js', - module: './dist/index.esm.js', + getExportPaths({ + main: './dist/index.js', + module: './dist/index.esm.js', + types: './dist/index.d.ts', + exports: { types: './dist/index.d.ts', - exports: { - types: './dist/index.d.ts', - import: './dist/index.mjs', - module: './dist/index.esm.js', - require: './dist/index.js', - }, + import: './dist/index.mjs', + module: './dist/index.esm.js', + require: './dist/index.js', }, - cwd, - ), + }), ).toEqual({ '.': { types: './dist/index.d.ts', @@ -208,16 +138,13 @@ describe('lib exports', () => { it('should warn the duplicated export conditions', () => { expect( - getExportPaths( - { - main: './dist/index.js', - exports: { - import: './dist/index.mjs', - require: './dist/index.cjs', - }, + getExportPaths({ + main: './dist/index.js', + exports: { + import: './dist/index.mjs', + require: './dist/index.cjs', }, - cwd, - ), + }), ).toEqual({ '.': { import: './dist/index.mjs', @@ -232,7 +159,7 @@ describe('lib exports', () => { pkg: PackageMetadata, exportName: string = '.', ) { - const parsedExportCondition = getExportPaths(pkg, cwd) + const parsedExportCondition = getExportPaths(pkg) const parsedExport = { source: `./src/${exportName === '.' ? 'index' : exportName}.ts`, name: exportName, @@ -292,7 +219,7 @@ describe('lib exports', () => { pkg: PackageMetadata, exportName: string = '.', ) { - const parsedExportCondition = getExportPaths(pkg, cwd) + const parsedExportCondition = getExportPaths(pkg) const parsedExport = { source: `./src/${exportName === '.' ? 'index' : exportName}.ts`, name: exportName, From 5aa9786a06ac2e9e6fbc8c6514afb9fd7e867b8f Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Fri, 18 Aug 2023 00:01:38 +0900 Subject: [PATCH 02/20] chore: PackageMetadata.exports type ExportCondition --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 0da1450f..effe55ec 100644 --- a/src/types.ts +++ b/src/types.ts @@ -49,7 +49,7 @@ type PackageMetadata = { dependencies?: Record peerDependencies?: Record peerDependenciesMeta?: Record> - exports?: string | Record + exports?: ExportCondition types?: string typings?: string } From 4148d6883445731ded4f022b1a7d23d422bc9fe8 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Fri, 18 Aug 2023 00:41:52 +0900 Subject: [PATCH 03/20] refac: move wildcard exports handlers to utils - temporary? --- src/bundle.ts | 4 ++- src/utils.ts | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/src/bundle.ts b/src/bundle.ts index 67e1b98c..f2935d03 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -17,6 +17,7 @@ import { fileExists, getSourcePathFromExportPath, getExportPath, + validateExports, } from './utils' import { constructDefaultExportCondition, @@ -58,9 +59,10 @@ async function bundle( assignDefault(options, 'target', 'es2015') const pkg = await getPackageMeta(cwd) + pkg.exports &&= await validateExports(pkg.exports, cwd) const packageType = getPackageType(pkg) - const exportPaths = getExportPaths(pkg, cwd) + const exportPaths = getExportPaths(pkg) const exportKeys = Object.keys(exportPaths).filter( (key) => key !== './package.json', ) diff --git a/src/utils.ts b/src/utils.ts index 2a152e8b..8738d9f7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import fs from 'fs/promises' +import type { Dirent } from 'fs' import path from 'path' -import type { PackageMetadata } from './types' +import type { ExportCondition, PackageMetadata } from './types' import { availableExportConventions, availableExtensions } from './constants' export function exit(err: string | Error) { @@ -143,3 +144,99 @@ export function filenameWithoutExtension(file: string | undefined) { } export const nonNullable = (n?: T): n is T => Boolean(n) + +const isExportableExtension = (filename: string): boolean => { + const ext = path.extname(filename).slice(1) + return [...availableExtensions, ...availableExportConventions].includes(ext) +} + +const hasSrc = (dirents: Dirent[]) => { + return dirents.some((dirent) => dirent.name === SRC && dirent.isDirectory()) +} + +export async function getExportables(cwd: string): Promise { + let currentDirPath = cwd + let dirents = await fs.readdir(cwd, { withFileTypes: true }) + if (hasSrc(dirents)) { + currentDirPath = path.join(cwd, SRC) + dirents = await fs.readdir(path.join(cwd, SRC), { withFileTypes: true }) + } + + const exportables: (string | undefined)[] = await Promise.all( + dirents.map(async (dirent) => { + if (dirent.isDirectory()) { + let innerDirents = await fs.readdir( + path.join(currentDirPath, dirent.name), + { + withFileTypes: true, + }, + ) + if (hasSrc(innerDirents)) { + currentDirPath = path.join(currentDirPath, dirent.name, SRC) + innerDirents = await fs.readdir(currentDirPath, { + withFileTypes: true, + }) + } + const hasExportableFile = innerDirents.some( + ({ name }) => name.startsWith('index') && isExportableExtension(name), + ) + return hasExportableFile ? dirent.name : undefined + } + + if ( + dirent.isFile() && + !dirent.name.startsWith('index') && + isExportableExtension(dirent.name) + ) { + return dirent.name + } + return undefined + }), + ) + return exportables.filter(nonNullable) +} + +export function getExportConditionByKey( + key: string, + exports: any, +): { [key: string]: ExportCondition } | undefined { + if (!key.includes('./*') || !exports[key]) return undefined + return { [key]: exports[key] } +} + +export async function validateExports( + exports: ExportCondition, + cwd: string, +): Promise { + const wildcardEntry = getExportConditionByKey('./*', exports) + console.log(!!wildcardEntry, wildcardEntry, exports, 'eejjeejj2') + if (!wildcardEntry) return exports + + const exportables = await getExportables(cwd) + const wildcardExports = exportables.map((exportable) => { + const filename = exportable.includes('.') + ? filenameWithoutExtension(exportable) + : undefined + + if (!filename) { + return { + [`./${exportable}`]: JSON.parse( + JSON.stringify(wildcardEntry['./*']).replace( + /\*/g, + `${exportable}/index`, + ), + ), + } + } + return JSON.parse(JSON.stringify(wildcardEntry).replace(/\*/g, filename)) + }) + + const resolvedExports = Object.assign( + {}, + exports, + ...wildcardExports, + exports, + ) + delete resolvedExports['./*'] + return resolvedExports +} From 72efa8933871e1eeac642810800f96ffb7e015f5 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Fri, 18 Aug 2023 04:08:20 +0900 Subject: [PATCH 04/20] chore: wildcard to experimental --- src/bundle.ts | 2 +- src/constants.ts | 2 + src/experimental/wildcard.ts | 90 +++++++++++++++++++++++++++++ src/types.ts | 2 +- src/utils.ts | 108 ++++------------------------------- 5 files changed, 104 insertions(+), 100 deletions(-) create mode 100644 src/experimental/wildcard.ts diff --git a/src/bundle.ts b/src/bundle.ts index f2935d03..4f452fbf 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -17,7 +17,6 @@ import { fileExists, getSourcePathFromExportPath, getExportPath, - validateExports, } from './utils' import { constructDefaultExportCondition, @@ -28,6 +27,7 @@ import { import type { BuildMetadata } from './types' import { TypescriptOptions, resolveTsConfig } from './typescript' import { logSizeStats } from './logging' +import { validateExports } from './experimental/wildcard' function assignDefault( options: BundleConfig, diff --git a/src/constants.ts b/src/constants.ts index 22a7f02d..9f2c2495 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -15,3 +15,5 @@ export const availableExportConventions = [ ] export const availableESExtensionsRegex = /\.(m|c)?[jt]sx?$/ export const dtsExtensionRegex = /\.d\.(m|c)?ts$/ + +export const SRC = 'src' \ No newline at end of file diff --git a/src/experimental/wildcard.ts b/src/experimental/wildcard.ts new file mode 100644 index 00000000..7f4d8048 --- /dev/null +++ b/src/experimental/wildcard.ts @@ -0,0 +1,90 @@ +import fs from 'fs/promises' +import path from 'path' +import { SRC } from '../constants' +import { ExportCondition } from '../types' +import { + filenameWithoutExtension, + hasAvailableExtension, + nonNullable, +} from '../utils' + +const getWildcardEntry = ( + key: string, + exports: string | Record, +): string | Record | undefined => { + if (typeof exports === 'string') return exports + if (!key.includes('./*') || !exports[key]) return undefined + return { [key]: exports[key] } +} + +export async function getExportables(cwd: string): Promise { + const dirents = await fs.readdir(path.resolve(cwd, SRC), { + withFileTypes: true, + }) + + const exportables: (string | undefined)[] = await Promise.all( + dirents.map(async (dirent) => { + if (dirent.isDirectory()) { + // Read inside directory and check if it has an index file + const innerDirents = await fs.readdir( + path.join(cwd, SRC, dirent.name), + { + withFileTypes: true, + }, + ) + const hasExportableFile = innerDirents.some( + ({ name }) => name.startsWith('index') && hasAvailableExtension(name), + ) + return hasExportableFile ? dirent.name : undefined + } + + if ( + dirent.isFile() && + !dirent.name.startsWith('index') && + hasAvailableExtension(dirent.name) + ) { + return dirent.name + } + return undefined + }), + ) + return exportables.filter(nonNullable) +} + +export async function validateExports( + exports: ExportCondition, + cwd: string, +): Promise { + const wildcardEntry = getWildcardEntry('./*', exports) + + if (!wildcardEntry) return exports + if (typeof wildcardEntry === 'string') return exports + + const exportables = await getExportables(cwd) + const wildcardExports = exportables.map((exportable) => { + const filename = exportable.includes('.') + ? filenameWithoutExtension(exportable) + : undefined + + if (!filename) { + return { + [`./${exportable}`]: JSON.parse( + JSON.stringify(wildcardEntry['./*']).replace( + /\*/g, + `${exportable}/index`, + ), + ), + } + } + return JSON.parse(JSON.stringify(wildcardEntry).replace(/\*/g, filename)) + }) + + const resolvedExports = Object.assign( + {}, + exports, + ...wildcardExports, + exports, + ) + delete resolvedExports['./*'] + return resolvedExports +} diff --git a/src/types.ts b/src/types.ts index effe55ec..0da1450f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -49,7 +49,7 @@ type PackageMetadata = { dependencies?: Record peerDependencies?: Record peerDependenciesMeta?: Record> - exports?: ExportCondition + exports?: string | Record types?: string typings?: string } diff --git a/src/utils.ts b/src/utils.ts index 8738d9f7..c8770435 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,11 @@ import fs from 'fs/promises' -import type { Dirent } from 'fs' import path from 'path' -import type { ExportCondition, PackageMetadata } from './types' -import { availableExportConventions, availableExtensions } from './constants' +import { PackageMetadata } from './types' +import { + availableExportConventions, + availableExtensions, + SRC, +} from './constants' export function exit(err: string | Error) { logger.error(err) @@ -73,7 +76,6 @@ export function getExportPath( export const isNotNull = (n: T | false): n is T => Boolean(n) -const SRC = 'src' // resolve from src/ directory export function resolveSourceFile(cwd: string, filename: string) { return path.resolve(cwd, SRC, filename) } @@ -145,98 +147,8 @@ export function filenameWithoutExtension(file: string | undefined) { export const nonNullable = (n?: T): n is T => Boolean(n) -const isExportableExtension = (filename: string): boolean => { - const ext = path.extname(filename).slice(1) - return [...availableExtensions, ...availableExportConventions].includes(ext) -} - -const hasSrc = (dirents: Dirent[]) => { - return dirents.some((dirent) => dirent.name === SRC && dirent.isDirectory()) -} - -export async function getExportables(cwd: string): Promise { - let currentDirPath = cwd - let dirents = await fs.readdir(cwd, { withFileTypes: true }) - if (hasSrc(dirents)) { - currentDirPath = path.join(cwd, SRC) - dirents = await fs.readdir(path.join(cwd, SRC), { withFileTypes: true }) - } - - const exportables: (string | undefined)[] = await Promise.all( - dirents.map(async (dirent) => { - if (dirent.isDirectory()) { - let innerDirents = await fs.readdir( - path.join(currentDirPath, dirent.name), - { - withFileTypes: true, - }, - ) - if (hasSrc(innerDirents)) { - currentDirPath = path.join(currentDirPath, dirent.name, SRC) - innerDirents = await fs.readdir(currentDirPath, { - withFileTypes: true, - }) - } - const hasExportableFile = innerDirents.some( - ({ name }) => name.startsWith('index') && isExportableExtension(name), - ) - return hasExportableFile ? dirent.name : undefined - } - - if ( - dirent.isFile() && - !dirent.name.startsWith('index') && - isExportableExtension(dirent.name) - ) { - return dirent.name - } - return undefined - }), - ) - return exportables.filter(nonNullable) -} - -export function getExportConditionByKey( - key: string, - exports: any, -): { [key: string]: ExportCondition } | undefined { - if (!key.includes('./*') || !exports[key]) return undefined - return { [key]: exports[key] } -} +export const fileExtension = (file: string | undefined) => + file ? path.extname(file).slice(1) : undefined -export async function validateExports( - exports: ExportCondition, - cwd: string, -): Promise { - const wildcardEntry = getExportConditionByKey('./*', exports) - console.log(!!wildcardEntry, wildcardEntry, exports, 'eejjeejj2') - if (!wildcardEntry) return exports - - const exportables = await getExportables(cwd) - const wildcardExports = exportables.map((exportable) => { - const filename = exportable.includes('.') - ? filenameWithoutExtension(exportable) - : undefined - - if (!filename) { - return { - [`./${exportable}`]: JSON.parse( - JSON.stringify(wildcardEntry['./*']).replace( - /\*/g, - `${exportable}/index`, - ), - ), - } - } - return JSON.parse(JSON.stringify(wildcardEntry).replace(/\*/g, filename)) - }) - - const resolvedExports = Object.assign( - {}, - exports, - ...wildcardExports, - exports, - ) - delete resolvedExports['./*'] - return resolvedExports -} +export const hasAvailableExtension = (filename: string): boolean => + availableExtensions.includes(path.extname(filename).slice(1)) From a3681c6b970fc44407df5a7f7bbebceb54e8cab6 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Fri, 18 Aug 2023 20:16:15 +0900 Subject: [PATCH 05/20] refac: refactor handling wildcard --- src/experimental/wildcard.ts | 91 +++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/src/experimental/wildcard.ts b/src/experimental/wildcard.ts index 7f4d8048..c95817c2 100644 --- a/src/experimental/wildcard.ts +++ b/src/experimental/wildcard.ts @@ -1,4 +1,5 @@ import fs from 'fs/promises' +import type { Dirent } from 'fs' import path from 'path' import { SRC } from '../constants' import { ExportCondition } from '../types' @@ -10,63 +11,55 @@ import { const getWildcardEntry = ( key: string, - exports: string | Record, -): string | Record | undefined => { - if (typeof exports === 'string') return exports + exports: Record, +): Record | undefined => { if (!key.includes('./*') || !exports[key]) return undefined return { [key]: exports[key] } } -export async function getExportables(cwd: string): Promise { - const dirents = await fs.readdir(path.resolve(cwd, SRC), { - withFileTypes: true, - }) +const isExportable = async ( + dirent: Dirent, + pathname: string, +): Promise => { + if (dirent.isDirectory()) { + const innerDirents = await fs.readdir(path.join(pathname, dirent.name), { + withFileTypes: true, + }) + return innerDirents.some( + ({ name }) => name.startsWith('index') && hasAvailableExtension(name), + ) + } + return ( + dirent.isFile() && + !dirent.name.startsWith('index') && + hasAvailableExtension(dirent.name) + ) +} +async function getExportables(cwd: string): Promise { + const pathname = path.resolve(cwd, SRC) + const dirents = await fs.readdir(pathname, { withFileTypes: true }) const exportables: (string | undefined)[] = await Promise.all( - dirents.map(async (dirent) => { - if (dirent.isDirectory()) { - // Read inside directory and check if it has an index file - const innerDirents = await fs.readdir( - path.join(cwd, SRC, dirent.name), - { - withFileTypes: true, - }, - ) - const hasExportableFile = innerDirents.some( - ({ name }) => name.startsWith('index') && hasAvailableExtension(name), - ) - return hasExportableFile ? dirent.name : undefined - } - - if ( - dirent.isFile() && - !dirent.name.startsWith('index') && - hasAvailableExtension(dirent.name) - ) { - return dirent.name - } - return undefined - }), + dirents.map(async (dirent) => + (await isExportable(dirent, pathname)) ? dirent.name : undefined, + ), ) return exportables.filter(nonNullable) } -export async function validateExports( - exports: ExportCondition, - cwd: string, -): Promise { - const wildcardEntry = getWildcardEntry('./*', exports) - - if (!wildcardEntry) return exports - if (typeof wildcardEntry === 'string') return exports - - const exportables = await getExportables(cwd) - const wildcardExports = exportables.map((exportable) => { +function mapWildcard( + wildcardEntry: string | Record, + exportables: string[], +): (string | ExportCondition)[] { + return exportables.map((exportable) => { const filename = exportable.includes('.') ? filenameWithoutExtension(exportable) : undefined if (!filename) { + if (typeof wildcardEntry === 'string') { + return wildcardEntry.replace(/\*/g, exportable) + } return { [`./${exportable}`]: JSON.parse( JSON.stringify(wildcardEntry['./*']).replace( @@ -78,11 +71,23 @@ export async function validateExports( } return JSON.parse(JSON.stringify(wildcardEntry).replace(/\*/g, filename)) }) +} + +export async function validateExports( + exports: ExportCondition | Record, + cwd: string, +): Promise { + if (typeof exports === 'string') return exports + const wildcardEntry = getWildcardEntry('./*', exports) + if (!wildcardEntry) return exports + + const exportables = await getExportables(cwd) + const resolvedWildcardExports = mapWildcard(wildcardEntry, exportables) const resolvedExports = Object.assign( {}, exports, - ...wildcardExports, + ...resolvedWildcardExports, exports, ) delete resolvedExports['./*'] From 19139e9a45e66cd4f96c0f0dc0a64e28b4075886 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Sun, 20 Aug 2023 19:05:43 +0900 Subject: [PATCH 06/20] feat: add console for experimental wildcard Co-authored-by: Jiachi Liu --- src/experimental/wildcard.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/experimental/wildcard.ts b/src/experimental/wildcard.ts index c95817c2..0d91d9ed 100644 --- a/src/experimental/wildcard.ts +++ b/src/experimental/wildcard.ts @@ -10,10 +10,10 @@ import { } from '../utils' const getWildcardEntry = ( - key: string, + key: string | undefined, exports: Record, ): Record | undefined => { - if (!key.includes('./*') || !exports[key]) return undefined + if (!key || !key.includes('./*') || !exports[key]) return undefined return { [key]: exports[key] } } @@ -77,8 +77,16 @@ export async function validateExports( exports: ExportCondition | Record, cwd: string, ): Promise { - if (typeof exports === 'string') return exports - const wildcardEntry = getWildcardEntry('./*', exports) + const wildcardKey = Object.keys(exports).find((key) => key.includes('./*')) + if (typeof exports === 'string' || !wildcardKey) return exports + + console.warn( + `The wildcard export "./*" is experimental and may change or be removed at any time.\n` + + 'To open an issue, please visit https://github.com/huozhi/bunchee/issues' + + '.\n', + ) + + const wildcardEntry = getWildcardEntry(wildcardKey, exports) if (!wildcardEntry) return exports const exportables = await getExportables(cwd) From 3251889c9672b988f0ef1358cee7e6e7e59b3fa0 Mon Sep 17 00:00:00 2001 From: Jiwon Choi Date: Mon, 21 Aug 2023 11:36:44 +0900 Subject: [PATCH 07/20] Update src/constants.ts Co-authored-by: Jiachi Liu --- src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.ts b/src/constants.ts index 9f2c2495..5687d1f6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,4 +16,4 @@ export const availableExportConventions = [ export const availableESExtensionsRegex = /\.(m|c)?[jt]sx?$/ export const dtsExtensionRegex = /\.d\.(m|c)?ts$/ -export const SRC = 'src' \ No newline at end of file +export const SRC = 'src' From be7a6f6b7f9cd196e3746b457bcabb5f78154f04 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Mon, 21 Aug 2023 12:23:09 +0900 Subject: [PATCH 08/20] refac: do not mutate pkg.exports --- src/bundle.ts | 10 ++++-- src/experimental/wildcard.ts | 69 ++++++++++++++++++------------------ src/exports.ts | 13 +++++-- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/bundle.ts b/src/bundle.ts index 4f452fbf..4875ea76 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -27,7 +27,7 @@ import { import type { BuildMetadata } from './types' import { TypescriptOptions, resolveTsConfig } from './typescript' import { logSizeStats } from './logging' -import { validateExports } from './experimental/wildcard' +import { resolveWildcardExports } from './experimental/wildcard' function assignDefault( options: BundleConfig, @@ -59,10 +59,14 @@ async function bundle( assignDefault(options, 'target', 'es2015') const pkg = await getPackageMeta(cwd) - pkg.exports &&= await validateExports(pkg.exports, cwd) + const resolvedWildcardExports = await resolveWildcardExports(pkg.exports, cwd) const packageType = getPackageType(pkg) - const exportPaths = getExportPaths(pkg) + const exportPaths = getExportPaths( + pkg, + packageType, + resolvedWildcardExports ? resolvedWildcardExports : undefined, + ) const exportKeys = Object.keys(exportPaths).filter( (key) => key !== './package.json', ) diff --git a/src/experimental/wildcard.ts b/src/experimental/wildcard.ts index 0d91d9ed..f9f4b1a5 100644 --- a/src/experimental/wildcard.ts +++ b/src/experimental/wildcard.ts @@ -9,12 +9,11 @@ import { nonNullable, } from '../utils' -const getWildcardEntry = ( - key: string | undefined, +// TODO: support nested wildcard exports (e.g. './foo/*') +const getWildcardExports = ( exports: Record, -): Record | undefined => { - if (!key || !key.includes('./*') || !exports[key]) return undefined - return { [key]: exports[key] } +): Record => { + return { './*': exports['./*'] } } const isExportable = async ( @@ -48,56 +47,58 @@ async function getExportables(cwd: string): Promise { } function mapWildcard( - wildcardEntry: string | Record, + wildcardExports: Record, exportables: string[], -): (string | ExportCondition)[] { +): ExportCondition[] { return exportables.map((exportable) => { const filename = exportable.includes('.') ? filenameWithoutExtension(exportable) : undefined if (!filename) { - if (typeof wildcardEntry === 'string') { - return wildcardEntry.replace(/\*/g, exportable) - } return { [`./${exportable}`]: JSON.parse( - JSON.stringify(wildcardEntry['./*']).replace( + JSON.stringify(wildcardExports['./*']).replace( /\*/g, `${exportable}/index`, ), ), } } - return JSON.parse(JSON.stringify(wildcardEntry).replace(/\*/g, filename)) + return JSON.parse(JSON.stringify(wildcardExports).replace(/\*/g, filename)) }) } -export async function validateExports( - exports: ExportCondition | Record, +export async function resolveWildcardExports( + exports: string | Record | undefined, cwd: string, -): Promise { - const wildcardKey = Object.keys(exports).find((key) => key.includes('./*')) - if (typeof exports === 'string' || !wildcardKey) return exports - - console.warn( - `The wildcard export "./*" is experimental and may change or be removed at any time.\n` + - 'To open an issue, please visit https://github.com/huozhi/bunchee/issues' + - '.\n', - ) +): Promise { + if (!exports || typeof exports === 'string') return undefined + const hasWildcard = !!exports['./*'] - const wildcardEntry = getWildcardEntry(wildcardKey, exports) - if (!wildcardEntry) return exports + if (hasWildcard) { + console.warn( + `The wildcard export "./*" is experimental and may change or be removed at any time.\n` + + 'To open an issue, please visit https://github.com/huozhi/bunchee/issues' + + '.\n', + ) + } else { + return undefined + } const exportables = await getExportables(cwd) - const resolvedWildcardExports = mapWildcard(wildcardEntry, exportables) + if (exportables.length > 0) { + const wildcardExports = getWildcardExports(exports) + const resolvedWildcardExports = mapWildcard(wildcardExports, exportables) - const resolvedExports = Object.assign( - {}, - exports, - ...resolvedWildcardExports, - exports, - ) - delete resolvedExports['./*'] - return resolvedExports + const resolvedExports = Object.assign( + {}, + exports, + ...resolvedWildcardExports, + exports, + ) + delete resolvedExports['./*'] + return resolvedExports + } + return undefined } diff --git a/src/exports.ts b/src/exports.ts index 1293e0e2..d0b66ac4 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -168,15 +168,22 @@ function parseExport( * pkg.main and pkg.module will be added to ['.'] if exists */ -export function getExportPaths(pkg: PackageMetadata) { +export function getExportPaths( + pkg: PackageMetadata, + pkgType?: PackageType, + resolvedWildcardExports?: ExportCondition, +) { const pathsMap: Record = {} - const packageType = getPackageType(pkg) + const packageType = pkgType ?? getPackageType(pkg) const isCjsPackage = packageType === 'commonjs' const { exports: exportsConditions } = pkg if (exportsConditions) { - const paths = parseExport(exportsConditions, packageType) + const paths = parseExport( + resolvedWildcardExports ? resolvedWildcardExports : exportsConditions, + packageType, + ) Object.assign(pathsMap, paths) } From 94bb8ac7d2692cdb616b183c0e610dea0690daad Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Mon, 21 Aug 2023 12:30:13 +0900 Subject: [PATCH 09/20] refac: simplify exportsConditions in getExportPaths --- src/bundle.ts | 6 +----- src/exports.ts | 7 ++----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/bundle.ts b/src/bundle.ts index 4875ea76..ffa782e1 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -62,11 +62,7 @@ async function bundle( const resolvedWildcardExports = await resolveWildcardExports(pkg.exports, cwd) const packageType = getPackageType(pkg) - const exportPaths = getExportPaths( - pkg, - packageType, - resolvedWildcardExports ? resolvedWildcardExports : undefined, - ) + const exportPaths = getExportPaths(pkg, packageType, resolvedWildcardExports) const exportKeys = Object.keys(exportPaths).filter( (key) => key !== './package.json', ) diff --git a/src/exports.ts b/src/exports.ts index d0b66ac4..53dbfa99 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -177,13 +177,10 @@ export function getExportPaths( const packageType = pkgType ?? getPackageType(pkg) const isCjsPackage = packageType === 'commonjs' - const { exports: exportsConditions } = pkg + const exportsConditions = resolvedWildcardExports ?? pkg.exports if (exportsConditions) { - const paths = parseExport( - resolvedWildcardExports ? resolvedWildcardExports : exportsConditions, - packageType, - ) + const paths = parseExport(exportsConditions, packageType) Object.assign(pathsMap, paths) } From d0a9df36d8273e144e52e232b66d82a62b741b98 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Mon, 21 Aug 2023 13:00:05 +0900 Subject: [PATCH 10/20] chore: add test for console.warn experimental --- test/integration.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 53f77c46..53cf189e 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -239,7 +239,7 @@ const testCases: { { name: 'wildcard-exports', args: [], - async expected(dir, { stdout }) { + async expected(dir, { stdout, stderr }) { const contentsRegex = { './dist/index.js': /'index'/, './dist/layout/index.js': /'layout'/, @@ -270,6 +270,7 @@ const testCases: { log.split('\n').forEach((line: string) => { expect(rawStdout).toContain(line.trim()) }) + expect(stderr).toContain('is experimental') }, }, ] From 0f6d7a9395b1b223cec23545884564317a20f6a9 Mon Sep 17 00:00:00 2001 From: Jiwon Choi Date: Tue, 5 Sep 2023 02:29:04 +0900 Subject: [PATCH 11/20] Update src/experimental/wildcard.ts Co-authored-by: Jiachi Liu --- src/experimental/wildcard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/experimental/wildcard.ts b/src/experimental/wildcard.ts index f9f4b1a5..a0c99318 100644 --- a/src/experimental/wildcard.ts +++ b/src/experimental/wildcard.ts @@ -70,7 +70,7 @@ function mapWildcard( } export async function resolveWildcardExports( - exports: string | Record | undefined, + exportsCondition: string | Record | undefined, cwd: string, ): Promise { if (!exports || typeof exports === 'string') return undefined From 4ff0bf52ebc485b541f96496563662ad2e997f43 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Tue, 5 Sep 2023 03:23:27 +0900 Subject: [PATCH 12/20] refac: resolve weird Object assign Co-authored-by: Jiachi Liu --- src/experimental/wildcard.ts | 56 +++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/experimental/wildcard.ts b/src/experimental/wildcard.ts index a0c99318..77f76621 100644 --- a/src/experimental/wildcard.ts +++ b/src/experimental/wildcard.ts @@ -9,11 +9,11 @@ import { nonNullable, } from '../utils' -// TODO: support nested wildcard exports (e.g. './foo/*') +// TODO: support nested wildcard exportsCondition (e.g. './foo/*') const getWildcardExports = ( - exports: Record, + exportsCondition: Record, ): Record => { - return { './*': exports['./*'] } + return { './*': exportsCondition['./*'] } } const isExportable = async ( @@ -35,12 +35,18 @@ const isExportable = async ( ) } -async function getExportables(cwd: string): Promise { +async function getExportables( + cwd: string, + excludeKeys: string[], +): Promise { const pathname = path.resolve(cwd, SRC) const dirents = await fs.readdir(pathname, { withFileTypes: true }) const exportables: (string | undefined)[] = await Promise.all( dirents.map(async (dirent) => - (await isExportable(dirent, pathname)) ? dirent.name : undefined, + (await isExportable(dirent, pathname)) && + !excludeKeys.includes(dirent.name) + ? dirent.name + : undefined, ), ) return exportables.filter(nonNullable) @@ -70,11 +76,13 @@ function mapWildcard( } export async function resolveWildcardExports( - exportsCondition: string | Record | undefined, + exportsCondition: ExportCondition, cwd: string, ): Promise { - if (!exports || typeof exports === 'string') return undefined - const hasWildcard = !!exports['./*'] + if (!exportsCondition || typeof exportsCondition === 'string') + return undefined + + const hasWildcard = !!exportsCondition['./*'] if (hasWildcard) { console.warn( @@ -82,23 +90,25 @@ export async function resolveWildcardExports( 'To open an issue, please visit https://github.com/huozhi/bunchee/issues' + '.\n', ) - } else { - return undefined - } - const exportables = await getExportables(cwd) - if (exportables.length > 0) { - const wildcardExports = getWildcardExports(exports) - const resolvedWildcardExports = mapWildcard(wildcardExports, exportables) + // './foo' -> ['foo']; './foo/bar' -> ['bar'] + const excludeKeys = Object.keys(exportsCondition) + .filter((key) => !['.', './*'].includes(key)) + .flatMap((key) => key.split('/').pop() ?? '') + const exportables = await getExportables(cwd, excludeKeys) - const resolvedExports = Object.assign( - {}, - exports, - ...resolvedWildcardExports, - exports, - ) - delete resolvedExports['./*'] - return resolvedExports + if (exportables.length > 0) { + const wildcardExports = getWildcardExports(exportsCondition) + const resolvedWildcardExports = mapWildcard(wildcardExports, exportables) + const resolvedExports = Object.assign( + exportsCondition, + ...resolvedWildcardExports, + ) + + delete resolvedExports['./*'] + return resolvedExports + } } + return undefined } From c12e7e6af69d7f87c14a053baa7a0d8e5032bfb8 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Tue, 5 Sep 2023 03:26:40 +0900 Subject: [PATCH 13/20] fix: revert exportsCondition param type --- src/experimental/wildcard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/experimental/wildcard.ts b/src/experimental/wildcard.ts index 77f76621..c844ebf1 100644 --- a/src/experimental/wildcard.ts +++ b/src/experimental/wildcard.ts @@ -76,7 +76,7 @@ function mapWildcard( } export async function resolveWildcardExports( - exportsCondition: ExportCondition, + exportsCondition: string | Record | undefined, cwd: string, ): Promise { if (!exportsCondition || typeof exportsCondition === 'string') From 50afd5f209f292cd3797a62d4cff5ca0d5339b08 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 4 Sep 2023 22:05:34 +0200 Subject: [PATCH 14/20] assign to a new object --- src/experimental/wildcard.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/experimental/wildcard.ts b/src/experimental/wildcard.ts index c844ebf1..1f868627 100644 --- a/src/experimental/wildcard.ts +++ b/src/experimental/wildcard.ts @@ -101,6 +101,7 @@ export async function resolveWildcardExports( const wildcardExports = getWildcardExports(exportsCondition) const resolvedWildcardExports = mapWildcard(wildcardExports, exportables) const resolvedExports = Object.assign( + {}, exportsCondition, ...resolvedWildcardExports, ) From 3ce5aed10f6b29cb08c57a9ebaab07e7eb185cdd Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 4 Sep 2023 22:07:06 +0200 Subject: [PATCH 15/20] rename import path --- src/bundle.ts | 2 +- src/{experimental => lib}/wildcard.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{experimental => lib}/wildcard.ts (100%) diff --git a/src/bundle.ts b/src/bundle.ts index ffa782e1..695e5384 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -27,7 +27,7 @@ import { import type { BuildMetadata } from './types' import { TypescriptOptions, resolveTsConfig } from './typescript' import { logSizeStats } from './logging' -import { resolveWildcardExports } from './experimental/wildcard' +import { resolveWildcardExports } from './lib/wildcard' function assignDefault( options: BundleConfig, diff --git a/src/experimental/wildcard.ts b/src/lib/wildcard.ts similarity index 100% rename from src/experimental/wildcard.ts rename to src/lib/wildcard.ts From 900d1abae2844ee0bdb1be16f951afffcdc8075b Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Tue, 5 Sep 2023 07:13:12 +0900 Subject: [PATCH 16/20] refac: proper object management --- src/lib/wildcard.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/lib/wildcard.ts b/src/lib/wildcard.ts index 1f868627..a4886275 100644 --- a/src/lib/wildcard.ts +++ b/src/lib/wildcard.ts @@ -57,21 +57,20 @@ function mapWildcard( exportables: string[], ): ExportCondition[] { return exportables.map((exportable) => { - const filename = exportable.includes('.') - ? filenameWithoutExtension(exportable) - : undefined + const filename = + exportable.includes('.') && filenameWithoutExtension(exportable) - if (!filename) { - return { - [`./${exportable}`]: JSON.parse( - JSON.stringify(wildcardExports['./*']).replace( + return { + [`./${filename ? filename : exportable}`]: Object.fromEntries( + Object.entries(wildcardExports['./*']).map(([key, value]) => [ + key, + (value as string).replace( /\*/g, - `${exportable}/index`, + filename ? filename : exportable + '/index', ), - ), - } + ]), + ), } - return JSON.parse(JSON.stringify(wildcardExports).replace(/\*/g, filename)) }) } From db330f31e2768b05646355a8b17f7f7a4d6cbe53 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Tue, 5 Sep 2023 07:17:08 +0900 Subject: [PATCH 17/20] refac: remove unnecessary map --- src/lib/wildcard.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/wildcard.ts b/src/lib/wildcard.ts index a4886275..4d377181 100644 --- a/src/lib/wildcard.ts +++ b/src/lib/wildcard.ts @@ -91,9 +91,10 @@ export async function resolveWildcardExports( ) // './foo' -> ['foo']; './foo/bar' -> ['bar'] - const excludeKeys = Object.keys(exportsCondition) - .filter((key) => !['.', './*'].includes(key)) - .flatMap((key) => key.split('/').pop() ?? '') + // will contain '*' also but it's not a problem + const excludeKeys = Object.keys(exportsCondition).map( + (key) => key.split('/').pop() as string, + ) const exportables = await getExportables(cwd, excludeKeys) if (exportables.length > 0) { From d87c59e7e50e9555d88d331d73af413d173d1f8a Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 5 Sep 2023 13:58:56 +0200 Subject: [PATCH 18/20] rename the variables, tune types --- src/bundle.ts | 2 +- src/exports.ts | 50 +++++++++++++++++++++++++++----------------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/bundle.ts b/src/bundle.ts index 695e5384..d1e333f9 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -106,7 +106,7 @@ async function bundle( exportPaths['.'] = constructDefaultExportCondition( { main: mainEntryPath, - types: typesEntryPath, + ...(typesEntryPath && { types: typesEntryPath }), }, packageType, ) diff --git a/src/exports.ts b/src/exports.ts index 53dbfa99..b6f5aece 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -27,23 +27,24 @@ function isExportLike(field: any): field is string | FullExportCondition { } function constructFullExportCondition( - value: string | Record, + exportCondition: string | Record, packageType: PackageType, ): FullExportCondition { const isCommonjs = packageType === 'commonjs' let result: FullExportCondition - if (typeof value === 'string') { + if (typeof exportCondition === 'string') { result = { - [isCommonjs ? 'require' : 'import']: value, + [isCommonjs ? 'require' : 'import']: exportCondition, } } else { // TODO: valid export condition, warn if it's not valid - const keys: string[] = Object.keys(value) + const keys: string[] = Object.keys(exportCondition) result = {} keys.forEach((key) => { + const condition = exportCondition[key] // Filter out nullable value - if (key in value && value[key]) { - result[key] = value[key] as string + if (key in exportCondition && condition) { + result[key] = condition } }) } @@ -62,21 +63,21 @@ function joinRelativePath(...segments: string[]) { function findExport( name: string, - value: ExportCondition, + exportCondition: ExportCondition, paths: Record, packageType: 'commonjs' | 'module', ): void { // TODO: handle export condition based on package.type - if (isExportLike(value)) { - paths[name] = constructFullExportCondition(value, packageType) + if (isExportLike(exportCondition)) { + paths[name] = constructFullExportCondition(exportCondition, packageType) return } - Object.keys(value).forEach((subpath) => { + Object.keys(exportCondition).forEach((subpath) => { const nextName = joinRelativePath(name, subpath) - const nestedValue = value[subpath] - findExport(nextName, nestedValue, paths, packageType) + const nestedExportCondition = exportCondition[subpath] + findExport(nextName, nestedExportCondition, paths, packageType) }) } @@ -119,8 +120,8 @@ function parseExport( paths['.'] = constructFullExportCondition(exportsCondition, packageType) } else { Object.keys(exportsCondition).forEach((key: string) => { - const value = exportsCondition[key] - findExport(key, value, paths, packageType) + const exportCondition = exportsCondition[key] + findExport(key, exportCondition, paths, packageType) }) } } @@ -251,17 +252,20 @@ export function getPackageType(pkg: PackageMetadata): PackageType { } export function constructDefaultExportCondition( - value: string | Record, + value: string | FullExportCondition, packageType: PackageType, ) { - const objValue = - typeof value === 'string' - ? { - [packageType === 'commonjs' ? 'require' : 'import']: value, - types: getTypings(value as PackageMetadata), - } - : value - return constructFullExportCondition(objValue, packageType) + let exportCondition + if (typeof value === 'string') { + const types = getTypings(value as PackageMetadata) + exportCondition = { + [packageType === 'commonjs' ? 'require' : 'import']: value, + ...(types && {types}), + } + } else { + exportCondition = value + } + return constructFullExportCondition(exportCondition, packageType) } export function isEsmExportName(name: string, ext: string) { From 9632405fc63423b6de1f77b48a345fd5cdf7fb14 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 5 Sep 2023 14:22:05 +0200 Subject: [PATCH 19/20] revert unnecessary change --- src/bundle.ts | 2 +- src/exports.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bundle.ts b/src/bundle.ts index d1e333f9..695e5384 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -106,7 +106,7 @@ async function bundle( exportPaths['.'] = constructDefaultExportCondition( { main: mainEntryPath, - ...(typesEntryPath && { types: typesEntryPath }), + types: typesEntryPath, }, packageType, ) diff --git a/src/exports.ts b/src/exports.ts index b6f5aece..f803af5c 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -252,7 +252,7 @@ export function getPackageType(pkg: PackageMetadata): PackageType { } export function constructDefaultExportCondition( - value: string | FullExportCondition, + value: string | Record, packageType: PackageType, ) { let exportCondition From b0edd61fdba017538aa48a025ddb36846136357d Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Tue, 5 Sep 2023 21:24:50 +0900 Subject: [PATCH 20/20] refac: wildcard filename mapping --- src/lib/wildcard.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/wildcard.ts b/src/lib/wildcard.ts index 4d377181..43aa8f8f 100644 --- a/src/lib/wildcard.ts +++ b/src/lib/wildcard.ts @@ -57,16 +57,16 @@ function mapWildcard( exportables: string[], ): ExportCondition[] { return exportables.map((exportable) => { - const filename = - exportable.includes('.') && filenameWithoutExtension(exportable) + const isFile = exportable.includes('.') + const filename = isFile ? filenameWithoutExtension(exportable)! : exportable return { - [`./${filename ? filename : exportable}`]: Object.fromEntries( + [`./${filename}`]: Object.fromEntries( Object.entries(wildcardExports['./*']).map(([key, value]) => [ key, (value as string).replace( /\*/g, - filename ? filename : exportable + '/index', + isFile ? filename : `${filename}/index`, ), ]), ),