Skip to content

Commit f6be910

Browse files
authored
Correct fields in package.json (#339)
1 parent 407c0be commit f6be910

File tree

19 files changed

+104
-187
lines changed

19 files changed

+104
-187
lines changed

.changeset/bright-drinks-provide.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'bob-the-bundler': major
3+
---
4+
5+
Drop "module" package.json field
6+
7+
The field was just a proposal and was never officially (and fully) defined by Node. Node instead uses (and recommends) the ["exports" field](https://nodejs.org/api/packages.html#exports).

.changeset/rare-lemons-count.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'bob-the-bundler': major
3+
---
4+
5+
Drop "typescript" package.json field

.changeset/wise-tigers-roll.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'bob-the-bundler': major
3+
---
4+
5+
"main" package.json field matches the location of "type" output
6+
7+
> The "type" field defines the module format that Node.js uses for all .js files that have that package.json file as their nearest parent.
8+
>
9+
> Files ending with .js are loaded as ES modules when the nearest parent package.json file contains a top-level field "type" with a value of "module".
10+
>
11+
> If the nearest parent package.json lacks a "type" field, or contains "type": "commonjs", .js files are treated as CommonJS. If the volume root is reached and no package.json is found, .js files are treated as CommonJS.
12+
13+
_[Node documentation](https://nodejs.org/api/packages.html#type)_

src/commands/bootstrap.ts

+4-12
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,10 @@ import { getWorkspaces } from '../utils/get-workspaces.js';
1010
import { rewriteCodeImports } from '../utils/rewrite-code-imports.js';
1111

1212
/** The default bob fields that should be within a package.json */
13-
export const presetFields = Object.freeze({
13+
export const presetFieldsDual = Object.freeze({
1414
type: 'module',
15-
main: 'dist/cjs/index.js',
16-
module: 'dist/esm/index.js',
15+
main: 'dist/esm/index.js',
1716
typings: 'dist/typings/index.d.ts',
18-
typescript: {
19-
definition: 'dist/typings/index.d.ts',
20-
},
2117
exports: {
2218
'.': {
2319
require: {
@@ -42,14 +38,10 @@ export const presetFields = Object.freeze({
4238
},
4339
});
4440

45-
export const presetFieldsESM = {
41+
export const presetFieldsOnlyESM = {
4642
type: 'module',
4743
main: 'dist/esm/index.js',
48-
module: 'dist/esm/index.js',
4944
typings: 'dist/typings/index.d.ts',
50-
typescript: {
51-
definition: 'dist/typings/index.d.ts',
52-
},
5345
exports: {
5446
'.': {
5547
import: {
@@ -93,7 +85,7 @@ async function applyPackageJSONPresetConfig(
9385
packageJSONPath: string,
9486
packageJSON: Record<string, unknown>,
9587
) {
96-
Object.assign(packageJSON, presetFields);
88+
Object.assign(packageJSON, presetFieldsDual);
9789
await fse.writeFile(packageJSONPath, JSON.stringify(packageJSON, null, 2));
9890
}
9991

src/commands/build.ts

+22-42
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { getRootPackageJSON } from '../utils/get-root-package-json.js';
1313
import { getWorkspacePackagePaths } from '../utils/get-workspace-package-paths.js';
1414
import { getWorkspaces } from '../utils/get-workspaces.js';
1515
import { rewriteExports } from '../utils/rewrite-exports.js';
16-
import { presetFields, presetFieldsESM } from './bootstrap.js';
16+
import { presetFieldsDual, presetFieldsOnlyESM } from './bootstrap.js';
1717

1818
export const DIST_DIR = 'dist';
1919

@@ -243,9 +243,9 @@ async function build({
243243
return;
244244
}
245245

246-
validatePackageJson(pkg, {
247-
includesCommonJS: config?.commonjs ?? true,
248-
});
246+
const dual = config?.commonjs ?? true;
247+
248+
validatePackageJson(pkg, { dual });
249249

250250
const declarations = await globby('**/*.d.ts', {
251251
cwd: getBuildPath('esm'),
@@ -285,7 +285,7 @@ async function build({
285285
),
286286
);
287287

288-
if (config?.commonjs === undefined) {
288+
if (dual) {
289289
// Transpile ESM to CJS and move CJS to dist/cjs only if there's something to transpile
290290
await fse.ensureDir(join(distPath, 'cjs'));
291291

@@ -385,9 +385,7 @@ function rewritePackageJson(pkg: Record<string, any>) {
385385
'engines',
386386
'name',
387387
'main',
388-
'module',
389388
'typings',
390-
'typescript',
391389
'type',
392390
];
393391

@@ -409,14 +407,10 @@ function rewritePackageJson(pkg: Record<string, any>) {
409407
const distDirStr = `${DIST_DIR}/`;
410408

411409
newPkg.main = newPkg.main.replace(distDirStr, '');
412-
newPkg.module = newPkg.module.replace(distDirStr, '');
413410
newPkg.typings = newPkg.typings.replace(distDirStr, '');
414-
newPkg.typescript = {
415-
definition: newPkg.typescript.definition.replace(distDirStr, ''),
416-
};
417411

418412
if (!pkg.exports) {
419-
newPkg.exports = presetFields.exports;
413+
newPkg.exports = presetFieldsDual.exports;
420414
}
421415
newPkg.exports = rewriteExports(pkg.exports, DIST_DIR);
422416

@@ -434,7 +428,7 @@ function rewritePackageJson(pkg: Record<string, any>) {
434428
export function validatePackageJson(
435429
pkg: any,
436430
opts: {
437-
includesCommonJS: boolean;
431+
dual: boolean;
438432
},
439433
) {
440434
function expect(key: string, expected: unknown) {
@@ -454,43 +448,29 @@ export function validatePackageJson(
454448
// 2. have an exports property
455449
// 3. have an exports and bin property
456450
if (Object.keys(pkg.bin ?? {}).length > 0) {
457-
if (opts.includesCommonJS === true) {
458-
expect('main', presetFields.main);
459-
expect('module', presetFields.module);
460-
expect('typings', presetFields.typings);
461-
expect('typescript.definition', presetFields.typescript.definition);
451+
if (opts.dual === true) {
452+
expect('main', presetFieldsDual.main);
453+
expect('typings', presetFieldsDual.typings);
462454
} else {
463-
expect('main', presetFieldsESM.main);
464-
expect('module', presetFieldsESM.module);
465-
expect('typings', presetFieldsESM.typings);
466-
expect('typescript.definition', presetFieldsESM.typescript.definition);
455+
expect('main', presetFieldsOnlyESM.main);
456+
expect('typings', presetFieldsOnlyESM.typings);
467457
}
468-
} else if (
469-
pkg.main !== undefined ||
470-
pkg.module !== undefined ||
471-
pkg.exports !== undefined ||
472-
pkg.typings !== undefined ||
473-
pkg.typescript !== undefined
474-
) {
475-
if (opts.includesCommonJS === true) {
458+
} else if (pkg.main !== undefined || pkg.exports !== undefined || pkg.typings !== undefined) {
459+
if (opts.dual === true) {
476460
// if there is no bin property, we NEED to check the exports.
477-
expect('main', presetFields.main);
478-
expect('module', presetFields.module);
479-
expect('typings', presetFields.typings);
480-
expect('typescript.definition', presetFields.typescript.definition);
461+
expect('main', presetFieldsDual.main);
462+
expect('typings', presetFieldsDual.typings);
481463

482464
// For now we enforce a top level exports property
483-
expect("exports['.'].require", presetFields.exports['.'].require);
484-
expect("exports['.'].import", presetFields.exports['.'].import);
485-
expect("exports['.'].default", presetFields.exports['.'].default);
465+
expect("exports['.'].require", presetFieldsDual.exports['.'].require);
466+
expect("exports['.'].import", presetFieldsDual.exports['.'].import);
467+
expect("exports['.'].default", presetFieldsDual.exports['.'].default);
486468
} else {
487-
expect('main', presetFieldsESM.main);
488-
expect('module', presetFieldsESM.module);
489-
expect('typings', presetFieldsESM.typings);
490-
expect('typescript.definition', presetFieldsESM.typescript.definition);
469+
expect('main', presetFieldsOnlyESM.main);
470+
expect('typings', presetFieldsOnlyESM.typings);
491471

492472
// For now, we enforce a top level exports property
493-
expect("exports['.']", presetFieldsESM.exports['.']);
473+
expect("exports['.']", presetFieldsOnlyESM.exports['.']);
494474
}
495475
}
496476
}

src/commands/check.ts

+22-25
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { getBobConfig } from '../config.js';
1010
import { getRootPackageJSON } from '../utils/get-root-package-json.js';
1111
import { getWorkspacePackagePaths } from '../utils/get-workspace-package-paths.js';
1212
import { getWorkspaces } from '../utils/get-workspaces.js';
13-
import { presetFields } from './bootstrap.js';
13+
import { presetFieldsDual } from './bootstrap.js';
1414

1515
const ExportsMapEntry = zod.object({
1616
default: zod.string(),
@@ -93,7 +93,7 @@ export const checkCommand = createCommand<{}, {}>(api => {
9393
cwd: path.join(cwd, 'dist'),
9494
packageJSON: distPackageJSON,
9595
skipExports: new Set<string>(config?.check?.skip ?? []),
96-
includesCommonJS: config?.commonjs ?? true,
96+
dual: config?.commonjs ?? true,
9797
});
9898
await checkEngines({
9999
packageJSON: distPackageJSON,
@@ -119,19 +119,19 @@ async function checkExportsMapIntegrity(args: {
119119
cwd: string;
120120
packageJSON: {
121121
name: string;
122-
exports: unknown;
122+
exports: any;
123123
bin: unknown;
124124
};
125125
skipExports: Set<string>;
126-
includesCommonJS: boolean;
126+
dual: boolean;
127127
}) {
128128
const exportsMapResult = ExportsMapModel.safeParse(args.packageJSON['exports']);
129129
if (exportsMapResult.success === false) {
130130
throw new Error(
131131
"Missing exports map within the 'package.json'.\n" +
132132
exportsMapResult.error.message +
133133
'\nCorrect Example:\n' +
134-
JSON.stringify(presetFields.exports, null, 2),
134+
JSON.stringify(presetFieldsDual.exports, null, 2),
135135
);
136136
}
137137

@@ -140,7 +140,7 @@ async function checkExportsMapIntegrity(args: {
140140
const cjsSkipExports = new Set<string>();
141141
const esmSkipExports = new Set<string>();
142142
for (const definedExport of args.skipExports) {
143-
if (args.includesCommonJS) {
143+
if (args.dual) {
144144
const cjsResult = resolve.resolve(args.packageJSON, definedExport, {
145145
require: true,
146146
})?.[0];
@@ -155,7 +155,7 @@ async function checkExportsMapIntegrity(args: {
155155
}
156156

157157
for (const key of Object.keys(exportsMap)) {
158-
if (args.includesCommonJS) {
158+
if (args.dual) {
159159
const cjsResult = resolve.resolve(args.packageJSON, key, {
160160
require: true,
161161
})?.[0];
@@ -208,9 +208,8 @@ async function checkExportsMapIntegrity(args: {
208208
}
209209

210210
const esmResult = resolve.resolve({ exports: exportsMap }, key)?.[0];
211-
212211
if (!esmResult) {
213-
throw new Error(`Could not resolve CommonJS import '${key}' for '${args.packageJSON.name}'.`);
212+
throw new Error(`Could not resolve export '${key}' in '${args.packageJSON.name}'.`);
214213
}
215214

216215
if (esmResult.match(/.(js|mjs)$/)) {
@@ -247,48 +246,46 @@ async function checkExportsMapIntegrity(args: {
247246
}
248247
}
249248

250-
const legacyRequire = resolve.legacy(args.packageJSON, {
251-
fields: ['main'],
252-
});
253-
if (!legacyRequire || typeof legacyRequire !== 'string') {
254-
throw new Error(`Could not resolve legacy CommonJS entrypoint.`);
249+
const exportsRequirePath = resolve.resolve({ exports: exportsMap }, '.', { require: true })?.[0];
250+
if (!exportsRequirePath || typeof exportsRequirePath !== 'string') {
251+
throw new Error('Could not resolve default CommonJS entrypoint in a Module project.');
255252
}
256253

257-
if (args.includesCommonJS) {
258-
const legacyRequireResult = await runRequireJSFileCommand({
259-
path: legacyRequire,
254+
if (args.dual) {
255+
const requireResult = await runRequireJSFileCommand({
256+
path: exportsRequirePath,
260257
cwd: args.cwd,
261258
});
262259

263-
if (legacyRequireResult.exitCode !== 0) {
260+
if (requireResult.exitCode !== 0) {
264261
throw new Error(
265-
`Require of file '${legacyRequire}' failed with error:\n` + legacyRequireResult.stderr,
262+
`Require of file '${exportsRequirePath}' failed with error:\n` + requireResult.stderr,
266263
);
267264
}
268265
} else {
269-
const legacyRequireResult = await runImportJSFileCommand({
270-
path: legacyRequire,
266+
const importResult = await runImportJSFileCommand({
267+
path: exportsRequirePath,
271268
cwd: args.cwd,
272269
});
273270

274-
if (legacyRequireResult.exitCode !== 0) {
271+
if (importResult.exitCode !== 0) {
275272
throw new Error(
276-
`Require of file '${legacyRequire}' failed with error:\n` + legacyRequireResult.stderr,
273+
`Import of file '${exportsRequirePath}' failed with error:\n` + importResult.stderr,
277274
);
278275
}
279276
}
280277

281278
const legacyImport = resolve.legacy(args.packageJSON);
282279
if (!legacyImport || typeof legacyImport !== 'string') {
283-
throw new Error(`Could not resolve legacy ESM entrypoint.`);
280+
throw new Error('Could not resolve default ESM entrypoint.');
284281
}
285282
const legacyImportResult = await runImportJSFileCommand({
286283
path: legacyImport,
287284
cwd: args.cwd,
288285
});
289286
if (legacyImportResult.exitCode !== 0) {
290287
throw new Error(
291-
`Require of file '${legacyRequire}' failed with error:\n` + legacyImportResult.stderr,
288+
`Require of file '${exportsRequirePath}' failed with error:\n` + legacyImportResult.stderr,
292289
);
293290
}
294291

src/config.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ const BobConfigModel = zod.optional(
55
[
66
zod.literal(false),
77
zod.object({
8-
commonjs: zod.optional(zod.literal(false), {
9-
description: 'Omit CommonJS output.',
10-
}),
8+
commonjs: zod
9+
.boolean({
10+
description:
11+
'Enable CommonJS output creating a dual output ESM+CJS. If set to `false`, will generate only ESM output.',
12+
})
13+
.optional()
14+
.default(true),
1115
build: zod.union(
1216
[
1317
zod.literal(false),

test/__fixtures__/simple-esm-only/package.json

-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"node": ">= 14.0.0"
66
},
77
"main": "dist/esm/index.js",
8-
"module": "dist/esm/index.js",
98
"exports": {
109
".": {
1110
"import": {
@@ -26,8 +25,5 @@
2625
},
2726
"bob": {
2827
"commonjs": false
29-
},
30-
"typescript": {
31-
"definition": "dist/typings/index.d.ts"
3228
}
3329
}

test/__fixtures__/simple-exports/package.json

+1-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
"node": ">= 12.0.0",
66
"pnpm": ">= 8.0.0"
77
},
8-
"main": "dist/cjs/index.js",
9-
"module": "dist/esm/index.js",
8+
"main": "dist/esm/index.js",
109
"exports": {
1110
".": {
1211
"require": {
@@ -42,8 +41,5 @@
4241
"publishConfig": {
4342
"directory": "dist",
4443
"access": "public"
45-
},
46-
"typescript": {
47-
"definition": "dist/typings/index.d.ts"
4844
}
4945
}

test/__fixtures__/simple-monorepo-pnpm/packages/a/package.json

+1-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
"engines": {
55
"node": ">= 14.0.0"
66
},
7-
"main": "dist/cjs/index.js",
8-
"module": "dist/esm/index.js",
7+
"main": "dist/esm/index.js",
98
"exports": {
109
".": {
1110
"require": {
@@ -44,8 +43,5 @@
4443
},
4544
"buildOptions": {
4645
"input": "./src/index.ts"
47-
},
48-
"typescript": {
49-
"definition": "dist/typings/index.d.ts"
5046
}
5147
}

0 commit comments

Comments
 (0)