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
7 changes: 7 additions & 0 deletions .changeset/bright-drinks-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'bob-the-bundler': major
---

Drop "module" package.json field

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).
5 changes: 5 additions & 0 deletions .changeset/rare-lemons-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'bob-the-bundler': major
---

Drop "typescript" package.json field
13 changes: 13 additions & 0 deletions .changeset/wise-tigers-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'bob-the-bundler': major
---

"main" package.json field matches the location of "type" output

> 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.
>
> 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".
>
> 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.

_[Node documentation](https://nodejs.org/api/packages.html#type)_
16 changes: 4 additions & 12 deletions src/commands/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@ import { getWorkspaces } from '../utils/get-workspaces.js';
import { rewriteCodeImports } from '../utils/rewrite-code-imports.js';

/** The default bob fields that should be within a package.json */
export const presetFields = Object.freeze({
export const presetFieldsDual = Object.freeze({
type: 'module',
main: 'dist/cjs/index.js',
module: 'dist/esm/index.js',
main: 'dist/esm/index.js',
typings: 'dist/typings/index.d.ts',
typescript: {
definition: 'dist/typings/index.d.ts',
},
exports: {
'.': {
require: {
Expand All @@ -42,14 +38,10 @@ export const presetFields = Object.freeze({
},
});

export const presetFieldsESM = {
export const presetFieldsOnlyESM = {
type: 'module',
main: 'dist/esm/index.js',
module: 'dist/esm/index.js',
typings: 'dist/typings/index.d.ts',
typescript: {
definition: 'dist/typings/index.d.ts',
},
exports: {
'.': {
import: {
Expand Down Expand Up @@ -93,7 +85,7 @@ async function applyPackageJSONPresetConfig(
packageJSONPath: string,
packageJSON: Record<string, unknown>,
) {
Object.assign(packageJSON, presetFields);
Object.assign(packageJSON, presetFieldsDual);
await fse.writeFile(packageJSONPath, JSON.stringify(packageJSON, null, 2));
}

Expand Down
64 changes: 22 additions & 42 deletions src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getRootPackageJSON } from '../utils/get-root-package-json.js';
import { getWorkspacePackagePaths } from '../utils/get-workspace-package-paths.js';
import { getWorkspaces } from '../utils/get-workspaces.js';
import { rewriteExports } from '../utils/rewrite-exports.js';
import { presetFields, presetFieldsESM } from './bootstrap.js';
import { presetFieldsDual, presetFieldsOnlyESM } from './bootstrap.js';

export const DIST_DIR = 'dist';

Expand Down Expand Up @@ -227,9 +227,9 @@ async function build({
return;
}

validatePackageJson(pkg, {
includesCommonJS: config?.commonjs ?? true,
});
const dual = config?.commonjs ?? true;

validatePackageJson(pkg, { dual });

const declarations = await globby('**/*.d.ts', {
cwd: getBuildPath('esm'),
Expand Down Expand Up @@ -269,7 +269,7 @@ async function build({
),
);

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

Expand Down Expand Up @@ -369,9 +369,7 @@ function rewritePackageJson(pkg: Record<string, any>) {
'engines',
'name',
'main',
'module',
'typings',
'typescript',
'type',
];

Expand All @@ -393,14 +391,10 @@ function rewritePackageJson(pkg: Record<string, any>) {
const distDirStr = `${DIST_DIR}/`;

newPkg.main = newPkg.main.replace(distDirStr, '');
newPkg.module = newPkg.module.replace(distDirStr, '');
newPkg.typings = newPkg.typings.replace(distDirStr, '');
newPkg.typescript = {
definition: newPkg.typescript.definition.replace(distDirStr, ''),
};

if (!pkg.exports) {
newPkg.exports = presetFields.exports;
newPkg.exports = presetFieldsDual.exports;
}
newPkg.exports = rewriteExports(pkg.exports, DIST_DIR);

Expand All @@ -418,7 +412,7 @@ function rewritePackageJson(pkg: Record<string, any>) {
export function validatePackageJson(
pkg: any,
opts: {
includesCommonJS: boolean;
dual: boolean;
},
) {
function expect(key: string, expected: unknown) {
Expand All @@ -438,43 +432,29 @@ export function validatePackageJson(
// 2. have an exports property
// 3. have an exports and bin property
if (Object.keys(pkg.bin ?? {}).length > 0) {
if (opts.includesCommonJS === true) {
expect('main', presetFields.main);
expect('module', presetFields.module);
expect('typings', presetFields.typings);
expect('typescript.definition', presetFields.typescript.definition);
if (opts.dual === true) {
expect('main', presetFieldsDual.main);
expect('typings', presetFieldsDual.typings);
} else {
expect('main', presetFieldsESM.main);
expect('module', presetFieldsESM.module);
expect('typings', presetFieldsESM.typings);
expect('typescript.definition', presetFieldsESM.typescript.definition);
expect('main', presetFieldsOnlyESM.main);
expect('typings', presetFieldsOnlyESM.typings);
}
} else if (
pkg.main !== undefined ||
pkg.module !== undefined ||
pkg.exports !== undefined ||
pkg.typings !== undefined ||
pkg.typescript !== undefined
) {
if (opts.includesCommonJS === true) {
} else if (pkg.main !== undefined || pkg.exports !== undefined || pkg.typings !== undefined) {
if (opts.dual === true) {
// if there is no bin property, we NEED to check the exports.
expect('main', presetFields.main);
expect('module', presetFields.module);
expect('typings', presetFields.typings);
expect('typescript.definition', presetFields.typescript.definition);
expect('main', presetFieldsDual.main);
expect('typings', presetFieldsDual.typings);

// For now we enforce a top level exports property
expect("exports['.'].require", presetFields.exports['.'].require);
expect("exports['.'].import", presetFields.exports['.'].import);
expect("exports['.'].default", presetFields.exports['.'].default);
expect("exports['.'].require", presetFieldsDual.exports['.'].require);
expect("exports['.'].import", presetFieldsDual.exports['.'].import);
expect("exports['.'].default", presetFieldsDual.exports['.'].default);
} else {
expect('main', presetFieldsESM.main);
expect('module', presetFieldsESM.module);
expect('typings', presetFieldsESM.typings);
expect('typescript.definition', presetFieldsESM.typescript.definition);
expect('main', presetFieldsOnlyESM.main);
expect('typings', presetFieldsOnlyESM.typings);

// For now, we enforce a top level exports property
expect("exports['.']", presetFieldsESM.exports['.']);
expect("exports['.']", presetFieldsOnlyESM.exports['.']);
}
}
}
Expand Down
47 changes: 22 additions & 25 deletions src/commands/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getBobConfig } from '../config.js';
import { getRootPackageJSON } from '../utils/get-root-package-json.js';
import { getWorkspacePackagePaths } from '../utils/get-workspace-package-paths.js';
import { getWorkspaces } from '../utils/get-workspaces.js';
import { presetFields } from './bootstrap.js';
import { presetFieldsDual } from './bootstrap.js';

const ExportsMapEntry = zod.object({
default: zod.string(),
Expand Down Expand Up @@ -93,7 +93,7 @@ export const checkCommand = createCommand<{}, {}>(api => {
cwd: path.join(cwd, 'dist'),
packageJSON: distPackageJSON,
skipExports: new Set<string>(config?.check?.skip ?? []),
includesCommonJS: config?.commonjs ?? true,
dual: config?.commonjs ?? true,
});
await checkEngines({
packageJSON: distPackageJSON,
Expand All @@ -119,19 +119,19 @@ async function checkExportsMapIntegrity(args: {
cwd: string;
packageJSON: {
name: string;
exports: unknown;
exports: any;
bin: unknown;
};
skipExports: Set<string>;
includesCommonJS: boolean;
dual: boolean;
}) {
const exportsMapResult = ExportsMapModel.safeParse(args.packageJSON['exports']);
if (exportsMapResult.success === false) {
throw new Error(
"Missing exports map within the 'package.json'.\n" +
exportsMapResult.error.message +
'\nCorrect Example:\n' +
JSON.stringify(presetFields.exports, null, 2),
JSON.stringify(presetFieldsDual.exports, null, 2),
);
}

Expand All @@ -140,7 +140,7 @@ async function checkExportsMapIntegrity(args: {
const cjsSkipExports = new Set<string>();
const esmSkipExports = new Set<string>();
for (const definedExport of args.skipExports) {
if (args.includesCommonJS) {
if (args.dual) {
const cjsResult = resolve.resolve(args.packageJSON, definedExport, {
require: true,
})?.[0];
Expand All @@ -155,7 +155,7 @@ async function checkExportsMapIntegrity(args: {
}

for (const key of Object.keys(exportsMap)) {
if (args.includesCommonJS) {
if (args.dual) {
const cjsResult = resolve.resolve(args.packageJSON, key, {
require: true,
})?.[0];
Expand Down Expand Up @@ -208,9 +208,8 @@ async function checkExportsMapIntegrity(args: {
}

const esmResult = resolve.resolve({ exports: exportsMap }, key)?.[0];

if (!esmResult) {
throw new Error(`Could not resolve CommonJS import '${key}' for '${args.packageJSON.name}'.`);
throw new Error(`Could not resolve export '${key}' in '${args.packageJSON.name}'.`);
}

if (esmResult.match(/.(js|mjs)$/)) {
Expand Down Expand Up @@ -247,48 +246,46 @@ async function checkExportsMapIntegrity(args: {
}
}

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

if (args.includesCommonJS) {
const legacyRequireResult = await runRequireJSFileCommand({
path: legacyRequire,
if (args.dual) {
const requireResult = await runRequireJSFileCommand({
path: exportsRequirePath,
cwd: args.cwd,
});

if (legacyRequireResult.exitCode !== 0) {
if (requireResult.exitCode !== 0) {
throw new Error(
`Require of file '${legacyRequire}' failed with error:\n` + legacyRequireResult.stderr,
`Require of file '${exportsRequirePath}' failed with error:\n` + requireResult.stderr,
);
}
} else {
const legacyRequireResult = await runImportJSFileCommand({
path: legacyRequire,
const importResult = await runImportJSFileCommand({
path: exportsRequirePath,
cwd: args.cwd,
});

if (legacyRequireResult.exitCode !== 0) {
if (importResult.exitCode !== 0) {
throw new Error(
`Require of file '${legacyRequire}' failed with error:\n` + legacyRequireResult.stderr,
`Import of file '${exportsRequirePath}' failed with error:\n` + importResult.stderr,
);
}
}

const legacyImport = resolve.legacy(args.packageJSON);
if (!legacyImport || typeof legacyImport !== 'string') {
throw new Error(`Could not resolve legacy ESM entrypoint.`);
throw new Error('Could not resolve default ESM entrypoint.');
}
const legacyImportResult = await runImportJSFileCommand({
path: legacyImport,
cwd: args.cwd,
});
if (legacyImportResult.exitCode !== 0) {
throw new Error(
`Require of file '${legacyRequire}' failed with error:\n` + legacyImportResult.stderr,
`Require of file '${exportsRequirePath}' failed with error:\n` + legacyImportResult.stderr,
);
}

Expand Down
10 changes: 7 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ const BobConfigModel = zod.optional(
[
zod.literal(false),
zod.object({
commonjs: zod.optional(zod.literal(false), {
description: 'Omit CommonJS output.',
}),
commonjs: zod
.boolean({
description:
'Enable CommonJS output creating a dual output ESM+CJS. If set to `false`, will generate only ESM output.',
})
.optional()
.default(true),
build: zod.union(
[
zod.literal(false),
Expand Down
4 changes: 0 additions & 4 deletions test/__fixtures__/simple-esm-only/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"node": ">= 14.0.0"
},
"main": "dist/esm/index.js",
"module": "dist/esm/index.js",
"exports": {
".": {
"import": {
Expand All @@ -26,8 +25,5 @@
},
"bob": {
"commonjs": false
},
"typescript": {
"definition": "dist/typings/index.d.ts"
}
}
6 changes: 1 addition & 5 deletions test/__fixtures__/simple-exports/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"node": ">= 12.0.0",
"pnpm": ">= 8.0.0"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"main": "dist/esm/index.js",
"exports": {
".": {
"require": {
Expand Down Expand Up @@ -42,8 +41,5 @@
"publishConfig": {
"directory": "dist",
"access": "public"
},
"typescript": {
"definition": "dist/typings/index.d.ts"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
"engines": {
"node": ">= 14.0.0"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"main": "dist/esm/index.js",
"exports": {
".": {
"require": {
Expand Down Expand Up @@ -44,8 +43,5 @@
},
"buildOptions": {
"input": "./src/index.ts"
},
"typescript": {
"definition": "dist/typings/index.d.ts"
}
}
Loading
Loading