@@ -223,53 +223,69 @@ async function build({
223223 return;
224224 }
225225
226- validatePackageJson(pkg, config?.commonjs ?? true);
227-
228- // remove <project>/dist
229- await fse.remove(distPath);
230-
231- // Copy type definitions
232- await fse.ensureDir(join(distPath, "typings"));
233-
234226 const declarations = await globby("**/*.d.ts", {
235227 cwd: getBuildPath("esm"),
236228 absolute: false,
237229 ignore: filesToExcludeFromDist,
238230 });
239231
240- await Promise.all(
241- declarations.map((filePath) =>
242- limit(() =>
243- fse.copy(
244- join(getBuildPath("esm"), filePath),
245- join(distPath, "typings", filePath)
246- )
247- )
248- )
249- );
250-
251- // Move ESM to dist/esm
252- await fse.ensureDir(join(distPath, "esm"));
253-
254232 const esmFiles = await globby("**/*.js", {
255233 cwd: getBuildPath("esm"),
256234 absolute: false,
257235 ignore: filesToExcludeFromDist,
258236 });
259237
238+ // Check whether al esm files are empty, if not - probably a types only build
239+ let emptyEsmFiles = true;
240+ for (const file of esmFiles) {
241+ const src = await fse.readFile(join(getBuildPath("esm"), file));
242+ if (src.toString().trim() !== "export {};") {
243+ emptyEsmFiles = false;
244+ break;
245+ }
246+ }
247+
248+ // Empty ESM files with existing declarations is a types-only package
249+ const typesOnly = emptyEsmFiles && declarations.length > 0;
250+
251+ validatePackageJson(pkg, {
252+ typesOnly,
253+ includesCommonJS: config?.commonjs ?? true,
254+ });
255+
256+ // remove <project>/dist
257+ await fse.remove(distPath);
258+
259+ // Copy type definitions
260+ await fse.ensureDir(join(distPath, "typings"));
260261 await Promise.all(
261- esmFiles .map((filePath) =>
262+ declarations .map((filePath) =>
262263 limit(() =>
263264 fse.copy(
264265 join(getBuildPath("esm"), filePath),
265- join(distPath, "esm ", filePath)
266+ join(distPath, "typings ", filePath)
266267 )
267268 )
268269 )
269270 );
270271
271- if (config?.commonjs === undefined) {
272- // Transpile ESM to CJS and move CJS to dist/cjs
272+ // If ESM files are not empty, copy them to dist/esm
273+ if (!emptyEsmFiles) {
274+ await fse.ensureDir(join(distPath, "esm"));
275+ await Promise.all(
276+ esmFiles.map((filePath) =>
277+ limit(() =>
278+ fse.copy(
279+ join(getBuildPath("esm"), filePath),
280+ join(distPath, "esm", filePath)
281+ )
282+ )
283+ )
284+ );
285+ }
286+
287+ if (!emptyEsmFiles && config?.commonjs === undefined) {
288+ // Transpile ESM to CJS and move CJS to dist/cjs only if there's something to transpile
273289 await fse.ensureDir(join(distPath, "cjs"));
274290
275291 const cjsFiles = await globby("**/*.js", {
@@ -323,8 +339,9 @@ async function build({
323339 // move the package.json to dist
324340 await fse.writeFile(
325341 join(distPath, "package.json"),
326- JSON.stringify(rewritePackageJson(pkg), null, 2)
342+ JSON.stringify(rewritePackageJson(pkg, typesOnly ), null, 2)
327343 );
344+
328345 // move README.md and LICENSE and other specified files
329346 await copyToDist(
330347 cwd,
@@ -350,7 +367,7 @@ async function build({
350367 reporter.success(`Built ${pkg.name}`);
351368}
352369
353- function rewritePackageJson(pkg: Record<string, any>) {
370+ function rewritePackageJson(pkg: Record<string, any>, typesOnly: boolean ) {
354371 const newPkg: Record<string, any> = {};
355372 const fields = [
356373 "name",
@@ -382,19 +399,26 @@ function rewritePackageJson(pkg: Record<string, any>) {
382399
383400 const distDirStr = `${DIST_DIR}/`;
384401
385- newPkg.main = newPkg.main.replace(distDirStr, "");
386- newPkg.module = newPkg.module.replace(distDirStr, "");
402+ if (typesOnly) {
403+ newPkg.main = "";
404+ delete newPkg.module;
405+ delete newPkg.type;
406+ } else {
407+ newPkg.main = newPkg.main.replace(distDirStr, "");
408+ newPkg.module = newPkg.module.replace(distDirStr, "");
409+ }
387410 newPkg.typings = newPkg.typings.replace(distDirStr, "");
388411 newPkg.typescript = {
389412 definition: newPkg.typescript.definition.replace(distDirStr, ""),
390413 };
391414
392- if (!pkg.exports) {
393- newPkg.exports = presetFields.exports;
415+ if (!typesOnly) {
416+ if (!pkg.exports) {
417+ newPkg.exports = presetFields.exports;
418+ }
419+ newPkg.exports = rewriteExports(pkg.exports, DIST_DIR);
394420 }
395421
396- newPkg.exports = rewriteExports(pkg.exports, DIST_DIR);
397-
398422 if (pkg.bin) {
399423 newPkg.bin = {};
400424
@@ -406,7 +430,13 @@ function rewritePackageJson(pkg: Record<string, any>) {
406430 return newPkg;
407431}
408432
409- export function validatePackageJson(pkg: any, includesCommonJS: boolean) {
433+ export function validatePackageJson(
434+ pkg: any,
435+ opts: {
436+ typesOnly: boolean;
437+ includesCommonJS: boolean;
438+ }
439+ ) {
410440 function expect(key: string, expected: unknown) {
411441 const received = get(pkg, key);
412442
@@ -418,13 +448,23 @@ export function validatePackageJson(pkg: any, includesCommonJS: boolean) {
418448 );
419449 }
420450
451+ // Type only packages have simpler rules (following the style of https://github.com/DefinitelyTyped/DefinitelyTyped packages)
452+ if (opts.typesOnly) {
453+ expect("main", "");
454+ expect("module", undefined);
455+ expect("typings", presetFields.typings);
456+ expect("typescript.definition", presetFields.typescript.definition);
457+ expect("exports", undefined);
458+ return;
459+ }
460+
421461 // If the package has NO binary we need to check the exports map.
422462 // a package should either
423463 // 1. have a bin property
424464 // 2. have a exports property
425465 // 3. have an exports and bin property
426466 if (Object.keys(pkg.bin ?? {}).length > 0) {
427- if (includesCommonJS === true) {
467+ if (opts. includesCommonJS === true) {
428468 expect("main", presetFields.main);
429469 expect("module", presetFields.module);
430470 expect("typings", presetFields.typings);
@@ -442,7 +482,7 @@ export function validatePackageJson(pkg: any, includesCommonJS: boolean) {
442482 pkg.typings !== undefined ||
443483 pkg.typescript !== undefined
444484 ) {
445- if (includesCommonJS === true) {
485+ if (opts. includesCommonJS === true) {
446486 // if there is no bin property, we NEED to check the exports.
447487 expect("main", presetFields.main);
448488 expect("module", presetFields.module);
0 commit comments