From 3c04fed89d4b5004ac8713425f46c0710167fd4e Mon Sep 17 00:00:00 2001 From: Andreas Schultz Date: Wed, 26 Aug 2026 10:45:24 +0200 Subject: [PATCH 01/10] Webpack 5 related changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace Storybook’s obsolete SCSS preset with an explicit webpack 5 loader configuration. - Replace url-loader with webpack 5 Asset Modules for inline assets. - Upgrade Sass tooling and declare css-loader, style-loader, and webpack directly. - Preserve standalone builds by satisfying style-loader’s webpack peer dependency. - Remove obsolete Jest PnP resolution. - Resolve Sass imports correctly with hoisted workspace dependencies. --- .storybook/main.js | 39 +++--- package.json | 12 +- scripts/compile-sass.ts | 9 +- scripts/sassDeprecationConfig.js | 4 +- yarn.lock | 219 ++++++++++++++++++++++++------- 5 files changed, 209 insertions(+), 74 deletions(-) diff --git a/.storybook/main.js b/.storybook/main.js index cbf56f51..eaacced1 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,5 +1,4 @@ const sass = require("sass"); -const path = require("path"); const sassRenderSyncConfig = require("./../scripts/sassConfig"); const { silenceDeprecations } = require("../scripts/sassDeprecationConfig"); @@ -9,15 +8,6 @@ module.exports = { "@storybook/addon-links", "@storybook/addon-docs", "@storybook/addon-a11y", - { - name: "@storybook/preset-scss", - options: { - sassLoaderOptions: { - implementation: sass, - sassOptions: {...sassRenderSyncConfig, silenceDeprecations}, - }, - }, - }, "@storybook/addon-webpack5-compiler-babel", ], framework: { @@ -72,13 +62,29 @@ module.exports = { config.devtool = false; } config.module.rules = [ + { + test: /\.s[ac]ss$/i, + use: [ + require.resolve("style-loader"), + { + loader: require.resolve("css-loader"), + options: { + importLoaders: 1, + }, + }, + { + loader: require.resolve("sass-loader"), + options: { + implementation: sass, + sassOptions: { ...sassRenderSyncConfig, silenceDeprecations }, + }, + }, + ], + }, { test: /\.(png|jpg|gif|svg)(\\?.*)?$/, include: /\.tobase64\./, - loader: "url-loader", - options: { - limit: true, - }, + type: "asset/inline", }, ...config.module.rules.map((rule) => { if ( @@ -92,11 +98,6 @@ module.exports = { return rule; }), ]; - config.resolve.alias = { - ...config.resolve.alias, - // Fix nasty bug with importing from this package, Storybook webpack cannot resolve it otherwise - "@codemirror/legacy-modes": path.resolve(__dirname, "../node_modules/@codemirror/legacy-modes"), - }; return config; }, }; diff --git a/package.json b/package.json index 2e116920..23b19553 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "registry": "https://registry.npmjs.org" }, "engines": { - "node": ">=18.18.0" + "node": ">=22.11.0" }, "style": "src/index.scss", "main": "dist/cjs/index.js", @@ -83,6 +83,7 @@ "@codemirror/legacy-modes": "^6.5.2", "@mavrin/remark-typograf": "^2.2.0", "@xyflow/react": "^12.9.3", + "assert": "^2.1.0", "classnames": "^2.5.1", "codemirror": "^6.0.2", "color": "^5.0.3", @@ -125,7 +126,6 @@ "@storybook/addon-links": "^10.4.0", "@storybook/addon-webpack5-compiler-babel": "^4.0.1", "@storybook/cli": "^10.4.0", - "@storybook/preset-scss": "^1.0.3", "@storybook/react": "^10.4.0", "@storybook/react-webpack5": "^10.4.0", "@testing-library/dom": "^10.4.1", @@ -143,6 +143,7 @@ "@typescript-eslint/parser": "^8.59.3", "babel-jest": "^30.4.1", "chromatic": "^16.10.1", + "css-loader": "^7.1.4", "eslint": "^10.3.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.1.1", @@ -152,7 +153,6 @@ "jest": "^30.4.2", "jest-environment-jsdom": "^30.4.1", "jest-fixed-jsdom": "^0.0.11", - "jest-pnp-resolver": "^1.2.3", "lint-staged": "^15.5.2", "node-sass-package-importer": "^5.3.3", "path": "^0.12.7", @@ -162,8 +162,9 @@ "regenerator-runtime": "^0.14.1", "rimraf": "^6.1.3", "sass": "^1.99.0", - "sass-loader": "^16.0.8", + "sass-loader": "^17.0.0", "storybook": "^10.4.0", + "style-loader": "^4.0.0", "stylelint": "^17.11.0", "stylelint-config-recess-order": "^7.7.0", "stylelint-config-standard-scss": "^17.0.0", @@ -171,7 +172,7 @@ "ts-node": "^10.9.2", "tsc-esm-fix": "^3.1.2", "typescript": "^5.9.3", - "url-loader": "^4.1.1", + "webpack": "^5.109.2", "yargs": "^18.0.0" }, "peerDependencies": { @@ -216,7 +217,6 @@ "src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts" ], - "resolver": "jest-pnp-resolver", "setupFiles": [ "react-app-polyfill/jsdom" ], diff --git a/scripts/compile-sass.ts b/scripts/compile-sass.ts index f3288bf6..08979d80 100644 --- a/scripts/compile-sass.ts +++ b/scripts/compile-sass.ts @@ -1,18 +1,21 @@ import tildeImporter from "node-sass-package-importer"; +import * as path from "path"; import * as sass from "sass"; import yargs from "yargs"; import sassRenderSyncConfig from "./sassConfig"; -import { silenceDeprecations } from "./sassDeprecationConfig" +import { silenceDeprecations } from "./sassDeprecationConfig"; const args = yargs(process.argv.slice(2)).argv as any; const styles = sass.renderSync({ importer: tildeImporter(), ...sassRenderSyncConfig, - silenceDeprecations: silenceDeprecations as sass.DeprecationOrId[], + silenceDeprecations: [...silenceDeprecations, "legacy-js-api"] as sass.DeprecationOrId[], file: "src/index.scss", - includePaths: ["node_modules"], // Carbon does not use tilde import syntax + // Resolve non-tilde imports from the effective workspace dependency root, including when + // dependencies are hoisted into a parent workspace. + includePaths: [path.resolve(path.dirname(require.resolve("@blueprintjs/core/package.json")), "../..")], }); if (args.outputCss) { diff --git a/scripts/sassDeprecationConfig.js b/scripts/sassDeprecationConfig.js index 888046ce..a286bfc3 100644 --- a/scripts/sassDeprecationConfig.js +++ b/scripts/sassDeprecationConfig.js @@ -1,3 +1,3 @@ module.exports = { - silenceDeprecations: ["import", "legacy-js-api"], -} + silenceDeprecations: ["import"], +}; diff --git a/yarn.lock b/yarn.lock index 9d1e1a2a..26814d8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3479,11 +3479,6 @@ tsconfig-paths "^4.2.0" webpack "5" -"@storybook/preset-scss@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@storybook/preset-scss/-/preset-scss-1.0.3.tgz#8ac834545c642dada0f64f510ef08dfb882e9737" - integrity sha512-o9Iz6wxPeNENrQa2mKlsDKynBfqU2uWaRP80HeWp4TkGgf7/x3DVF2O7yi9N0x/PI1qzzTTpxlQ90D62XmpiTw== - "@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0": version "1.0.6--canary.9.0c3f3b7.0" resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.6--canary.9.0c3f3b7.0.tgz#7f10f3c641f32e4513a8b6ffb5036933e7059534" @@ -4610,6 +4605,17 @@ asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +assert@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd" + integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw== + dependencies: + call-bind "^1.0.2" + is-nan "^1.3.2" + object-is "^1.1.5" + object.assign "^4.1.4" + util "^0.12.5" + assertion-error@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" @@ -4760,11 +4766,6 @@ baseline-browser-mapping@^2.10.12: resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz#47bdc13027af28d341f367a4f35a07ce872e27b4" integrity sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ== -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -4852,6 +4853,16 @@ call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply- es-errors "^1.3.0" function-bind "^1.1.2" +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.9.tgz#39a644700c80bc7d0ca9102fc6d1d43b2fd7eee7" + integrity sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + get-intrinsic "^1.3.0" + set-function-length "^1.2.2" + call-bind@^1.0.7, call-bind@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" @@ -5386,7 +5397,7 @@ css-functions-list@^3.3.3: resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.3.3.tgz#c4ab5008659de2e3baf3752c8fdef7662f3ffe23" integrity sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg== -css-loader@^7.1.2: +css-loader@^7.1.2, css-loader@^7.1.4: version "7.1.4" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.4.tgz#8f6bf9f8fc8cbef7d2ef6e80acc6545eaefa90b1" integrity sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw== @@ -5875,11 +5886,6 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - endent@^2.0.1: version "2.1.0" resolved "https://registry.yarnpkg.com/endent/-/endent-2.1.0.tgz#5aaba698fb569e5e18e69e1ff7a28ff35373cd88" @@ -5897,6 +5903,14 @@ enhanced-resolve@^5.17.1: graceful-fs "^4.2.4" tapable "^2.2.0" +enhanced-resolve@^5.24.4: + version "5.24.5" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz#b4dad3255b7545f07ba5535189868e9f85f47573" + integrity sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.3.3" + entities@1.0: version "1.0.0" resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" @@ -6023,6 +6037,11 @@ es-module-lexer@^1.2.1, es-module-lexer@^1.5.0: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== +es-module-lexer@^2.1.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.3.2.tgz#311fa4f40168c1975c505477c51b23234d41ad55" + integrity sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw== + es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" @@ -6655,6 +6674,11 @@ functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +generator-function@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -7393,7 +7417,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@~2.0.1: +inherits@2, inherits@^2.0.3, inherits@~2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -7447,6 +7471,14 @@ is-alphanumerical@^2.0.0: is-alphabetical "^2.0.0" is-decimal "^2.0.0" +is-arguments@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b" + integrity sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" @@ -7575,6 +7607,17 @@ is-generator-function@^1.0.10: has-tostringtag "^1.0.2" safe-regex-test "^1.1.0" +is-generator-function@^1.0.7: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== + dependencies: + call-bound "^1.0.4" + generator-function "^2.0.0" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -7606,6 +7649,14 @@ is-map@^2.0.3: resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== +is-nan@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-number-object@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" @@ -7695,7 +7746,7 @@ is-symbol@^1.0.4, is-symbol@^1.1.1: has-symbols "^1.1.0" safe-regex-test "^1.1.0" -is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15, is-typed-array@^1.1.3: version "1.1.15" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== @@ -8391,7 +8442,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.1.2, json5@^2.2.2, json5@^2.2.3: +json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -8495,15 +8546,6 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== -loader-utils@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -9208,6 +9250,11 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + mime-types@^2.1.27, mime-types@^2.1.31: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" @@ -9247,6 +9294,16 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minimizer-webpack-plugin@^5.6.1: + version "5.6.1" + resolved "https://registry.yarnpkg.com/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz#289922a4c96c4ed1ddb76b8a00bd8074e89a2f7f" + integrity sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + jest-worker "^27.4.5" + schema-utils "^4.3.0" + terser "^5.31.1" + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" @@ -9418,6 +9475,14 @@ object-inspect@^1.13.3: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-is@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -10710,12 +10775,10 @@ safe-regex-test@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-loader@^16.0.8: - version "16.0.8" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-16.0.8.tgz#b7eef3b7947b32e2988dd760a2f7f0d547838e6b" - integrity sha512-hcov4ZwZJIGbEuyNr9EmiTmZueyrxSToE6GOzoZnq5JM7ecRO7ttyvilPn+VmRsqiP16+VYZzVnGZj/hzZgKBA== - dependencies: - neo-async "^2.6.2" +sass-loader@^17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-17.0.0.tgz#8f4f8864d834c1abb18f0f3d2bc653bbf29dde2a" + integrity sha512-0Ybm8ohBQ9LcrycVrFQp/KQBNX5a3Wda9/smS0mE/xLffzEnwvV8nykOzrbiSWNzTE3IB/jiXx8O4QmDPG2+Gw== sass@^1.99.0: version "1.99.0" @@ -10747,7 +10810,7 @@ scheduler@^0.27.0: resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== -schema-utils@^3.0.0, schema-utils@^3.1.1: +schema-utils@^3.1.1: version "3.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== @@ -10766,6 +10829,16 @@ schema-utils@^4.0.0, schema-utils@^4.3.0, schema-utils@^4.3.2: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" +schema-utils@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" + integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -11452,6 +11525,11 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tapable@^2.3.0, tapable@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160" + integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A== + temp@^0.8.4: version "0.8.4" resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2" @@ -11952,15 +12030,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -url-loader@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" - integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== - dependencies: - loader-utils "^2.0.0" - mime-types "^2.1.27" - schema-utils "^3.0.0" - use-sync-external-store@^1.2.0, use-sync-external-store@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" @@ -11983,6 +12052,17 @@ util@^0.10.3: dependencies: inherits "2.0.3" +util@^0.12.5: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -12060,6 +12140,13 @@ watchpack@^2.4.1: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +watchpack@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.2.tgz#e12e82d84674266fc1c6dbfe38891b92ff0522ec" + integrity sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg== + dependencies: + graceful-fs "^4.1.2" + web-namespaces@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" @@ -12095,6 +12182,11 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== +webpack-sources@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.5.1.tgz#76c2418486dcc02b2aa0694c104176c2858fe84a" + integrity sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw== + webpack-virtual-modules@^0.6.0, webpack-virtual-modules@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" @@ -12130,6 +12222,32 @@ webpack@5: watchpack "^2.4.1" webpack-sources "^3.2.3" +webpack@^5.109.2: + version "5.109.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.109.2.tgz#b58dc289561c3282db35c210a99379a836a4c28d" + integrity sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw== + dependencies: + "@types/estree" "^1.0.8" + "@types/json-schema" "^7.0.15" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.16.0" + browserslist "^4.28.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.24.4" + es-module-lexer "^2.1.0" + eslint-scope "5.1.1" + events "^3.2.0" + graceful-fs "^4.2.11" + mime-db "^1.54.0" + minimizer-webpack-plugin "^5.6.1" + neo-async "^2.6.2" + schema-utils "^4.3.3" + tapable "^2.3.0" + watchpack "^2.5.2" + webpack-sources "^3.5.1" + whatwg-encoding@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" @@ -12213,6 +12331,19 @@ which-typed-array@^1.1.16, which-typed-array@^1.1.18: gopd "^1.2.0" has-tostringtag "^1.0.2" +which-typed-array@^1.1.2: + version "1.1.22" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.22.tgz#8f3cc78aefb40b437346dd40a1dbfa5d1da43fe9" + integrity sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.9" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@^1.2.14, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" From f358d19c96f29e63889735cfd36f1294a754bb5d Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Tue, 1 Sep 2026 11:35:39 +0200 Subject: [PATCH 02/10] switch engines info back to former value because it is meant for consumers, not developers - but add .nvmrc to have the definition for developers --- .nvmrc | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..b03f4086 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +lts/krypton diff --git a/package.json b/package.json index 8fa8c25f..1a495c48 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "registry": "https://registry.npmjs.org" }, "engines": { - "node": ">=22.11.0" + "node": ">=18.18.0" }, "style": "src/index.scss", "main": "dist/cjs/index.js", From 65db32280e9faf7cc9a31f8649d90e4985ecf9ee Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Tue, 1 Sep 2026 12:54:25 +0200 Subject: [PATCH 03/10] fix Storybook implication with existing /dist filder, and cirrect the ESM build in there --- .storybook/main.js | 11 ++++ CHANGELOG.md | 5 ++ scripts/fix-esm-dist | 1 + scripts/fix-esm-imports.mjs | 73 +++++++++++++++++++++ src/components/Tabs/stories/Tab.stories.tsx | 2 +- 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 scripts/fix-esm-imports.mjs diff --git a/.storybook/main.js b/.storybook/main.js index eaacced1..649d43b5 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,3 +1,4 @@ +const path = require("path"); const sass = require("sass"); const sassRenderSyncConfig = require("./../scripts/sassConfig"); const { silenceDeprecations } = require("../scripts/sassDeprecationConfig"); @@ -54,6 +55,16 @@ module.exports = { }, }, webpackFinal: async (config, { configType }) => { + // never let the compiled package output take part in the Storybook build, + // stories always have to use the sources + // a directory import like `"./../../../../"` is resolved by the `exports` field of our + // own package.json and would silently pull `dist/esm/` into the preview bundle, + // this restriction removes it from the candidates so that the root `index.ts` is used + const distPath = path.resolve(__dirname, "..", "dist"); + config.resolve.restrictions = [ + ...(config.resolve.restrictions ?? []), + new RegExp(`^(?!${distPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[/\\\\])`), + ]; // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' if (configType === "PRODUCTION") { // remove source maps from production storybook diff --git a/CHANGELOG.md b/CHANGELOG.md index 301b6e92..b0f2606f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `` - fix description and story to point out that `PropertyValueList` need always to be used as wrapper +- ESM distribution + - the imports of the `@codemirror/legacy-modes` modes were written with an additional `.js` suffix, but the `exports` map of this package only provides the extension-less sub paths, so they were expanded to unresolvable paths like `mode/jinja2.js.js` +- Storybook + - the `` story imported the package root directory, this way the `exports` field of our own `package.json` pulled the built `dist/esm/` output into the preview bundle instead of the sources, and the Storybook build failed as soon as `dist/` existed + - the webpack configuration excludes `dist/` from module resolution now, so the sources are always used even if a story references the package root ### Deprecated diff --git a/scripts/fix-esm-dist b/scripts/fix-esm-dist index 61006373..13f13e46 100755 --- a/scripts/fix-esm-dist +++ b/scripts/fix-esm-dist @@ -1,4 +1,5 @@ yarn tsc-esm-fix --target='dist/esm' +node ./scripts/fix-esm-imports.mjs dist/esm cat >dist/esm/package.json < + fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const entryPath = path.join(directory, entry.name); + if (entry.isDirectory()) return collectFiles(entryPath); + return entry.name.endsWith(".js") ? [entryPath] : []; + }); + +const resolvesToFile = (specifier) => { + try { + return fs.existsSync(fileURLToPath(import.meta.resolve(specifier))); + } catch { + return false; + } +}; + +const replacements = new Map(); + +/** @returns the specifier that should be used instead, or `undefined` to keep it as it is */ +const repairedSpecifier = (specifier) => { + if (!replacements.has(specifier)) { + const withoutExtension = specifier.replace(/\.js$/, ""); + const repairable = + specifier !== withoutExtension && !resolvesToFile(specifier) && resolvesToFile(withoutExtension); + replacements.set(specifier, repairable ? withoutExtension : undefined); + } + return replacements.get(specifier); +}; + +let repaired = 0; + +for (const file of collectFiles(target)) { + const contents = fs.readFileSync(file, "utf8"); + const fixedContents = contents.replace(specifiers, (reference, quote, specifier) => { + // relative references are resolved against the output directory, only bare ones can be checked here + if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("node:")) return reference; + const replacement = repairedSpecifier(specifier); + return replacement === undefined ? reference : reference.replace(specifier, replacement); + }); + if (fixedContents !== contents) { + fs.writeFileSync(file, fixedContents); + repaired += 1; + } +} + +for (const [specifier, replacement] of replacements) { + if (replacement !== undefined) { + console.log(`fix-esm-imports: "${specifier}" -> "${replacement}"`); + } +} +console.log(`fix-esm-imports: repaired imports in ${repaired} file(s) of ${target}`); diff --git a/src/components/Tabs/stories/Tab.stories.tsx b/src/components/Tabs/stories/Tab.stories.tsx index 44ec92bd..e8b3f801 100644 --- a/src/components/Tabs/stories/Tab.stories.tsx +++ b/src/components/Tabs/stories/Tab.stories.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Meta, StoryFn } from "@storybook/react"; -import { Tab as TabDummyForStorybook, Tabs } from "./../../../../"; +import { Tab as TabDummyForStorybook, Tabs } from "./../../../../index"; export default { title: "Components/Tabs/Tab", From a65414901294cc3ffe8291e71f00e5590ee4087c Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Tue, 1 Sep 2026 14:36:36 +0200 Subject: [PATCH 04/10] update changelog re assert inclusion --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0f2606f..f8bea97c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Storybook - the `` story imported the package root directory, this way the `exports` field of our own `package.json` pulled the built `dist/esm/` output into the preview bundle instead of the sources, and the Storybook build failed as soon as `dist/` existed - the webpack configuration excludes `dist/` from module resolution now, so the sources are always used even if a story references the package root +- Added explicitly `assert` dependency + - the linter of `` uses `jshint`, which imports `console-browserify`, and this package requires the node core modules `assert` and `util` without declaring them; bundlers based on webpack 5 do not provide shims for node core modules anymore, so the polyfill (and `util` together with it) is part of the delivery now ### Deprecated From d3a06fcc78727d161a24a49de4236d26764ba2d4 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Tue, 1 Sep 2026 16:44:29 +0200 Subject: [PATCH 05/10] fix includePaths, so it works also if they would not have the same --- scripts/compile-sass.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/scripts/compile-sass.ts b/scripts/compile-sass.ts index 08979d80..900bb70b 100644 --- a/scripts/compile-sass.ts +++ b/scripts/compile-sass.ts @@ -8,14 +8,34 @@ import { silenceDeprecations } from "./sassDeprecationConfig"; const args = yargs(process.argv.slice(2)).argv as any; +/** + * Returns the dependency root a package was installed into, i.e. the `node_modules` directory + * holding it. The number of levels to climb is taken from the package name, this way scoped + * (`@scope/name`) and plain (`name`) packages are both handled correctly. + */ +const dependencyRoot = (packageName: string) => + path.resolve( + path.dirname(require.resolve(`${packageName}/package.json`)), + ...packageName.split("/").map(() => ".."), + ); + +// Imports without tilde syntax are not handled by the importer below, sass resolves them relative +// to the importing file and via these include paths. `@blueprintjs/core` and `@carbon/react` both +// use them internally to reach their sibling packages, e.g. `@blueprintjs/colors` or +// `@carbon/styles`. Their dependency roots are looked up separately because the packages do not +// need to share one, e.g. if only one of them is hoisted into a parent workspace. The local +// `node_modules` is kept as a fallback. All paths are absolute, so that duplicates of the same +// directory can be removed. +const includePaths = [ + ...new Set([dependencyRoot("@blueprintjs/core"), dependencyRoot("@carbon/react"), path.resolve("node_modules")]), +]; + const styles = sass.renderSync({ importer: tildeImporter(), ...sassRenderSyncConfig, silenceDeprecations: [...silenceDeprecations, "legacy-js-api"] as sass.DeprecationOrId[], file: "src/index.scss", - // Resolve non-tilde imports from the effective workspace dependency root, including when - // dependencies are hoisted into a parent workspace. - includePaths: [path.resolve(path.dirname(require.resolve("@blueprintjs/core/package.json")), "../..")], + includePaths, }); if (args.outputCss) { From b7e1776f4c4a583e6431d8cd335ca65d874e6e7c Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 2 Sep 2026 12:56:28 +0200 Subject: [PATCH 06/10] set engines.node to 18.19 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8bea97c..f867596b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `` - `noDrag` parameter: Add the `nodrag` class to the Switch element. Default: `true` +### Changed + +- minimum node version (`engines.node`) is `18.19.0` now + - the build of the ESM distribution needs a synchronous `import.meta.resolve`, which is only available since this version + ### Fixed - `` diff --git a/package.json b/package.json index 1a495c48..a2ef6824 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "registry": "https://registry.npmjs.org" }, "engines": { - "node": ">=18.18.0" + "node": ">=18.19.0" }, "style": "src/index.scss", "main": "dist/cjs/index.js", From d00253a88d7fe03e5fb0c612e091a2da79ab8d65 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 2 Sep 2026 12:57:49 +0200 Subject: [PATCH 07/10] improve error handling --- scripts/fix-esm-dist | 4 ++++ scripts/fix-esm-imports.mjs | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/scripts/fix-esm-dist b/scripts/fix-esm-dist index 13f13e46..ef7c3043 100755 --- a/scripts/fix-esm-dist +++ b/scripts/fix-esm-dist @@ -1,3 +1,7 @@ +#!/bin/sh +# abort as soon as one of the steps fails, otherwise a broken ESM output would be +# reported as a successful build because the exit code of the last command wins +set -e yarn tsc-esm-fix --target='dist/esm' node ./scripts/fix-esm-imports.mjs dist/esm cat >dist/esm/package.json < fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const entryPath = path.join(directory, entry.name); @@ -29,11 +51,16 @@ const collectFiles = (directory) => }); const resolvesToFile = (specifier) => { + let resolved; try { - return fs.existsSync(fileURLToPath(import.meta.resolve(specifier))); - } catch { - return false; + resolved = import.meta.resolve(specifier); + } catch (error) { + // only a failed resolution is an expected answer, everything else (a broken resolver, a + // permission problem, …) has to abort the build instead of silently repairing nothing + if (unresolvableCodes.has(error?.code)) return false; + throw error; } + return fs.existsSync(fileURLToPath(resolved)); }; const replacements = new Map(); From d8a8498a6f3b314e72d35ab23220755c71fbae81 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 2 Sep 2026 13:15:37 +0200 Subject: [PATCH 08/10] fix import detection --- scripts/fix-esm-imports.mjs | 46 +++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/scripts/fix-esm-imports.mjs b/scripts/fix-esm-imports.mjs index d94cf45a..40c09fd0 100644 --- a/scripts/fix-esm-imports.mjs +++ b/scripts/fix-esm-imports.mjs @@ -10,11 +10,17 @@ * * Every bare import in the ESM output is checked here: when it does not point to an * existing file but its extension-less form does, the added extension is removed again. + * + * The references are collected by the TypeScript scanner instead of a regular expression, so + * that all import forms are covered (including side effect only imports like `import "…";`) + * and no comment, string literal or regular expression that happens to contain something like + * `from "…"` is mistaken for a module reference. */ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import ts from "typescript"; const target = process.argv[2] ?? "dist/esm"; @@ -28,9 +34,6 @@ if (typeof import.meta.resolve !== "function") { ); } -/** matches the specifier of static imports/re-exports and dynamic imports */ -const specifiers = /(?:\bfrom\s*|\bimport\s*\(\s*)(["'])([^"']+)\1/g; - /** error codes of node's module resolution that really mean "there is nothing to import here" */ const unresolvableCodes = new Set([ "ERR_MODULE_NOT_FOUND", @@ -43,6 +46,14 @@ const unresolvableCodes = new Set([ "ERR_INVALID_PACKAGE_TARGET", ]); +/** + * Only bare references can be checked here, relative ones are resolved against the output + * directory and references using a URL scheme (`node:`, `data:`, `http:`, …) never address a + * package sub path. + */ +const isBareSpecifier = (specifier) => + !specifier.startsWith(".") && !specifier.startsWith("/") && !/^\w[\w+.-]*:/.test(specifier); + const collectFiles = (directory) => fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const entryPath = path.join(directory, entry.name); @@ -70,22 +81,39 @@ const repairedSpecifier = (specifier) => { if (!replacements.has(specifier)) { const withoutExtension = specifier.replace(/\.js$/, ""); const repairable = - specifier !== withoutExtension && !resolvesToFile(specifier) && resolvesToFile(withoutExtension); + isBareSpecifier(specifier) && + specifier !== withoutExtension && + !resolvesToFile(specifier) && + resolvesToFile(withoutExtension); replacements.set(specifier, repairable ? withoutExtension : undefined); } return replacements.get(specifier); }; +/** @returns the module references of the given source, the last one in the file first */ +const moduleReferences = (contents) => + [...ts.preProcessFile(contents, true, true).importedFiles].sort((one, other) => other.pos - one.pos); + let repaired = 0; for (const file of collectFiles(target)) { const contents = fs.readFileSync(file, "utf8"); - const fixedContents = contents.replace(specifiers, (reference, quote, specifier) => { - // relative references are resolved against the output directory, only bare ones can be checked here - if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("node:")) return reference; + let fixedContents = contents; + // the references are replaced from the end of the file to its beginning, this way the + // positions of all not yet handled ones stay valid + for (const reference of moduleReferences(contents)) { + const specifier = reference.fileName; const replacement = repairedSpecifier(specifier); - return replacement === undefined ? reference : reference.replace(specifier, replacement); - }); + if (replacement === undefined) continue; + // `pos` addresses the quote opening the reference, its text starts behind it + const start = reference.pos + 1; + if (fixedContents.slice(start, start + specifier.length) !== specifier) { + throw new Error( + `fix-esm-imports: cannot repair "${specifier}" in ${file}, the source at position ${start} does not match the parsed reference`, + ); + } + fixedContents = fixedContents.slice(0, start) + replacement + fixedContents.slice(start + specifier.length); + } if (fixedContents !== contents) { fs.writeFileSync(file, fixedContents); repaired += 1; From 0474b1b37fce3b1de4241d22e4885aed73c89d3a Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 2 Sep 2026 13:30:14 +0200 Subject: [PATCH 09/10] harden the includePaths resolver --- scripts/compile-sass.ts | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/scripts/compile-sass.ts b/scripts/compile-sass.ts index 900bb70b..0b800a50 100644 --- a/scripts/compile-sass.ts +++ b/scripts/compile-sass.ts @@ -1,3 +1,4 @@ +import * as fs from "fs"; import tildeImporter from "node-sass-package-importer"; import * as path from "path"; import * as sass from "sass"; @@ -8,26 +9,44 @@ import { silenceDeprecations } from "./sassDeprecationConfig"; const args = yargs(process.argv.slice(2)).argv as any; +/** + * Returns the directory a package was installed into, or `undefined` if it cannot be found. + * The `node_modules` chain of the resolver is walked directly instead of resolving the + * `package.json` of the package, because a package is free to hide that file behind its + * `exports` map, which would let `require.resolve()` fail with `ERR_PACKAGE_PATH_NOT_EXPORTED`. + * Symbolic links are resolved, so that the real location is used, e.g. the store directory of pnpm. + */ +const packageDirectory = (packageName: string) => { + for (const searchPath of require.resolve.paths(packageName) ?? []) { + const candidate = path.join(searchPath, ...packageName.split("/")); + if (fs.existsSync(path.join(candidate, "package.json"))) return fs.realpathSync(candidate); + } + return undefined; +}; + /** * Returns the dependency root a package was installed into, i.e. the `node_modules` directory * holding it. The number of levels to climb is taken from the package name, this way scoped * (`@scope/name`) and plain (`name`) packages are both handled correctly. */ -const dependencyRoot = (packageName: string) => - path.resolve( - path.dirname(require.resolve(`${packageName}/package.json`)), - ...packageName.split("/").map(() => ".."), - ); +const dependencyRoot = (packageName: string) => { + const directory = packageDirectory(packageName); + return directory === undefined ? undefined : path.resolve(directory, ...packageName.split("/").map(() => "..")); +}; // Imports without tilde syntax are not handled by the importer below, sass resolves them relative // to the importing file and via these include paths. `@blueprintjs/core` and `@carbon/react` both // use them internally to reach their sibling packages, e.g. `@blueprintjs/colors` or // `@carbon/styles`. Their dependency roots are looked up separately because the packages do not // need to share one, e.g. if only one of them is hoisted into a parent workspace. The local -// `node_modules` is kept as a fallback. All paths are absolute, so that duplicates of the same -// directory can be removed. +// `node_modules` is kept as a fallback, it is also used alone if a dependency root cannot be +// determined. All paths are absolute, so that duplicates of the same directory can be removed. const includePaths = [ - ...new Set([dependencyRoot("@blueprintjs/core"), dependencyRoot("@carbon/react"), path.resolve("node_modules")]), + ...new Set( + [dependencyRoot("@blueprintjs/core"), dependencyRoot("@carbon/react"), path.resolve("node_modules")].filter( + (includePath): includePath is string => includePath !== undefined, + ), + ), ]; const styles = sass.renderSync({ From 9f78af8b8765fffa5100690d1bfe028625b03d84 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 2 Sep 2026 13:33:02 +0200 Subject: [PATCH 10/10] istall explitely util along assert --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a2ef6824..b9cb60df 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "remark-parse": "^11.0.0", "reset-css": "^5.0.2", "unified": "^11.0.5", + "util": "^0.12.5", "wicg-inert": "^3.1.3" }, "devDependencies": {