Skip to content

Commit

Permalink
perf: only generate declared types (#475)
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi authored Mar 7, 2024
1 parent ba53d9a commit f21b708
Show file tree
Hide file tree
Showing 24 changed files with 159 additions and 151 deletions.
83 changes: 45 additions & 38 deletions src/build-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,6 @@ export async function buildEntryConfig(
): Promise<BuncheeRollupConfig[]> {
const configs: BuncheeRollupConfig[] = []
const { entries } = pluginContext

for (const exportCondition of Object.values(entries)) {
const rollupConfigs = await buildConfig(
bundleConfig,
Expand All @@ -487,6 +486,7 @@ async function buildConfig(
pkg,
exportCondition,
cwd,
dts,
)

// If there's nothing found, give a default output
Expand All @@ -501,46 +501,53 @@ async function buildConfig(
}
let bundleOptions: ExportOutput[] = []

// multi outputs with specified format

// CLI output option is always prioritized
if (file) {
const fallbackExport = outputExports[0]
bundleOptions = [
{
file: resolve(cwd, file),
format: bundleConfig.format || fallbackExport.format,
exportCondition: fallbackExport.exportCondition,
},
]
const absoluteFile = resolve(cwd, file)
const absoluteTypeFile = getExportFileTypePath(absoluteFile)
if (dts) {
bundleOptions = [
{
file: absoluteTypeFile,
format: 'esm',
exportCondition: 'types',
},
]
} else {
const fallbackExport = outputExports[0]
bundleOptions = [
{
file: absoluteFile,
format: bundleConfig.format || fallbackExport.format,
exportCondition: fallbackExport.exportCondition,
},
]
}
} else {
bundleOptions = outputExports.map((exportDist) => {
return {
file: resolve(cwd, exportDist.file),
format: exportDist.format,
exportCondition: exportDist.exportCondition,
}
})
}
if (dts) {
// types could have duplicates, dedupe them
// e.g. { types, import, .. } use the same `types` condition with all conditions
const uniqTypes = new Set<string>()
bundleOptions.forEach((bundleOption) => {
if (exportCondition.export.types) {
uniqTypes.add(resolve(cwd, exportCondition.export.types))
}
const typeForExtension = getExportFileTypePath(bundleOption.file)
uniqTypes.add(typeForExtension)
})
// CLI output option is always prioritized
if (dts) {
// types could have duplicates, dedupe them
// e.g. { types, import, .. } use the same `types` condition with all conditions
const uniqTypes = new Set<string>()
outputExports.forEach((exportDist) => {
uniqTypes.add(resolve(cwd, exportDist.file))
})

bundleOptions = Array.from(uniqTypes).map((typeFile) => {
return {
file: typeFile,
format: 'esm',
exportCondition: 'types',
}
})
bundleOptions = Array.from(uniqTypes).map((typeFile) => {
return {
file: typeFile,
format: 'esm',
exportCondition: 'types',
}
})
} else {
bundleOptions = outputExports.map((exportDist) => {
return {
file: resolve(cwd, exportDist.file),
format: exportDist.format,
exportCondition: exportDist.exportCondition,
}
})
}
}

const outputConfigs = bundleOptions.map(async (bundleOption) => {
Expand Down
3 changes: 1 addition & 2 deletions src/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export async function collectEntriesFromParsedExports(
const normalizedExportPath = normalizeExportPath(entryExportPath)
const entryExportPathType = getExportTypeFromExportPath(entryExportPath)
const outputExports = parsedExportsInfo.get(normalizedExportPath)

if (!outputExports) {
continue
}
Expand Down Expand Up @@ -203,7 +202,7 @@ function getExportTypeFromExportTypes(types: string[]): string {
new Set(types).forEach((value) => {
if (specialExportConventions.has(value)) {
exportType = value
} else if (value === 'import' || value === 'require') {
} else if (value === 'import' || value === 'require' || value === 'types') {
exportType = value
}
})
Expand Down
30 changes: 25 additions & 5 deletions src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ function collectExportPath(
// End of searching, export value is file path.
// <export key>: <export value> (string)
if (typeof exportValue === 'string') {
exportTypes.add(exportKey.startsWith('./') ? 'default' : exportKey)
const composedTypes = new Set(exportTypes)
composedTypes.add(exportKey.startsWith('.') ? 'default' : exportKey)
const exportInfo = exportToDist.get(currentPath)
const exportCondition = Array.from(exportTypes).join('.')
const exportCondition = Array.from(composedTypes).join('.')
if (!exportInfo) {
const outputConditionPair: [string, string] = [
exportValue,
Expand Down Expand Up @@ -137,6 +138,10 @@ export function parseExports(pkg: PackageMetadata): ParsedExportsInfo {
? joinRelativePath(currentPath, exportKey)
: currentPath

if (!isExportPath) {
exportTypes.add(exportKey)
}

collectExportPath(
exportValue,
exportKey,
Expand All @@ -160,7 +165,8 @@ export function parseExports(pkg: PackageMetadata): ParsedExportsInfo {
}
}

if (pkg.main || pkg.module) {
// Handle package.json global exports fields
if (pkg.main || pkg.module || pkg.types) {
const mainExportPath = pkg.main
const moduleExportPath = pkg.module
const typesEntryPath = pkg.types
Expand All @@ -169,7 +175,10 @@ export function parseExports(pkg: PackageMetadata): ParsedExportsInfo {
'.',
[
...(existingExportInfo || []),
[mainExportPath, getMainFieldExportType(pkg)],
Boolean(mainExportPath) && [
mainExportPath,
getMainFieldExportType(pkg),
],
Boolean(moduleExportPath) && [moduleExportPath, 'import'],
Boolean(typesEntryPath) && [typesEntryPath, 'types'],
].filter(Boolean) as [string, string][],
Expand Down Expand Up @@ -215,6 +224,10 @@ export function isCjsExportName(
)
}

export function getFileExportType(composedTypes: string) {
return composedTypes.split('.').pop() as string
}

export type ExportOutput = {
format: OutputOptions['format']
file: string
Expand All @@ -224,12 +237,19 @@ export function getExportsDistFilesOfCondition(
pkg: PackageMetadata,
parsedExportCondition: ParsedExportCondition,
cwd: string,
dts: boolean,
): ExportOutput[] {
const dist: ExportOutput[] = []
const exportConditionNames = Object.keys(parsedExportCondition.export)
const uniqueFiles = new Set<string>()
for (const exportCondition of exportConditionNames) {
if (exportCondition === 'types') {
const exportType = getFileExportType(exportCondition)
// Filter out non-types field when generating types jobs
if (dts && exportType !== 'types') {
continue
}
// Filter out types field when generating asset jobs
if (!dts && exportType === 'types') {
continue
}
const filePath = parsedExportCondition.export[exportCondition]
Expand Down
8 changes: 1 addition & 7 deletions src/plugins/output-state-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,6 @@ function normalizeExportName(exportName: string): string {
return result
}

function getExportNameWithoutExportCondition(exportName: string): string {
return exportName.includes('.') ? exportName.split('.')[0] : exportName
}

function logOutputState(sizeCollector: ReturnType<typeof createOutputState>) {
const stats = sizeCollector.getSizeStats()

Expand All @@ -135,9 +131,7 @@ function logOutputState(sizeCollector: ReturnType<typeof createOutputState>) {
.map(([filename]) => filename.length)
const maxFilenameLength = Math.max(...allFileNameLengths)
const statsArray = [...stats.entries()].sort(([a], [b]) => {
const comp =
getExportNameWithoutExportCondition(a).length -
getExportNameWithoutExportCondition(b).length
const comp = normalizeExportPath(a).length - normalizeExportPath(b).length
return comp === 0 ? a.localeCompare(b) : comp
})

Expand Down
3 changes: 2 additions & 1 deletion test/cli/workspace/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ describe('cli', () => {
const distFile = path.join(distDir, 'index.mjs')

expect(fs.existsSync(distFile)).toBe(true)
expect(fs.existsSync(distFile.replace('.mjs', '.d.mts'))).toBe(true)
// CLI does not generate declaration files
expect(fs.existsSync(distFile.replace('.mjs', '.d.mts'))).toBe(false)
expect(code).toBe(0)
},
)
Expand Down
64 changes: 2 additions & 62 deletions test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
stripANSIColor,
existsFile,
assertFilesContent,
getChunkFileNamesFromLog,
assertContainFiles,
deleteFile,
} from './testing-utils'
Expand Down Expand Up @@ -86,75 +85,18 @@ const testCases: {
)
},
},
{
name: 'multi-entries',
async expected(dir, { stdout }) {
const contentsRegex = {
'./dist/index.js': /'index'/,
'./dist/shared/index.mjs': /'shared'/,
'./dist/shared/edge-light.mjs': /'shared.edge-light'/,
'./dist/server/edge.mjs': /'server.edge-light'/,
'./dist/server/react-server.mjs': /'server.react-server'/,
// types
'./dist/server/index.d.ts': `export { Client } from '../client/index.cjs';\nexport { Shared } from '../shared/index.cjs';`,
'./dist/client/index.d.mts': `export { Shared } from '../shared/index.mjs'`,
'./dist/client/index.d.cts': `export { Shared } from '../shared/index.cjs'`,
'./dist/client/index.d.ts': `export { Shared } from '../shared/index.cjs'`,
}

await assertFilesContent(dir, contentsRegex)

const log = `\
dist/shared/index.d.mts
dist/index.d.ts
dist/server/index.d.ts
dist/server/index.d.mts
dist/lite.d.ts
dist/server/edge.d.mts
dist/shared/edge-light.d.mts
dist/server/react-server.d.mts
dist/client/index.d.ts
dist/client/index.d.cts
dist/client/index.d.mts
dist/shared/edge-light.mjs
dist/shared/index.mjs
dist/index.js
dist/client/index.cjs
dist/client/index.mjs
dist/lite.js
dist/server/react-server.mjs
dist/server/edge.mjs
dist/server/index.mjs
`

const rawStdout = stripANSIColor(stdout)
getChunkFileNamesFromLog(log).forEach((chunk: string) => {
expect(rawStdout).toContain(chunk)
})
},
},
{
name: 'ts-dual-package-type-cjs',
args: [],
async expected(dir) {
assertContainFiles(dir, [
'./dist/index.js',
'./dist/index.mjs',
'./dist/index.d.ts',
'./dist/index.d.mts',
])
assertContainFiles(dir, ['./dist/index.js', './dist/index.mjs'])
},
},
{
name: 'ts-dual-package-module',
args: [],
async expected(dir) {
const distFiles = [
'./dist/index.js',
'./dist/index.cjs',
'./dist/index.d.ts',
'./dist/index.d.cts',
]
const distFiles = ['./dist/index.js', './dist/index.cjs']
assertContainFiles(dir, distFiles)
},
},
Expand All @@ -165,8 +107,6 @@ const testCases: {
const distFiles = [
'./dist/index.mjs',
'./dist/index.cjs',
'./dist/index.d.mts',
'./dist/index.d.cts',
'./dist/index.d.ts',
]
assertContainFiles(dir, distFiles)
Expand Down
18 changes: 4 additions & 14 deletions test/integration/bin/cts/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { readFile } from 'fs/promises'
import { createIntegrationTest, existsFile } from '../../utils'
import { assertFilesContent, createIntegrationTest } from '../../utils'

describe('integration bin/cts', () => {
it('should work with bin as .cts extension', async () => {
Expand All @@ -8,18 +7,9 @@ describe('integration bin/cts', () => {
directory: __dirname,
},
async ({ distDir }) => {
const distFiles = [
`${distDir}/bin/index.cjs`,
`${distDir}/bin/index.d.cts`,
]

for (const distFile of distFiles) {
expect(await existsFile(distFile)).toBe(true)
}

expect(await readFile(distFiles[0], 'utf-8')).toContain(
'#!/usr/bin/env node',
)
await assertFilesContent(distDir, {
'bin/index.cjs': '#!/usr/bin/env node',
})
},
)
})
Expand Down
19 changes: 5 additions & 14 deletions test/integration/bin/multi-path/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { readFile } from 'fs/promises'
import { createIntegrationTest, existsFile } from '../../utils'
import { assertFilesContent, createIntegrationTest } from '../../utils'

describe('integration bin/multi-path', () => {
it('should work with bin as multi path', async () => {
Expand All @@ -8,18 +7,10 @@ describe('integration bin/multi-path', () => {
directory: __dirname,
},
async ({ distDir }) => {
const distBinFiles = [`${distDir}/bin/a.js`, `${distDir}/bin/b.js`]
const distTypeFiles = [`${distDir}/bin/a.d.ts`, `${distDir}/bin/b.d.ts`]
const distFiles = distBinFiles.concat(distTypeFiles)

for (const distFile of distFiles) {
expect(await existsFile(distFile)).toBe(true)
}
for (const distScriptFile of distBinFiles) {
expect(await readFile(distScriptFile, 'utf-8')).toContain(
'#!/usr/bin/env node',
)
}
await assertFilesContent(distDir, {
'bin/a.js': '#!/usr/bin/env node',
'bin/b.js': '#!/usr/bin/env node',
})
},
)
})
Expand Down
2 changes: 1 addition & 1 deletion test/integration/bin/single-path/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('integration bin/single-path', () => {
directory: __dirname,
},
async ({ distDir }) => {
const distFiles = ['bin.js', 'bin.d.ts']
const distFiles = ['bin.js']
await assertContainFiles(distDir, distFiles)
await assertFilesContent(distDir, {
'bin.js': /console.log\('Hello, world!'\)/,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit f21b708

Please sign in to comment.