From 05fe7a8876c2e9e13fe0b1c9c13df921e26cc2a1 Mon Sep 17 00:00:00 2001 From: daniel-stoian-lgp Date: Thu, 9 Jul 2026 15:33:24 +0300 Subject: [PATCH 1/3] added vite support --- index.js | 5 +- plugins/ILibPlugin/ilib-paths.js | 49 +++++ plugins/ILibPlugin/index.js | 35 +--- plugins/PrerenderPlugin/index.js | 64 +------ plugins/PrerenderPlugin/parse-locales.js | 74 ++++++++ plugins/ViteHtmlPlugin/README.md | 34 ++++ plugins/ViteHtmlPlugin/index.js | 129 ++++++++++++++ plugins/ViteILibPlugin/README.md | 54 ++++++ plugins/ViteILibPlugin/index.js | 218 +++++++++++++++++++++++ plugins/ViteWebOSMetaPlugin/README.md | 37 ++++ plugins/ViteWebOSMetaPlugin/index.js | 126 +++++++++++++ plugins/WebOSMetaPlugin/appinfo.js | 60 +++++++ plugins/WebOSMetaPlugin/index.js | 47 +---- 13 files changed, 788 insertions(+), 144 deletions(-) create mode 100644 plugins/ILibPlugin/ilib-paths.js create mode 100644 plugins/PrerenderPlugin/parse-locales.js create mode 100644 plugins/ViteHtmlPlugin/README.md create mode 100644 plugins/ViteHtmlPlugin/index.js create mode 100644 plugins/ViteILibPlugin/README.md create mode 100644 plugins/ViteILibPlugin/index.js create mode 100644 plugins/ViteWebOSMetaPlugin/README.md create mode 100644 plugins/ViteWebOSMetaPlugin/index.js create mode 100644 plugins/WebOSMetaPlugin/appinfo.js diff --git a/index.js b/index.js index acb88a2..d1315ca 100644 --- a/index.js +++ b/index.js @@ -26,5 +26,8 @@ exportOnDemand({ PrerenderPlugin: () => require('./plugins/PrerenderPlugin'), SnapshotPlugin: () => require('./plugins/SnapshotPlugin'), VerboseLogPlugin: () => require('./plugins/VerboseLogPlugin'), - WebOSMetaPlugin: () => require('./plugins/WebOSMetaPlugin') + WebOSMetaPlugin: () => require('./plugins/WebOSMetaPlugin'), + ViteHtmlPlugin: () => require('./plugins/ViteHtmlPlugin'), + ViteILibPlugin: () => require('./plugins/ViteILibPlugin'), + ViteWebOSMetaPlugin: () => require('./plugins/ViteWebOSMetaPlugin') }); diff --git a/plugins/ILibPlugin/ilib-paths.js b/plugins/ILibPlugin/ilib-paths.js new file mode 100644 index 0000000..96bcf8d --- /dev/null +++ b/plugins/ILibPlugin/ilib-paths.js @@ -0,0 +1,49 @@ +/* eslint-env node, es6 */ +/** + * ilib-paths + * + * Path/constant helpers shared by `ILibPlugin` (webpack) and `ViteILibPlugin` + * (Vite). Kept dependency-free (only `fs`/`path`) so the Vite path can reuse them + * without pulling webpack, tapable, or fast-glob (which `ILibPlugin/index.js` + * requires at module load). + */ +const fs = require('fs'); +const path = require('path'); + +// Walk up from `dir` looking for `node_modules/`; return its path relative to cwd. +function packageSearch(dir, pkg) { + let pkgPath; + if (!path.isAbsolute(dir)) dir = path.join(process.cwd(), dir); + while (dir.length > 0 && dir !== path.dirname(dir) && !pkgPath) { + const full = path.join(dir, 'node_modules', pkg); + if (fs.existsSync(full)) { + pkgPath = path.relative(process.cwd(), full); + } else { + dir = path.dirname(dir); + } + } + return pkgPath; +} + +// Normalize a filepath to be relative to the context, using forward-slashes, and +// replace each '..' with '_', keeping in line with the file-loader and other standards. +function transformPath(context, file) { + return path + .relative(context, file) + .replace(/\\/g, '/') + .replace(/\.\.(\/)?/g, '_$1'); +} + +// The `ILIB__PATH` constant name for a bundle/package. +function bundleConst(name) { + return ( + 'ILIB_' + + path + .basename(name) + .toUpperCase() + .replace(/[-_\s]/g, '_') + + '_PATH' + ); +} + +module.exports = {packageSearch, transformPath, bundleConst}; diff --git a/plugins/ILibPlugin/index.js b/plugins/ILibPlugin/index.js index a82ffbc..1c40e8f 100644 --- a/plugins/ILibPlugin/index.js +++ b/plugins/ILibPlugin/index.js @@ -4,6 +4,7 @@ const fs = require('graceful-fs'); const {SyncWaterfallHook} = require('tapable'); const {ContextReplacementPlugin, Compilation, DefinePlugin, Template, sources} = require('webpack'); const app = require('../../option-parser'); +const {packageSearch, transformPath, bundleConst} = require('./ilib-paths'); function packageName(file) { try { @@ -13,46 +14,12 @@ function packageName(file) { } } -function packageSearch(dir, pkg) { - let pkgPath; - if (!path.isAbsolute(dir)) dir = path.join(process.cwd(), dir); - while (dir.length > 0 && dir !== path.dirname(dir) && !pkgPath) { - const full = path.join(dir, 'node_modules', pkg); - if (fs.existsSync(full)) { - pkgPath = path.relative(process.cwd(), full); - } else { - dir = path.dirname(dir); - } - } - return pkgPath; -} - // Determine if it's a NodeJS output filesystem or if it's a foreign/virtual one. // The internal webpack5 implementation of outputFileSystem is graceful-fs. function isNodeOutputFS(compiler) { return compiler.outputFileSystem && JSON.stringify(compiler.outputFileSystem) === JSON.stringify(fs); } -// Normalize a filepath to be relative to the webpack context, using forward-slashes, and -// replace each '..' with '_', keeping in line with the file-loader and other webpack standards. -function transformPath(context, file) { - return path - .relative(context, file) - .replace(/\\/g, '/') - .replace(/\.\.(\/)?/g, '_$1'); -} - -function bundleConst(name) { - return ( - 'ILIB_' + - path - .basename(name) - .toUpperCase() - .replace(/[-_\s]/g, '_') + - '_PATH' - ); -} - function resolveBundle({dir, context, symlinks, relative, publicPath}) { const bundle = {resolved: dir, path: dir, emit: true}; if (path.isAbsolute(bundle.path)) { diff --git a/plugins/PrerenderPlugin/index.js b/plugins/PrerenderPlugin/index.js index 85c6376..6bc70e8 100644 --- a/plugins/PrerenderPlugin/index.js +++ b/plugins/PrerenderPlugin/index.js @@ -6,6 +6,7 @@ const {htmlTagObjectToString} = require('html-webpack-plugin/lib/html-tags'); const {Compilation, sources} = require('webpack'); const templates = require('./templates'); const vdomServer = require('./vdom-server-render'); +const {parseLocales} = require('./parse-locales'); const prerenderPluginHooksMap = new WeakMap(); @@ -338,69 +339,6 @@ function isNodeOutputFS(compiler) { return compiler.outputFileSystem && JSON.stringify(compiler.outputFileSystem) === JSON.stringify(gracefulFs); } -// Determine the desired target locales based of option content. -// Can be a preset like 'tv' or 'signage', 'used' for all used app-level locales, 'all' for -// all locales supported by ilib, a custom json file input, or a comma-separated lists -function parseLocales(context, target) { - if (!target || target === 'none') { - return []; - } else if (Array.isArray(target)) { - return target; - } else if (target.toLowerCase() === 'webos') { - return JSON.parse(fs.readFileSync(path.join(__dirname, 'locales-webos.json'), {encoding: 'utf8'})).locales; - } else if (target === 'tv') { - return JSON.parse(fs.readFileSync(path.join(__dirname, 'locales-tv.json'), {encoding: 'utf8'})).locales; - } else if (target === 'signage') { - return JSON.parse(fs.readFileSync(path.join(__dirname, 'locales-signage.json'), {encoding: 'utf8'})).locales; - } else if (target === 'used') { - return detectLocales(path.join(context, 'resources', 'ilibmanifest.json')); - } else if (target === 'all') { - return detectLocales(path.join('node_modules', 'ilib', 'locale', 'ilibmanifest.json'), true); - } else if (/\.json$/i.test(target)) { - return JSON.parse(fs.readFileSync(target, {encoding: 'utf8'})).locales; - } else { - return target.split(/\s*[\n,]\s*/).filter(Boolean); - } -} - -// Scan an ilib manifest and detect all locales that it uses. -function detectLocales(manifest, deepestOnly) { - try { - const meta = JSON.parse(fs.readFileSync(manifest, {encoding: 'utf8'})); - const locales = []; - let curr, currLocale; - for (let i = 0; meta.files && i < meta.files.length; i++) { - curr = path.dirname(meta.files[i]); - currLocale = curr.replace(/[\\/]+/, '-'); - if (locales.indexOf(curr) === -1 && /^([a-z]{2})\b/.test(currLocale)) { - if (deepestOnly) { - // Remove any matches of parent directories. - for (let x = curr; x.indexOf('/') !== -1 || x.indexOf('\\') !== -1; x = path.dirname(x)) { - const index = locales.indexOf(x.replace(/[\\/]+/, '-')); - if (index >= 0) { - locales.splice(index, 1); - } - } - // Only add the entry if children aren't already in the list. - let childFound = false; - for (let k = 0; k < locales.length && !childFound; k++) { - childFound = locales[k].indexOf(currLocale) === 0; - } - if (!childFound) { - locales.push(currLocale); - } - } else { - locales.push(currLocale); - } - } - } - locales.sort((a, b) => a.split('-').length > b.split('-').length); - return locales; - } catch (e) { - return []; - } -} - // Simplifies and groups the locales and aliases to ensure minimal output needed. function simplifyAliases(locales, status) { const links = {}; diff --git a/plugins/PrerenderPlugin/parse-locales.js b/plugins/PrerenderPlugin/parse-locales.js new file mode 100644 index 0000000..2b01289 --- /dev/null +++ b/plugins/PrerenderPlugin/parse-locales.js @@ -0,0 +1,74 @@ +/* eslint-env node, es6 */ +/** + * parse-locales + * + * Locale-list resolution shared by `PrerenderPlugin` (webpack `--isomorphic`) and + * `ViteILibPlugin` (Vite iLib locale filtering, `-l`). + */ +const fs = require('fs'); +const path = require('path'); + +// Determine the desired target locales based of option content. +// Can be a preset like 'tv' or 'signage', 'used' for all used app-level locales, 'all' for +// all locales supported by ilib, a custom json file input, or a comma-separated lists +function parseLocales(context, target) { + if (!target || target === 'none') { + return []; + } else if (Array.isArray(target)) { + return target; + } else if (target.toLowerCase() === 'webos') { + return JSON.parse(fs.readFileSync(path.join(__dirname, 'locales-webos.json'), {encoding: 'utf8'})).locales; + } else if (target === 'tv') { + return JSON.parse(fs.readFileSync(path.join(__dirname, 'locales-tv.json'), {encoding: 'utf8'})).locales; + } else if (target === 'signage') { + return JSON.parse(fs.readFileSync(path.join(__dirname, 'locales-signage.json'), {encoding: 'utf8'})).locales; + } else if (target === 'used') { + return detectLocales(path.join(context, 'resources', 'ilibmanifest.json')); + } else if (target === 'all') { + return detectLocales(path.join('node_modules', 'ilib', 'locale', 'ilibmanifest.json'), true); + } else if (/\.json$/i.test(target)) { + return JSON.parse(fs.readFileSync(target, {encoding: 'utf8'})).locales; + } else { + return target.split(/\s*[\n,]\s*/).filter(Boolean); + } +} + +// Scan an ilib manifest and detect all locales that it uses. +function detectLocales(manifest, deepestOnly) { + try { + const meta = JSON.parse(fs.readFileSync(manifest, {encoding: 'utf8'})); + const locales = []; + let curr, currLocale; + for (let i = 0; meta.files && i < meta.files.length; i++) { + curr = path.dirname(meta.files[i]); + currLocale = curr.replace(/[\\/]+/, '-'); + if (locales.indexOf(curr) === -1 && /^([a-z]{2})\b/.test(currLocale)) { + if (deepestOnly) { + // Remove any matches of parent directories. + for (let x = curr; x.indexOf('/') !== -1 || x.indexOf('\\') !== -1; x = path.dirname(x)) { + const index = locales.indexOf(x.replace(/[\\/]+/, '-')); + if (index >= 0) { + locales.splice(index, 1); + } + } + // Only add the entry if children aren't already in the list. + let childFound = false; + for (let k = 0; k < locales.length && !childFound; k++) { + childFound = locales[k].indexOf(currLocale) === 0; + } + if (!childFound) { + locales.push(currLocale); + } + } else { + locales.push(currLocale); + } + } + } + locales.sort((a, b) => a.split('-').length > b.split('-').length); + return locales; + } catch (e) { + return []; + } +} + +module.exports = {parseLocales, detectLocales}; diff --git a/plugins/ViteHtmlPlugin/README.md b/plugins/ViteHtmlPlugin/README.md new file mode 100644 index 0000000..f297db2 --- /dev/null +++ b/plugins/ViteHtmlPlugin/README.md @@ -0,0 +1,34 @@ +# ViteHtmlPlugin + +Vite counterpart to the `HtmlWebpackPlugin` wiring used by `@enact/cli`. Enact +apps have no `index.html`; this plugin synthesizes the document from the same +`.ejs` template the webpack build uses and injects the app entry as an ES module. + +- **Dev server** — serves the synthesized document at `/`, letting Vite inject + its HMR client via `transformIndexHtml`. +- **Production build** — emits `index.html` referencing the hashed entry chunk + and linking its CSS, honoring the configured `base` public path. + +## Usage + +```js +const {ViteHtmlPlugin} = require('@enact/dev-utils'); + +module.exports = { + plugins: [ + ViteHtmlPlugin({ + entry: '/abs/path/to/combined-entry.js', + title: 'My App', + template: '/abs/path/to/html-template.ejs' + }) + ] +}; +``` + +## Options + +| Option | Type | Description | +| --- | --- | --- | +| `entry` | `string` | Absolute path to the (combined polyfills + app) entry module. | +| `title` | `string` | Document title substituted into the template's `<%= … %>` token. | +| `template` | `string` | Absolute path to an `.ejs`/`.html` template. Falls back to a built-in default when omitted. | diff --git a/plugins/ViteHtmlPlugin/index.js b/plugins/ViteHtmlPlugin/index.js new file mode 100644 index 0000000..3217660 --- /dev/null +++ b/plugins/ViteHtmlPlugin/index.js @@ -0,0 +1,129 @@ +/* eslint-env node, es6 */ +/** + * ViteHtmlPlugin + * + * Vite counterpart to the webpack `HtmlWebpackPlugin` wiring used by @enact/cli. + * Enact apps do not ship an `index.html`; Therefore, default Vite bahaviour cannot be applied + * the document is synthesized from an`.ejs` template (the same one the webpack build uses) with the app entry + * injected as an ES module. + * + * It handles both modes: + * - dev server: serves the synthesized document at `/`, letting Vite inject + * its HMR client via `transformIndexHtml`. + * - production build: emits `index.html` with the hashed entry chunk and its + * CSS linked, honoring the configured `base` public path. + * + * Options: + * entry {string} Absolute path to the (combined) app entry module. + * title {string} Document title. + * template {string} Absolute path to an .ejs/.html template (optional). + */ +const fs = require('fs'); + +// A classic (non-module) head script that runs before any deferred module script. +// Enact's `polyfills.js` and core-js reference the Node `global`, which doesn't +// exist in the browser (webpack supplied it via node-polyfill-webpack-plugin). +// Defining it on the global object makes bare `global` references to be resolved without +// modifying any code. `globalThis === window` on the browser main thread. +const NODE_GLOBALS_SHIM = ''; + +// Render the base document from the template (or a sane default), substituting +// the single `<%= ... %>` title token used by the Enact HTML template, and +// injecting the Node-globals shim into . +function baseHtml(template, title) { + let html; + if (template && fs.existsSync(template)) { + html = fs.readFileSync(template, 'utf8').replace(/<%=[^%]*%>/g, title || ''); + } else { + html = + '' + + '' + + '' + + `${title || ''}
`; + } + return /<\/head>/i.test(html) ? html.replace(/<\/head>/i, `${NODE_GLOBALS_SHIM}`) : NODE_GLOBALS_SHIM + html; +} + +// Dev: reference the source entry through Vite's filesystem endpoint so the +// combined polyfills+app module (and its CSS) is loaded and HMR-tracked. +function injectDev(html, entry) { + const src = '/@fs/' + entry.replace(/\\/g, '/'); + const script = ``; + return html.replace(/<\/body>/i, `${script}`); +} + +// Build: reference the emitted entry chunk and link its CSS. +function injectBuild(html, {scriptFile, cssFiles, base}) { + const prefix = base.endsWith('/') ? base : base + '/'; + const links = cssFiles.map(f => ``).join(''); + const script = ``; + return html.replace(/<\/head>/i, `${links}`).replace(/<\/body>/i, `${script}`); +} + +module.exports = function ViteHtmlPlugin({entry, title = '', template} = {}) { + let base = '/'; + return { + name: 'enact-vite-html', + // SPA so Vite doesn't expect an MPA-style html input. + config() { + return {appType: 'spa'}; + }, + configResolved(resolved) { + base = resolved.base || '/'; + }, + // Dev server: intercept the root request and serve the synthesized document. + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + const url = req.url && req.url.split('?')[0]; + if (url !== '/' && url !== '/index.html') return next(); + try { + let html = injectDev(baseHtml(template, title), entry); + html = await server.transformIndexHtml(req.url, html); + res.statusCode = 200; + res.setHeader('Content-Type', 'text/html'); + res.end(html); + } catch (err) { + next(err); + } + }); + }, + // Production build: locate the entry chunk + its CSS and emit index.html. + generateBundle(options, bundle) { + let entryChunk; + const cssFiles = []; + const seen = new Set(); + const addCss = f => { + if (!seen.has(f)) { + seen.add(f); + cssFiles.push(f); + } + }; + + for (const fileName of Object.keys(bundle)) { + const chunk = bundle[fileName]; + if (chunk.type === 'chunk' && chunk.isEntry) { + entryChunk = chunk; + const imported = chunk.viteMetadata && chunk.viteMetadata.importedCss; + if (imported) imported.forEach(addCss); + } + } + // Include any remaining top-level CSS assets (e.g. --no-split-css output). + for (const fileName of Object.keys(bundle)) { + if (bundle[fileName].type === 'asset' && fileName.endsWith('.css')) addCss(fileName); + } + + if (!entryChunk) { + this.warn('ViteHtmlPlugin: no entry chunk found; skipping index.html emission.'); + return; + } + + const html = injectBuild(baseHtml(template, title), { + scriptFile: entryChunk.fileName, + cssFiles, + base + }); + this.emitFile({type: 'asset', fileName: 'index.html', source: html}); + } + }; +}; diff --git a/plugins/ViteILibPlugin/README.md b/plugins/ViteILibPlugin/README.md new file mode 100644 index 0000000..aa75ff6 --- /dev/null +++ b/plugins/ViteILibPlugin/README.md @@ -0,0 +1,54 @@ +# ViteILibPlugin + +Vite counterpart to the webpack `ILibPlugin`. `@enact/i18n`'s runtime loader is +bundler-agnostic (it fetches locale JSON over HTTP from a set of `ILIB_*` global +constants), so this plugin only needs to define those constants and make the data +available at the URLs they point to. + +- **Constants** (via the `config` hook → `define`): `ILIB_BASE_PATH`, + `ILIB_RESOURCES_PATH`, `ILIB_CACHE_ID`, `ILIB_NO_ASSETS`, per-app/per-theme + `ILIB__PATH`, and optional `ILIB_ADDITIONAL_RESOURCES_PATH` — matching the + values the webpack ILibPlugin computes. +- **Data** — build: copies the iLib `locale/`, app `resources/`, and theme + `resources/` trees into the output on `writeBundle` (a directory copy, not 6.7k + individual `emitFile` calls). Dev: serves them from their on-disk source via + middleware. +- **Locale filtering** (`locales` option; webpack's `enact pack -l …`) — when set, + the `ilibmanifest.json` file lists are trimmed to the requested locales plus + shared (non-locale) data, and a trimmed manifest is emitted/served in place of + the full one. Accepts a preset (`used`/`tv`/`signage`/`webos`/`all`), a `.json` + file, or a comma/newline list; without it the full trees are used. Example: + `-l en-US,ko-KR` trims ~70 MB → ~19 MB. + +The webpack plugin's neutralization of iLib's non-browser platform loaders is +handled separately in `@enact/cli`'s `vite.config.js` (`ILIB_LOADER_RE` stub). + +## Usage + +```js +const {ViteILibPlugin} = require('@enact/dev-utils'); + +module.exports = { + plugins: [ + ViteILibPlugin({ + context: appContext, // app root (defaults to cwd) + publicPath: '/', // matches Vite `base` + ilibAdditionalResourcesPath // optional + }) + ] +}; +``` + +## Options + +| Option | Default | Description | +| --- | --- | --- | +| `context` | `process.cwd()` | App root; output paths are computed relative to it. | +| `ilib` | auto-detected | iLib base package path (`@enact/i18n/ilib` or `ilib`). | +| `resources` | `'resources'` | App resources directory. | +| `publicPath` | `'/'` | Public base URL; should match Vite `base`. | +| `symlinks` | `true` | Resolve symlinked packages to their real path. | +| `emit` | `true` | Copy/serve the data. When `false`, sets `ILIB_NO_ASSETS`. | +| `cacheId` | timestamp | Value for `ILIB_CACHE_ID`. | +| `ilibAdditionalResourcesPath` | — | Extra runtime resource path. | +| `locales` | — | Locale-filter target (`-l`): preset / `.json` / comma list. When set, only the matching locale data (plus shared data) is emitted/served. | diff --git a/plugins/ViteILibPlugin/index.js b/plugins/ViteILibPlugin/index.js new file mode 100644 index 0000000..41915ca --- /dev/null +++ b/plugins/ViteILibPlugin/index.js @@ -0,0 +1,218 @@ +/* eslint-env node, es6 */ +/** + * ViteILibPlugin + * + * Vite counterpart to the webpack `ILibPlugin`. `@enact/i18n`'s runtime loader + * (`Loader.js`) is bundler-agnostic. It reads a set of `ILIB_*` global constants + * for the base URLs of iLib locale data / app `resources` / theme bundles, then + * fetches the JSON over HTTP. So this plugin only needs to: + * + * 1. `define` those `ILIB_*` constants (via the `config` hook), matching the + * values the webpack ILibPlugin computes, for the Rollup build and, via + * `optimizeDeps.esbuildOptions.define`, the dev-server pre-bundled deps. + * 2. Make the referenced data available at those URLs: + * - build → copy the source trees into the output on `writeBundle`. + * - dev → serve them from their on-disk source via middleware. + * + * Locale filtering (webpack's `enact pack -l …`) is supported via the `locales` + * option: when set, the iLib/resource `ilibmanifest.json` file lists are trimmed + * to the requested locales (plus shared, non-locale data), and a trimmed manifest + * is emitted/served in place of the full one. Without it, the full trees are + * copied/served (matching an unfiltered webpack build). + * + * The webpack plugin's neutralization of iLib's non-browser platform loaders is + * handled separately in `@enact/cli`'s `vite.config.js` (`ILIB_LOADER_RE` stub). + */ +const path = require('path'); +const fs = require('fs'); +const app = require('../../option-parser'); +// Shared with PrerenderPlugin (webpack `--isomorphic`) — dependency-free locale +// resolution, so reusing it here does not pull webpack into the Vite path. +const {parseLocales} = require('../PrerenderPlugin/parse-locales'); + +// Path/constant helpers shared with ILibPlugin (webpack) — dependency-free. +const {transformPath, bundleConst, packageSearch} = require('../ILibPlugin/ilib-paths'); + +// Join URL segments with single slashes, preserving a leading slash. +function joinUrl(...parts) { + return parts + .filter(p => p != null && p !== '') + .join('/') + .replace(/([^:])\/{2,}/g, '$1/') + .replace(/^\/{2,}/, '/'); +} + +// True if two slash-separated locale paths lie on the same root-to-leaf lineage. +function sameLineage(a, b) { + const as = a.split('/'); + const bs = b.split('/'); + const n = Math.min(as.length, bs.length); + for (let i = 0; i < n; i++) { + if (as[i] !== bs[i]) return false; + } + return true; +} + +module.exports = function ViteILibPlugin(options = {}) { + const opts = Object.assign({}, options); + const context = opts.context || process.cwd(); + const symlinks = opts.symlinks !== false; + const publicPath = opts.publicPath || '/'; + const emit = opts.emit !== false; + + // Locale filtering (webpack `-l`). Null = no filtering (emit everything). + const locales = opts.locales ? parseLocales(context, opts.locales) : null; + const allowedLocales = locales ? locales.map(l => l.replace(/-/g, '/')) : null; + + // Keep a manifest file if it's shared (non-locale) data, or its locale + // directory lies on the lineage of a requested locale. A locale directory has a + // 2-3 char lowercase language code as its first segment; the few 3-char iLib + // data dirs that would collide (nfc/nfd) and the `und` fallback are shared. + // Longer data dirs (zoneinfo, charmaps, charset, ctype, scripts, nfkc, nfkd) + // are excluded by the length test. + const SHARED_TOP = new Set(['nfc', 'nfd', 'und']); + function fileAllowed(relFile) { + if (!allowedLocales) return true; + const dir = path.dirname(relFile).replace(/\\/g, '/'); + if (dir === '.' || dir === '') return true; // root files (shared) + const first = dir.split('/')[0]; + if (SHARED_TOP.has(first) || !/^[a-z]{2,3}$/.test(first)) return true; // shared data + return allowedLocales.some(loc => sameLineage(dir, loc)); + } + + // Resolve the iLib base package (with @enact/i18n/ilib backward-compat), matching ILibPlugin. + const ilibRel = + opts.ilib || + process.env.ILIB_BASE_PATH || + packageSearch(context, path.join('@enact', 'i18n', 'ilib')) || + packageSearch(context, 'ilib'); + const resourcesRel = opts.resources || 'resources'; + + const defined = {}; + // {urlDir, srcDir, files} — files is a filtered manifest list, or null for a full copy. + const assets = []; + + function addBundle(name, dirRel, subdir) { + if (!dirRel) return; + let abs = path.isAbsolute(dirRel) ? dirRel : path.join(context, dirRel); + if (symlinks && fs.existsSync(abs)) abs = fs.realpathSync(abs); + const rel = transformPath(context, abs); + const url = joinUrl(publicPath, rel); + if (name) defined[name] = JSON.stringify(url); + const srcDir = subdir ? path.join(abs, subdir) : abs; + const urlDir = subdir ? joinUrl(rel, subdir) : rel; + if (emit && fs.existsSync(srcDir)) { + let files = null; + if (allowedLocales) { + const manifest = path.join(srcDir, 'ilibmanifest.json'); + if (fs.existsSync(manifest)) { + try { + const all = JSON.parse(fs.readFileSync(manifest, {encoding: 'utf8'})).files || []; + files = all.filter(fileAllowed); + } catch (e) { + files = null; + } + } + } + assets.push({urlDir, srcDir, files}); + } + return url; + } + + // iLib base data lives under /locale; ILIB_BASE_PATH points at . + addBundle('ILIB_BASE_PATH', ilibRel, 'locale'); + // App resources. + const resourcesUrl = addBundle('ILIB_RESOURCES_PATH', resourcesRel); + // Per-app + per-theme bundle path constants (theme resources supply locale data too). + defined[bundleConst(app.name)] = JSON.stringify(resourcesUrl); + let pkgDir = context; + for (let t = app.theme; t; t = t.theme) { + const themeDir = packageSearch(pkgDir, t.name); + if (themeDir) { + pkgDir = themeDir; + addBundle(bundleConst(t.name), themeDir, 'resources'); + } + } + + defined.ILIB_CACHE_ID = JSON.stringify(String(opts.cacheId || 'enact-ilib-' + Date.now())); + defined.ILIB_NO_ASSETS = JSON.stringify(!emit); + if (opts.ilibAdditionalResourcesPath) { + defined.ILIB_ADDITIONAL_RESOURCES_PATH = JSON.stringify(opts.ilibAdditionalResourcesPath); + } + + let resolvedBase = publicPath; + + return { + name: 'enact-vite-ilib', + // Inject the ILIB_* constants as build-time defines. `define` covers the + // Rollup build and live-transformed dev modules; `optimizeDeps.esbuildOptions + // .define` is required additionally so the constants also reach dev-server + // pre-bundled dependencies (e.g. @enact/i18n's Loader) — Vite does not apply + // `config.define` to the dep optimizer for arbitrary global constants. + config() { + return { + define: defined, + optimizeDeps: {esbuildOptions: {define: defined}} + }; + }, + configResolved(resolved) { + resolvedBase = resolved.base || publicPath; + }, + // Build: copy the data into the output — the filtered file set (with a trimmed + // manifest) when locale filtering is active, otherwise the whole tree. + writeBundle(outputOptions) { + const outDir = outputOptions.dir || (outputOptions.file && path.dirname(outputOptions.file)); + if (!outDir) return; + for (const {urlDir, srcDir, files} of assets) { + const dest = path.join(outDir, urlDir); + try { + if (files) { + fs.mkdirSync(dest, {recursive: true}); + for (const f of files) { + const s = path.join(srcDir, f); + const d = path.join(dest, f); + if (fs.existsSync(s)) { + fs.mkdirSync(path.dirname(d), {recursive: true}); + fs.copyFileSync(s, d); + } + } + fs.writeFileSync(path.join(dest, 'ilibmanifest.json'), JSON.stringify({files}, null, '\t')); + } else { + fs.cpSync(srcDir, dest, {recursive: true}); + } + } catch (e) { + this.warn(`ViteILibPlugin: failed to copy iLib data ${srcDir} -> ${dest}: ${e.message}`); + } + } + }, + // Dev: serve the data from its on-disk source; when filtering, serve the + // trimmed manifest and only allow the kept files. + configureServer(server) { + const routes = assets.map(({urlDir, srcDir, files}) => ({ + prefix: joinUrl(resolvedBase, urlDir).replace(/\/?$/, '/'), + srcDir, + allowed: files ? new Set(files.map(f => f.replace(/\\/g, '/'))) : null, + manifest: files ? JSON.stringify({files}, null, '\t') : null + })); + server.middlewares.use((req, res, next) => { + const url = decodeURIComponent((req.url || '').split('?')[0]); + const route = routes.find(r => url.startsWith(r.prefix)); + if (!route) return next(); + const rel = url.slice(route.prefix.length); + if (route.manifest && rel === 'ilibmanifest.json') { + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + return res.end(route.manifest); + } + if (route.allowed && !route.allowed.has(rel)) return next(); + const file = path.join(route.srcDir, rel); + if (!file.startsWith(route.srcDir) || !fs.existsSync(file) || !fs.statSync(file).isFile()) { + return next(); + } + res.statusCode = 200; + res.setHeader('Content-Type', file.endsWith('.json') ? 'application/json' : 'application/octet-stream'); + fs.createReadStream(file).pipe(res); + }); + } + }; +}; diff --git a/plugins/ViteWebOSMetaPlugin/README.md b/plugins/ViteWebOSMetaPlugin/README.md new file mode 100644 index 0000000..156394d --- /dev/null +++ b/plugins/ViteWebOSMetaPlugin/README.md @@ -0,0 +1,37 @@ +# ViteWebOSMetaPlugin + +Vite counterpart to the webpack `WebOSMetaPlugin`. Discovers the root +`appinfo.json` (project root or `./webos-meta/`) and any localized +`resources/**/appinfo.json`, and makes them — plus the image assets they +reference (`icon`, `largeIcon`, `splashBackground`, …) — available in the output. + +- **build** — writes the appinfo file(s) and copies referenced assets into the + output on `writeBundle`. +- **dev** — serves them from computed content / on-disk source via middleware. + +The `` fallback (use `appinfo.title` when no app/theme title is set) is +applied in `@enact/cli`'s `vite.config.js` via the static +`ViteWebOSMetaPlugin.readTitle(context)` helper, since `ViteHtmlPlugin` owns the +HTML document. + +## Usage + +```js +const {ViteWebOSMetaPlugin} = require('@enact/dev-utils'); + +module.exports = { + plugins: [ViteWebOSMetaPlugin({context: appContext, publicPath: '/'})] +}; +``` + +## Options + +| Option | Default | Description | +| --- | --- | --- | +| `context` | `process.cwd()` | App root. | +| `path` | — | Explicit directory to search for `appinfo.json` first. | +| `publicPath` | `'/'` | Public base URL; should match Vite `base`. | + +## Not yet migrated + +`$`-prefixed system assets (`sys-assets/<spec>/…`). diff --git a/plugins/ViteWebOSMetaPlugin/index.js b/plugins/ViteWebOSMetaPlugin/index.js new file mode 100644 index 0000000..1db182d --- /dev/null +++ b/plugins/ViteWebOSMetaPlugin/index.js @@ -0,0 +1,126 @@ +/* eslint-env node, es6 */ +/** + * ViteWebOSMetaPlugin + * + * Vite counterpart to the webpack `WebOSMetaPlugin`. Discovers the root + * `appinfo.json` (in the project root or `./webos-meta/`) plus any localized + * `resources/**\/appinfo.json`, and makes them — together with the image assets + * they reference (icons, splash, etc.) — available in the output: + * - build → written/copied into the output on `writeBundle`. + * - dev → served from computed content / on-disk source via middleware. + * + * The document `<title>` fallback (use appinfo.title when no app title is set) is + * handled in `vite.config.js` where `ViteHtmlPlugin` gets its title, since that + * plugin owns the HTML document. + * + * `$`-prefixed system assets (sys-assets/<spec>/…) are not yet handled. + * + * Options: {context, path (explicit appinfo dir), publicPath} + */ +const fs = require('fs'); +const path = require('path'); +const glob = require('fast-glob'); +// appinfo helpers shared with WebOSMetaPlugin (webpack) — dependency-free. +const {props: ASSET_PROPS, readAppInfo, rootAppInfo} = require('../WebOSMetaPlugin/appinfo'); + +const MIME = { + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.json': 'application/json' +}; + +function joinUrl(...parts) { + return parts + .filter(p => p != null && p !== '') + .join('/') + .replace(/([^:])\/{2,}/g, '$1/') + .replace(/^\/{2,}/, '/'); +} + +// Read the appinfo title (root) for use as an HTML <title> fallback. +ViteWebOSMetaPlugin.readTitle = function (context, specific) { + const meta = rootAppInfo(context, specific); + return meta && meta.obj && meta.obj.title; +}; + +function ViteWebOSMetaPlugin(options = {}) { + const context = options.context || process.cwd(); + const scan = options.path; + const publicPath = options.publicPath || '/'; + + // Output entries: {name: output-relative path, src?: source file, source?: string content}. + const outputs = []; + + function addAssets(metaDir, outDir, appinfo) { + for (const prop of ASSET_PROPS) { + const val = appinfo[prop]; + if (val && val.charAt(0) !== '$') { + const src = path.resolve(metaDir, val); + if (fs.existsSync(src)) { + outputs.push({name: joinUrl(outDir, val), src}); + } + } + } + } + + // Root appinfo + its assets. + const meta = rootAppInfo(context, scan); + if (meta && meta.obj) { + addAssets(meta.path, '', meta.obj); + outputs.push({name: 'appinfo.json', source: JSON.stringify(meta.obj, null, '\t')}); + } + + // Localized appinfo files under resources/ + their assets. + const localized = glob.sync('resources/**/appinfo.json', {cwd: context, onlyFiles: true}); + for (const rel of localized) { + const file = path.join(context, rel); + const locMeta = readAppInfo(file); + if (locMeta) { + addAssets(path.dirname(file), path.dirname(rel), locMeta); + outputs.push({name: rel.replace(/\\/g, '/'), source: JSON.stringify(locMeta, null, '\t')}); + } + } + + let resolvedBase = publicPath; + + return { + name: 'enact-vite-webosmeta', + configResolved(resolved) { + resolvedBase = resolved.base || publicPath; + }, + // Build: write appinfo.json(s) and copy their referenced assets into the output. + writeBundle(outputOptions) { + const outDir = outputOptions.dir || (outputOptions.file && path.dirname(outputOptions.file)); + if (!outDir) return; + for (const o of outputs) { + const dest = path.join(outDir, o.name); + try { + fs.mkdirSync(path.dirname(dest), {recursive: true}); + if (o.src) fs.copyFileSync(o.src, dest); + else fs.writeFileSync(dest, o.source); + } catch (e) { + this.warn(`ViteWebOSMetaPlugin: failed to emit ${o.name}: ${e.message}`); + } + } + }, + // Dev: serve the appinfo files and assets from computed content / source. + configureServer(server) { + const routes = new Map(); + for (const o of outputs) routes.set(joinUrl(resolvedBase, o.name), o); + server.middlewares.use((req, res, next) => { + const url = decodeURIComponent((req.url || '').split('?')[0]); + const o = routes.get(url); + if (!o) return next(); + res.statusCode = 200; + res.setHeader('Content-Type', MIME[path.extname(o.name).toLowerCase()] || 'application/octet-stream'); + if (o.src) fs.createReadStream(o.src).pipe(res); + else res.end(o.source); + }); + } + }; +} + +module.exports = ViteWebOSMetaPlugin; diff --git a/plugins/WebOSMetaPlugin/appinfo.js b/plugins/WebOSMetaPlugin/appinfo.js new file mode 100644 index 0000000..641e902 --- /dev/null +++ b/plugins/WebOSMetaPlugin/appinfo.js @@ -0,0 +1,60 @@ +/* eslint-env node, es6 */ +/** + * appinfo + * + * appinfo.json helpers shared by `WebOSMetaPlugin` (webpack) and + * `ViteWebOSMetaPlugin` (Vite). Kept dependency-free (only `fs`/`path`) so the + * Vite path can reuse them without pulling webpack, tapable, or fast-glob (which + * `WebOSMetaPlugin/index.js` requires at module load). + */ +const fs = require('fs'); +const path = require('path'); + +// List of asset-pointing appinfo properties. +const props = [ + 'icon', + 'largeIcon', + 'extraLargeIcon', + 'miniicon', + 'smallicon', + 'splashicon', + 'splashBackground', + 'bgImage', + 'imageForRecents' +]; + +function readAppInfo(file) { + // Read and parse appinfo.json file if it exists. + if (fs.existsSync(file)) { + try { + const meta = JSON.parse(fs.readFileSync(file, {encoding: 'utf8'})); + return meta; + } catch (e) { + console.log('ERROR: unable to read/parse appinfo.json at ' + file); + } + } +} + +function rootAppInfo(context, specific) { + // The accepted root locations to search for the appinfo.json and its relative + // assets are project root or ./webos-meta. + const rootDir = [context, path.join(context, './webos-meta')]; + // If a specific path is requested, prepend it to the search list + if (specific) { + if (path.isAbsolute(specific)) { + rootDir.unshift(specific); + } else { + rootDir.unshift(path.join(context, specific)); + } + } + // Check each search location, and if found, return the data and path it was found at. + let meta; + for (let i = 0; i < rootDir.length; i++) { + meta = readAppInfo(path.join(rootDir[i], 'appinfo.json')); + if (meta) { + return {path: rootDir[i], obj: meta}; + } + } +} + +module.exports = {props, readAppInfo, rootAppInfo}; diff --git a/plugins/WebOSMetaPlugin/index.js b/plugins/WebOSMetaPlugin/index.js index 1ba9ead..87913e1 100644 --- a/plugins/WebOSMetaPlugin/index.js +++ b/plugins/WebOSMetaPlugin/index.js @@ -3,19 +3,8 @@ const path = require('path'); const glob = require('fast-glob'); const {SyncWaterfallHook} = require('tapable'); const {Compilation, sources} = require('webpack'); +const {props, readAppInfo, rootAppInfo} = require('./appinfo'); -// List of asset-pointing appinfo properties. -const props = [ - 'icon', - 'largeIcon', - 'extraLargeIcon', - 'miniicon', - 'smallicon', - 'splashicon', - 'splashBackground', - 'bgImage', - 'imageForRecents' -]; // System assets starting with '$' are dynamic and will be within a variable // directory within sysAssetsPath to denote system spec ('HD720', 'HD1080', etc.). let sysAssetsPath = 'sys-assets'; @@ -25,18 +14,6 @@ let variableSysPaths = null; // share assets. const assetPathCache = {}; -function readAppInfo(file) { - // Read and parse appinfo.json file if it exists. - if (fs.existsSync(file)) { - try { - const meta = JSON.parse(fs.readFileSync(file, {encoding: 'utf8'})); - return meta; - } catch (e) { - console.log('ERROR: unable to read/parse appinfo.json at ' + file); - } - } -} - function handleSysAssetPath(context, appinfo) { // If the sysAsset base is specified, override the default one if (appinfo.sysAssetsBasePath && appinfo.sysAssetsBasePath !== sysAssetsPath) { @@ -72,28 +49,6 @@ function detectSysAssets(name) { return result; } -function rootAppInfo(context, specific) { - // The accepted root locations to search for the appinfo.json and its relative - // assets are project root or ./webos-meta. - const rootDir = [context, path.join(context, './webos-meta')]; - // If a specific path is requested, prepend it to the search list - if (specific) { - if (path.isAbsolute(specific)) { - rootDir.unshift(specific); - } else { - rootDir.unshift(path.join(context, specific)); - } - } - // Check each search location, and if found, return the data and path it was found at. - let meta; - for (let i = 0; i < rootDir.length; i++) { - meta = readAppInfo(path.join(rootDir[i], 'appinfo.json')); - if (meta) { - return {path: rootDir[i], obj: meta}; - } - } -} - function addMetaAssets(metaDir, outDir, appinfo, compilation) { // For each appinfo.json property that contains a webos meta asset, resolve that asset, // and add its data to the compilation assets array. From 388b5fcf46b9c9513e20ae195fc4faabf3b83843 Mon Sep 17 00:00:00 2001 From: daniel-stoian-lgp <daniel.stoian@lgepartner.com> Date: Thu, 9 Jul 2026 17:57:59 +0300 Subject: [PATCH 2/3] added vite support --- plugins/ViteILibPlugin/index.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/ViteILibPlugin/index.js b/plugins/ViteILibPlugin/index.js index 41915ca..dcbf1e9 100644 --- a/plugins/ViteILibPlugin/index.js +++ b/plugins/ViteILibPlugin/index.js @@ -92,15 +92,19 @@ module.exports = function ViteILibPlugin(options = {}) { // {urlDir, srcDir, files} — files is a filtered manifest list, or null for a full copy. const assets = []; - function addBundle(name, dirRel, subdir) { + function addBundle(name, dirRel, subdir, loaderAppendsSubdir) { if (!dirRel) return; let abs = path.isAbsolute(dirRel) ? dirRel : path.join(context, dirRel); if (symlinks && fs.existsSync(abs)) abs = fs.realpathSync(abs); const rel = transformPath(context, abs); - const url = joinUrl(publicPath, rel); - if (name) defined[name] = JSON.stringify(url); const srcDir = subdir ? path.join(abs, subdir) : abs; const urlDir = subdir ? joinUrl(rel, subdir) : rel; + // The constant must point at the directory the runtime loader fetches from. + // For theme/app resources that is the served data dir (urlDir). For the iLib + // *base*, the loader appends the `locale/` subdir itself, so the constant + // points at the package dir (rel) instead. + const url = joinUrl(publicPath, loaderAppendsSubdir ? rel : urlDir); + if (name) defined[name] = JSON.stringify(url); if (emit && fs.existsSync(srcDir)) { let files = null; if (allowedLocales) { @@ -119,8 +123,9 @@ module.exports = function ViteILibPlugin(options = {}) { return url; } - // iLib base data lives under <ilib>/locale; ILIB_BASE_PATH points at <ilib>. - addBundle('ILIB_BASE_PATH', ilibRel, 'locale'); + // iLib base data lives under <ilib>/locale; ILIB_BASE_PATH points at <ilib> + // (the loader appends `locale/` itself). + addBundle('ILIB_BASE_PATH', ilibRel, 'locale', true); // App resources. const resourcesUrl = addBundle('ILIB_RESOURCES_PATH', resourcesRel); // Per-app + per-theme bundle path constants (theme resources supply locale data too). From df083cf2ca15312585712d456d3db9dda42b9734 Mon Sep 17 00:00:00 2001 From: daniel-stoian-lgp <daniel.stoian@lgepartner.com> Date: Fri, 10 Jul 2026 15:19:07 +0300 Subject: [PATCH 3/3] migrated pack for --no-minify, --verbose, --stats --- mixins/index.js | 6 ++ mixins/vite.js | 82 ++++++++++++++++ npm-shrinkwrap.json | 224 +++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 4 files changed, 308 insertions(+), 5 deletions(-) create mode 100644 mixins/vite.js diff --git a/mixins/index.js b/mixins/index.js index 2a5f514..acb5bef 100644 --- a/mixins/index.js +++ b/mixins/index.js @@ -26,5 +26,11 @@ module.exports = { } return config; + }, + // Vite counterpart to `apply`: shapes a resolved Vite config from the same opts + // (--no-minify, --verbose, --stats). The webpack `apply` above can't be reused + // because it mutates webpack-specific config (plugins/minimizers/output). + applyVite: function (config, opts = {}) { + return require('./vite').apply(config, opts); } }; diff --git a/mixins/vite.js b/mixins/vite.js new file mode 100644 index 0000000..42e084a --- /dev/null +++ b/mixins/vite.js @@ -0,0 +1,82 @@ +const path = require('path'); + +// Vite counterpart to the webpack `mixins.apply`. Given a resolved Vite `InlineConfig` +// and the CLI `opts`, applies the small build-shaping flags that don't belong in the +// base config factory: `--no-minify`, `--verbose`, and `--stats`. Mirrors the webpack +// mixins (unmangled.js, verbose.js, stats.js) +module.exports = { + apply: function (config, opts = {}) { + config.plugins = config.plugins || []; + + // --no-minify (private): keep Terser's optimizations/dead-code removal but + // disable mangling and beautify the output for readability. Mirrors the webpack + // `unmangled` mixin. Only meaningful when the build actually minifies (production); + // a development build is already unminified, so leave it untouched. + if (opts.minify === false && config.build && config.build.minify) { + config.build.minify = 'terser'; + config.build.terserOptions = Object.assign({}, config.build.terserOptions, { + mangle: false, + compress: true, + format: Object.assign({beautify: true, comments: true}, (config.build.terserOptions || {}).format) + }); + } + + // --verbose: raise Vite's log level and log build-phase transitions with a running + // module count. Rollup doesn't know the total module count up front, so unlike the + // webpack ProgressPlugin there's no percentage; module counts are the honest analog. + if (opts.verbose) { + config.logLevel = 'info'; + config.plugins.push(verbosePlugin()); + } + + // --stats: emit a static bundle-analysis treemap next to the build output + // (webpack: webpack-bundle-analyzer's BundleAnalyzerPlugin -> stats.html). + if (opts.stats) { + let visualizer; + try { + ({visualizer} = require('rollup-plugin-visualizer')); + } catch (e) { + console.log( + 'NOTICE: --stats requires the "rollup-plugin-visualizer" package, which is not ' + + 'installed; skipping bundle analysis.' + ); + return config; + } + const outDir = (config.build && config.build.outDir) || 'dist'; + config.plugins.push( + visualizer({ + filename: path.join(outDir, 'stats.html'), + title: 'Enact bundle analysis', + template: 'treemap', + open: false, + gzipSize: true, + brotliSize: true + }) + ); + } + + return config; + } +}; + +// Lightweight Rollup/Vite plugin that narrates the build phases when `--verbose` is set. +function verbosePlugin() { + let modules = 0; + return { + name: 'enact-vite-verbose', + apply: 'build', + buildStart() { + modules = 0; + console.log('[verbose] build start — resolving & transforming modules…'); + }, + moduleParsed() { + modules++; + }, + renderStart() { + console.log(`[verbose] render start — ${modules} modules parsed, generating chunks…`); + }, + generateBundle(options, bundle) { + console.log(`[verbose] emitting ${Object.keys(bundle).length} output file(s)…`); + } + }; +} diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 4e0c3ee..3d8e76b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -21,6 +21,7 @@ "loader-utils": "^3.3.1", "mock-require": "^3.0.3", "resolve": "^1.22.12", + "rollup-plugin-visualizer": "^6.0.11", "tapable": "^2.3.3", "webpack-bundle-analyzer": "^5.3.0", "webpack-sources": "^3.5.0" @@ -719,7 +720,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -729,7 +729,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1116,11 +1115,24 @@ "node": ">= 10.0" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1133,7 +1145,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/commander": { @@ -1328,6 +1339,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -1460,6 +1480,12 @@ "integrity": "sha512-cH1jZgJHoezfTnKfKwnScpHywTFVnJUNITDPREFdhNjiuD502+QFpG0Qk7G8jhsV/f+CEAFlIrzP1fT+IMb92g==", "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/enhanced-resolve": { "version": "5.24.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz", @@ -9099,6 +9125,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-document.all": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", @@ -9140,6 +9181,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -9369,6 +9419,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -9881,6 +9943,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", @@ -10234,6 +10313,15 @@ "strip-ansi": "^6.0.1" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -10284,6 +10372,57 @@ "node": ">=0.10.0" } }, + "node_modules/rollup-plugin-visualizer": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.11.tgz", + "integrity": "sha512-TBwVHVY7buHjIKVLqr9scTVFwqZqMXINcCphPwIWKPDCOBIa+jCQfafvbjRJDZgXdq/A996Dy6yGJ/+/NtAXDQ==", + "license": "MIT", + "dependencies": { + "open": "^8.0.0", + "picomatch": "^4.0.2", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "rolldown": "1.x || ^1.0.0-beta", + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -10632,6 +10771,20 @@ "node": ">= 0.4" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", @@ -10696,7 +10849,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11378,6 +11530,23 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", @@ -11399,6 +11568,51 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 2967650..39a0fd1 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "loader-utils": "^3.3.1", "mock-require": "^3.0.3", "resolve": "^1.22.12", + "rollup-plugin-visualizer": "^6.0.11", "tapable": "^2.3.3", "webpack-bundle-analyzer": "^5.3.0", "webpack-sources": "^3.5.0"