vinext 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/build/google-fonts/find-font-files-in-css.d.ts +16 -0
- package/dist/build/google-fonts/find-font-files-in-css.js +28 -0
- package/dist/build/report.d.ts +1 -2
- package/dist/build/report.js +2 -11
- package/dist/build/run-prerender.js +1 -1
- package/dist/check.js +3 -3
- package/dist/client/pages-router-link-navigation.d.ts +10 -0
- package/dist/client/pages-router-link-navigation.js +3 -3
- package/dist/deploy.js +3 -3
- package/dist/entries/app-rsc-entry.js +1 -0
- package/dist/entries/app-rsc-manifest.js +76 -46
- package/dist/entries/pages-server-entry.js +1 -0
- package/dist/index.js +69 -54
- package/dist/init.d.ts +5 -4
- package/dist/init.js +27 -8
- package/dist/plugins/fonts.js +3 -1
- package/dist/plugins/middleware-export-validation.d.ts +5 -0
- package/dist/plugins/middleware-export-validation.js +36 -0
- package/dist/plugins/og-asset-ownership.d.ts +27 -0
- package/dist/plugins/og-asset-ownership.js +285 -0
- package/dist/plugins/og-assets.js +22 -4
- package/dist/server/accept-encoding.d.ts +29 -0
- package/dist/server/accept-encoding.js +95 -0
- package/dist/server/app-browser-entry.js +8 -5
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
- package/dist/server/app-browser-rsc-redirect.js +8 -1
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-middleware.d.ts +1 -0
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-page-dispatch.d.ts +2 -2
- package/dist/server/app-page-head.js +2 -2
- package/dist/server/app-page-request.d.ts +7 -0
- package/dist/server/app-page-request.js +19 -1
- package/dist/server/app-prerender-endpoints.d.ts +3 -3
- package/dist/server/app-prerender-endpoints.js +3 -4
- package/dist/server/app-route-module-loader.d.ts +62 -14
- package/dist/server/app-route-module-loader.js +61 -10
- package/dist/server/app-rsc-handler.d.ts +7 -6
- package/dist/server/app-rsc-handler.js +31 -26
- package/dist/server/app-rsc-route-matching.d.ts +9 -0
- package/dist/server/app-rsc-route-matching.js +13 -0
- package/dist/server/app-ssr-entry.js +2 -2
- package/dist/server/headers.d.ts +5 -1
- package/dist/server/headers.js +5 -1
- package/dist/server/metadata-route-response.d.ts +3 -3
- package/dist/server/middleware-runtime.d.ts +2 -1
- package/dist/server/middleware-runtime.js +19 -8
- package/dist/server/middleware.d.ts +2 -2
- package/dist/server/middleware.js +26 -11
- package/dist/server/pages-request-pipeline.d.ts +3 -1
- package/dist/server/pages-request-pipeline.js +4 -2
- package/dist/server/prod-server.d.ts +1 -1
- package/dist/server/prod-server.js +85 -72
- package/dist/shims/font-google-base.d.ts +1 -0
- package/dist/shims/font-google-base.js +6 -21
- package/dist/shims/internal/interpolate-as.d.ts +25 -0
- package/dist/shims/internal/interpolate-as.js +196 -0
- package/dist/shims/link.js +24 -1
- package/dist/shims/navigation.js +1 -1
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/router.d.ts +1 -3
- package/dist/shims/router.js +62 -18
- package/dist/typegen.js +2 -10
- package/dist/utils/project.d.ts +7 -1
- package/dist/utils/project.js +16 -2
- package/package.json +3 -3
package/dist/init.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 4. Rename CJS config files to .cjs
|
|
11
11
|
* 5. Add vinext scripts to package.json
|
|
12
12
|
* 6. Generate vite.config.ts
|
|
13
|
-
* 7. Update .gitignore to include /dist/
|
|
13
|
+
* 7. Update .gitignore to include /dist/ and .vinext/
|
|
14
14
|
* 8. Print summary
|
|
15
15
|
*
|
|
16
16
|
* Non-destructive: does NOT modify next.config, tsconfig, or source files.
|
|
@@ -32,7 +32,7 @@ type InitResult = {
|
|
|
32
32
|
renamedConfigs: Array<[string, string]>; /** Whether scripts were added to package.json */
|
|
33
33
|
addedScripts: string[]; /** Whether vite.config.ts was generated */
|
|
34
34
|
generatedViteConfig: boolean; /** Whether vite.config.ts generation was skipped (already exists) */
|
|
35
|
-
skippedViteConfig: boolean; /** Whether .gitignore was updated to include
|
|
35
|
+
skippedViteConfig: boolean; /** Whether .gitignore was updated to include vinext-generated output */
|
|
36
36
|
updatedGitignore: boolean;
|
|
37
37
|
};
|
|
38
38
|
declare function generateViteConfig(_isAppRouter: boolean): string;
|
|
@@ -59,8 +59,9 @@ declare function isDepInstalled(root: string, dep: string): boolean;
|
|
|
59
59
|
*/
|
|
60
60
|
declare function getReactUpgradeDeps(root: string): string[];
|
|
61
61
|
/**
|
|
62
|
-
* Ensure
|
|
63
|
-
*
|
|
62
|
+
* Ensure vinext-generated output directories are listed in .gitignore.
|
|
63
|
+
* Creates the file if it doesn't exist. Returns true if the file was modified
|
|
64
|
+
* (or created), false if all entries were already present.
|
|
64
65
|
*/
|
|
65
66
|
declare function updateGitignore(root: string): boolean;
|
|
66
67
|
declare function init(options: InitOptions): Promise<InitResult>;
|
package/dist/init.js
CHANGED
|
@@ -16,7 +16,7 @@ import { execFileSync } from "node:child_process";
|
|
|
16
16
|
* 4. Rename CJS config files to .cjs
|
|
17
17
|
* 5. Add vinext scripts to package.json
|
|
18
18
|
* 6. Generate vite.config.ts
|
|
19
|
-
* 7. Update .gitignore to include /dist/
|
|
19
|
+
* 7. Update .gitignore to include /dist/ and .vinext/
|
|
20
20
|
* 8. Print summary
|
|
21
21
|
*
|
|
22
22
|
* Non-destructive: does NOT modify next.config, tsconfig, or source files.
|
|
@@ -140,20 +140,39 @@ function installDeps(root, deps, exec, { dev = true } = {}) {
|
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
|
-
* Ensure
|
|
144
|
-
*
|
|
143
|
+
* Ensure vinext-generated output directories are listed in .gitignore.
|
|
144
|
+
* Creates the file if it doesn't exist. Returns true if the file was modified
|
|
145
|
+
* (or created), false if all entries were already present.
|
|
145
146
|
*/
|
|
146
147
|
function updateGitignore(root) {
|
|
147
148
|
const gitignorePath = path.join(root, ".gitignore");
|
|
148
|
-
const
|
|
149
|
+
const entries = [{
|
|
150
|
+
entry: "/dist/",
|
|
151
|
+
coveredBy: new Set([
|
|
152
|
+
"/dist/",
|
|
153
|
+
"/dist",
|
|
154
|
+
"dist/",
|
|
155
|
+
"dist"
|
|
156
|
+
])
|
|
157
|
+
}, {
|
|
158
|
+
entry: ".vinext/",
|
|
159
|
+
coveredBy: new Set([
|
|
160
|
+
"/.vinext/",
|
|
161
|
+
"/.vinext",
|
|
162
|
+
".vinext/",
|
|
163
|
+
".vinext"
|
|
164
|
+
])
|
|
165
|
+
}];
|
|
149
166
|
let content = "";
|
|
167
|
+
let lines = [];
|
|
150
168
|
if (fs.existsSync(gitignorePath)) {
|
|
151
169
|
content = fs.readFileSync(gitignorePath, "utf-8");
|
|
152
|
-
|
|
153
|
-
if (lines.includes(exactEntry) || lines.includes("dist/") || lines.includes("dist")) return false;
|
|
170
|
+
lines = content.split("\n").map((l) => l.trim());
|
|
154
171
|
}
|
|
172
|
+
const missingEntries = entries.filter(({ coveredBy }) => !lines.some((line) => coveredBy.has(line))).map(({ entry }) => entry);
|
|
173
|
+
if (missingEntries.length === 0) return false;
|
|
155
174
|
const separator = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
156
|
-
fs.writeFileSync(gitignorePath, content + separator + "
|
|
175
|
+
fs.writeFileSync(gitignorePath, content + separator + missingEntries.join("\n") + "\n", "utf-8");
|
|
157
176
|
return true;
|
|
158
177
|
}
|
|
159
178
|
async function init(options) {
|
|
@@ -212,7 +231,7 @@ async function init(options) {
|
|
|
212
231
|
for (const script of addedScripts) console.log(` \u2713 Added ${script} script`);
|
|
213
232
|
if (generatedViteConfig) console.log(` \u2713 Generated vite.config.ts`);
|
|
214
233
|
if (skippedViteConfig) console.log(` - Skipped vite.config.ts (already exists, use --force to overwrite)`);
|
|
215
|
-
if (updatedGitignore) console.log(` \u2713 Added
|
|
234
|
+
if (updatedGitignore) console.log(` \u2713 Added vinext output directories to .gitignore`);
|
|
216
235
|
console.log(`
|
|
217
236
|
Next steps:
|
|
218
237
|
${pmName} run dev:vinext Start the vinext dev server
|
package/dist/plugins/fonts.js
CHANGED
|
@@ -4,6 +4,7 @@ import { buildFallbackFontFace, getFallbackFontOverrideMetrics } from "../build/
|
|
|
4
4
|
import { validateGoogleFontOptions } from "../build/google-fonts/validate.js";
|
|
5
5
|
import { getFontAxes } from "../build/google-fonts/get-axes.js";
|
|
6
6
|
import { buildGoogleFontsUrl } from "../build/google-fonts/build-url.js";
|
|
7
|
+
import { findFontFilesInCss } from "../build/google-fonts/find-font-files-in-css.js";
|
|
7
8
|
import { ASSET_PREFIX_URL_DIR } from "../utils/asset-prefix.js";
|
|
8
9
|
import { CONTENT_TYPES } from "../server/static-file-cache.js";
|
|
9
10
|
import fs from "node:fs";
|
|
@@ -543,11 +544,12 @@ function createGoogleFontsPlugin(fontGoogleShimPath, shimsDir) {
|
|
|
543
544
|
return;
|
|
544
545
|
}
|
|
545
546
|
const servedCSS = _rewriteCachedFontCssToServedUrls(localCSS, cacheDir, transformAssetsDir);
|
|
547
|
+
const preloadUrls = findFontFilesInCss(servedCSS, validated.preload ? validated.subsets : void 0).filter((file) => file.preloadFontFile).map((file) => file.googleFontFileUrl);
|
|
546
548
|
const fallbackMetrics = validated.adjustFontFallback === false ? void 0 : getFallbackFontOverrideMetrics(family);
|
|
547
549
|
const adjustedFallbackCSS = fallbackMetrics ? buildFallbackFontFace(family, fallbackMetrics) : void 0;
|
|
548
550
|
const validatedFontWeight = validated.weights.length === 1 && validated.weights[0] !== "variable" ? Number(validated.weights[0]) : void 0;
|
|
549
551
|
const validatedFontStyle = validated.styles.length === 1 ? validated.styles[0] : void 0;
|
|
550
|
-
const internalFontProperties = [`selfHostedCSS: ${JSON.stringify(servedCSS)}`];
|
|
552
|
+
const internalFontProperties = [`selfHostedCSS: ${JSON.stringify(servedCSS)}`, `preloadUrls: ${JSON.stringify(preloadUrls)}`];
|
|
551
553
|
if (adjustedFallbackCSS) internalFontProperties.push(`adjustedFallbackCSS: ${JSON.stringify(adjustedFallbackCSS)}`);
|
|
552
554
|
if (Number.isFinite(validatedFontWeight)) internalFontProperties.push(`fontWeight: ${validatedFontWeight}`);
|
|
553
555
|
if (validatedFontStyle) internalFontProperties.push(`fontStyle: ${JSON.stringify(validatedFontStyle)}`);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
//#region src/plugins/middleware-export-validation.d.ts
|
|
2
|
+
declare function hasValidMiddlewareModuleExport(source: string, id: string, isProxy: boolean): boolean;
|
|
3
|
+
declare function validateMiddlewareModuleExports(source: string, id: string, filePath: string, isProxy: boolean): void;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { hasValidMiddlewareModuleExport, validateMiddlewareModuleExports };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { stripViteModuleQuery } from "../utils/path.js";
|
|
2
|
+
import { createMiddlewareMissingExportError } from "../server/middleware-runtime.js";
|
|
3
|
+
import { parseAst } from "vite";
|
|
4
|
+
//#region src/plugins/middleware-export-validation.ts
|
|
5
|
+
function parserLanguage(id) {
|
|
6
|
+
const cleanId = stripViteModuleQuery(id).toLowerCase();
|
|
7
|
+
if (cleanId.endsWith(".tsx")) return "tsx";
|
|
8
|
+
if (cleanId.endsWith(".ts") || cleanId.endsWith(".mts") || cleanId.endsWith(".cts")) return "ts";
|
|
9
|
+
return "jsx";
|
|
10
|
+
}
|
|
11
|
+
function astName(value) {
|
|
12
|
+
if (!value) return null;
|
|
13
|
+
if (typeof value.name === "string") return value.name;
|
|
14
|
+
if (typeof value.value === "string") return value.value;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function hasValidMiddlewareModuleExport(source, id, isProxy) {
|
|
18
|
+
const ast = parseAst(source, { lang: parserLanguage(id) });
|
|
19
|
+
const expectedExport = isProxy ? "proxy" : "middleware";
|
|
20
|
+
for (const statement of ast.body) {
|
|
21
|
+
if (statement.type === "ExportDefaultDeclaration") return true;
|
|
22
|
+
if (statement.type !== "ExportNamedDeclaration") continue;
|
|
23
|
+
const declaration = statement.declaration;
|
|
24
|
+
if (declaration?.type === "FunctionDeclaration" && astName(declaration.id) === expectedExport) return true;
|
|
25
|
+
if (declaration?.type === "VariableDeclaration") {
|
|
26
|
+
for (const declarator of declaration.declarations ?? []) if (astName(declarator.id) === expectedExport) return true;
|
|
27
|
+
}
|
|
28
|
+
for (const specifier of statement.specifiers ?? []) if (astName(specifier.exported ?? specifier.local) === expectedExport) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function validateMiddlewareModuleExports(source, id, filePath, isProxy) {
|
|
33
|
+
if (!hasValidMiddlewareModuleExport(source, id, isProxy)) throw createMiddlewareMissingExportError(filePath, isProxy);
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { hasValidMiddlewareModuleExport, validateMiddlewareModuleExports };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Alias } from "vite";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/og-asset-ownership.d.ts
|
|
4
|
+
type OgAssetModuleBoundary = {
|
|
5
|
+
assetRoot: string;
|
|
6
|
+
moduleDir: string;
|
|
7
|
+
};
|
|
8
|
+
declare class OgAssetOwnership {
|
|
9
|
+
private projectRoot;
|
|
10
|
+
private readonly linkedPackageRoots;
|
|
11
|
+
private readonly dependencyPackageNames;
|
|
12
|
+
private readonly stringAliasesByFirstCharacter;
|
|
13
|
+
private regularExpressionAliases;
|
|
14
|
+
private configuredAliases;
|
|
15
|
+
configure(projectRoot: string, aliases: readonly Alias[]): void;
|
|
16
|
+
reset(): void;
|
|
17
|
+
shouldTrackImport(source: string): boolean;
|
|
18
|
+
recordResolvedImport(source: string, resolvedId: string): Promise<void>;
|
|
19
|
+
resolveModuleBoundary(moduleId: string): Promise<OgAssetModuleBoundary | null>;
|
|
20
|
+
resolveContainedAsset(assetRoot: string, assetPath: string): Promise<string | null>;
|
|
21
|
+
private findAlias;
|
|
22
|
+
private getExpectedPackageName;
|
|
23
|
+
private resolveAliasPackageRoot;
|
|
24
|
+
private resolveConfiguredAliasRoot;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { OgAssetModuleBoundary, OgAssetOwnership };
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
//#region src/plugins/og-asset-ownership.ts
|
|
5
|
+
const realpathNative = promisify(fs.realpath.native);
|
|
6
|
+
function isPathInside(root, target) {
|
|
7
|
+
const relative = path.relative(root, target);
|
|
8
|
+
return relative === "" || !path.isAbsolute(relative) && !relative.startsWith(`..${path.sep}`) && relative !== "..";
|
|
9
|
+
}
|
|
10
|
+
async function findPackageRoot(moduleDir, expectedPackageName) {
|
|
11
|
+
let currentDir = moduleDir;
|
|
12
|
+
while (true) {
|
|
13
|
+
try {
|
|
14
|
+
const packageJsonPath = path.join(currentDir, "package.json");
|
|
15
|
+
if ((await fs.promises.stat(packageJsonPath)).isFile()) {
|
|
16
|
+
const manifest = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf8"));
|
|
17
|
+
return expectedPackageName === void 0 || manifest.name === expectedPackageName ? currentDir : null;
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
const parentDir = path.dirname(currentDir);
|
|
21
|
+
if (parentDir === currentDir) return null;
|
|
22
|
+
currentDir = parentDir;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function getPackageNameFromSpecifier(specifier) {
|
|
26
|
+
if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("\0") || specifier.startsWith("#")) return null;
|
|
27
|
+
const segments = specifier.split("/");
|
|
28
|
+
if (specifier.startsWith("@")) return segments.length >= 2 ? `${segments[0]}/${segments[1]}` : null;
|
|
29
|
+
return segments[0] || null;
|
|
30
|
+
}
|
|
31
|
+
function getAliasedPackageName(value) {
|
|
32
|
+
if (typeof value !== "string") return null;
|
|
33
|
+
return value.match(/^(?:npm:|workspace:)(@[^/]+\/[^@]+|[^@*~^]+)(?:@|$)/)?.[1] ?? null;
|
|
34
|
+
}
|
|
35
|
+
function aliasMatches(find, source) {
|
|
36
|
+
if (typeof find === "string") return source === find || source.startsWith(`${find}/`);
|
|
37
|
+
find.lastIndex = 0;
|
|
38
|
+
return find.test(source);
|
|
39
|
+
}
|
|
40
|
+
function applyAlias(find, replacement, source) {
|
|
41
|
+
if (typeof find === "string") return replacement + source.slice(find.length);
|
|
42
|
+
find.lastIndex = 0;
|
|
43
|
+
return source.replace(find, replacement);
|
|
44
|
+
}
|
|
45
|
+
function getNodeModulesPackageRoot(logicalProjectRoot, logicalModulePath) {
|
|
46
|
+
const isProjectPath = isPathInside(logicalProjectRoot, logicalModulePath);
|
|
47
|
+
const parsedPath = path.parse(logicalModulePath);
|
|
48
|
+
const baseRoot = isProjectPath ? logicalProjectRoot : parsedPath.root;
|
|
49
|
+
const segments = (isProjectPath ? path.relative(logicalProjectRoot, logicalModulePath) : logicalModulePath.slice(parsedPath.root.length)).split(path.sep);
|
|
50
|
+
const nodeModulesIndex = segments.lastIndexOf("node_modules");
|
|
51
|
+
if (nodeModulesIndex === -1) return null;
|
|
52
|
+
const packageSegment = segments[nodeModulesIndex + 1];
|
|
53
|
+
if (!packageSegment) return null;
|
|
54
|
+
const packageSegmentCount = packageSegment.startsWith("@") ? 2 : 1;
|
|
55
|
+
if (segments.length <= nodeModulesIndex + packageSegmentCount) return null;
|
|
56
|
+
if (packageSegmentCount === 2 && !segments[nodeModulesIndex + 2]) return null;
|
|
57
|
+
return path.join(baseRoot, ...segments.slice(0, nodeModulesIndex + 1 + packageSegmentCount));
|
|
58
|
+
}
|
|
59
|
+
async function readPackageName(packageRoot) {
|
|
60
|
+
try {
|
|
61
|
+
const manifest = JSON.parse(await fs.promises.readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
62
|
+
return typeof manifest.name === "string" ? manifest.name : null;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function packageOwnsAliasFile(packageRoot, packageName, aliasFile) {
|
|
68
|
+
try {
|
|
69
|
+
const manifest = JSON.parse(await fs.promises.readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
70
|
+
if (packageName !== null && manifest.name === packageName) return true;
|
|
71
|
+
return [
|
|
72
|
+
"main",
|
|
73
|
+
"module",
|
|
74
|
+
"source",
|
|
75
|
+
"browser"
|
|
76
|
+
].some((field) => typeof manifest[field] === "string" && path.resolve(packageRoot, manifest[field]) === aliasFile);
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function packageOwnsAliasDirectory(packageRoot, packageName, aliasDirectory) {
|
|
82
|
+
if (aliasDirectory === packageRoot) return true;
|
|
83
|
+
try {
|
|
84
|
+
const manifest = JSON.parse(await fs.promises.readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
85
|
+
if (packageName !== null && manifest.name === packageName) return true;
|
|
86
|
+
return [
|
|
87
|
+
"main",
|
|
88
|
+
"module",
|
|
89
|
+
"source",
|
|
90
|
+
"browser"
|
|
91
|
+
].some((field) => typeof manifest[field] === "string" && isPathInside(aliasDirectory, path.resolve(packageRoot, manifest[field])));
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
var OgAssetOwnership = class {
|
|
97
|
+
projectRoot = process.cwd();
|
|
98
|
+
linkedPackageRoots = /* @__PURE__ */ new Set();
|
|
99
|
+
dependencyPackageNames = /* @__PURE__ */ new Map();
|
|
100
|
+
stringAliasesByFirstCharacter = /* @__PURE__ */ new Map();
|
|
101
|
+
regularExpressionAliases = [];
|
|
102
|
+
configuredAliases = [];
|
|
103
|
+
configure(projectRoot, aliases) {
|
|
104
|
+
this.projectRoot = path.resolve(projectRoot);
|
|
105
|
+
this.dependencyPackageNames.clear();
|
|
106
|
+
try {
|
|
107
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(this.projectRoot, "package.json"), "utf8"));
|
|
108
|
+
for (const field of [
|
|
109
|
+
"dependencies",
|
|
110
|
+
"devDependencies",
|
|
111
|
+
"optionalDependencies",
|
|
112
|
+
"peerDependencies"
|
|
113
|
+
]) {
|
|
114
|
+
const dependencies = manifest[field];
|
|
115
|
+
if (dependencies === null || typeof dependencies !== "object") continue;
|
|
116
|
+
for (const [packageName, value] of Object.entries(dependencies)) this.dependencyPackageNames.set(packageName, getAliasedPackageName(value) ?? packageName);
|
|
117
|
+
}
|
|
118
|
+
} catch {}
|
|
119
|
+
this.stringAliasesByFirstCharacter.clear();
|
|
120
|
+
this.regularExpressionAliases = [];
|
|
121
|
+
this.configuredAliases = [];
|
|
122
|
+
for (const [index, alias] of aliases.entries()) {
|
|
123
|
+
const indexedAlias = {
|
|
124
|
+
...alias,
|
|
125
|
+
index
|
|
126
|
+
};
|
|
127
|
+
this.configuredAliases.push(indexedAlias);
|
|
128
|
+
if (typeof alias.find === "string") {
|
|
129
|
+
const firstCharacter = alias.find[0];
|
|
130
|
+
if (firstCharacter === void 0) continue;
|
|
131
|
+
const matchingAliases = this.stringAliasesByFirstCharacter.get(firstCharacter) ?? [];
|
|
132
|
+
matchingAliases.push(indexedAlias);
|
|
133
|
+
this.stringAliasesByFirstCharacter.set(firstCharacter, matchingAliases);
|
|
134
|
+
} else this.regularExpressionAliases.push(indexedAlias);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
reset() {
|
|
138
|
+
this.linkedPackageRoots.clear();
|
|
139
|
+
}
|
|
140
|
+
shouldTrackImport(source) {
|
|
141
|
+
return this.findAlias(source) !== void 0 || this.getExpectedPackageName(source) !== void 0;
|
|
142
|
+
}
|
|
143
|
+
async recordResolvedImport(source, resolvedId) {
|
|
144
|
+
const configuredAlias = this.findAlias(source);
|
|
145
|
+
const sourcePackageName = getPackageNameFromSpecifier(source);
|
|
146
|
+
const expectedPackageName = this.getExpectedPackageName(source);
|
|
147
|
+
if (configuredAlias === void 0 && expectedPackageName === void 0) return;
|
|
148
|
+
let realResolvedPath;
|
|
149
|
+
try {
|
|
150
|
+
realResolvedPath = await realpathNative(path.resolve(resolvedId.split("?")[0]));
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let packageRoot;
|
|
155
|
+
if (configuredAlias !== void 0) packageRoot = await this.resolveAliasPackageRoot(configuredAlias, source, sourcePackageName, realResolvedPath);
|
|
156
|
+
else packageRoot = await findPackageRoot(path.dirname(realResolvedPath), expectedPackageName);
|
|
157
|
+
if (packageRoot !== null) this.linkedPackageRoots.add(packageRoot);
|
|
158
|
+
}
|
|
159
|
+
async resolveModuleBoundary(moduleId) {
|
|
160
|
+
const modulePath = path.resolve(moduleId.split("?")[0]);
|
|
161
|
+
let realProjectRoot;
|
|
162
|
+
let realModulePath;
|
|
163
|
+
try {
|
|
164
|
+
[realProjectRoot, realModulePath] = await Promise.all([realpathNative(this.projectRoot), realpathNative(modulePath)]);
|
|
165
|
+
} catch {
|
|
166
|
+
try {
|
|
167
|
+
realProjectRoot = await realpathNative(this.projectRoot);
|
|
168
|
+
const realModuleDir = await realpathNative(path.dirname(modulePath));
|
|
169
|
+
realModulePath = path.join(realModuleDir, path.basename(modulePath));
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const moduleDir = path.dirname(realModulePath);
|
|
175
|
+
const logicalPackageRoot = getNodeModulesPackageRoot(this.projectRoot, modulePath);
|
|
176
|
+
if (logicalPackageRoot !== null) {
|
|
177
|
+
let realPackageRoot;
|
|
178
|
+
try {
|
|
179
|
+
realPackageRoot = await realpathNative(logicalPackageRoot);
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
if (isPathInside(realPackageRoot, realModulePath)) return {
|
|
184
|
+
assetRoot: realPackageRoot,
|
|
185
|
+
moduleDir
|
|
186
|
+
};
|
|
187
|
+
const declaredPackageName = await readPackageName(logicalPackageRoot);
|
|
188
|
+
if (declaredPackageName === null) return null;
|
|
189
|
+
const logicalModuleRelativePath = path.relative(logicalPackageRoot, modulePath);
|
|
190
|
+
if (path.isAbsolute(logicalModuleRelativePath) || logicalModuleRelativePath === ".." || logicalModuleRelativePath.startsWith(`..${path.sep}`)) return null;
|
|
191
|
+
const canonicalPackageRoot = await findPackageRoot(moduleDir, declaredPackageName);
|
|
192
|
+
if (canonicalPackageRoot === null) return null;
|
|
193
|
+
if (path.relative(canonicalPackageRoot, realModulePath) !== logicalModuleRelativePath) return null;
|
|
194
|
+
return {
|
|
195
|
+
assetRoot: canonicalPackageRoot,
|
|
196
|
+
moduleDir
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (isPathInside(realProjectRoot, realModulePath)) return {
|
|
200
|
+
assetRoot: realProjectRoot,
|
|
201
|
+
moduleDir
|
|
202
|
+
};
|
|
203
|
+
const linkedPackageRoot = [...this.linkedPackageRoots].filter((root) => isPathInside(root, realModulePath)).sort((a, b) => b.length - a.length)[0];
|
|
204
|
+
if (linkedPackageRoot !== void 0) return {
|
|
205
|
+
assetRoot: linkedPackageRoot,
|
|
206
|
+
moduleDir
|
|
207
|
+
};
|
|
208
|
+
const configuredAliasRoot = await this.resolveConfiguredAliasRoot(realModulePath);
|
|
209
|
+
return configuredAliasRoot === null ? null : {
|
|
210
|
+
assetRoot: configuredAliasRoot,
|
|
211
|
+
moduleDir
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async resolveContainedAsset(assetRoot, assetPath) {
|
|
215
|
+
try {
|
|
216
|
+
const realPath = await realpathNative(assetPath);
|
|
217
|
+
return isPathInside(assetRoot, realPath) ? realPath : null;
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
findAlias(source) {
|
|
223
|
+
return [...this.stringAliasesByFirstCharacter.get(source[0] ?? "") ?? [], ...this.regularExpressionAliases].filter((alias) => aliasMatches(alias.find, source)).sort((a, b) => a.index - b.index)[0];
|
|
224
|
+
}
|
|
225
|
+
getExpectedPackageName(source) {
|
|
226
|
+
const packageName = getPackageNameFromSpecifier(source);
|
|
227
|
+
return packageName === null ? void 0 : this.dependencyPackageNames.get(packageName);
|
|
228
|
+
}
|
|
229
|
+
async resolveAliasPackageRoot(alias, source, sourcePackageName, realResolvedPath) {
|
|
230
|
+
const aliasTarget = applyAlias(alias.find, alias.replacement, source);
|
|
231
|
+
if (!path.isAbsolute(aliasTarget)) return null;
|
|
232
|
+
try {
|
|
233
|
+
const realAliasTarget = await realpathNative(aliasTarget);
|
|
234
|
+
const aliasTargetStat = await fs.promises.stat(realAliasTarget);
|
|
235
|
+
const hasCapture = alias.find instanceof RegExp && alias.replacement.includes("$");
|
|
236
|
+
let configuredDirectory = null;
|
|
237
|
+
if (!hasCapture) {
|
|
238
|
+
const realReplacement = await realpathNative(alias.replacement);
|
|
239
|
+
if ((await fs.promises.stat(realReplacement)).isDirectory()) configuredDirectory = realReplacement;
|
|
240
|
+
}
|
|
241
|
+
if (configuredDirectory !== null || aliasTargetStat.isDirectory()) {
|
|
242
|
+
const aliasBoundary = configuredDirectory ?? realAliasTarget;
|
|
243
|
+
const packageRoot = await findPackageRoot(path.dirname(realResolvedPath));
|
|
244
|
+
if (packageRoot === null || !isPathInside(aliasBoundary, packageRoot) || !isPathInside(packageRoot, realResolvedPath)) return null;
|
|
245
|
+
return packageRoot;
|
|
246
|
+
}
|
|
247
|
+
const packageRoot = await findPackageRoot(path.dirname(realResolvedPath));
|
|
248
|
+
if (packageRoot === null || !await packageOwnsAliasFile(packageRoot, sourcePackageName, realAliasTarget) || !isPathInside(packageRoot, realResolvedPath)) return null;
|
|
249
|
+
return packageRoot;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async resolveConfiguredAliasRoot(realModulePath) {
|
|
255
|
+
const packageRoot = await findPackageRoot(path.dirname(realModulePath));
|
|
256
|
+
if (packageRoot === null || !isPathInside(packageRoot, realModulePath)) return null;
|
|
257
|
+
for (const alias of this.configuredAliases) {
|
|
258
|
+
if (!path.isAbsolute(alias.replacement)) continue;
|
|
259
|
+
const packageName = typeof alias.find === "string" ? getPackageNameFromSpecifier(alias.find) : null;
|
|
260
|
+
if (alias.replacement.indexOf("$") !== -1) {
|
|
261
|
+
const packageManifestName = await readPackageName(packageRoot);
|
|
262
|
+
if (packageManifestName === null || !aliasMatches(alias.find, packageManifestName)) continue;
|
|
263
|
+
const aliasTarget = applyAlias(alias.find, alias.replacement, packageManifestName);
|
|
264
|
+
if (!path.isAbsolute(aliasTarget)) continue;
|
|
265
|
+
try {
|
|
266
|
+
const realAliasTarget = await realpathNative(aliasTarget);
|
|
267
|
+
const aliasTargetStat = await fs.promises.stat(realAliasTarget);
|
|
268
|
+
if (aliasTargetStat.isDirectory() && isPathInside(realAliasTarget, realModulePath) || aliasTargetStat.isFile() && realAliasTarget === realModulePath) return packageRoot;
|
|
269
|
+
} catch {}
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const realReplacement = await realpathNative(alias.replacement);
|
|
274
|
+
if ((await fs.promises.stat(realReplacement)).isDirectory()) {
|
|
275
|
+
if (isPathInside(packageRoot, realReplacement) && isPathInside(realReplacement, realModulePath) && await packageOwnsAliasDirectory(packageRoot, packageName, realReplacement)) return packageRoot;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (realReplacement === realModulePath && await packageOwnsAliasFile(packageRoot, packageName, realReplacement)) return packageRoot;
|
|
279
|
+
} catch {}
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
//#endregion
|
|
285
|
+
export { OgAssetOwnership };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OgAssetOwnership } from "./og-asset-ownership.js";
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -15,29 +16,46 @@ import MagicString from "magic-string";
|
|
|
15
16
|
*/
|
|
16
17
|
function createOgInlineFetchAssetsPlugin() {
|
|
17
18
|
const cache = /* @__PURE__ */ new Map();
|
|
19
|
+
const ownership = new OgAssetOwnership();
|
|
18
20
|
let isBuild = false;
|
|
19
21
|
return {
|
|
20
22
|
name: "vinext:og-inline-fetch-assets",
|
|
21
23
|
enforce: "pre",
|
|
22
24
|
configResolved(config) {
|
|
23
25
|
isBuild = config.command === "build";
|
|
26
|
+
ownership.configure(config.root, config.resolve.alias);
|
|
24
27
|
},
|
|
25
28
|
buildStart() {
|
|
26
29
|
if (isBuild) cache.clear();
|
|
30
|
+
ownership.reset();
|
|
31
|
+
},
|
|
32
|
+
async resolveId(source, importer, options) {
|
|
33
|
+
if (!ownership.shouldTrackImport(source)) return null;
|
|
34
|
+
const resolved = await this.resolve(source, importer, {
|
|
35
|
+
...options,
|
|
36
|
+
skipSelf: true
|
|
37
|
+
});
|
|
38
|
+
if (resolved === null || resolved.external) return null;
|
|
39
|
+
await ownership.recordResolvedImport(source, resolved.id);
|
|
40
|
+
return null;
|
|
27
41
|
},
|
|
28
42
|
transform: {
|
|
29
43
|
filter: { code: "import.meta.url" },
|
|
30
44
|
async handler(code, id) {
|
|
31
45
|
const useCache = isBuild;
|
|
32
|
-
const
|
|
46
|
+
const boundary = await ownership.resolveModuleBoundary(id);
|
|
47
|
+
if (boundary === null) return null;
|
|
48
|
+
const { assetRoot, moduleDir } = boundary;
|
|
33
49
|
let newCode = code;
|
|
34
50
|
let didReplace = false;
|
|
35
51
|
const readAsBase64 = async (absPath) => {
|
|
36
|
-
const
|
|
52
|
+
const realPath = await ownership.resolveContainedAsset(assetRoot, absPath);
|
|
53
|
+
if (realPath === null) return null;
|
|
54
|
+
const cached = useCache ? cache.get(realPath) : void 0;
|
|
37
55
|
if (cached !== void 0) return cached;
|
|
38
56
|
try {
|
|
39
|
-
const b64 = (await fs.promises.readFile(
|
|
40
|
-
if (useCache) cache.set(
|
|
57
|
+
const b64 = (await fs.promises.readFile(realPath)).toString("base64");
|
|
58
|
+
if (useCache) cache.set(realPath, b64);
|
|
41
59
|
return b64;
|
|
42
60
|
} catch {
|
|
43
61
|
return null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { IncomingMessage } from "node:http";
|
|
2
|
+
|
|
3
|
+
//#region src/server/accept-encoding.d.ts
|
|
4
|
+
type ContentEncoding = "zstd" | "br" | "gzip" | "deflate";
|
|
5
|
+
type NegotiatedEncoding = ContentEncoding | "identity";
|
|
6
|
+
/** Parsed explicit coding qualities plus the wildcard quality, when present. */
|
|
7
|
+
type ParsedAcceptEncoding = {
|
|
8
|
+
qualities: Map<string, number>;
|
|
9
|
+
orders: Map<string, number>;
|
|
10
|
+
wildcardQuality: number | null;
|
|
11
|
+
};
|
|
12
|
+
/** Parse an Accept-Encoding header into exact-token numeric qualities. */
|
|
13
|
+
declare function parseAcceptedEncodings(accept: string): ParsedAcceptEncoding;
|
|
14
|
+
/** Return the effective quality for a coding, including wildcard/identity rules. */
|
|
15
|
+
declare function getEncodingQuality(parsed: ParsedAcceptEncoding, encoding: string): number;
|
|
16
|
+
declare function isEncodingAccepted(parsed: ParsedAcceptEncoding, encoding: string): boolean;
|
|
17
|
+
/** Choose the highest-quality available coding, using array order as the tie-breaker. */
|
|
18
|
+
declare function selectAcceptedEncoding<T extends string>(parsed: ParsedAcceptEncoding, available: readonly T[]): T | null;
|
|
19
|
+
/**
|
|
20
|
+
* Select a content coding while matching Next.js's compression middleware:
|
|
21
|
+
* an explicit identity preference can win, but implicit identity remains a
|
|
22
|
+
* fallback rather than suppressing an otherwise acceptable encoding.
|
|
23
|
+
*/
|
|
24
|
+
declare function selectContentEncoding<T extends ContentEncoding>(parsed: ParsedAcceptEncoding, available: readonly T[]): T | "identity";
|
|
25
|
+
declare const HAS_ZSTD: boolean;
|
|
26
|
+
/** Select by client q-value first, then zstd > br > gzip > deflate > identity. */
|
|
27
|
+
declare function negotiateEncoding(req: IncomingMessage): NegotiatedEncoding;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { HAS_ZSTD, NegotiatedEncoding, ParsedAcceptEncoding, getEncodingQuality, isEncodingAccepted, negotiateEncoding, parseAcceptedEncodings, selectAcceptedEncoding, selectContentEncoding };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import zlib from "node:zlib";
|
|
2
|
+
//#region src/server/accept-encoding.ts
|
|
3
|
+
/** Parse an Accept-Encoding header into exact-token numeric qualities. */
|
|
4
|
+
function parseAcceptedEncodings(accept) {
|
|
5
|
+
const qualities = /* @__PURE__ */ new Map();
|
|
6
|
+
const orders = /* @__PURE__ */ new Map();
|
|
7
|
+
let wildcardQuality = null;
|
|
8
|
+
for (const [order, part] of accept.split(",").entries()) {
|
|
9
|
+
const trimmed = part.trim();
|
|
10
|
+
if (trimmed.length === 0) continue;
|
|
11
|
+
const semi = trimmed.indexOf(";");
|
|
12
|
+
const token = (semi === -1 ? trimmed : trimmed.slice(0, semi)).trim().toLowerCase();
|
|
13
|
+
if (token.length === 0) continue;
|
|
14
|
+
let quality = 1;
|
|
15
|
+
if (semi !== -1) {
|
|
16
|
+
let qStr;
|
|
17
|
+
for (const param of trimmed.slice(semi + 1).split(";")) {
|
|
18
|
+
const [name, value] = param.trim().split("=");
|
|
19
|
+
if (name !== "q") continue;
|
|
20
|
+
qStr = value ?? "";
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
if (qStr !== void 0) quality = Number.parseFloat(qStr);
|
|
24
|
+
}
|
|
25
|
+
if (token === "*") {
|
|
26
|
+
if (wildcardQuality === null || Number.isNaN(quality) || Number.isNaN(wildcardQuality) || quality >= wildcardQuality) wildcardQuality = quality;
|
|
27
|
+
} else {
|
|
28
|
+
const currentQuality = qualities.get(token);
|
|
29
|
+
if (currentQuality === void 0 || Number.isNaN(quality) || Number.isNaN(currentQuality) || quality >= currentQuality) {
|
|
30
|
+
qualities.set(token, quality);
|
|
31
|
+
orders.set(token, order);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
qualities,
|
|
37
|
+
orders,
|
|
38
|
+
wildcardQuality
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Return the effective quality for a coding, including wildcard/identity rules. */
|
|
42
|
+
function getEncodingQuality(parsed, encoding) {
|
|
43
|
+
const normalized = encoding.toLowerCase();
|
|
44
|
+
const explicit = parsed.qualities.get(normalized);
|
|
45
|
+
if (explicit !== void 0) return Number.isNaN(explicit) ? 0 : explicit;
|
|
46
|
+
if (normalized === "identity") return parsed.wildcardQuality === 0 ? 0 : 1;
|
|
47
|
+
if (parsed.wildcardQuality !== null) return Number.isNaN(parsed.wildcardQuality) ? 0 : parsed.wildcardQuality;
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
function isEncodingAccepted(parsed, encoding) {
|
|
51
|
+
return getEncodingQuality(parsed, encoding) > 0;
|
|
52
|
+
}
|
|
53
|
+
/** Choose the highest-quality available coding, using array order as the tie-breaker. */
|
|
54
|
+
function selectAcceptedEncoding(parsed, available) {
|
|
55
|
+
let selected = null;
|
|
56
|
+
let selectedQuality = 0;
|
|
57
|
+
for (const encoding of available) {
|
|
58
|
+
const quality = getEncodingQuality(parsed, encoding);
|
|
59
|
+
if (quality > selectedQuality) {
|
|
60
|
+
selected = encoding;
|
|
61
|
+
selectedQuality = quality;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return selected;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Select a content coding while matching Next.js's compression middleware:
|
|
68
|
+
* an explicit identity preference can win, but implicit identity remains a
|
|
69
|
+
* fallback rather than suppressing an otherwise acceptable encoding.
|
|
70
|
+
*/
|
|
71
|
+
function selectContentEncoding(parsed, available) {
|
|
72
|
+
const selected = selectAcceptedEncoding(parsed, available);
|
|
73
|
+
if (selected === null) return "identity";
|
|
74
|
+
const identityQuality = parsed.qualities.get("identity");
|
|
75
|
+
if (identityQuality === void 0 || Number.isNaN(identityQuality)) return selected;
|
|
76
|
+
const selectedQuality = getEncodingQuality(parsed, selected);
|
|
77
|
+
if (identityQuality > selectedQuality) return "identity";
|
|
78
|
+
if (identityQuality < selectedQuality) return selected;
|
|
79
|
+
if (!parsed.qualities.has(selected)) return "identity";
|
|
80
|
+
return (parsed.orders.get("identity") ?? Number.POSITIVE_INFINITY) < (parsed.orders.get(selected) ?? Number.POSITIVE_INFINITY) ? "identity" : selected;
|
|
81
|
+
}
|
|
82
|
+
const HAS_ZSTD = typeof zlib.createZstdCompress === "function";
|
|
83
|
+
/** Select by client q-value first, then zstd > br > gzip > deflate > identity. */
|
|
84
|
+
function negotiateEncoding(req) {
|
|
85
|
+
const accept = req.headers["accept-encoding"];
|
|
86
|
+
if (typeof accept !== "string") return "identity";
|
|
87
|
+
return selectContentEncoding(parseAcceptedEncodings(accept), [
|
|
88
|
+
...HAS_ZSTD ? ["zstd"] : [],
|
|
89
|
+
"br",
|
|
90
|
+
"gzip",
|
|
91
|
+
"deflate"
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { HAS_ZSTD, getEncodingQuality, isEncodingAccepted, negotiateEncoding, parseAcceptedEncodings, selectAcceptedEncoding, selectContentEncoding };
|