diff --git a/litmus/.gitignore b/litmus/.gitignore index 1841bca25..598a14e87 100644 --- a/litmus/.gitignore +++ b/litmus/.gitignore @@ -1 +1,2 @@ -manifest.scss \ No newline at end of file +manifest.scss +dist-vite diff --git a/litmus/features/charts/line-graph/SmoothingOvershoot.js b/litmus/features/charts/line-graph/SmoothingOvershoot.tsx similarity index 100% rename from litmus/features/charts/line-graph/SmoothingOvershoot.js rename to litmus/features/charts/line-graph/SmoothingOvershoot.tsx diff --git a/litmus/index.vite.html b/litmus/index.vite.html new file mode 100644 index 000000000..924bd1b90 --- /dev/null +++ b/litmus/index.vite.html @@ -0,0 +1,20 @@ + + + + + + Cx - Litmus (Vite) + + + +
+ + + diff --git a/litmus/index.vite.js b/litmus/index.vite.js new file mode 100644 index 000000000..729a46098 --- /dev/null +++ b/litmus/index.vite.js @@ -0,0 +1,31 @@ +import { Widget, startAppLoop, History } from "cx/ui"; +import { Timing, Debug } from "cx/util"; +import { Store } from "cx/data"; +import "./error"; + +import "./index.scss"; + +// Vite entry point. Change the import below to test a different demo, +// or keep it in sync with index.js (the webpack entry). +import Demo from "./features/charts/line-graph/SmoothingOvershoot"; + +let store = (window.store = new Store()); + +Widget.resetCounter(); +Timing.enable("app-loop"); +Debug.enable("app-data"); + +History.connect(store, "url"); + +let stop; + +if (import.meta.hot) { + import.meta.hot.accept(); + import.meta.hot.dispose((data) => { + data.state = store.getData(); + if (stop) stop(); + }); + if (import.meta.hot.data?.state) store.load(import.meta.hot.data.state); +} + +stop = startAppLoop(document.getElementById("app"), store, Demo); diff --git a/litmus/package.json b/litmus/package.json index 5427a8e2b..e061ad4b4 100644 --- a/litmus/package.json +++ b/litmus/package.json @@ -4,7 +4,9 @@ "version": "1.0.0", "scripts": { "start": "webpack-dev-server --open", - "start:prod": "webpack serve --mode production --open" + "start:prod": "webpack serve --mode production --open", + "start:vite": "vite", + "build:vite": "vite build" }, "dependencies": { "casual": "^1.6.2", @@ -46,6 +48,9 @@ "ts-loader": "^9.5.1", "typescript": "^5.9.3", "url-loader": "^4.1.1", + "vite": "^8.0.0", + "vite-plugin-cx-scss-manifest": "workspace:*", + "vite-plugin-transform-cx-imports": "workspace:*", "webpack": "^5.103.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cleanup-plugin": "^0.5.1", diff --git a/litmus/vite.config.mjs b/litmus/vite.config.mjs new file mode 100644 index 000000000..1c79e6df6 --- /dev/null +++ b/litmus/vite.config.mjs @@ -0,0 +1,73 @@ +import { defineConfig } from "vite"; +import { transform } from "esbuild"; +import path from "path"; +import { fileURLToPath } from "url"; +import cxScssManifest from "vite-plugin-cx-scss-manifest"; +import transformCxImports from "vite-plugin-transform-cx-imports"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// run against live CxJS sources instead of the compiled build output +const cxSrc = path.resolve(__dirname, "../packages/cx/src").replace(/\\/g, "/"); + +export default defineConfig(({ command }) => ({ + plugins: [ + transformCxImports(), + // registered after the rewrite so it only ever sees deep imports (collected in resolveId) + cxScssManifest({ + outputPath: path.join(__dirname, "manifest.scss"), + }), + { + // litmus examples use JSX inside .js files, which rolldown's built-in + // transform doesn't support, so those files go through esbuild instead + name: "litmus-js-jsx", + enforce: "pre", + async transform(code, id) { + const file = id.split("?")[0]; + if (!file.endsWith(".js") || file.includes("node_modules")) return null; + const result = await transform(code, { + loader: "jsx", + jsx: "automatic", + jsxImportSource: "cx", + jsxDev: command === "serve", + sourcemap: true, + }); + return { code: result.code, map: result.map }; + }, + }, + ], + oxc: { + jsx: { + runtime: "automatic", + importSource: "cx", + }, + }, + resolve: { + alias: [ + { find: /^cx\/src\/(.*)$/, replacement: `${cxSrc}/$1` }, + { find: /^cx\/(jsx-dev-runtime|jsx-runtime)$/, replacement: `${cxSrc}/$1.ts` }, + { find: /^cx\/([\w-]+)$/, replacement: `${cxSrc}/$1/index.ts` }, + // manifest js paths point to compiled .js files - strip the extension + // so the resolver can pick up the .ts/.tsx sources + { find: /^cx\/([\w-]+)\/(.*)\.js$/, replacement: `${cxSrc}/$1/$2` }, + { find: /^cx\/([\w-]+)\/(.*)$/, replacement: `${cxSrc}/$1/$2` }, + ], + }, + css: { + preprocessorOptions: { + scss: { + silenceDeprecations: ["legacy-js-api", "import", "global-builtin", "slash-div"], + }, + }, + }, + server: { + port: 8090, + open: "/index.vite.html", + }, + build: { + outDir: "dist-vite", + rollupOptions: { + input: path.join(__dirname, "index.vite.html"), + }, + }, +})); diff --git a/packages/cx/src/jsx-dev-runtime.ts b/packages/cx/src/jsx-dev-runtime.ts index 3962ec63d..c0ef91063 100644 --- a/packages/cx/src/jsx-dev-runtime.ts +++ b/packages/cx/src/jsx-dev-runtime.ts @@ -2,4 +2,5 @@ import { jsx, jsxs } from "./jsx-runtime"; export const jsxDEV = jsx; export const jsxsDEV = jsxs; -export { Fragment, JSX } from "./jsx-runtime"; +export { Fragment } from "./jsx-runtime"; +export type { JSX } from "./jsx-runtime"; diff --git a/packages/cx/src/ui/index.ts b/packages/cx/src/ui/index.ts index 3ebfedc72..c4a89745d 100644 --- a/packages/cx/src/ui/index.ts +++ b/packages/cx/src/ui/index.ts @@ -43,9 +43,6 @@ export * from "./exprHelpers"; //re-export computable here import { computable } from "../data/computable"; -import { - createAccessorModelProxy, - createModel, - AccessorChain, -} from "../data/createAccessorModelProxy"; -export { computable, createAccessorModelProxy, createModel, AccessorChain }; +import { createAccessorModelProxy, createModel } from "../data/createAccessorModelProxy"; +export { computable, createAccessorModelProxy, createModel }; +export type { AccessorChain } from "../data/createAccessorModelProxy"; diff --git a/packages/cx/src/widgets/grid/Grid.tsx b/packages/cx/src/widgets/grid/Grid.tsx index dffbd6708..269f3682a 100644 --- a/packages/cx/src/widgets/grid/Grid.tsx +++ b/packages/cx/src/widgets/grid/Grid.tsx @@ -72,10 +72,11 @@ import DropDownIcon from "../icons/sort-asc"; import { captureMouse2, getCursorPos } from "../overlay/captureMouse"; import { tooltipMouseLeave, tooltipMouseMove } from "../overlay/tooltip-ops"; import { createGridCellEditor } from "./createGridCellEditor"; -import { GridRow, GridRowComponent, GridRowConfig, GridRowInstance } from "./GridRow"; +import { GridRow, GridRowComponent, GridRowInstance } from "./GridRow"; +import type { GridRowConfig } from "./GridRow"; import { Create, CreateConfig } from "../../util"; import type { TreeAdapter } from "../../ui/adapter/TreeAdapter"; -export { GridRowConfig }; +export type { GridRowConfig }; export type GridFetchRecordsResult = T[] | { records: T[]; lastPage?: boolean; totalRecordCount?: number }; diff --git a/packages/vite-plugin-cx-scss-manifest/LICENSE.md b/packages/vite-plugin-cx-scss-manifest/LICENSE.md new file mode 100644 index 000000000..7a1a10e4e --- /dev/null +++ b/packages/vite-plugin-cx-scss-manifest/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2026 Codaxy d.o.o. Banja Luka + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/vite-plugin-cx-scss-manifest/README.md b/packages/vite-plugin-cx-scss-manifest/README.md new file mode 100644 index 000000000..0a92fd3bf --- /dev/null +++ b/packages/vite-plugin-cx-scss-manifest/README.md @@ -0,0 +1,106 @@ +# vite-plugin-cx-scss-manifest + +A Vite plugin that analyzes your application's source code and generates an SCSS manifest file +to include only the CxJS widget styles that are actually used. For smaller apps, this can reduce +the generated CSS by 70-90%. + +This is the Vite counterpart of [cx-scss-manifest-webpack-plugin](../cx-scss-manifest-webpack-plugin). + +## How It Works + +CxJS ships SCSS for every widget (Button, Grid, Window, etc.). By default, all widget styles are +included during SCSS compilation. This plugin watches which CxJS modules your application's +modules import, then generates a manifest that tells the SCSS compiler to include only the +styles for those modules. + +Deep imports (`cx/widgets/grid/Grid.js`) are collected from Vite's module resolution, which +costs nothing. Named imports from a namespace barrel (`import { Grid } from "cx/widgets"`) require +a look at the import statements of that module, done with +[es-module-lexer](https://github.com/guybedford/es-module-lexer), which costs a few percent of +the transform Vite already runs on every module. Pair this plugin with +[vite-plugin-transform-cx-imports](../vite-plugin-transform-cx-imports) (registered first) and, +wherever that plugin's rewrite is active, barrel imports become deep imports before this plugin +sees them, so no source parsing happens at all. + +The generated `manifest.scss` looks like: + +```scss +@use "cx/src/util/scss/include.scss" as * with ($cx-include-all: false); + +@include cx-widgets( + "cx/widgets/Button", + "cx/widgets/Grid", + "cx/widgets/HtmlElement" +); +``` + +## Installation + +```bash +npm install vite-plugin-cx-scss-manifest --save-dev +``` + +## Usage + +Add the plugin to your `vite.config.js`: + +```js +import { defineConfig } from "vite"; +import transformCxImports from "vite-plugin-transform-cx-imports"; +import cxScssManifest from "vite-plugin-cx-scss-manifest"; +import path from "path"; + +export default defineConfig({ + plugins: [ + transformCxImports(), + cxScssManifest({ + outputPath: path.join(__dirname, "manifest.scss"), + }), + ], +}); +``` + +Then import the manifest **before** the CxJS SCSS in your main stylesheet: + +```scss +@use "manifest"; +@use "cx/src/index"; +``` + +The import order matters. The manifest must be loaded first so it configures the SCSS +module system before any widget styles are compiled. + +### With a Theme + +When using a SCSS theme, import the manifest before the theme: + +```scss +@use "manifest"; +@use "cx-theme-aquamarine/src/index"; +``` + +The theme internally loads `cx/src/index`, so you don't need to import it separately. + +## Important + +The generated `manifest.scss` should be checked into version control for apps that use +`vite build` — SCSS is compiled in the same pass, so the manifest from the _previous_ +build is what actually takes effect. Starting from an empty manifest would ship a build +with incomplete styles. For dev-only environments, gitignoring the file is fine; the +worst case is an unstyled first paint in a fresh clone until the manifest fills up. + +- **Dev server:** modules are transformed on demand, so manifest entries are collected as + pages are visited. Whenever a new CxJS widget is detected, the manifest is updated and + Vite hot-reloads the stylesheet automatically. +- **Production build:** the full module graph is scanned, so all imports (including lazy + routes) are detected. If the manifest changes during a build, the plugin emits a + warning; run the build again to apply the changes. + +Importing a module records every manifest key that module provides, so the file also +lists entries that have no styles of their own (e.g. `cx/widgets/Option` next to +`cx/widgets/Select`). Those are harmless: the SCSS side only looks up the names it knows. + +Entries are only ever added, never removed — existing entries are re-read from the file +on startup, so a dev session that visits only a few pages never shrinks the manifest. +To reset it (e.g. after removing widgets from the app), delete the entries from +`manifest.scss` and run a production build. diff --git a/packages/vite-plugin-cx-scss-manifest/package.json b/packages/vite-plugin-cx-scss-manifest/package.json new file mode 100644 index 000000000..6e979736e --- /dev/null +++ b/packages/vite-plugin-cx-scss-manifest/package.json @@ -0,0 +1,24 @@ +{ + "name": "vite-plugin-cx-scss-manifest", + "version": "26.8.0", + "description": "Vite plugin that inspects app's source code and generates a manifest file for importing only required elements of CxJS SCSS", + "main": "src/index.js", + "types": "src/index.d.ts", + "author": "Codaxy", + "license": "MIT", + "bugs": { + "url": "https://github.com/codaxy/cxjs" + }, + "homepage": "https://github.com/codaxy/cxjs", + "dependencies": { + "es-module-lexer": "^1.5.0" + }, + "peerDependencies": { + "cx": "*", + "vite": ">=4.0.0" + }, + "repository": { + "type": "git", + "url": "git@github.com:codaxy/cx.git" + } +} diff --git a/packages/vite-plugin-cx-scss-manifest/src/index.d.ts b/packages/vite-plugin-cx-scss-manifest/src/index.d.ts new file mode 100644 index 000000000..2b4d12578 --- /dev/null +++ b/packages/vite-plugin-cx-scss-manifest/src/index.d.ts @@ -0,0 +1,12 @@ +import type { Plugin } from "vite"; + +declare namespace cxScssManifestPlugin { + interface Options { + /** Path of the manifest.scss file to generate. */ + outputPath: string; + } +} + +declare function cxScssManifestPlugin(options: cxScssManifestPlugin.Options): Plugin[]; + +export = cxScssManifestPlugin; diff --git a/packages/vite-plugin-cx-scss-manifest/src/index.js b/packages/vite-plugin-cx-scss-manifest/src/index.js new file mode 100644 index 000000000..a598673ed --- /dev/null +++ b/packages/vite-plugin-cx-scss-manifest/src/index.js @@ -0,0 +1,210 @@ +let fs = require("fs"); +let { init, parse } = require("es-module-lexer"); + +const pluginName = "cx-scss-manifest"; + +// named imports from a namespace barrel: "cx/widgets", "cx/ui", "cx/charts", ... +const barrelRe = /^cx\/(\w+)$/; + +// cheap pre-check so the lexer only runs for modules that actually import a barrel +const hasBarrelImportRe = /["']cx\/\w+["']/; + +// deep imports, either as written ("cx/widgets/grid/Grid.js") or as already rewritten by an +// alias (".../cx/src/widgets/grid/Grid", "node_modules/cx/build/widgets/grid/Grid.js") +const deepImportRe = /(?:^cx|[\\/]cx[\\/](?:src|build))[\\/](\w+)[\\/]([\w\\/.-]+?)(?:\.[jt]sx?)?$/; + +const scannedFileRe = /\.(jsx?|tsx?|mjs|mts)$/; + +function loadCxManifest(root) { + let manifestPath; + try { + manifestPath = require.resolve("cx/manifest.js", { paths: [root, __dirname] }); + } catch (e) { + throw new Error( + "vite-plugin-cx-scss-manifest: Unable to resolve cx/manifest.js. Make sure the cx package is installed.", + ); + } + return require(manifestPath); +} + +module.exports = function cxScssManifestPlugin(options = {}) { + let { outputPath } = options; + if (!outputPath) throw new Error("vite-plugin-cx-scss-manifest: The outputPath option is required."); + + let cxManifest = null; + let nameLookup = null; // export name -> [manifest keys] + let jsPathLookup = null; // module path without extension ("widgets/grid/Grid") -> [manifest keys] + let manifest = {}; + let dirty = false; + let isBuild = false; + let logger = console; + let writeTimer = null; + let parseWarned = new Set(); + + //touch output file immediately to avoid compilation errors + if (!fs.existsSync(outputPath)) fs.writeFileSync(outputPath, ""); + + function record(key) { + if (cxManifest[key] && !manifest[key]) { + manifest[key] = true; + dirty = true; + } + } + + function recordName(ns, name) { + let exact = ns + "/" + name; + if (cxManifest[exact]) return record(exact); + // the export may live in another namespace, e.g. ui widgets re-exported through cx/widgets + let candidates = nameLookup[name]; + if (candidates) for (let key of candidates) record(key); + } + + function write() { + dirty = false; + let content = "//THIS FILE IS AUTO-GENERATED USING vite-plugin-cx-scss-manifest\n\n"; + content += '@use "cx/src/util/scss/include.scss" as * with ($cx-include-all: false);\n\n'; + + let keys = Object.keys(manifest); + keys.sort(); + + content += "@include cx-widgets(\n"; + content += keys.map((k) => '\t"cx/' + k + '"').join(",\n"); + content += "\n);\n"; + + let previousContent = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, "utf8") : ""; + if (content == previousContent) return false; + logger.info("CxJS SCSS manifest update."); + fs.writeFileSync(outputPath, content); + return true; + } + + function scheduleWrite() { + if (!dirty || isBuild) return; + if (writeTimer) clearTimeout(writeTimer); + writeTimer = setTimeout(() => { + writeTimer = null; + try { + write(); + } catch (e) { + logger.error(`[${pluginName}] Failed to write ${outputPath}: ${e.message}`); + } + }, 100); + } + + function recordResolvedImport(source, importer) { + if (!importer || importer.indexOf("node_modules") !== -1) return; + let m = deepImportRe.exec(source); + if (!m) return; + // one file usually backs several manifest keys (Select.js -> widgets/Select, widgets/Option); + // importing the file pulls in all of them, and so should their styles + let keys = jsPathLookup[m[1] + "/" + m[2]]; + if (!keys) return; + for (let key of keys) record(key); + scheduleWrite(); + } + + return [ + { + // Every deep import passes through resolveId with its importer, in dev and in build, so + // deep imports (including the ones produced by vite-plugin-transform-cx-imports) are + // collected without parsing any source code. resolveId is a first-wins hook and Vite's + // own resolver runs before normal plugins, so this part has to be enforced "pre". + name: pluginName + ":resolve", + enforce: "pre", + resolveId(source, importer) { + recordResolvedImport(source, importer); + return null; + }, + }, + { + name: pluginName, + + configResolved(config) { + isBuild = config.command === "build"; + logger = config.logger; + cxManifest = loadCxManifest(config.root); + nameLookup = {}; + jsPathLookup = {}; + for (let key in cxManifest) { + let name = key.substring(key.indexOf("/") + 1); + if (!nameLookup[name]) nameLookup[name] = []; + nameLookup[name].push(key); + let js = cxManifest[key].js; + if (js) { + let jsPath = js.replace(/\.[jt]sx?$/, ""); + if (!jsPathLookup[jsPath]) jsPathLookup[jsPath] = []; + jsPathLookup[jsPath].push(key); + } + } + + //seed with entries from a previous run so the manifest only ever grows + if (fs.existsSync(outputPath)) { + let previousContent = fs.readFileSync(outputPath, "utf8"); + let entryRe = /"cx\/([^"]+)"/g, + m; + while ((m = entryRe.exec(previousContent))) { + if (cxManifest[m[1]]) manifest[m[1]] = true; + } + } + }, + + // Named imports from a barrel ("import { Grid } from 'cx/widgets'") can't be attributed to a + // module in resolveId, so those still need a look at the import statements. Note that this + // hook sees post-transform JavaScript: Vite's core esbuild/oxc plugin runs before user + // plugins, so TypeScript syntax and type-only imports are already gone. + async transform(code, id) { + //scan only the app's own modules + if (id.indexOf("node_modules") !== -1) return null; + let file = id.split("?")[0]; + if (!scannedFileRe.test(file)) return null; + if (!hasBarrelImportRe.test(code)) return null; + + await init; + let imports; + try { + [imports] = parse(code, id); + } catch (e) { + if (!parseWarned.has(file)) { + parseWarned.add(file); + logger.warn(`[${pluginName}] Unable to parse imports of ${file}: ${e.message}`); + } + return null; + } + + for (let imp of imports) { + if (!imp.n) continue; + let m = barrelRe.exec(imp.n); + if (!m) continue; + let ns = m[1]; + + //named imports, e.g. import { Button, Grid as CxGrid } from "cx/widgets" + let stmt = code.substring(imp.ss, imp.se); + let braceStart = stmt.indexOf("{"); + if (braceStart === -1 || braceStart > stmt.indexOf(imp.n)) continue; + let braceEnd = stmt.indexOf("}", braceStart); + if (braceEnd === -1) continue; + let specifiers = stmt.substring(braceStart + 1, braceEnd).split(","); + for (let spec of specifiers) { + let name = spec.trim().split(/\s+/)[0]; + if (/^\w+$/.test(name)) recordName(ns, name); + } + } + + scheduleWrite(); + return null; + }, + + buildEnd() { + if (writeTimer) { + clearTimeout(writeTimer); + writeTimer = null; + } + if (write() && isBuild) { + this.warn( + "manifest.scss has been updated. CSS in this build was compiled using the previous manifest - run the build again to apply the changes.", + ); + } + }, + }, + ]; +}; diff --git a/packages/vite-plugin-transform-cx-imports/LICENSE.md b/packages/vite-plugin-transform-cx-imports/LICENSE.md new file mode 100644 index 000000000..7a1a10e4e --- /dev/null +++ b/packages/vite-plugin-transform-cx-imports/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2026 Codaxy d.o.o. Banja Luka + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/vite-plugin-transform-cx-imports/README.md b/packages/vite-plugin-transform-cx-imports/README.md new file mode 100644 index 000000000..fad57b309 --- /dev/null +++ b/packages/vite-plugin-transform-cx-imports/README.md @@ -0,0 +1,83 @@ +# vite-plugin-transform-cx-imports + +A Vite plugin that rewrites CxJS namespace imports into direct file imports, using the +manifest shipped with the `cx` package. This is the Vite counterpart of +[babel-plugin-transform-cx-imports](../babel-plugin-transform-cx-imports). + +```js +// before +import { Button, Grid as CxGrid } from "cx/widgets"; + +// after +import { Button } from "cx/widgets/Button.js"; +import { Grid as CxGrid } from "cx/widgets/grid/Grid.js"; +``` + +## Why + +- **Faster dev server:** importing `cx/widgets` pulls the entire namespace barrel (and + everything it re-exports) through Vite's transform pipeline on cold start. Direct file + imports load only the modules your app actually uses. +- **Deterministic tree shaking:** production bundles don't have to rely on the bundler + proving the barrel re-exports side-effect free. + +## Installation + +```bash +npm install vite-plugin-transform-cx-imports --save-dev +``` + +## Usage + +```js +import { defineConfig } from "vite"; +import transformCxImports from "vite-plugin-transform-cx-imports"; + +export default defineConfig({ + plugins: [transformCxImports()], +}); +``` + +### Options + +- `dev` (default `"auto"`) — whether to rewrite imports in the dev server. Production builds + are always rewritten. With `"auto"`, the rewrite is active in dev only when Vite does not + pre-bundle cx: when cx is aliased, linked from outside `node_modules`, or listed in + `optimizeDeps.exclude`. Pass `true` or `false` to force it either way. + +### Dev server and dependency pre-bundling + +When cx is a regular dependency in `node_modules`, Vite pre-bundles it into one flat chunk +in dev. There is nothing for the rewrite to speed up there, and it would actually hurt: +Vite's dependency scanner never runs config plugins, so it would pre-bundle the `cx/widgets` +barrels it finds in the source while the served code imports deep paths that were never +optimized, causing re-optimization reloads and duplicated module instances. That is why +`dev: "auto"` leaves such setups alone. + +If you want the rewrite in dev anyway (e.g. to only load the widgets a page uses), exclude cx +from pre-bundling and pre-bundle its CommonJS dependencies explicitly, as Vite serves an +excluded package's own imports raw: + +```js +optimizeDeps: { + exclude: ["cx"], + include: ["react", "react/jsx-runtime", "react-dom"], +}, +``` + +The plugin detects the exclusion and switches the rewrite on. + +## Behavior notes + +- Only pure named imports from namespace barrels (`cx/widgets`, `cx/ui`, `cx/charts`, ...) + are rewritten. Deep imports, namespace imports and dynamic imports are left untouched. +- Names that cannot be found in the cx manifest stay on the barrel import and produce a + one-time warning. +- Exports re-exported across namespaces (e.g. a `cx/ui` widget imported from `cx/widgets`) + are resolved through the manifest and rewritten to their actual source file. +- The plugin only parses modules that contain a barrel import, using + [es-module-lexer](https://github.com/guybedford/es-module-lexer), which costs a few + percent of the transform Vite already runs on every module. +- When combined with `vite-plugin-cx-scss-manifest`, register this plugin first. The + manifest plugin collects deep imports through `resolveId` at no cost, so with the barrels + already rewritten it never has to parse anything. diff --git a/packages/vite-plugin-transform-cx-imports/package.json b/packages/vite-plugin-transform-cx-imports/package.json new file mode 100644 index 000000000..d5043cfe2 --- /dev/null +++ b/packages/vite-plugin-transform-cx-imports/package.json @@ -0,0 +1,25 @@ +{ + "name": "vite-plugin-transform-cx-imports", + "version": "26.8.0", + "description": "Vite plugin that rewrites CxJS namespace imports into direct file imports for faster dev-server startup and better tree shaking", + "main": "src/index.js", + "types": "src/index.d.ts", + "author": "Codaxy", + "license": "MIT", + "bugs": { + "url": "https://github.com/codaxy/cxjs" + }, + "homepage": "https://github.com/codaxy/cxjs", + "dependencies": { + "es-module-lexer": "^1.5.0", + "magic-string": "^0.30.0" + }, + "peerDependencies": { + "cx": "*", + "vite": ">=4.0.0" + }, + "repository": { + "type": "git", + "url": "git@github.com:codaxy/cx.git" + } +} diff --git a/packages/vite-plugin-transform-cx-imports/src/index.d.ts b/packages/vite-plugin-transform-cx-imports/src/index.d.ts new file mode 100644 index 000000000..3b7b6b839 --- /dev/null +++ b/packages/vite-plugin-transform-cx-imports/src/index.d.ts @@ -0,0 +1,16 @@ +import type { Plugin } from "vite"; + +declare namespace transformCxImports { + interface Options { + /** + * Whether to rewrite imports in the dev server. Production builds are always rewritten. + * "auto" (default) rewrites only when Vite does not pre-bundle cx, i.e. when cx is excluded + * via optimizeDeps.exclude, aliased, or linked from outside node_modules. + */ + dev?: "auto" | boolean; + } +} + +declare function transformCxImports(options?: transformCxImports.Options): Plugin; + +export = transformCxImports; diff --git a/packages/vite-plugin-transform-cx-imports/src/index.js b/packages/vite-plugin-transform-cx-imports/src/index.js new file mode 100644 index 000000000..82e96fd83 --- /dev/null +++ b/packages/vite-plugin-transform-cx-imports/src/index.js @@ -0,0 +1,163 @@ +let fs = require("fs"); +let { init, parse } = require("es-module-lexer"); +let MagicString = require("magic-string"); + +const pluginName = "transform-cx-imports"; + +// matches namespace barrel imports: "cx/widgets", "cx/ui", "cx/charts", ... +const barrelRe = /^cx\/(\w+)$/; + +// cheap pre-check so the lexer only runs for modules that actually import a barrel +const hasBarrelImportRe = /["']cx\/\w+["']/; + +const scannedFileRe = /\.(jsx?|tsx?|mjs|mts)$/; + +function loadCxManifest(root) { + let manifestPath; + try { + manifestPath = require.resolve("cx/manifest.js", { paths: [root, __dirname] }); + } catch (e) { + throw new Error( + "vite-plugin-transform-cx-imports: Unable to resolve cx/manifest.js. Make sure the cx package is installed.", + ); + } + return require(manifestPath); +} + +function aliasMatches(find, source) { + if (find instanceof RegExp) return find.test(source); + return source === find || source.indexOf(find + "/") === 0; +} + +// Vite pre-bundles cx in dev when it is a regular dependency living in node_modules and is +// neither excluded from optimization nor aliased somewhere else (e.g. to a source checkout). +function isCxPreBundled(config) { + let exclude = config.optimizeDeps && config.optimizeDeps.exclude; + if (exclude && exclude.indexOf("cx") !== -1) return false; + let alias = (config.resolve && config.resolve.alias) || []; + for (let a of alias) if (aliasMatches(a.find, "cx/widgets")) return false; + try { + let pkg = require.resolve("cx/package.json", { paths: [config.root] }); + if (!config.resolve.preserveSymlinks) pkg = fs.realpathSync(pkg); + return pkg.indexOf("node_modules") !== -1; + } catch (e) { + return false; + } +} + +module.exports = function transformCxImports(options = {}) { + let { dev = "auto" } = options; + + let cxManifest = null; + let nameLookup = null; // export name -> [manifest keys] + let logger = console; + let enabled = true; + let warned = {}; + let parseWarned = new Set(); + + function findEntry(ns, name) { + let entry = cxManifest[ns + "/" + name]; + if (entry) return entry; + // the export may live in another namespace, e.g. ui widgets re-exported through cx/widgets + let candidates = nameLookup[name]; + if (candidates && candidates.length === 1) return cxManifest[candidates[0]]; + return null; + } + + return { + name: pluginName, + + configResolved(config) { + logger = config.logger; + cxManifest = loadCxManifest(config.root); + nameLookup = {}; + for (let key in cxManifest) { + let name = key.substring(key.indexOf("/") + 1); + if (!nameLookup[name]) nameLookup[name] = []; + nameLookup[name].push(key); + } + + // In dev, the dependency scanner never sees the rewritten imports. If cx gets + // pre-bundled, the scanner bundles the barrels while the served code would import + // deep paths that were never optimized, which causes re-optimization reloads and + // duplicated module instances. Pre-bundled cx is one flat chunk anyway, so the rewrite + // has nothing to gain there - stay out of the way unless told otherwise. + if (config.command === "serve") { + enabled = dev === "auto" ? !isCxPreBundled(config) : !!dev; + if (!enabled) logger.info(`[${pluginName}] cx is pre-bundled in dev, barrel imports are left as they are`); + } + }, + + // This hook sees post-transform JavaScript: the core esbuild/oxc plugin runs before user + // plugins, so TypeScript syntax and type-only imports are already gone. + async transform(code, id) { + if (!enabled) return null; + //rewrite only the app's own modules + if (id.indexOf("node_modules") !== -1) return null; + let file = id.split("?")[0]; + if (!scannedFileRe.test(file)) return null; + if (!hasBarrelImportRe.test(code)) return null; + + await init; + let imports; + try { + [imports] = parse(code, id); + } catch (e) { + if (!parseWarned.has(file)) { + parseWarned.add(file); + logger.warn(`[${pluginName}] Unable to parse imports of ${file}: ${e.message}`); + } + return null; + } + + let s = null; + + for (let imp of imports) { + if (!imp.n || imp.d !== -1) continue; //skip dynamic imports + let m = barrelRe.exec(imp.n); + if (!m) continue; + let ns = m[1]; + + let stmt = code.substring(imp.ss, imp.se); + //rewrite only pure named imports, i.e. import { A, B as C } from "cx/widgets" + if (!/^import\s*\{/.test(stmt)) continue; + let braceEnd = stmt.indexOf("}"); + if (braceEnd === -1) continue; + + let rewritten = []; + let kept = []; + let specifiers = stmt.substring(stmt.indexOf("{") + 1, braceEnd).split(","); + for (let spec of specifiers) { + spec = spec.trim(); + if (!spec) continue; + let parts = spec.split(/\s+as\s+/); + let imported = parts[0].trim(); + let local = parts[1] ? parts[1].trim() : imported; + let entry = /^\w+$/.test(imported) ? findEntry(ns, imported) : null; + if (!entry || !entry.js) { + kept.push(spec); + let key = ns + "/" + imported; + if (/^\w+$/.test(imported) && !warned[key]) { + warned[key] = true; + logger.warn(`[${pluginName}] ${key} not found in the cx manifest, keeping the barrel import`); + } + continue; + } + rewritten.push( + `import { ${imported === local ? imported : imported + " as " + local} } from "cx/${entry.js}";`, + ); + } + + if (!rewritten.length) continue; + if (kept.length) rewritten.unshift(`import { ${kept.join(", ")} } from "${imp.n}";`); + if (!s) s = new MagicString(code); + // the statement end reported by the lexer excludes the trailing semicolon - swallow it too + let end = code[imp.se] === ";" ? imp.se + 1 : imp.se; + s.overwrite(imp.ss, end, rewritten.join("\n")); + } + + if (!s) return null; + return { code: s.toString(), map: s.generateMap({ hires: "boundary" }) }; + }, + }; +}; diff --git a/yarn.lock b/yarn.lock index 73c37402a..2311dd4ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1512,6 +1512,16 @@ __metadata: languageName: node linkType: hard +"@emnapi/core@npm:2.0.0-alpha.3": + version: 2.0.0-alpha.3 + resolution: "@emnapi/core@npm:2.0.0-alpha.3" + dependencies: + "@emnapi/wasi-threads": "npm:2.0.1" + tslib: "npm:^2.4.0" + checksum: 10/cd71d0af78c858daf14d378d960922ac9a00e108401c76d852fe467916fd5ac8d5575303a14c73d24eaed8dc626464f2a29f2905542b9a457e4b90a6ed519008 + languageName: node + linkType: hard + "@emnapi/core@npm:^1.7.1": version: 1.8.1 resolution: "@emnapi/core@npm:1.8.1" @@ -1522,6 +1532,15 @@ __metadata: languageName: node linkType: hard +"@emnapi/runtime@npm:2.0.0-alpha.3": + version: 2.0.0-alpha.3 + resolution: "@emnapi/runtime@npm:2.0.0-alpha.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/f5acb0f51e225b18f4aba6223016e3628d23a05f90ef4be11f7a25b09e0c8b285350f095818e087946945b696127286a50a57f7aaf94d420e6116f5e2101c531 + languageName: node + linkType: hard + "@emnapi/runtime@npm:^1.7.0, @emnapi/runtime@npm:^1.7.1": version: 1.8.1 resolution: "@emnapi/runtime@npm:1.8.1" @@ -1540,6 +1559,15 @@ __metadata: languageName: node linkType: hard +"@emnapi/wasi-threads@npm:2.0.1": + version: 2.0.1 + resolution: "@emnapi/wasi-threads@npm:2.0.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/91dbd7d2ec4ebe01e0e889d9d4d6b79a2a3180ed61e2bdc537c2f7dcb026ac2f8d54126a0966bbcf6fc714f834638d40462c6c9a9bcfb35ee2a7b9897cd06877 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/aix-ppc64@npm:0.25.12" @@ -2403,6 +2431,18 @@ __metadata: languageName: node linkType: hard +"@napi-rs/wasm-runtime@npm:^1.2.0": + version: 1.2.2 + resolution: "@napi-rs/wasm-runtime@npm:1.2.2" + dependencies: + "@tybys/wasm-util": "npm:^0.10.3" + peerDependencies: + "@emnapi/core": ^1.7.1 || ^2.0.0-alpha.3 + "@emnapi/runtime": ^1.7.1 || ^2.0.0-alpha.3 + checksum: 10/19b07c017845142b711e1515684d33c16a4c0c0d64a8876c30953f0e6975a3d018ce6e8bfb1721f25dc34df3bcc689cfb44e80fe8b8ee1b72b8429d5ff2eaf61 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -2459,6 +2499,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/types@npm:=0.142.0": + version: 0.142.0 + resolution: "@oxc-project/types@npm:0.142.0" + checksum: 10/490ea98f11955763f3baad4fe0b8dc5cafff4be465fdbc1d1685ef5be3ac686781894536de8ca02dc5d4b62b36bcbd98a4401c728786df1190f7a8fc5495287b + languageName: node + linkType: hard + "@pagefind/darwin-arm64@npm:1.4.0": version: 1.4.0 resolution: "@pagefind/darwin-arm64@npm:1.4.0" @@ -2666,6 +2713,115 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-android-arm64@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-android-arm64@npm:1.2.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-darwin-arm64@npm:1.2.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-darwin-x64@npm:1.2.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-freebsd-x64@npm:1.2.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-ppc64-gnu@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-wasm32-wasi@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.2.1" + dependencies: + "@emnapi/core": "npm:2.0.0-alpha.3" + "@emnapi/runtime": "npm:2.0.0-alpha.3" + "@napi-rs/wasm-runtime": "npm:^1.2.0" + checksum: 10/230cb2f4d2a1dad5ae2c70ef5073c6a21d5fce3fd38cde49c80fd2e22f656807db69622830fa9dbf9c071bc37f705f2a5aca84d1dadccd0c32dbf5c0f9eea4ec + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.2.1": + version: 1.2.1 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rolldown/pluginutils@npm:1.0.0-beta.27": version: 1.0.0-beta.27 resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27" @@ -2673,6 +2829,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/pluginutils@npm:^1.0.0": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10/4e95cf9ce23d75e5aa03ea0249cd86f7d1e21f83fbf6f8520e4edd8a251ba1b82c4ba9bc13cd24b6c4661daec6225b06e6d35c64c604e731b230b2a49af47d05 + languageName: node + linkType: hard + "@rollup/plugin-babel@npm:^6.1.0": version: 6.1.0 resolution: "@rollup/plugin-babel@npm:6.1.0" @@ -3538,6 +3701,15 @@ __metadata: languageName: node linkType: hard +"@tybys/wasm-util@npm:^0.10.3": + version: 0.10.3 + resolution: "@tybys/wasm-util@npm:0.10.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/6cf39f7a2926b1c8bc6fe3f9f03318a33dd6dae81bdbd059983f9c6ee22d10a827f12564d648c05a2d4926e03c86cbe2799fb20609ee65e9efc39603039b4765 + languageName: node + linkType: hard + "@types/babel__core@npm:^7.20.5": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -6995,7 +7167,7 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.7.0": +"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.5.0, es-module-lexer@npm:^1.7.0": version: 1.7.0 resolution: "es-module-lexer@npm:1.7.0" checksum: 10/b6f3e576a3fed4d82b0d0ad4bbf6b3a5ad694d2e7ce8c4a069560da3db6399381eaba703616a182b16dde50ce998af64e07dcf49f2ae48153b9e07be3f107087 @@ -9631,6 +9803,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-android-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-android-arm64@npm:1.33.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-darwin-arm64@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-darwin-arm64@npm:1.30.2" @@ -9638,6 +9817,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-arm64@npm:1.33.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-darwin-x64@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-darwin-x64@npm:1.30.2" @@ -9645,6 +9831,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-x64@npm:1.33.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "lightningcss-freebsd-x64@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-freebsd-x64@npm:1.30.2" @@ -9652,6 +9845,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-freebsd-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-freebsd-x64@npm:1.33.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "lightningcss-linux-arm-gnueabihf@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-arm-gnueabihf@npm:1.30.2" @@ -9659,6 +9859,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm-gnueabihf@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.33.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "lightningcss-linux-arm64-gnu@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-arm64-gnu@npm:1.30.2" @@ -9666,6 +9873,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "lightningcss-linux-arm64-musl@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-arm64-musl@npm:1.30.2" @@ -9673,6 +9887,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "lightningcss-linux-x64-gnu@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-x64-gnu@npm:1.30.2" @@ -9680,6 +9901,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-x64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "lightningcss-linux-x64-musl@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-x64-musl@npm:1.30.2" @@ -9687,6 +9915,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-x64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-musl@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "lightningcss-win32-arm64-msvc@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-win32-arm64-msvc@npm:1.30.2" @@ -9694,6 +9929,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-win32-arm64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-win32-x64-msvc@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-win32-x64-msvc@npm:1.30.2" @@ -9701,6 +9943,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-win32-x64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "lightningcss@npm:1.30.2": version: 1.30.2 resolution: "lightningcss@npm:1.30.2" @@ -9744,6 +9993,49 @@ __metadata: languageName: node linkType: hard +"lightningcss@npm:^1.33.0": + version: 1.33.0 + resolution: "lightningcss@npm:1.33.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.33.0" + lightningcss-darwin-arm64: "npm:1.33.0" + lightningcss-darwin-x64: "npm:1.33.0" + lightningcss-freebsd-x64: "npm:1.33.0" + lightningcss-linux-arm-gnueabihf: "npm:1.33.0" + lightningcss-linux-arm64-gnu: "npm:1.33.0" + lightningcss-linux-arm64-musl: "npm:1.33.0" + lightningcss-linux-x64-gnu: "npm:1.33.0" + lightningcss-linux-x64-musl: "npm:1.33.0" + lightningcss-win32-arm64-msvc: "npm:1.33.0" + lightningcss-win32-x64-msvc: "npm:1.33.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10/9b3b5db404af352fe5861926b9909281d29bede175539035c1a01e1ad4dcee8bb6f871e8e3d01a67a85e9e1cd18a63e52871a48cf62feab4d1aa868d4f2c3c2e + languageName: node + linkType: hard + "lilconfig@npm:^3.1.1": version: 3.1.3 resolution: "lilconfig@npm:3.1.3" @@ -9798,6 +10090,9 @@ __metadata: ts-loader: "npm:^9.5.1" typescript: "npm:^5.9.3" url-loader: "npm:^4.1.1" + vite: "npm:^8.0.0" + vite-plugin-cx-scss-manifest: "workspace:*" + vite-plugin-transform-cx-imports: "workspace:*" webpack: "npm:^5.103.0" webpack-bundle-analyzer: "npm:^4.10.2" webpack-cleanup-plugin: "npm:^0.5.1" @@ -9976,7 +10271,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.17, magic-string@npm:^0.30.21": +"magic-string@npm:^0.30.0, magic-string@npm:^0.30.17, magic-string@npm:^0.30.21": version: 0.30.21 resolution: "magic-string@npm:0.30.21" dependencies: @@ -11170,6 +11465,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.16": + version: 3.3.16 + resolution: "nanoid@npm:3.3.16" + bin: + nanoid: bin/nanoid.cjs + checksum: 10/8004af92b5541af1dbd23b69845b5026f777d5b7ef07163cea1837aae86e052ced8b383cecbf8a4f1b5e77ae207df96dc45e16b9e0fa3c4b761d085f1e42851b + languageName: node + linkType: hard + "negotiator@npm:0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" @@ -11821,6 +12125,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.4, picomatch@npm:^4.0.5": + version: 4.0.5 + resolution: "picomatch@npm:4.0.5" + checksum: 10/8bad770af9dcdb7f94ad1a893adcbe08a97d75a18872f8fed37161d3124217471c402b3929f051532f795f4ff2e53dcebb1644df4c1a5f3a9c476fef3b9f8c51 + languageName: node + linkType: hard + "pify@npm:^2.0.0": version: 2.3.0 resolution: "pify@npm:2.3.0" @@ -12006,6 +12317,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.23": + version: 8.5.25 + resolution: "postcss@npm:8.5.25" + dependencies: + nanoid: "npm:^3.3.16" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10/68aaeb314a1b2407e11e926acc0462b01b3e911d3d9e755d06adc5b2ececdefe8432dc3c3c93eacc07138952e17997d4674529061b8e3a338a7ac4c1accfdf98 + languageName: node + linkType: hard + "prettier@npm:^3.3.3": version: 3.6.2 resolution: "prettier@npm:3.6.2" @@ -12830,6 +13152,64 @@ __metadata: languageName: node linkType: hard +"rolldown@npm:~1.2.0": + version: 1.2.1 + resolution: "rolldown@npm:1.2.1" + dependencies: + "@oxc-project/types": "npm:=0.142.0" + "@rolldown/binding-android-arm64": "npm:1.2.1" + "@rolldown/binding-darwin-arm64": "npm:1.2.1" + "@rolldown/binding-darwin-x64": "npm:1.2.1" + "@rolldown/binding-freebsd-x64": "npm:1.2.1" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.1" + "@rolldown/binding-linux-arm64-gnu": "npm:1.2.1" + "@rolldown/binding-linux-arm64-musl": "npm:1.2.1" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.1" + "@rolldown/binding-linux-s390x-gnu": "npm:1.2.1" + "@rolldown/binding-linux-x64-gnu": "npm:1.2.1" + "@rolldown/binding-linux-x64-musl": "npm:1.2.1" + "@rolldown/binding-openharmony-arm64": "npm:1.2.1" + "@rolldown/binding-wasm32-wasi": "npm:1.2.1" + "@rolldown/binding-win32-arm64-msvc": "npm:1.2.1" + "@rolldown/binding-win32-x64-msvc": "npm:1.2.1" + "@rolldown/pluginutils": "npm:^1.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: ./bin/cli.mjs + checksum: 10/32cdaf5488cb64700907d27ede2a5936d3c1acb88e2b701bf6ecf88846fbed5f3b23f5da55ba101a0cebe2f0d6f5a746fa66b81692adfb806feb44b784d06de7 + languageName: node + linkType: hard + "rollup-plugin-prettier@npm:^4.1.1": version: 4.1.2 resolution: "rollup-plugin-prettier@npm:4.1.2" @@ -14276,6 +14656,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.17": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10/f85e8a217d675c3f78d5f0ad25ea4557e7e023ed13ddc2b014da10bd0312eea53a34cd52356af07ccdff777f1243012547656282a4ca70936f68bf5065fbaa71 + languageName: node + linkType: hard + "tinyrainbow@npm:^3.0.3": version: 3.0.3 resolution: "tinyrainbow@npm:3.0.3" @@ -15075,6 +15465,29 @@ __metadata: languageName: node linkType: hard +"vite-plugin-cx-scss-manifest@workspace:*, vite-plugin-cx-scss-manifest@workspace:packages/vite-plugin-cx-scss-manifest": + version: 0.0.0-use.local + resolution: "vite-plugin-cx-scss-manifest@workspace:packages/vite-plugin-cx-scss-manifest" + dependencies: + es-module-lexer: "npm:^1.5.0" + peerDependencies: + cx: "*" + vite: ">=4.0.0" + languageName: unknown + linkType: soft + +"vite-plugin-transform-cx-imports@workspace:*, vite-plugin-transform-cx-imports@workspace:packages/vite-plugin-transform-cx-imports": + version: 0.0.0-use.local + resolution: "vite-plugin-transform-cx-imports@workspace:packages/vite-plugin-transform-cx-imports" + dependencies: + es-module-lexer: "npm:^1.5.0" + magic-string: "npm:^0.30.0" + peerDependencies: + cx: "*" + vite: ">=4.0.0" + languageName: unknown + linkType: soft + "vite@npm:^6.0.0 || ^7.0.0": version: 7.3.1 resolution: "vite@npm:7.3.1" @@ -15185,6 +15598,63 @@ __metadata: languageName: node linkType: hard +"vite@npm:^8.0.0": + version: 8.2.0 + resolution: "vite@npm:8.2.0" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.33.0" + picomatch: "npm:^4.0.5" + postcss: "npm:^8.5.23" + rolldown: "npm:~1.2.0" + tinyglobby: "npm:^0.2.17" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.4.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10/c019ae45923186aeced9c7ccd661250473e43eb5a136e8031a7ee2b02b69d9dfe5bd6a267130e2888a1dc69a3e5481779e467f5c2e0f15671e9360b40a8056ff + languageName: node + linkType: hard + "vitefu@npm:^1.1.1": version: 1.1.1 resolution: "vitefu@npm:1.1.1"