mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(build): prevent Vite dev transform overflow (#1379)
* fix(build): prevent Vite dev transform overflow * fix(build): preserve commented Vite URL imports
This commit is contained in:
1 parent
f40320e42e
commit
d9a763e77d
9 files changed
+215
-6
No files matched your search
@@ -0,0 +1,8 @@
|
||||
---
|
||||
type: internal
|
||||
area: build
|
||||
---
|
||||
|
||||
Electron development serve no longer crashes while Vite transforms a large
|
||||
lazy chunk. The pinned Vite release now carries a backtracking-safe transform
|
||||
filter fix, with a regression check guarding dependency updates.
|
||||
@@ -150,6 +150,9 @@ jobs:
|
||||
- name: Validate Nx dependency version policy
|
||||
run: pnpm run deps:nx:validate
|
||||
|
||||
- name: Validate Vite dev-server transform filter patch
|
||||
run: pnpm run deps:vite:test
|
||||
|
||||
- name: Validate stylesheet Nx inputs
|
||||
run: pnpm run styles:inputs:validate
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ This file provides guidance to coding agents working in this repository.
|
||||
- See `docs/architecture/nx-workspace-boundaries.md` for the current Nx tag and alias policy.
|
||||
- Keep `nx` and every official `@nx/*` package on the same exact version; run
|
||||
`pnpm run deps:nx:validate` after dependency updates.
|
||||
- Vite `7.3.5`, resolved through Angular's build tooling, is patched with
|
||||
bounded transform prefilters and the upstream precise matchers in
|
||||
`patches/vite@7.3.5.patch`. Keep the patch until supported Angular tooling
|
||||
resolves a Vite version containing the fix, and run `pnpm run deps:vite:test`
|
||||
after related dependency updates.
|
||||
- A directory holding files consumed by other projects must be an Nx project.
|
||||
Nx builds its graph from TypeScript imports only, so a relative SCSS `@use`
|
||||
across project roots creates no edge and the imported file lands in no task
|
||||
|
||||
@@ -76,6 +76,11 @@ pnpm nx show projects
|
||||
- See `docs/architecture/nx-workspace-boundaries.md` for the current Nx tag and alias policy.
|
||||
- Keep `nx` and every official `@nx/*` package on the same exact version; run
|
||||
`pnpm run deps:nx:validate` after dependency updates.
|
||||
- Vite `7.3.5`, resolved through Angular's build tooling, is patched with
|
||||
bounded transform prefilters and the upstream precise matchers in
|
||||
`patches/vite@7.3.5.patch`. Keep the patch until supported Angular tooling
|
||||
resolves a Vite version containing the fix, and run `pnpm run deps:vite:test`
|
||||
after related dependency updates.
|
||||
- A directory holding files consumed by other projects must be an Nx project.
|
||||
Nx builds its graph from TypeScript imports only, so a relative SCSS `@use`
|
||||
across project roots creates no edge and the imported file lands in no task
|
||||
|
||||
@@ -50,6 +50,33 @@ Omit `pnpm nx migrate --run-migrations` when the first command reports that no
|
||||
migrations exist. Major Nx updates always use this manual workflow and the
|
||||
resulting PR runs the full CI pipeline.
|
||||
|
||||
## Vite Dev-Server Patch
|
||||
|
||||
Angular's development builder currently resolves Vite `7.3.5`. That release's
|
||||
asset and worker transform prefilters can catastrophically backtrack on a large
|
||||
generated chunk containing unrelated `new URL...` expressions while searching
|
||||
for a valid `new URL(..., import.meta.url)` construct. The Electron development
|
||||
server can then fail a lazy chunk request with `Maximum call stack size
|
||||
exceeded`, even though static builds succeed because they do not pass emitted
|
||||
chunks through Vite's request-time plugin container.
|
||||
|
||||
`patches/vite@7.3.5.patch` backports Vite's upstream precise-matcher fix from
|
||||
[vitejs/vite#21800](https://github.com/vitejs/vite/pull/21800). Bounded
|
||||
prefilters keep the request-time scan linear while allowing comment-bearing
|
||||
asset and worker expressions to reach the precise matcher after Vite strips
|
||||
comments. Keep the patch while the supported Angular toolchain resolves Vite
|
||||
`7.3.5`; remove it only after the resolved Vite contains the upstream fix. Run
|
||||
the regression check after any related manifest or lockfile update:
|
||||
|
||||
```bash
|
||||
pnpm run deps:vite:test
|
||||
```
|
||||
|
||||
CI runs the same check. It resolves Vite from `@angular/build`, verifies the
|
||||
patched prefilter/matcher wiring and version pin, stress-tests the false-positive
|
||||
chunk shape, and preserves ordinary and comment-bearing asset and worker
|
||||
`new URL(..., import.meta.url)` matches.
|
||||
|
||||
## Placement Decision
|
||||
|
||||
- `apps/` owns runtime applications, development servers, E2E applications,
|
||||
|
||||
+3
-1
@@ -36,6 +36,7 @@
|
||||
"deps:nx:test": "node --test tools/dependencies/check-nx-version-sync.test.mjs",
|
||||
"deps:nx:check": "node tools/dependencies/check-nx-version-sync.mjs",
|
||||
"deps:nx:validate": "pnpm run deps:nx:test && pnpm run deps:nx:check",
|
||||
"deps:vite:test": "node --test tools/dependencies/vite-transform-filter.test.mjs",
|
||||
"styles:inputs:test": "node --test tools/nx/check-stylesheet-inputs.test.mjs",
|
||||
"styles:inputs:check": "node tools/nx/check-stylesheet-inputs.mjs",
|
||||
"styles:inputs:validate": "pnpm run styles:inputs:test && pnpm run styles:inputs:check",
|
||||
@@ -250,7 +251,8 @@
|
||||
"yaml@1.10.2": "1.10.3"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"nx-electron@22.0.0": "patches/nx-electron@22.0.0.patch"
|
||||
"nx-electron@22.0.0": "patches/nx-electron@22.0.0.patch",
|
||||
"vite@7.3.5": "patches/vite@7.3.5.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
diff --git a/dist/node/chunks/config.js b/dist/node/chunks/config.js
|
||||
index 9a45043275249a25101fd7991ffcfc316478840e..5435b17de9ca65b64a8abacb609b75587226b106 100644
|
||||
--- a/dist/node/chunks/config.js
|
||||
+++ b/dist/node/chunks/config.js
|
||||
@@ -27667,7 +27667,8 @@ async function getWorkerType(raw, clean, i$1) {
|
||||
if (workerOpts.type && (workerOpts.type === "module" || workerOpts.type === "classic")) return workerOpts.type;
|
||||
return "classic";
|
||||
}
|
||||
-const workerImportMetaUrlRE = /new\s+(?:Worker|SharedWorker)\s*\(\s*new\s+URL.+?import\.meta\.url/s;
|
||||
+const workerImportMetaUrlFilterRE = /\bnew\s+(?:Worker|SharedWorker)\b/;
|
||||
+const workerImportMetaUrlRE = /\bnew\s+(?:Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\))/dg;
|
||||
function workerImportMetaUrlPlugin(config$2) {
|
||||
const isBuild = config$2.command === "build";
|
||||
let workerResolver;
|
||||
@@ -27688,10 +27689,10 @@ function workerImportMetaUrlPlugin(config$2) {
|
||||
- filter: { code: workerImportMetaUrlRE },
|
||||
+ filter: { code: workerImportMetaUrlFilterRE },
|
||||
async handler(code, id) {
|
||||
let s;
|
||||
+ const re = new RegExp(workerImportMetaUrlRE);
|
||||
const cleanString = stripLiteral(code);
|
||||
- const workerImportMetaUrlRE$1 = /\bnew\s+(?:Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*\))/dg;
|
||||
let match;
|
||||
- while (match = workerImportMetaUrlRE$1.exec(cleanString)) {
|
||||
+ while (match = re.exec(cleanString)) {
|
||||
const [[, endIndex], [expStart, expEnd], [urlStart, urlEnd]] = match.indices;
|
||||
const rawUrl = code.slice(urlStart, urlEnd);
|
||||
if (rawUrl[0] === "`" && rawUrl.includes("${")) this.error(`\`new URL(url, import.meta.url)\` is not supported in dynamic template string.`, expStart);
|
||||
@@ -27733,6 +27734,8 @@ function workerImportMetaUrlPlugin(config$2) {
|
||||
}
|
||||
|
||||
//#endregion
|
||||
+const assetImportMetaUrlFilterRE = /\bnew\s+URL\b/;
|
||||
+const assetImportMetaUrlRE = /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg;
|
||||
//#region src/node/plugins/assetImportMetaUrl.ts
|
||||
/**
|
||||
* Convert `new URL('./foo.png', import.meta.url)` to its resolved built URL
|
||||
@@ -27763,14 +27765,14 @@ function assetImportMetaUrlPlugin(config$2) {
|
||||
transform: {
|
||||
filter: {
|
||||
id: { exclude: [exactRegex(preloadHelperId), exactRegex(CLIENT_ENTRY)] },
|
||||
- code: /new\s+URL.+import\.meta\.url/s
|
||||
+ code: assetImportMetaUrlFilterRE
|
||||
},
|
||||
async handler(code, id) {
|
||||
let s;
|
||||
- const assetImportMetaUrlRE = /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg;
|
||||
+ const re = new RegExp(assetImportMetaUrlRE);
|
||||
const cleanString = stripLiteral(code);
|
||||
let match;
|
||||
- while (match = assetImportMetaUrlRE.exec(cleanString)) {
|
||||
+ while (match = re.exec(cleanString)) {
|
||||
const [[startIndex, endIndex], [urlStart, urlEnd]] = match.indices;
|
||||
if (hasViteIgnoreRE.test(code.slice(startIndex, urlStart))) continue;
|
||||
const rawUrl = code.slice(urlStart, urlEnd);
|
||||
Generated
+8
-5
@@ -35,6 +35,9 @@ patchedDependencies:
|
||||
nx-electron@22.0.0:
|
||||
hash: f4bbde1778360c4c462b255bec47a337c0465d59cdcab14ecb601161f3467002
|
||||
path: patches/nx-electron@22.0.0.patch
|
||||
vite@7.3.5:
|
||||
hash: b3a8f88ab420b6504a61ffb0f8e402f112c7fda7f3f3e802d21272da7969882d
|
||||
path: patches/vite@7.3.5.patch
|
||||
|
||||
importers:
|
||||
|
||||
@@ -11902,7 +11905,7 @@ snapshots:
|
||||
'@babel/helper-annotate-as-pure': 7.27.3
|
||||
'@babel/helper-split-export-declaration': 7.24.7
|
||||
'@inquirer/confirm': 5.1.21(@types/node@20.19.9)
|
||||
'@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0))
|
||||
'@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.5(patch_hash=b3a8f88ab420b6504a61ffb0f8e402f112c7fda7f3f3e802d21272da7969882d)(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0))
|
||||
beasties: 0.4.1
|
||||
browserslist: 4.28.2
|
||||
esbuild: 0.28.1
|
||||
@@ -11923,7 +11926,7 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
typescript: 5.9.3
|
||||
undici: 7.24.4
|
||||
vite: 7.3.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0)
|
||||
vite: 7.3.5(patch_hash=b3a8f88ab420b6504a61ffb0f8e402f112c7fda7f3f3e802d21272da7969882d)(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0)
|
||||
watchpack: 2.5.1
|
||||
optionalDependencies:
|
||||
'@angular/core': 21.2.17(@angular/compiler@21.2.17)(rxjs@7.8.2)(zone.js@0.16.2)
|
||||
@@ -16577,9 +16580,9 @@ snapshots:
|
||||
global: 4.4.0
|
||||
is-function: 1.0.2
|
||||
|
||||
'@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0))':
|
||||
'@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.5(patch_hash=b3a8f88ab420b6504a61ffb0f8e402f112c7fda7f3f3e802d21272da7969882d)(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0))':
|
||||
dependencies:
|
||||
vite: 7.3.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0)
|
||||
vite: 7.3.5(patch_hash=b3a8f88ab420b6504a61ffb0f8e402f112c7fda7f3f3e802d21272da7969882d)(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0)
|
||||
|
||||
'@webassemblyjs/ast@1.14.1':
|
||||
dependencies:
|
||||
@@ -24377,7 +24380,7 @@ snapshots:
|
||||
tsx: 4.23.1
|
||||
yaml: 2.9.0
|
||||
|
||||
vite@7.3.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0):
|
||||
vite@7.3.5(patch_hash=b3a8f88ab420b6504a61ffb0f8e402f112c7fda7f3f3e802d21272da7969882d)(@types/node@20.19.9)(jiti@1.21.7)(less@4.5.1)(sass-embedded@1.97.1)(sass@1.97.3)(terser@5.44.1)(tsx@4.23.1)(yaml@2.9.0):
|
||||
dependencies:
|
||||
esbuild: 0.27.7
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { performance } from 'node:perf_hooks';
|
||||
import test from 'node:test';
|
||||
import { runInNewContext } from 'node:vm';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const angularBuildRequire = createRequire(
|
||||
require.resolve('@angular/build/package.json')
|
||||
);
|
||||
const vitePackagePath = angularBuildRequire.resolve('vite/package.json');
|
||||
const vitePackage = JSON.parse(await readFile(vitePackagePath, 'utf8'));
|
||||
const viteConfigPath = join(
|
||||
dirname(vitePackagePath),
|
||||
'dist/node/chunks/config.js'
|
||||
);
|
||||
const viteConfig = await readFile(viteConfigPath, 'utf8');
|
||||
|
||||
function extractRegExp(name) {
|
||||
const declaration = new RegExp(
|
||||
`const ${name} = (\\/[^\\n]+\\/[a-z]*);`
|
||||
).exec(viteConfig);
|
||||
assert.ok(declaration, `Unable to find ${name} in ${viteConfigPath}`);
|
||||
return runInNewContext(declaration[1]);
|
||||
}
|
||||
|
||||
test('pins the Vite version carrying the local transform-filter backport', () => {
|
||||
assert.equal(vitePackage.version, '7.3.5');
|
||||
});
|
||||
|
||||
test('uses bounded Vite prefilters with precise handler matchers', () => {
|
||||
assert.ok(
|
||||
/filter: \{\s*id: \{[^}]+\},\s*code: assetImportMetaUrlFilterRE\s*\}/s.test(
|
||||
viteConfig
|
||||
),
|
||||
'asset import-meta transform must use the bounded prefilter'
|
||||
);
|
||||
assert.ok(
|
||||
/filter: \{ code: workerImportMetaUrlFilterRE \}/.test(viteConfig),
|
||||
'worker import-meta transform must use the bounded prefilter'
|
||||
);
|
||||
assert.ok(
|
||||
/const re = new RegExp\(assetImportMetaUrlRE\)/.test(viteConfig),
|
||||
'asset handler must clone the precise matcher'
|
||||
);
|
||||
assert.ok(
|
||||
/const re = new RegExp\(workerImportMetaUrlRE\)/.test(viteConfig),
|
||||
'worker handler must clone the precise matcher'
|
||||
);
|
||||
assert.ok(
|
||||
!/code: \/new\\s\+URL\.\+import\\\.meta\\\.url\/s/.test(viteConfig),
|
||||
'the backtracking asset transform prefilter must be absent'
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects a large false-positive chunk without regex backtracking', () => {
|
||||
const assetFilter = extractRegExp('assetImportMetaUrlFilterRE');
|
||||
const workerFilter = extractRegExp('workerImportMetaUrlFilterRE');
|
||||
const largeCode =
|
||||
`new URLSearchParams();\n`.repeat(200) + `var a = 1;\n`.repeat(200_000);
|
||||
const start = performance.now();
|
||||
|
||||
assert.equal(assetFilter.test(largeCode), false);
|
||||
assert.equal(workerFilter.test(largeCode), false);
|
||||
assert.ok(
|
||||
performance.now() - start < 250,
|
||||
'Vite transform filters must reject the stress input without backtracking'
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps valid asset and worker import-meta URL patterns eligible', () => {
|
||||
const assetFilter = extractRegExp('assetImportMetaUrlFilterRE');
|
||||
const workerFilter = extractRegExp('workerImportMetaUrlFilterRE');
|
||||
const assetMatcher = extractRegExp('assetImportMetaUrlRE');
|
||||
const workerMatcher = extractRegExp('workerImportMetaUrlRE');
|
||||
const assetExpression = `new URL('./asset.png', import.meta.url)`;
|
||||
const workerExpression = `new Worker(new URL('./worker.js', import.meta.url))`;
|
||||
|
||||
assert.equal(assetFilter.test(assetExpression), true);
|
||||
assert.equal(workerFilter.test(workerExpression), true);
|
||||
assert.equal(assetMatcher.test(assetExpression), true);
|
||||
assert.equal(workerMatcher.test(workerExpression), true);
|
||||
});
|
||||
|
||||
test('keeps comment-bearing import-meta URL patterns eligible', () => {
|
||||
const assetFilter = extractRegExp('assetImportMetaUrlFilterRE');
|
||||
const workerFilter = extractRegExp('workerImportMetaUrlFilterRE');
|
||||
|
||||
assert.equal(
|
||||
assetFilter.test(`new URL(/* keep */ './asset.png', import.meta.url)`),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
workerFilter.test(
|
||||
`new Worker(/* keep */ new URL('./worker.js', import.meta.url))`
|
||||
),
|
||||
true
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user