Skip to content

Commit 9e6341a

Browse files
TheAlexLichtersxzz
andauthored
feat(exports): add exclude option (#340)
Co-authored-by: Kevin Deng <sxzz@sxzz.moe>
1 parent 039fb82 commit 9e6341a

4 files changed

Lines changed: 110 additions & 16 deletions

File tree

pnpm-lock.yaml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/features/pkg/exports.test.ts

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe.concurrent('generateExports', () => {
2828
test('only one entry', async ({ expect }) => {
2929
const results = generateExports(
3030
FAKE_PACKAGE_JSON,
31-
{ es: [genChunk('main.js')] },
31+
{ es: [genChunk('main.js'), genChunk('chunk.js', false)] },
3232
{},
3333
)
3434
await expect(results).resolves.toMatchInlineSnapshot(`
@@ -276,6 +276,72 @@ describe.concurrent('generateExports', () => {
276276
`)
277277
})
278278

279+
test('exclude via regex', async ({ expect }) => {
280+
const results = generateExports(
281+
FAKE_PACKAGE_JSON,
282+
{ es: [genChunk('index.js'), genChunk('foo.js'), genChunk('bar.js')] },
283+
{ exclude: [/bar/] },
284+
)
285+
286+
await expect(results).resolves.toMatchInlineSnapshot(`
287+
{
288+
"exports": {
289+
".": "./index.js",
290+
"./foo": "./foo.js",
291+
"./package.json": "./package.json",
292+
},
293+
"main": "./index.js",
294+
"module": "./index.js",
295+
"publishExports": undefined,
296+
"types": undefined,
297+
}
298+
`)
299+
})
300+
301+
test('exclude via filename', async ({ expect }) => {
302+
const results = generateExports(
303+
FAKE_PACKAGE_JSON,
304+
{
305+
es: [genChunk('index.js'), genChunk('foo.js'), genChunk('abc/bar.js')],
306+
},
307+
{ exclude: ['abc/bar.js'] },
308+
)
309+
310+
await expect(results).resolves.toMatchInlineSnapshot(`
311+
{
312+
"exports": {
313+
".": "./index.js",
314+
"./foo": "./foo.js",
315+
"./package.json": "./package.json",
316+
},
317+
"main": "./index.js",
318+
"module": "./index.js",
319+
"publishExports": undefined,
320+
"types": undefined,
321+
}
322+
`)
323+
})
324+
325+
test('multiple excludes', async ({ expect }) => {
326+
const results = generateExports(
327+
FAKE_PACKAGE_JSON,
328+
{ es: [genChunk('foo.js'), genChunk('abc/bar.js')] },
329+
{ exclude: ['abc/bar.js', /foo/] },
330+
)
331+
332+
await expect(results).resolves.toMatchInlineSnapshot(`
333+
{
334+
"exports": {
335+
"./package.json": "./package.json",
336+
},
337+
"main": undefined,
338+
"module": undefined,
339+
"publishExports": undefined,
340+
"types": undefined,
341+
}
342+
`)
343+
})
344+
279345
test('export all', async ({ expect }) => {
280346
const results = generateExports(
281347
FAKE_PACKAGE_JSON,
@@ -381,12 +447,12 @@ describe.concurrent('generateExports', () => {
381447
})
382448
})
383449

384-
function genChunk(fileName: string) {
450+
function genChunk(fileName: string, isEntry = true) {
385451
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
386452
return {
387453
type: 'chunk',
388454
fileName,
389-
isEntry: true,
455+
isEntry,
390456
facadeModuleId: `./SRC/${fileName}`,
391457
outDir: cwd,
392458
} as RolldownChunk

src/features/pkg/exports.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ import { readFile, writeFile } from 'node:fs/promises'
22
import path from 'node:path'
33
import { RE_DTS } from 'rolldown-plugin-dts/filename'
44
import { detectIndentation } from '../../utils/format.ts'
5-
import { slash } from '../../utils/general.ts'
5+
import { matchPattern, slash } from '../../utils/general.ts'
66
import type { NormalizedFormat, ResolvedConfig } from '../../config/types.ts'
7-
import type { ChunksByFormat, RolldownChunk } from '../../utils/chunks.ts'
7+
import type {
8+
ChunksByFormat,
9+
RolldownChunk,
10+
RolldownCodeChunk,
11+
} from '../../utils/chunks.ts'
812
import type { Awaitable } from '../../utils/types.ts'
913
import type { PackageJson } from 'pkg-types'
1014

@@ -21,6 +25,18 @@ export interface ExportsOptions {
2125
*/
2226
all?: boolean
2327

28+
/**
29+
* Define filenames or RegExp patterns to exclude files from exports.
30+
* This is useful for excluding files that should not be part of the package exports,
31+
* such as bin files or internal utilities.
32+
*
33+
* @example
34+
* ```js
35+
* exclude: ['foo.ts', /\.spec\.ts$/, /internal/]
36+
* ```
37+
*/
38+
exclude?: (RegExp | string)[]
39+
2440
customExports?: (
2541
exports: Record<string, any>,
2642
context: {
@@ -65,10 +81,18 @@ export async function writeExports(
6581

6682
type SubExport = Partial<Record<'cjs' | 'es' | 'src', string>>
6783

84+
function shouldExclude(
85+
fileName: string,
86+
exclude?: (RegExp | string)[],
87+
): boolean {
88+
if (!exclude?.length) return false
89+
return matchPattern(fileName, exclude)
90+
}
91+
6892
export async function generateExports(
6993
pkg: PackageJson,
7094
chunks: ChunksByFormat,
71-
{ devExports, all, customExports }: ExportsOptions,
95+
{ devExports, all, exclude, customExports }: ExportsOptions,
7296
): Promise<{
7397
main: string | undefined
7498
module: string | undefined
@@ -90,16 +114,19 @@ export async function generateExports(
90114
][]) {
91115
if (format !== 'es' && format !== 'cjs') continue
92116

117+
// Filter out non-entry chunks and excluded files
118+
const filteredChunks = chunksByFormat.filter(
119+
(chunk): chunk is RolldownCodeChunk =>
120+
chunk.type === 'chunk' &&
121+
chunk.isEntry &&
122+
!shouldExclude(chunk.fileName, exclude),
123+
)
124+
93125
const onlyOneEntry =
94-
chunksByFormat.filter(
95-
(chunk) =>
96-
chunk.type === 'chunk' &&
97-
chunk.isEntry &&
98-
!RE_DTS.test(chunk.fileName),
99-
).length === 1
100-
for (const chunk of chunksByFormat) {
101-
if (chunk.type !== 'chunk' || !chunk.isEntry) continue
126+
filteredChunks.filter((chunk) => !RE_DTS.test(chunk.fileName)).length ===
127+
1
102128

129+
for (const chunk of filteredChunks) {
103130
const normalizedName = slash(chunk.fileName)
104131
const ext = path.extname(chunk.fileName)
105132
let name = normalizedName.slice(0, -ext.length)

src/utils/chunks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { NormalizedFormat, ResolvedConfig } from '../config/types.ts'
22
import type { OutputAsset, OutputChunk } from 'rolldown'
33

44
export type RolldownChunk = (OutputChunk | OutputAsset) & { outDir: string }
5+
export type RolldownCodeChunk = RolldownChunk & { type: 'chunk' }
56
export type ChunksByFormat = Partial<Record<NormalizedFormat, RolldownChunk[]>>
67
export interface TsdownBundle extends AsyncDisposable {
78
chunks: RolldownChunk[]

0 commit comments

Comments
 (0)