nx/packages/rollup/src/plugins/package-json/update-package-json.spec.ts
Jack Hsu 2d2c0b5acb
fix(bundling): explicitly set types for exports entries in package.json (#27152)
We currently rely on the TS behavior of matching `d.ts` files based on
the `.js` file names. e.g. `foo.js` matches `foo.d.ts`. However, it
isn't working for all tools so we should explicitly set it.

Most modern packages are still setting it even though it is not
technically needed. e.g.
[Nuxt](https://unpkg.com/browse/nuxt@3.12.4/package.json)

Note: If both ESM and CJS are present, then prefer `*.esm.d.ts` files
since the generated types are in ESM format.

## Current Behavior
`exports` entries are missing `types` field

## Expected Behavior
`exports` entries have `types` field set

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #18835
2024-07-26 12:23:46 -04:00

321 lines
7.6 KiB
TypeScript

import { updatePackageJson } from './update-package-json';
import * as utils from 'nx/src/utils/fileutils';
import { PackageJson } from 'nx/src/utils/package-json';
describe('updatePackageJson', () => {
const commonOptions = {
outputPath: 'dist/index.js',
tsConfig: './tsconfig.json',
project: './package.json',
main: './index.js',
entryRoot: '.',
projectRoot: '.',
assets: [],
rollupConfig: [],
};
describe('generateExportsField: true', () => {
it('should support ESM', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
generateExportsField: true,
format: ['esm'],
},
{} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
exports: {
'./package.json': './package.json',
'.': {
import: './index.esm.js',
types: './index.esm.d.ts',
},
},
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.esm.d.ts',
});
spy.mockRestore();
});
it('should support CJS', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
generateExportsField: true,
format: ['cjs'],
},
{} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
exports: {
'./package.json': './package.json',
'.': './index.cjs.js',
},
main: './index.cjs.js',
type: 'commonjs',
types: './index.cjs.d.ts',
});
spy.mockRestore();
});
it('should support ESM + CJS', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
generateExportsField: true,
format: ['esm', 'cjs'],
},
{} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
exports: {
'./package.json': './package.json',
'.': {
module: './index.esm.js',
import: './index.cjs.mjs',
default: './index.cjs.js',
types: './index.esm.d.ts',
},
},
main: './index.cjs.js',
module: './index.esm.js',
types: './index.esm.d.ts',
});
spy.mockRestore();
});
it('should support custom exports field', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
generateExportsField: true,
format: ['esm'],
},
{
exports: {
'./foo': {
import: './some/custom/path/foo.esm.js',
types: './some/custom/path/foo.d.ts',
},
},
} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
exports: {
'./package.json': './package.json',
'.': {
import: './index.esm.js',
types: './index.esm.d.ts',
},
'./foo': {
import: './some/custom/path/foo.esm.js',
types: './some/custom/path/foo.d.ts',
},
},
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.esm.d.ts',
});
spy.mockRestore();
});
});
describe('generateExportsField: false', () => {
it('should support ESM', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
format: ['esm'],
},
{} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.esm.d.ts',
});
spy.mockRestore();
});
it('should support CJS', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
format: ['cjs'],
},
{} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.cjs.js',
type: 'commonjs',
types: './index.cjs.d.ts',
});
spy.mockRestore();
});
it('should support ESM + CJS', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
format: ['esm', 'cjs'],
},
{} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.cjs.js',
module: './index.esm.js',
types: './index.esm.d.ts',
});
spy.mockRestore();
});
it('should support custom exports field', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
format: ['esm'],
},
{
exports: {
'./foo': './foo.esm.js',
},
} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.esm.d.ts',
exports: {
'./foo': './foo.esm.js',
},
});
spy.mockRestore();
});
});
describe('skipTypeField', () => {
it('should not include type field if skipTypeField is true', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
format: ['esm'],
skipTypeField: true,
},
{
exports: {
'./foo': './foo.esm.js',
},
} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.esm.js',
module: './index.esm.js',
types: './index.esm.d.ts',
exports: {
'./foo': './foo.esm.js',
},
});
spy.mockRestore();
});
it('should include type field if skipTypeField is undefined', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
format: ['esm'],
},
{
exports: {
'./foo': './foo.esm.js',
},
} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.esm.d.ts',
exports: {
'./foo': './foo.esm.js',
},
});
spy.mockRestore();
});
it('should include type field if skipTypeField is false', () => {
const spy = jest.spyOn(utils, 'writeJsonFile');
updatePackageJson(
{
...commonOptions,
format: ['esm'],
skipTypeField: false,
},
{
exports: {
'./foo': './foo.esm.js',
},
} as unknown as PackageJson
);
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.esm.d.ts',
exports: {
'./foo': './foo.esm.js',
},
});
spy.mockRestore();
});
});
});