From 04e06b5d5fc4836b7dd5896c9db1e10d5bd9a79c Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Thu, 9 Dec 2021 13:02:30 -0700 Subject: [PATCH 01/56] feat(workers): add support for workers style environemnt. See #431 --- packages/cli/src/build/config.ts | 35 ++++++++++++++++++++++++++++ packages/cli/src/build/rollup.ts | 16 ++++++++++++- packages/cli/src/dev.ts | 11 +++++++++ packages/cli/src/entrypoint.ts | 1 + packages/cli/src/init.ts | 24 +++++++++++++++++++ packages/cli/src/messages.ts | 5 +++- packages/cli/src/package.ts | 8 ++++--- packages/cli/src/utils.ts | 27 ++++++++++++++++++++- packages/cli/src/validate-package.ts | 2 ++ packages/cli/src/validate.ts | 11 ++++++++- site/src/pages/configuration.mdx | 6 +++++ 11 files changed, 139 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index 8ac33e93..69caf420 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -207,5 +207,40 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }); } + let hasWorkerField = pkg.entrypoints[0].json.worker !== undefined; + + if (hasWorkerField) { + configs.push({ + config: getRollupConfig( + pkg, + pkg.entrypoints, + aliases, + "worker", + () => {} + ), + outputs: [ + { + format: "cjs" as const, + entryFileNames: "[name].worker.cjs.js", + chunkFileNames: "dist/[name]-[hash].worker.cjs.js", + dir: pkg.directory, + exports: "named" as const, + interop, + plugins: cjsPlugins, + }, + ...(hasModuleField + ? [ + { + format: "es" as const, + entryFileNames: "[name].worker.esm.js", + chunkFileNames: "dist/[name]-[hash].worker.esm.js", + dir: pkg.directory, + }, + ] + : []), + ], + }); + } + return configs; } diff --git a/packages/cli/src/build/rollup.ts b/packages/cli/src/build/rollup.ts index 564ad484..7ab4bab0 100644 --- a/packages/cli/src/build/rollup.ts +++ b/packages/cli/src/build/rollup.ts @@ -31,7 +31,12 @@ const makeExternalPredicate = (externalArr: string[]) => { return (id: string) => pattern.test(id); }; -export type RollupConfigType = "umd" | "browser" | "node-dev" | "node-prod"; +export type RollupConfigType = + | "umd" + | "browser" + | "node-dev" + | "node-prod" + | "worker"; export let getRollupConfig = ( pkg: Package, @@ -193,6 +198,15 @@ export let getRollupConfig = ( }, preventAssignment: true, }), + type === "worker" && + replace({ + values: { + ["typeof " + "document"]: JSON.stringify("undefined"), + ["typeof " + "window"]: JSON.stringify("undefined"), + ["typeof " + "process"]: JSON.stringify("undefined"), + }, + preventAssignment: true, + }), ].filter((x): x is Plugin => !!x), }; diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index b95de07e..86d36e7a 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -218,6 +218,17 @@ unregister(); ); } } + if (entrypoint.json.worker) { + let workerField = validFields.worker(entrypoint); + for (let key of Object.keys(workerField)) { + promises.push( + fs.symlink( + entrypoint.source, + path.join(entrypoint.directory, workerField[key]) + ) + ); + } + } return Promise.all(promises); }) diff --git a/packages/cli/src/entrypoint.ts b/packages/cli/src/entrypoint.ts index e238fc9b..7f50b8cb 100644 --- a/packages/cli/src/entrypoint.ts +++ b/packages/cli/src/entrypoint.ts @@ -9,6 +9,7 @@ export class Entrypoint extends Item<{ module?: JSONValue; "umd:main"?: JSONValue; browser?: JSONValue; + worker?: JSONValue; preconstruct: { source?: JSONValue; umdName?: JSONValue; diff --git a/packages/cli/src/init.ts b/packages/cli/src/init.ts index 6383cdb9..90d132d3 100644 --- a/packages/cli/src/init.ts +++ b/packages/cli/src/init.ts @@ -93,6 +93,30 @@ async function doInit(pkg: Package) { } } + let someEntrypointsHaveAWorkerField = pkg.entrypoints.some( + (entrypoint) => entrypoint.json.worker !== undefined + ); + + let someEntrypointsHaveAnInvalidWorkerField = pkg.entrypoints.some( + (entrypoint) => !isFieldValid.worker(entrypoint) + ); + if ( + someEntrypointsHaveAWorkerField && + someEntrypointsHaveAnInvalidWorkerField + ) { + let shouldFixWorkerField = await confirms.fixWorkerField(pkg); + if (shouldFixWorkerField) { + pkg.setFieldOnEntrypoints("worker"); + } else { + throw new FixableError( + errors.fieldMustExistInAllEntrypointsIfExistsDeclinedFixDuringInit( + "worker" + ), + pkg.name + ); + } + } + await Promise.all(pkg.entrypoints.map((x) => x.save())); } diff --git a/packages/cli/src/messages.ts b/packages/cli/src/messages.ts index ed5279f5..3e78db25 100644 --- a/packages/cli/src/messages.ts +++ b/packages/cli/src/messages.ts @@ -2,7 +2,7 @@ import { PKG_JSON_CONFIG_FIELD } from "./constants"; import { createPromptConfirmLoader } from "./prompt"; import chalk from "chalk"; -type Field = "main" | "module" | "browser" | "umd:main"; +type Field = "main" | "module" | "browser" | "umd:main" | "worker"; export let errors = { noSource: (source: string) => @@ -38,6 +38,9 @@ export let confirms = { fixBrowserField: createPromptConfirmLoader( "would you like to fix the browser build?" ), + fixWorkerField: createPromptConfirmLoader( + "would you like to fix the worker build?" + ), createEntrypointPkgJson: createPromptConfirmLoader( "A package.json file does not exist for this entrypoint, would you like to create one automatically?" ), diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index a8d1d7e5..8d00c88f 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -26,7 +26,7 @@ function getFieldsUsedInEntrypoints( for (let descriptor of descriptors) { if (descriptor.contents !== undefined) { let parsed = jsonParse(descriptor.contents, descriptor.filename); - for (let field of ["module", "umd:main", "browser"] as const) { + for (let field of ["module", "umd:main", "browser", "worker"] as const) { if (parsed[field] !== undefined) { fields.add(field); } @@ -47,7 +47,7 @@ function getPlainEntrypointContent( string | Record >> = {}; for (const field of fields) { - if (field === "browser") { + if (field === "browser" || field === "worker") { obj[field] = validFieldsFromPkg[field]( pkg, fields.has("module"), @@ -264,7 +264,9 @@ export class Package extends Item<{ return pkg; } - setFieldOnEntrypoints(field: "main" | "browser" | "module" | "umd:main") { + setFieldOnEntrypoints( + field: "main" | "browser" | "module" | "umd:main" | "worker" + ) { this.entrypoints.forEach((entrypoint) => { entrypoint.json = setFieldInOrder( entrypoint.json, diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 2259ad0c..5d2f1c4c 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -15,11 +15,12 @@ let fields = [ "module", "umd:main", "browser", + "worker", ]; export function setFieldInOrder< Obj extends { [key: string]: any }, - Key extends "main" | "module" | "umd:main" | "browser", + Key extends "main" | "module" | "umd:main" | "browser" | "worker", Val extends any >(obj: Obj, field: Key, value: Val): Obj & { [k in Key]: Val } { if (field in obj) { @@ -129,6 +130,22 @@ export const validFieldsFromPkg = { } return obj; }, + worker( + pkg: Package, + hasModuleBuild: boolean, + entrypointName: string, + forceStrategy?: DistFilenameStrategy + ) { + let safeName = getDistName(pkg, entrypointName, forceStrategy); + + let obj = { + [`./dist/${safeName}.cjs.js`]: `./dist/${safeName}.worker.cjs.js`, + }; + if (hasModuleBuild) { + obj[`./dist/${safeName}.esm.js`] = `./dist/${safeName}.worker.esm.js`; + } + return obj; + }, }; export const validFields = { @@ -148,6 +165,14 @@ export const validFields = { entrypoint.name ); }, + worker(entrypoint: Entrypoint, forceStrategy?: DistFilenameStrategy) { + return validFieldsFromPkg.worker( + entrypoint.package, + entrypoint.json.module !== undefined, + entrypoint.name, + forceStrategy + ); + }, }; export function flowTemplate(hasDefaultExport: boolean, relativePath: string) { diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 65f0b26f..5a253e2f 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -15,6 +15,7 @@ export async function fixPackage(pkg: Package) { module: pkg.entrypoints.some((x) => x.json.module !== undefined), "umd:main": pkg.entrypoints.some((x) => x.json["umd:main"] !== undefined), browser: pkg.entrypoints.some((x) => x.json.browser !== undefined), + worker: pkg.entrypoints.some((x) => x.json.worker !== undefined), }; keys(fields) @@ -41,6 +42,7 @@ export function validatePackage(pkg: Package) { module: pkg.entrypoints[0].json.module !== undefined, "umd:main": pkg.entrypoints[0].json["umd:main"] !== undefined, browser: pkg.entrypoints[0].json.browser !== undefined, + worker: pkg.entrypoints[0].json.worker !== undefined, }; pkg.entrypoints.forEach((entrypoint) => { diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index e0e65cc3..4dae257c 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -25,6 +25,9 @@ export const isFieldValid = { browser(entrypoint: Entrypoint): boolean { return equal(entrypoint.json.browser, validFields.browser(entrypoint)); }, + worker(entrypoint: Entrypoint): boolean { + return equal(entrypoint.json.worker, validFields.worker(entrypoint)); + }, }; export function isUmdNameSpecified(entrypoint: Entrypoint) { @@ -38,7 +41,13 @@ function validateEntrypoint(entrypoint: Entrypoint, log: boolean) { logger.info(infos.validEntrypoint, entrypoint.name); } const fatalErrors: FatalError[] = []; - for (const field of ["main", "module", "umd:main", "browser"] as const) { + for (const field of [ + "main", + "module", + "umd:main", + "browser", + "worker", + ] as const) { if (field !== "main" && entrypoint.json[field] === undefined) { continue; } diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index 7b6d56e5..637d2983 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -183,3 +183,9 @@ Example: The `browser` field specifies alias files exclusive to browsers. This allows you to create different bundles from your source code based on `typeof window` and `typeof document` checks - thanks to that you can, for example, remove server-only code (just for those bundles). **Note:** Those files are not meant to be consumed by browsers "as is". They just assume browser-like environment, but they still can contain for example references to `process.env.NODE_ENV` as that is meant to be replaced by a consuming bundler. + +### `worker` + +The `worker` field specifies alias files exclusive to DOM-less javascript environments like CloudFlare Workers. This allows you to create different bundles from your source code based on `typeof window` and `typeof document` and `typeof process` checks - thanks to that you can, for example, remove broser-only or Node.js-only code (just for those bundles). + +**Note:** Those files are not meant to be consumed by workers "as is". They just assume worker-like environment, but they still can contain for example references to `process.env.NODE_ENV` as that is meant to be replaced by a consuming bundler. From b0feebb69a7ffe67c5e077bd10dbe7a436d14c28 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Fri, 10 Dec 2021 11:40:51 -0700 Subject: [PATCH 02/56] feat(exports): adds support for package exports --- packages/cli/src/build/config.ts | 18 +++- packages/cli/src/entrypoint.ts | 12 ++- packages/cli/src/init.ts | 18 ++-- packages/cli/src/messages.ts | 4 +- packages/cli/src/package.ts | 54 ++++++++-- packages/cli/src/utils.ts | 156 +++++++++++++++++++++++++-- packages/cli/src/validate-package.ts | 3 +- packages/cli/src/validate.ts | 15 ++- 8 files changed, 241 insertions(+), 39 deletions(-) diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index 69caf420..508baef5 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -172,7 +172,21 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }); }); - let hasBrowserField = pkg.entrypoints[0].json.browser !== undefined; + let hasBrowserField = false; + let hasWorkerField = false; + let hasExportsField = typeof pkg.entrypoints[0].json.exports == "object"; + if (hasExportsField) { + hasBrowserField = Object.values(pkg.entrypoints[0].json.exports!).some( + (condition) => + typeof condition === "object" && condition.browser !== undefined + ); + hasWorkerField = Object.values(pkg.entrypoints[0].json.exports!).some( + (condition) => + typeof condition === "object" && condition.worker !== undefined + ); + } else if (hasBrowserField === false) { + hasBrowserField = pkg.entrypoints[0].json.browser !== undefined; + } if (hasBrowserField) { configs.push({ @@ -207,8 +221,6 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }); } - let hasWorkerField = pkg.entrypoints[0].json.worker !== undefined; - if (hasWorkerField) { configs.push({ config: getRollupConfig( diff --git a/packages/cli/src/entrypoint.ts b/packages/cli/src/entrypoint.ts index 7f50b8cb..8641da72 100644 --- a/packages/cli/src/entrypoint.ts +++ b/packages/cli/src/entrypoint.ts @@ -4,12 +4,22 @@ import { Package } from "./package"; import { JSONValue } from "./utils"; import normalizePath from "normalize-path"; +export type ExportsConditions = { + module?: string; + default?: string; +}; + +export type ExportsItem = { + browser?: ExportsConditions | string; + worker?: ExportsConditions | string; +} & ExportsConditions; + export class Entrypoint extends Item<{ main?: JSONValue; module?: JSONValue; "umd:main"?: JSONValue; browser?: JSONValue; - worker?: JSONValue; + exports?: Record; preconstruct: { source?: JSONValue; umdName?: JSONValue; diff --git a/packages/cli/src/init.ts b/packages/cli/src/init.ts index 90d132d3..f8563ca0 100644 --- a/packages/cli/src/init.ts +++ b/packages/cli/src/init.ts @@ -93,24 +93,24 @@ async function doInit(pkg: Package) { } } - let someEntrypointsHaveAWorkerField = pkg.entrypoints.some( - (entrypoint) => entrypoint.json.worker !== undefined + let someEntrypointsHaveAExportsField = pkg.entrypoints.some( + (entrypoint) => entrypoint.json.exports !== undefined ); - let someEntrypointsHaveAnInvalidWorkerField = pkg.entrypoints.some( - (entrypoint) => !isFieldValid.worker(entrypoint) + let someEntrypointsHaveAnInvalidExportsField = pkg.entrypoints.some( + (entrypoint) => !isFieldValid.exports(entrypoint) ); if ( - someEntrypointsHaveAWorkerField && - someEntrypointsHaveAnInvalidWorkerField + someEntrypointsHaveAExportsField && + someEntrypointsHaveAnInvalidExportsField ) { - let shouldFixWorkerField = await confirms.fixWorkerField(pkg); + let shouldFixWorkerField = await confirms.fixExportsField(pkg); if (shouldFixWorkerField) { - pkg.setFieldOnEntrypoints("worker"); + pkg.setFieldOnEntrypoints("exports"); } else { throw new FixableError( errors.fieldMustExistInAllEntrypointsIfExistsDeclinedFixDuringInit( - "worker" + "exports" ), pkg.name ); diff --git a/packages/cli/src/messages.ts b/packages/cli/src/messages.ts index 3e78db25..657c1fae 100644 --- a/packages/cli/src/messages.ts +++ b/packages/cli/src/messages.ts @@ -2,7 +2,7 @@ import { PKG_JSON_CONFIG_FIELD } from "./constants"; import { createPromptConfirmLoader } from "./prompt"; import chalk from "chalk"; -type Field = "main" | "module" | "browser" | "umd:main" | "worker"; +type Field = "main" | "module" | "browser" | "umd:main" | "exports"; export let errors = { noSource: (source: string) => @@ -38,7 +38,7 @@ export let confirms = { fixBrowserField: createPromptConfirmLoader( "would you like to fix the browser build?" ), - fixWorkerField: createPromptConfirmLoader( + fixExportsField: createPromptConfirmLoader( "would you like to fix the worker build?" ), createEntrypointPkgJson: createPromptConfirmLoader( diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 8d00c88f..2d957190 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -4,7 +4,7 @@ import * as fs from "fs-extra"; import nodePath from "path"; import { Item } from "./item"; import { BatchError, FatalError } from "./errors"; -import { Entrypoint } from "./entrypoint"; +import { Entrypoint, ExportsItem } from "./entrypoint"; import jsonParse from "parse-json"; import { errors, confirms } from "./messages"; @@ -22,37 +22,67 @@ import normalizePath from "normalize-path"; function getFieldsUsedInEntrypoints( descriptors: { contents: string | undefined; filename: string }[] ) { + let hasBrowserField = false; + let hasWorkerField = false; const fields = new Set(["main"]); for (let descriptor of descriptors) { if (descriptor.contents !== undefined) { let parsed = jsonParse(descriptor.contents, descriptor.filename); - for (let field of ["module", "umd:main", "browser", "worker"] as const) { - if (parsed[field] !== undefined) { + for (let field of ["module", "umd:main", "browser", "exports"] as const) { + const value = parsed[field]; + if (value !== undefined) { fields.add(field); + if (field === "exports" && value["."]) { + const conditions: ExportsItem[] = Object.values(value["."]); + hasBrowserField = + hasBrowserField || + conditions.some( + (condition) => + typeof condition === "object" && + condition.browser !== undefined + ); + hasWorkerField = + hasWorkerField || + conditions.some( + (condition) => + typeof condition === "object" && + condition.worker !== undefined + ); + } } } } } - return fields; + return [fields, hasBrowserField, hasWorkerField]; } function getPlainEntrypointContent( pkg: Package, fields: Set, entrypointDir: string, - indent: string + indent: string, + hasBrowserField: boolean, + hasWorkerField: boolean ) { const obj: Partial + string | Record >> = {}; for (const field of fields) { - if (field === "browser" || field === "worker") { + if (field === "browser") { obj[field] = validFieldsFromPkg[field]( pkg, fields.has("module"), getEntrypointName(pkg, entrypointDir) ); + } else if (field === "exports") { + obj[field] = validFieldsFromPkg[field]( + pkg, + fields.has("module"), + hasBrowserField, + hasWorkerField, + getEntrypointName(pkg, entrypointDir) + ); } else { obj[field] = validFieldsFromPkg[field]( pkg, @@ -72,7 +102,9 @@ function createEntrypoints( sourceFile: string; }[] ) { - let fields = getFieldsUsedInEntrypoints(descriptors); + let [fields, hasBrowserField, hasWorkerField] = getFieldsUsedInEntrypoints( + descriptors + ); return Promise.all( descriptors.map(async ({ filename, contents, hasAccepted, sourceFile }) => { @@ -93,7 +125,9 @@ function createEntrypoints( pkg, fields, nodePath.dirname(filename), - pkg.indent + pkg.indent, + hasBrowserField, + hasWorkerField ); await fs.outputFile(filename, contents); } @@ -265,7 +299,7 @@ export class Package extends Item<{ } setFieldOnEntrypoints( - field: "main" | "browser" | "module" | "umd:main" | "worker" + field: "main" | "browser" | "module" | "umd:main" | "exports" ) { this.entrypoints.forEach((entrypoint) => { entrypoint.json = setFieldInOrder( diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 5d2f1c4c..f2f96b30 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -1,5 +1,5 @@ import normalizePath from "normalize-path"; -import { Entrypoint } from "./entrypoint"; +import { Entrypoint, ExportsConditions, ExportsItem } from "./entrypoint"; import { Package } from "./package"; import * as nodePath from "path"; import { FatalError } from "./errors"; @@ -15,12 +15,12 @@ let fields = [ "module", "umd:main", "browser", - "worker", + "exports", ]; export function setFieldInOrder< Obj extends { [key: string]: any }, - Key extends "main" | "module" | "umd:main" | "browser" | "worker", + Key extends "main" | "module" | "umd:main" | "browser" | "exports", Val extends any >(obj: Obj, field: Key, value: Val): Obj & { [k in Key]: Val } { if (field in obj) { @@ -130,19 +130,143 @@ export const validFieldsFromPkg = { } return obj; }, - worker( + exports( pkg: Package, hasModuleBuild: boolean, + hasBrowserField: boolean, + hasWorkerField: boolean, entrypointName: string, forceStrategy?: DistFilenameStrategy + ) { + let obj: Record = { + ".": exportsHelpers.root( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy + ), + }; + if (hasBrowserField) { + obj["."] = { + browser: exportsHelpers.browser( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy + ), + ...obj["."], + }; + } + if (hasWorkerField) { + obj["."] = { + worker: exportsHelpers.worker( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy + ), + ...obj["."], + }; + } + pkg.entrypoints.forEach((entrypoint) => { + if (entrypointName === entrypoint.name) { + return; + } + const entrypointPath = nodePath + .relative(pkg.directory, entrypoint.source) + .replace("src/", "") + .replace(/\.[tj]sx?$/, ""); + let conditions: ExportsItem = exportsHelpers.root( + pkg, + entrypoint.json.module !== undefined, + entrypoint.name, + forceStrategy, + entrypointPath + "/" + ); + + if (hasBrowserField) { + conditions = { + browser: exportsHelpers.browser( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy, + entrypointPath + "/" + ), + ...conditions, + }; + } + if (hasWorkerField) { + conditions = { + worker: exportsHelpers.worker( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy, + entrypointPath + "/" + ), + ...conditions, + }; + } + + obj[`./${entrypointPath}`] = conditions; + }); + return obj; + }, +}; + +const exportsHelpers = { + root( + pkg: Package, + hasModuleBuild: boolean, + entrypointName: string, + forceStrategy?: DistFilenameStrategy, + prefix: string = "" ) { let safeName = getDistName(pkg, entrypointName, forceStrategy); - let obj = { - [`./dist/${safeName}.cjs.js`]: `./dist/${safeName}.worker.cjs.js`, + let obj: ExportsConditions = { + default: `./${prefix}dist/${safeName}.cjs.js`, + }; + if (hasModuleBuild) { + obj = { + module: `./${prefix}dist/${safeName}.esm.js`, + ...obj, + }; + } + return obj; + }, + browser( + pkg: Package, + hasModuleBuild: boolean, + entrypointName: string, + forceStrategy?: DistFilenameStrategy, + prefix: string = "" + ) { + let safeName = getDistName(pkg, entrypointName, forceStrategy); + + let obj: ExportsConditions = { + default: `./${prefix}dist/${safeName}.browser.cjs.js`, }; if (hasModuleBuild) { - obj[`./dist/${safeName}.esm.js`] = `./dist/${safeName}.worker.esm.js`; + obj = { module: `./${prefix}dist/${safeName}.browser.esm.js`, ...obj }; + } + return obj; + }, + worker( + pkg: Package, + hasModuleBuild: boolean, + entrypointName: string, + forceStrategy?: DistFilenameStrategy, + prefix: string = "" + ) { + let safeName = getDistName(pkg, entrypointName, forceStrategy); + + let obj: ExportsConditions = { + default: `./${prefix}dist/${safeName}.worker.cjs.js`, + }; + if (hasModuleBuild) { + obj = { module: `./${prefix}dist/${safeName}.worker.esm.js`, ...obj }; } return obj; }, @@ -165,10 +289,24 @@ export const validFields = { entrypoint.name ); }, - worker(entrypoint: Entrypoint, forceStrategy?: DistFilenameStrategy) { - return validFieldsFromPkg.worker( + exports(entrypoint: Entrypoint, forceStrategy?: DistFilenameStrategy) { + if (typeof entrypoint.json.exports === "undefined") { + return; + } + const conditions = Object.values(entrypoint.json.exports); + const hasBrowserField = conditions.some( + (condition) => + typeof condition === "object" && condition.browser !== undefined + ); + const hasWorkerField = conditions.some( + (condition) => + typeof condition === "object" && condition.worker !== undefined + ); + return validFieldsFromPkg.exports( entrypoint.package, entrypoint.json.module !== undefined, + hasBrowserField, + hasWorkerField, entrypoint.name, forceStrategy ); diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 5a253e2f..240fe0c4 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -15,7 +15,7 @@ export async function fixPackage(pkg: Package) { module: pkg.entrypoints.some((x) => x.json.module !== undefined), "umd:main": pkg.entrypoints.some((x) => x.json["umd:main"] !== undefined), browser: pkg.entrypoints.some((x) => x.json.browser !== undefined), - worker: pkg.entrypoints.some((x) => x.json.worker !== undefined), + exports: pkg.entrypoints.some((x) => x.json.exports !== undefined), }; keys(fields) @@ -42,7 +42,6 @@ export function validatePackage(pkg: Package) { module: pkg.entrypoints[0].json.module !== undefined, "umd:main": pkg.entrypoints[0].json["umd:main"] !== undefined, browser: pkg.entrypoints[0].json.browser !== undefined, - worker: pkg.entrypoints[0].json.worker !== undefined, }; pkg.entrypoints.forEach((entrypoint) => { diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index 4dae257c..02877243 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -25,8 +25,17 @@ export const isFieldValid = { browser(entrypoint: Entrypoint): boolean { return equal(entrypoint.json.browser, validFields.browser(entrypoint)); }, - worker(entrypoint: Entrypoint): boolean { - return equal(entrypoint.json.worker, validFields.worker(entrypoint)); + exports(entrypoint: Entrypoint): boolean { + const validExports = validFields.exports(entrypoint); + if (validExports === undefined) { + return false; + } + Object.keys(validExports).forEach((key) => { + if (!equal(validExports![key], entrypoint.json.exports![key])) { + return false; + } + }); + return true; }, }; @@ -46,7 +55,7 @@ function validateEntrypoint(entrypoint: Entrypoint, log: boolean) { "module", "umd:main", "browser", - "worker", + "exports", ] as const) { if (field !== "main" && entrypoint.json[field] === undefined) { continue; From b25d5230c2866e2f4687c8c79abb6baec7e9f550 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Fri, 10 Dec 2021 12:08:20 -0700 Subject: [PATCH 03/56] fixing dev mode with package.exports --- packages/cli/src/dev.ts | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index 86d36e7a..5e1a64de 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -207,6 +207,28 @@ unregister(); ) ); } + if (entrypoint.json.exports) { + let exportsField = validFields.exports(entrypoint); + if (exportsField?.["."] && typeof exportsField["."] === "object") { + for (let key of Object.keys(exportsField["."])) { + if (["browser", "worker"].includes(key)) { + for (let key2 of Object.keys( + (exportsField["."] as any)[key] + )) { + promises.push( + fs.symlink( + entrypoint.source, + path.join( + entrypoint.directory, + (exportsField["."] as any)[key][key2] + ) + ) + ); + } + } + } + } + } if (entrypoint.json.browser) { let browserField = validFields.browser(entrypoint); for (let key of Object.keys(browserField)) { @@ -218,17 +240,6 @@ unregister(); ); } } - if (entrypoint.json.worker) { - let workerField = validFields.worker(entrypoint); - for (let key of Object.keys(workerField)) { - promises.push( - fs.symlink( - entrypoint.source, - path.join(entrypoint.directory, workerField[key]) - ) - ); - } - } return Promise.all(promises); }) From b54889399bcb5b80f1f0d0f3d243f4ee16eacafd Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Fri, 10 Dec 2021 12:09:53 -0700 Subject: [PATCH 04/56] fixing error message for package exports --- packages/cli/src/messages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/messages.ts b/packages/cli/src/messages.ts index 657c1fae..f6cffda4 100644 --- a/packages/cli/src/messages.ts +++ b/packages/cli/src/messages.ts @@ -39,7 +39,7 @@ export let confirms = { "would you like to fix the browser build?" ), fixExportsField: createPromptConfirmLoader( - "would you like to fix the worker build?" + "would you like to fix the exports build(s)?" ), createEntrypointPkgJson: createPromptConfirmLoader( "A package.json file does not exist for this entrypoint, would you like to create one automatically?" From bf841d95dff1681627bc0272212a84563283e356 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Fri, 10 Dec 2021 12:17:04 -0700 Subject: [PATCH 05/56] update docs for exports --- site/src/pages/configuration.mdx | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index 637d2983..4bdd1628 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -184,8 +184,25 @@ The `browser` field specifies alias files exclusive to browsers. This allows you **Note:** Those files are not meant to be consumed by browsers "as is". They just assume browser-like environment, but they still can contain for example references to `process.env.NODE_ENV` as that is meant to be replaced by a consuming bundler. -### `worker` +### `exports` +The exports field allows you to specify custom conditional exports for various environments. You can use this to create custom builds targeted for these specific environments. Currently, only "browser" and "worker" conditionals are supported along with their "module" and "default" variants. +Here is an example config using "browser" and "worker" conditionals: -The `worker` field specifies alias files exclusive to DOM-less javascript environments like CloudFlare Workers. This allows you to create different bundles from your source code based on `typeof window` and `typeof document` and `typeof process` checks - thanks to that you can, for example, remove broser-only or Node.js-only code (just for those bundles). - -**Note:** Those files are not meant to be consumed by workers "as is". They just assume worker-like environment, but they still can contain for example references to `process.env.NODE_ENV` as that is meant to be replaced by a consuming bundler. +```json +{ + "exports": { + ".": { + "worker": { + "module": "./dist/my-package.worker.esm.js", + "default": "./dist/my-package.worker.cjs.js" + }, + "browser": { + "module": "./dist/my-package.browser.esm.js", + "default": "./dist/my-package.browser.cjs.js" + }, + "module": "./dist/my-package.esm.js", + "default": "./dist/my-package.cjs.js" + } + } +} +``` From 7d490556f77239e26ccccbac540079d6b7c5b7a3 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Fri, 10 Dec 2021 16:54:20 -0700 Subject: [PATCH 06/56] make config more clear --- packages/cli/src/build/config.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index 508baef5..e5bbeea2 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -172,23 +172,23 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }); }); - let hasBrowserField = false; - let hasWorkerField = false; + let hasBrowserCondition = false; + let hasWorkerCondition = false; let hasExportsField = typeof pkg.entrypoints[0].json.exports == "object"; if (hasExportsField) { - hasBrowserField = Object.values(pkg.entrypoints[0].json.exports!).some( + hasBrowserCondition = Object.values(pkg.entrypoints[0].json.exports!).some( (condition) => typeof condition === "object" && condition.browser !== undefined ); - hasWorkerField = Object.values(pkg.entrypoints[0].json.exports!).some( + hasWorkerCondition = Object.values(pkg.entrypoints[0].json.exports!).some( (condition) => typeof condition === "object" && condition.worker !== undefined ); - } else if (hasBrowserField === false) { - hasBrowserField = pkg.entrypoints[0].json.browser !== undefined; } - if (hasBrowserField) { + let hasBrowserField = pkg.entrypoints[0].json.browser !== undefined; + + if (hasBrowserField || hasBrowserCondition) { configs.push({ config: getRollupConfig( pkg, @@ -221,7 +221,7 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }); } - if (hasWorkerField) { + if (hasWorkerCondition) { configs.push({ config: getRollupConfig( pkg, From 89300f331cc356c46d4a44b1fa70aa124903cb11 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Tue, 14 Dec 2021 13:23:37 -0700 Subject: [PATCH 07/56] adding support for production and development package exports conditionals --- packages/cli/package.json | 13 +- .../__tests__/__snapshots__/basic.ts.snap | 45 ++++++ .../__tests__/__snapshots__/build.ts.snap | 111 ++++++++++++++ .../__snapshots__/entrypoints.ts.snap | 59 +++++++- .../__tests__/__snapshots__/other.ts.snap | 89 ++++++++++++ packages/cli/src/build/__tests__/basic.ts | 57 +++++++- packages/cli/src/build/__tests__/build.ts | 11 +- .../cli/src/build/__tests__/entrypoints.ts | 22 ++- packages/cli/src/build/__tests__/other.ts | 46 +++++- packages/cli/src/build/config.ts | 109 ++++++++++++-- packages/cli/src/build/rollup.ts | 36 +++-- packages/cli/src/entrypoint.ts | 41 +++++- packages/cli/src/package.ts | 8 +- packages/cli/src/utils.ts | 137 +++++++++++------- packages/cli/src/validate.ts | 11 +- 15 files changed, 662 insertions(+), 133 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ac496657..9de351e7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -2,13 +2,7 @@ "name": "@preconstruct/cli", "version": "2.1.7", "description": "Dev and build your code painlessly in monorepos", - "files": [ - "bin.js", - "cli", - "worker", - "!**/*.d.ts", - "dist" - ], + "files": ["bin.js", "cli", "worker", "!**/*.d.ts", "dist"], "bin": { "preconstruct": "./bin.js" }, @@ -54,10 +48,7 @@ "v8-compile-cache": "^2.1.1" }, "preconstruct": { - "entrypoints": [ - "cli", - "worker" - ] + "entrypoints": ["cli", "worker"] }, "devDependencies": { "escape-string-regexp": "^4.0.0", diff --git a/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap index 4da63de7..1a72ef18 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap @@ -33,7 +33,25 @@ exports.default = index; " `; +exports[`basic: valid-package.esm.dev.js 1`] = ` +"var index = \\"something\\"; + +export default index; +" +`; + exports[`basic: valid-package.esm.js 1`] = ` +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./valid-package.esm.prod.js\\"); +} else { + module.exports = require(\\"./valid-package.esm.dev.js\\"); +} +" +`; + +exports[`basic: valid-package.esm.prod.js 1`] = ` "var index = \\"something\\"; export default index; @@ -109,7 +127,34 @@ exports.thing = thing$1; " `; +exports[`typescript thing: weird-typescript-thing.esm.d.ts 1`] = ` +"export * from \\"./declarations/src/index\\"; +" +`; + +exports[`typescript thing: weird-typescript-thing.esm.dev.js 1`] = ` +"const thing = () => \\"wow\\"; + +const makeThing = () => thing(); + +const thing$1 = makeThing(); + +export { thing$1 as thing }; +" +`; + exports[`typescript thing: weird-typescript-thing.esm.js 1`] = ` +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./weird-typescript-thing.esm.prod.js\\"); +} else { + module.exports = require(\\"./weird-typescript-thing.esm.dev.js\\"); +} +" +`; + +exports[`typescript thing: weird-typescript-thing.esm.prod.js 1`] = ` "const thing = () => \\"wow\\"; const makeThing = () => thing(); diff --git a/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap index 6ccfa6f0..9d61f9ff 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap @@ -49,7 +49,35 @@ exports.something = a; " `; +exports[`flow: flow.esm.dev.js 1`] = ` +"var a = \\"wow\\"; + +function doSomething(arg) { + return \\"something\\" + arg; +} + +export { doSomething, a as something }; +" +`; + exports[`flow: flow.esm.js 1`] = ` +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./flow.esm.prod.js\\"); +} else { + module.exports = require(\\"./flow.esm.dev.js\\"); +} +" +`; + +exports[`flow: flow.esm.js.flow 1`] = ` +"// @flow +export * from \\"../src/index.js\\"; +" +`; + +exports[`flow: flow.esm.prod.js 1`] = ` "var a = \\"wow\\"; function doSomething(arg) { @@ -108,7 +136,36 @@ exports.doSomething = doSomething; " `; +exports[`flow: flow-export-default.esm.dev.js 1`] = ` +"function doSomething(arg) { + return \\"something\\" + arg; +} +var index = \\"wow\\"; + +export default index; +export { doSomething }; +" +`; + exports[`flow: flow-export-default.esm.js 1`] = ` +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./flow-export-default.esm.prod.js\\"); +} else { + module.exports = require(\\"./flow-export-default.esm.dev.js\\"); +} +" +`; + +exports[`flow: flow-export-default.esm.js.flow 1`] = ` +"// @flow +export * from \\"../src/index.js\\"; +export { default } from \\"../src/index.js\\"; +" +`; + +exports[`flow: flow-export-default.esm.prod.js 1`] = ` "function doSomething(arg) { return \\"something\\" + arg; } @@ -152,7 +209,25 @@ exports.default = index; " `; +exports[`monorepo single package: some-scope-package-two-single-package.esm.dev.js 1`] = ` +"var index = 2; + +export default index; +" +`; + exports[`monorepo single package: some-scope-package-two-single-package.esm.js 1`] = ` +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./some-scope-package-two-single-package.esm.prod.js\\"); +} else { + module.exports = require(\\"./some-scope-package-two-single-package.esm.dev.js\\"); +} +" +`; + +exports[`monorepo single package: some-scope-package-two-single-package.esm.prod.js 1`] = ` "var index = 2; export default index; @@ -336,7 +411,25 @@ exports.default = index; " `; +exports[`monorepo: some-scope-package-one.esm.dev.js 1`] = ` +"var index = 1; + +export default index; +" +`; + exports[`monorepo: some-scope-package-one.esm.js 1`] = ` +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./some-scope-package-one.esm.prod.js\\"); +} else { + module.exports = require(\\"./some-scope-package-one.esm.dev.js\\"); +} +" +`; + +exports[`monorepo: some-scope-package-one.esm.prod.js 1`] = ` "var index = 1; export default index; @@ -376,7 +469,25 @@ exports.default = index; " `; +exports[`monorepo: some-scope-package-two.esm.dev.js 1`] = ` +"var index = 2; + +export default index; +" +`; + exports[`monorepo: some-scope-package-two.esm.js 1`] = ` +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./some-scope-package-two.esm.prod.js\\"); +} else { + module.exports = require(\\"./some-scope-package-two.esm.dev.js\\"); +} +" +`; + +exports[`monorepo: some-scope-package-two.esm.prod.js 1`] = ` "var index = 2; export default index; diff --git a/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap index 7f21d9b4..06ff0ca3 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap @@ -78,7 +78,14 @@ exports[`two entrypoints where one requires the other entrypoint: src/multiply.j export let multiply = (a, b) => identity(a * b);" `; -exports[`two entrypoints with a common dependency: dist/chunk-this-is-not-the-real-hash-09d8aa2cd41dd37e55ea1ca5c8546d35.esm.js 1`] = ` +exports[`two entrypoints with a common dependency: dist/chunk-this-is-not-the-real-hash-09d8aa2cd41dd37e55ea1ca5c8546d35.esm.dev.js 1`] = ` +"let identity = x => x; + +export { identity as i }; +" +`; + +exports[`two entrypoints with a common dependency: dist/chunk-this-is-not-the-real-hash-09d8aa2cd41dd37e55ea1ca5c8546d35.esm.prod.js 1`] = ` "let identity = x => x; export { identity as i }; @@ -142,9 +149,30 @@ exports.sum = sum; " `; +exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.dev.js 1`] = ` +"import { i as identity } from './chunk-some-hash.esm.dev.js'; +export { i as identity } from './chunk-some-hash.esm.dev.js'; + +let sum = (a, b) => identity(a + b); + +export { sum }; +" +`; + exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.js 1`] = ` -"import { i as identity } from './chunk-some-hash.esm.js'; -export { i as identity } from './chunk-some-hash.esm.js'; +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./common-dependency-two-entrypoints.esm.prod.js\\"); +} else { + module.exports = require(\\"./common-dependency-two-entrypoints.esm.dev.js\\"); +} +" +`; + +exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.prod.js 1`] = ` +"import { i as identity } from './chunk-some-hash.esm.prod.js'; +export { i as identity } from './chunk-some-hash.esm.prod.js'; let sum = (a, b) => identity(a + b); @@ -191,9 +219,30 @@ exports.multiply = multiply; " `; +exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.dev.js 1`] = ` +"import { i as identity } from '../../dist/chunk-some-hash.esm.dev.js'; +export { i as identity } from '../../dist/chunk-some-hash.esm.dev.js'; + +let multiply = (a, b) => identity(a * b); + +export { multiply }; +" +`; + exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.js 1`] = ` -"import { i as identity } from '../../dist/chunk-some-hash.esm.js'; -export { i as identity } from '../../dist/chunk-some-hash.esm.js'; +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./common-dependency-two-entrypoints-multiply.esm.prod.js\\"); +} else { + module.exports = require(\\"./common-dependency-two-entrypoints-multiply.esm.dev.js\\"); +} +" +`; + +exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.prod.js 1`] = ` +"import { i as identity } from '../../dist/chunk-some-hash.esm.prod.js'; +export { i as identity } from '../../dist/chunk-some-hash.esm.prod.js'; let multiply = (a, b) => identity(a * b); diff --git a/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap index 70d2bd71..7f29a01f 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap @@ -1,8 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`browser no module: dist/browser-no-module.browser.cjs.dev.js 1`] = ` +"'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +let thing = \\"wow\\"; + +{ + thing = \\"something\\"; +} + +{ + thing += \\"other\\"; +} + +var thing$1 = thing; + +exports.default = thing$1; +" +`; + exports[`browser no module: dist/browser-no-module.browser.cjs.js 1`] = ` "'use strict'; +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./browser-no-module.browser.cjs.prod.js\\"); +} else { + module.exports = require(\\"./browser-no-module.browser.cjs.dev.js\\"); +} +" +`; + +exports[`browser no module: dist/browser-no-module.browser.cjs.prod.js 1`] = ` +"'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); let thing = \\"wow\\"; @@ -223,7 +255,42 @@ exports.createStore = createStore; " `; +exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.d.ts 1`] = ` +"export * from \\"./declarations/src/index\\"; +" +`; + +exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.dev.js 1`] = ` +"import { combineReducers, configureStore } from '@reduxjs/toolkit'; + +// @ts-ignore (installed during test) +var rootReducer = combineReducers({ + /* blah blah blah */ +}); + +// @ts-ignore (installed during test) +function createStore() { + return configureStore({ + reducer: rootReducer + }); +} + +export { createStore }; +" +`; + exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.js 1`] = ` +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./typescript-force-dts-emit.esm.prod.js\\"); +} else { + module.exports = require(\\"./typescript-force-dts-emit.esm.dev.js\\"); +} +" +`; + +exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.prod.js 1`] = ` "import { combineReducers, configureStore } from '@reduxjs/toolkit'; // @ts-ignore (installed during test) @@ -283,7 +350,29 @@ exports.default = Foo; " `; +exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.dev.js 1`] = ` +"import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck'; + +var Foo = function Foo() { + _classCallCheck(this, Foo); +}; + +export default Foo; +" +`; + exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.js 1`] = ` +"'use strict'; + +if (process.env.NODE_ENV === \\"production\\") { + module.exports = require(\\"./external-babel-runtime.esm.prod.js\\"); +} else { + module.exports = require(\\"./external-babel-runtime.esm.dev.js\\"); +} +" +`; + +exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.prod.js 1`] = ` "import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck'; var Foo = function Foo() { diff --git a/packages/cli/src/build/__tests__/basic.ts b/packages/cli/src/build/__tests__/basic.ts index 0d6ef7fb..09463c85 100644 --- a/packages/cli/src/build/__tests__/basic.ts +++ b/packages/cli/src/build/__tests__/basic.ts @@ -154,7 +154,7 @@ test("typescript declarationMap", async () => { //# sourceMappingURL=index.d.ts.map ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/declarations/src/index.d.ts.map ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ {"version":3,"file":"index.d.ts","sourceRoot":"../../../src","sources":["index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,OAAiB,CAAC"} - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.cjs.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.cjs.d.ts, dist/typescript-declarationMap.esm.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ export * from "./declarations/src/index"; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.cjs.dev.js, dist/typescript-declarationMap.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ @@ -175,11 +175,20 @@ test("typescript declarationMap", async () => { module.exports = require("./typescript-declarationMap.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.esm.dev.js, dist/typescript-declarationMap.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ const thing = "wow"; export { thing }; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./typescript-declarationMap.esm.prod.js"); + } else { + module.exports = require("./typescript-declarationMap.esm.dev.js"); + } + `); }); @@ -362,7 +371,7 @@ test("imports helpers from @babel/runtime without @babel/plugin-transform-runtim module.exports = require("./test.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.dev.js, dist/test.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck'; var Other = function Other() { @@ -375,6 +384,15 @@ test("imports helpers from @babel/runtime without @babel/plugin-transform-runtim export { Other, Thing }; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./test.esm.prod.js"); + } else { + module.exports = require("./test.esm.dev.js"); + } + `); }); @@ -423,7 +441,7 @@ test("imports helpers from @babel/runtime-corejs2 without @babel/plugin-transfor module.exports = require("./test.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.dev.js, dist/test.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ import _classCallCheck from '@babel/runtime-corejs2/helpers/esm/classCallCheck'; var Other = function Other() { @@ -436,6 +454,15 @@ test("imports helpers from @babel/runtime-corejs2 without @babel/plugin-transfor export { Other, Thing }; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./test.esm.prod.js"); + } else { + module.exports = require("./test.esm.dev.js"); + } + `); }); @@ -484,7 +511,7 @@ test("imports helpers from @babel/runtime-corejs3 without @babel/plugin-transfor module.exports = require("./test.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.dev.js, dist/test.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ import _classCallCheck from '@babel/runtime-corejs3/helpers/esm/classCallCheck'; var Other = function Other() { @@ -497,6 +524,15 @@ test("imports helpers from @babel/runtime-corejs3 without @babel/plugin-transfor export { Other, Thing }; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./test.esm.prod.js"); + } else { + module.exports = require("./test.esm.dev.js"); + } + `); }); @@ -852,11 +888,20 @@ test("new dist filenames", async () => { module.exports = require("./scope-test.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.esm.dev.js, dist/scope-test.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ var index = "something"; export default index; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./scope-test.esm.prod.js"); + } else { + module.exports = require("./scope-test.esm.dev.js"); + } + `); }); diff --git a/packages/cli/src/build/__tests__/build.ts b/packages/cli/src/build/__tests__/build.ts index 75a71c33..7e496d50 100644 --- a/packages/cli/src/build/__tests__/build.ts +++ b/packages/cli/src/build/__tests__/build.ts @@ -488,7 +488,7 @@ test("json", async () => { module.exports = require("./json-package.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/json-package.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/json-package.esm.dev.js, dist/json-package.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ var changesetsSchema = { $schema: "http://json-schema.org/draft-07/schema#", type: "object", @@ -503,6 +503,15 @@ test("json", async () => { export { schema }; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/json-package.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./json-package.esm.prod.js"); + } else { + module.exports = require("./json-package.esm.dev.js"); + } + `); }); diff --git a/packages/cli/src/build/__tests__/entrypoints.ts b/packages/cli/src/build/__tests__/entrypoints.ts index 1cbb59a6..dc1f9946 100644 --- a/packages/cli/src/build/__tests__/entrypoints.ts +++ b/packages/cli/src/build/__tests__/entrypoints.ts @@ -55,11 +55,20 @@ test("multiple entrypoints", async () => { module.exports = require("./multiple-entrypoints.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/multiple-entrypoints.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/multiple-entrypoints.esm.dev.js, dist/multiple-entrypoints.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ let sum = (a, b) => a + b; export { sum }; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/multiple-entrypoints.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./multiple-entrypoints.esm.prod.js"); + } else { + module.exports = require("./multiple-entrypoints.esm.dev.js"); + } + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.cjs.dev.js, multiply/dist/multiple-entrypoints-multiply.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; @@ -78,11 +87,20 @@ test("multiple entrypoints", async () => { module.exports = require("./multiple-entrypoints-multiply.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.esm.dev.js, multiply/dist/multiple-entrypoints-multiply.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ let multiply = (a, b) => a * b; export { multiply }; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./multiple-entrypoints-multiply.esm.prod.js"); + } else { + module.exports = require("./multiple-entrypoints-multiply.esm.dev.js"); + } + `); }); diff --git a/packages/cli/src/build/__tests__/other.ts b/packages/cli/src/build/__tests__/other.ts index dd750369..58bd5f57 100644 --- a/packages/cli/src/build/__tests__/other.ts +++ b/packages/cli/src/build/__tests__/other.ts @@ -48,7 +48,7 @@ test("browser", async () => { await build(dir); expect(await getDist(dir)).toMatchInlineSnapshot(` - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.cjs.dev.js, dist/browser.browser.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); @@ -67,7 +67,16 @@ test("browser", async () => { exports.default = thing$1; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./browser.browser.cjs.prod.js"); + } else { + module.exports = require("./browser.browser.cjs.dev.js"); + } + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.esm.dev.js, dist/browser.browser.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ let thing = "wow"; { @@ -82,6 +91,15 @@ test("browser", async () => { export default thing$1; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./browser.browser.esm.prod.js"); + } else { + module.exports = require("./browser.browser.esm.dev.js"); + } + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.cjs.dev.js, dist/browser.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; @@ -110,7 +128,7 @@ test("browser", async () => { module.exports = require("./browser.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.esm.dev.js, dist/browser.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ let thing = "wow"; if (typeof window !== "undefined") { @@ -125,6 +143,15 @@ test("browser", async () => { export default thing$1; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./browser.esm.prod.js"); + } else { + module.exports = require("./browser.esm.dev.js"); + } + `); }); @@ -180,7 +207,7 @@ test("typescript", async () => { declare var obj: object; export { obj }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.cjs.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.cjs.d.ts, dist/typescript.esm.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ export * from "./declarations/src/index"; export { default } from "./declarations/src/index"; @@ -230,7 +257,7 @@ test("typescript", async () => { module.exports = require("./typescript.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.esm.dev.js, dist/typescript.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ import * as path from 'path'; export { path }; @@ -241,6 +268,15 @@ test("typescript", async () => { export default thing; export { obj }; + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./typescript.esm.prod.js"); + } else { + module.exports = require("./typescript.esm.dev.js"); + } + `); }); diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index e5bbeea2..7da6c760 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -92,7 +92,8 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { pkg, pkg.entrypoints, aliases, - "node-dev", + "node", + "dev", pkg.project.experimentalFlags.logCompiledFiles ? (filename) => { logger.info( @@ -116,8 +117,8 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { ? [ { format: "es" as const, - entryFileNames: "[name].esm.js", - chunkFileNames: "dist/[name]-[hash].esm.js", + entryFileNames: "[name].esm.dev.js", + chunkFileNames: "dist/[name]-[hash].esm.dev.js", dir: pkg.directory, }, ] @@ -130,7 +131,8 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { pkg, pkg.entrypoints, aliases, - "node-prod", + "node", + "prod", () => {} ), outputs: [ @@ -143,6 +145,16 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { interop, plugins: cjsPlugins, }, + ...(hasModuleField + ? [ + { + format: "es" as const, + entryFileNames: "[name].esm.prod.js", + chunkFileNames: "dist/[name]-[hash].esm.prod.js", + dir: pkg.directory, + }, + ] + : []), ], }); @@ -152,7 +164,14 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { if (pkg.entrypoints[0].json["umd:main"] !== undefined) pkg.entrypoints.forEach((entrypoint) => { configs.push({ - config: getRollupConfig(pkg, [entrypoint], aliases, "umd", () => {}), + config: getRollupConfig( + pkg, + [entrypoint], + aliases, + "browser", + "umd", + () => {} + ), outputs: [ { format: "umd" as const, @@ -195,13 +214,45 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { pkg.entrypoints, aliases, "browser", + "dev", + () => {} + ), + outputs: [ + { + format: "cjs" as const, + entryFileNames: "[name].browser.cjs.dev.js", + chunkFileNames: "dist/[name]-[hash].browser.cjs.dev.js", + dir: pkg.directory, + exports: "named" as const, + interop, + plugins: cjsPlugins, + }, + ...(hasModuleField + ? [ + { + format: "es" as const, + entryFileNames: "[name].browser.esm.dev.js", + chunkFileNames: "dist/[name]-[hash].browser.esm.dev.js", + dir: pkg.directory, + }, + ] + : []), + ], + }); + configs.push({ + config: getRollupConfig( + pkg, + pkg.entrypoints, + aliases, + "browser", + "prod", () => {} ), outputs: [ { format: "cjs" as const, - entryFileNames: "[name].browser.cjs.js", - chunkFileNames: "dist/[name]-[hash].browser.cjs.js", + entryFileNames: "[name].browser.cjs.prod.js", + chunkFileNames: "dist/[name]-[hash].browser.cjs.prod.js", dir: pkg.directory, exports: "named" as const, interop, @@ -211,8 +262,8 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { ? [ { format: "es" as const, - entryFileNames: "[name].browser.esm.js", - chunkFileNames: "dist/[name]-[hash].browser.esm.js", + entryFileNames: "[name].browser.esm.prod.js", + chunkFileNames: "dist/[name]-[hash].browser.esm.prod.js", dir: pkg.directory, }, ] @@ -228,13 +279,45 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { pkg.entrypoints, aliases, "worker", + "dev", + () => {} + ), + outputs: [ + { + format: "cjs" as const, + entryFileNames: "[name].worker.cjs.dev.js", + chunkFileNames: "dist/[name]-[hash].worker.cjs.dev.js", + dir: pkg.directory, + exports: "named" as const, + interop, + plugins: cjsPlugins, + }, + ...(hasModuleField + ? [ + { + format: "es" as const, + entryFileNames: "[name].worker.esm.dev.js", + chunkFileNames: "dist/[name]-[hash].worker.esm.dev.js", + dir: pkg.directory, + }, + ] + : []), + ], + }); + configs.push({ + config: getRollupConfig( + pkg, + pkg.entrypoints, + aliases, + "worker", + "prod", () => {} ), outputs: [ { format: "cjs" as const, - entryFileNames: "[name].worker.cjs.js", - chunkFileNames: "dist/[name]-[hash].worker.cjs.js", + entryFileNames: "[name].worker.cjs.prod.js", + chunkFileNames: "dist/[name]-[hash].worker.cjs.prod.js", dir: pkg.directory, exports: "named" as const, interop, @@ -244,8 +327,8 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { ? [ { format: "es" as const, - entryFileNames: "[name].worker.esm.js", - chunkFileNames: "dist/[name]-[hash].worker.esm.js", + entryFileNames: "[name].worker.esm.prod.js", + chunkFileNames: "dist/[name]-[hash].worker.esm.prod.js", dir: pkg.directory, }, ] diff --git a/packages/cli/src/build/rollup.ts b/packages/cli/src/build/rollup.ts index 7ab4bab0..caf34225 100644 --- a/packages/cli/src/build/rollup.ts +++ b/packages/cli/src/build/rollup.ts @@ -31,29 +31,27 @@ const makeExternalPredicate = (externalArr: string[]) => { return (id: string) => pattern.test(id); }; -export type RollupConfigType = - | "umd" - | "browser" - | "node-dev" - | "node-prod" - | "worker"; +export type RollupConfigEnvironment = "dev" | "prod" | "umd"; + +export type RollupConfigType = "browser" | "node" | "node" | "worker"; export let getRollupConfig = ( pkg: Package, entrypoints: Array, aliases: Aliases, type: RollupConfigType, + env: RollupConfigEnvironment, reportTransformedFile: (filename: string) => void ): RollupOptions => { let external = []; if (pkg.json.peerDependencies) { external.push(...Object.keys(pkg.json.peerDependencies)); } - if (pkg.json.dependencies && type !== "umd") { + if (pkg.json.dependencies && env !== "umd") { external.push(...Object.keys(pkg.json.dependencies)); } - if (type === "node-dev" || type === "node-prod") { + if (type === "node") { external.push(...builtInModules); } @@ -111,7 +109,7 @@ export let getRollupConfig = ( } } case "THIS_IS_UNDEFINED": { - if (type === "umd") { + if (env === "umd") { return; } warnings.push( @@ -145,8 +143,8 @@ export let getRollupConfig = ( } }, } as Plugin, - type === "node-prod" && flowAndNodeDevProdEntry(pkg, warnings), - type === "node-prod" && typescriptDeclarations(pkg), + env === "prod" && flowAndNodeDevProdEntry(pkg, warnings), + env === "prod" && typescriptDeclarations(pkg), babel({ cwd: pkg.project.directory, reportTransformedFile, @@ -163,7 +161,7 @@ export let getRollupConfig = ( } })(), }), - type === "umd" && + env === "umd" && cjs({ include: ["**/node_modules/**", "node_modules/**"], }), @@ -172,25 +170,25 @@ export let getRollupConfig = ( json({ namedExports: false, }), - type === "umd" && + env === "umd" && alias({ entries: aliases, }), resolve({ extensions: EXTENSIONS, - browser: type === "umd", + browser: type === "browser" || type === "worker", customResolveOptions: { - moduleDirectory: type === "umd" ? "node_modules" : [], + moduleDirectory: env === "umd" ? "node_modules" : [], }, }), - type === "umd" && inlineProcessEnvNodeEnv({ sourceMap: true }), - type === "umd" && + env === "umd" && inlineProcessEnvNodeEnv({ sourceMap: true }), + env === "umd" && terser({ sourceMap: true, compress: true, }), - type === "node-prod" && inlineProcessEnvNodeEnv({ sourceMap: false }), - (type === "browser" || type === "umd") && + env === "prod" && inlineProcessEnvNodeEnv({ sourceMap: false }), + type === "browser" && replace({ values: { ["typeof " + "document"]: JSON.stringify("object"), diff --git a/packages/cli/src/entrypoint.ts b/packages/cli/src/entrypoint.ts index 8641da72..4fc09c09 100644 --- a/packages/cli/src/entrypoint.ts +++ b/packages/cli/src/entrypoint.ts @@ -5,21 +5,48 @@ import { JSONValue } from "./utils"; import normalizePath from "normalize-path"; export type ExportsConditions = { + worker?: { + production: { + module?: string; + default: string; + }; + development: { + module?: string; + default: string; + }; + module?: string; + default: string; + }; + browser?: { + production: { + module?: string; + default: string; + }; + development: { + module?: string; + default: string; + }; + module?: string; + default: string; + }; + production?: { + module?: string; + default: string; + }; + development?: { + module?: string; + default: string; + }; module?: string; - default?: string; + default: string; }; -export type ExportsItem = { - browser?: ExportsConditions | string; - worker?: ExportsConditions | string; -} & ExportsConditions; - export class Entrypoint extends Item<{ main?: JSONValue; module?: JSONValue; "umd:main"?: JSONValue; browser?: JSONValue; - exports?: Record; + exports?: Record; preconstruct: { source?: JSONValue; umdName?: JSONValue; diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 2d957190..f9a6e0eb 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -4,7 +4,7 @@ import * as fs from "fs-extra"; import nodePath from "path"; import { Item } from "./item"; import { BatchError, FatalError } from "./errors"; -import { Entrypoint, ExportsItem } from "./entrypoint"; +import { Entrypoint, ExportsConditions } from "./entrypoint"; import jsonParse from "parse-json"; import { errors, confirms } from "./messages"; @@ -21,7 +21,7 @@ import normalizePath from "normalize-path"; function getFieldsUsedInEntrypoints( descriptors: { contents: string | undefined; filename: string }[] -) { +): [Set, boolean, boolean] { let hasBrowserField = false; let hasWorkerField = false; const fields = new Set(["main"]); @@ -33,7 +33,7 @@ function getFieldsUsedInEntrypoints( if (value !== undefined) { fields.add(field); if (field === "exports" && value["."]) { - const conditions: ExportsItem[] = Object.values(value["."]); + const conditions: ExportsConditions[] = Object.values(value["."]); hasBrowserField = hasBrowserField || conditions.some( @@ -66,7 +66,7 @@ function getPlainEntrypointContent( ) { const obj: Partial + string | Record >> = {}; for (const field of fields) { if (field === "browser") { diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index f2f96b30..41074a37 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -1,5 +1,5 @@ import normalizePath from "normalize-path"; -import { Entrypoint, ExportsConditions, ExportsItem } from "./entrypoint"; +import { Entrypoint, ExportsConditions } from "./entrypoint"; import { Package } from "./package"; import * as nodePath from "path"; import { FatalError } from "./errors"; @@ -137,37 +137,39 @@ export const validFieldsFromPkg = { hasWorkerField: boolean, entrypointName: string, forceStrategy?: DistFilenameStrategy - ) { - let obj: Record = { - ".": exportsHelpers.root( - pkg, - hasModuleBuild, - entrypointName, - forceStrategy - ), - }; + ): Record { + let output: Record = {}; + let obj: ExportsConditions = exportsHelpers.root( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy + ); if (hasBrowserField) { - obj["."] = { - browser: exportsHelpers.browser( + obj = { + browser: exportsHelpers.target( pkg, hasModuleBuild, entrypointName, - forceStrategy + forceStrategy, + "browser" ), - ...obj["."], + ...obj, }; } if (hasWorkerField) { - obj["."] = { - worker: exportsHelpers.worker( + obj = { + worker: exportsHelpers.target( pkg, hasModuleBuild, entrypointName, - forceStrategy + forceStrategy, + "worker" ), - ...obj["."], + ...obj, }; } + output["."] = obj; pkg.entrypoints.forEach((entrypoint) => { if (entrypointName === entrypoint.name) { return; @@ -176,7 +178,8 @@ export const validFieldsFromPkg = { .relative(pkg.directory, entrypoint.source) .replace("src/", "") .replace(/\.[tj]sx?$/, ""); - let conditions: ExportsItem = exportsHelpers.root( + + let conditions: ExportsConditions = exportsHelpers.root( pkg, entrypoint.json.module !== undefined, entrypoint.name, @@ -186,11 +189,12 @@ export const validFieldsFromPkg = { if (hasBrowserField) { conditions = { - browser: exportsHelpers.browser( + browser: exportsHelpers.target( pkg, hasModuleBuild, - entrypointName, + entrypoint.name, forceStrategy, + "browser", entrypointPath + "/" ), ...conditions, @@ -198,20 +202,24 @@ export const validFieldsFromPkg = { } if (hasWorkerField) { conditions = { - worker: exportsHelpers.worker( + worker: exportsHelpers.target( pkg, hasModuleBuild, - entrypointName, + entrypoint.name, forceStrategy, + "worker", entrypointPath + "/" ), ...conditions, }; } - - obj[`./${entrypointPath}`] = conditions; + output[`./${entrypointPath}`] = conditions; }); - return obj; + return { + "./package.json": "./package.json", + ...output, + "./": "./", + }; }, }; @@ -223,50 +231,79 @@ const exportsHelpers = { forceStrategy?: DistFilenameStrategy, prefix: string = "" ) { - let safeName = getDistName(pkg, entrypointName, forceStrategy); - - let obj: ExportsConditions = { - default: `./${prefix}dist/${safeName}.cjs.js`, - }; - if (hasModuleBuild) { - obj = { - module: `./${prefix}dist/${safeName}.esm.js`, - ...obj, - }; - } - return obj; + return exportsHelpers.target( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy, + "", + prefix + ); }, - browser( + target( pkg: Package, hasModuleBuild: boolean, entrypointName: string, forceStrategy?: DistFilenameStrategy, + target: string = "", prefix: string = "" ) { - let safeName = getDistName(pkg, entrypointName, forceStrategy); - - let obj: ExportsConditions = { - default: `./${prefix}dist/${safeName}.browser.cjs.js`, + const obj = exportsHelpers.env( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy, + "", + target, + prefix + ); + const production = exportsHelpers.env( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy, + "prod", + target, + prefix + ); + const development = exportsHelpers.env( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy, + "dev", + target, + prefix + ); + return { + production, + development, + ...obj, }; - if (hasModuleBuild) { - obj = { module: `./${prefix}dist/${safeName}.browser.esm.js`, ...obj }; - } - return obj; }, - worker( + env( pkg: Package, hasModuleBuild: boolean, entrypointName: string, forceStrategy?: DistFilenameStrategy, + env: string = "", + target: string = "", prefix: string = "" ) { let safeName = getDistName(pkg, entrypointName, forceStrategy); let obj: ExportsConditions = { - default: `./${prefix}dist/${safeName}.worker.cjs.js`, + default: `./${prefix}dist/${safeName}.${target ? `${target}.` : ""}cjs.${ + env ? `${env}.` : "" + }js`, }; if (hasModuleBuild) { - obj = { module: `./${prefix}dist/${safeName}.worker.esm.js`, ...obj }; + obj = { + module: `./${prefix}dist/${safeName}.${target ? `${target}.` : ""}esm.${ + env ? `${env}.` : "" + }js`, + ...obj, + }; } return obj; }, diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index 02877243..f41907ed 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -26,16 +26,7 @@ export const isFieldValid = { return equal(entrypoint.json.browser, validFields.browser(entrypoint)); }, exports(entrypoint: Entrypoint): boolean { - const validExports = validFields.exports(entrypoint); - if (validExports === undefined) { - return false; - } - Object.keys(validExports).forEach((key) => { - if (!equal(validExports![key], entrypoint.json.exports![key])) { - return false; - } - }); - return true; + return equal(entrypoint.json.exports, validFields.exports(entrypoint)); }, }; From 05f1cddd11c6cfe69e4d9f8c56efa75764a80fcd Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Thu, 16 Dec 2021 10:02:24 -0700 Subject: [PATCH 08/56] make package exports support opt-in. Also add config for extra fields --- packages/cli/src/build/config.ts | 23 +++++++------ packages/cli/src/init.ts | 43 +++++++++++++------------ packages/cli/src/package.ts | 17 ++++++---- packages/cli/src/utils.ts | 48 ++++++++++++++++------------ packages/cli/src/validate-package.ts | 5 ++- packages/cli/src/validate.ts | 4 +++ 6 files changed, 82 insertions(+), 58 deletions(-) diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index 7da6c760..0f17c8c0 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -193,16 +193,19 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { let hasBrowserCondition = false; let hasWorkerCondition = false; - let hasExportsField = typeof pkg.entrypoints[0].json.exports == "object"; - if (hasExportsField) { - hasBrowserCondition = Object.values(pkg.entrypoints[0].json.exports!).some( - (condition) => - typeof condition === "object" && condition.browser !== undefined - ); - hasWorkerCondition = Object.values(pkg.entrypoints[0].json.exports!).some( - (condition) => - typeof condition === "object" && condition.worker !== undefined - ); + if (pkg.json.preconstruct.exports) { + if (typeof pkg.entrypoints[0].json.exports == "object") { + hasBrowserCondition = Object.values( + pkg.entrypoints[0].json.exports! + ).some( + (condition) => + typeof condition === "object" && condition.browser !== undefined + ); + hasWorkerCondition = Object.values(pkg.entrypoints[0].json.exports!).some( + (condition) => + typeof condition === "object" && condition.worker !== undefined + ); + } } let hasBrowserField = pkg.entrypoints[0].json.browser !== undefined; diff --git a/packages/cli/src/init.ts b/packages/cli/src/init.ts index f8563ca0..adad83cc 100644 --- a/packages/cli/src/init.ts +++ b/packages/cli/src/init.ts @@ -93,27 +93,30 @@ async function doInit(pkg: Package) { } } - let someEntrypointsHaveAExportsField = pkg.entrypoints.some( - (entrypoint) => entrypoint.json.exports !== undefined - ); + // usage of package exports is currently opt-in + if (pkg.json.preconstruct.exports) { + let someEntrypointsHaveAExportsField = pkg.entrypoints.some( + (entrypoint) => entrypoint.json.exports !== undefined + ); - let someEntrypointsHaveAnInvalidExportsField = pkg.entrypoints.some( - (entrypoint) => !isFieldValid.exports(entrypoint) - ); - if ( - someEntrypointsHaveAExportsField && - someEntrypointsHaveAnInvalidExportsField - ) { - let shouldFixWorkerField = await confirms.fixExportsField(pkg); - if (shouldFixWorkerField) { - pkg.setFieldOnEntrypoints("exports"); - } else { - throw new FixableError( - errors.fieldMustExistInAllEntrypointsIfExistsDeclinedFixDuringInit( - "exports" - ), - pkg.name - ); + let someEntrypointsHaveAnInvalidExportsField = pkg.entrypoints.some( + (entrypoint) => !isFieldValid.exports(entrypoint) + ); + if ( + someEntrypointsHaveAExportsField && + someEntrypointsHaveAnInvalidExportsField + ) { + let shouldFixWorkerField = await confirms.fixExportsField(pkg); + if (shouldFixWorkerField) { + pkg.setFieldOnEntrypoints("exports"); + } else { + throw new FixableError( + errors.fieldMustExistInAllEntrypointsIfExistsDeclinedFixDuringInit( + "exports" + ), + pkg.name + ); + } } } diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index f9a6e0eb..7f87d94f 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -76,13 +76,15 @@ function getPlainEntrypointContent( getEntrypointName(pkg, entrypointDir) ); } else if (field === "exports") { - obj[field] = validFieldsFromPkg[field]( - pkg, - fields.has("module"), - hasBrowserField, - hasWorkerField, - getEntrypointName(pkg, entrypointDir) - ); + if (pkg.json.preconstruct.exports) { + obj[field] = validFieldsFromPkg[field]( + pkg, + fields.has("module"), + hasBrowserField, + hasWorkerField, + getEntrypointName(pkg, entrypointDir) + ); + } } else { obj[field] = validFieldsFromPkg[field]( pkg, @@ -139,6 +141,7 @@ function createEntrypoints( export class Package extends Item<{ name?: JSONValue; preconstruct: { + exports?: boolean | { extra?: Record }; entrypoints?: JSONValue; }; dependencies?: Record; diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 41074a37..19518b1b 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -215,10 +215,16 @@ export const validFieldsFromPkg = { } output[`./${entrypointPath}`] = conditions; }); + let extra: Record | null = null; + if (typeof pkg.json.preconstruct?.exports === "object") { + if (pkg.json.preconstruct.exports.extra) { + extra = pkg.json.preconstruct.exports.extra; + } + } return { "./package.json": "./package.json", ...output, - "./": "./", + ...extra, }; }, }; @@ -327,26 +333,28 @@ export const validFields = { ); }, exports(entrypoint: Entrypoint, forceStrategy?: DistFilenameStrategy) { - if (typeof entrypoint.json.exports === "undefined") { - return; + if (entrypoint.package.json.preconstruct.exports) { + if (typeof entrypoint.json.exports === "undefined") { + return; + } + const conditions = Object.values(entrypoint.json.exports); + const hasBrowserField = conditions.some( + (condition) => + typeof condition === "object" && condition.browser !== undefined + ); + const hasWorkerField = conditions.some( + (condition) => + typeof condition === "object" && condition.worker !== undefined + ); + return validFieldsFromPkg.exports( + entrypoint.package, + entrypoint.json.module !== undefined, + hasBrowserField, + hasWorkerField, + entrypoint.name, + forceStrategy + ); } - const conditions = Object.values(entrypoint.json.exports); - const hasBrowserField = conditions.some( - (condition) => - typeof condition === "object" && condition.browser !== undefined - ); - const hasWorkerField = conditions.some( - (condition) => - typeof condition === "object" && condition.worker !== undefined - ); - return validFieldsFromPkg.exports( - entrypoint.package, - entrypoint.json.module !== undefined, - hasBrowserField, - hasWorkerField, - entrypoint.name, - forceStrategy - ); }, }; diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 240fe0c4..a8bd3998 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -15,7 +15,9 @@ export async function fixPackage(pkg: Package) { module: pkg.entrypoints.some((x) => x.json.module !== undefined), "umd:main": pkg.entrypoints.some((x) => x.json["umd:main"] !== undefined), browser: pkg.entrypoints.some((x) => x.json.browser !== undefined), - exports: pkg.entrypoints.some((x) => x.json.exports !== undefined), + exports: pkg.json.preconstruct.exports + ? pkg.entrypoints.some((x) => x.json.exports !== undefined) + : false, }; keys(fields) @@ -42,6 +44,7 @@ export function validatePackage(pkg: Package) { module: pkg.entrypoints[0].json.module !== undefined, "umd:main": pkg.entrypoints[0].json["umd:main"] !== undefined, browser: pkg.entrypoints[0].json.browser !== undefined, + // "exports" is missing because it is not a requirement for other entrypoints to have an exports field. }; pkg.entrypoints.forEach((entrypoint) => { diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index f41907ed..39ab682f 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -51,6 +51,10 @@ function validateEntrypoint(entrypoint: Entrypoint, log: boolean) { if (field !== "main" && entrypoint.json[field] === undefined) { continue; } + if (field === "exports" && !entrypoint.package.json.preconstruct.exports) { + // exports field is currently op-in + continue; + } if (!isFieldValid[field](entrypoint)) { let isUsingOldDistFilenames: boolean; let prevDistFilenameStrategy = From a9851058052172128d9451c472dc97d660af9e55 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Fri, 17 Dec 2021 12:16:00 -0700 Subject: [PATCH 09/56] move exports functionality behind global expirimental flag --- __fixtures__/package-exports/package.json | 11 ++++ __fixtures__/package-exports/src/index.ts | 1 + packages/cli/src/__tests__/fix.ts | 53 +++++++++++++++++ packages/cli/src/build/config.ts | 10 ++-- packages/cli/src/dev.ts | 40 ++++++++----- packages/cli/src/entrypoint.ts | 39 +----------- packages/cli/src/init.ts | 27 --------- packages/cli/src/messages.ts | 2 +- packages/cli/src/package.ts | 36 ++++++++++-- packages/cli/src/project.ts | 2 + packages/cli/src/utils.ts | 72 +++++++++++------------ packages/cli/src/validate-package.ts | 25 ++++++-- packages/cli/src/validate.ts | 18 ++---- 13 files changed, 187 insertions(+), 149 deletions(-) create mode 100644 __fixtures__/package-exports/package.json create mode 100644 __fixtures__/package-exports/src/index.ts diff --git a/__fixtures__/package-exports/package.json b/__fixtures__/package-exports/package.json new file mode 100644 index 00000000..2563375b --- /dev/null +++ b/__fixtures__/package-exports/package.json @@ -0,0 +1,11 @@ +{ + "name": "package-exports", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "private": true, + "module": "dist/package-exports.esm.js", + "preconstruct": { + "exports": true + } +} diff --git a/__fixtures__/package-exports/src/index.ts b/__fixtures__/package-exports/src/index.ts new file mode 100644 index 00000000..af8ec620 --- /dev/null +++ b/__fixtures__/package-exports/src/index.ts @@ -0,0 +1 @@ +export default "something"; diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index dd140830..e8fc67cd 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -70,6 +70,59 @@ test("set main and module field", async () => { `); }); +test("set exports field when opt-in", async () => { + let tmpPath = f.copy("package-exports"); + + await modifyPkg(tmpPath, (json) => { + json.exports = { ".": { browser: {}, worker: {} } }; + }); + + await fix(tmpPath); + + let pkg = await getPkg(tmpPath); + + expect(pkg).toMatchInlineSnapshot(` + Object { + "exports": Object { + ".": Object { + "browser": Object { + "default": "./dist/package-exports.browser.cjs.js", + "module": "./dist/package-exports.browser.esm.dev.js", + "production": Object { + "default": "./dist/package-exports.browser.cjs.prod.js", + "module": "./dist/package-exports.browser.esm.prod.js", + }, + }, + "default": "./dist/package-exports.cjs.js", + "module": "./dist/package-exports.esm.dev.js", + "production": Object { + "default": "./dist/package-exports.cjs.prod.js", + "module": "./dist/package-exports.esm.prod.js", + }, + "worker": Object { + "default": "./dist/package-exports.worker.cjs.js", + "module": "./dist/package-exports.worker.esm.dev.js", + "production": Object { + "default": "./dist/package-exports.worker.cjs.prod.js", + "module": "./dist/package-exports.worker.esm.prod.js", + }, + }, + }, + "./package.json": "./package.json", + }, + "license": "MIT", + "main": "dist/package-exports.cjs.js", + "module": "dist/package-exports.esm.js", + "name": "package-exports", + "preconstruct": Object { + "exports": true, + }, + "private": true, + "version": "1.0.0", + } + `); +}); + test("new dist filenames", async () => { let tmpPath = f.copy("basic-package"); diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index 0f17c8c0..fed0ffac 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -193,15 +193,13 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { let hasBrowserCondition = false; let hasWorkerCondition = false; - if (pkg.json.preconstruct.exports) { - if (typeof pkg.entrypoints[0].json.exports == "object") { - hasBrowserCondition = Object.values( - pkg.entrypoints[0].json.exports! - ).some( + if (pkg.project.experimentalFlags.exports) { + if (typeof pkg.json.exports == "object") { + hasBrowserCondition = Object.values(pkg.json.exports!).some( (condition) => typeof condition === "object" && condition.browser !== undefined ); - hasWorkerCondition = Object.values(pkg.entrypoints[0].json.exports!).some( + hasWorkerCondition = Object.values(pkg.json.exports!).some( (condition) => typeof condition === "object" && condition.worker !== undefined ); diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index 5e1a64de..78c6cc9a 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -207,23 +207,31 @@ unregister(); ) ); } - if (entrypoint.json.exports) { - let exportsField = validFields.exports(entrypoint); - if (exportsField?.["."] && typeof exportsField["."] === "object") { - for (let key of Object.keys(exportsField["."])) { - if (["browser", "worker"].includes(key)) { - for (let key2 of Object.keys( - (exportsField["."] as any)[key] - )) { - promises.push( - fs.symlink( - entrypoint.source, - path.join( - entrypoint.directory, - (exportsField["."] as any)[key][key2] + if ( + pkg.project.experimentalFlags.exports && + pkg.json.preconstruct.exports + ) { + if (entrypoint.json.exports) { + let exportsField = validFields.exports(pkg); + if ( + exportsField?.["."] && + typeof exportsField["."] === "object" + ) { + for (let key of Object.keys(exportsField["."])) { + if (["browser", "worker"].includes(key)) { + for (let key2 of Object.keys( + (exportsField["."] as any)[key] + )) { + promises.push( + fs.symlink( + entrypoint.source, + path.join( + entrypoint.directory, + (exportsField["."] as any)[key][key2] + ) ) - ) - ); + ); + } } } } diff --git a/packages/cli/src/entrypoint.ts b/packages/cli/src/entrypoint.ts index 4fc09c09..72ead774 100644 --- a/packages/cli/src/entrypoint.ts +++ b/packages/cli/src/entrypoint.ts @@ -1,46 +1,9 @@ import nodePath from "path"; import { Item } from "./item"; -import { Package } from "./package"; +import { Package, ExportsConditions } from "./package"; import { JSONValue } from "./utils"; import normalizePath from "normalize-path"; -export type ExportsConditions = { - worker?: { - production: { - module?: string; - default: string; - }; - development: { - module?: string; - default: string; - }; - module?: string; - default: string; - }; - browser?: { - production: { - module?: string; - default: string; - }; - development: { - module?: string; - default: string; - }; - module?: string; - default: string; - }; - production?: { - module?: string; - default: string; - }; - development?: { - module?: string; - default: string; - }; - module?: string; - default: string; -}; - export class Entrypoint extends Item<{ main?: JSONValue; module?: JSONValue; diff --git a/packages/cli/src/init.ts b/packages/cli/src/init.ts index adad83cc..6383cdb9 100644 --- a/packages/cli/src/init.ts +++ b/packages/cli/src/init.ts @@ -93,33 +93,6 @@ async function doInit(pkg: Package) { } } - // usage of package exports is currently opt-in - if (pkg.json.preconstruct.exports) { - let someEntrypointsHaveAExportsField = pkg.entrypoints.some( - (entrypoint) => entrypoint.json.exports !== undefined - ); - - let someEntrypointsHaveAnInvalidExportsField = pkg.entrypoints.some( - (entrypoint) => !isFieldValid.exports(entrypoint) - ); - if ( - someEntrypointsHaveAExportsField && - someEntrypointsHaveAnInvalidExportsField - ) { - let shouldFixWorkerField = await confirms.fixExportsField(pkg); - if (shouldFixWorkerField) { - pkg.setFieldOnEntrypoints("exports"); - } else { - throw new FixableError( - errors.fieldMustExistInAllEntrypointsIfExistsDeclinedFixDuringInit( - "exports" - ), - pkg.name - ); - } - } - } - await Promise.all(pkg.entrypoints.map((x) => x.save())); } diff --git a/packages/cli/src/messages.ts b/packages/cli/src/messages.ts index f6cffda4..e335d63e 100644 --- a/packages/cli/src/messages.ts +++ b/packages/cli/src/messages.ts @@ -39,7 +39,7 @@ export let confirms = { "would you like to fix the browser build?" ), fixExportsField: createPromptConfirmLoader( - "would you like to fix the exports build(s)?" + "would you like to fix the exports field?" ), createEntrypointPkgJson: createPromptConfirmLoader( "A package.json file does not exist for this entrypoint, would you like to create one automatically?" diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 7f87d94f..37a152da 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -4,7 +4,7 @@ import * as fs from "fs-extra"; import nodePath from "path"; import { Item } from "./item"; import { BatchError, FatalError } from "./errors"; -import { Entrypoint, ExportsConditions } from "./entrypoint"; +import { Entrypoint } from "./entrypoint"; import jsonParse from "parse-json"; import { errors, confirms } from "./messages"; @@ -76,7 +76,7 @@ function getPlainEntrypointContent( getEntrypointName(pkg, entrypointDir) ); } else if (field === "exports") { - if (pkg.json.preconstruct.exports) { + if (pkg.project.experimentalFlags.exports) { obj[field] = validFieldsFromPkg[field]( pkg, fields.has("module"), @@ -138,12 +138,38 @@ function createEntrypoints( ); } +export type ExportsConditions = { + worker?: { + production: { + module?: string; + default: string; + }; + module?: string; + default: string; + }; + browser?: { + production: { + module?: string; + default: string; + }; + module?: string; + default: string; + }; + production?: { + module?: string; + default: string; + }; + module?: string; + default: string; +}; + export class Package extends Item<{ name?: JSONValue; preconstruct: { - exports?: boolean | { extra?: Record }; + exports?: boolean | { extra?: Record }; entrypoints?: JSONValue; }; + exports?: Record; dependencies?: Record; peerDependencies?: Record; }> { @@ -301,9 +327,7 @@ export class Package extends Item<{ return pkg; } - setFieldOnEntrypoints( - field: "main" | "browser" | "module" | "umd:main" | "exports" - ) { + setFieldOnEntrypoints(field: "main" | "browser" | "module" | "umd:main") { this.entrypoints.forEach((entrypoint) => { entrypoint.json = setFieldInOrder( entrypoint.json, diff --git a/packages/cli/src/project.ts b/packages/cli/src/project.ts index 0646ad5a..a9cf52c9 100644 --- a/packages/cli/src/project.ts +++ b/packages/cli/src/project.ts @@ -26,6 +26,7 @@ export class Project extends Item<{ packages?: JSONValue; distFilenameStrategy?: JSONValue; ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports?: boolean; logCompiledFiles?: JSONValue; typeScriptProxyFileWithImportEqualsRequireAndExportEquals?: JSONValue; keepDynamicImportAsDynamicImportInCommonJS?: JSONValue; @@ -36,6 +37,7 @@ export class Project extends Item<{ let config = this.json.preconstruct.___experimentalFlags_WILL_CHANGE_IN_PATCH || {}; return { + exports: !!config.exports, logCompiledFiles: !!config.logCompiledFiles, typeScriptProxyFileWithImportEqualsRequireAndExportEquals: !!config.typeScriptProxyFileWithImportEqualsRequireAndExportEquals, keepDynamicImportAsDynamicImportInCommonJS: !!config.keepDynamicImportAsDynamicImportInCommonJS, diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 19518b1b..8c4e8a82 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -1,6 +1,6 @@ import normalizePath from "normalize-path"; -import { Entrypoint, ExportsConditions } from "./entrypoint"; -import { Package } from "./package"; +import { Entrypoint } from "./entrypoint"; +import { Package, ExportsConditions } from "./package"; import * as nodePath from "path"; import { FatalError } from "./errors"; @@ -215,10 +215,13 @@ export const validFieldsFromPkg = { } output[`./${entrypointPath}`] = conditions; }); - let extra: Record | null = null; - if (typeof pkg.json.preconstruct?.exports === "object") { + let extra: Record | null = null; + if ( + pkg.project.experimentalFlags.exports && + typeof pkg.json.preconstruct.exports === "object" + ) { if (pkg.json.preconstruct.exports.extra) { - extra = pkg.json.preconstruct.exports.extra; + extra = pkg.json.preconstruct.exports.extra as Record; } } return { @@ -272,18 +275,8 @@ const exportsHelpers = { target, prefix ); - const development = exportsHelpers.env( - pkg, - hasModuleBuild, - entrypointName, - forceStrategy, - "dev", - target, - prefix - ); return { production, - development, ...obj, }; }, @@ -304,9 +297,10 @@ const exportsHelpers = { }js`, }; if (hasModuleBuild) { + // esm doesn't support conditional imports so if env is not set we default to dev version obj = { module: `./${prefix}dist/${safeName}.${target ? `${target}.` : ""}esm.${ - env ? `${env}.` : "" + env ? `${env}.` : "dev." }js`, ...obj, }; @@ -332,29 +326,31 @@ export const validFields = { entrypoint.name ); }, - exports(entrypoint: Entrypoint, forceStrategy?: DistFilenameStrategy) { - if (entrypoint.package.json.preconstruct.exports) { - if (typeof entrypoint.json.exports === "undefined") { - return; - } - const conditions = Object.values(entrypoint.json.exports); - const hasBrowserField = conditions.some( - (condition) => - typeof condition === "object" && condition.browser !== undefined - ); - const hasWorkerField = conditions.some( - (condition) => - typeof condition === "object" && condition.worker !== undefined - ); - return validFieldsFromPkg.exports( - entrypoint.package, - entrypoint.json.module !== undefined, - hasBrowserField, - hasWorkerField, - entrypoint.name, - forceStrategy - ); + exports(pkg: Package, forceStrategy?: DistFilenameStrategy) { + if (typeof pkg.json.exports === "undefined") { + return; } + const conditions = Object.values(pkg.json.exports); + const hasBrowserField = conditions.some( + (condition) => + typeof condition === "object" && condition.browser !== undefined + ); + const hasWorkerField = conditions.some( + (condition) => + typeof condition === "object" && condition.worker !== undefined + ); + + const hasModuleField = pkg.entrypoints.some( + (entrypoint) => entrypoint.json.module !== undefined + ); + return validFieldsFromPkg.exports( + pkg, + hasModuleField, + hasBrowserField, + hasWorkerField, + pkg.name, + forceStrategy + ); }, }; diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index a8bd3998..7c795a56 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -3,6 +3,8 @@ import resolveFrom from "resolve-from"; import chalk from "chalk"; import { errors } from "./messages"; import { Package } from "./package"; +import { isFieldValid } from "./validate"; +import { validFields, setFieldInOrder } from "./utils"; let keys: (obj: Obj) => (keyof Obj)[] = Object.keys; @@ -15,11 +17,10 @@ export async function fixPackage(pkg: Package) { module: pkg.entrypoints.some((x) => x.json.module !== undefined), "umd:main": pkg.entrypoints.some((x) => x.json["umd:main"] !== undefined), browser: pkg.entrypoints.some((x) => x.json.browser !== undefined), - exports: pkg.json.preconstruct.exports - ? pkg.entrypoints.some((x) => x.json.exports !== undefined) - : false, }; + pkg.json = setFieldInOrder(pkg.json, "exports", validFields.exports(pkg)); + keys(fields) .filter((x) => fields[x]) .forEach((field) => { @@ -44,9 +45,25 @@ export function validatePackage(pkg: Package) { module: pkg.entrypoints[0].json.module !== undefined, "umd:main": pkg.entrypoints[0].json["umd:main"] !== undefined, browser: pkg.entrypoints[0].json.browser !== undefined, - // "exports" is missing because it is not a requirement for other entrypoints to have an exports field. + // "exports" is missing because it is validated on the root package }; + if ( + pkg.project.experimentalFlags.exports && + pkg.json.preconstruct.exports && + pkg.json.exports && + !isFieldValid.exports(pkg) + ) { + throw new FixableError( + errors.invalidField( + "exports", + pkg.json.exports, + validFields.exports(pkg) + ), + pkg.name + ); + } + pkg.entrypoints.forEach((entrypoint) => { keys(fields).forEach((field) => { if (entrypoint.json[field] && !fields[field]) { diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index 39ab682f..565d8f09 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -1,4 +1,5 @@ import { Project } from "./project"; +import { Package } from "./package"; import { Entrypoint } from "./entrypoint"; import { errors, successes, infos } from "./messages"; import { BatchError, FatalError, FixableError } from "./errors"; @@ -25,8 +26,8 @@ export const isFieldValid = { browser(entrypoint: Entrypoint): boolean { return equal(entrypoint.json.browser, validFields.browser(entrypoint)); }, - exports(entrypoint: Entrypoint): boolean { - return equal(entrypoint.json.exports, validFields.exports(entrypoint)); + exports(pkg: Package): boolean { + return equal(pkg.json.exports, validFields.exports(pkg)); }, }; @@ -41,20 +42,10 @@ function validateEntrypoint(entrypoint: Entrypoint, log: boolean) { logger.info(infos.validEntrypoint, entrypoint.name); } const fatalErrors: FatalError[] = []; - for (const field of [ - "main", - "module", - "umd:main", - "browser", - "exports", - ] as const) { + for (const field of ["main", "module", "umd:main", "browser"] as const) { if (field !== "main" && entrypoint.json[field] === undefined) { continue; } - if (field === "exports" && !entrypoint.package.json.preconstruct.exports) { - // exports field is currently op-in - continue; - } if (!isFieldValid[field](entrypoint)) { let isUsingOldDistFilenames: boolean; let prevDistFilenameStrategy = @@ -123,6 +114,7 @@ export const FORMER_FLAGS_THAT_ARE_ENABLED_NOW = new Set([ ]); export const EXPERIMENTAL_FLAGS = new Set([ + "exports", "logCompiledFiles", "typeScriptProxyFileWithImportEqualsRequireAndExportEquals", "keepDynamicImportAsDynamicImportInCommonJS", From 008c8e04631287bce817d77a8f5e6e159084cff3 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Fri, 17 Dec 2021 12:51:12 -0700 Subject: [PATCH 10/56] remove extra config check --- packages/cli/src/package.ts | 2 +- packages/cli/src/validate-package.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 37a152da..4b3ce59e 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -166,7 +166,7 @@ export type ExportsConditions = { export class Package extends Item<{ name?: JSONValue; preconstruct: { - exports?: boolean | { extra?: Record }; + exports?: { extra?: Record }; entrypoints?: JSONValue; }; exports?: Record; diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 7c795a56..1aacdcf5 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -50,7 +50,6 @@ export function validatePackage(pkg: Package) { if ( pkg.project.experimentalFlags.exports && - pkg.json.preconstruct.exports && pkg.json.exports && !isFieldValid.exports(pkg) ) { From 7522d55688aaf50abedf844e9eedabafa377b779 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Fri, 17 Dec 2021 13:46:51 -0700 Subject: [PATCH 11/56] add note to docs that exports feature is experimental --- site/src/pages/configuration.mdx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index 4bdd1628..d1c05c1e 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -184,7 +184,7 @@ The `browser` field specifies alias files exclusive to browsers. This allows you **Note:** Those files are not meant to be consumed by browsers "as is". They just assume browser-like environment, but they still can contain for example references to `process.env.NODE_ENV` as that is meant to be replaced by a consuming bundler. -### `exports` +### `exports` (experimental) The exports field allows you to specify custom conditional exports for various environments. You can use this to create custom builds targeted for these specific environments. Currently, only "browser" and "worker" conditionals are supported along with their "module" and "default" variants. Here is an example config using "browser" and "worker" conditionals: @@ -206,3 +206,16 @@ Here is an example config using "browser" and "worker" conditionals: } } ``` + +**Note:** This is currently an experimental feature and may change in the future. To opt in you can can add `___experimentalFlags_WILL_CHANGE_IN_PATCH.exports = true` in your project's preconstruct config. +Here is an example of how to opt-in to this feature: + +```json +{ + "preconstruct": { + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true, + } + } +} +``` From 83b436eac80c97571a835ba9c63a9555970b8b7c Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Mon, 14 Feb 2022 14:34:50 -0700 Subject: [PATCH 12/56] add test to assert that order of conditions are correct --- packages/cli/src/__tests__/fix.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index e8fc67cd..626e15b0 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -81,6 +81,14 @@ test("set exports field when opt-in", async () => { let pkg = await getPkg(tmpPath); + // Assert that the order of conditions are correct. + const conditionsOrder = Object.keys(pkg.exports['.']); + expect(conditionsOrder).toEqual(['worker', 'browser', 'production', 'module', 'default']); + const subConditionsOrder = Object.keys(pkg.exports['.'].browser); + expect(subConditionsOrder).toEqual(['production', 'module', 'default']); + + // NOTE: The order of the conditions is important and JEST is sorting the keys alphabetically. + // The tests above actually assert that the order is correct. expect(pkg).toMatchInlineSnapshot(` Object { "exports": Object { From 1d2620ac3c2739fb61228830a703f3d13ceae953 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Mon, 14 Feb 2022 15:33:37 -0700 Subject: [PATCH 13/56] allow user to configure what conditionals they'd like to support --- __fixtures__/package-exports/package.json | 5 +- packages/cli/src/__tests__/fix.ts | 27 ++-- packages/cli/src/project.ts | 2 +- packages/cli/src/utils.ts | 167 +++++++++++----------- packages/cli/src/validate-package.ts | 2 +- packages/cli/src/validate.ts | 5 +- site/src/pages/configuration.mdx | 42 +++--- 7 files changed, 134 insertions(+), 116 deletions(-) diff --git a/__fixtures__/package-exports/package.json b/__fixtures__/package-exports/package.json index 2563375b..e68861ec 100644 --- a/__fixtures__/package-exports/package.json +++ b/__fixtures__/package-exports/package.json @@ -6,6 +6,9 @@ "private": true, "module": "dist/package-exports.esm.js", "preconstruct": { - "exports": true + "exports": ["worker", "browser", "module"], + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } } } diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index 626e15b0..a8e39579 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -73,19 +73,21 @@ test("set main and module field", async () => { test("set exports field when opt-in", async () => { let tmpPath = f.copy("package-exports"); - await modifyPkg(tmpPath, (json) => { - json.exports = { ".": { browser: {}, worker: {} } }; - }); - await fix(tmpPath); let pkg = await getPkg(tmpPath); // Assert that the order of conditions are correct. - const conditionsOrder = Object.keys(pkg.exports['.']); - expect(conditionsOrder).toEqual(['worker', 'browser', 'production', 'module', 'default']); - const subConditionsOrder = Object.keys(pkg.exports['.'].browser); - expect(subConditionsOrder).toEqual(['production', 'module', 'default']); + const conditionsOrder = Object.keys(pkg.exports["."]); + expect(conditionsOrder).toEqual([ + "worker", + "browser", + "production", + "module", + "default", + ]); + const subConditionsOrder = Object.keys(pkg.exports["."].browser); + expect(subConditionsOrder).toEqual(["production", "module", "default"]); // NOTE: The order of the conditions is important and JEST is sorting the keys alphabetically. // The tests above actually assert that the order is correct. @@ -123,7 +125,14 @@ test("set exports field when opt-in", async () => { "module": "dist/package-exports.esm.js", "name": "package-exports", "preconstruct": Object { - "exports": true, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": Object { + "exports": true, + }, + "exports": Array [ + "worker", + "browser", + "module", + ], }, "private": true, "version": "1.0.0", diff --git a/packages/cli/src/project.ts b/packages/cli/src/project.ts index a9cf52c9..f227dab3 100644 --- a/packages/cli/src/project.ts +++ b/packages/cli/src/project.ts @@ -26,7 +26,7 @@ export class Project extends Item<{ packages?: JSONValue; distFilenameStrategy?: JSONValue; ___experimentalFlags_WILL_CHANGE_IN_PATCH: { - exports?: boolean; + exports?: boolean | string[]; logCompiledFiles?: JSONValue; typeScriptProxyFileWithImportEqualsRequireAndExportEquals?: JSONValue; keepDynamicImportAsDynamicImportInCommonJS?: JSONValue; diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 8c4e8a82..b449360f 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -139,81 +139,81 @@ export const validFieldsFromPkg = { forceStrategy?: DistFilenameStrategy ): Record { let output: Record = {}; - let obj: ExportsConditions = exportsHelpers.root( - pkg, - hasModuleBuild, - entrypointName, - forceStrategy - ); - if (hasBrowserField) { - obj = { - browser: exportsHelpers.target( + pkg.entrypoints.forEach((entrypoint) => { + if (entrypointName === entrypoint.name) { + let obj: ExportsConditions = exportsHelpers.root( pkg, hasModuleBuild, entrypointName, - forceStrategy, - "browser" - ), - ...obj, - }; - } - if (hasWorkerField) { - obj = { - worker: exportsHelpers.target( + forceStrategy + ); + if (hasBrowserField) { + obj = { + browser: exportsHelpers.target( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy, + "browser" + ), + ...obj, + }; + } + if (hasWorkerField) { + obj = { + worker: exportsHelpers.target( + pkg, + hasModuleBuild, + entrypointName, + forceStrategy, + "worker" + ), + ...obj, + }; + } + output["."] = obj; + } else { + const entrypointPath = nodePath + .relative(pkg.directory, entrypoint.source) + .replace("src/", "") + .replace(/\.[tj]sx?$/, ""); + + let conditions: ExportsConditions = exportsHelpers.root( pkg, - hasModuleBuild, - entrypointName, + entrypoint.json.module !== undefined, + entrypoint.name, forceStrategy, - "worker" - ), - ...obj, - }; - } - output["."] = obj; - pkg.entrypoints.forEach((entrypoint) => { - if (entrypointName === entrypoint.name) { - return; - } - const entrypointPath = nodePath - .relative(pkg.directory, entrypoint.source) - .replace("src/", "") - .replace(/\.[tj]sx?$/, ""); + entrypointPath + "/" + ); - let conditions: ExportsConditions = exportsHelpers.root( - pkg, - entrypoint.json.module !== undefined, - entrypoint.name, - forceStrategy, - entrypointPath + "/" - ); - - if (hasBrowserField) { - conditions = { - browser: exportsHelpers.target( - pkg, - hasModuleBuild, - entrypoint.name, - forceStrategy, - "browser", - entrypointPath + "/" - ), - ...conditions, - }; - } - if (hasWorkerField) { - conditions = { - worker: exportsHelpers.target( - pkg, - hasModuleBuild, - entrypoint.name, - forceStrategy, - "worker", - entrypointPath + "/" - ), - ...conditions, - }; + if (hasBrowserField) { + conditions = { + browser: exportsHelpers.target( + pkg, + hasModuleBuild, + entrypoint.name, + forceStrategy, + "browser", + entrypointPath + "/" + ), + ...conditions, + }; + } + if (hasWorkerField) { + conditions = { + worker: exportsHelpers.target( + pkg, + hasModuleBuild, + entrypoint.name, + forceStrategy, + "worker", + entrypointPath + "/" + ), + ...conditions, + }; + } + output[`./${entrypointPath}`] = conditions; } - output[`./${entrypointPath}`] = conditions; }); let extra: Record | null = null; if ( @@ -327,22 +327,27 @@ export const validFields = { ); }, exports(pkg: Package, forceStrategy?: DistFilenameStrategy) { - if (typeof pkg.json.exports === "undefined") { + // skip if not enabled for the project + if (!pkg.project.experimentalFlags.exports) { + return; + } + // skip if not enabled for the package + if (!pkg.json.preconstruct.exports) { return; } - const conditions = Object.values(pkg.json.exports); - const hasBrowserField = conditions.some( - (condition) => - typeof condition === "object" && condition.browser !== undefined - ); - const hasWorkerField = conditions.some( - (condition) => - typeof condition === "object" && condition.worker !== undefined - ); - const hasModuleField = pkg.entrypoints.some( - (entrypoint) => entrypoint.json.module !== undefined - ); + // default values when `exports = true`; + let hasWorkerField = false; + let hasBrowserField = true; + let hasModuleField = true; + + const conditions = pkg.json.preconstruct.exports; + if (Array.isArray(conditions)) { + hasWorkerField = conditions.includes("worker"); + hasBrowserField = conditions.includes("browser"); + hasModuleField = conditions.includes("module"); + } + return validFieldsFromPkg.exports( pkg, hasModuleField, diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 1aacdcf5..9a05d797 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -50,7 +50,7 @@ export function validatePackage(pkg: Package) { if ( pkg.project.experimentalFlags.exports && - pkg.json.exports && + pkg.json.preconstruct.exports && !isFieldValid.exports(pkg) ) { throw new FixableError( diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index 565d8f09..238c523d 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -27,7 +27,10 @@ export const isFieldValid = { return equal(entrypoint.json.browser, validFields.browser(entrypoint)); }, exports(pkg: Package): boolean { - return equal(pkg.json.exports, validFields.exports(pkg)); + // we use JSON string compare and not `fast-deep-equal` function because we also need to assert that order of conditions is correct. + const currentExports = JSON.stringify(pkg.json.exports); + const generatedExports = JSON.stringify(validFields.exports(pkg)); + return currentExports === generatedExports; }, }; diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index d1c05c1e..901bfc4a 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -185,37 +185,35 @@ The `browser` field specifies alias files exclusive to browsers. This allows you **Note:** Those files are not meant to be consumed by browsers "as is". They just assume browser-like environment, but they still can contain for example references to `process.env.NODE_ENV` as that is meant to be replaced by a consuming bundler. ### `exports` (experimental) -The exports field allows you to specify custom conditional exports for various environments. You can use this to create custom builds targeted for these specific environments. Currently, only "browser" and "worker" conditionals are supported along with their "module" and "default" variants. -Here is an example config using "browser" and "worker" conditionals: +The exports field allows you to specify custom conditional exports for various environments. You can use this to create custom builds targeted for these specific environments. Currently, only "browser", "worker", and "module" conditionals are supported. -```json +To opt into this experimental feature, you must enable it in the root of your project by setting the `exports` experimental flag in your preconstruct config section of your `package.json` file to "true". +```diff { - "exports": { - ".": { - "worker": { - "module": "./dist/my-package.worker.esm.js", - "default": "./dist/my-package.worker.cjs.js" - }, - "browser": { - "module": "./dist/my-package.browser.esm.js", - "default": "./dist/my-package.browser.cjs.js" - }, - "module": "./dist/my-package.esm.js", - "default": "./dist/my-package.cjs.js" - } + "name": "sample-package", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "private": true, + "preconstruct": { ++ "___experimentalFlags_WILL_CHANGE_IN_PATCH": { ++ "exports": true ++ }, } } ``` -**Note:** This is currently an experimental feature and may change in the future. To opt in you can can add `___experimentalFlags_WILL_CHANGE_IN_PATCH.exports = true` in your project's preconstruct config. -Here is an example of how to opt-in to this feature: +Additionally, you'll also need to enable the feature on each individual pacakge and provide the list of conditionals you'd like to support. -```json +```diff { + "name": "@sample/package", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "private": true, "preconstruct": { - "___experimentalFlags_WILL_CHANGE_IN_PATCH": { - "exports": true, - } ++ "exports": ["browser", "worker", "module"] } } ``` From ea633db5b7000dcf05438a7c4f42b50e0127d2c2 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Mon, 14 Feb 2022 16:00:13 -0700 Subject: [PATCH 14/56] make the type for conditions a little more strict --- packages/cli/src/project.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/project.ts b/packages/cli/src/project.ts index f227dab3..67313822 100644 --- a/packages/cli/src/project.ts +++ b/packages/cli/src/project.ts @@ -8,6 +8,8 @@ import { validateIncludedFiles } from "./validate-included-files"; import { FatalError } from "./errors"; import { JSONValue } from "./utils"; +type ExportsCondition = "browser" | "worker" | "module" | "default"; + const allSettled = (promises: Promise[]) => Promise.all( promises.map((promise) => @@ -26,7 +28,7 @@ export class Project extends Item<{ packages?: JSONValue; distFilenameStrategy?: JSONValue; ___experimentalFlags_WILL_CHANGE_IN_PATCH: { - exports?: boolean | string[]; + exports?: boolean | ExportsCondition[]; logCompiledFiles?: JSONValue; typeScriptProxyFileWithImportEqualsRequireAndExportEquals?: JSONValue; keepDynamicImportAsDynamicImportInCommonJS?: JSONValue; From 45e94c3d22ef4d6f89687c474c3d587f62111762 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Mon, 14 Feb 2022 17:09:04 -0700 Subject: [PATCH 15/56] validate sorting of all fields --- __fixtures__/package-exports/package.json | 4 ++- packages/cli/src/__tests__/fix.ts | 12 +++++--- packages/cli/src/package.ts | 7 ++++- packages/cli/src/project.ts | 4 +-- packages/cli/src/utils.ts | 2 +- packages/cli/src/validate.ts | 37 ++++++++++++++++++++--- site/src/pages/configuration.mdx | 4 ++- 7 files changed, 54 insertions(+), 16 deletions(-) diff --git a/__fixtures__/package-exports/package.json b/__fixtures__/package-exports/package.json index e68861ec..992ac1ae 100644 --- a/__fixtures__/package-exports/package.json +++ b/__fixtures__/package-exports/package.json @@ -6,7 +6,9 @@ "private": true, "module": "dist/package-exports.esm.js", "preconstruct": { - "exports": ["worker", "browser", "module"], + "exports": { + "conditions": ["worker", "browser", "module"] + }, "___experimentalFlags_WILL_CHANGE_IN_PATCH": { "exports": true } diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index a8e39579..623ae164 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -128,11 +128,13 @@ test("set exports field when opt-in", async () => { "___experimentalFlags_WILL_CHANGE_IN_PATCH": Object { "exports": true, }, - "exports": Array [ - "worker", - "browser", - "module", - ], + "exports": Object { + "conditions": Array [ + "worker", + "browser", + "module", + ], + }, }, "private": true, "version": "1.0.0", diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 4b3ce59e..294efc6f 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -163,10 +163,15 @@ export type ExportsConditions = { default: string; }; +export type ExportsCondition = "browser" | "worker" | "module" | "default"; + export class Package extends Item<{ name?: JSONValue; preconstruct: { - exports?: { extra?: Record }; + exports?: { + extra?: Record; + conditions?: ExportsConditions[]; + }; entrypoints?: JSONValue; }; exports?: Record; diff --git a/packages/cli/src/project.ts b/packages/cli/src/project.ts index 67313822..a9cf52c9 100644 --- a/packages/cli/src/project.ts +++ b/packages/cli/src/project.ts @@ -8,8 +8,6 @@ import { validateIncludedFiles } from "./validate-included-files"; import { FatalError } from "./errors"; import { JSONValue } from "./utils"; -type ExportsCondition = "browser" | "worker" | "module" | "default"; - const allSettled = (promises: Promise[]) => Promise.all( promises.map((promise) => @@ -28,7 +26,7 @@ export class Project extends Item<{ packages?: JSONValue; distFilenameStrategy?: JSONValue; ___experimentalFlags_WILL_CHANGE_IN_PATCH: { - exports?: boolean | ExportsCondition[]; + exports?: boolean; logCompiledFiles?: JSONValue; typeScriptProxyFileWithImportEqualsRequireAndExportEquals?: JSONValue; keepDynamicImportAsDynamicImportInCommonJS?: JSONValue; diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index b449360f..675eb349 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -341,7 +341,7 @@ export const validFields = { let hasBrowserField = true; let hasModuleField = true; - const conditions = pkg.json.preconstruct.exports; + const conditions = pkg.json.preconstruct.exports.conditions; if (Array.isArray(conditions)) { hasWorkerField = conditions.includes("worker"); hasBrowserField = conditions.includes("browser"); diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index 238c523d..488a609e 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -27,13 +27,42 @@ export const isFieldValid = { return equal(entrypoint.json.browser, validFields.browser(entrypoint)); }, exports(pkg: Package): boolean { - // we use JSON string compare and not `fast-deep-equal` function because we also need to assert that order of conditions is correct. - const currentExports = JSON.stringify(pkg.json.exports); - const generatedExports = JSON.stringify(validFields.exports(pkg)); - return currentExports === generatedExports; + const generated = validFields.exports(pkg); + if (!equal(pkg.json.exports, generated)) { + return false; + } + // make sure conditions are in proper order + if (pkg.json.exports) { + const packageNames = Object.keys(generated); + return packageNames.every((pkgName) => { + const generatedConditions = generated[pkgName]; + if (typeof generatedConditions !== "object") { + return true; + } + return assertKeysAreInSameOrder( + generatedConditions, + pkg.json.exports[pkgName] + ); + }); + } + return true; }, }; +function assertKeysAreInSameOrder(generated, origional) { + const generatedKeys = Object.keys(generated); + const origionalKeys = Object.keys(origional); + return generatedKeys.every((key, idx) => { + if (key !== origionalKeys[idx]) { + return false; + } + if (typeof generated[key] === "object") { + return assertKeysAreInSameOrder(generated[key], origional[key]); + } + return true; + }); +} + export function isUmdNameSpecified(entrypoint: Entrypoint) { return typeof entrypoint.json.preconstruct.umdName === "string"; } diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index 901bfc4a..f5b19ae0 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -213,7 +213,9 @@ Additionally, you'll also need to enable the feature on each individual pacakge "license": "MIT", "private": true, "preconstruct": { -+ "exports": ["browser", "worker", "module"] ++ "exports": { ++ "conditions": ["worker", "browser", "module"] ++ }, } } ``` From a57571643ee24428192a3d6b4ab535386d801fd8 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Sun, 6 Mar 2022 12:28:48 -0700 Subject: [PATCH 16/56] simplify snapshot test for exports ordering --- packages/cli/src/__tests__/fix.ts | 96 ++++++++++++++----------------- 1 file changed, 42 insertions(+), 54 deletions(-) diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index 623ae164..98973366 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -77,68 +77,56 @@ test("set exports field when opt-in", async () => { let pkg = await getPkg(tmpPath); - // Assert that the order of conditions are correct. - const conditionsOrder = Object.keys(pkg.exports["."]); - expect(conditionsOrder).toEqual([ - "worker", - "browser", - "production", - "module", - "default", - ]); - const subConditionsOrder = Object.keys(pkg.exports["."].browser); - expect(subConditionsOrder).toEqual(["production", "module", "default"]); - // NOTE: The order of the conditions is important and JEST is sorting the keys alphabetically. // The tests above actually assert that the order is correct. - expect(pkg).toMatchInlineSnapshot(` - Object { - "exports": Object { - ".": Object { - "browser": Object { - "default": "./dist/package-exports.browser.cjs.js", - "module": "./dist/package-exports.browser.esm.dev.js", - "production": Object { - "default": "./dist/package-exports.browser.cjs.prod.js", - "module": "./dist/package-exports.browser.esm.prod.js", + expect(JSON.stringify(pkg, null, 2)).toMatchInlineSnapshot(` + "{ + \\"name\\": \\"package-exports\\", + \\"version\\": \\"1.0.0\\", + \\"main\\": \\"dist/package-exports.cjs.js\\", + \\"license\\": \\"MIT\\", + \\"private\\": true, + \\"module\\": \\"dist/package-exports.esm.js\\", + \\"exports\\": { + \\"./package.json\\": \\"./package.json\\", + \\".\\": { + \\"worker\\": { + \\"production\\": { + \\"module\\": \\"./dist/package-exports.worker.esm.prod.js\\", + \\"default\\": \\"./dist/package-exports.worker.cjs.prod.js\\" }, + \\"module\\": \\"./dist/package-exports.worker.esm.dev.js\\", + \\"default\\": \\"./dist/package-exports.worker.cjs.js\\" }, - "default": "./dist/package-exports.cjs.js", - "module": "./dist/package-exports.esm.dev.js", - "production": Object { - "default": "./dist/package-exports.cjs.prod.js", - "module": "./dist/package-exports.esm.prod.js", - }, - "worker": Object { - "default": "./dist/package-exports.worker.cjs.js", - "module": "./dist/package-exports.worker.esm.dev.js", - "production": Object { - "default": "./dist/package-exports.worker.cjs.prod.js", - "module": "./dist/package-exports.worker.esm.prod.js", + \\"browser\\": { + \\"production\\": { + \\"module\\": \\"./dist/package-exports.browser.esm.prod.js\\", + \\"default\\": \\"./dist/package-exports.browser.cjs.prod.js\\" }, + \\"module\\": \\"./dist/package-exports.browser.esm.dev.js\\", + \\"default\\": \\"./dist/package-exports.browser.cjs.js\\" }, - }, - "./package.json": "./package.json", + \\"production\\": { + \\"module\\": \\"./dist/package-exports.esm.prod.js\\", + \\"default\\": \\"./dist/package-exports.cjs.prod.js\\" + }, + \\"module\\": \\"./dist/package-exports.esm.dev.js\\", + \\"default\\": \\"./dist/package-exports.cjs.js\\" + } }, - "license": "MIT", - "main": "dist/package-exports.cjs.js", - "module": "dist/package-exports.esm.js", - "name": "package-exports", - "preconstruct": Object { - "___experimentalFlags_WILL_CHANGE_IN_PATCH": Object { - "exports": true, - }, - "exports": Object { - "conditions": Array [ - "worker", - "browser", - "module", - ], + \\"preconstruct\\": { + \\"exports\\": { + \\"conditions\\": [ + \\"worker\\", + \\"browser\\", + \\"module\\" + ] }, - }, - "private": true, - "version": "1.0.0", - } + \\"___experimentalFlags_WILL_CHANGE_IN_PATCH\\": { + \\"exports\\": true + } + } + }" `); }); From e050e5c8b04852fb0c625191fcafe44ff2a28902 Mon Sep 17 00:00:00 2001 From: Nick Randall Date: Sun, 13 Mar 2022 17:05:37 -0600 Subject: [PATCH 17/56] default esm to use use dev build when prod or dev are not specified --- .../__tests__/__snapshots__/basic.ts.snap | 22 +------- .../__tests__/__snapshots__/build.ts.snap | 55 ++----------------- .../__snapshots__/entrypoints.ts.snap | 22 +------- .../__tests__/__snapshots__/other.ts.snap | 22 +------- packages/cli/src/build/__tests__/basic.ts | 45 ++------------- packages/cli/src/build/__tests__/build.ts | 9 +-- .../cli/src/build/__tests__/entrypoints.ts | 18 +----- packages/cli/src/build/__tests__/other.ts | 27 +-------- packages/cli/src/build/rollup.ts | 2 +- .../rollup-plugins/flow-and-prod-dev-entry.ts | 10 ++++ 10 files changed, 33 insertions(+), 199 deletions(-) diff --git a/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap index 1a72ef18..466776ca 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap @@ -40,16 +40,7 @@ export default index; " `; -exports[`basic: valid-package.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./valid-package.esm.prod.js\\"); -} else { - module.exports = require(\\"./valid-package.esm.dev.js\\"); -} -" -`; +exports[`basic: valid-package.esm.js 1`] = `"export * from \\"./valid-package.esm.dev.js\\";"`; exports[`basic: valid-package.esm.prod.js 1`] = ` "var index = \\"something\\"; @@ -143,16 +134,7 @@ export { thing$1 as thing }; " `; -exports[`typescript thing: weird-typescript-thing.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./weird-typescript-thing.esm.prod.js\\"); -} else { - module.exports = require(\\"./weird-typescript-thing.esm.dev.js\\"); -} -" -`; +exports[`typescript thing: weird-typescript-thing.esm.js 1`] = `"export * from \\"./weird-typescript-thing.esm.dev.js\\";"`; exports[`typescript thing: weird-typescript-thing.esm.prod.js 1`] = ` "const thing = () => \\"wow\\"; diff --git a/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap index 9d61f9ff..96b370a4 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap @@ -60,16 +60,7 @@ export { doSomething, a as something }; " `; -exports[`flow: flow.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./flow.esm.prod.js\\"); -} else { - module.exports = require(\\"./flow.esm.dev.js\\"); -} -" -`; +exports[`flow: flow.esm.js 1`] = `"export * from \\"./flow.esm.dev.js\\";"`; exports[`flow: flow.esm.js.flow 1`] = ` "// @flow @@ -147,16 +138,7 @@ export { doSomething }; " `; -exports[`flow: flow-export-default.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./flow-export-default.esm.prod.js\\"); -} else { - module.exports = require(\\"./flow-export-default.esm.dev.js\\"); -} -" -`; +exports[`flow: flow-export-default.esm.js 1`] = `"export * from \\"./flow-export-default.esm.dev.js\\";"`; exports[`flow: flow-export-default.esm.js.flow 1`] = ` "// @flow @@ -216,16 +198,7 @@ export default index; " `; -exports[`monorepo single package: some-scope-package-two-single-package.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./some-scope-package-two-single-package.esm.prod.js\\"); -} else { - module.exports = require(\\"./some-scope-package-two-single-package.esm.dev.js\\"); -} -" -`; +exports[`monorepo single package: some-scope-package-two-single-package.esm.js 1`] = `"export * from \\"./some-scope-package-two-single-package.esm.dev.js\\";"`; exports[`monorepo single package: some-scope-package-two-single-package.esm.prod.js 1`] = ` "var index = 2; @@ -418,16 +391,7 @@ export default index; " `; -exports[`monorepo: some-scope-package-one.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./some-scope-package-one.esm.prod.js\\"); -} else { - module.exports = require(\\"./some-scope-package-one.esm.dev.js\\"); -} -" -`; +exports[`monorepo: some-scope-package-one.esm.js 1`] = `"export * from \\"./some-scope-package-one.esm.dev.js\\";"`; exports[`monorepo: some-scope-package-one.esm.prod.js 1`] = ` "var index = 1; @@ -476,16 +440,7 @@ export default index; " `; -exports[`monorepo: some-scope-package-two.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./some-scope-package-two.esm.prod.js\\"); -} else { - module.exports = require(\\"./some-scope-package-two.esm.dev.js\\"); -} -" -`; +exports[`monorepo: some-scope-package-two.esm.js 1`] = `"export * from \\"./some-scope-package-two.esm.dev.js\\";"`; exports[`monorepo: some-scope-package-two.esm.prod.js 1`] = ` "var index = 2; diff --git a/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap index 06ff0ca3..c75f9b2c 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap @@ -159,16 +159,7 @@ export { sum }; " `; -exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./common-dependency-two-entrypoints.esm.prod.js\\"); -} else { - module.exports = require(\\"./common-dependency-two-entrypoints.esm.dev.js\\"); -} -" -`; +exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.js 1`] = `"export * from \\"./common-dependency-two-entrypoints.esm.dev.js\\";"`; exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.prod.js 1`] = ` "import { i as identity } from './chunk-some-hash.esm.prod.js'; @@ -229,16 +220,7 @@ export { multiply }; " `; -exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./common-dependency-two-entrypoints-multiply.esm.prod.js\\"); -} else { - module.exports = require(\\"./common-dependency-two-entrypoints-multiply.esm.dev.js\\"); -} -" -`; +exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.js 1`] = `"export * from \\"./common-dependency-two-entrypoints-multiply.esm.dev.js\\";"`; exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.prod.js 1`] = ` "import { i as identity } from '../../dist/chunk-some-hash.esm.prod.js'; diff --git a/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap index 7f29a01f..fc4c1ffd 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap @@ -279,16 +279,7 @@ export { createStore }; " `; -exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./typescript-force-dts-emit.esm.prod.js\\"); -} else { - module.exports = require(\\"./typescript-force-dts-emit.esm.dev.js\\"); -} -" -`; +exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.js 1`] = `"export * from \\"./typescript-force-dts-emit.esm.dev.js\\";"`; exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.prod.js 1`] = ` "import { combineReducers, configureStore } from '@reduxjs/toolkit'; @@ -361,16 +352,7 @@ export default Foo; " `; -exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.js 1`] = ` -"'use strict'; - -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./external-babel-runtime.esm.prod.js\\"); -} else { - module.exports = require(\\"./external-babel-runtime.esm.dev.js\\"); -} -" -`; +exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.js 1`] = `"export * from \\"./external-babel-runtime.esm.dev.js\\";"`; exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.prod.js 1`] = ` "import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck'; diff --git a/packages/cli/src/build/__tests__/basic.ts b/packages/cli/src/build/__tests__/basic.ts index 09463c85..f09535c0 100644 --- a/packages/cli/src/build/__tests__/basic.ts +++ b/packages/cli/src/build/__tests__/basic.ts @@ -181,14 +181,7 @@ test("typescript declarationMap", async () => { export { thing }; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./typescript-declarationMap.esm.prod.js"); - } else { - module.exports = require("./typescript-declarationMap.esm.dev.js"); - } - + export * from "./typescript-declarationMap.esm.dev.js"; `); }); @@ -385,14 +378,7 @@ test("imports helpers from @babel/runtime without @babel/plugin-transform-runtim export { Other, Thing }; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./test.esm.prod.js"); - } else { - module.exports = require("./test.esm.dev.js"); - } - + export * from "./test.esm.dev.js"; `); }); @@ -455,14 +441,7 @@ test("imports helpers from @babel/runtime-corejs2 without @babel/plugin-transfor export { Other, Thing }; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./test.esm.prod.js"); - } else { - module.exports = require("./test.esm.dev.js"); - } - + export * from "./test.esm.dev.js"; `); }); @@ -525,14 +504,7 @@ test("imports helpers from @babel/runtime-corejs3 without @babel/plugin-transfor export { Other, Thing }; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./test.esm.prod.js"); - } else { - module.exports = require("./test.esm.dev.js"); - } - + export * from "./test.esm.dev.js"; `); }); @@ -894,14 +866,7 @@ test("new dist filenames", async () => { export default index; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./scope-test.esm.prod.js"); - } else { - module.exports = require("./scope-test.esm.dev.js"); - } - + export * from "./scope-test.esm.dev.js"; `); }); diff --git a/packages/cli/src/build/__tests__/build.ts b/packages/cli/src/build/__tests__/build.ts index 7e496d50..9711a9ab 100644 --- a/packages/cli/src/build/__tests__/build.ts +++ b/packages/cli/src/build/__tests__/build.ts @@ -504,14 +504,7 @@ test("json", async () => { export { schema }; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/json-package.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./json-package.esm.prod.js"); - } else { - module.exports = require("./json-package.esm.dev.js"); - } - + export * from "./json-package.esm.dev.js"; `); }); diff --git a/packages/cli/src/build/__tests__/entrypoints.ts b/packages/cli/src/build/__tests__/entrypoints.ts index dc1f9946..c55f4bd9 100644 --- a/packages/cli/src/build/__tests__/entrypoints.ts +++ b/packages/cli/src/build/__tests__/entrypoints.ts @@ -61,14 +61,7 @@ test("multiple entrypoints", async () => { export { sum }; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/multiple-entrypoints.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./multiple-entrypoints.esm.prod.js"); - } else { - module.exports = require("./multiple-entrypoints.esm.dev.js"); - } - + export * from "./multiple-entrypoints.esm.dev.js"; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.cjs.dev.js, multiply/dist/multiple-entrypoints-multiply.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; @@ -93,14 +86,7 @@ test("multiple entrypoints", async () => { export { multiply }; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./multiple-entrypoints-multiply.esm.prod.js"); - } else { - module.exports = require("./multiple-entrypoints-multiply.esm.dev.js"); - } - + export * from "./multiple-entrypoints-multiply.esm.dev.js"; `); }); diff --git a/packages/cli/src/build/__tests__/other.ts b/packages/cli/src/build/__tests__/other.ts index 58bd5f57..c3f3d684 100644 --- a/packages/cli/src/build/__tests__/other.ts +++ b/packages/cli/src/build/__tests__/other.ts @@ -92,14 +92,7 @@ test("browser", async () => { export default thing$1; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./browser.browser.esm.prod.js"); - } else { - module.exports = require("./browser.browser.esm.dev.js"); - } - + export * from "./browser.browser.esm.dev.js"; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.cjs.dev.js, dist/browser.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; @@ -144,14 +137,7 @@ test("browser", async () => { export default thing$1; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./browser.esm.prod.js"); - } else { - module.exports = require("./browser.esm.dev.js"); - } - + export * from "./browser.esm.dev.js"; `); }); @@ -269,14 +255,7 @@ test("typescript", async () => { export { obj }; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./typescript.esm.prod.js"); - } else { - module.exports = require("./typescript.esm.dev.js"); - } - + export * from "./typescript.esm.dev.js"; `); }); diff --git a/packages/cli/src/build/rollup.ts b/packages/cli/src/build/rollup.ts index caf34225..241b482d 100644 --- a/packages/cli/src/build/rollup.ts +++ b/packages/cli/src/build/rollup.ts @@ -33,7 +33,7 @@ const makeExternalPredicate = (externalArr: string[]) => { export type RollupConfigEnvironment = "dev" | "prod" | "umd"; -export type RollupConfigType = "browser" | "node" | "node" | "worker"; +export type RollupConfigType = "browser" | "node" | "worker"; export let getRollupConfig = ( pkg: Package, diff --git a/packages/cli/src/rollup-plugins/flow-and-prod-dev-entry.ts b/packages/cli/src/rollup-plugins/flow-and-prod-dev-entry.ts index 8a3ff2c7..813c7ac7 100644 --- a/packages/cli/src/rollup-plugins/flow-and-prod-dev-entry.ts +++ b/packages/cli/src/rollup-plugins/flow-and-prod-dev-entry.ts @@ -81,6 +81,7 @@ export default function flowAndNodeDevProdEntry( continue; } + let mainFieldPath = file.fileName.replace(/\.prod\.js$/, ".js"); let relativeToSource = path.relative( path.dirname(path.join(opts.dir!, file.fileName)), @@ -110,6 +111,15 @@ export default function flowAndNodeDevProdEntry( } } + if (file.fileName.includes(".esm.")) { + this.emitFile({ + type: "asset", + fileName: mainFieldPath, + source: `export * from "./${path.basename(getDevPath(mainFieldPath))}";`, + }); + continue; + } + let mainEntrySource = `'use strict'; if (${ From 68deb25a732b11fdcfc4c8fcd9a1a2b95bea70c3 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 1 Jul 2022 16:00:49 +1000 Subject: [PATCH 18/56] Remove prod esm builds --- .../__tests__/__snapshots__/basic.ts.snap | 31 +------- .../__tests__/__snapshots__/build.ts.snap | 76 ++----------------- .../__snapshots__/entrypoints.ts.snap | 45 ++--------- .../__tests__/__snapshots__/other.ts.snap | 43 +---------- packages/cli/src/build/__tests__/basic.ts | 22 ++---- packages/cli/src/build/__tests__/build.ts | 4 +- .../cli/src/build/__tests__/entrypoints.ts | 8 +- packages/cli/src/build/__tests__/other.ts | 14 +--- packages/cli/src/build/config.ts | 28 +------ packages/cli/src/package.ts | 14 +--- .../rollup-plugins/flow-and-prod-dev-entry.ts | 10 --- packages/cli/src/validate.ts | 35 +-------- 12 files changed, 37 insertions(+), 293 deletions(-) diff --git a/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap index 466776ca..4da63de7 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/basic.ts.snap @@ -33,16 +33,7 @@ exports.default = index; " `; -exports[`basic: valid-package.esm.dev.js 1`] = ` -"var index = \\"something\\"; - -export default index; -" -`; - -exports[`basic: valid-package.esm.js 1`] = `"export * from \\"./valid-package.esm.dev.js\\";"`; - -exports[`basic: valid-package.esm.prod.js 1`] = ` +exports[`basic: valid-package.esm.js 1`] = ` "var index = \\"something\\"; export default index; @@ -118,25 +109,7 @@ exports.thing = thing$1; " `; -exports[`typescript thing: weird-typescript-thing.esm.d.ts 1`] = ` -"export * from \\"./declarations/src/index\\"; -" -`; - -exports[`typescript thing: weird-typescript-thing.esm.dev.js 1`] = ` -"const thing = () => \\"wow\\"; - -const makeThing = () => thing(); - -const thing$1 = makeThing(); - -export { thing$1 as thing }; -" -`; - -exports[`typescript thing: weird-typescript-thing.esm.js 1`] = `"export * from \\"./weird-typescript-thing.esm.dev.js\\";"`; - -exports[`typescript thing: weird-typescript-thing.esm.prod.js 1`] = ` +exports[`typescript thing: weird-typescript-thing.esm.js 1`] = ` "const thing = () => \\"wow\\"; const makeThing = () => thing(); diff --git a/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap index 96b370a4..6ccfa6f0 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/build.ts.snap @@ -49,26 +49,7 @@ exports.something = a; " `; -exports[`flow: flow.esm.dev.js 1`] = ` -"var a = \\"wow\\"; - -function doSomething(arg) { - return \\"something\\" + arg; -} - -export { doSomething, a as something }; -" -`; - -exports[`flow: flow.esm.js 1`] = `"export * from \\"./flow.esm.dev.js\\";"`; - -exports[`flow: flow.esm.js.flow 1`] = ` -"// @flow -export * from \\"../src/index.js\\"; -" -`; - -exports[`flow: flow.esm.prod.js 1`] = ` +exports[`flow: flow.esm.js 1`] = ` "var a = \\"wow\\"; function doSomething(arg) { @@ -127,27 +108,7 @@ exports.doSomething = doSomething; " `; -exports[`flow: flow-export-default.esm.dev.js 1`] = ` -"function doSomething(arg) { - return \\"something\\" + arg; -} -var index = \\"wow\\"; - -export default index; -export { doSomething }; -" -`; - -exports[`flow: flow-export-default.esm.js 1`] = `"export * from \\"./flow-export-default.esm.dev.js\\";"`; - -exports[`flow: flow-export-default.esm.js.flow 1`] = ` -"// @flow -export * from \\"../src/index.js\\"; -export { default } from \\"../src/index.js\\"; -" -`; - -exports[`flow: flow-export-default.esm.prod.js 1`] = ` +exports[`flow: flow-export-default.esm.js 1`] = ` "function doSomething(arg) { return \\"something\\" + arg; } @@ -191,16 +152,7 @@ exports.default = index; " `; -exports[`monorepo single package: some-scope-package-two-single-package.esm.dev.js 1`] = ` -"var index = 2; - -export default index; -" -`; - -exports[`monorepo single package: some-scope-package-two-single-package.esm.js 1`] = `"export * from \\"./some-scope-package-two-single-package.esm.dev.js\\";"`; - -exports[`monorepo single package: some-scope-package-two-single-package.esm.prod.js 1`] = ` +exports[`monorepo single package: some-scope-package-two-single-package.esm.js 1`] = ` "var index = 2; export default index; @@ -384,16 +336,7 @@ exports.default = index; " `; -exports[`monorepo: some-scope-package-one.esm.dev.js 1`] = ` -"var index = 1; - -export default index; -" -`; - -exports[`monorepo: some-scope-package-one.esm.js 1`] = `"export * from \\"./some-scope-package-one.esm.dev.js\\";"`; - -exports[`monorepo: some-scope-package-one.esm.prod.js 1`] = ` +exports[`monorepo: some-scope-package-one.esm.js 1`] = ` "var index = 1; export default index; @@ -433,16 +376,7 @@ exports.default = index; " `; -exports[`monorepo: some-scope-package-two.esm.dev.js 1`] = ` -"var index = 2; - -export default index; -" -`; - -exports[`monorepo: some-scope-package-two.esm.js 1`] = `"export * from \\"./some-scope-package-two.esm.dev.js\\";"`; - -exports[`monorepo: some-scope-package-two.esm.prod.js 1`] = ` +exports[`monorepo: some-scope-package-two.esm.js 1`] = ` "var index = 2; export default index; diff --git a/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap index c75f9b2c..7f21d9b4 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/entrypoints.ts.snap @@ -78,14 +78,7 @@ exports[`two entrypoints where one requires the other entrypoint: src/multiply.j export let multiply = (a, b) => identity(a * b);" `; -exports[`two entrypoints with a common dependency: dist/chunk-this-is-not-the-real-hash-09d8aa2cd41dd37e55ea1ca5c8546d35.esm.dev.js 1`] = ` -"let identity = x => x; - -export { identity as i }; -" -`; - -exports[`two entrypoints with a common dependency: dist/chunk-this-is-not-the-real-hash-09d8aa2cd41dd37e55ea1ca5c8546d35.esm.prod.js 1`] = ` +exports[`two entrypoints with a common dependency: dist/chunk-this-is-not-the-real-hash-09d8aa2cd41dd37e55ea1ca5c8546d35.esm.js 1`] = ` "let identity = x => x; export { identity as i }; @@ -149,21 +142,9 @@ exports.sum = sum; " `; -exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.dev.js 1`] = ` -"import { i as identity } from './chunk-some-hash.esm.dev.js'; -export { i as identity } from './chunk-some-hash.esm.dev.js'; - -let sum = (a, b) => identity(a + b); - -export { sum }; -" -`; - -exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.js 1`] = `"export * from \\"./common-dependency-two-entrypoints.esm.dev.js\\";"`; - -exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.prod.js 1`] = ` -"import { i as identity } from './chunk-some-hash.esm.prod.js'; -export { i as identity } from './chunk-some-hash.esm.prod.js'; +exports[`two entrypoints with a common dependency: dist/common-dependency-two-entrypoints.esm.js 1`] = ` +"import { i as identity } from './chunk-some-hash.esm.js'; +export { i as identity } from './chunk-some-hash.esm.js'; let sum = (a, b) => identity(a + b); @@ -210,21 +191,9 @@ exports.multiply = multiply; " `; -exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.dev.js 1`] = ` -"import { i as identity } from '../../dist/chunk-some-hash.esm.dev.js'; -export { i as identity } from '../../dist/chunk-some-hash.esm.dev.js'; - -let multiply = (a, b) => identity(a * b); - -export { multiply }; -" -`; - -exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.js 1`] = `"export * from \\"./common-dependency-two-entrypoints-multiply.esm.dev.js\\";"`; - -exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.prod.js 1`] = ` -"import { i as identity } from '../../dist/chunk-some-hash.esm.prod.js'; -export { i as identity } from '../../dist/chunk-some-hash.esm.prod.js'; +exports[`two entrypoints with a common dependency: multiply/dist/common-dependency-two-entrypoints-multiply.esm.js 1`] = ` +"import { i as identity } from '../../dist/chunk-some-hash.esm.js'; +export { i as identity } from '../../dist/chunk-some-hash.esm.js'; let multiply = (a, b) => identity(a * b); diff --git a/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap index fc4c1ffd..01c4cb12 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap @@ -255,33 +255,7 @@ exports.createStore = createStore; " `; -exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.d.ts 1`] = ` -"export * from \\"./declarations/src/index\\"; -" -`; - -exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.dev.js 1`] = ` -"import { combineReducers, configureStore } from '@reduxjs/toolkit'; - -// @ts-ignore (installed during test) -var rootReducer = combineReducers({ - /* blah blah blah */ -}); - -// @ts-ignore (installed during test) -function createStore() { - return configureStore({ - reducer: rootReducer - }); -} - -export { createStore }; -" -`; - -exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.js 1`] = `"export * from \\"./typescript-force-dts-emit.esm.dev.js\\";"`; - -exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.prod.js 1`] = ` +exports[`typescript with forced dts emit: dist/typescript-force-dts-emit.esm.js 1`] = ` "import { combineReducers, configureStore } from '@reduxjs/toolkit'; // @ts-ignore (installed during test) @@ -341,20 +315,7 @@ exports.default = Foo; " `; -exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.dev.js 1`] = ` -"import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck'; - -var Foo = function Foo() { - _classCallCheck(this, Foo); -}; - -export default Foo; -" -`; - -exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.js 1`] = `"export * from \\"./external-babel-runtime.esm.dev.js\\";"`; - -exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.prod.js 1`] = ` +exports[`using external @babel/runtime helpers: dist/external-babel-runtime.esm.js 1`] = ` "import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck'; var Foo = function Foo() { diff --git a/packages/cli/src/build/__tests__/basic.ts b/packages/cli/src/build/__tests__/basic.ts index f09535c0..0d6ef7fb 100644 --- a/packages/cli/src/build/__tests__/basic.ts +++ b/packages/cli/src/build/__tests__/basic.ts @@ -154,7 +154,7 @@ test("typescript declarationMap", async () => { //# sourceMappingURL=index.d.ts.map ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/declarations/src/index.d.ts.map ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ {"version":3,"file":"index.d.ts","sourceRoot":"../../../src","sources":["index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,OAAiB,CAAC"} - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.cjs.d.ts, dist/typescript-declarationMap.esm.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.cjs.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ export * from "./declarations/src/index"; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.cjs.dev.js, dist/typescript-declarationMap.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ @@ -175,13 +175,11 @@ test("typescript declarationMap", async () => { module.exports = require("./typescript-declarationMap.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.esm.dev.js, dist/typescript-declarationMap.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ const thing = "wow"; export { thing }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript-declarationMap.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./typescript-declarationMap.esm.dev.js"; `); }); @@ -364,7 +362,7 @@ test("imports helpers from @babel/runtime without @babel/plugin-transform-runtim module.exports = require("./test.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.dev.js, dist/test.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck'; var Other = function Other() { @@ -377,8 +375,6 @@ test("imports helpers from @babel/runtime without @babel/plugin-transform-runtim export { Other, Thing }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./test.esm.dev.js"; `); }); @@ -427,7 +423,7 @@ test("imports helpers from @babel/runtime-corejs2 without @babel/plugin-transfor module.exports = require("./test.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.dev.js, dist/test.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ import _classCallCheck from '@babel/runtime-corejs2/helpers/esm/classCallCheck'; var Other = function Other() { @@ -440,8 +436,6 @@ test("imports helpers from @babel/runtime-corejs2 without @babel/plugin-transfor export { Other, Thing }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./test.esm.dev.js"; `); }); @@ -490,7 +484,7 @@ test("imports helpers from @babel/runtime-corejs3 without @babel/plugin-transfor module.exports = require("./test.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.dev.js, dist/test.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ import _classCallCheck from '@babel/runtime-corejs3/helpers/esm/classCallCheck'; var Other = function Other() { @@ -503,8 +497,6 @@ test("imports helpers from @babel/runtime-corejs3 without @babel/plugin-transfor export { Other, Thing }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./test.esm.dev.js"; `); }); @@ -860,13 +852,11 @@ test("new dist filenames", async () => { module.exports = require("./scope-test.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.esm.dev.js, dist/scope-test.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ var index = "something"; export default index; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./scope-test.esm.dev.js"; `); }); diff --git a/packages/cli/src/build/__tests__/build.ts b/packages/cli/src/build/__tests__/build.ts index 9711a9ab..75a71c33 100644 --- a/packages/cli/src/build/__tests__/build.ts +++ b/packages/cli/src/build/__tests__/build.ts @@ -488,7 +488,7 @@ test("json", async () => { module.exports = require("./json-package.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/json-package.esm.dev.js, dist/json-package.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/json-package.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ var changesetsSchema = { $schema: "http://json-schema.org/draft-07/schema#", type: "object", @@ -503,8 +503,6 @@ test("json", async () => { export { schema }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/json-package.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./json-package.esm.dev.js"; `); }); diff --git a/packages/cli/src/build/__tests__/entrypoints.ts b/packages/cli/src/build/__tests__/entrypoints.ts index c55f4bd9..1cbb59a6 100644 --- a/packages/cli/src/build/__tests__/entrypoints.ts +++ b/packages/cli/src/build/__tests__/entrypoints.ts @@ -55,13 +55,11 @@ test("multiple entrypoints", async () => { module.exports = require("./multiple-entrypoints.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/multiple-entrypoints.esm.dev.js, dist/multiple-entrypoints.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/multiple-entrypoints.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ let sum = (a, b) => a + b; export { sum }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/multiple-entrypoints.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./multiple-entrypoints.esm.dev.js"; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.cjs.dev.js, multiply/dist/multiple-entrypoints-multiply.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; @@ -80,13 +78,11 @@ test("multiple entrypoints", async () => { module.exports = require("./multiple-entrypoints-multiply.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.esm.dev.js, multiply/dist/multiple-entrypoints-multiply.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ let multiply = (a, b) => a * b; export { multiply }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ multiply/dist/multiple-entrypoints-multiply.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./multiple-entrypoints-multiply.esm.dev.js"; `); }); diff --git a/packages/cli/src/build/__tests__/other.ts b/packages/cli/src/build/__tests__/other.ts index c3f3d684..2c2be57f 100644 --- a/packages/cli/src/build/__tests__/other.ts +++ b/packages/cli/src/build/__tests__/other.ts @@ -76,7 +76,7 @@ test("browser", async () => { module.exports = require("./browser.browser.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.esm.dev.js, dist/browser.browser.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ let thing = "wow"; { @@ -91,8 +91,6 @@ test("browser", async () => { export default thing$1; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./browser.browser.esm.dev.js"; ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.cjs.dev.js, dist/browser.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; @@ -121,7 +119,7 @@ test("browser", async () => { module.exports = require("./browser.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.esm.dev.js, dist/browser.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ let thing = "wow"; if (typeof window !== "undefined") { @@ -136,8 +134,6 @@ test("browser", async () => { export default thing$1; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./browser.esm.dev.js"; `); }); @@ -193,7 +189,7 @@ test("typescript", async () => { declare var obj: object; export { obj }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.cjs.d.ts, dist/typescript.esm.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.cjs.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ export * from "./declarations/src/index"; export { default } from "./declarations/src/index"; @@ -243,7 +239,7 @@ test("typescript", async () => { module.exports = require("./typescript.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.esm.dev.js, dist/typescript.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ import * as path from 'path'; export { path }; @@ -254,8 +250,6 @@ test("typescript", async () => { export default thing; export { obj }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/typescript.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - export * from "./typescript.esm.dev.js"; `); }); diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index fed0ffac..c802a6ef 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -117,8 +117,8 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { ? [ { format: "es" as const, - entryFileNames: "[name].esm.dev.js", - chunkFileNames: "dist/[name]-[hash].esm.dev.js", + entryFileNames: "[name].esm.js", + chunkFileNames: "dist/[name]-[hash].esm.js", dir: pkg.directory, }, ] @@ -145,16 +145,6 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { interop, plugins: cjsPlugins, }, - ...(hasModuleField - ? [ - { - format: "es" as const, - entryFileNames: "[name].esm.prod.js", - chunkFileNames: "dist/[name]-[hash].esm.prod.js", - dir: pkg.directory, - }, - ] - : []), ], }); @@ -232,8 +222,8 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { ? [ { format: "es" as const, - entryFileNames: "[name].browser.esm.dev.js", - chunkFileNames: "dist/[name]-[hash].browser.esm.dev.js", + entryFileNames: "[name].browser.esm.js", + chunkFileNames: "dist/[name]-[hash].browser.esm.js", dir: pkg.directory, }, ] @@ -259,16 +249,6 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { interop, plugins: cjsPlugins, }, - ...(hasModuleField - ? [ - { - format: "es" as const, - entryFileNames: "[name].browser.esm.prod.js", - chunkFileNames: "dist/[name]-[hash].browser.esm.prod.js", - dir: pkg.directory, - }, - ] - : []), ], }); } diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 294efc6f..a8303dd8 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -140,22 +140,10 @@ function createEntrypoints( export type ExportsConditions = { worker?: { - production: { - module?: string; - default: string; - }; module?: string; default: string; }; browser?: { - production: { - module?: string; - default: string; - }; - module?: string; - default: string; - }; - production?: { module?: string; default: string; }; @@ -170,7 +158,7 @@ export class Package extends Item<{ preconstruct: { exports?: { extra?: Record; - conditions?: ExportsConditions[]; + conditions?: ExportsCondition[]; }; entrypoints?: JSONValue; }; diff --git a/packages/cli/src/rollup-plugins/flow-and-prod-dev-entry.ts b/packages/cli/src/rollup-plugins/flow-and-prod-dev-entry.ts index 813c7ac7..8a3ff2c7 100644 --- a/packages/cli/src/rollup-plugins/flow-and-prod-dev-entry.ts +++ b/packages/cli/src/rollup-plugins/flow-and-prod-dev-entry.ts @@ -81,7 +81,6 @@ export default function flowAndNodeDevProdEntry( continue; } - let mainFieldPath = file.fileName.replace(/\.prod\.js$/, ".js"); let relativeToSource = path.relative( path.dirname(path.join(opts.dir!, file.fileName)), @@ -111,15 +110,6 @@ export default function flowAndNodeDevProdEntry( } } - if (file.fileName.includes(".esm.")) { - this.emitFile({ - type: "asset", - fileName: mainFieldPath, - source: `export * from "./${path.basename(getDevPath(mainFieldPath))}";`, - }); - continue; - } - let mainEntrySource = `'use strict'; if (${ diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index 488a609e..c3f74b24 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -28,41 +28,12 @@ export const isFieldValid = { }, exports(pkg: Package): boolean { const generated = validFields.exports(pkg); - if (!equal(pkg.json.exports, generated)) { - return false; - } - // make sure conditions are in proper order - if (pkg.json.exports) { - const packageNames = Object.keys(generated); - return packageNames.every((pkgName) => { - const generatedConditions = generated[pkgName]; - if (typeof generatedConditions !== "object") { - return true; - } - return assertKeysAreInSameOrder( - generatedConditions, - pkg.json.exports[pkgName] - ); - }); - } - return true; + + // JSON.stringify to make sure conditions are in proper order + return JSON.stringify(pkg.json.exports) === JSON.stringify(generated); }, }; -function assertKeysAreInSameOrder(generated, origional) { - const generatedKeys = Object.keys(generated); - const origionalKeys = Object.keys(origional); - return generatedKeys.every((key, idx) => { - if (key !== origionalKeys[idx]) { - return false; - } - if (typeof generated[key] === "object") { - return assertKeysAreInSameOrder(generated[key], origional[key]); - } - return true; - }); -} - export function isUmdNameSpecified(entrypoint: Entrypoint) { return typeof entrypoint.json.preconstruct.umdName === "string"; } From 7a49579572f87b30f1c4227165fbf2ae19d76507 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 1 Jul 2022 16:14:50 +1000 Subject: [PATCH 19/56] WIP --- packages/cli/src/__tests__/fix.ts | 12 --------- packages/cli/src/dev.ts | 43 ++++++++++--------------------- packages/cli/src/utils.ts | 40 ++++++---------------------- 3 files changed, 21 insertions(+), 74 deletions(-) diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index 98973366..a53d1acf 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -91,25 +91,13 @@ test("set exports field when opt-in", async () => { \\"./package.json\\": \\"./package.json\\", \\".\\": { \\"worker\\": { - \\"production\\": { - \\"module\\": \\"./dist/package-exports.worker.esm.prod.js\\", - \\"default\\": \\"./dist/package-exports.worker.cjs.prod.js\\" - }, \\"module\\": \\"./dist/package-exports.worker.esm.dev.js\\", \\"default\\": \\"./dist/package-exports.worker.cjs.js\\" }, \\"browser\\": { - \\"production\\": { - \\"module\\": \\"./dist/package-exports.browser.esm.prod.js\\", - \\"default\\": \\"./dist/package-exports.browser.cjs.prod.js\\" - }, \\"module\\": \\"./dist/package-exports.browser.esm.dev.js\\", \\"default\\": \\"./dist/package-exports.browser.cjs.js\\" }, - \\"production\\": { - \\"module\\": \\"./dist/package-exports.esm.prod.js\\", - \\"default\\": \\"./dist/package-exports.cjs.prod.js\\" - }, \\"module\\": \\"./dist/package-exports.esm.dev.js\\", \\"default\\": \\"./dist/package-exports.cjs.js\\" } diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index 78c6cc9a..fa5a3e13 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -207,36 +207,19 @@ unregister(); ) ); } - if ( - pkg.project.experimentalFlags.exports && - pkg.json.preconstruct.exports - ) { - if (entrypoint.json.exports) { - let exportsField = validFields.exports(pkg); - if ( - exportsField?.["."] && - typeof exportsField["."] === "object" - ) { - for (let key of Object.keys(exportsField["."])) { - if (["browser", "worker"].includes(key)) { - for (let key2 of Object.keys( - (exportsField["."] as any)[key] - )) { - promises.push( - fs.symlink( - entrypoint.source, - path.join( - entrypoint.directory, - (exportsField["."] as any)[key][key2] - ) - ) - ); - } - } - } - } - } - } + + // if ( + // pkg.project.experimentalFlags.exports && + // pkg.json.preconstruct.exports?.conditions?.includes("worker") + // ) { + // promises.push( + // fs.symlink( + // entrypoint.source, + // path.join(entrypoint.directory, browserField[key]) + // ) + // ); + // } + if (entrypoint.json.browser) { let browserField = validFields.browser(entrypoint); for (let key of Object.keys(browserField)) { diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 675eb349..c14b044b 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -215,19 +215,10 @@ export const validFieldsFromPkg = { output[`./${entrypointPath}`] = conditions; } }); - let extra: Record | null = null; - if ( - pkg.project.experimentalFlags.exports && - typeof pkg.json.preconstruct.exports === "object" - ) { - if (pkg.json.preconstruct.exports.extra) { - extra = pkg.json.preconstruct.exports.extra as Record; - } - } return { "./package.json": "./package.json", ...output, - ...extra, + ...pkg.json.preconstruct.exports?.extra, }; }, }; @@ -257,51 +248,36 @@ const exportsHelpers = { target: string = "", prefix: string = "" ) { - const obj = exportsHelpers.env( - pkg, - hasModuleBuild, - entrypointName, - forceStrategy, - "", - target, - prefix - ); - const production = exportsHelpers.env( + return exportsHelpers.env( pkg, hasModuleBuild, entrypointName, forceStrategy, - "prod", target, prefix ); - return { - production, - ...obj, - }; }, env( pkg: Package, hasModuleBuild: boolean, entrypointName: string, forceStrategy?: DistFilenameStrategy, - env: string = "", target: string = "", prefix: string = "" ) { let safeName = getDistName(pkg, entrypointName, forceStrategy); let obj: ExportsConditions = { - default: `./${prefix}dist/${safeName}.${target ? `${target}.` : ""}cjs.${ - env ? `${env}.` : "" - }js`, + default: `./${prefix}dist/${safeName}.${ + target ? `${target}.` : "" + }cjs.js`, }; if (hasModuleBuild) { // esm doesn't support conditional imports so if env is not set we default to dev version obj = { - module: `./${prefix}dist/${safeName}.${target ? `${target}.` : ""}esm.${ - env ? `${env}.` : "dev." - }js`, + module: `./${prefix}dist/${safeName}.${ + target ? `${target}.` : "" + }esm.js`, ...obj, }; } From 69e6a78a39165c3377fac71bd41896349d777b67 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 1 Jul 2022 16:27:47 +1000 Subject: [PATCH 20/56] Remove forceStrategy --- packages/cli/src/utils.ts | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index c14b044b..cddf510e 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -135,8 +135,7 @@ export const validFieldsFromPkg = { hasModuleBuild: boolean, hasBrowserField: boolean, hasWorkerField: boolean, - entrypointName: string, - forceStrategy?: DistFilenameStrategy + entrypointName: string ): Record { let output: Record = {}; pkg.entrypoints.forEach((entrypoint) => { @@ -144,8 +143,7 @@ export const validFieldsFromPkg = { let obj: ExportsConditions = exportsHelpers.root( pkg, hasModuleBuild, - entrypointName, - forceStrategy + entrypointName ); if (hasBrowserField) { obj = { @@ -153,7 +151,6 @@ export const validFieldsFromPkg = { pkg, hasModuleBuild, entrypointName, - forceStrategy, "browser" ), ...obj, @@ -165,7 +162,6 @@ export const validFieldsFromPkg = { pkg, hasModuleBuild, entrypointName, - forceStrategy, "worker" ), ...obj, @@ -182,7 +178,6 @@ export const validFieldsFromPkg = { pkg, entrypoint.json.module !== undefined, entrypoint.name, - forceStrategy, entrypointPath + "/" ); @@ -192,7 +187,6 @@ export const validFieldsFromPkg = { pkg, hasModuleBuild, entrypoint.name, - forceStrategy, "browser", entrypointPath + "/" ), @@ -205,7 +199,6 @@ export const validFieldsFromPkg = { pkg, hasModuleBuild, entrypoint.name, - forceStrategy, "worker", entrypointPath + "/" ), @@ -228,14 +221,12 @@ const exportsHelpers = { pkg: Package, hasModuleBuild: boolean, entrypointName: string, - forceStrategy?: DistFilenameStrategy, prefix: string = "" ) { return exportsHelpers.target( pkg, hasModuleBuild, entrypointName, - forceStrategy, "", prefix ); @@ -244,7 +235,6 @@ const exportsHelpers = { pkg: Package, hasModuleBuild: boolean, entrypointName: string, - forceStrategy?: DistFilenameStrategy, target: string = "", prefix: string = "" ) { @@ -252,7 +242,6 @@ const exportsHelpers = { pkg, hasModuleBuild, entrypointName, - forceStrategy, target, prefix ); @@ -261,11 +250,10 @@ const exportsHelpers = { pkg: Package, hasModuleBuild: boolean, entrypointName: string, - forceStrategy?: DistFilenameStrategy, target: string = "", prefix: string = "" ) { - let safeName = getDistName(pkg, entrypointName, forceStrategy); + let safeName = getDistName(pkg, entrypointName); let obj: ExportsConditions = { default: `./${prefix}dist/${safeName}.${ @@ -302,7 +290,7 @@ export const validFields = { entrypoint.name ); }, - exports(pkg: Package, forceStrategy?: DistFilenameStrategy) { + exports(pkg: Package) { // skip if not enabled for the project if (!pkg.project.experimentalFlags.exports) { return; @@ -329,8 +317,7 @@ export const validFields = { hasModuleField, hasBrowserField, hasWorkerField, - pkg.name, - forceStrategy + pkg.name ); }, }; From ba08537a95c460a0c00db4ab05686d619929cbe1 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 1 Jul 2022 17:07:35 +1000 Subject: [PATCH 21/56] Things --- packages/cli/src/__tests__/fix.ts | 12 +- packages/cli/src/build/config.ts | 22 +-- packages/cli/src/dev.ts | 18 ++- packages/cli/src/package.ts | 77 +++------ packages/cli/src/utils.ts | 225 +++++++-------------------- packages/cli/src/validate-package.ts | 10 +- packages/cli/src/validate.ts | 32 ++-- 7 files changed, 126 insertions(+), 270 deletions(-) diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index a53d1acf..ca31702b 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -90,15 +90,15 @@ test("set exports field when opt-in", async () => { \\"exports\\": { \\"./package.json\\": \\"./package.json\\", \\".\\": { - \\"worker\\": { - \\"module\\": \\"./dist/package-exports.worker.esm.dev.js\\", - \\"default\\": \\"./dist/package-exports.worker.cjs.js\\" - }, \\"browser\\": { - \\"module\\": \\"./dist/package-exports.browser.esm.dev.js\\", + \\"module\\": \\"./dist/package-exports.browser.esm.js\\", \\"default\\": \\"./dist/package-exports.browser.cjs.js\\" }, - \\"module\\": \\"./dist/package-exports.esm.dev.js\\", + \\"worker\\": { + \\"module\\": \\"./dist/package-exports.worker.esm.js\\", + \\"default\\": \\"./dist/package-exports.worker.cjs.js\\" + }, + \\"module\\": \\"./dist/package-exports.esm.js\\", \\"default\\": \\"./dist/package-exports.cjs.js\\" } }, diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index c802a6ef..a3db6b1c 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -181,24 +181,9 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }); }); - let hasBrowserCondition = false; - let hasWorkerCondition = false; - if (pkg.project.experimentalFlags.exports) { - if (typeof pkg.json.exports == "object") { - hasBrowserCondition = Object.values(pkg.json.exports!).some( - (condition) => - typeof condition === "object" && condition.browser !== undefined - ); - hasWorkerCondition = Object.values(pkg.json.exports!).some( - (condition) => - typeof condition === "object" && condition.worker !== undefined - ); - } - } - let hasBrowserField = pkg.entrypoints[0].json.browser !== undefined; - if (hasBrowserField || hasBrowserCondition) { + if (hasBrowserField) { configs.push({ config: getRollupConfig( pkg, @@ -253,7 +238,10 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }); } - if (hasWorkerCondition) { + if ( + pkg.project.experimentalFlags.exports && + pkg.json.preconstruct.exports?.conditions?.includes("worker") + ) { configs.push({ config: getRollupConfig( pkg, diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index fa5a3e13..edf53ddd 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -1,6 +1,6 @@ import { Project } from "./project"; import { success, info } from "./logger"; -import { tsTemplate, flowTemplate, validFields } from "./utils"; +import { tsTemplate, flowTemplate, validFieldsForEntrypoint } from "./utils"; import * as babel from "@babel/core"; import * as fs from "fs-extra"; import path from "path"; @@ -69,7 +69,7 @@ export async function writeDevTSFile( entrypointSourceContent: string ) { let cjsDistPath = path - .join(entrypoint.directory, validFields.main(entrypoint)) + .join(entrypoint.directory, validFieldsForEntrypoint.main(entrypoint)) .replace(/\.js$/, ".d.ts"); let output = await (entrypoint.package.project.experimentalFlags @@ -115,7 +115,7 @@ async function writeTypeSystemFile( if (typeSystem === undefined) return; let cjsDistPath = path.join( entrypoint.directory, - validFields.main(entrypoint) + validFieldsForEntrypoint.main(entrypoint) ); if (typeSystem === "flow") { @@ -166,7 +166,10 @@ export default async function dev(projectDir: string) { let promises = [ writeTypeSystemFile(typeSystemPromise, entrypoint), fs.writeFile( - path.join(entrypoint.directory, validFields.main(entrypoint)), + path.join( + entrypoint.directory, + validFieldsForEntrypoint.main(entrypoint) + ), `"use strict"; // this file might look strange and you might be wondering what it's for // it's lets you import your source files by importing this entrypoint @@ -203,7 +206,10 @@ unregister(); promises.push( fs.symlink( entrypoint.source, - path.join(entrypoint.directory, validFields.module(entrypoint)) + path.join( + entrypoint.directory, + validFieldsForEntrypoint.module(entrypoint) + ) ) ); } @@ -221,7 +227,7 @@ unregister(); // } if (entrypoint.json.browser) { - let browserField = validFields.browser(entrypoint); + let browserField = validFieldsForEntrypoint.browser(entrypoint); for (let key of Object.keys(browserField)) { promises.push( fs.symlink( diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index a8303dd8..c5254893 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -11,7 +11,7 @@ import { errors, confirms } from "./messages"; import { Project } from "./project"; import { getUselessGlobsThatArentReallyGlobsForNewEntrypoints } from "./glob-thing"; import { - validFields, + validFieldsForEntrypoint, validFieldsFromPkg, JSONValue, getEntrypointName, @@ -21,51 +21,30 @@ import normalizePath from "normalize-path"; function getFieldsUsedInEntrypoints( descriptors: { contents: string | undefined; filename: string }[] -): [Set, boolean, boolean] { - let hasBrowserField = false; - let hasWorkerField = false; - const fields = new Set(["main"]); +): Set { + const fields = new Set(["main"]); for (let descriptor of descriptors) { if (descriptor.contents !== undefined) { let parsed = jsonParse(descriptor.contents, descriptor.filename); - for (let field of ["module", "umd:main", "browser", "exports"] as const) { + for (let field of ["module", "umd:main", "browser"] as const) { const value = parsed[field]; if (value !== undefined) { fields.add(field); - if (field === "exports" && value["."]) { - const conditions: ExportsConditions[] = Object.values(value["."]); - hasBrowserField = - hasBrowserField || - conditions.some( - (condition) => - typeof condition === "object" && - condition.browser !== undefined - ); - hasWorkerField = - hasWorkerField || - conditions.some( - (condition) => - typeof condition === "object" && - condition.worker !== undefined - ); - } } } } } - return [fields, hasBrowserField, hasWorkerField]; + return fields; } function getPlainEntrypointContent( pkg: Package, - fields: Set, + fields: Set, entrypointDir: string, - indent: string, - hasBrowserField: boolean, - hasWorkerField: boolean + indent: string ) { const obj: Partial >> = {}; for (const field of fields) { @@ -75,16 +54,6 @@ function getPlainEntrypointContent( fields.has("module"), getEntrypointName(pkg, entrypointDir) ); - } else if (field === "exports") { - if (pkg.project.experimentalFlags.exports) { - obj[field] = validFieldsFromPkg[field]( - pkg, - fields.has("module"), - hasBrowserField, - hasWorkerField, - getEntrypointName(pkg, entrypointDir) - ); - } } else { obj[field] = validFieldsFromPkg[field]( pkg, @@ -104,9 +73,7 @@ function createEntrypoints( sourceFile: string; }[] ) { - let [fields, hasBrowserField, hasWorkerField] = getFieldsUsedInEntrypoints( - descriptors - ); + let fields = getFieldsUsedInEntrypoints(descriptors); return Promise.all( descriptors.map(async ({ filename, contents, hasAccepted, sourceFile }) => { @@ -127,9 +94,7 @@ function createEntrypoints( pkg, fields, nodePath.dirname(filename), - pkg.indent, - hasBrowserField, - hasWorkerField + pkg.indent ); await fs.outputFile(filename, contents); } @@ -138,18 +103,14 @@ function createEntrypoints( ); } -export type ExportsConditions = { - worker?: { - module?: string; - default: string; - }; - browser?: { - module?: string; - default: string; - }; - module?: string; - default: string; -}; +export type ExportsConditions = + | { + worker?: { module: string; default: string } | string; + browser?: { module: string; default: string } | string; + module?: string; + default: string; + } + | string; export type ExportsCondition = "browser" | "worker" | "module" | "default"; @@ -325,7 +286,7 @@ export class Package extends Item<{ entrypoint.json = setFieldInOrder( entrypoint.json, field, - validFields[field](entrypoint) + validFieldsForEntrypoint[field](entrypoint) ); }); } diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index cddf510e..ca3489ae 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -130,150 +130,71 @@ export const validFieldsFromPkg = { } return obj; }, - exports( - pkg: Package, - hasModuleBuild: boolean, - hasBrowserField: boolean, - hasWorkerField: boolean, - entrypointName: string - ): Record { - let output: Record = {}; - pkg.entrypoints.forEach((entrypoint) => { - if (entrypointName === entrypoint.name) { - let obj: ExportsConditions = exportsHelpers.root( - pkg, - hasModuleBuild, - entrypointName - ); - if (hasBrowserField) { - obj = { - browser: exportsHelpers.target( - pkg, - hasModuleBuild, - entrypointName, - "browser" - ), - ...obj, - }; - } - if (hasWorkerField) { - obj = { - worker: exportsHelpers.target( - pkg, - hasModuleBuild, - entrypointName, - "worker" - ), - ...obj, - }; - } - output["."] = obj; - } else { - const entrypointPath = nodePath - .relative(pkg.directory, entrypoint.source) - .replace("src/", "") - .replace(/\.[tj]sx?$/, ""); - - let conditions: ExportsConditions = exportsHelpers.root( - pkg, - entrypoint.json.module !== undefined, - entrypoint.name, - entrypointPath + "/" - ); - - if (hasBrowserField) { - conditions = { - browser: exportsHelpers.target( - pkg, - hasModuleBuild, - entrypoint.name, - "browser", - entrypointPath + "/" - ), - ...conditions, - }; - } - if (hasWorkerField) { - conditions = { - worker: exportsHelpers.target( - pkg, - hasModuleBuild, - entrypoint.name, - "worker", - entrypointPath + "/" - ), - ...conditions, - }; - } - output[`./${entrypointPath}`] = conditions; - } - }); - return { - "./package.json": "./package.json", - ...output, - ...pkg.json.preconstruct.exports?.extra, - }; - }, }; -const exportsHelpers = { - root( - pkg: Package, - hasModuleBuild: boolean, - entrypointName: string, - prefix: string = "" - ) { - return exportsHelpers.target( - pkg, - hasModuleBuild, - entrypointName, - "", - prefix - ); - }, - target( - pkg: Package, - hasModuleBuild: boolean, - entrypointName: string, - target: string = "", - prefix: string = "" - ) { - return exportsHelpers.env( - pkg, +export function exportsField( + pkg: Package +): Record | undefined { + if (!pkg.project.experimentalFlags.exports) { + return; + } + if (!pkg.json.preconstruct.exports) { + return; + } + const specifiedConditions: ("worker" | "browser" | "module")[] = + (pkg.json.preconstruct.exports?.conditions as any) ?? []; + let hasModuleBuild = specifiedConditions.includes("module"); + + let output: Record = {}; + pkg.entrypoints.forEach((entrypoint) => { + let exportConditions; + exportConditions = getExportConditions( + entrypoint, hasModuleBuild, - entrypointName, - target, - prefix + undefined ); - }, - env( - pkg: Package, - hasModuleBuild: boolean, - entrypointName: string, - target: string = "", - prefix: string = "" - ) { - let safeName = getDistName(pkg, entrypointName); - - let obj: ExportsConditions = { - default: `./${prefix}dist/${safeName}.${ - target ? `${target}.` : "" - }cjs.js`, - }; - if (hasModuleBuild) { - // esm doesn't support conditional imports so if env is not set we default to dev version - obj = { - module: `./${prefix}dist/${safeName}.${ - target ? `${target}.` : "" - }esm.js`, - ...obj, + for (const condition of ["worker", "browser"] as const) { + if (!specifiedConditions.includes(condition)) continue; + if (typeof exportConditions === "string") { + exportConditions = { default: exportConditions }; + } + exportConditions = { + [condition]: getExportConditions(entrypoint, hasModuleBuild, condition), + ...exportConditions, }; } - return obj; - }, -}; -export const validFields = { + output[ + "." + entrypoint.name.replace(entrypoint.package.name, "") + ] = exportConditions; + }); + return { + "./package.json": "./package.json", + ...output, + ...pkg.json.preconstruct.exports?.extra, + }; +} + +function getExportConditions( + entrypoint: Entrypoint, + hasModuleBuild: boolean, + target: "worker" | "browser" | undefined +): { module: string; default: string } | string { + const safeName = getDistName(entrypoint.package, entrypoint.name); + const prefix = entrypoint.name.replace(entrypoint.package.name, ""); + const defaultPath = `.${prefix}/dist/${safeName}.${ + target ? `${target}.` : "" + }cjs.js`; + if (hasModuleBuild) { + return { + module: `.${prefix}/dist/${safeName}.${target ? `${target}.` : ""}esm.js`, + default: defaultPath, + }; + } + return defaultPath; +} + +export const validFieldsForEntrypoint = { main(entrypoint: Entrypoint) { return validFieldsFromPkg.main(entrypoint.package, entrypoint.name); }, @@ -290,36 +211,6 @@ export const validFields = { entrypoint.name ); }, - exports(pkg: Package) { - // skip if not enabled for the project - if (!pkg.project.experimentalFlags.exports) { - return; - } - // skip if not enabled for the package - if (!pkg.json.preconstruct.exports) { - return; - } - - // default values when `exports = true`; - let hasWorkerField = false; - let hasBrowserField = true; - let hasModuleField = true; - - const conditions = pkg.json.preconstruct.exports.conditions; - if (Array.isArray(conditions)) { - hasWorkerField = conditions.includes("worker"); - hasBrowserField = conditions.includes("browser"); - hasModuleField = conditions.includes("module"); - } - - return validFieldsFromPkg.exports( - pkg, - hasModuleField, - hasBrowserField, - hasWorkerField, - pkg.name - ); - }, }; export function flowTemplate(hasDefaultExport: boolean, relativePath: string) { diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 9a05d797..0e069d4b 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -4,7 +4,7 @@ import chalk from "chalk"; import { errors } from "./messages"; import { Package } from "./package"; import { isFieldValid } from "./validate"; -import { validFields, setFieldInOrder } from "./utils"; +import { setFieldInOrder, exportsField } from "./utils"; let keys: (obj: Obj) => (keyof Obj)[] = Object.keys; @@ -19,7 +19,7 @@ export async function fixPackage(pkg: Package) { browser: pkg.entrypoints.some((x) => x.json.browser !== undefined), }; - pkg.json = setFieldInOrder(pkg.json, "exports", validFields.exports(pkg)); + pkg.json = setFieldInOrder(pkg.json, "exports", exportsField(pkg)); keys(fields) .filter((x) => fields[x]) @@ -54,11 +54,7 @@ export function validatePackage(pkg: Package) { !isFieldValid.exports(pkg) ) { throw new FixableError( - errors.invalidField( - "exports", - pkg.json.exports, - validFields.exports(pkg) - ), + errors.invalidField("exports", pkg.json.exports, exportsField(pkg)), pkg.name ); } diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index c3f74b24..d6f66155 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -3,7 +3,7 @@ import { Package } from "./package"; import { Entrypoint } from "./entrypoint"; import { errors, successes, infos } from "./messages"; import { BatchError, FatalError, FixableError } from "./errors"; -import { validFields } from "./utils"; +import { exportsField, validFieldsForEntrypoint } from "./utils"; import * as logger from "./logger"; import equal from "fast-deep-equal"; import { validatePackage } from "./validate-package"; @@ -15,20 +15,33 @@ import chalk from "chalk"; export const isFieldValid = { main(entrypoint: Entrypoint) { - return entrypoint.json.main === validFields.main(entrypoint); + return entrypoint.json.main === validFieldsForEntrypoint.main(entrypoint); }, module(entrypoint: Entrypoint) { - return entrypoint.json.module === validFields.module(entrypoint); + return ( + entrypoint.json.module === validFieldsForEntrypoint.module(entrypoint) + ); }, "umd:main"(entrypoint: Entrypoint) { - return entrypoint.json["umd:main"] === validFields["umd:main"](entrypoint); + return ( + entrypoint.json["umd:main"] === + validFieldsForEntrypoint["umd:main"](entrypoint) + ); }, browser(entrypoint: Entrypoint): boolean { - return equal(entrypoint.json.browser, validFields.browser(entrypoint)); + return equal( + entrypoint.json.browser, + validFieldsForEntrypoint.browser(entrypoint) + ); }, exports(pkg: Package): boolean { - const generated = validFields.exports(pkg); - + if ( + !pkg.project.experimentalFlags.exports || + !pkg.json.preconstruct.exports + ) { + return true; + } + const generated = exportsField(pkg); // JSON.stringify to make sure conditions are in proper order return JSON.stringify(pkg.json.exports) === JSON.stringify(generated); }, @@ -57,7 +70,8 @@ function validateEntrypoint(entrypoint: Entrypoint, log: boolean) { entrypoint.package.project.json.preconstruct.distFilenameStrategy = "unscoped-package-name"; isUsingOldDistFilenames = - validFields[field](entrypoint) === entrypoint.json[field]; + validFieldsForEntrypoint[field](entrypoint) === + entrypoint.json[field]; } finally { if (prevDistFilenameStrategy === undefined) { delete entrypoint.package.project.json.preconstruct @@ -90,7 +104,7 @@ function validateEntrypoint(entrypoint: Entrypoint, log: boolean) { errors.invalidField( field, entrypoint.json[field], - validFields[field](entrypoint) + validFieldsForEntrypoint[field](entrypoint) ), entrypoint.name ) From edce3aea3d0a6f3ce2d7fa800a39317f5076faa8 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 1 Jul 2022 18:47:07 +1000 Subject: [PATCH 22/56] Enforce alignment with browser and module fields and conditions --- packages/cli/src/messages.ts | 4 +++ packages/cli/src/validate-package.ts | 52 +++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/messages.ts b/packages/cli/src/messages.ts index e335d63e..77062345 100644 --- a/packages/cli/src/messages.ts +++ b/packages/cli/src/messages.ts @@ -20,6 +20,10 @@ export let errors = { "packages must have at least one entrypoint, this package has no entrypoints", fieldMustExistInAllEntrypointsIfExistsDeclinedFixDuringInit: (field: Field) => `all entrypoints in a package must have the same fields and one entrypoint in this package has a ${field} field but you've declined the fix`, + missingConditionWithFieldPresent: (condition: "browser" | "module") => + `the exports field is configured and the ${condition} field exists in this package but it is not specified in the preconstruct.exports.conditions field`, + missingFieldWithConditionPresent: (condition: "browser" | "module") => + `the exports field is configured and the ${condition} condition exists is set in preconstruct.exports.conditions the field is not present at the top-level`, }; export let confirms = { diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 0e069d4b..0d84900a 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -19,6 +19,23 @@ export async function fixPackage(pkg: Package) { browser: pkg.entrypoints.some((x) => x.json.browser !== undefined), }; + if (pkg.project.experimentalFlags.exports && pkg.json.preconstruct.exports) { + for (const condition of ["module", "browser"] as const) { + if ( + fields[condition] || + !!pkg.json.preconstruct.exports.conditions?.includes(condition) + ) { + if (!pkg.json.preconstruct.exports.conditions) { + pkg.json.preconstruct.exports.conditions = []; + } + if (!pkg.json.preconstruct.exports.conditions.includes(condition)) { + pkg.json.preconstruct.exports.conditions.push(condition); + } + fields[condition] = true; + } + } + } + pkg.json = setFieldInOrder(pkg.json, "exports", exportsField(pkg)); keys(fields) @@ -48,15 +65,32 @@ export function validatePackage(pkg: Package) { // "exports" is missing because it is validated on the root package }; - if ( - pkg.project.experimentalFlags.exports && - pkg.json.preconstruct.exports && - !isFieldValid.exports(pkg) - ) { - throw new FixableError( - errors.invalidField("exports", pkg.json.exports, exportsField(pkg)), - pkg.name - ); + if (pkg.project.experimentalFlags.exports && pkg.json.preconstruct.exports) { + for (const condition of ["module", "browser"] as const) { + const hasField = fields[condition]; + const hasCondition = !!pkg.json.preconstruct.exports?.conditions?.includes( + condition + ); + if (hasField && !hasCondition) { + throw new FixableError( + errors.missingConditionWithFieldPresent(condition), + pkg.name + ); + } + if (!hasField && hasCondition) { + throw new FixableError( + errors.missingFieldWithConditionPresent(condition), + pkg.name + ); + } + } + + if (!isFieldValid.exports(pkg)) { + throw new FixableError( + errors.invalidField("exports", pkg.json.exports, exportsField(pkg)), + pkg.name + ); + } } pkg.entrypoints.forEach((entrypoint) => { From 38c13f90d043ec244098b530718927a56c87a445 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 1 Jul 2022 19:16:47 +1000 Subject: [PATCH 23/56] envConditions --- packages/cli/src/__tests__/fix.ts | 80 ++++++++++++++++------------ packages/cli/src/build/config.ts | 2 +- packages/cli/src/messages.ts | 7 ++- packages/cli/src/package.ts | 18 +++---- packages/cli/src/utils.ts | 36 ++++--------- packages/cli/src/validate-package.ts | 64 +++++++++++----------- 6 files changed, 103 insertions(+), 104 deletions(-) diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index ca31702b..30958058 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -9,6 +9,7 @@ import { createPackageCheckTestCreator, testdir, js, + getFiles, } from "../../test-utils"; import { promptInput as _promptInput } from "../prompt"; import fs from "fs-extra"; @@ -71,50 +72,63 @@ test("set main and module field", async () => { }); test("set exports field when opt-in", async () => { - let tmpPath = f.copy("package-exports"); + let tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "package-exports", + main: "index.js", + module: "dist/package-exports.esm.js", + preconstruct: { + exports: { + envConditions: ["worker", "browser"], + }, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); await fix(tmpPath); - let pkg = await getPkg(tmpPath); - - // NOTE: The order of the conditions is important and JEST is sorting the keys alphabetically. - // The tests above actually assert that the order is correct. - expect(JSON.stringify(pkg, null, 2)).toMatchInlineSnapshot(` - "{ - \\"name\\": \\"package-exports\\", - \\"version\\": \\"1.0.0\\", - \\"main\\": \\"dist/package-exports.cjs.js\\", - \\"license\\": \\"MIT\\", - \\"private\\": true, - \\"module\\": \\"dist/package-exports.esm.js\\", - \\"exports\\": { - \\"./package.json\\": \\"./package.json\\", - \\".\\": { - \\"browser\\": { - \\"module\\": \\"./dist/package-exports.browser.esm.js\\", - \\"default\\": \\"./dist/package-exports.browser.cjs.js\\" + expect(await getFiles(tmpPath, ["package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "package-exports", + "main": "dist/package-exports.cjs.js", + "module": "dist/package-exports.esm.js", + "browser": { + "./dist/package-exports.cjs.js": "./dist/package-exports.browser.cjs.js", + "./dist/package-exports.esm.js": "./dist/package-exports.browser.esm.js" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "browser": { + "module": "./dist/package-exports.browser.esm.js", + "default": "./dist/package-exports.browser.cjs.js" }, - \\"worker\\": { - \\"module\\": \\"./dist/package-exports.worker.esm.js\\", - \\"default\\": \\"./dist/package-exports.worker.cjs.js\\" + "worker": { + "module": "./dist/package-exports.worker.esm.js", + "default": "./dist/package-exports.worker.cjs.js" }, - \\"module\\": \\"./dist/package-exports.esm.js\\", - \\"default\\": \\"./dist/package-exports.cjs.js\\" + "module": "./dist/package-exports.esm.js", + "default": "./dist/package-exports.cjs.js" } }, - \\"preconstruct\\": { - \\"exports\\": { - \\"conditions\\": [ - \\"worker\\", - \\"browser\\", - \\"module\\" + "preconstruct": { + "exports": { + "envConditions": [ + "worker", + "browser" ] }, - \\"___experimentalFlags_WILL_CHANGE_IN_PATCH\\": { - \\"exports\\": true + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true } } - }" + } + `); }); diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index a3db6b1c..ee7380fc 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -240,7 +240,7 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { if ( pkg.project.experimentalFlags.exports && - pkg.json.preconstruct.exports?.conditions?.includes("worker") + pkg.json.preconstruct.exports?.envConditions?.includes("worker") ) { configs.push({ config: getRollupConfig( diff --git a/packages/cli/src/messages.ts b/packages/cli/src/messages.ts index 77062345..dcc6c04d 100644 --- a/packages/cli/src/messages.ts +++ b/packages/cli/src/messages.ts @@ -20,10 +20,9 @@ export let errors = { "packages must have at least one entrypoint, this package has no entrypoints", fieldMustExistInAllEntrypointsIfExistsDeclinedFixDuringInit: (field: Field) => `all entrypoints in a package must have the same fields and one entrypoint in this package has a ${field} field but you've declined the fix`, - missingConditionWithFieldPresent: (condition: "browser" | "module") => - `the exports field is configured and the ${condition} field exists in this package but it is not specified in the preconstruct.exports.conditions field`, - missingFieldWithConditionPresent: (condition: "browser" | "module") => - `the exports field is configured and the ${condition} condition exists is set in preconstruct.exports.conditions the field is not present at the top-level`, + missingBrowserConditionWithFieldPresent: `the exports field is configured and the browser field exists in this package but it is not specified in the preconstruct.exports.conditions field`, + missingBrowserFieldWithConditionPresent: `the exports field is configured and the browser condition exists is set in preconstruct.exports.conditions the field is not present at the top-level`, + noModuleFieldWithExportsField: `when using the exports field, the module field must also be specified`, }; export let confirms = { diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index c5254893..67ddbff0 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -103,23 +103,21 @@ function createEntrypoints( ); } -export type ExportsConditions = - | { - worker?: { module: string; default: string } | string; - browser?: { module: string; default: string } | string; - module?: string; - default: string; - } - | string; +export type ExportsConditions = { + worker?: { module: string; default: string }; + browser?: { module: string; default: string }; + module: string; + default: string; +}; -export type ExportsCondition = "browser" | "worker" | "module" | "default"; +export type EnvCondition = "browser" | "worker"; export class Package extends Item<{ name?: JSONValue; preconstruct: { exports?: { extra?: Record; - conditions?: ExportsCondition[]; + envConditions?: EnvCondition[]; }; entrypoints?: JSONValue; }; diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index ca3489ae..01859fcb 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -141,25 +141,18 @@ export function exportsField( if (!pkg.json.preconstruct.exports) { return; } - const specifiedConditions: ("worker" | "browser" | "module")[] = - (pkg.json.preconstruct.exports?.conditions as any) ?? []; - let hasModuleBuild = specifiedConditions.includes("module"); + const envConditions: ("worker" | "browser")[] = + pkg.json.preconstruct.exports?.envConditions ?? []; let output: Record = {}; pkg.entrypoints.forEach((entrypoint) => { - let exportConditions; - exportConditions = getExportConditions( + let exportConditions: ExportsConditions = getExportConditions( entrypoint, - hasModuleBuild, undefined ); - for (const condition of ["worker", "browser"] as const) { - if (!specifiedConditions.includes(condition)) continue; - if (typeof exportConditions === "string") { - exportConditions = { default: exportConditions }; - } + for (const env of envConditions) { exportConditions = { - [condition]: getExportConditions(entrypoint, hasModuleBuild, condition), + [env]: getExportConditions(entrypoint, env), ...exportConditions, }; } @@ -177,21 +170,14 @@ export function exportsField( function getExportConditions( entrypoint: Entrypoint, - hasModuleBuild: boolean, - target: "worker" | "browser" | undefined -): { module: string; default: string } | string { + env: "worker" | "browser" | undefined +) { const safeName = getDistName(entrypoint.package, entrypoint.name); const prefix = entrypoint.name.replace(entrypoint.package.name, ""); - const defaultPath = `.${prefix}/dist/${safeName}.${ - target ? `${target}.` : "" - }cjs.js`; - if (hasModuleBuild) { - return { - module: `.${prefix}/dist/${safeName}.${target ? `${target}.` : ""}esm.js`, - default: defaultPath, - }; - } - return defaultPath; + return { + module: `.${prefix}/dist/${safeName}.${env ? `${env}.` : ""}esm.js`, + default: `.${prefix}/dist/${safeName}.${env ? `${env}.` : ""}cjs.js`, + }; } export const validFieldsForEntrypoint = { diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 0d84900a..4bd42e92 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -20,19 +20,17 @@ export async function fixPackage(pkg: Package) { }; if (pkg.project.experimentalFlags.exports && pkg.json.preconstruct.exports) { - for (const condition of ["module", "browser"] as const) { - if ( - fields[condition] || - !!pkg.json.preconstruct.exports.conditions?.includes(condition) - ) { - if (!pkg.json.preconstruct.exports.conditions) { - pkg.json.preconstruct.exports.conditions = []; - } - if (!pkg.json.preconstruct.exports.conditions.includes(condition)) { - pkg.json.preconstruct.exports.conditions.push(condition); - } - fields[condition] = true; + if ( + fields.browser || + !!pkg.json.preconstruct.exports.envConditions?.includes("browser") + ) { + if (!pkg.json.preconstruct.exports.envConditions) { + pkg.json.preconstruct.exports.envConditions = []; + } + if (!pkg.json.preconstruct.exports.envConditions.includes("browser")) { + pkg.json.preconstruct.exports.envConditions.push("browser"); } + fields.browser = true; } } @@ -66,33 +64,37 @@ export function validatePackage(pkg: Package) { }; if (pkg.project.experimentalFlags.exports && pkg.json.preconstruct.exports) { - for (const condition of ["module", "browser"] as const) { - const hasField = fields[condition]; - const hasCondition = !!pkg.json.preconstruct.exports?.conditions?.includes( - condition + if (!fields.module) { + throw new FixableError( + errors.missingBrowserConditionWithFieldPresent, + pkg.name ); - if (hasField && !hasCondition) { - throw new FixableError( - errors.missingConditionWithFieldPresent(condition), - pkg.name - ); - } - if (!hasField && hasCondition) { - throw new FixableError( - errors.missingFieldWithConditionPresent(condition), - pkg.name - ); - } } - - if (!isFieldValid.exports(pkg)) { + const hasField = fields["browser"]; + const hasCondition = !!pkg.json.preconstruct.exports?.envConditions?.includes( + "browser" + ); + if (hasField && !hasCondition) { + throw new FixableError( + errors.missingBrowserConditionWithFieldPresent, + pkg.name + ); + } + if (!hasField && hasCondition) { throw new FixableError( - errors.invalidField("exports", pkg.json.exports, exportsField(pkg)), + errors.missingBrowserFieldWithConditionPresent, pkg.name ); } } + if (!isFieldValid.exports(pkg)) { + throw new FixableError( + errors.invalidField("exports", pkg.json.exports, exportsField(pkg)), + pkg.name + ); + } + pkg.entrypoints.forEach((entrypoint) => { keys(fields).forEach((field) => { if (entrypoint.json[field] && !fields[field]) { From 7e31d4dfb87b412bf79e0493d2a329a122054943 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 1 Jul 2022 19:29:11 +1000 Subject: [PATCH 24/56] Some tests --- __fixtures__/package-exports/package.json | 16 -- __fixtures__/package-exports/src/index.ts | 1 - packages/cli/src/__tests__/fix.ts | 291 ++++++++++++++++++++++ packages/cli/src/validate-package.ts | 8 +- 4 files changed, 298 insertions(+), 18 deletions(-) delete mode 100644 __fixtures__/package-exports/package.json delete mode 100644 __fixtures__/package-exports/src/index.ts diff --git a/__fixtures__/package-exports/package.json b/__fixtures__/package-exports/package.json deleted file mode 100644 index 992ac1ae..00000000 --- a/__fixtures__/package-exports/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "package-exports", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "private": true, - "module": "dist/package-exports.esm.js", - "preconstruct": { - "exports": { - "conditions": ["worker", "browser", "module"] - }, - "___experimentalFlags_WILL_CHANGE_IN_PATCH": { - "exports": true - } - } -} diff --git a/__fixtures__/package-exports/src/index.ts b/__fixtures__/package-exports/src/index.ts deleted file mode 100644 index af8ec620..00000000 --- a/__fixtures__/package-exports/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export default "something"; diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index 30958058..82f4931f 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -132,6 +132,297 @@ test("set exports field when opt-in", async () => { `); }); +test("set exports field when opt-in", async () => { + let tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "package-exports", + main: "index.js", + module: "dist/package-exports.esm.js", + preconstruct: { + exports: { + envConditions: ["worker", "browser"], + }, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + + await fix(tmpPath); + + expect(await getFiles(tmpPath, ["package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "package-exports", + "main": "dist/package-exports.cjs.js", + "module": "dist/package-exports.esm.js", + "browser": { + "./dist/package-exports.cjs.js": "./dist/package-exports.browser.cjs.js", + "./dist/package-exports.esm.js": "./dist/package-exports.browser.esm.js" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "browser": { + "module": "./dist/package-exports.browser.esm.js", + "default": "./dist/package-exports.browser.cjs.js" + }, + "worker": { + "module": "./dist/package-exports.worker.esm.js", + "default": "./dist/package-exports.worker.cjs.js" + }, + "module": "./dist/package-exports.esm.js", + "default": "./dist/package-exports.cjs.js" + } + }, + "preconstruct": { + "exports": { + "envConditions": [ + "worker", + "browser" + ] + }, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } + } + } + + `); +}); + +test("set exports field when opt-in with no env conditions", async () => { + let tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "package-exports", + main: "index.js", + module: "dist/package-exports.esm.js", + preconstruct: { + exports: true, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + + await fix(tmpPath); + + expect(await getFiles(tmpPath, ["package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "package-exports", + "main": "dist/package-exports.cjs.js", + "module": "dist/package-exports.esm.js", + "exports": { + "./package.json": "./package.json", + ".": { + "module": "./dist/package-exports.esm.js", + "default": "./dist/package-exports.cjs.js" + } + }, + "preconstruct": { + "exports": true, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } + } + } + + `); +}); + +test("set exports field with multiple entrypoints", async () => { + let tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "@blah/something", + main: "index.js", + module: "dist/package-exports.esm.js", + preconstruct: { + entrypoints: ["index.js", "other.js", "deep/something.js"], + exports: { + envConditions: ["worker", "browser"], + }, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "other/package.json": JSON.stringify({}), + "deep/something/package.json": JSON.stringify({}), + "src/index.js": "", + "src/other.js": "", + "src/deep/something.js": "", + }); + + await fix(tmpPath); + + expect(await getFiles(tmpPath, ["**/package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ deep/something/package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "main": "dist/blah-something-deep-something.cjs.js", + "module": "dist/blah-something-deep-something.esm.js", + "browser": { + "./dist/blah-something-deep-something.cjs.js": "./dist/blah-something-deep-something.browser.cjs.js", + "./dist/blah-something-deep-something.esm.js": "./dist/blah-something-deep-something.browser.esm.js" + } + } + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ other/package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "main": "dist/blah-something-other.cjs.js", + "module": "dist/blah-something-other.esm.js", + "browser": { + "./dist/blah-something-other.cjs.js": "./dist/blah-something-other.browser.cjs.js", + "./dist/blah-something-other.esm.js": "./dist/blah-something-other.browser.esm.js" + } + } + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "@blah/something", + "main": "dist/blah-something.cjs.js", + "module": "dist/blah-something.esm.js", + "browser": { + "./dist/blah-something.cjs.js": "./dist/blah-something.browser.cjs.js", + "./dist/blah-something.esm.js": "./dist/blah-something.browser.esm.js" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "browser": { + "module": "./dist/blah-something.browser.esm.js", + "default": "./dist/blah-something.browser.cjs.js" + }, + "worker": { + "module": "./dist/blah-something.worker.esm.js", + "default": "./dist/blah-something.worker.cjs.js" + }, + "module": "./dist/blah-something.esm.js", + "default": "./dist/blah-something.cjs.js" + }, + "./other": { + "browser": { + "module": "./other/dist/blah-something-other.browser.esm.js", + "default": "./other/dist/blah-something-other.browser.cjs.js" + }, + "worker": { + "module": "./other/dist/blah-something-other.worker.esm.js", + "default": "./other/dist/blah-something-other.worker.cjs.js" + }, + "module": "./other/dist/blah-something-other.esm.js", + "default": "./other/dist/blah-something-other.cjs.js" + }, + "./deep/something": { + "browser": { + "module": "./deep/something/dist/blah-something-deep-something.browser.esm.js", + "default": "./deep/something/dist/blah-something-deep-something.browser.cjs.js" + }, + "worker": { + "module": "./deep/something/dist/blah-something-deep-something.worker.esm.js", + "default": "./deep/something/dist/blah-something-deep-something.worker.cjs.js" + }, + "module": "./deep/something/dist/blah-something-deep-something.esm.js", + "default": "./deep/something/dist/blah-something-deep-something.cjs.js" + } + }, + "preconstruct": { + "entrypoints": [ + "index.js", + "other.js", + "deep/something.js" + ], + "exports": { + "envConditions": [ + "worker", + "browser" + ] + }, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } + } + } + + `); +}); + +test("set exports field without root entrypoint", async () => { + let tmpPath = await testdir({ + "package.json": + JSON.stringify( + { + name: "@blah/something", + preconstruct: { + entrypoints: ["other.js"], + exports: { + envConditions: ["worker", "browser"], + }, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }, + null, + 2 + ) + "\n", + "other/package.json": JSON.stringify({}), + "src/other.js": "", + }); + + await fix(tmpPath); + + expect(await getFiles(tmpPath, ["**/package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ other/package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "main": "dist/blah-something-other.cjs.js", + "module": "dist/blah-something-other.esm.js", + "browser": { + "./dist/blah-something-other.cjs.js": "./dist/blah-something-other.browser.cjs.js", + "./dist/blah-something-other.esm.js": "./dist/blah-something-other.browser.esm.js" + } + } + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "@blah/something", + "preconstruct": { + "entrypoints": [ + "other.js" + ], + "exports": { + "envConditions": [ + "worker", + "browser" + ] + }, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } + }, + "exports": { + "./package.json": "./package.json", + "./other": { + "browser": { + "module": "./other/dist/blah-something-other.browser.esm.js", + "default": "./other/dist/blah-something-other.browser.cjs.js" + }, + "worker": { + "module": "./other/dist/blah-something-other.worker.esm.js", + "default": "./other/dist/blah-something-other.worker.cjs.js" + }, + "module": "./other/dist/blah-something-other.esm.js", + "default": "./other/dist/blah-something-other.cjs.js" + } + } + } + + `); +}); + test("new dist filenames", async () => { let tmpPath = f.copy("basic-package"); diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 4bd42e92..6411abb2 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -14,7 +14,11 @@ export async function fixPackage(pkg: Package) { } let fields = { main: true, - module: pkg.entrypoints.some((x) => x.json.module !== undefined), + module: + pkg.entrypoints.some((x) => x.json.module !== undefined) || + !!( + pkg.project.experimentalFlags.exports && pkg.json.preconstruct.exports + ), "umd:main": pkg.entrypoints.some((x) => x.json["umd:main"] !== undefined), browser: pkg.entrypoints.some((x) => x.json.browser !== undefined), }; @@ -36,6 +40,8 @@ export async function fixPackage(pkg: Package) { pkg.json = setFieldInOrder(pkg.json, "exports", exportsField(pkg)); + await pkg.save(); + keys(fields) .filter((x) => fields[x]) .forEach((field) => { From dfe88ee71f04e1802223e5ba1f1cb6c21a7ad8e5 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 1 Jul 2022 19:34:55 +1000 Subject: [PATCH 25/56] Fix dev --- packages/cli/src/dev.ts | 37 +++++++++++++++++++++++-------------- packages/cli/src/utils.ts | 10 +++++----- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index edf53ddd..40c4bd3a 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -1,6 +1,11 @@ import { Project } from "./project"; import { success, info } from "./logger"; -import { tsTemplate, flowTemplate, validFieldsForEntrypoint } from "./utils"; +import { + tsTemplate, + flowTemplate, + validFieldsForEntrypoint, + getExportConditions, +} from "./utils"; import * as babel from "@babel/core"; import * as fs from "fs-extra"; import path from "path"; @@ -214,25 +219,29 @@ unregister(); ); } - // if ( - // pkg.project.experimentalFlags.exports && - // pkg.json.preconstruct.exports?.conditions?.includes("worker") - // ) { - // promises.push( - // fs.symlink( - // entrypoint.source, - // path.join(entrypoint.directory, browserField[key]) - // ) - // ); - // } + if ( + pkg.project.experimentalFlags.exports && + pkg.json.preconstruct.exports?.envConditions?.includes("worker") + ) { + for (const output of Object.values( + getExportConditions(entrypoint, "browser") + )) { + promises.push( + fs.symlink( + entrypoint.source, + path.join(entrypoint.directory, output) + ) + ); + } + } if (entrypoint.json.browser) { let browserField = validFieldsForEntrypoint.browser(entrypoint); - for (let key of Object.keys(browserField)) { + for (let output of Object.values(browserField)) { promises.push( fs.symlink( entrypoint.source, - path.join(entrypoint.directory, browserField[key]) + path.join(entrypoint.directory, output) ) ); } diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 01859fcb..9737002d 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -135,10 +135,10 @@ export const validFieldsFromPkg = { export function exportsField( pkg: Package ): Record | undefined { - if (!pkg.project.experimentalFlags.exports) { - return; - } - if (!pkg.json.preconstruct.exports) { + if ( + !pkg.project.experimentalFlags.exports || + !pkg.json.preconstruct.exports + ) { return; } const envConditions: ("worker" | "browser")[] = @@ -168,7 +168,7 @@ export function exportsField( }; } -function getExportConditions( +export function getExportConditions( entrypoint: Entrypoint, env: "worker" | "browser" | undefined ) { From da2b0002f389df0cf8140c53029450029ac760b0 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Mon, 4 Jul 2022 09:50:28 +1000 Subject: [PATCH 26/56] Validate exports field config --- packages/cli/src/build/config.ts | 6 +-- packages/cli/src/dev.ts | 7 +-- packages/cli/src/package.ts | 79 ++++++++++++++++++++++++++++ packages/cli/src/utils.ts | 12 ++--- packages/cli/src/validate-package.ts | 33 ++++++------ packages/cli/src/validate.ts | 7 +-- 6 files changed, 104 insertions(+), 40 deletions(-) diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index ee7380fc..77cf1762 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -238,10 +238,8 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }); } - if ( - pkg.project.experimentalFlags.exports && - pkg.json.preconstruct.exports?.envConditions?.includes("worker") - ) { + const exportsFieldConfig = pkg.exportsFieldConfig(); + if (exportsFieldConfig?.envConditions.has("worker")) { configs.push({ config: getRollupConfig( pkg, diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index 40c4bd3a..ecf66b91 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -219,12 +219,9 @@ unregister(); ); } - if ( - pkg.project.experimentalFlags.exports && - pkg.json.preconstruct.exports?.envConditions?.includes("worker") - ) { + if (pkg.exportsFieldConfig()?.envConditions?.has("worker")) { for (const output of Object.values( - getExportConditions(entrypoint, "browser") + getExportConditions(entrypoint, "worker") )) { promises.push( fs.symlink( diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 67ddbff0..756c99e6 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -298,4 +298,83 @@ export class Package extends Item<{ } return this.json.name; } + + exportsFieldConfig(): CanonicalExportsFieldConfig { + if (!this.project.experimentalFlags.exports) { + return; + } + return parseExportsFieldConfig(this.json.preconstruct.exports, this.name); + } +} + +type CanonicalExportsFieldConfig = + | undefined + | { + envConditions: Set<"worker" | "browser">; + extra: Record; + }; + +function parseExportsFieldConfig( + config: unknown, + name: string +): CanonicalExportsFieldConfig { + if ( + (typeof config !== "boolean" && + typeof config !== "object" && + config !== undefined) || + config === null + ) { + throw new FatalError( + 'the "preconstruct.exports" field must be a boolean or an object', + name + ); + } + if (config === false || config === undefined) { + return undefined; + } + const parsedConfig: CanonicalExportsFieldConfig = { + envConditions: new Set(), + extra: {}, + }; + if (config === true) { + return parsedConfig; + } + for (const [key, value] of Object.entries(config) as [string, unknown][]) { + if (key === "extra") { + if (typeof value === "object" && value !== null) { + parsedConfig.extra = value as Record; + } else { + throw new FatalError( + 'the "preconstruct.exports.extra" field must be an object if it is present', + name + ); + } + } else if (key === "envConditions") { + if ( + Array.isArray(value) && + value.every( + (v): v is "worker" | "browser" => v === "worker" || v === "browser" + ) + ) { + parsedConfig.envConditions = new Set(value); + if (parsedConfig.envConditions.size !== value.length) { + throw new FatalError( + 'the "preconstruct.exports.envConditions" field must not have duplicates', + name + ); + } + } else { + throw new FatalError( + 'the "preconstruct.exports.envConditions" field must be an array containing zero or more of "worker" and "browser" if it is present', + name + ); + } + } else { + throw new FatalError( + `the "preconstruct.exports" field contains an unknown key "${key}"`, + name + ); + } + } + return parsedConfig; } diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 9737002d..1a590b73 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -135,14 +135,10 @@ export const validFieldsFromPkg = { export function exportsField( pkg: Package ): Record | undefined { - if ( - !pkg.project.experimentalFlags.exports || - !pkg.json.preconstruct.exports - ) { + const exportsFieldConfig = pkg.exportsFieldConfig(); + if (!exportsFieldConfig) { return; } - const envConditions: ("worker" | "browser")[] = - pkg.json.preconstruct.exports?.envConditions ?? []; let output: Record = {}; pkg.entrypoints.forEach((entrypoint) => { @@ -150,7 +146,7 @@ export function exportsField( entrypoint, undefined ); - for (const env of envConditions) { + for (const env of exportsFieldConfig.envConditions) { exportConditions = { [env]: getExportConditions(entrypoint, env), ...exportConditions, @@ -164,7 +160,7 @@ export function exportsField( return { "./package.json": "./package.json", ...output, - ...pkg.json.preconstruct.exports?.extra, + ...exportsFieldConfig.extra, }; } diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 6411abb2..c252b5c6 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -16,18 +16,18 @@ export async function fixPackage(pkg: Package) { main: true, module: pkg.entrypoints.some((x) => x.json.module !== undefined) || - !!( - pkg.project.experimentalFlags.exports && pkg.json.preconstruct.exports - ), + !!(pkg.project.experimentalFlags.exports && pkg.exportsFieldConfig()), "umd:main": pkg.entrypoints.some((x) => x.json["umd:main"] !== undefined), browser: pkg.entrypoints.some((x) => x.json.browser !== undefined), }; - if (pkg.project.experimentalFlags.exports && pkg.json.preconstruct.exports) { - if ( - fields.browser || - !!pkg.json.preconstruct.exports.envConditions?.includes("browser") - ) { + const exportsFieldConfig = pkg.exportsFieldConfig(); + + if (exportsFieldConfig) { + if (fields.browser || exportsFieldConfig.envConditions.has("browser")) { + if (typeof pkg.json.preconstruct.exports !== "object") { + pkg.json.preconstruct.exports = {}; + } if (!pkg.json.preconstruct.exports.envConditions) { pkg.json.preconstruct.exports.envConditions = []; } @@ -66,20 +66,17 @@ export function validatePackage(pkg: Package) { module: pkg.entrypoints[0].json.module !== undefined, "umd:main": pkg.entrypoints[0].json["umd:main"] !== undefined, browser: pkg.entrypoints[0].json.browser !== undefined, - // "exports" is missing because it is validated on the root package + // "exports" is not here because it is not like these fields, it exists on a package, not an entrypoint }; - if (pkg.project.experimentalFlags.exports && pkg.json.preconstruct.exports) { + const exportsFieldConfig = pkg.exportsFieldConfig(); + + if (exportsFieldConfig) { if (!fields.module) { - throw new FixableError( - errors.missingBrowserConditionWithFieldPresent, - pkg.name - ); + throw new FixableError(errors.noModuleFieldWithExportsField, pkg.name); } - const hasField = fields["browser"]; - const hasCondition = !!pkg.json.preconstruct.exports?.envConditions?.includes( - "browser" - ); + const hasField = fields.browser; + const hasCondition = exportsFieldConfig.envConditions.has("browser"); if (hasField && !hasCondition) { throw new FixableError( errors.missingBrowserConditionWithFieldPresent, diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts index d6f66155..8f7c7e56 100644 --- a/packages/cli/src/validate.ts +++ b/packages/cli/src/validate.ts @@ -35,13 +35,10 @@ export const isFieldValid = { ); }, exports(pkg: Package): boolean { - if ( - !pkg.project.experimentalFlags.exports || - !pkg.json.preconstruct.exports - ) { + const generated = exportsField(pkg); + if (generated === undefined) { return true; } - const generated = exportsField(pkg); // JSON.stringify to make sure conditions are in proper order return JSON.stringify(pkg.json.exports) === JSON.stringify(generated); }, From 85e49db3ef24e2e8aec67c1d336ae2838a98dd19 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Mon, 4 Jul 2022 14:45:52 +1000 Subject: [PATCH 27/56] dev test --- packages/cli/src/__tests__/dev.ts | 40 ++++++++++++++++++++++++++++++- packages/cli/test-utils/index.ts | 7 ++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index ac7712ef..7c33a6fe 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -2,7 +2,7 @@ import spawn from "spawndamnit"; import path from "path"; import * as fs from "fs-extra"; import * as realFs from "fs"; -import { js, testdir, typescriptFixture } from "../../test-utils"; +import { getFiles, js, testdir, typescriptFixture } from "../../test-utils"; import dev from "../dev"; import normalizePath from "normalize-path"; import escapeStringRegexp from "escape-string-regexp"; @@ -305,3 +305,41 @@ test("typescript with typeScriptProxyFileWithImportEqualsRequireAndExportEquals" " `); }); + +test("exports field with worker condition", async () => { + let tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "@something/blah", + main: "dist/something-blah.cjs.js", + module: "dist/something-blah.esm.js", + exports: { + "./package.json": "./package.json", + ".": { + worker: { + module: "./dist/something-blah.worker.esm.js", + default: "./dist/something-blah.worker.cjs.js", + }, + module: "./dist/something-blah.esm.js", + default: "./dist/something-blah.cjs.js", + }, + }, + preconstruct: { + exports: { + envConditions: ["worker"], + }, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + + await dev(tmpPath); + + expect(await getFiles(tmpPath, ["dist/**", "!dist/something-blah.cjs.js"])) + .toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/something-blah.esm.js, dist/something-blah.worker.cjs.js, dist/something-blah.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + symbolic link to ../src/index.js + `); +}); diff --git a/packages/cli/test-utils/index.ts b/packages/cli/test-utils/index.ts index 6f0cb5d4..318f97a9 100644 --- a/packages/cli/test-utils/index.ts +++ b/packages/cli/test-utils/index.ts @@ -392,6 +392,13 @@ export async function getDist(dir: string) { } async function readNormalizedFile(filePath: string): Promise { + const stat = await fs.lstat(filePath); + if (stat.isSymbolicLink()) { + const targetPath = await fs.readlink(filePath); + return `symbolic link to ${normalizePath( + path.relative(path.dirname(filePath), targetPath) + )}`; + } let content = await fs.readFile(filePath, "utf8"); // to normalise windows line endings content = content.replace(/\r\n/g, "\n"); From 51e7b204f4b53253ca2cf0e792dbbb3c049934ce Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Mon, 4 Jul 2022 14:55:38 +1000 Subject: [PATCH 28/56] Build test --- packages/cli/src/build/__tests__/build.ts | 125 ++++++++++++++++++++++ packages/cli/src/utils.ts | 4 +- 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/build/__tests__/build.ts b/packages/cli/src/build/__tests__/build.ts index 75a71c33..fbad55fb 100644 --- a/packages/cli/src/build/__tests__/build.ts +++ b/packages/cli/src/build/__tests__/build.ts @@ -656,3 +656,128 @@ test("using @babel/plugin-transform-runtime with useESModules: true", async () = `); }); + +test("worker and browser build", async () => { + let dir = await testdir({ + "package.json": JSON.stringify({ + name: "@scope/test", + main: "dist/scope-test.cjs.js", + module: "dist/scope-test.esm.js", + browser: { + "./dist/scope-test.esm.js": "./dist/scope-test.browser.esm.js", + "./dist/scope-test.cjs.js": "./dist/scope-test.browser.cjs.js", + }, + exports: { + "./package.json": "./package.json", + ".": { + browser: { + module: "./dist/scope-test.browser.esm.js", + default: "./dist/scope-test.browser.cjs.js", + }, + worker: { + module: "./dist/scope-test.worker.esm.js", + default: "./dist/scope-test.worker.cjs.js", + }, + module: "./dist/scope-test.esm.js", + default: "./dist/scope-test.cjs.js", + }, + }, + preconstruct: { + exports: { + envConditions: ["browser", "worker"], + }, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + node_modules: { + kind: "symlink", + path: repoNodeModules, + }, + "src/index.js": js` + export const thing = typeof window; + `, + }); + await build(dir); + expect(await getDist(dir)).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.browser.cjs.dev.js, dist/scope-test.browser.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + Object.defineProperty(exports, '__esModule', { value: true }); + + const thing = "object"; + + exports.thing = thing; + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./scope-test.browser.cjs.prod.js"); + } else { + module.exports = require("./scope-test.browser.cjs.dev.js"); + } + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + const thing = "object"; + + export { thing }; + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.cjs.dev.js, dist/scope-test.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + Object.defineProperty(exports, '__esModule', { value: true }); + + const thing = typeof window; + + exports.thing = thing; + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./scope-test.cjs.prod.js"); + } else { + module.exports = require("./scope-test.cjs.dev.js"); + } + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + const thing = typeof window; + + export { thing }; + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.cjs.dev.js, dist/scope-test.worker.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + Object.defineProperty(exports, '__esModule', { value: true }); + + const thing = "undefined"; + + exports.thing = thing; + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./scope-test.worker.cjs.prod.js"); + } else { + module.exports = require("./scope-test.worker.cjs.dev.js"); + } + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.esm.dev.js, dist/scope-test.worker.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + const thing = "undefined"; + + export { thing }; + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + 'use strict'; + + if (process.env.NODE_ENV === "production") { + module.exports = require("./scope-test.worker.esm.prod.js"); + } else { + module.exports = require("./scope-test.worker.esm.dev.js"); + } + + `); +}); diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 1a590b73..af7a5b96 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -146,7 +146,9 @@ export function exportsField( entrypoint, undefined ); - for (const env of exportsFieldConfig.envConditions) { + // not iterating over envConditions, just to make the ordering explicits + for (const env of ["worker", "browser"] as const) { + if (!exportsFieldConfig.envConditions.has(env)) continue; exportConditions = { [env]: getExportConditions(entrypoint, env), ...exportConditions, From 988438aa452bafb7731c2324e679ec8d3457f4f9 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 12:57:21 +1000 Subject: [PATCH 29/56] First pass at docs --- .changeset/afraid-experts-attack.md | 5 ++ packages/cli/src/build/rollup.ts | 1 - packages/cli/src/package.ts | 9 +- site/src/pages/configuration.mdx | 127 ++++++++++++++++++++-------- 4 files changed, 103 insertions(+), 39 deletions(-) create mode 100644 .changeset/afraid-experts-attack.md diff --git a/.changeset/afraid-experts-attack.md b/.changeset/afraid-experts-attack.md new file mode 100644 index 00000000..a6ea63ea --- /dev/null +++ b/.changeset/afraid-experts-attack.md @@ -0,0 +1,5 @@ +--- +"@preconstruct/cli": patch +--- + +Added experimental `exports` flag. diff --git a/packages/cli/src/build/rollup.ts b/packages/cli/src/build/rollup.ts index 241b482d..c5159585 100644 --- a/packages/cli/src/build/rollup.ts +++ b/packages/cli/src/build/rollup.ts @@ -201,7 +201,6 @@ export let getRollupConfig = ( values: { ["typeof " + "document"]: JSON.stringify("undefined"), ["typeof " + "window"]: JSON.stringify("undefined"), - ["typeof " + "process"]: JSON.stringify("undefined"), }, preventAssignment: true, }), diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 756c99e6..574a7461 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -322,7 +322,8 @@ function parseExportsFieldConfig( (typeof config !== "boolean" && typeof config !== "object" && config !== undefined) || - config === null + config === null || + Array.isArray(config) ) { throw new FatalError( 'the "preconstruct.exports" field must be a boolean or an object', @@ -341,7 +342,11 @@ function parseExportsFieldConfig( } for (const [key, value] of Object.entries(config) as [string, unknown][]) { if (key === "extra") { - if (typeof value === "object" && value !== null) { + if ( + typeof value === "object" && + value !== null && + !Array.isArray(value) + ) { parsedConfig.extra = value as Record; } else { throw new FatalError( diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index f5b19ae0..5509986f 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -132,6 +132,97 @@ Packages map 1:1 with npm packages. Along with specifying the `entrypoints` opti } ``` +### `exports` (experimental) + +```ts +| boolean +| { + envConditions?: ("browser" | "worker")[]; + extra?: Record; + }; +``` + +The `exports` config allows you to specify opt-in to generating an `exports` field. + +Using the `exports` field enables a couple of things: + +- Importing non-root entrypoints in Node.js ESM +- Disallowing importing modules that aren't explicitly specified in the `exports` field +- More specific builds for certain environments + +> Note that Preconstruct's support for the `exports` field does not currently include emitting ESM compatible with Node.js. While ESM builds are generated, they are targeting bundlers, not Node.js or browsers directly so they use the `module` condition, not the `import` condition. + +Note that adding an `exports` field can `arguably` + +conditional exports for various environments. You can use this to create custom builds targeted for these specific environments. Currently, only "browser", "worker", and "module" conditionals are supported. + +To opt into this experimental feature, you must enable it in the root of your project by setting the `exports` experimental flag in your preconstruct config section of your `package.json` file to `true`. + +```diff +{ + "name": "@sample/repo", + "version": "0.0.0", + "preconstruct": { ++ "___experimentalFlags_WILL_CHANGE_IN_PATCH": { ++ "exports": true ++ }, + } +} +``` + +Additionally, you'll also need to enable the feature on each individual pacakge. + +```diff +{ + "name": "@sample/package", + "version": "1.0.0", + "preconstruct": { ++ "exports": true + } +} +``` + +#### `envConditions` + +`Array<"browser" | "worker">` + +Specifying the `envConditions` option adds additional environments that Preconstruct will generate bundles for. This option is currently aimed at generating bundles with `typeof SOME_ENV_SPECIFIC_GLOBAL` replaced with what it would be in that environment. It may be expanded to provide the ability to have Preconstruct resolve a different file or etc. depending on the environment in the future. + +- `browser`: Generates a bundle targeting browsers. When this condition is used, the top-level `browser` field will also be set so that older bundlers that do not understand the `exports` field will be able to use the browser build. When building with this condition, `typeof document` and `typeof window` will be replaced with `"object"` and dead-code elimination will occur based on that. +- `worker`: Generates a bundle targeting web workers/server-side JS runtimes that use web APIs. When building with this condition, `typeof document` and `typeof window` will be replaced with `"undefined"` and dead-code elimination will occur based on that. + +```json +{ + "name": "@sample/package", + "version": "1.0.0", + "preconstruct": { + "exports": { + "envConditions": ["browser", "worker"] + } + } +} +``` + +#### `extra` + +`Record` + +Preconstruct will enforce that the `exports` field that is written can be directly generated by your config, this means that extra properties are not allowed to be written directly in the `exports` field. If you want to add extra entries to the `exports` field, you can use the `extra` option in `preconstruct.exports` and then `preconstruct fix` will add add them to the actual `exports` field. + +```json +{ + "name": "@sample/package", + "version": "1.0.0", + "preconstruct": { + "exports": { + "extra": { + "./something": "./something.js" + } + } + } +} +``` + ## Entrypoints Entrypoints are the lowest level configuration point and describe a set of bundles for a particular entrypoint. They are configured by the `package.json` in the folder of the entrypoint. We also have a guide on [adding a second entrypoint](/guides/adding-a-second-entrypoint) @@ -183,39 +274,3 @@ Example: The `browser` field specifies alias files exclusive to browsers. This allows you to create different bundles from your source code based on `typeof window` and `typeof document` checks - thanks to that you can, for example, remove server-only code (just for those bundles). **Note:** Those files are not meant to be consumed by browsers "as is". They just assume browser-like environment, but they still can contain for example references to `process.env.NODE_ENV` as that is meant to be replaced by a consuming bundler. - -### `exports` (experimental) -The exports field allows you to specify custom conditional exports for various environments. You can use this to create custom builds targeted for these specific environments. Currently, only "browser", "worker", and "module" conditionals are supported. - -To opt into this experimental feature, you must enable it in the root of your project by setting the `exports` experimental flag in your preconstruct config section of your `package.json` file to "true". -```diff -{ - "name": "sample-package", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "private": true, - "preconstruct": { -+ "___experimentalFlags_WILL_CHANGE_IN_PATCH": { -+ "exports": true -+ }, - } -} -``` - -Additionally, you'll also need to enable the feature on each individual pacakge and provide the list of conditionals you'd like to support. - -```diff -{ - "name": "@sample/package", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "private": true, - "preconstruct": { -+ "exports": { -+ "conditions": ["worker", "browser", "module"] -+ }, - } -} -``` From be90e4e2a779b0046b1c208a9eff2776c29a1c6e Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 14:07:05 +1000 Subject: [PATCH 30/56] GitHub Actions Windows --- packages/cli/src/__tests__/validate.ts | 58 ++++++++++++++++++++++++++ packages/cli/test-utils/index.ts | 7 +++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/__tests__/validate.ts b/packages/cli/src/__tests__/validate.ts index 1f883086..069aa781 100644 --- a/packages/cli/src/__tests__/validate.ts +++ b/packages/cli/src/__tests__/validate.ts @@ -10,6 +10,7 @@ import { repoNodeModules, } from "../../test-utils"; import { confirms as _confirms } from "../messages"; +import { JSONValue } from "../utils"; const f = fixturez(__dirname); @@ -657,3 +658,60 @@ test("just wrong dist filenames doesn't report about the changed dist filename s ] `); }); + +describe("exports field config", () => { + const exportsFieldConfigTestDir = (config: JSONValue) => { + return testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + module: "dist/pkg-a.esm.js", + exports: { + "./package.json": "./package.json", + ".": { + module: "./dist/pkg-a.esm.js", + default: "./dist/pkg-a.cjs.js", + }, + }, + preconstruct: { + exports: config, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + }; + describe("invalid", () => { + test("null", async () => { + const tmpPath = await exportsFieldConfigTestDir(null); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: the "preconstruct.exports" field must be a boolean or an object]` + ); + }); + test("some string", async () => { + const tmpPath = await exportsFieldConfigTestDir("blah"); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: the "preconstruct.exports" field must be a boolean or an object]` + ); + }); + }); + + describe("true", () => { + const configsEquivalentToTrue = [ + {}, + { envConditions: [] }, + { envConditions: [], extra: {} }, + { extra: {} }, + {}, + true, + ]; + for (const config of configsEquivalentToTrue) { + test(`${JSON.stringify(config)}`, async () => { + const tmpPath = await exportsFieldConfigTestDir(config); + await validate(tmpPath); + }); + } + }); +}); diff --git a/packages/cli/test-utils/index.ts b/packages/cli/test-utils/index.ts index 318f97a9..367c7a7f 100644 --- a/packages/cli/test-utils/index.ts +++ b/packages/cli/test-utils/index.ts @@ -394,9 +394,12 @@ export async function getDist(dir: string) { async function readNormalizedFile(filePath: string): Promise { const stat = await fs.lstat(filePath); if (stat.isSymbolicLink()) { - const targetPath = await fs.readlink(filePath); + const [targetPath, realFilePath] = await Promise.all([ + fs.readlink(filePath).then((x) => fs.realpath(x)), + fs.realpath(filePath), + ]); return `symbolic link to ${normalizePath( - path.relative(path.dirname(filePath), targetPath) + path.relative(path.dirname(realFilePath), targetPath) )}`; } let content = await fs.readFile(filePath, "utf8"); From 72e5df204b7f055ba3fac19755808fe902378d6e Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 14:18:40 +1000 Subject: [PATCH 31/56] Test things --- packages/cli/src/__tests__/validate.ts | 36 ++++++++++++++++++++++++++ packages/cli/test-utils/index.ts | 4 ++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/__tests__/validate.ts b/packages/cli/src/__tests__/validate.ts index 069aa781..374faec4 100644 --- a/packages/cli/src/__tests__/validate.ts +++ b/packages/cli/src/__tests__/validate.ts @@ -696,6 +696,42 @@ describe("exports field config", () => { `[Error: the "preconstruct.exports" field must be a boolean or an object]` ); }); + test("extra not object", async () => { + const tmpPath = await exportsFieldConfigTestDir({ extra: "blah" }); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: the "preconstruct.exports.extra" field must be an object if it is present]` + ); + }); + test("envConditions not array", async () => { + const tmpPath = await exportsFieldConfigTestDir({ envConditions: {} }); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: the "preconstruct.exports.envConditions" field must be an array containing zero or more of "worker" and "browser" if it is present]` + ); + }); + test("envConditions duplicates", async () => { + const tmpPath = await exportsFieldConfigTestDir({ + envConditions: ["worker", "worker"], + }); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: the "preconstruct.exports.envConditions" field must not have duplicates]` + ); + }); + test("envConditions invalid condition", async () => { + const tmpPath = await exportsFieldConfigTestDir({ + envConditions: ["worker", "asfdasfd"], + }); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: the "preconstruct.exports.envConditions" field must be an array containing zero or more of "worker" and "browser" if it is present]` + ); + }); + test("unknown key", async () => { + const tmpPath = await exportsFieldConfigTestDir({ + something: true, + }); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: the "preconstruct.exports" field contains an unknown key "something"]` + ); + }); }); describe("true", () => { diff --git a/packages/cli/test-utils/index.ts b/packages/cli/test-utils/index.ts index 367c7a7f..f1b29cf8 100644 --- a/packages/cli/test-utils/index.ts +++ b/packages/cli/test-utils/index.ts @@ -396,7 +396,9 @@ async function readNormalizedFile(filePath: string): Promise { if (stat.isSymbolicLink()) { const [targetPath, realFilePath] = await Promise.all([ fs.readlink(filePath).then((x) => fs.realpath(x)), - fs.realpath(filePath), + fs + .realpath(path.dirname(filePath)) + .then((x) => path.join(x, path.basename(filePath))), ]); return `symbolic link to ${normalizePath( path.relative(path.dirname(realFilePath), targetPath) From 014821bb964eec7b1049d9e4fb43b68d3e8be925 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 14:29:01 +1000 Subject: [PATCH 32/56] tests --- packages/cli/src/__tests__/validate.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/cli/src/__tests__/validate.ts b/packages/cli/src/__tests__/validate.ts index 374faec4..79712026 100644 --- a/packages/cli/src/__tests__/validate.ts +++ b/packages/cli/src/__tests__/validate.ts @@ -750,4 +750,26 @@ describe("exports field config", () => { }); } }); + describe("false", () => { + const configsEquivalentToFalse = [false, undefined]; + for (const config of configsEquivalentToFalse) { + test(`${JSON.stringify(config)}`, async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + module: "dist/pkg-a.esm.js", + preconstruct: { + exports: config, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + await validate(tmpPath); + }); + } + }); }); From 8f95c67588174131068a177ceb321f1075ecb2c8 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 14:29:23 +1000 Subject: [PATCH 33/56] Revert some changes --- packages/cli/src/build/config.ts | 88 ++++---------------------------- packages/cli/src/build/rollup.ts | 35 +++++++------ 2 files changed, 30 insertions(+), 93 deletions(-) diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index 77cf1762..84192663 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -92,8 +92,7 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { pkg, pkg.entrypoints, aliases, - "node", - "dev", + "node-dev", pkg.project.experimentalFlags.logCompiledFiles ? (filename) => { logger.info( @@ -131,8 +130,7 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { pkg, pkg.entrypoints, aliases, - "node", - "prod", + "node-prod", () => {} ), outputs: [ @@ -154,14 +152,7 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { if (pkg.entrypoints[0].json["umd:main"] !== undefined) pkg.entrypoints.forEach((entrypoint) => { configs.push({ - config: getRollupConfig( - pkg, - [entrypoint], - aliases, - "browser", - "umd", - () => {} - ), + config: getRollupConfig(pkg, [entrypoint], aliases, "umd", () => {}), outputs: [ { format: "umd" as const, @@ -190,14 +181,13 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { pkg.entrypoints, aliases, "browser", - "dev", () => {} ), outputs: [ { format: "cjs" as const, - entryFileNames: "[name].browser.cjs.dev.js", - chunkFileNames: "dist/[name]-[hash].browser.cjs.dev.js", + entryFileNames: "[name].browser.cjs.js", + chunkFileNames: "dist/[name]-[hash].browser.cjs.js", dir: pkg.directory, exports: "named" as const, interop, @@ -215,30 +205,10 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { : []), ], }); - configs.push({ - config: getRollupConfig( - pkg, - pkg.entrypoints, - aliases, - "browser", - "prod", - () => {} - ), - outputs: [ - { - format: "cjs" as const, - entryFileNames: "[name].browser.cjs.prod.js", - chunkFileNames: "dist/[name]-[hash].browser.cjs.prod.js", - dir: pkg.directory, - exports: "named" as const, - interop, - plugins: cjsPlugins, - }, - ], - }); } const exportsFieldConfig = pkg.exportsFieldConfig(); + // note module builds always exist when using the exports field if (exportsFieldConfig?.envConditions.has("worker")) { configs.push({ config: getRollupConfig( @@ -246,60 +216,24 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { pkg.entrypoints, aliases, "worker", - "dev", () => {} ), outputs: [ { format: "cjs" as const, - entryFileNames: "[name].worker.cjs.dev.js", - chunkFileNames: "dist/[name]-[hash].worker.cjs.dev.js", + entryFileNames: "[name].worker.cjs.js", + chunkFileNames: "dist/[name]-[hash].worker.cjs.js", dir: pkg.directory, exports: "named" as const, interop, plugins: cjsPlugins, }, - ...(hasModuleField - ? [ - { - format: "es" as const, - entryFileNames: "[name].worker.esm.dev.js", - chunkFileNames: "dist/[name]-[hash].worker.esm.dev.js", - dir: pkg.directory, - }, - ] - : []), - ], - }); - configs.push({ - config: getRollupConfig( - pkg, - pkg.entrypoints, - aliases, - "worker", - "prod", - () => {} - ), - outputs: [ { - format: "cjs" as const, - entryFileNames: "[name].worker.cjs.prod.js", - chunkFileNames: "dist/[name]-[hash].worker.cjs.prod.js", + format: "es" as const, + entryFileNames: "[name].worker.esm.dev.js", + chunkFileNames: "dist/[name]-[hash].worker.esm.js", dir: pkg.directory, - exports: "named" as const, - interop, - plugins: cjsPlugins, }, - ...(hasModuleField - ? [ - { - format: "es" as const, - entryFileNames: "[name].worker.esm.prod.js", - chunkFileNames: "dist/[name]-[hash].worker.esm.prod.js", - dir: pkg.directory, - }, - ] - : []), ], }); } diff --git a/packages/cli/src/build/rollup.ts b/packages/cli/src/build/rollup.ts index c5159585..6aa4fc2c 100644 --- a/packages/cli/src/build/rollup.ts +++ b/packages/cli/src/build/rollup.ts @@ -31,27 +31,29 @@ const makeExternalPredicate = (externalArr: string[]) => { return (id: string) => pattern.test(id); }; -export type RollupConfigEnvironment = "dev" | "prod" | "umd"; - -export type RollupConfigType = "browser" | "node" | "worker"; +export type RollupConfigType = + | "umd" + | "browser" + | "worker" + | "node-dev" + | "node-prod"; export let getRollupConfig = ( pkg: Package, entrypoints: Array, aliases: Aliases, type: RollupConfigType, - env: RollupConfigEnvironment, reportTransformedFile: (filename: string) => void ): RollupOptions => { let external = []; if (pkg.json.peerDependencies) { external.push(...Object.keys(pkg.json.peerDependencies)); } - if (pkg.json.dependencies && env !== "umd") { + if (pkg.json.dependencies && type !== "umd") { external.push(...Object.keys(pkg.json.dependencies)); } - if (type === "node") { + if (type === "node-dev" || type === "node-prod") { external.push(...builtInModules); } @@ -109,7 +111,7 @@ export let getRollupConfig = ( } } case "THIS_IS_UNDEFINED": { - if (env === "umd") { + if (type === "umd") { return; } warnings.push( @@ -143,8 +145,8 @@ export let getRollupConfig = ( } }, } as Plugin, - env === "prod" && flowAndNodeDevProdEntry(pkg, warnings), - env === "prod" && typescriptDeclarations(pkg), + type === "node-prod" && flowAndNodeDevProdEntry(pkg, warnings), + type === "node-prod" && typescriptDeclarations(pkg), babel({ cwd: pkg.project.directory, reportTransformedFile, @@ -161,7 +163,7 @@ export let getRollupConfig = ( } })(), }), - env === "umd" && + type === "umd" && cjs({ include: ["**/node_modules/**", "node_modules/**"], }), @@ -170,24 +172,25 @@ export let getRollupConfig = ( json({ namedExports: false, }), - env === "umd" && + type === "umd" && alias({ entries: aliases, }), resolve({ extensions: EXTENSIONS, - browser: type === "browser" || type === "worker", + // only umd builds will actually load dependencies which is where this browser flag actually makes a difference + browser: type === "umd", customResolveOptions: { - moduleDirectory: env === "umd" ? "node_modules" : [], + moduleDirectory: type === "umd" ? "node_modules" : [], }, }), - env === "umd" && inlineProcessEnvNodeEnv({ sourceMap: true }), - env === "umd" && + type === "umd" && inlineProcessEnvNodeEnv({ sourceMap: true }), + type === "umd" && terser({ sourceMap: true, compress: true, }), - env === "prod" && inlineProcessEnvNodeEnv({ sourceMap: false }), + type === "node-prod" && inlineProcessEnvNodeEnv({ sourceMap: false }), type === "browser" && replace({ values: { From 8391a301cc2943c33e3551d9a41ee57ebbc49938 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 14:30:56 +1000 Subject: [PATCH 34/56] Tests --- .../__tests__/__snapshots__/other.ts.snap | 32 ------------------ packages/cli/src/build/__tests__/build.ts | 33 ++----------------- packages/cli/src/build/__tests__/other.ts | 11 +------ packages/cli/src/build/config.ts | 2 +- 4 files changed, 5 insertions(+), 73 deletions(-) diff --git a/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap b/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap index 01c4cb12..70d2bd71 100644 --- a/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap +++ b/packages/cli/src/build/__tests__/__snapshots__/other.ts.snap @@ -1,40 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`browser no module: dist/browser-no-module.browser.cjs.dev.js 1`] = ` -"'use strict'; - -Object.defineProperty(exports, '__esModule', { value: true }); - -let thing = \\"wow\\"; - -{ - thing = \\"something\\"; -} - -{ - thing += \\"other\\"; -} - -var thing$1 = thing; - -exports.default = thing$1; -" -`; - exports[`browser no module: dist/browser-no-module.browser.cjs.js 1`] = ` "'use strict'; -if (process.env.NODE_ENV === \\"production\\") { - module.exports = require(\\"./browser-no-module.browser.cjs.prod.js\\"); -} else { - module.exports = require(\\"./browser-no-module.browser.cjs.dev.js\\"); -} -" -`; - -exports[`browser no module: dist/browser-no-module.browser.cjs.prod.js 1`] = ` -"'use strict'; - Object.defineProperty(exports, '__esModule', { value: true }); let thing = \\"wow\\"; diff --git a/packages/cli/src/build/__tests__/build.ts b/packages/cli/src/build/__tests__/build.ts index fbad55fb..5f047e6c 100644 --- a/packages/cli/src/build/__tests__/build.ts +++ b/packages/cli/src/build/__tests__/build.ts @@ -701,7 +701,7 @@ test("worker and browser build", async () => { }); await build(dir); expect(await getDist(dir)).toMatchInlineSnapshot(` - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.browser.cjs.dev.js, dist/scope-test.browser.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); @@ -710,15 +710,6 @@ test("worker and browser build", async () => { exports.thing = thing; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./scope-test.browser.cjs.prod.js"); - } else { - module.exports = require("./scope-test.browser.cjs.dev.js"); - } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ const thing = "object"; @@ -747,7 +738,7 @@ test("worker and browser build", async () => { export { thing }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.cjs.dev.js, dist/scope-test.worker.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); @@ -756,28 +747,10 @@ test("worker and browser build", async () => { exports.thing = thing; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./scope-test.worker.cjs.prod.js"); - } else { - module.exports = require("./scope-test.worker.cjs.dev.js"); - } - - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.esm.dev.js, dist/scope-test.worker.esm.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ const thing = "undefined"; export { thing }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./scope-test.worker.esm.prod.js"); - } else { - module.exports = require("./scope-test.worker.esm.dev.js"); - } - `); }); diff --git a/packages/cli/src/build/__tests__/other.ts b/packages/cli/src/build/__tests__/other.ts index 2c2be57f..dd750369 100644 --- a/packages/cli/src/build/__tests__/other.ts +++ b/packages/cli/src/build/__tests__/other.ts @@ -48,7 +48,7 @@ test("browser", async () => { await build(dir); expect(await getDist(dir)).toMatchInlineSnapshot(` - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.cjs.dev.js, dist/browser.browser.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); @@ -67,15 +67,6 @@ test("browser", async () => { exports.default = thing$1; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - if (process.env.NODE_ENV === "production") { - module.exports = require("./browser.browser.cjs.prod.js"); - } else { - module.exports = require("./browser.browser.cjs.dev.js"); - } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/browser.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ let thing = "wow"; diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index 84192663..360060e2 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -230,7 +230,7 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }, { format: "es" as const, - entryFileNames: "[name].worker.esm.dev.js", + entryFileNames: "[name].worker.esm.js", chunkFileNames: "dist/[name]-[hash].worker.esm.js", dir: pkg.directory, }, From c824e52cb056f50735017d6c8c15985739195413 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 14:33:22 +1000 Subject: [PATCH 35/56] Revert another thing --- packages/cli/src/build/rollup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/build/rollup.ts b/packages/cli/src/build/rollup.ts index 6aa4fc2c..9b76481c 100644 --- a/packages/cli/src/build/rollup.ts +++ b/packages/cli/src/build/rollup.ts @@ -191,7 +191,7 @@ export let getRollupConfig = ( compress: true, }), type === "node-prod" && inlineProcessEnvNodeEnv({ sourceMap: false }), - type === "browser" && + (type === "browser" || type === "umd") && replace({ values: { ["typeof " + "document"]: JSON.stringify("object"), From 3052d2961257f3f4fc5484f3cbc4940b3c5afcce Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 14:49:21 +1000 Subject: [PATCH 36/56] Remove CommonJS worker build --- packages/cli/src/__tests__/dev.ts | 1 - packages/cli/src/__tests__/fix.ts | 42 +++++++++----------- packages/cli/src/build/__tests__/build.ts | 48 +++++++++-------------- packages/cli/src/build/config.ts | 9 ----- packages/cli/src/dev.ts | 4 +- packages/cli/src/package.ts | 2 +- packages/cli/src/utils.ts | 18 ++++++--- 7 files changed, 51 insertions(+), 73 deletions(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index 7c33a6fe..aa04dc8f 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -317,7 +317,6 @@ test("exports field with worker condition", async () => { ".": { worker: { module: "./dist/something-blah.worker.esm.js", - default: "./dist/something-blah.worker.cjs.js", }, module: "./dist/something-blah.esm.js", default: "./dist/something-blah.cjs.js", diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index 82f4931f..053f0e06 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -104,14 +104,13 @@ test("set exports field when opt-in", async () => { "exports": { "./package.json": "./package.json", ".": { + "worker": { + "module": "./dist/package-exports.worker.esm.js" + }, "browser": { "module": "./dist/package-exports.browser.esm.js", "default": "./dist/package-exports.browser.cjs.js" }, - "worker": { - "module": "./dist/package-exports.worker.esm.js", - "default": "./dist/package-exports.worker.cjs.js" - }, "module": "./dist/package-exports.esm.js", "default": "./dist/package-exports.cjs.js" } @@ -165,14 +164,13 @@ test("set exports field when opt-in", async () => { "exports": { "./package.json": "./package.json", ".": { + "worker": { + "module": "./dist/package-exports.worker.esm.js" + }, "browser": { "module": "./dist/package-exports.browser.esm.js", "default": "./dist/package-exports.browser.cjs.js" }, - "worker": { - "module": "./dist/package-exports.worker.esm.js", - "default": "./dist/package-exports.worker.cjs.js" - }, "module": "./dist/package-exports.esm.js", "default": "./dist/package-exports.cjs.js" } @@ -293,38 +291,35 @@ test("set exports field with multiple entrypoints", async () => { "exports": { "./package.json": "./package.json", ".": { + "worker": { + "module": "./dist/blah-something.worker.esm.js" + }, "browser": { "module": "./dist/blah-something.browser.esm.js", "default": "./dist/blah-something.browser.cjs.js" }, - "worker": { - "module": "./dist/blah-something.worker.esm.js", - "default": "./dist/blah-something.worker.cjs.js" - }, "module": "./dist/blah-something.esm.js", "default": "./dist/blah-something.cjs.js" }, "./other": { + "worker": { + "module": "./other/dist/blah-something-other.worker.esm.js" + }, "browser": { "module": "./other/dist/blah-something-other.browser.esm.js", "default": "./other/dist/blah-something-other.browser.cjs.js" }, - "worker": { - "module": "./other/dist/blah-something-other.worker.esm.js", - "default": "./other/dist/blah-something-other.worker.cjs.js" - }, "module": "./other/dist/blah-something-other.esm.js", "default": "./other/dist/blah-something-other.cjs.js" }, "./deep/something": { + "worker": { + "module": "./deep/something/dist/blah-something-deep-something.worker.esm.js" + }, "browser": { "module": "./deep/something/dist/blah-something-deep-something.browser.esm.js", "default": "./deep/something/dist/blah-something-deep-something.browser.cjs.js" }, - "worker": { - "module": "./deep/something/dist/blah-something-deep-something.worker.esm.js", - "default": "./deep/something/dist/blah-something-deep-something.worker.cjs.js" - }, "module": "./deep/something/dist/blah-something-deep-something.esm.js", "default": "./deep/something/dist/blah-something-deep-something.cjs.js" } @@ -406,14 +401,13 @@ test("set exports field without root entrypoint", async () => { "exports": { "./package.json": "./package.json", "./other": { + "worker": { + "module": "./other/dist/blah-something-other.worker.esm.js" + }, "browser": { "module": "./other/dist/blah-something-other.browser.esm.js", "default": "./other/dist/blah-something-other.browser.cjs.js" }, - "worker": { - "module": "./other/dist/blah-something-other.worker.esm.js", - "default": "./other/dist/blah-something-other.worker.cjs.js" - }, "module": "./other/dist/blah-something-other.esm.js", "default": "./other/dist/blah-something-other.cjs.js" } diff --git a/packages/cli/src/build/__tests__/build.ts b/packages/cli/src/build/__tests__/build.ts index 5f047e6c..ac63bbf0 100644 --- a/packages/cli/src/build/__tests__/build.ts +++ b/packages/cli/src/build/__tests__/build.ts @@ -660,26 +660,23 @@ test("using @babel/plugin-transform-runtime with useESModules: true", async () = test("worker and browser build", async () => { let dir = await testdir({ "package.json": JSON.stringify({ - name: "@scope/test", - main: "dist/scope-test.cjs.js", - module: "dist/scope-test.esm.js", + name: "@exports/test", + main: "dist/exports-test.cjs.js", + module: "dist/exports-test.esm.js", browser: { - "./dist/scope-test.esm.js": "./dist/scope-test.browser.esm.js", - "./dist/scope-test.cjs.js": "./dist/scope-test.browser.cjs.js", + "./dist/exports-test.esm.js": "./dist/exports-test.browser.esm.js", + "./dist/exports-test.cjs.js": "./dist/exports-test.browser.cjs.js", }, exports: { "./package.json": "./package.json", ".": { + worker: { module: "./dist/exports-test.worker.esm.js" }, browser: { - module: "./dist/scope-test.browser.esm.js", - default: "./dist/scope-test.browser.cjs.js", + module: "./dist/exports-test.browser.esm.js", + default: "./dist/exports-test.browser.cjs.js", }, - worker: { - module: "./dist/scope-test.worker.esm.js", - default: "./dist/scope-test.worker.cjs.js", - }, - module: "./dist/scope-test.esm.js", - default: "./dist/scope-test.cjs.js", + module: "./dist/exports-test.esm.js", + default: "./dist/exports-test.cjs.js", }, }, preconstruct: { @@ -701,7 +698,7 @@ test("worker and browser build", async () => { }); await build(dir); expect(await getDist(dir)).toMatchInlineSnapshot(` - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/exports-test.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); @@ -710,12 +707,12 @@ test("worker and browser build", async () => { exports.thing = thing; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/exports-test.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ const thing = "object"; export { thing }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.cjs.dev.js, dist/scope-test.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/exports-test.cjs.dev.js, dist/exports-test.cjs.prod.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); @@ -724,30 +721,21 @@ test("worker and browser build", async () => { exports.thing = thing; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/exports-test.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 'use strict'; if (process.env.NODE_ENV === "production") { - module.exports = require("./scope-test.cjs.prod.js"); + module.exports = require("./exports-test.cjs.prod.js"); } else { - module.exports = require("./scope-test.cjs.dev.js"); + module.exports = require("./exports-test.cjs.dev.js"); } - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/exports-test.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ const thing = typeof window; export { thing }; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - Object.defineProperty(exports, '__esModule', { value: true }); - - const thing = "undefined"; - - exports.thing = thing; - - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/scope-test.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/exports-test.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ const thing = "undefined"; export { thing }; diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index 360060e2..c2a10768 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -219,15 +219,6 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { () => {} ), outputs: [ - { - format: "cjs" as const, - entryFileNames: "[name].worker.cjs.js", - chunkFileNames: "dist/[name]-[hash].worker.cjs.js", - dir: pkg.directory, - exports: "named" as const, - interop, - plugins: cjsPlugins, - }, { format: "es" as const, entryFileNames: "[name].worker.esm.js", diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index ecf66b91..7fe0334e 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -4,7 +4,7 @@ import { tsTemplate, flowTemplate, validFieldsForEntrypoint, - getExportConditions, + getModuleTypeExportConditions, } from "./utils"; import * as babel from "@babel/core"; import * as fs from "fs-extra"; @@ -221,7 +221,7 @@ unregister(); if (pkg.exportsFieldConfig()?.envConditions?.has("worker")) { for (const output of Object.values( - getExportConditions(entrypoint, "worker") + getModuleTypeExportConditions(entrypoint, "worker") )) { promises.push( fs.symlink( diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 574a7461..3637d94e 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -104,7 +104,7 @@ function createEntrypoints( } export type ExportsConditions = { - worker?: { module: string; default: string }; + worker?: { module: string }; browser?: { module: string; default: string }; module: string; default: string; diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index af7a5b96..f8cf38c1 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -142,15 +142,21 @@ export function exportsField( let output: Record = {}; pkg.entrypoints.forEach((entrypoint) => { - let exportConditions: ExportsConditions = getExportConditions( + let exportConditions: ExportsConditions = getModuleTypeExportConditions( entrypoint, undefined ); - // not iterating over envConditions, just to make the ordering explicits - for (const env of ["worker", "browser"] as const) { - if (!exportsFieldConfig.envConditions.has(env)) continue; + if (exportsFieldConfig.envConditions.has("browser")) { exportConditions = { - [env]: getExportConditions(entrypoint, env), + browser: getModuleTypeExportConditions(entrypoint, "browser"), + ...exportConditions, + }; + } + if (exportsFieldConfig.envConditions.has("worker")) { + exportConditions = { + worker: { + module: getModuleTypeExportConditions(entrypoint, "worker").module, + }, ...exportConditions, }; } @@ -166,7 +172,7 @@ export function exportsField( }; } -export function getExportConditions( +export function getModuleTypeExportConditions( entrypoint: Entrypoint, env: "worker" | "browser" | undefined ) { From ca512ce12453f236a3d1c4abbea5a03c9a8e7410 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 16:27:56 +1000 Subject: [PATCH 37/56] Tests --- packages/cli/src/__tests__/fix.ts | 200 ++++++++++++++++++++++++- packages/cli/src/__tests__/validate.ts | 85 +++++++++++ packages/cli/src/messages.ts | 7 +- packages/cli/src/validate-package.ts | 7 +- 4 files changed, 289 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index 053f0e06..1b7dc687 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -757,7 +757,203 @@ test("unexpected former experimental flag is removed", async () => { export let x = true; `, }); + await fix(tmpPath); + expect(await getFiles(tmpPath, ["package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "pkg-a", + "main": "dist/pkg-a.cjs.js" + } - await expect(fix(tmpPath)); - expect(getPkg(tmpPath)).toMatchInlineSnapshot(`Promise {}`); + `); +}); + +test("no module field with exports field", async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + preconstruct: { + exports: true, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + await fix(tmpPath); + expect(await getFiles(tmpPath, ["package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "pkg-a", + "main": "dist/pkg-a.cjs.js", + "module": "dist/pkg-a.esm.js", + "exports": { + "./package.json": "./package.json", + ".": { + "module": "./dist/pkg-a.esm.js", + "default": "./dist/pkg-a.cjs.js" + } + }, + "preconstruct": { + "exports": true, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } + } + } + + `); +}); + +test("has browser field but no browser condition", async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + module: "dist/pkg-a.esm.js", + browser: { + "./dist/pkg-a.cjs.js": "./dist/pkg-a.browser.cjs.js", + "./dist/pkg-a.esm.js": "./dist/pkg-a.browser.esm.js", + }, + preconstruct: { + exports: true, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + await fix(tmpPath); + expect(await getFiles(tmpPath, ["package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "pkg-a", + "main": "dist/pkg-a.cjs.js", + "module": "dist/pkg-a.esm.js", + "browser": { + "./dist/pkg-a.cjs.js": "./dist/pkg-a.browser.cjs.js", + "./dist/pkg-a.esm.js": "./dist/pkg-a.browser.esm.js" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "browser": { + "module": "./dist/pkg-a.browser.esm.js", + "default": "./dist/pkg-a.browser.cjs.js" + }, + "module": "./dist/pkg-a.esm.js", + "default": "./dist/pkg-a.cjs.js" + } + }, + "preconstruct": { + "exports": { + "envConditions": [ + "browser" + ] + }, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } + } + } + + `); +}); + +test("has browser condition but no browser field", async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + module: "dist/pkg-a.esm.js", + preconstruct: { + exports: { + envConditions: ["browser"], + }, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + + await fix(tmpPath); + expect(await getFiles(tmpPath, ["package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "pkg-a", + "main": "dist/pkg-a.cjs.js", + "module": "dist/pkg-a.esm.js", + "browser": { + "./dist/pkg-a.cjs.js": "./dist/pkg-a.browser.cjs.js", + "./dist/pkg-a.esm.js": "./dist/pkg-a.browser.esm.js" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "browser": { + "module": "./dist/pkg-a.browser.esm.js", + "default": "./dist/pkg-a.browser.cjs.js" + }, + "module": "./dist/pkg-a.esm.js", + "default": "./dist/pkg-a.cjs.js" + } + }, + "preconstruct": { + "exports": { + "envConditions": [ + "browser" + ] + }, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } + } + } + + `); +}); + +test("preconstruct.exports: true no exports field", async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + module: "dist/pkg-a.esm.js", + preconstruct: { + exports: true, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + await fix(tmpPath); + expect(await getFiles(tmpPath, ["package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "pkg-a", + "main": "dist/pkg-a.cjs.js", + "module": "dist/pkg-a.esm.js", + "exports": { + "./package.json": "./package.json", + ".": { + "module": "./dist/pkg-a.esm.js", + "default": "./dist/pkg-a.cjs.js" + } + }, + "preconstruct": { + "exports": true, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } + } + } + + `); }); diff --git a/packages/cli/src/__tests__/validate.ts b/packages/cli/src/__tests__/validate.ts index 79712026..7c67f964 100644 --- a/packages/cli/src/__tests__/validate.ts +++ b/packages/cli/src/__tests__/validate.ts @@ -773,3 +773,88 @@ describe("exports field config", () => { } }); }); + +test("no module field with exports field", async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + preconstruct: { + exports: true, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: when using the exports field, the module field must also be specified]` + ); +}); + +test("has browser field but no browser condition", async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + module: "dist/pkg-a.esm.js", + browser: { + "./dist/pkg-a.cjs.js": "./dist/pkg-a.browser.cjs.js", + "./dist/pkg-a.esm.js": "./dist/pkg-a.browser.esm.js", + }, + preconstruct: { + exports: true, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: the exports field is configured and the browser field exists in this package but it is not specified in the preconstruct.exports.envConditions field]` + ); +}); + +test("has browser condition but no browser field", async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + module: "dist/pkg-a.esm.js", + preconstruct: { + exports: { + envConditions: ["browser"], + }, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: the exports field is configured and the browser condition is set in preconstruct.exports.envConditions but the field is not present at the top-level]` + ); +}); + +test("preconstruct.exports: true no exports field", async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + module: "dist/pkg-a.esm.js", + preconstruct: { + exports: true, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }), + "src/index.js": "", + }); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( + `[Error: exports field was not found, expected \`{"./package.json":"./package.json",".":{"module":"./dist/pkg-a.esm.js","default":"./dist/pkg-a.cjs.js"}}\`]` + ); +}); diff --git a/packages/cli/src/messages.ts b/packages/cli/src/messages.ts index dcc6c04d..0d2e2ce6 100644 --- a/packages/cli/src/messages.ts +++ b/packages/cli/src/messages.ts @@ -20,8 +20,8 @@ export let errors = { "packages must have at least one entrypoint, this package has no entrypoints", fieldMustExistInAllEntrypointsIfExistsDeclinedFixDuringInit: (field: Field) => `all entrypoints in a package must have the same fields and one entrypoint in this package has a ${field} field but you've declined the fix`, - missingBrowserConditionWithFieldPresent: `the exports field is configured and the browser field exists in this package but it is not specified in the preconstruct.exports.conditions field`, - missingBrowserFieldWithConditionPresent: `the exports field is configured and the browser condition exists is set in preconstruct.exports.conditions the field is not present at the top-level`, + missingBrowserConditionWithFieldPresent: `the exports field is configured and the browser field exists in this package but it is not specified in the preconstruct.exports.envConditions field`, + missingBrowserFieldWithConditionPresent: `the exports field is configured and the browser condition is set in preconstruct.exports.envConditions but the field is not present at the top-level`, noModuleFieldWithExportsField: `when using the exports field, the module field must also be specified`, }; @@ -41,9 +41,6 @@ export let confirms = { fixBrowserField: createPromptConfirmLoader( "would you like to fix the browser build?" ), - fixExportsField: createPromptConfirmLoader( - "would you like to fix the exports field?" - ), createEntrypointPkgJson: createPromptConfirmLoader( "A package.json file does not exist for this entrypoint, would you like to create one automatically?" ), diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index c252b5c6..3458dda4 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -12,17 +12,18 @@ export async function fixPackage(pkg: Package) { if (pkg.entrypoints.length === 0) { throw new FatalError(errors.noEntrypoints, pkg.name); } + + const exportsFieldConfig = pkg.exportsFieldConfig(); + let fields = { main: true, module: pkg.entrypoints.some((x) => x.json.module !== undefined) || - !!(pkg.project.experimentalFlags.exports && pkg.exportsFieldConfig()), + !!exportsFieldConfig, "umd:main": pkg.entrypoints.some((x) => x.json["umd:main"] !== undefined), browser: pkg.entrypoints.some((x) => x.json.browser !== undefined), }; - const exportsFieldConfig = pkg.exportsFieldConfig(); - if (exportsFieldConfig) { if (fields.browser || exportsFieldConfig.envConditions.has("browser")) { if (typeof pkg.json.preconstruct.exports !== "object") { From 1c461072885bfb7d42312652e7ef36638f50fb92 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 16:52:30 +1000 Subject: [PATCH 38/56] Test TypeScript with moduleResolution: nodenext --- package.json | 2 +- packages/cli/src/build/__tests__/build.ts | 96 +++++++++++++++++++++++ yarn.lock | 8 +- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3cbf0df4..f68d6d71 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "normalize-path": "^3.0.0", "outdent": "^0.7.1", "prettier": "^2.1.2", - "typescript": "^4.5.2" + "typescript": "^4.7.4" }, "jest": { "reporters": [ diff --git a/packages/cli/src/build/__tests__/build.ts b/packages/cli/src/build/__tests__/build.ts index ac63bbf0..b960991e 100644 --- a/packages/cli/src/build/__tests__/build.ts +++ b/packages/cli/src/build/__tests__/build.ts @@ -1,6 +1,7 @@ import build from "../"; import fixturez from "fixturez"; import path from "path"; +import fs from "fs-extra"; import { initBasic, getPkg, @@ -12,9 +13,11 @@ import { repoNodeModules, basicPkgJson, getFiles, + ts, } from "../../../test-utils"; import { doPromptInput as _doPromptInput } from "../../prompt"; import { confirms as _confirms } from "../../messages"; +import spawn from "spawndamnit"; const f = fixturez(__dirname); @@ -742,3 +745,96 @@ test("worker and browser build", async () => { `); }); + +test("typescript with nodenext module resolution", async () => { + let dir = await testdir({ + "package.json": JSON.stringify({ + name: "@exports/repo", + preconstruct: { + packages: ["packages/pkg-a"], + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { exports: true }, + }, + }), + "packages/pkg-a/package.json": JSON.stringify({ + name: "pkg-a", + main: "dist/pkg-a.cjs.js", + module: "dist/pkg-a.esm.js", + exports: { + "./package.json": "./package.json", + ".": { + module: "./dist/pkg-a.esm.js", + default: "./dist/pkg-a.cjs.js", + }, + "./something": { + module: "./something/dist/pkg-a-something.esm.js", + default: "./something/dist/pkg-a-something.cjs.js", + }, + }, + preconstruct: { + entrypoints: ["index.ts", "something.ts"], + exports: true, + }, + }), + "packages/pkg-a/something/package.json": JSON.stringify({ + main: "dist/pkg-a-something.cjs.js", + module: "dist/pkg-a-something.esm.js", + }), + "packages/pkg-a/src/index.ts": ts` + export const thing = "index"; + `, + "packages/pkg-a/src/something.ts": ts` + export const something = "something"; + `, + "packages/pkg-a/not-exported.ts": ts` + export const notExported = true; + `, + + "packages/pkg-a/node_modules": { + kind: "symlink", + path: repoNodeModules, + }, + "blah.ts": ts` + import { thing } from "pkg-a"; + import { something } from "pkg-a/something"; + import { notExported } from "pkg-a/not-exported"; // should error + + function acceptThing(x: T) {} + + acceptThing<"index">(thing); + acceptThing<"something">(something); + + // this is to check that TypeScript is actually checking things + acceptThing<"other">(thing); // should error + acceptThing<"other">(something); // should error + `, + "tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "NodeNext", + moduleResolution: "nodenext", + strict: true, + declaration: true, + }, + }), + }); + await fs.ensureSymlink( + path.join(dir, "packages/pkg-a"), + path.join(dir, "node_modules/pkg-a") + ); + await build(dir); + let { code, stdout, stderr } = await spawn( + path.join( + path.dirname(require.resolve("typescript/package.json")), + "bin/tsc" + ), + [], + { cwd: dir } + ); + expect(code).toBe(2); + expect(stdout.toString("utf8")).toMatchInlineSnapshot(` + "blah.ts(3,29): error TS2307: Cannot find module 'pkg-a/not-exported' or its corresponding type declarations. + blah.ts(11,22): error TS2345: Argument of type '\\"index\\"' is not assignable to parameter of type '\\"other\\"'. + blah.ts(12,22): error TS2345: Argument of type '\\"something\\"' is not assignable to parameter of type '\\"other\\"'. + " + `); + expect(stderr.toString("utf8")).toMatchInlineSnapshot(`""`); +}); diff --git a/yarn.lock b/yarn.lock index 40519c2f..f9d9086e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15213,10 +15213,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" - integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== +typescript@^4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== typescript@~3.9.7: version "3.9.7" From 92bb8bd37d3280fbb06e20b619f114f8e9b1383e Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 16:55:56 +1000 Subject: [PATCH 39/56] Windows test debugging --- packages/cli/test-utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/test-utils/index.ts b/packages/cli/test-utils/index.ts index f1b29cf8..392fd45c 100644 --- a/packages/cli/test-utils/index.ts +++ b/packages/cli/test-utils/index.ts @@ -402,7 +402,7 @@ async function readNormalizedFile(filePath: string): Promise { ]); return `symbolic link to ${normalizePath( path.relative(path.dirname(realFilePath), targetPath) - )}`; + )}. ${JSON.stringify({ targetPath, realFilePath }, null, 2)}`; } let content = await fs.readFile(filePath, "utf8"); // to normalise windows line endings From 42749213c3ab06032db0388eaa9165bbab58104e Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Tue, 5 Jul 2022 17:16:31 +1000 Subject: [PATCH 40/56] Sort entrypoints --- packages/cli/src/package.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 3637d94e..1a2bbed5 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -158,6 +158,20 @@ export class Package extends Item<{ onlyFiles: true, absolute: true, }); + // sorting the entrypoints is important since we want to have something consistent + // to write into the `exports` field and file systems don't guarantee an order + entrypoints = [ + ...entrypoints.sort((a, b) => { + // shortest entrypoints first since shorter entrypoints + // are generally more commonly used + const comparison = a.length - b.length; + if (comparison !== 0) return comparison; + // then .sort's default behaviour because we just need something stable + if (a < b) return -1; + if (b > a) return 1; + return 0; + }), + ]; if (!entrypoints.length) { let oldEntrypoints = await fastGlob(pkg.configEntrypoints, { cwd: pkg.directory, From 04c096274cdefd036fd18f93756e0644ed0b8a6b Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 10:02:49 +1000 Subject: [PATCH 41/56] Only ESM browser builds when using the exports field --- packages/cli/src/__tests__/dev.ts | 9 ++- packages/cli/src/__tests__/fix.ts | 88 ++++++++--------------- packages/cli/src/__tests__/validate.ts | 7 +- packages/cli/src/build/__tests__/build.ts | 19 ++--- packages/cli/src/build/config.ts | 25 ++++--- packages/cli/src/dev.ts | 19 +++-- packages/cli/src/package.ts | 4 +- packages/cli/src/utils.ts | 59 +++++++-------- packages/cli/test-utils/index.ts | 4 +- 9 files changed, 96 insertions(+), 138 deletions(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index aa04dc8f..ffdbb690 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -315,10 +315,10 @@ test("exports field with worker condition", async () => { exports: { "./package.json": "./package.json", ".": { - worker: { - module: "./dist/something-blah.worker.esm.js", + module: { + worker: "./dist/something-blah.worker.esm.js", + default: "./dist/something-blah.esm.js", }, - module: "./dist/something-blah.esm.js", default: "./dist/something-blah.cjs.js", }, }, @@ -335,10 +335,9 @@ test("exports field with worker condition", async () => { }); await dev(tmpPath); - expect(await getFiles(tmpPath, ["dist/**", "!dist/something-blah.cjs.js"])) .toMatchInlineSnapshot(` - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/something-blah.esm.js, dist/something-blah.worker.cjs.js, dist/something-blah.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/something-blah.esm.js, dist/something-blah.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ symbolic link to ../src/index.js `); }); diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index 1b7dc687..f13edbd5 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -98,20 +98,16 @@ test("set exports field when opt-in", async () => { "main": "dist/package-exports.cjs.js", "module": "dist/package-exports.esm.js", "browser": { - "./dist/package-exports.cjs.js": "./dist/package-exports.browser.cjs.js", "./dist/package-exports.esm.js": "./dist/package-exports.browser.esm.js" }, "exports": { "./package.json": "./package.json", ".": { - "worker": { - "module": "./dist/package-exports.worker.esm.js" + "module": { + "worker": "./dist/package-exports.worker.esm.js", + "browser": "./dist/package-exports.browser.esm.js", + "default": "./dist/package-exports.esm.js" }, - "browser": { - "module": "./dist/package-exports.browser.esm.js", - "default": "./dist/package-exports.browser.cjs.js" - }, - "module": "./dist/package-exports.esm.js", "default": "./dist/package-exports.cjs.js" } }, @@ -158,20 +154,16 @@ test("set exports field when opt-in", async () => { "main": "dist/package-exports.cjs.js", "module": "dist/package-exports.esm.js", "browser": { - "./dist/package-exports.cjs.js": "./dist/package-exports.browser.cjs.js", "./dist/package-exports.esm.js": "./dist/package-exports.browser.esm.js" }, "exports": { "./package.json": "./package.json", ".": { - "worker": { - "module": "./dist/package-exports.worker.esm.js" - }, - "browser": { - "module": "./dist/package-exports.browser.esm.js", - "default": "./dist/package-exports.browser.cjs.js" + "module": { + "worker": "./dist/package-exports.worker.esm.js", + "browser": "./dist/package-exports.browser.esm.js", + "default": "./dist/package-exports.esm.js" }, - "module": "./dist/package-exports.esm.js", "default": "./dist/package-exports.cjs.js" } }, @@ -264,7 +256,6 @@ test("set exports field with multiple entrypoints", async () => { "main": "dist/blah-something-deep-something.cjs.js", "module": "dist/blah-something-deep-something.esm.js", "browser": { - "./dist/blah-something-deep-something.cjs.js": "./dist/blah-something-deep-something.browser.cjs.js", "./dist/blah-something-deep-something.esm.js": "./dist/blah-something-deep-something.browser.esm.js" } } @@ -274,7 +265,6 @@ test("set exports field with multiple entrypoints", async () => { "main": "dist/blah-something-other.cjs.js", "module": "dist/blah-something-other.esm.js", "browser": { - "./dist/blah-something-other.cjs.js": "./dist/blah-something-other.browser.cjs.js", "./dist/blah-something-other.esm.js": "./dist/blah-something-other.browser.esm.js" } } @@ -285,42 +275,32 @@ test("set exports field with multiple entrypoints", async () => { "main": "dist/blah-something.cjs.js", "module": "dist/blah-something.esm.js", "browser": { - "./dist/blah-something.cjs.js": "./dist/blah-something.browser.cjs.js", "./dist/blah-something.esm.js": "./dist/blah-something.browser.esm.js" }, "exports": { "./package.json": "./package.json", ".": { - "worker": { - "module": "./dist/blah-something.worker.esm.js" - }, - "browser": { - "module": "./dist/blah-something.browser.esm.js", - "default": "./dist/blah-something.browser.cjs.js" + "module": { + "worker": "./dist/blah-something.worker.esm.js", + "browser": "./dist/blah-something.browser.esm.js", + "default": "./dist/blah-something.esm.js" }, - "module": "./dist/blah-something.esm.js", "default": "./dist/blah-something.cjs.js" }, "./other": { - "worker": { - "module": "./other/dist/blah-something-other.worker.esm.js" - }, - "browser": { - "module": "./other/dist/blah-something-other.browser.esm.js", - "default": "./other/dist/blah-something-other.browser.cjs.js" + "module": { + "worker": "./other/dist/blah-something-other.worker.esm.js", + "browser": "./other/dist/blah-something-other.browser.esm.js", + "default": "./other/dist/blah-something-other.esm.js" }, - "module": "./other/dist/blah-something-other.esm.js", "default": "./other/dist/blah-something-other.cjs.js" }, "./deep/something": { - "worker": { - "module": "./deep/something/dist/blah-something-deep-something.worker.esm.js" + "module": { + "worker": "./deep/something/dist/blah-something-deep-something.worker.esm.js", + "browser": "./deep/something/dist/blah-something-deep-something.browser.esm.js", + "default": "./deep/something/dist/blah-something-deep-something.esm.js" }, - "browser": { - "module": "./deep/something/dist/blah-something-deep-something.browser.esm.js", - "default": "./deep/something/dist/blah-something-deep-something.browser.cjs.js" - }, - "module": "./deep/something/dist/blah-something-deep-something.esm.js", "default": "./deep/something/dist/blah-something-deep-something.cjs.js" } }, @@ -376,7 +356,6 @@ test("set exports field without root entrypoint", async () => { "main": "dist/blah-something-other.cjs.js", "module": "dist/blah-something-other.esm.js", "browser": { - "./dist/blah-something-other.cjs.js": "./dist/blah-something-other.browser.cjs.js", "./dist/blah-something-other.esm.js": "./dist/blah-something-other.browser.esm.js" } } @@ -401,14 +380,11 @@ test("set exports field without root entrypoint", async () => { "exports": { "./package.json": "./package.json", "./other": { - "worker": { - "module": "./other/dist/blah-something-other.worker.esm.js" - }, - "browser": { - "module": "./other/dist/blah-something-other.browser.esm.js", - "default": "./other/dist/blah-something-other.browser.cjs.js" + "module": { + "worker": "./other/dist/blah-something-other.worker.esm.js", + "browser": "./other/dist/blah-something-other.browser.esm.js", + "default": "./other/dist/blah-something-other.esm.js" }, - "module": "./other/dist/blah-something-other.esm.js", "default": "./other/dist/blah-something-other.cjs.js" } } @@ -834,17 +810,15 @@ test("has browser field but no browser condition", async () => { "main": "dist/pkg-a.cjs.js", "module": "dist/pkg-a.esm.js", "browser": { - "./dist/pkg-a.cjs.js": "./dist/pkg-a.browser.cjs.js", "./dist/pkg-a.esm.js": "./dist/pkg-a.browser.esm.js" }, "exports": { "./package.json": "./package.json", ".": { - "browser": { - "module": "./dist/pkg-a.browser.esm.js", - "default": "./dist/pkg-a.browser.cjs.js" + "module": { + "browser": "./dist/pkg-a.browser.esm.js", + "default": "./dist/pkg-a.esm.js" }, - "module": "./dist/pkg-a.esm.js", "default": "./dist/pkg-a.cjs.js" } }, @@ -889,17 +863,15 @@ test("has browser condition but no browser field", async () => { "main": "dist/pkg-a.cjs.js", "module": "dist/pkg-a.esm.js", "browser": { - "./dist/pkg-a.cjs.js": "./dist/pkg-a.browser.cjs.js", "./dist/pkg-a.esm.js": "./dist/pkg-a.browser.esm.js" }, "exports": { "./package.json": "./package.json", ".": { - "browser": { - "module": "./dist/pkg-a.browser.esm.js", - "default": "./dist/pkg-a.browser.cjs.js" + "module": { + "browser": "./dist/pkg-a.browser.esm.js", + "default": "./dist/pkg-a.esm.js" }, - "module": "./dist/pkg-a.esm.js", "default": "./dist/pkg-a.cjs.js" } }, diff --git a/packages/cli/src/__tests__/validate.ts b/packages/cli/src/__tests__/validate.ts index 7c67f964..ef57d451 100644 --- a/packages/cli/src/__tests__/validate.ts +++ b/packages/cli/src/__tests__/validate.ts @@ -812,9 +812,10 @@ test("has browser field but no browser condition", async () => { }), "src/index.js": "", }); - await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( - `[Error: the exports field is configured and the browser field exists in this package but it is not specified in the preconstruct.exports.envConditions field]` - ); + await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot(` + [Error: 🎁 pkg-a the exports field is configured and the browser field exists in this package but it is not specified in the preconstruct.exports.envConditions field + 🎁 pkg-a browser field is invalid, found \`{"./dist/pkg-a.cjs.js":"./dist/pkg-a.browser.cjs.js","./dist/pkg-a.esm.js":"./dist/pkg-a.browser.esm.js"}\`, expected \`{"./dist/pkg-a.esm.js":"./dist/pkg-a.browser.esm.js"}\`] + `); }); test("has browser condition but no browser field", async () => { diff --git a/packages/cli/src/build/__tests__/build.ts b/packages/cli/src/build/__tests__/build.ts index b960991e..4be90164 100644 --- a/packages/cli/src/build/__tests__/build.ts +++ b/packages/cli/src/build/__tests__/build.ts @@ -668,17 +668,15 @@ test("worker and browser build", async () => { module: "dist/exports-test.esm.js", browser: { "./dist/exports-test.esm.js": "./dist/exports-test.browser.esm.js", - "./dist/exports-test.cjs.js": "./dist/exports-test.browser.cjs.js", }, exports: { "./package.json": "./package.json", ".": { - worker: { module: "./dist/exports-test.worker.esm.js" }, - browser: { - module: "./dist/exports-test.browser.esm.js", - default: "./dist/exports-test.browser.cjs.js", + module: { + worker: "./dist/exports-test.worker.esm.js", + browser: "./dist/exports-test.browser.esm.js", + default: "./dist/exports-test.esm.js", }, - module: "./dist/exports-test.esm.js", default: "./dist/exports-test.cjs.js", }, }, @@ -701,15 +699,6 @@ test("worker and browser build", async () => { }); await build(dir); expect(await getDist(dir)).toMatchInlineSnapshot(` - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/exports-test.browser.cjs.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - 'use strict'; - - Object.defineProperty(exports, '__esModule', { value: true }); - - const thing = "object"; - - exports.thing = thing; - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/exports-test.browser.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ const thing = "object"; diff --git a/packages/cli/src/build/config.ts b/packages/cli/src/build/config.ts index c2a10768..e7f93519 100644 --- a/packages/cli/src/build/config.ts +++ b/packages/cli/src/build/config.ts @@ -172,6 +172,8 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { }); }); + const exportsFieldConfig = pkg.exportsFieldConfig(); + let hasBrowserField = pkg.entrypoints[0].json.browser !== undefined; if (hasBrowserField) { @@ -184,7 +186,7 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { () => {} ), outputs: [ - { + !exportsFieldConfig && { format: "cjs" as const, entryFileNames: "[name].browser.cjs.js", chunkFileNames: "dist/[name]-[hash].browser.cjs.js", @@ -193,21 +195,18 @@ export function getRollupConfigs(pkg: Package, aliases: Aliases) { interop, plugins: cjsPlugins, }, - ...(hasModuleField - ? [ - { - format: "es" as const, - entryFileNames: "[name].browser.esm.js", - chunkFileNames: "dist/[name]-[hash].browser.esm.js", - dir: pkg.directory, - }, - ] - : []), - ], + hasModuleField && { + format: "es" as const, + entryFileNames: "[name].browser.esm.js", + chunkFileNames: "dist/[name]-[hash].browser.esm.js", + dir: pkg.directory, + }, + ].filter( + (value): value is Exclude => value !== false + ), }); } - const exportsFieldConfig = pkg.exportsFieldConfig(); // note module builds always exist when using the exports field if (exportsFieldConfig?.envConditions.has("worker")) { configs.push({ diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index 7fe0334e..d76ec648 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -4,7 +4,7 @@ import { tsTemplate, flowTemplate, validFieldsForEntrypoint, - getModuleTypeExportConditions, + getExportsFieldOutputPath, } from "./utils"; import * as babel from "@babel/core"; import * as fs from "fs-extra"; @@ -220,16 +220,15 @@ unregister(); } if (pkg.exportsFieldConfig()?.envConditions?.has("worker")) { - for (const output of Object.values( - getModuleTypeExportConditions(entrypoint, "worker") - )) { - promises.push( - fs.symlink( - entrypoint.source, - path.join(entrypoint.directory, output) + promises.push( + fs.symlink( + entrypoint.source, + path.join( + pkg.directory, + getExportsFieldOutputPath(entrypoint, "worker") ) - ); - } + ) + ); } if (entrypoint.json.browser) { diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 1a2bbed5..3da3c5f5 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -104,9 +104,7 @@ function createEntrypoints( } export type ExportsConditions = { - worker?: { module: string }; - browser?: { module: string; default: string }; - module: string; + module: string | { worker?: string; browser?: string; default: string }; default: string; }; diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index f8cf38c1..2dfc3c09 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -122,13 +122,17 @@ export const validFieldsFromPkg = { browser(pkg: Package, hasModuleBuild: boolean, entrypointName: string) { let safeName = getDistName(pkg, entrypointName); - let obj = { - [`./dist/${safeName}.cjs.js`]: `./dist/${safeName}.browser.cjs.js`, + const moduleBuild = { + [`./dist/${safeName}.esm.js`]: `./dist/${safeName}.browser.esm.js`, }; - if (hasModuleBuild) { - obj[`./dist/${safeName}.esm.js`] = `./dist/${safeName}.browser.esm.js`; + if (pkg.exportsFieldConfig()) { + return moduleBuild; } - return obj; + + return { + [`./dist/${safeName}.cjs.js`]: `./dist/${safeName}.browser.cjs.js`, + ...(hasModuleBuild && moduleBuild), + }; }, }; @@ -142,24 +146,21 @@ export function exportsField( let output: Record = {}; pkg.entrypoints.forEach((entrypoint) => { - let exportConditions: ExportsConditions = getModuleTypeExportConditions( - entrypoint, - undefined - ); - if (exportsFieldConfig.envConditions.has("browser")) { - exportConditions = { - browser: getModuleTypeExportConditions(entrypoint, "browser"), - ...exportConditions, - }; - } - if (exportsFieldConfig.envConditions.has("worker")) { - exportConditions = { - worker: { - module: getModuleTypeExportConditions(entrypoint, "worker").module, - }, - ...exportConditions, - }; - } + const esmBuild = getExportsFieldOutputPath(entrypoint, "esm"); + const exportConditions = { + module: exportsFieldConfig.envConditions.size + ? { + ...(exportsFieldConfig.envConditions.has("worker") && { + worker: getExportsFieldOutputPath(entrypoint, "worker"), + }), + ...(exportsFieldConfig.envConditions.has("browser") && { + browser: getExportsFieldOutputPath(entrypoint, "browser"), + }), + default: esmBuild, + } + : esmBuild, + default: getExportsFieldOutputPath(entrypoint, "cjs"), + }; output[ "." + entrypoint.name.replace(entrypoint.package.name, "") @@ -172,16 +173,16 @@ export function exportsField( }; } -export function getModuleTypeExportConditions( +export function getExportsFieldOutputPath( entrypoint: Entrypoint, - env: "worker" | "browser" | undefined + type: "cjs" | "esm" | "worker" | "browser" ) { const safeName = getDistName(entrypoint.package, entrypoint.name); + const format = type === "cjs" ? "cjs" : "esm"; + const env = type === "worker" || type === "browser" ? type : undefined; + const prefix = entrypoint.name.replace(entrypoint.package.name, ""); - return { - module: `.${prefix}/dist/${safeName}.${env ? `${env}.` : ""}esm.js`, - default: `.${prefix}/dist/${safeName}.${env ? `${env}.` : ""}cjs.js`, - }; + return `.${prefix}/dist/${safeName}.${env ? `${env}.` : ""}${format}.js`; } export const validFieldsForEntrypoint = { diff --git a/packages/cli/test-utils/index.ts b/packages/cli/test-utils/index.ts index 392fd45c..ce8dbd35 100644 --- a/packages/cli/test-utils/index.ts +++ b/packages/cli/test-utils/index.ts @@ -395,14 +395,14 @@ async function readNormalizedFile(filePath: string): Promise { const stat = await fs.lstat(filePath); if (stat.isSymbolicLink()) { const [targetPath, realFilePath] = await Promise.all([ - fs.readlink(filePath).then((x) => fs.realpath(x)), + fs.realpath(filePath), fs .realpath(path.dirname(filePath)) .then((x) => path.join(x, path.basename(filePath))), ]); return `symbolic link to ${normalizePath( path.relative(path.dirname(realFilePath), targetPath) - )}. ${JSON.stringify({ targetPath, realFilePath }, null, 2)}`; + )}`; } let content = await fs.readFile(filePath, "utf8"); // to normalise windows line endings From 9560a5d2f277fd998f5302517cb3a13fe10d6e0a Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 10:17:10 +1000 Subject: [PATCH 42/56] ./package.json after entrypoints --- packages/cli/src/__tests__/dev.ts | 2 +- packages/cli/src/__tests__/fix.ts | 36 +++++++++++------------ packages/cli/src/__tests__/validate.ts | 4 +-- packages/cli/src/build/__tests__/build.ts | 4 +-- packages/cli/src/utils.ts | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index ffdbb690..7ab033b1 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -313,7 +313,6 @@ test("exports field with worker condition", async () => { main: "dist/something-blah.cjs.js", module: "dist/something-blah.esm.js", exports: { - "./package.json": "./package.json", ".": { module: { worker: "./dist/something-blah.worker.esm.js", @@ -321,6 +320,7 @@ test("exports field with worker condition", async () => { }, default: "./dist/something-blah.cjs.js", }, + "./package.json": "./package.json", }, preconstruct: { exports: { diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index f13edbd5..ab40ca14 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -101,7 +101,6 @@ test("set exports field when opt-in", async () => { "./dist/package-exports.esm.js": "./dist/package-exports.browser.esm.js" }, "exports": { - "./package.json": "./package.json", ".": { "module": { "worker": "./dist/package-exports.worker.esm.js", @@ -109,7 +108,8 @@ test("set exports field when opt-in", async () => { "default": "./dist/package-exports.esm.js" }, "default": "./dist/package-exports.cjs.js" - } + }, + "./package.json": "./package.json" }, "preconstruct": { "exports": { @@ -157,7 +157,6 @@ test("set exports field when opt-in", async () => { "./dist/package-exports.esm.js": "./dist/package-exports.browser.esm.js" }, "exports": { - "./package.json": "./package.json", ".": { "module": { "worker": "./dist/package-exports.worker.esm.js", @@ -165,7 +164,8 @@ test("set exports field when opt-in", async () => { "default": "./dist/package-exports.esm.js" }, "default": "./dist/package-exports.cjs.js" - } + }, + "./package.json": "./package.json" }, "preconstruct": { "exports": { @@ -208,11 +208,11 @@ test("set exports field when opt-in with no env conditions", async () => { "main": "dist/package-exports.cjs.js", "module": "dist/package-exports.esm.js", "exports": { - "./package.json": "./package.json", ".": { "module": "./dist/package-exports.esm.js", "default": "./dist/package-exports.cjs.js" - } + }, + "./package.json": "./package.json" }, "preconstruct": { "exports": true, @@ -278,7 +278,6 @@ test("set exports field with multiple entrypoints", async () => { "./dist/blah-something.esm.js": "./dist/blah-something.browser.esm.js" }, "exports": { - "./package.json": "./package.json", ".": { "module": { "worker": "./dist/blah-something.worker.esm.js", @@ -302,7 +301,8 @@ test("set exports field with multiple entrypoints", async () => { "default": "./deep/something/dist/blah-something-deep-something.esm.js" }, "default": "./deep/something/dist/blah-something-deep-something.cjs.js" - } + }, + "./package.json": "./package.json" }, "preconstruct": { "entrypoints": [ @@ -378,7 +378,6 @@ test("set exports field without root entrypoint", async () => { } }, "exports": { - "./package.json": "./package.json", "./other": { "module": { "worker": "./other/dist/blah-something-other.worker.esm.js", @@ -386,7 +385,8 @@ test("set exports field without root entrypoint", async () => { "default": "./other/dist/blah-something-other.esm.js" }, "default": "./other/dist/blah-something-other.cjs.js" - } + }, + "./package.json": "./package.json" } } @@ -766,11 +766,11 @@ test("no module field with exports field", async () => { "main": "dist/pkg-a.cjs.js", "module": "dist/pkg-a.esm.js", "exports": { - "./package.json": "./package.json", ".": { "module": "./dist/pkg-a.esm.js", "default": "./dist/pkg-a.cjs.js" - } + }, + "./package.json": "./package.json" }, "preconstruct": { "exports": true, @@ -813,14 +813,14 @@ test("has browser field but no browser condition", async () => { "./dist/pkg-a.esm.js": "./dist/pkg-a.browser.esm.js" }, "exports": { - "./package.json": "./package.json", ".": { "module": { "browser": "./dist/pkg-a.browser.esm.js", "default": "./dist/pkg-a.esm.js" }, "default": "./dist/pkg-a.cjs.js" - } + }, + "./package.json": "./package.json" }, "preconstruct": { "exports": { @@ -866,14 +866,14 @@ test("has browser condition but no browser field", async () => { "./dist/pkg-a.esm.js": "./dist/pkg-a.browser.esm.js" }, "exports": { - "./package.json": "./package.json", ".": { "module": { "browser": "./dist/pkg-a.browser.esm.js", "default": "./dist/pkg-a.esm.js" }, "default": "./dist/pkg-a.cjs.js" - } + }, + "./package.json": "./package.json" }, "preconstruct": { "exports": { @@ -913,11 +913,11 @@ test("preconstruct.exports: true no exports field", async () => { "main": "dist/pkg-a.cjs.js", "module": "dist/pkg-a.esm.js", "exports": { - "./package.json": "./package.json", ".": { "module": "./dist/pkg-a.esm.js", "default": "./dist/pkg-a.cjs.js" - } + }, + "./package.json": "./package.json" }, "preconstruct": { "exports": true, diff --git a/packages/cli/src/__tests__/validate.ts b/packages/cli/src/__tests__/validate.ts index ef57d451..83a6fd1e 100644 --- a/packages/cli/src/__tests__/validate.ts +++ b/packages/cli/src/__tests__/validate.ts @@ -667,11 +667,11 @@ describe("exports field config", () => { main: "dist/pkg-a.cjs.js", module: "dist/pkg-a.esm.js", exports: { - "./package.json": "./package.json", ".": { module: "./dist/pkg-a.esm.js", default: "./dist/pkg-a.cjs.js", }, + "./package.json": "./package.json", }, preconstruct: { exports: config, @@ -856,6 +856,6 @@ test("preconstruct.exports: true no exports field", async () => { "src/index.js": "", }); await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( - `[Error: exports field was not found, expected \`{"./package.json":"./package.json",".":{"module":"./dist/pkg-a.esm.js","default":"./dist/pkg-a.cjs.js"}}\`]` + `[Error: exports field was not found, expected \`{".":{"module":"./dist/pkg-a.esm.js","default":"./dist/pkg-a.cjs.js"},"./package.json":"./package.json"}\`]` ); }); diff --git a/packages/cli/src/build/__tests__/build.ts b/packages/cli/src/build/__tests__/build.ts index 4be90164..5255db16 100644 --- a/packages/cli/src/build/__tests__/build.ts +++ b/packages/cli/src/build/__tests__/build.ts @@ -670,7 +670,6 @@ test("worker and browser build", async () => { "./dist/exports-test.esm.js": "./dist/exports-test.browser.esm.js", }, exports: { - "./package.json": "./package.json", ".": { module: { worker: "./dist/exports-test.worker.esm.js", @@ -679,6 +678,7 @@ test("worker and browser build", async () => { }, default: "./dist/exports-test.cjs.js", }, + "./package.json": "./package.json", }, preconstruct: { exports: { @@ -749,7 +749,6 @@ test("typescript with nodenext module resolution", async () => { main: "dist/pkg-a.cjs.js", module: "dist/pkg-a.esm.js", exports: { - "./package.json": "./package.json", ".": { module: "./dist/pkg-a.esm.js", default: "./dist/pkg-a.cjs.js", @@ -758,6 +757,7 @@ test("typescript with nodenext module resolution", async () => { module: "./something/dist/pkg-a-something.esm.js", default: "./something/dist/pkg-a-something.cjs.js", }, + "./package.json": "./package.json", }, preconstruct: { entrypoints: ["index.ts", "something.ts"], diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 2dfc3c09..9a9206c8 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -167,8 +167,8 @@ export function exportsField( ] = exportConditions; }); return { - "./package.json": "./package.json", ...output, + "./package.json": "./package.json", ...exportsFieldConfig.extra, }; } From 28f5d48b2743d86d792a9cd2c446fd9830ac6057 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 10:31:30 +1000 Subject: [PATCH 43/56] thing for windows debugging again --- packages/cli/test-utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/test-utils/index.ts b/packages/cli/test-utils/index.ts index ce8dbd35..3f3eb4c8 100644 --- a/packages/cli/test-utils/index.ts +++ b/packages/cli/test-utils/index.ts @@ -402,7 +402,7 @@ async function readNormalizedFile(filePath: string): Promise { ]); return `symbolic link to ${normalizePath( path.relative(path.dirname(realFilePath), targetPath) - )}`; + )} ${JSON.stringify({ realFilePath, targetPath }, null, 2)}`; } let content = await fs.readFile(filePath, "utf8"); // to normalise windows line endings From f1f8e60d0cce28c483409a3fd3d05bf8a091dfb9 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 10:41:16 +1000 Subject: [PATCH 44/56] Try a different thing? --- packages/cli/src/__tests__/dev.ts | 20 ++++++++++++++++---- packages/cli/test-utils/index.ts | 12 ------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index 7ab033b1..43fdf0c6 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -331,13 +331,25 @@ test("exports field with worker condition", async () => { }, }, }), - "src/index.js": "", + "src/index.js": "console.log(1)", }); await dev(tmpPath); - expect(await getFiles(tmpPath, ["dist/**", "!dist/something-blah.cjs.js"])) - .toMatchInlineSnapshot(` + const files = await getFiles(tmpPath, [ + "dist/**", + "!dist/something-blah.cjs.js", + ]); + expect(files).toMatchInlineSnapshot(` ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/something-blah.esm.js, dist/something-blah.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ - symbolic link to ../src/index.js + console.log(1) `); + await Promise.all( + Object.keys(files).map(async (filename) => { + const [realpathFromSymlink, realpath] = await Promise.all([ + fs.realpath(path.join(tmpPath, filename)), + fs.realpath(path.join(tmpPath, "src/index.js")), + ]); + expect(realpathFromSymlink).toEqual(realpath); + }) + ); }); diff --git a/packages/cli/test-utils/index.ts b/packages/cli/test-utils/index.ts index 3f3eb4c8..6f0cb5d4 100644 --- a/packages/cli/test-utils/index.ts +++ b/packages/cli/test-utils/index.ts @@ -392,18 +392,6 @@ export async function getDist(dir: string) { } async function readNormalizedFile(filePath: string): Promise { - const stat = await fs.lstat(filePath); - if (stat.isSymbolicLink()) { - const [targetPath, realFilePath] = await Promise.all([ - fs.realpath(filePath), - fs - .realpath(path.dirname(filePath)) - .then((x) => path.join(x, path.basename(filePath))), - ]); - return `symbolic link to ${normalizePath( - path.relative(path.dirname(realFilePath), targetPath) - )} ${JSON.stringify({ realFilePath, targetPath }, null, 2)}`; - } let content = await fs.readFile(filePath, "utf8"); // to normalise windows line endings content = content.replace(/\r\n/g, "\n"); From 1c0f9e88c97258320ce0738493cdd6f7ad4453ed Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 10:50:37 +1000 Subject: [PATCH 45/56] try realpath again? --- packages/cli/src/__tests__/dev.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index 43fdf0c6..e28a3eb2 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -347,7 +347,9 @@ test("exports field with worker condition", async () => { Object.keys(files).map(async (filename) => { const [realpathFromSymlink, realpath] = await Promise.all([ fs.realpath(path.join(tmpPath, filename)), - fs.realpath(path.join(tmpPath, "src/index.js")), + fs + .realpath(path.join(tmpPath, "src/index.js")) + .then((x) => fs.realpath(x)), ]); expect(realpathFromSymlink).toEqual(realpath); }) From 3430e86036ae925c292eec1a5fffa029dfac6dcf Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 10:51:16 +1000 Subject: [PATCH 46/56] Try realpath again? --- packages/cli/src/__tests__/dev.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index e28a3eb2..0a9b38a9 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -346,10 +346,8 @@ test("exports field with worker condition", async () => { await Promise.all( Object.keys(files).map(async (filename) => { const [realpathFromSymlink, realpath] = await Promise.all([ - fs.realpath(path.join(tmpPath, filename)), - fs - .realpath(path.join(tmpPath, "src/index.js")) - .then((x) => fs.realpath(x)), + fs.realpath(path.join(tmpPath, filename)).then((x) => fs.realpath(x)), + fs.realpath(path.join(tmpPath, "src/index.js")), ]); expect(realpathFromSymlink).toEqual(realpath); }) From 0956b9a3cabd3add91209279aae05b58ad7169c9 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 10:53:02 +1000 Subject: [PATCH 47/56] Another attempt --- packages/cli/src/__tests__/dev.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index 0a9b38a9..73fd8244 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -349,6 +349,12 @@ test("exports field with worker condition", async () => { fs.realpath(path.join(tmpPath, filename)).then((x) => fs.realpath(x)), fs.realpath(path.join(tmpPath, "src/index.js")), ]); + expect({ + realpathFromSymlink, + realpath, + notRealpath: path.join(tmpPath, "src/index.js"), + readlink: fs.readlink(path.join(tmpPath, filename)), + }).toEqual({}); expect(realpathFromSymlink).toEqual(realpath); }) ); From fe182828a49ec07aa13ff70986989022fb3782ce Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 10:54:44 +1000 Subject: [PATCH 48/56] Try a thing? --- packages/cli/src/__tests__/dev.ts | 11 +++-------- site/src/pages/configuration.mdx | 8 +++----- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index 73fd8244..2a398e7b 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -343,18 +343,13 @@ test("exports field with worker condition", async () => { ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/something-blah.esm.js, dist/something-blah.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ console.log(1) `); + const realpathOfDir = await fs.realpath(tmpPath); await Promise.all( Object.keys(files).map(async (filename) => { const [realpathFromSymlink, realpath] = await Promise.all([ - fs.realpath(path.join(tmpPath, filename)).then((x) => fs.realpath(x)), - fs.realpath(path.join(tmpPath, "src/index.js")), + fs.readlink(path.join(realpathOfDir, filename)), + path.join(realpathOfDir, "src/index.js"), ]); - expect({ - realpathFromSymlink, - realpath, - notRealpath: path.join(tmpPath, "src/index.js"), - readlink: fs.readlink(path.join(tmpPath, filename)), - }).toEqual({}); expect(realpathFromSymlink).toEqual(realpath); }) ); diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index 5509986f..e714de7a 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -147,14 +147,12 @@ The `exports` config allows you to specify opt-in to generating an `exports` fie Using the `exports` field enables a couple of things: - Importing non-root entrypoints in Node.js ESM -- Disallowing importing modules that aren't explicitly specified in the `exports` field +- Disallowing importing modules that aren't specified in the `exports` field - More specific builds for certain environments > Note that Preconstruct's support for the `exports` field does not currently include emitting ESM compatible with Node.js. While ESM builds are generated, they are targeting bundlers, not Node.js or browsers directly so they use the `module` condition, not the `import` condition. -Note that adding an `exports` field can `arguably` - -conditional exports for various environments. You can use this to create custom builds targeted for these specific environments. Currently, only "browser", "worker", and "module" conditionals are supported. +Note that adding an `exports` field can arguably be a breaking change, you may want to use the `extra` option to add more exports so that imports that worked previously still work or only add the `exports` field in a major version. To opt into this experimental feature, you must enable it in the root of your project by setting the `exports` experimental flag in your preconstruct config section of your `package.json` file to `true`. @@ -188,7 +186,7 @@ Additionally, you'll also need to enable the feature on each individual pacakge. Specifying the `envConditions` option adds additional environments that Preconstruct will generate bundles for. This option is currently aimed at generating bundles with `typeof SOME_ENV_SPECIFIC_GLOBAL` replaced with what it would be in that environment. It may be expanded to provide the ability to have Preconstruct resolve a different file or etc. depending on the environment in the future. -- `browser`: Generates a bundle targeting browsers. When this condition is used, the top-level `browser` field will also be set so that older bundlers that do not understand the `exports` field will be able to use the browser build. When building with this condition, `typeof document` and `typeof window` will be replaced with `"object"` and dead-code elimination will occur based on that. +- `browser`: Generates a bundle targeting browsers. When this condition is used, the top-level `browser` field will also be set so that older bundlers that do not understand the `exports` field will be able to use the browser build though CommonJS browser builds will not exist. When building with this condition, `typeof document` and `typeof window` will be replaced with `"object"` and dead-code elimination will occur based on that. - `worker`: Generates a bundle targeting web workers/server-side JS runtimes that use web APIs. When building with this condition, `typeof document` and `typeof window` will be replaced with `"undefined"` and dead-code elimination will occur based on that. ```json From 7be8e951e310bd8d54395ccc59ba047ead9c0e33 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 11:06:36 +1000 Subject: [PATCH 49/56] Try another thing? --- packages/cli/src/__tests__/dev.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index 2a398e7b..3cbde72c 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -333,7 +333,7 @@ test("exports field with worker condition", async () => { }), "src/index.js": "console.log(1)", }); - + tmpPath = await fs.realpath(tmpPath); await dev(tmpPath); const files = await getFiles(tmpPath, [ "dist/**", @@ -343,7 +343,7 @@ test("exports field with worker condition", async () => { ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/something-blah.esm.js, dist/something-blah.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ console.log(1) `); - const realpathOfDir = await fs.realpath(tmpPath); + const realpathOfDir = tmpPath; await Promise.all( Object.keys(files).map(async (filename) => { const [realpathFromSymlink, realpath] = await Promise.all([ From 374a0b43d444727af7a75d88603779bc64651a4e Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 14:13:45 +1000 Subject: [PATCH 50/56] more windows debugging --- packages/cli/src/__tests__/dev.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index 3cbde72c..d9a025fa 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -343,14 +343,13 @@ test("exports field with worker condition", async () => { ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ dist/something-blah.esm.js, dist/something-blah.worker.esm.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ console.log(1) `); - const realpathOfDir = tmpPath; await Promise.all( Object.keys(files).map(async (filename) => { - const [realpathFromSymlink, realpath] = await Promise.all([ - fs.readlink(path.join(realpathOfDir, filename)), - path.join(realpathOfDir, "src/index.js"), - ]); - expect(realpathFromSymlink).toEqual(realpath); + expect({ + a: fs.readlinkSync(path.join(tmpPath, filename)), + b: fs.realpathSync(path.join(tmpPath, filename)), + c: path.join(tmpPath, "src/index.js"), + }).toEqual({}); }) ); }); From 5558764a8b4f50be76dad944590dc776e1adb7e9 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 14:55:57 +1000 Subject: [PATCH 51/56] Try a different thing --- packages/cli/src/__tests__/dev.ts | 57 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index d9a025fa..4f561aaf 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -307,33 +307,34 @@ test("typescript with typeScriptProxyFileWithImportEqualsRequireAndExportEquals" }); test("exports field with worker condition", async () => { - let tmpPath = await testdir({ - "package.json": JSON.stringify({ - name: "@something/blah", - main: "dist/something-blah.cjs.js", - module: "dist/something-blah.esm.js", - exports: { - ".": { - module: { - worker: "./dist/something-blah.worker.esm.js", - default: "./dist/something-blah.esm.js", - }, - default: "./dist/something-blah.cjs.js", - }, - "./package.json": "./package.json", - }, - preconstruct: { + let tmpPath = realFs.realpathSync.native( + await testdir({ + "package.json": JSON.stringify({ + name: "@something/blah", + main: "dist/something-blah.cjs.js", + module: "dist/something-blah.esm.js", exports: { - envConditions: ["worker"], + ".": { + module: { + worker: "./dist/something-blah.worker.esm.js", + default: "./dist/something-blah.esm.js", + }, + default: "./dist/something-blah.cjs.js", + }, + "./package.json": "./package.json", }, - ___experimentalFlags_WILL_CHANGE_IN_PATCH: { - exports: true, + preconstruct: { + exports: { + envConditions: ["worker"], + }, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, }, - }, - }), - "src/index.js": "console.log(1)", - }); - tmpPath = await fs.realpath(tmpPath); + }), + "src/index.js": "console.log(1)", + }) + ); await dev(tmpPath); const files = await getFiles(tmpPath, [ "dist/**", @@ -345,11 +346,9 @@ test("exports field with worker condition", async () => { `); await Promise.all( Object.keys(files).map(async (filename) => { - expect({ - a: fs.readlinkSync(path.join(tmpPath, filename)), - b: fs.realpathSync(path.join(tmpPath, filename)), - c: path.join(tmpPath, "src/index.js"), - }).toEqual({}); + expect(fs.realpathSync(path.join(tmpPath, filename))).toEqual( + path.join(tmpPath, "src/index.js") + ); }) ); }); From 803290f959a861ba11ec658291934cefb8aab897 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Wed, 6 Jul 2022 16:10:53 +1000 Subject: [PATCH 52/56] Async --- packages/cli/src/__tests__/dev.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/__tests__/dev.ts b/packages/cli/src/__tests__/dev.ts index 4f561aaf..b9fc6774 100644 --- a/packages/cli/src/__tests__/dev.ts +++ b/packages/cli/src/__tests__/dev.ts @@ -346,7 +346,7 @@ test("exports field with worker condition", async () => { `); await Promise.all( Object.keys(files).map(async (filename) => { - expect(fs.realpathSync(path.join(tmpPath, filename))).toEqual( + expect(await fs.realpath(path.join(tmpPath, filename))).toEqual( path.join(tmpPath, "src/index.js") ); }) From a57d57bc6977de2fcccfab8cd647ac286333598d Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Thu, 7 Jul 2022 10:43:50 +1000 Subject: [PATCH 53/56] Docs --- site/src/pages/configuration.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index e714de7a..182b30e9 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -142,7 +142,7 @@ Packages map 1:1 with npm packages. Along with specifying the `entrypoints` opti }; ``` -The `exports` config allows you to specify opt-in to generating an `exports` field. +The `exports` config allows you to opt-in to generating an `exports` field. Using the `exports` field enables a couple of things: @@ -150,7 +150,7 @@ Using the `exports` field enables a couple of things: - Disallowing importing modules that aren't specified in the `exports` field - More specific builds for certain environments -> Note that Preconstruct's support for the `exports` field does not currently include emitting ESM compatible with Node.js. While ESM builds are generated, they are targeting bundlers, not Node.js or browsers directly so they use the `module` condition, not the `import` condition. +> Note that Preconstruct's support for the `exports` field does not currently include generating ESM compatible with Node.js. While ESM builds are generated, they are targeting bundlers, not Node.js or browsers directly so they use the `module` condition, not the `import` condition. Note that adding an `exports` field can arguably be a breaking change, you may want to use the `extra` option to add more exports so that imports that worked previously still work or only add the `exports` field in a major version. @@ -186,7 +186,9 @@ Additionally, you'll also need to enable the feature on each individual pacakge. Specifying the `envConditions` option adds additional environments that Preconstruct will generate bundles for. This option is currently aimed at generating bundles with `typeof SOME_ENV_SPECIFIC_GLOBAL` replaced with what it would be in that environment. It may be expanded to provide the ability to have Preconstruct resolve a different file or etc. depending on the environment in the future. -- `browser`: Generates a bundle targeting browsers. When this condition is used, the top-level `browser` field will also be set so that older bundlers that do not understand the `exports` field will be able to use the browser build though CommonJS browser builds will not exist. When building with this condition, `typeof document` and `typeof window` will be replaced with `"object"` and dead-code elimination will occur based on that. +Builds + +- `browser`: Generates a bundle targeting browsers. When this condition is used, the top-level `browser` field will also be set so that older bundlers that do not understand the `exports` field will be able to use the browser build (though when using the exports field, browser CommonJS builds will not be built). When building with this condition, `typeof document` and `typeof window` will be replaced with `"object"` and dead-code elimination will occur based on that. - `worker`: Generates a bundle targeting web workers/server-side JS runtimes that use web APIs. When building with this condition, `typeof document` and `typeof window` will be replaced with `"undefined"` and dead-code elimination will occur based on that. ```json @@ -205,7 +207,7 @@ Specifying the `envConditions` option adds additional environments that Preconst `Record` -Preconstruct will enforce that the `exports` field that is written can be directly generated by your config, this means that extra properties are not allowed to be written directly in the `exports` field. If you want to add extra entries to the `exports` field, you can use the `extra` option in `preconstruct.exports` and then `preconstruct fix` will add add them to the actual `exports` field. +Preconstruct will enforce that the `exports` field that is written can is directly a function of your config, this means that extra properties are not allowed to be written directly in the `exports` field. If you want to add extra entries to the `exports` field, you can use the `extra` option in `preconstruct.exports` and then `preconstruct fix` will add add them to the actual `exports` field. ```json { From caebf9e49eaaf90c103f730a126dc4d891caba91 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Thu, 7 Jul 2022 10:45:07 +1000 Subject: [PATCH 54/56] Update changeset --- .changeset/afraid-experts-attack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/afraid-experts-attack.md b/.changeset/afraid-experts-attack.md index a6ea63ea..a540c540 100644 --- a/.changeset/afraid-experts-attack.md +++ b/.changeset/afraid-experts-attack.md @@ -2,4 +2,4 @@ "@preconstruct/cli": patch --- -Added experimental `exports` flag. +Added experimental `exports` flag. See the docs at the `exports` section of https://preconstruct.tools/configuration. From d1f5c6e21693cbf2fa57d42c5b7f0bbbbf76ecac Mon Sep 17 00:00:00 2001 From: Mitchell Hamilton Date: Fri, 8 Jul 2022 09:12:24 +1000 Subject: [PATCH 55/56] Update site/src/pages/configuration.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mateusz Burzyński --- site/src/pages/configuration.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index 182b30e9..028c27fe 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -168,7 +168,7 @@ To opt into this experimental feature, you must enable it in the root of your pr } ``` -Additionally, you'll also need to enable the feature on each individual pacakge. +Additionally, you'll also need to enable the feature on each individual package. ```diff { From e73af13e969d8cead97990a2cd9f783b924ed413 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 8 Jul 2022 14:18:13 +1000 Subject: [PATCH 56/56] Project level exports field config --- packages/cli/src/__tests__/fix.ts | 53 ++++++++++++++++++++++++++ packages/cli/src/__tests__/validate.ts | 4 +- packages/cli/src/package.ts | 34 +++++++++++++++-- packages/cli/src/project.ts | 3 +- packages/cli/src/validate-package.ts | 9 +++-- site/src/pages/configuration.mdx | 2 +- 6 files changed, 93 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/__tests__/fix.ts b/packages/cli/src/__tests__/fix.ts index ab40ca14..9adb5627 100644 --- a/packages/cli/src/__tests__/fix.ts +++ b/packages/cli/src/__tests__/fix.ts @@ -929,3 +929,56 @@ test("preconstruct.exports: true no exports field", async () => { `); }); + +test("project level exports field config", async () => { + const tmpPath = await testdir({ + "package.json": JSON.stringify( + { + name: "repo", + preconstruct: { + packages: ["packages/*"], + exports: true, + ___experimentalFlags_WILL_CHANGE_IN_PATCH: { + exports: true, + }, + }, + }, + null, + 2 + ), + "packages/pkg-a/package.json": JSON.stringify({ + name: "pkg-a", + }), + "packages/pkg-a/src/index.js": "", + }); + await fix(tmpPath); + expect(await getFiles(tmpPath, ["**/package.json"])).toMatchInlineSnapshot(` + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "repo", + "preconstruct": { + "packages": [ + "packages/*" + ], + "exports": true, + "___experimentalFlags_WILL_CHANGE_IN_PATCH": { + "exports": true + } + } + } + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ packages/pkg-a/package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ + { + "name": "pkg-a", + "main": "dist/pkg-a.cjs.js", + "module": "dist/pkg-a.esm.js", + "exports": { + ".": { + "module": "./dist/pkg-a.esm.js", + "default": "./dist/pkg-a.cjs.js" + }, + "./package.json": "./package.json" + } + } + + `); +}); diff --git a/packages/cli/src/__tests__/validate.ts b/packages/cli/src/__tests__/validate.ts index 83a6fd1e..b935c532 100644 --- a/packages/cli/src/__tests__/validate.ts +++ b/packages/cli/src/__tests__/validate.ts @@ -687,13 +687,13 @@ describe("exports field config", () => { test("null", async () => { const tmpPath = await exportsFieldConfigTestDir(null); await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( - `[Error: the "preconstruct.exports" field must be a boolean or an object]` + `[Error: the "preconstruct.exports" field must be a boolean or an object at the package level]` ); }); test("some string", async () => { const tmpPath = await exportsFieldConfigTestDir("blah"); await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot( - `[Error: the "preconstruct.exports" field must be a boolean or an object]` + `[Error: the "preconstruct.exports" field must be a boolean or an object at the package level]` ); }); test("extra not object", async () => { diff --git a/packages/cli/src/package.ts b/packages/cli/src/package.ts index 3da3c5f5..ddbd49c5 100644 --- a/packages/cli/src/package.ts +++ b/packages/cli/src/package.ts @@ -315,7 +315,25 @@ export class Package extends Item<{ if (!this.project.experimentalFlags.exports) { return; } - return parseExportsFieldConfig(this.json.preconstruct.exports, this.name); + let defaultExportsFieldEnabled = false; + if (this.project.directory !== this.directory) { + const exportsFieldConfig = this.project.json.preconstruct.exports; + if (exportsFieldConfig !== undefined) { + if (typeof exportsFieldConfig === "boolean") { + defaultExportsFieldEnabled = exportsFieldConfig; + } else { + throw new FatalError( + 'the "preconstruct.exports" field must be a boolean at the project level', + this.project.name + ); + } + } + } + return parseExportsFieldConfig( + this.json.preconstruct.exports, + defaultExportsFieldEnabled, + this.name + ); } } @@ -327,9 +345,14 @@ type CanonicalExportsFieldConfig = }; function parseExportsFieldConfig( - config: unknown, + _config: unknown, + defaultExportsFieldEnabled: boolean, name: string ): CanonicalExportsFieldConfig { + // the seperate assignment vs declaration is so that TypeScript's + // control flow analysis does what we want + let config; + config = _config; if ( (typeof config !== "boolean" && typeof config !== "object" && @@ -338,11 +361,14 @@ function parseExportsFieldConfig( Array.isArray(config) ) { throw new FatalError( - 'the "preconstruct.exports" field must be a boolean or an object', + 'the "preconstruct.exports" field must be a boolean or an object at the package level', name ); } - if (config === false || config === undefined) { + if (config === undefined) { + config = defaultExportsFieldEnabled; + } + if (config === false) { return undefined; } const parsedConfig: CanonicalExportsFieldConfig = { diff --git a/packages/cli/src/project.ts b/packages/cli/src/project.ts index a9cf52c9..3171ada5 100644 --- a/packages/cli/src/project.ts +++ b/packages/cli/src/project.ts @@ -25,8 +25,9 @@ export class Project extends Item<{ globals?: Record; packages?: JSONValue; distFilenameStrategy?: JSONValue; + exports?: JSONValue; ___experimentalFlags_WILL_CHANGE_IN_PATCH: { - exports?: boolean; + exports?: JSONValue; logCompiledFiles?: JSONValue; typeScriptProxyFileWithImportEqualsRequireAndExportEquals?: JSONValue; keepDynamicImportAsDynamicImportInCommonJS?: JSONValue; diff --git a/packages/cli/src/validate-package.ts b/packages/cli/src/validate-package.ts index 3458dda4..81812546 100644 --- a/packages/cli/src/validate-package.ts +++ b/packages/cli/src/validate-package.ts @@ -39,15 +39,16 @@ export async function fixPackage(pkg: Package) { } } - pkg.json = setFieldInOrder(pkg.json, "exports", exportsField(pkg)); - - await pkg.save(); - keys(fields) .filter((x) => fields[x]) .forEach((field) => { pkg.setFieldOnEntrypoints(field); }); + + pkg.json = setFieldInOrder(pkg.json, "exports", exportsField(pkg)); + + await pkg.save(); + return (await Promise.all(pkg.entrypoints.map((x) => x.save()))).some( (x) => x ); diff --git a/site/src/pages/configuration.mdx b/site/src/pages/configuration.mdx index 028c27fe..a5635b78 100644 --- a/site/src/pages/configuration.mdx +++ b/site/src/pages/configuration.mdx @@ -168,7 +168,7 @@ To opt into this experimental feature, you must enable it in the root of your pr } ``` -Additionally, you'll also need to enable the feature on each individual package. +The `exports` field feature then needs to be enabled, you can do this at the project or package level like this. The `envConditions` and `extra` options can only be configured at a package level. ```diff {