Skip to content

Commit

Permalink
fix: optimize config loading of types, cjs and esm
Browse files Browse the repository at this point in the history
  • Loading branch information
prisis committed May 27, 2024
1 parent 54cf22c commit ed4368f
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 46 deletions.
36 changes: 24 additions & 12 deletions packages/packem/src/create-bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ const generateOptions = (
const options = defu(buildConfig, package_.packem, inputConfig, preset, <BuildOptions>{
alias: {},
clean: true,
declaration: false,
declaration: undefined,
dependencies: [],
devDependencies: [],
emitCJS: false,
emitESM: true,
emitCJS: undefined,
emitESM: undefined,
entries: [],
externals: [...Module.builtinModules, ...Module.builtinModules.map((m) => `node:${m}`)],
failOnWarn: true,
Expand Down Expand Up @@ -331,10 +331,6 @@ const generateOptions = (
logger.info('Using "' + cyan(options.transformerName) + '" as transformer.');
}

if (options.emitESM === false && options.emitCJS === false) {
throw new Error("Both emitESM and emitCJS are disabled. At least one of them must be enabled.");
}

// Resolve dirs relative to rootDir
options.outDir = resolve(options.rootDir, options.outDir);

Expand All @@ -350,10 +346,6 @@ const generateOptions = (
);
}

if (options.declaration && tsconfig === undefined) {
throw new Error("Cannot build declaration files without a tsconfig.json");
}

// Infer dependencies from pkg
options.dependencies = Object.keys(package_.dependencies ?? {});
options.peerDependencies = Object.keys(package_.peerDependencies ?? {});
Expand Down Expand Up @@ -387,7 +379,7 @@ const prepareEntries = async (context: BuildContext, rootDirectory: string): Pro
throw new Error(`Missing entry input: ${dumpObject(entry)}`);
}

if (context.options.declaration !== undefined && entry.declaration === undefined) {
if (!context.options.declaration && entry.declaration === undefined) {
entry.declaration = context.options.declaration;
}

Expand Down Expand Up @@ -560,6 +552,26 @@ const build = async (
// Allow to prepare and extending context
await context.hooks.callHook("build:prepare", context);

if (context.options.emitESM === false && context.options.emitCJS === false) {
throw new Error("Both emitESM and emitCJS are disabled. At least one of them must be enabled.");
}

if (context.options.declaration && tsconfig === undefined) {
throw new Error("Cannot build declaration files without a tsconfig.json");
}

if (context.options.emitESM === undefined) {
logger.info("Emitting ESM bundles, is disabled.");
}

if (context.options.emitCJS === undefined) {
logger.info("Emitting CJS bundles, is disabled.");
}

if (!context.options.declaration) {
logger.info("Declaration files, are disabled.");
}

await prepareEntries(context, rootDirectory);

// Call build:before
Expand Down
13 changes: 7 additions & 6 deletions packages/packem/src/preset/auto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import overwriteWithPublishConfig from "./utils/overwrite-with-publish-config";

const autoPreset: BuildPreset = {
hooks: {
// eslint-disable-next-line sonarjs/cognitive-complexity
"build:prepare": function (context) {
// Disable auto if entries already provided of pkg not available
if (!context.pkg || context.options.entries.length > 0) {
if (context.options.entries.length > 0) {
return;
}

Expand All @@ -35,10 +36,10 @@ const autoPreset: BuildPreset = {
let package_ = { ...context.pkg } as NormalizedPackageJson;

if (package_.publishConfig) {
package_ = overwriteWithPublishConfig(package_);
package_ = overwriteWithPublishConfig(package_, context.options.declaration);
}

const result = inferEntries(package_, sourceFiles, context.options.rootDir);
const result = inferEntries(package_, sourceFiles, context.options.declaration, context.options.rootDir);

// eslint-disable-next-line no-loops/no-loops,no-restricted-syntax
for (const message of result.warnings) {
Expand All @@ -47,15 +48,15 @@ const autoPreset: BuildPreset = {

context.options.entries.push(...result.entries);

if (result.cjs) {
if (context.options.emitCJS === undefined && result.cjs) {
context.options.emitCJS = true;
}

if (result.esm) {
if (context.options.emitESM === undefined && result.esm) {
context.options.emitESM = true;
}

if (result.dts) {
if (context.options.declaration === undefined && result.dts) {
context.options.declaration = result.dts;
}

Expand Down
12 changes: 7 additions & 5 deletions packages/packem/src/preset/utils/infer-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,26 @@ import { existsSync } from "node:fs";
import type { PackageJson } from "@visulima/package";
import { resolve } from "@visulima/path";

import type { BuildEntry, InferEntriesResult } from "../../types";
import type { BuildConfig, BuildEntry, InferEntriesResult } from "../../types";
import extractExportFilenames from "../../utils/extract-export-filenames";
import { inferExportTypeFromFileName } from "../../utils/infer-export-type";
import getEntrypointPaths from "./get-entrypoint-paths";

/**
* @param {PackageJson} packageJson The contents of a package.json file to serve as the source for inferred entries.
* @param {string[]} sourceFiles A list of source files to use for inferring entries.
* @param {BuildConfig["declaration"]} declaration Whether to emit declaration files.
* @param {string | undefined} rootDirectory The root directory of the project.
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
const inferEntries = (packageJson: PackageJson, sourceFiles: string[], rootDirectory = "."): InferEntriesResult => {
const inferEntries = (packageJson: PackageJson, sourceFiles: string[], declaration: BuildConfig["declaration"], rootDirectory = "."): InferEntriesResult => {
const warnings = [];

// Sort files so least-nested files are first
sourceFiles.sort((a, b) => a.split("/").length - b.split("/").length);

// Come up with a list of all output files & their formats
const outputs = extractExportFilenames(packageJson.exports, packageJson.type || "commonjs");
const outputs = extractExportFilenames(packageJson.exports, packageJson.type ?? "commonjs", declaration);

if (packageJson.bin) {
const binaries = typeof packageJson.bin === "string" ? [packageJson.bin] : Object.values(packageJson.bin);
Expand All @@ -43,8 +44,8 @@ const inferEntries = (packageJson: PackageJson, sourceFiles: string[], rootDirec
}

// Entry point for TypeScript
if (packageJson.types || packageJson.typings) {
outputs.push({ file: (packageJson.types || packageJson.typings) as string });
if (declaration === undefined && (packageJson.types || packageJson.typings)) {
outputs.push({ file: (packageJson.types ?? packageJson.typings) as string });
}

// Try to detect output types
Expand Down Expand Up @@ -88,6 +89,7 @@ const inferEntries = (packageJson: PackageJson, sourceFiles: string[], rootDirec
return source;
}

// eslint-disable-next-line @rushstack/security/no-unsafe-regexp,security/detect-non-literal-regexp
const SOURCE_RE = new RegExp(`(?<=/|$)${d}${isDirectory ? "" : "\\.\\w+"}$`);

return sourceFiles.find((index) => SOURCE_RE.test(index))?.replace(/(?:\.d\.(?:m|c)?ts|\.\w+)$/, "");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { NormalizedPackageJson } from "@visulima/package";
import type { BuildConfig } from "../../types";

// eslint-disable-next-line sonarjs/cognitive-complexity
const overwriteWithPublishConfig = (package_: NormalizedPackageJson): NormalizedPackageJson => {
const overwriteWithPublishConfig = (package_: NormalizedPackageJson, declaration: BuildConfig["declaration"]): NormalizedPackageJson => {
const { publishConfig } = package_;

if (publishConfig) {
Expand All @@ -25,10 +26,10 @@ const overwriteWithPublishConfig = (package_: NormalizedPackageJson): Normalized
package_.module = publishConfig.module as NormalizedPackageJson["module"];
}

if (publishConfig.types && typeof publishConfig.types === "string" && publishConfig.types !== "") {
if (declaration === undefined && publishConfig.types && typeof publishConfig.types === "string" && publishConfig.types !== "") {
// eslint-disable-next-line no-param-reassign
package_.types = publishConfig.types as NormalizedPackageJson["types"];
} else if (publishConfig.typings && typeof publishConfig.typings === "string" && publishConfig.typings !== "") {
} else if (declaration === undefined && publishConfig.typings && typeof publishConfig.typings === "string" && publishConfig.typings !== "") {
// eslint-disable-next-line no-param-reassign
package_.typings = publishConfig.typings as NormalizedPackageJson["typings"];
}
Expand Down
22 changes: 16 additions & 6 deletions packages/packem/src/utils/extract-export-filenames.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import type { PackageJson } from "@visulima/package";

import type { BuildOptions } from "../types";
import { inferExportType, inferExportTypeFromFileName } from "./infer-export-type";

type OutputDescriptor = { fieldName?: string; file: string; isExecutable?: true; type?: "cjs" | "esm" };

const extractExportFilenames = (packageExports: PackageJson["exports"], type: PackageJson["type"], conditions: string[] = []): OutputDescriptor[] => {
const extractExportFilenames = (
packageExports: PackageJson["exports"],
type: PackageJson["type"],
declaration: BuildOptions["declaration"],
conditions: string[] = [],
): OutputDescriptor[] => {
if (!packageExports) {
return [];
}
Expand All @@ -14,7 +20,7 @@ const extractExportFilenames = (packageExports: PackageJson["exports"], type: Pa
const fileType = type === "module" ? "esm" : "cjs";

if (inferredType && inferredType !== fileType) {
throw new Error(`Exported file "${packageExports}" has an extension that does not match the package.json type "${type}".`);
throw new Error(`Exported file "${packageExports}" has an extension that does not match the package.json type "${type as string}".`);
}

return [{ file: packageExports, type: inferredType ?? fileType }];
Expand All @@ -24,14 +30,18 @@ const extractExportFilenames = (packageExports: PackageJson["exports"], type: Pa
Object.entries(packageExports)
// Filter out .json subpaths such as package.json
.filter(([subpath]) => !subpath.endsWith(".json"))
.flatMap(([condition, packageExport]) =>
(typeof packageExport === "string"
.flatMap(([condition, packageExport]) => {
if (condition === "types" && declaration === undefined) {
return [];
}

return typeof packageExport === "string"
? {
file: packageExport,
type: inferExportType(condition, conditions, packageExport, type),
}
: extractExportFilenames(packageExport, type, [...conditions, condition])),
)
: extractExportFilenames(packageExport, type, declaration, [...conditions, condition]);
})
);
};

Expand Down
8 changes: 2 additions & 6 deletions packages/packem/src/utils/infer-export-type.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import type { PackageJson } from "@visulima/package";

export const inferExportTypeFromFileName = (filename: string): "cjs" | "esm" | undefined => {
if (filename.endsWith(".d.ts")) {
if (filename.endsWith(".mjs") || filename.endsWith(".d.mts")) {
return "esm";
}

if (filename.endsWith(".mjs")) {
return "esm";
}

if (filename.endsWith(".cjs")) {
if (filename.endsWith(".cjs") || filename.endsWith(".d.cts")) {
return "cjs";
}

Expand Down
13 changes: 5 additions & 8 deletions packages/packem/src/validator/validate-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,16 @@ import extractExportFilenames from "../utils/extract-export-filenames";
import levenstein from "../utils/levenstein";
import warn from "../utils/warn";

// eslint-disable-next-line sonarjs/cognitive-complexity
const validatePackage = (package_: PackageJson, context: BuildContext): void => {
if (!package_) {
return;
}

const filenames = new Set(
[
...(typeof package_.bin === "string" ? [package_.bin] : Object.values(package_.bin || {})),
...(typeof package_.bin === "string" ? [package_.bin] : Object.values(package_.bin ?? {})),
package_.main,
package_.module,
package_.types,
package_.typings,
...extractExportFilenames(package_.exports, package_.type ?? "commonjs").map((index) => index.file),
context.options.declaration ? package_.types : "",
context.options.declaration ? package_.typings : "",
...extractExportFilenames(package_.exports, package_.type ?? "commonjs", context.options.declaration).map((index) => index.file),
].map(
(index) =>
index &&
Expand Down

0 comments on commit ed4368f

Please sign in to comment.