Skip to content
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
1 change: 1 addition & 0 deletions __snapshots__/tsnapi/index.snapshot.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface ExportsOptions {
isPublish: boolean;
}) => Awaitable<Record<string, any>>);
inlinedDependencies?: boolean;
extensions?: boolean;
bin?: boolean | string | Record<string, string>;
}
export interface InlineConfig extends UserConfig {
Expand Down
154 changes: 154 additions & 0 deletions src/features/pkg/exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,160 @@ describe('generateExports', () => {
})
})

test('extensions adds .js to subpath export keys', async ({ expect }) => {
const results = generateExports(
{
es: [genChunk('index.js'), genChunk('foo.js'), genChunk('bar.js')],
},
{
exports: { extensions: true },
},
)
await expect(results).resolves.toMatchInlineSnapshot(`
{
"bin": undefined,
"exports": {
".": "./index.js",
"./bar.js": "./bar.js",
"./foo.js": "./foo.js",
"./package.json": "./package.json",
},
"inlinedDependencies": undefined,
"main": undefined,
"module": undefined,
"publishExports": undefined,
"types": undefined,
}
`)
})

test('extensions with directory index entries', async ({ expect }) => {
const results = generateExports(
{
es: [genChunk('index.js'), genChunk('foo/index.js')],
},
{
exports: { extensions: true },
},
)
await expect(results).resolves.toMatchInlineSnapshot(`
{
"bin": undefined,
"exports": {
".": "./index.js",
"./foo.js": "./foo/index.js",
"./package.json": "./package.json",
},
"inlinedDependencies": undefined,
"main": undefined,
"module": undefined,
"publishExports": undefined,
"types": undefined,
}
`)
})

test('extensions with dual formats', async ({ expect }) => {
const results = generateExports(
{
es: [genChunk('index.js'), genChunk('utils.js')],
cjs: [genChunk('index.cjs'), genChunk('utils.cjs')],
},
{
exports: { extensions: true },
},
)
await expect(results).resolves.toMatchInlineSnapshot(`
{
"bin": undefined,
"exports": {
".": {
"import": "./index.js",
"require": "./index.cjs",
},
"./package.json": "./package.json",
"./utils.js": {
"import": "./utils.js",
"require": "./utils.cjs",
},
},
"inlinedDependencies": undefined,
"main": "./index.cjs",
"module": "./index.js",
"publishExports": undefined,
"types": undefined,
}
`)
})

test('extensions does not affect root export', async ({ expect }) => {
const results = generateExports(
{
es: [genChunk('main.js')],
},
{
exports: { extensions: true },
},
)
await expect(results).resolves.toMatchInlineSnapshot(`
{
"bin": undefined,
"exports": {
".": "./main.js",
"./package.json": "./package.json",
},
"inlinedDependencies": undefined,
"main": undefined,
"module": undefined,
"publishExports": undefined,
"types": undefined,
}
`)
})

test('extensions with devExports', async ({ expect }) => {
const results = await generateExports(
{
es: [genChunk('index.js'), genChunk('utils.js')],
cjs: [genChunk('index.cjs'), genChunk('utils.cjs')],
},
{
exports: { extensions: true, devExports: '@my-org/source' },
},
)
// key order matters
expect(JSON.stringify(results, undefined, 2)).toMatchInlineSnapshot(`
"{
"main": "./index.cjs",
"module": "./index.js",
"exports": {
".": {
"@my-org/source": "./SRC/index.js",
"import": "./index.js",
"require": "./index.cjs"
},
"./utils.js": {
"@my-org/source": "./SRC/utils.js",
"import": "./utils.js",
"require": "./utils.cjs"
},
"./package.json": "./package.json"
},
"publishExports": {
".": {
"import": "./index.js",
"require": "./index.cjs"
},
"./utils.js": {
"import": "./utils.js",
"require": "./utils.cjs"
},
"./package.json": "./package.json"
}
}"
`)
})

test('generate css publish exports', async ({ expect }) => {
const results = generateExports(
{ es: [genChunk('index.js'), genAsset('style.css')] },
Expand Down
18 changes: 18 additions & 0 deletions src/features/pkg/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,19 @@ export interface ExportsOptions {
*/
inlinedDependencies?: boolean

/**
* Add file extensions to subpath export keys.
*
* When enabled, all subpath exports (except the root `"."`) will include
* a `.js` extension in the key (e.g., `"./utils.js"` instead of `"./utils"`).
*
* This follows the Node.js recommendation for subpath exports:
* @see {@link https://nodejs.org/api/packages.html#extensions-in-subpaths}
*
* @default false
*/
extensions?: boolean

/**
* Auto-generate the `bin` field in package.json.
*
Expand Down Expand Up @@ -185,6 +198,7 @@ export async function generateExports(
exclude,
customExports,
legacy,
extensions,
inlinedDependencies: emitInlinedDeps = true,
bin,
},
Expand Down Expand Up @@ -268,6 +282,10 @@ export async function generateExports(
name = `./${name}`
}

if (extensions && name !== '.') {
name = `${name}.js`
}

let subExport = exportsMap.get(name)
if (!subExport) {
subExport = {}
Expand Down
Loading