Skip to content

Commit 14fa965

Browse files
authored
feat: omit commonjs mode (#112)
* feat: disable commonjs output * chore: add changeset
1 parent fa80a54 commit 14fa965

File tree

10 files changed

+260
-106
lines changed

10 files changed

+260
-106
lines changed

.changeset/brown-deers-hammer.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"bob-the-bundler": minor
3+
---
4+
5+
Disable commonjs output via package.json
6+
7+
```json
8+
{
9+
"name": "my-package",
10+
"bob": {
11+
"commonjs": false
12+
}
13+
}
14+
```

src/commands/bootstrap.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,32 @@ export const presetFields = Object.freeze({
3434
default: "./dist/esm/index.js",
3535
},
3636
},
37-
"./*": {
38-
require: {
39-
types: "./dist/typings/*.d.ts",
40-
default: "./dist/cjs/*.js",
41-
},
37+
"./package.json": "./package.json",
38+
},
39+
publishConfig: {
40+
directory: "dist",
41+
access: "public",
42+
},
43+
});
44+
45+
export const presetFieldsESM = {
46+
type: "module",
47+
main: "dist/esm/index.js",
48+
module: "dist/esm/index.js",
49+
typings: "dist/typings/index.d.ts",
50+
typescript: {
51+
definition: "dist/typings/index.d.ts",
52+
},
53+
exports: {
54+
".": {
4255
import: {
43-
types: "./dist/typings/*.d.ts",
44-
default: "./dist/esm/*.js",
56+
types: "./dist/typings/index.d.ts",
57+
default: "./dist/esm/index.js",
4558
},
4659
/** without this default (THAT MUST BE LAST!!!) webpack will have a midlife crisis. */
4760
default: {
48-
types: "./dist/typings/*.d.ts",
49-
default: "./dist/esm/*.js",
61+
types: "./dist/typings/index.d.ts",
62+
default: "./dist/esm/index.js",
5063
},
5164
},
5265
"./package.json": "./package.json",
@@ -55,7 +68,7 @@ export const presetFields = Object.freeze({
5568
directory: "dist",
5669
access: "public",
5770
},
58-
});
71+
};
5972

6073
async function applyESMModuleTransform(cwd: string) {
6174
const filePaths = await globby("**/*.ts", {

src/commands/build.ts

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { getWorkspaces } from "../utils/get-workspaces";
1313
import { createCommand } from "../command";
1414
import { getBobConfig } from "../config";
1515
import { rewriteExports } from "../utils/rewrite-exports";
16-
import { presetFields } from "./bootstrap";
16+
import { presetFields, presetFieldsESM } from "./bootstrap";
1717
import { getWorkspacePackagePaths } from "../utils/get-workspace-package-paths";
1818

1919
export const DIST_DIR = "dist";
@@ -201,7 +201,7 @@ async function build({
201201
return;
202202
}
203203

204-
validatePackageJson(pkg);
204+
validatePackageJson(pkg, config?.commonjs ?? true);
205205

206206
// remove <project>/dist
207207
await fse.remove(distPath);
@@ -246,31 +246,33 @@ async function build({
246246
)
247247
);
248248

249-
// Transpile ESM to CJS and move CJS to dist/cjs
250-
await fse.ensureDir(join(distPath, "cjs"));
251-
252-
const cjsFiles = await globby("**/*.js", {
253-
cwd: getBuildPath("cjs"),
254-
absolute: false,
255-
ignore: filesToExcludeFromDist,
256-
});
257-
258-
await Promise.all(
259-
cjsFiles.map((filePath) =>
260-
limit(() =>
261-
fse.copy(
262-
join(getBuildPath("cjs"), filePath),
263-
join(distPath, "cjs", filePath)
249+
if (config?.commonjs === undefined) {
250+
// Transpile ESM to CJS and move CJS to dist/cjs
251+
await fse.ensureDir(join(distPath, "cjs"));
252+
253+
const cjsFiles = await globby("**/*.js", {
254+
cwd: getBuildPath("cjs"),
255+
absolute: false,
256+
ignore: filesToExcludeFromDist,
257+
});
258+
259+
await Promise.all(
260+
cjsFiles.map((filePath) =>
261+
limit(() =>
262+
fse.copy(
263+
join(getBuildPath("cjs"), filePath),
264+
join(distPath, "cjs", filePath)
265+
)
264266
)
265267
)
266-
)
267-
);
268+
);
268269

269-
// Add package.json to dist/cjs to ensure files are interpreted as commonjs
270-
await fse.writeFile(
271-
join(distPath, "cjs", "package.json"),
272-
JSON.stringify({ type: "commonjs" })
273-
);
270+
// Add package.json to dist/cjs to ensure files are interpreted as commonjs
271+
await fse.writeFile(
272+
join(distPath, "cjs", "package.json"),
273+
JSON.stringify({ type: "commonjs" })
274+
);
275+
}
274276

275277
// move the package.json to dist
276278
await fse.writeFile(
@@ -358,16 +360,15 @@ function rewritePackageJson(pkg: Record<string, any>) {
358360
return newPkg;
359361
}
360362

361-
export function validatePackageJson(pkg: any) {
363+
export function validatePackageJson(pkg: any, includesCommonJS: boolean) {
362364
function expect(key: string, expected: unknown) {
363365
const received = get(pkg, key);
364366

365367
assert.deepEqual(
366368
received,
367369
expected,
368370
`${pkg.name}: "${key}" equals "${JSON.stringify(received)}"` +
369-
`, should be "${JSON.stringify(expected)}".\n` +
370-
`!!! You can run 'bob bootstrap' for fixing your package.json. !!!`
371+
`, should be "${JSON.stringify(expected)}".`
371372
);
372373
}
373374

@@ -376,28 +377,45 @@ export function validatePackageJson(pkg: any) {
376377
// 1. have a bin property
377378
// 2. have a exports property
378379
// 3. have an exports and bin property
379-
if (Object.keys(pkg.bin ?? {}).length === 0) {
380-
expect("main", presetFields.main);
381-
expect("module", presetFields.module);
382-
expect("typings", presetFields.typings);
383-
expect("typescript.definition", presetFields.typescript.definition);
380+
if (Object.keys(pkg.bin ?? {}).length > 0) {
381+
if (includesCommonJS === true) {
382+
expect("main", presetFields.main);
383+
expect("module", presetFields.module);
384+
expect("typings", presetFields.typings);
385+
expect("typescript.definition", presetFields.typescript.definition);
386+
} else {
387+
expect("main", presetFieldsESM.main);
388+
expect("module", presetFieldsESM.module);
389+
expect("typings", presetFieldsESM.typings);
390+
expect("typescript.definition", presetFieldsESM.typescript.definition);
391+
}
384392
} else if (
385393
pkg.main !== undefined ||
386394
pkg.module !== undefined ||
387395
pkg.exports !== undefined ||
388396
pkg.typings !== undefined ||
389397
pkg.typescript !== undefined
390398
) {
391-
// if there is no bin property, we NEED to check the exports.
392-
expect("main", presetFields.main);
393-
expect("module", presetFields.module);
394-
expect("typings", presetFields.typings);
395-
expect("typescript.definition", presetFields.typescript.definition);
396-
397-
// For now we enforce a top level exports property
398-
expect("exports['.'].require", presetFields.exports["."].require);
399-
expect("exports['.'].import", presetFields.exports["."].import);
400-
expect("exports['.'].default", presetFields.exports["."].default);
399+
if (includesCommonJS === true) {
400+
// if there is no bin property, we NEED to check the exports.
401+
expect("main", presetFields.main);
402+
expect("module", presetFields.module);
403+
expect("typings", presetFields.typings);
404+
expect("typescript.definition", presetFields.typescript.definition);
405+
406+
// For now we enforce a top level exports property
407+
expect("exports['.'].require", presetFields.exports["."].require);
408+
expect("exports['.'].import", presetFields.exports["."].import);
409+
expect("exports['.'].default", presetFields.exports["."].default);
410+
} else {
411+
expect("main", presetFieldsESM.main);
412+
expect("module", presetFieldsESM.module);
413+
expect("typings", presetFieldsESM.typings);
414+
expect("typescript.definition", presetFieldsESM.typescript.definition);
415+
416+
// For now we enforce a top level exports property
417+
expect("exports['.']", presetFieldsESM.exports["."]);
418+
}
401419
}
402420
}
403421

src/commands/check.ts

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const ExportsMapModel = zod.record(
2121
zod.union([
2222
zod.string(),
2323
zod.object({
24-
require: ExportsMapEntry,
24+
require: zod.optional(ExportsMapEntry),
2525
import: ExportsMapEntry,
2626
default: ExportsMapEntry,
2727
}),
@@ -93,6 +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,
9697
});
9798
} catch (err) {
9899
api.reporter.error(
@@ -121,6 +122,7 @@ async function checkExportsMapIntegrity(args: {
121122
bin: unknown;
122123
};
123124
skipExports: Set<string>;
125+
includesCommonJS: boolean;
124126
}) {
125127
const exportsMapResult = ExportsMapModel.safeParse(
126128
args.packageJSON["exports"]
@@ -139,11 +141,13 @@ async function checkExportsMapIntegrity(args: {
139141
const cjsSkipExports = new Set<string>();
140142
const esmSkipExports = new Set<string>();
141143
for (const definedExport of args.skipExports) {
142-
const cjsResult = resolve.resolve(args.packageJSON, definedExport, {
143-
require: true,
144-
});
145-
if (typeof cjsResult === "string") {
146-
cjsSkipExports.add(cjsResult);
144+
if (args.includesCommonJS) {
145+
const cjsResult = resolve.resolve(args.packageJSON, definedExport, {
146+
require: true,
147+
});
148+
if (typeof cjsResult === "string") {
149+
cjsSkipExports.add(cjsResult);
150+
}
147151
}
148152
const esmResult = resolve.resolve(args.packageJSON, definedExport);
149153
if (typeof esmResult === "string") {
@@ -152,49 +156,51 @@ async function checkExportsMapIntegrity(args: {
152156
}
153157

154158
for (const key of Object.keys(exportsMap)) {
155-
const cjsResult = resolve.resolve(args.packageJSON, key, {
156-
require: true,
157-
});
159+
if (args.includesCommonJS) {
160+
const cjsResult = resolve.resolve(args.packageJSON, key, {
161+
require: true,
162+
});
158163

159-
if (!cjsResult) {
160-
throw new Error(
161-
`Could not resolve CommonJS import '${key}' for '${args.packageJSON.name}'.`
162-
);
163-
}
164+
if (!cjsResult) {
165+
throw new Error(
166+
`Could not resolve CommonJS import '${key}' for '${args.packageJSON.name}'.`
167+
);
168+
}
164169

165-
if (cjsResult.match(/.(js|cjs)$/)) {
166-
const cjsFilePaths = await globby(cjsResult, {
167-
cwd: args.cwd,
168-
});
170+
if (cjsResult.match(/.(js|cjs)$/)) {
171+
const cjsFilePaths = await globby(cjsResult, {
172+
cwd: args.cwd,
173+
});
169174

170-
const limit = pLimit(20);
171-
await Promise.all(
172-
cjsFilePaths.map((file) =>
173-
limit(async () => {
174-
if (cjsSkipExports.has(file)) {
175-
return;
176-
}
175+
const limit = pLimit(20);
176+
await Promise.all(
177+
cjsFilePaths.map((file) =>
178+
limit(async () => {
179+
if (cjsSkipExports.has(file)) {
180+
return;
181+
}
177182

178-
const result = await runRequireJSFileCommand({
179-
path: file,
180-
cwd: args.cwd,
181-
});
183+
const result = await runRequireJSFileCommand({
184+
path: file,
185+
cwd: args.cwd,
186+
});
182187

183-
if (result.exitCode !== 0) {
184-
throw new Error(
185-
`Require of file '${file}' failed.\n` +
186-
`In case this file is expected to raise an error please add an export to the 'bob.check.skip' field in your 'package.json' file.\n` +
187-
`Error:\n` +
188-
result.stderr
189-
);
190-
}
191-
})
192-
)
193-
);
194-
} else {
195-
// package.json or other files
196-
// for now we just make sure they exists
197-
await fse.stat(path.join(args.cwd, cjsResult));
188+
if (result.exitCode !== 0) {
189+
throw new Error(
190+
`Require of file '${file}' failed.\n` +
191+
`In case this file is expected to raise an error please add an export to the 'bob.check.skip' field in your 'package.json' file.\n` +
192+
`Error:\n` +
193+
result.stderr
194+
);
195+
}
196+
})
197+
)
198+
);
199+
} else {
200+
// package.json or other files
201+
// for now we just make sure they exists
202+
await fse.stat(path.join(args.cwd, cjsResult));
203+
}
198204
}
199205

200206
const esmResult = resolve.resolve({ exports: exportsMap }, key);
@@ -242,16 +248,31 @@ async function checkExportsMapIntegrity(args: {
242248
if (!legacyRequire || typeof legacyRequire !== "string") {
243249
throw new Error(`Could not resolve legacy CommonJS entrypoint.`);
244250
}
245-
const legacyRequireResult = await runRequireJSFileCommand({
246-
path: legacyRequire,
247-
cwd: args.cwd,
248-
});
249251

250-
if (legacyRequireResult.exitCode !== 0) {
251-
throw new Error(
252-
`Require of file '${legacyRequire}' failed with error:\n` +
253-
legacyRequireResult.stderr
254-
);
252+
if (args.includesCommonJS) {
253+
const legacyRequireResult = await runRequireJSFileCommand({
254+
path: legacyRequire,
255+
cwd: args.cwd,
256+
});
257+
258+
if (legacyRequireResult.exitCode !== 0) {
259+
throw new Error(
260+
`Require of file '${legacyRequire}' failed with error:\n` +
261+
legacyRequireResult.stderr
262+
);
263+
}
264+
} else {
265+
const legacyRequireResult = await runImportJSFileCommand({
266+
path: legacyRequire,
267+
cwd: args.cwd,
268+
});
269+
270+
if (legacyRequireResult.exitCode !== 0) {
271+
throw new Error(
272+
`Require of file '${legacyRequire}' failed with error:\n` +
273+
legacyRequireResult.stderr
274+
);
275+
}
255276
}
256277

257278
const legacyImport = resolve.legacy(args.packageJSON);

0 commit comments

Comments
 (0)