From 14c4898b5350403de8e27559a0093a6f65a283c9 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Thu, 27 Feb 2025 14:40:07 -0500 Subject: [PATCH 01/16] feat: improve sorting of imports/exports Force relative position of certain conditions, sort paths in alphabetically with exception of paths with wildcards. --- index.js | 164 +++++++++++++++++++++++++++++++++++++++++++++++- tests/fields.js | 99 +++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 5f77b81..4dd767c 100755 --- a/index.js +++ b/index.js @@ -270,6 +270,166 @@ const sortScripts = onObject((scripts, packageJson) => { return sortObjectKeys(scripts, order) }) +/** + * Sorts an array in relative terms defined by the `order` + * + * The effect of relative sort is that keys not in the `order` will be kept + * in the order they were in the original array unless it is shifted to + * accommodate a key in the `order` + */ +const relativeOrderSort = (list, order) => { + const orderMap = new Map(order.map((key, index) => [key, index])) + let closestIndex = 0 + for (const item of list) { + if (orderMap.has(item)) { + closestIndex = orderMap.get(item) + } else { + orderMap.set(item, closestIndex) + } + } + return list.sort((a, b) => { + const aIndex = orderMap.get(a) + const bIndex = orderMap.get(b) + return aIndex - bIndex + }) +} + +const withLastKey = (keyName, { [keyName]: keyValue, ...rest }) => ({ + ...rest, + [keyName]: keyValue, +}) + +const sortConditionObject = (conditionObject) => { + const bundler = [ + 'vite', + 'rollup', + 'webpack', + + /** + * Bun is a target environment and a bundler + * so it must come before other target environments + * and reference syntaxes + */ + // bun + 'bun', + 'macro', + + /** + * Deno is a target environment and a bundler + * so it must come before other target environments + * and reference syntaxes + */ + 'deno', + ] + + const implementationVariants = ['react-server'] + + const referenceSyntax = [ + /** + * 'types' condition must come before `import` or `require`, as typescript + * will use those if encountered first. + */ + 'types', + + /** + * 'script' condition must come before 'module' condition, as 'script' + * may also be used by bundlers but in more specific conditions than + * 'module' + */ + 'script', + 'esmodules', + /** + * 'module' condition must come before 'import'. import may include pure node ESM modules + * that are only compatible with node environments, while 'module' may be + * used by bundlers and leverage other bundler features + */ + 'module', + 'import', + 'require', + 'style', + 'stylus', + 'sass', + 'asset', + ] + + const targetEnvironment = [ + 'browser', + 'electron', + 'node', + 'react-native', + 'worker', + 'worklet', + ] + + const environment = ['development', 'test', 'production'] + + const order = relativeOrderSort(Object.keys(conditionObject), [ + /** + * Bundler conditions are generally more important than other conditions + * because they leverage code that will not work outside of the + * bundler environment + */ + ...bundler, + /** + * Implementation variants need to be placed before "reference syntax" and + * "target environments" because similar to "bundler" conditions, + * they only work in specific environments and may expose code overrides + * for any of the conditions in "reference syntax" and "target environments" + */ + ...implementationVariants, + ...referenceSyntax, + ...targetEnvironment, + ...environment, + ]) + return withLastKey('default', sortObjectKeys(conditionObject, order)) +} + +const sortPathLikeObjectWithWildcards = onObject((object) => { + // Replace all '*' with the highest possible unicode character + // To force all wildcards to be at the end, but relative to + // the path they are in + const wildcard = '\u{10FFFF}' + const sortableWildcardPaths = new Map() + const sortablePath = (path) => { + if (sortableWildcardPaths.has(path)) return sortableWildcardPaths.get(path) + const wildcardWeightedPath = path.replace(/\*/g, wildcard) + sortableWildcardPaths.set(path, wildcardWeightedPath) + return wildcardWeightedPath + } + return sortObjectKeys(object, (a, b) => { + return sortablePath(a).localeCompare(sortablePath(b)) + }) +}) + +const sortExportsOrImports = onObject((exportOrImports) => { + const exportsWithSortedChildren = Object.fromEntries( + Object.entries(exportOrImports).map(([key, value]) => { + return [key, sortExportsOrImports(value)] + }), + ) + + const keys = Object.keys(exportsWithSortedChildren) + let isConditionObject = true + let isPathLikeObject = true + for (const key of keys) { + const keyIsPathLike = key.startsWith('.') || key.startsWith('#') + + isConditionObject &&= !keyIsPathLike + isPathLikeObject &&= keyIsPathLike + } + + if (isConditionObject) { + return sortConditionObject(exportsWithSortedChildren) + } + + if (isPathLikeObject) { + return sortPathLikeObjectWithWildcards(exportsWithSortedChildren) + } + + // Object is improperly formatted. Leave it alone + return exportOrImports +}) + // fields marked `vscode` are for `Visual Studio Code extension manifest` only // https://code.visualstudio.com/api/references/extension-manifest // Supported fields: @@ -306,8 +466,8 @@ const fields = [ /* vscode */ { key: 'publisher' }, { key: 'sideEffects' }, { key: 'type' }, - { key: 'imports' }, - { key: 'exports' }, + { key: 'imports', over: sortExportsOrImports }, + { key: 'exports', over: sortExportsOrImports }, { key: 'main' }, { key: 'svelte' }, { key: 'umd:main' }, diff --git a/tests/fields.js b/tests/fields.js index 458c8ae..92a3263 100644 --- a/tests/fields.js +++ b/tests/fields.js @@ -326,3 +326,102 @@ test('pnpm', macro.sortObject, { }, }, }) + +test('imports', macro.sortObject, { + path: 'imports', + value: { + '#c': './index.js', + '#c/sub': './index.js', + '#c/*': './wild/*.js', + '#a': './sub/index.js', + '#b/sub/*': './wild/*.js', + '#b/*': './wild/*.js', + '#b/sub': './wild/sub-module.js', + }, + expect: { + '#a': './sub/index.js', + '#b/sub': './wild/sub-module.js', + '#b/sub/*': './wild/*.js', + '#b/*': './wild/*.js', + '#c': './index.js', + '#c/sub': './index.js', + '#c/*': './wild/*.js', + }, +}) +test('exports level 1', macro.sortObject, { + path: 'exports', + value: { + './sub': './sub/index.js', + './a-wildcard/*': './wild/*.js', + './a-wildcard/sub': './wild/sub-module.js', + '.': './index.js', + }, + expect: { + '.': './index.js', + './a-wildcard/sub': './wild/sub-module.js', + './a-wildcard/*': './wild/*.js', + './sub': './sub/index.js', + }, +}) + +test('exports conditions', macro.sortObject, { + path: 'exports', + value: { + custom: './custom.js', + module: './module.js', + vite: './vite.js', + rollup: './rollup.js', + webpack: './webpack.js', + import: './import.js', + types: './types/index.d.ts', + script: './script.js', + node: './node.js', + 'react-native': './react-native.js', + stylus: './style.styl', + sass: './style.sass', + esmodules: './esmodules.js', + default: './index.js', + style: './style.css', + asset: './asset.png', + 'react-server': './react-server.js', + browser: './browser.js', + electron: './electron.js', + deno: './deno.js', + worker: './worker.js', + development: './development.js', + test: './test.js', + worklet: './worklet.js', + bun: './bun.js', + macro: './macro.js', + production: './production.js', + }, + expect: { + custom: './custom.js', + vite: './vite.js', + rollup: './rollup.js', + webpack: './webpack.js', + bun: './bun.js', + macro: './macro.js', + deno: './deno.js', + 'react-server': './react-server.js', + types: './types/index.d.ts', + script: './script.js', + esmodules: './esmodules.js', + module: './module.js', + import: './import.js', + style: './style.css', + stylus: './style.styl', + sass: './style.sass', + asset: './asset.png', + browser: './browser.js', + electron: './electron.js', + node: './node.js', + 'react-native': './react-native.js', + worker: './worker.js', + worklet: './worklet.js', + development: './development.js', + test: './test.js', + production: './production.js', + default: './index.js', + }, +}) From f230fc10623162642ecc8fca8f3c14f490ea1b71 Mon Sep 17 00:00:00 2001 From: George Date: Thu, 27 Feb 2025 15:53:07 -0500 Subject: [PATCH 02/16] Update index.js Co-authored-by: Keith Cirkel --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index 4dd767c..650eddd 100755 --- a/index.js +++ b/index.js @@ -310,7 +310,6 @@ const sortConditionObject = (conditionObject) => { * so it must come before other target environments * and reference syntaxes */ - // bun 'bun', 'macro', From 2a24a3fc6b4279460e5c55a7d7068e263f3e3e7f Mon Sep 17 00:00:00 2001 From: George Taveras Date: Thu, 27 Feb 2025 15:55:26 -0500 Subject: [PATCH 03/16] fix: avoid adding key that does not exist --- index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 650eddd..44199c8 100755 --- a/index.js +++ b/index.js @@ -294,10 +294,13 @@ const relativeOrderSort = (list, order) => { }) } -const withLastKey = (keyName, { [keyName]: keyValue, ...rest }) => ({ - ...rest, - [keyName]: keyValue, -}) +const withLastKey = (keyName, { [keyName]: keyValue, ...rest }) => + typeof keyValue !== 'undefined' + ? { + ...rest, + [keyName]: keyValue, + } + : rest const sortConditionObject = (conditionObject) => { const bundler = [ From 7e8e002d3fe47effe0b4d6d2abae037256c68d6b Mon Sep 17 00:00:00 2001 From: George Taveras Date: Thu, 27 Feb 2025 20:54:25 -0500 Subject: [PATCH 04/16] fix: use older syntax for older node versions --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 44199c8..52cd66f 100755 --- a/index.js +++ b/index.js @@ -416,8 +416,8 @@ const sortExportsOrImports = onObject((exportOrImports) => { for (const key of keys) { const keyIsPathLike = key.startsWith('.') || key.startsWith('#') - isConditionObject &&= !keyIsPathLike - isPathLikeObject &&= keyIsPathLike + isConditionObject = isConditionObject && !keyIsPathLike + isPathLikeObject = isPathLikeObject && keyIsPathLike } if (isConditionObject) { From 1e25a6ece3a96973c3711093c0d0b6833530da28 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Thu, 27 Feb 2025 22:47:52 -0500 Subject: [PATCH 05/16] fix: ordering of conditions 1. types before target environments 2. environments first 3. support module-sync --- index.js | 49 +++++++++++++++++++++++++++++++++---------------- tests/fields.js | 24 ++++++++++++++---------- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index 52cd66f..92347f1 100755 --- a/index.js +++ b/index.js @@ -303,7 +303,7 @@ const withLastKey = (keyName, { [keyName]: keyValue, ...rest }) => : rest const sortConditionObject = (conditionObject) => { - const bundler = [ + const bundlerConditions = [ 'vite', 'rollup', 'webpack', @@ -319,20 +319,21 @@ const sortConditionObject = (conditionObject) => { /** * Deno is a target environment and a bundler * so it must come before other target environments - * and reference syntaxes + * and reference syntaxes. + * + * It also must come before typescript conditions since deno + * can perform its own type checking (without using tsc) using + * this condition, and it will require different types if using + * deno specific APIs. */ 'deno', ] - const implementationVariants = ['react-server'] + const typescriptConditions = ['types'] - const referenceSyntax = [ - /** - * 'types' condition must come before `import` or `require`, as typescript - * will use those if encountered first. - */ - 'types', + const implementationVariantConditions = ['react-server'] + const referenceSyntaxConditions = [ /** * 'script' condition must come before 'module' condition, as 'script' * may also be used by bundlers but in more specific conditions than @@ -346,6 +347,7 @@ const sortConditionObject = (conditionObject) => { * used by bundlers and leverage other bundler features */ 'module', + 'module-sync', 'import', 'require', 'style', @@ -354,7 +356,7 @@ const sortConditionObject = (conditionObject) => { 'asset', ] - const targetEnvironment = [ + const targetEnvironmentConditions = [ 'browser', 'electron', 'node', @@ -363,25 +365,40 @@ const sortConditionObject = (conditionObject) => { 'worklet', ] - const environment = ['development', 'test', 'production'] + const environmentConditions = ['test', 'development', 'production'] const order = relativeOrderSort(Object.keys(conditionObject), [ + /** + * Environment conditions at the top as they are generally used to override + * default behavior based on the environment + */ + ...environmentConditions, /** * Bundler conditions are generally more important than other conditions * because they leverage code that will not work outside of the * bundler environment */ - ...bundler, + ...bundlerConditions, + /** + * Typescript conditions must come before + * - 'import' + * - 'require' + * - 'node' + * - 'browser' + * - 'deno' + * + * and any other environment condition that can be targeted by typescript + */ + ...typescriptConditions, /** * Implementation variants need to be placed before "reference syntax" and * "target environments" because similar to "bundler" conditions, * they only work in specific environments and may expose code overrides * for any of the conditions in "reference syntax" and "target environments" */ - ...implementationVariants, - ...referenceSyntax, - ...targetEnvironment, - ...environment, + ...implementationVariantConditions, + ...targetEnvironmentConditions, + ...referenceSyntaxConditions, ]) return withLastKey('default', sortObjectKeys(conditionObject, order)) } diff --git a/tests/fields.js b/tests/fields.js index 92a3263..6dc86e5 100644 --- a/tests/fields.js +++ b/tests/fields.js @@ -390,38 +390,42 @@ test('exports conditions', macro.sortObject, { worker: './worker.js', development: './development.js', test: './test.js', + require: './require.js', worklet: './worklet.js', bun: './bun.js', macro: './macro.js', + 'module-sync': './module-sync.js', production: './production.js', }, expect: { custom: './custom.js', + test: './test.js', + development: './development.js', + production: './production.js', vite: './vite.js', rollup: './rollup.js', webpack: './webpack.js', bun: './bun.js', macro: './macro.js', deno: './deno.js', - 'react-server': './react-server.js', types: './types/index.d.ts', + 'react-server': './react-server.js', + browser: './browser.js', + electron: './electron.js', + node: './node.js', + 'react-native': './react-native.js', + worker: './worker.js', + worklet: './worklet.js', script: './script.js', esmodules: './esmodules.js', module: './module.js', + 'module-sync': './module-sync.js', import: './import.js', + require: './require.js', style: './style.css', stylus: './style.styl', sass: './style.sass', asset: './asset.png', - browser: './browser.js', - electron: './electron.js', - node: './node.js', - 'react-native': './react-native.js', - worker: './worker.js', - worklet: './worklet.js', - development: './development.js', - test: './test.js', - production: './production.js', default: './index.js', }, }) From 3d5adf788be4dc21890e61580085cb0addcd29c4 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Thu, 27 Feb 2025 22:48:22 -0500 Subject: [PATCH 06/16] chore: add test-watch script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7b89ad3..3656180 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "semantic-release": "semantic-release", "test": "ava && dtslint --localTs node_modules/typescript/lib", "test-coverage": "nyc ava", + "test-watch": "ava --watch", "update-snapshots": "ava --update-snapshots" }, "commitlint": { From 5b8c8820f95d7ea12a75d21aa8f0e77131999afd Mon Sep 17 00:00:00 2001 From: George Taveras Date: Thu, 27 Feb 2025 23:05:59 -0500 Subject: [PATCH 07/16] feat: support node-addons Additionally fix order of `module-sync` which goes after `require` and `import` --- index.js | 3 ++- tests/fields.js | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 92347f1..045fa06 100755 --- a/index.js +++ b/index.js @@ -347,9 +347,9 @@ const sortConditionObject = (conditionObject) => { * used by bundlers and leverage other bundler features */ 'module', - 'module-sync', 'import', 'require', + 'module-sync', 'style', 'stylus', 'sass', @@ -359,6 +359,7 @@ const sortConditionObject = (conditionObject) => { const targetEnvironmentConditions = [ 'browser', 'electron', + 'node-addons', 'node', 'react-native', 'worker', diff --git a/tests/fields.js b/tests/fields.js index 6dc86e5..713a571 100644 --- a/tests/fields.js +++ b/tests/fields.js @@ -388,6 +388,7 @@ test('exports conditions', macro.sortObject, { electron: './electron.js', deno: './deno.js', worker: './worker.js', + 'node-addons': './node-addons.js', development: './development.js', test: './test.js', require: './require.js', @@ -412,6 +413,7 @@ test('exports conditions', macro.sortObject, { 'react-server': './react-server.js', browser: './browser.js', electron: './electron.js', + 'node-addons': './node-addons.js', node: './node.js', 'react-native': './react-native.js', worker: './worker.js', @@ -419,9 +421,9 @@ test('exports conditions', macro.sortObject, { script: './script.js', esmodules: './esmodules.js', module: './module.js', - 'module-sync': './module-sync.js', import: './import.js', require: './require.js', + 'module-sync': './module-sync.js', style: './style.css', stylus: './style.styl', sass: './style.sass', From a054fa368c1718aad0bc8fc23d980e74a5bee895 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Thu, 27 Feb 2025 23:08:33 -0500 Subject: [PATCH 08/16] fix: wrong comment --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index 045fa06..30c0d78 100755 --- a/index.js +++ b/index.js @@ -386,7 +386,6 @@ const sortConditionObject = (conditionObject) => { * - 'require' * - 'node' * - 'browser' - * - 'deno' * * and any other environment condition that can be targeted by typescript */ From bb91e8123f153f4504010bd58c23dee09ea4d012 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Thu, 27 Feb 2025 23:20:54 -0500 Subject: [PATCH 09/16] fix: `macro` condition is more specific than `bun` --- index.js | 2 +- tests/fields.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 30c0d78..308dac6 100755 --- a/index.js +++ b/index.js @@ -313,8 +313,8 @@ const sortConditionObject = (conditionObject) => { * so it must come before other target environments * and reference syntaxes */ - 'bun', 'macro', + 'bun', /** * Deno is a target environment and a bundler diff --git a/tests/fields.js b/tests/fields.js index 713a571..1bf5155 100644 --- a/tests/fields.js +++ b/tests/fields.js @@ -390,10 +390,10 @@ test('exports conditions', macro.sortObject, { worker: './worker.js', 'node-addons': './node-addons.js', development: './development.js', + bun: './bun.js', test: './test.js', require: './require.js', worklet: './worklet.js', - bun: './bun.js', macro: './macro.js', 'module-sync': './module-sync.js', production: './production.js', @@ -406,8 +406,8 @@ test('exports conditions', macro.sortObject, { vite: './vite.js', rollup: './rollup.js', webpack: './webpack.js', - bun: './bun.js', macro: './macro.js', + bun: './bun.js', deno: './deno.js', types: './types/index.d.ts', 'react-server': './react-server.js', From c543aad03a222538be0c2984b1c2fc612a7d0748 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Sat, 1 Mar 2025 01:14:47 -0500 Subject: [PATCH 10/16] fix: relativeOrderSort should leave unspecified items in place --- index.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 308dac6..7ab5be4 100755 --- a/index.js +++ b/index.js @@ -278,16 +278,16 @@ const sortScripts = onObject((scripts, packageJson) => { * accommodate a key in the `order` */ const relativeOrderSort = (list, order) => { - const orderMap = new Map(order.map((key, index) => [key, index])) - let closestIndex = 0 - for (const item of list) { - if (orderMap.has(item)) { - closestIndex = orderMap.get(item) - } else { - orderMap.set(item, closestIndex) - } - } + const orderMap = new Map( + order.map((key, index) => { + return [key, index] + }), + ) + return list.sort((a, b) => { + if (!orderMap.has(a) && !orderMap.has(b)) { + return 0 + } const aIndex = orderMap.get(a) const bIndex = orderMap.get(b) return aIndex - bIndex From ce0093e9d976aba14274abb8c6ebc897224e9979 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Sat, 1 Mar 2025 01:27:18 -0500 Subject: [PATCH 11/16] feat: capture more standard conditions Adds known edge conditions and addition compilation targets from the WinterCG standard (Called out in the Node.js community conditions documentation. - https://nodejs.org/api/packages.html#community-conditions-definitions --- index.js | 89 +++++++++++++++++++++++++++---------------------- tests/fields.js | 36 +++++++++++++++----- 2 files changed, 78 insertions(+), 47 deletions(-) diff --git a/index.js b/index.js index 7ab5be4..f16bcc0 100755 --- a/index.js +++ b/index.js @@ -303,37 +303,35 @@ const withLastKey = (keyName, { [keyName]: keyValue, ...rest }) => : rest const sortConditionObject = (conditionObject) => { - const bundlerConditions = [ - 'vite', - 'rollup', - 'webpack', - - /** - * Bun is a target environment and a bundler - * so it must come before other target environments - * and reference syntaxes - */ - 'macro', - 'bun', - - /** - * Deno is a target environment and a bundler - * so it must come before other target environments - * and reference syntaxes. - * - * It also must come before typescript conditions since deno - * can perform its own type checking (without using tsc) using - * this condition, and it will require different types if using - * deno specific APIs. - */ - 'deno', - ] + /** + * Sources: + * - WinterCG maintained list of standard runtime keys: https://runtime-keys.proposal.wintercg.org + * - Node.js conditional exports: https://nodejs.org/api/packages.html#conditional-exports + * - Webpack conditions: https://webpack.js.org/guides/package-exports/#conditions + * - Bun condition: https://bun.sh/docs/runtime/modules#importing-packages + * - Bun macro condition: https://bun.sh/docs/bundler/macros#export-condition-macro + */ + const bundlerConditions = ['vite', 'rollup', 'webpack'] const typescriptConditions = ['types'] - const implementationVariantConditions = ['react-server'] + const serverVariantConditions = ['react-server'] + const edgeConditions = [ + 'azion', + 'edge-routine', + 'fastly', + 'edge-light', + 'lagon', + 'netlify', + 'wasmer', + 'workerd', + ] const referenceSyntaxConditions = [ + 'asset', + 'sass', + 'stylus', + 'style', /** * 'script' condition must come before 'module' condition, as 'script' * may also be used by bundlers but in more specific conditions than @@ -348,19 +346,27 @@ const sortConditionObject = (conditionObject) => { */ 'module', 'import', - 'require', + /** + * `module-sync` condition must come before `require` condition and after + * `import`. + */ 'module-sync', - 'style', - 'stylus', - 'sass', - 'asset', + 'require', ] const targetEnvironmentConditions = [ 'browser', + /** + * bun macro condition must come before 'bun' + */ + 'macro', + 'bun', + 'deno', 'electron', + 'kiesel', // https://runtime-keys.proposal.wintercg.org/#kiesel 'node-addons', 'node', + 'moddable', // https://runtime-keys.proposal.wintercg.org/#moddable 'react-native', 'worker', 'worklet', @@ -374,12 +380,6 @@ const sortConditionObject = (conditionObject) => { * default behavior based on the environment */ ...environmentConditions, - /** - * Bundler conditions are generally more important than other conditions - * because they leverage code that will not work outside of the - * bundler environment - */ - ...bundlerConditions, /** * Typescript conditions must come before * - 'import' @@ -391,12 +391,23 @@ const sortConditionObject = (conditionObject) => { */ ...typescriptConditions, /** - * Implementation variants need to be placed before "reference syntax" and + * Bundler conditions are generally more important than other conditions + * because they leverage code that will not work outside of the + * bundler environment + */ + ...bundlerConditions, + /** + * Edge runtimes are often variants of other target environments, so they must come + * before the target environment conditions + */ + ...edgeConditions, + /** + * Server variants need to be placed before "reference syntax" and * "target environments" because similar to "bundler" conditions, * they only work in specific environments and may expose code overrides * for any of the conditions in "reference syntax" and "target environments" */ - ...implementationVariantConditions, + ...serverVariantConditions, ...targetEnvironmentConditions, ...referenceSyntaxConditions, ]) diff --git a/tests/fields.js b/tests/fields.js index 1bf5155..073f90e 100644 --- a/tests/fields.js +++ b/tests/fields.js @@ -369,33 +369,43 @@ test('exports conditions', macro.sortObject, { value: { custom: './custom.js', module: './module.js', + lagon: './lagon.js', vite: './vite.js', rollup: './rollup.js', + wasmer: './wasmer.js', webpack: './webpack.js', import: './import.js', types: './types/index.d.ts', script: './script.js', node: './node.js', + 'edge-light': './edge-light.js', + netlify: './netlify.js', 'react-native': './react-native.js', stylus: './style.styl', sass: './style.sass', esmodules: './esmodules.js', default: './index.js', + azion: './azion.js', style: './style.css', asset: './asset.png', 'react-server': './react-server.js', browser: './browser.js', + workerd: './workerd.js', electron: './electron.js', deno: './deno.js', + fastly: './fastly.js', worker: './worker.js', 'node-addons': './node-addons.js', development: './development.js', bun: './bun.js', test: './test.js', require: './require.js', + 'edge-routine': './edge-routine.js', worklet: './worklet.js', + moddable: './moddable.js', macro: './macro.js', 'module-sync': './module-sync.js', + kiesel: './keisel.js', production: './production.js', }, expect: { @@ -403,31 +413,41 @@ test('exports conditions', macro.sortObject, { test: './test.js', development: './development.js', production: './production.js', + types: './types/index.d.ts', vite: './vite.js', rollup: './rollup.js', webpack: './webpack.js', + azion: './azion.js', + 'edge-routine': './edge-routine.js', + fastly: './fastly.js', + 'edge-light': './edge-light.js', + lagon: './lagon.js', + netlify: './netlify.js', + wasmer: './wasmer.js', + workerd: './workerd.js', + 'react-server': './react-server.js', + browser: './browser.js', macro: './macro.js', bun: './bun.js', deno: './deno.js', - types: './types/index.d.ts', - 'react-server': './react-server.js', - browser: './browser.js', electron: './electron.js', + kiesel: './keisel.js', 'node-addons': './node-addons.js', node: './node.js', + moddable: './moddable.js', 'react-native': './react-native.js', worker: './worker.js', worklet: './worklet.js', + asset: './asset.png', + sass: './style.sass', + stylus: './style.styl', + style: './style.css', script: './script.js', esmodules: './esmodules.js', module: './module.js', import: './import.js', - require: './require.js', 'module-sync': './module-sync.js', - style: './style.css', - stylus: './style.styl', - sass: './style.sass', - asset: './asset.png', + require: './require.js', default: './index.js', }, }) From f1849195dd2a5ce3d8fc072bd4a1ff2dc9fe5919 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Sat, 1 Mar 2025 01:30:45 -0500 Subject: [PATCH 12/16] chore: improve edge condition sorting Apply some alphabetical order --- index.js | 2 +- tests/fields.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index f16bcc0..94a1e37 100755 --- a/index.js +++ b/index.js @@ -318,9 +318,9 @@ const sortConditionObject = (conditionObject) => { const serverVariantConditions = ['react-server'] const edgeConditions = [ 'azion', + 'edge-light', 'edge-routine', 'fastly', - 'edge-light', 'lagon', 'netlify', 'wasmer', diff --git a/tests/fields.js b/tests/fields.js index 073f90e..122d82b 100644 --- a/tests/fields.js +++ b/tests/fields.js @@ -418,9 +418,9 @@ test('exports conditions', macro.sortObject, { rollup: './rollup.js', webpack: './webpack.js', azion: './azion.js', + 'edge-light': './edge-light.js', 'edge-routine': './edge-routine.js', fastly: './fastly.js', - 'edge-light': './edge-light.js', lagon: './lagon.js', netlify: './netlify.js', wasmer: './wasmer.js', From f9f4320b6f92b79b957e70579186c23a7385a2d4 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Sat, 1 Mar 2025 01:38:54 -0500 Subject: [PATCH 13/16] chore: improve comments --- index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 94a1e37..1ae1de1 100755 --- a/index.js +++ b/index.js @@ -397,15 +397,15 @@ const sortConditionObject = (conditionObject) => { */ ...bundlerConditions, /** - * Edge runtimes are often variants of other target environments, so they must come + * Edge run-times are often variants of other target environments, so they must come * before the target environment conditions */ ...edgeConditions, /** - * Server variants need to be placed before "reference syntax" and - * "target environments" because similar to "bundler" conditions, - * they only work in specific environments and may expose code overrides - * for any of the conditions in "reference syntax" and "target environments" + * Server variants need to be placed before `referenceSyntaxConditions` and + * `targetEnvironmentConditions` since they may use multiple syntaxes and target + * environments. They should also go after `edgeConditions` + * to allow custom implementations per edge runtime. */ ...serverVariantConditions, ...targetEnvironmentConditions, From de9940a6904d6b79afaf5d22bb16b8789b296e0d Mon Sep 17 00:00:00 2001 From: George Taveras Date: Sat, 1 Mar 2025 12:10:13 -0500 Subject: [PATCH 14/16] feat: svelte and reorder - move types to top - move browser under deno and bun since those can be used to bundle app for browsers --- index.js | 28 ++++++++++++++-------------- tests/fields.js | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 1ae1de1..f1326ff 100755 --- a/index.js +++ b/index.js @@ -302,6 +302,14 @@ const withLastKey = (keyName, { [keyName]: keyValue, ...rest }) => } : rest +const withFirstKey = (keyName, { [keyName]: keyValue, ...rest }) => + typeof keyValue !== 'undefined' + ? { + [keyName]: keyValue, + ...rest, + } + : rest + const sortConditionObject = (conditionObject) => { /** * Sources: @@ -313,8 +321,6 @@ const sortConditionObject = (conditionObject) => { */ const bundlerConditions = ['vite', 'rollup', 'webpack'] - const typescriptConditions = ['types'] - const serverVariantConditions = ['react-server'] const edgeConditions = [ 'azion', @@ -328,6 +334,7 @@ const sortConditionObject = (conditionObject) => { ] const referenceSyntaxConditions = [ + 'svelte', 'asset', 'sass', 'stylus', @@ -355,13 +362,13 @@ const sortConditionObject = (conditionObject) => { ] const targetEnvironmentConditions = [ - 'browser', /** * bun macro condition must come before 'bun' */ 'macro', 'bun', 'deno', + 'browser', 'electron', 'kiesel', // https://runtime-keys.proposal.wintercg.org/#kiesel 'node-addons', @@ -380,16 +387,6 @@ const sortConditionObject = (conditionObject) => { * default behavior based on the environment */ ...environmentConditions, - /** - * Typescript conditions must come before - * - 'import' - * - 'require' - * - 'node' - * - 'browser' - * - * and any other environment condition that can be targeted by typescript - */ - ...typescriptConditions, /** * Bundler conditions are generally more important than other conditions * because they leverage code that will not work outside of the @@ -411,7 +408,10 @@ const sortConditionObject = (conditionObject) => { ...targetEnvironmentConditions, ...referenceSyntaxConditions, ]) - return withLastKey('default', sortObjectKeys(conditionObject, order)) + return withFirstKey( + 'types', + withLastKey('default', sortObjectKeys(conditionObject, order)), + ) } const sortPathLikeObjectWithWildcards = onObject((object) => { diff --git a/tests/fields.js b/tests/fields.js index 122d82b..bb553dc 100644 --- a/tests/fields.js +++ b/tests/fields.js @@ -409,11 +409,11 @@ test('exports conditions', macro.sortObject, { production: './production.js', }, expect: { + types: './types/index.d.ts', custom: './custom.js', test: './test.js', development: './development.js', production: './production.js', - types: './types/index.d.ts', vite: './vite.js', rollup: './rollup.js', webpack: './webpack.js', @@ -426,10 +426,10 @@ test('exports conditions', macro.sortObject, { wasmer: './wasmer.js', workerd: './workerd.js', 'react-server': './react-server.js', - browser: './browser.js', macro: './macro.js', bun: './bun.js', deno: './deno.js', + browser: './browser.js', electron: './electron.js', kiesel: './keisel.js', 'node-addons': './node-addons.js', From d07bd01b88497a04870bf31737962c6e2bde2faa Mon Sep 17 00:00:00 2001 From: George Taveras Date: Sat, 1 Mar 2025 12:43:26 -0500 Subject: [PATCH 15/16] fix: ensure custom conditions are not touched --- index.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index f1326ff..da50023 100755 --- a/index.js +++ b/index.js @@ -283,15 +283,24 @@ const relativeOrderSort = (list, order) => { return [key, index] }), ) - - return list.sort((a, b) => { - if (!orderMap.has(a) && !orderMap.has(b)) { - return 0 + const indexes = list.flatMap((item, i) => { + if (orderMap.has(item)) { + return i } - const aIndex = orderMap.get(a) - const bIndex = orderMap.get(b) + return [] + }) + const sortedIndexes = indexes.toSorted((a, b) => { + const aIndex = orderMap.get(list[a]) + const bIndex = orderMap.get(list[b]) return aIndex - bIndex }) + + const copy = [...list] + sortedIndexes.forEach((desiredIndex, thisIndex) => { + copy[indexes[thisIndex]] = list[desiredIndex] + }) + + return copy } const withLastKey = (keyName, { [keyName]: keyValue, ...rest }) => From 26c91c758b44a95437ce4b6e391681f391040213 Mon Sep 17 00:00:00 2001 From: George Taveras Date: Sat, 1 Mar 2025 14:10:25 -0500 Subject: [PATCH 16/16] fix: node >=12 <=18 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index da50023..5ac3fbe 100755 --- a/index.js +++ b/index.js @@ -289,7 +289,7 @@ const relativeOrderSort = (list, order) => { } return [] }) - const sortedIndexes = indexes.toSorted((a, b) => { + const sortedIndexes = [...indexes].sort((a, b) => { const aIndex = orderMap.get(list[a]) const bIndex = orderMap.get(list[b]) return aIndex - bIndex