Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: only generate declared types #475

Merged
merged 4 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading