vinext 0.0.32 → 0.0.34
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 +7 -6
- package/dist/config/next-config.d.ts +2 -0
- package/dist/config/next-config.js +4 -0
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.js +52 -4
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-browser-entry.js +3 -330
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.js +444 -1265
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-ssr-entry.js +4 -460
- package/dist/entries/app-ssr-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +8 -1
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +13 -0
- package/dist/entries/runtime-entry-module.js +27 -0
- package/dist/entries/runtime-entry-module.js.map +1 -0
- package/dist/index.js +302 -23
- package/dist/index.js.map +1 -1
- package/dist/plugins/optimize-imports.d.ts +38 -0
- package/dist/plugins/optimize-imports.js +557 -0
- package/dist/plugins/optimize-imports.js.map +1 -0
- package/dist/server/app-browser-entry.d.ts +1 -0
- package/dist/server/app-browser-entry.js +160 -0
- package/dist/server/app-browser-entry.js.map +1 -0
- package/dist/server/app-browser-stream.d.ts +33 -0
- package/dist/server/app-browser-stream.js +54 -0
- package/dist/server/app-browser-stream.js.map +1 -0
- package/dist/server/app-page-boundary-render.d.ts +63 -0
- package/dist/server/app-page-boundary-render.js +182 -0
- package/dist/server/app-page-boundary-render.js.map +1 -0
- package/dist/server/app-page-boundary.d.ts +57 -0
- package/dist/server/app-page-boundary.js +60 -0
- package/dist/server/app-page-boundary.js.map +1 -0
- package/dist/server/app-page-cache.d.ts +61 -0
- package/dist/server/app-page-cache.js +133 -0
- package/dist/server/app-page-cache.js.map +1 -0
- package/dist/server/app-page-execution.d.ts +46 -0
- package/dist/server/app-page-execution.js +109 -0
- package/dist/server/app-page-execution.js.map +1 -0
- package/dist/server/app-page-probe.d.ts +17 -0
- package/dist/server/app-page-probe.js +35 -0
- package/dist/server/app-page-probe.js.map +1 -0
- package/dist/server/app-page-render.d.ts +59 -0
- package/dist/server/app-page-render.js +174 -0
- package/dist/server/app-page-render.js.map +1 -0
- package/dist/server/app-page-request.d.ts +58 -0
- package/dist/server/app-page-request.js +79 -0
- package/dist/server/app-page-request.js.map +1 -0
- package/dist/server/app-page-response.d.ts +51 -0
- package/dist/server/app-page-response.js +90 -0
- package/dist/server/app-page-response.js.map +1 -0
- package/dist/server/app-page-stream.d.ts +55 -0
- package/dist/server/app-page-stream.js +65 -0
- package/dist/server/app-page-stream.js.map +1 -0
- package/dist/server/app-route-handler-cache.d.ts +42 -0
- package/dist/server/app-route-handler-cache.js +69 -0
- package/dist/server/app-route-handler-cache.js.map +1 -0
- package/dist/server/app-route-handler-execution.d.ts +64 -0
- package/dist/server/app-route-handler-execution.js +100 -0
- package/dist/server/app-route-handler-execution.js.map +1 -0
- package/dist/server/app-route-handler-policy.d.ts +51 -0
- package/dist/server/app-route-handler-policy.js +57 -0
- package/dist/server/app-route-handler-policy.js.map +1 -0
- package/dist/server/app-route-handler-response.d.ts +26 -0
- package/dist/server/app-route-handler-response.js +61 -0
- package/dist/server/app-route-handler-response.js.map +1 -0
- package/dist/server/app-route-handler-runtime.d.ts +27 -0
- package/dist/server/app-route-handler-runtime.js +99 -0
- package/dist/server/app-route-handler-runtime.js.map +1 -0
- package/dist/server/app-ssr-entry.d.ts +19 -0
- package/dist/server/app-ssr-entry.js +105 -0
- package/dist/server/app-ssr-entry.js.map +1 -0
- package/dist/server/app-ssr-stream.d.ts +30 -0
- package/dist/server/app-ssr-stream.js +116 -0
- package/dist/server/app-ssr-stream.js.map +1 -0
- package/dist/server/prod-server.d.ts +13 -1
- package/dist/server/prod-server.js +113 -19
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/worker-utils.d.ts +0 -6
- package/dist/server/worker-utils.js +41 -5
- package/dist/server/worker-utils.js.map +1 -1
- package/dist/shims/error-boundary.js +1 -1
- package/dist/shims/font-google-base.js +1 -1
- package/dist/shims/font-google-base.js.map +1 -1
- package/dist/shims/font-google.d.ts +2 -3
- package/dist/shims/font-google.js +2 -3
- package/dist/shims/metadata.js +3 -3
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/request-state-types.d.ts +2 -2
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/package.json +1 -1
- package/dist/shims/font-google.generated.d.ts +0 -1929
- package/dist/shims/font-google.generated.js +0 -1929
- package/dist/shims/font-google.generated.js.map +0 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ResolvedNextConfig } from "../config/next-config.js";
|
|
2
|
+
import { Plugin } from "vite";
|
|
3
|
+
|
|
4
|
+
//#region src/plugins/optimize-imports.d.ts
|
|
5
|
+
interface BarrelExportEntry {
|
|
6
|
+
source: string;
|
|
7
|
+
isNamespace: boolean;
|
|
8
|
+
originalName?: string;
|
|
9
|
+
}
|
|
10
|
+
type BarrelExportMap = Map<string, BarrelExportEntry>;
|
|
11
|
+
/**
|
|
12
|
+
* Packages whose barrel imports are automatically optimized.
|
|
13
|
+
* Matches Next.js's built-in optimizePackageImports defaults plus radix-ui.
|
|
14
|
+
* @see https://github.com/vercel/next.js/blob/9c31bbdaa/packages/next/src/server/config.ts#L1301
|
|
15
|
+
*/
|
|
16
|
+
declare const DEFAULT_OPTIMIZE_PACKAGES: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Build a map of exported names → source sub-module for a barrel package.
|
|
19
|
+
*
|
|
20
|
+
* Parses the barrel entry file AST and extracts the export map.
|
|
21
|
+
* Handles: `export * as X from`, `export { A } from`, `import * as X; export { X }`,
|
|
22
|
+
* and `export * from "./sub"` (recursively resolves wildcard re-exports).
|
|
23
|
+
*
|
|
24
|
+
* Returns null if the entry cannot be resolved, the file cannot be read, or
|
|
25
|
+
* the file has a parse error. Returns an empty map if the file is valid but
|
|
26
|
+
* exports nothing.
|
|
27
|
+
*/
|
|
28
|
+
declare function buildBarrelExportMap(packageName: string, resolveEntry: (pkg: string) => string | null, readFile: (filepath: string) => Promise<string | null>, cache?: Map<string, BarrelExportMap>): Promise<BarrelExportMap | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Creates the vinext:optimize-imports Vite plugin.
|
|
31
|
+
*
|
|
32
|
+
* @param nextConfig - Resolved Next.js config (may be undefined before config hook runs).
|
|
33
|
+
* @param getRoot - Returns the current project root (set by the vinext:config hook).
|
|
34
|
+
*/
|
|
35
|
+
declare function createOptimizeImportsPlugin(getNextConfig: () => ResolvedNextConfig | undefined, getRoot: () => string): Plugin;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { DEFAULT_OPTIMIZE_PACKAGES, buildBarrelExportMap, createOptimizeImportsPlugin };
|
|
38
|
+
//# sourceMappingURL=optimize-imports.d.ts.map
|
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseAst } from "vite";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import MagicString from "magic-string";
|
|
6
|
+
//#region src/plugins/optimize-imports.ts
|
|
7
|
+
/**
|
|
8
|
+
* Read a file's contents, returning null on any error.
|
|
9
|
+
* Module-level so a single function instance is shared across all transform calls.
|
|
10
|
+
*/
|
|
11
|
+
async function readFileSafe(filepath) {
|
|
12
|
+
try {
|
|
13
|
+
return await fs.readFile(filepath, "utf-8");
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Extract the string name from an Identifier ({name}) or Literal ({value}) AST node.
|
|
19
|
+
* Returns null for unexpected node shapes so callers can degrade gracefully rather than crash. */
|
|
20
|
+
function astName(node) {
|
|
21
|
+
if (node.name !== void 0) return node.name;
|
|
22
|
+
if (typeof node.value === "string") return node.value;
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Packages whose barrel imports are automatically optimized.
|
|
27
|
+
* Matches Next.js's built-in optimizePackageImports defaults plus radix-ui.
|
|
28
|
+
* @see https://github.com/vercel/next.js/blob/9c31bbdaa/packages/next/src/server/config.ts#L1301
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_OPTIMIZE_PACKAGES = [
|
|
31
|
+
"lucide-react",
|
|
32
|
+
"date-fns",
|
|
33
|
+
"lodash-es",
|
|
34
|
+
"ramda",
|
|
35
|
+
"antd",
|
|
36
|
+
"react-bootstrap",
|
|
37
|
+
"ahooks",
|
|
38
|
+
"@ant-design/icons",
|
|
39
|
+
"@headlessui/react",
|
|
40
|
+
"@headlessui-float/react",
|
|
41
|
+
"@heroicons/react/20/solid",
|
|
42
|
+
"@heroicons/react/24/solid",
|
|
43
|
+
"@heroicons/react/24/outline",
|
|
44
|
+
"@visx/visx",
|
|
45
|
+
"@tremor/react",
|
|
46
|
+
"rxjs",
|
|
47
|
+
"@mui/material",
|
|
48
|
+
"@mui/icons-material",
|
|
49
|
+
"recharts",
|
|
50
|
+
"react-use",
|
|
51
|
+
"effect",
|
|
52
|
+
"@effect/schema",
|
|
53
|
+
"@effect/platform",
|
|
54
|
+
"@effect/platform-node",
|
|
55
|
+
"@effect/platform-browser",
|
|
56
|
+
"@effect/platform-bun",
|
|
57
|
+
"@effect/sql",
|
|
58
|
+
"@effect/sql-mssql",
|
|
59
|
+
"@effect/sql-mysql2",
|
|
60
|
+
"@effect/sql-pg",
|
|
61
|
+
"@effect/sql-sqlite-node",
|
|
62
|
+
"@effect/sql-sqlite-bun",
|
|
63
|
+
"@effect/sql-sqlite-wasm",
|
|
64
|
+
"@effect/sql-sqlite-react-native",
|
|
65
|
+
"@effect/rpc",
|
|
66
|
+
"@effect/rpc-http",
|
|
67
|
+
"@effect/typeclass",
|
|
68
|
+
"@effect/experimental",
|
|
69
|
+
"@effect/opentelemetry",
|
|
70
|
+
"@material-ui/core",
|
|
71
|
+
"@material-ui/icons",
|
|
72
|
+
"@tabler/icons-react",
|
|
73
|
+
"mui-core",
|
|
74
|
+
"react-icons/ai",
|
|
75
|
+
"react-icons/bi",
|
|
76
|
+
"react-icons/bs",
|
|
77
|
+
"react-icons/cg",
|
|
78
|
+
"react-icons/ci",
|
|
79
|
+
"react-icons/di",
|
|
80
|
+
"react-icons/fa",
|
|
81
|
+
"react-icons/fa6",
|
|
82
|
+
"react-icons/fc",
|
|
83
|
+
"react-icons/fi",
|
|
84
|
+
"react-icons/gi",
|
|
85
|
+
"react-icons/go",
|
|
86
|
+
"react-icons/gr",
|
|
87
|
+
"react-icons/hi",
|
|
88
|
+
"react-icons/hi2",
|
|
89
|
+
"react-icons/im",
|
|
90
|
+
"react-icons/io",
|
|
91
|
+
"react-icons/io5",
|
|
92
|
+
"react-icons/lia",
|
|
93
|
+
"react-icons/lib",
|
|
94
|
+
"react-icons/lu",
|
|
95
|
+
"react-icons/md",
|
|
96
|
+
"react-icons/pi",
|
|
97
|
+
"react-icons/ri",
|
|
98
|
+
"react-icons/rx",
|
|
99
|
+
"react-icons/si",
|
|
100
|
+
"react-icons/sl",
|
|
101
|
+
"react-icons/tb",
|
|
102
|
+
"react-icons/tfi",
|
|
103
|
+
"react-icons/ti",
|
|
104
|
+
"react-icons/vsc",
|
|
105
|
+
"react-icons/wi",
|
|
106
|
+
"radix-ui"
|
|
107
|
+
];
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a package.json exports value to a string entry path.
|
|
110
|
+
* Prefers node → import → module → default conditions, recursing into nested objects.
|
|
111
|
+
* When `preferReactServer` is true (RSC environment), "react-server" is checked first
|
|
112
|
+
* so that packages like `react` and `react-dom` resolve their RSC-compatible entry points.
|
|
113
|
+
*/
|
|
114
|
+
function resolveExportsValue(value, preferReactServer) {
|
|
115
|
+
if (typeof value === "string") return value;
|
|
116
|
+
if (typeof value === "object" && value !== null) {
|
|
117
|
+
const conditions = preferReactServer ? [
|
|
118
|
+
"react-server",
|
|
119
|
+
"node",
|
|
120
|
+
"import",
|
|
121
|
+
"module",
|
|
122
|
+
"default"
|
|
123
|
+
] : [
|
|
124
|
+
"node",
|
|
125
|
+
"import",
|
|
126
|
+
"module",
|
|
127
|
+
"default"
|
|
128
|
+
];
|
|
129
|
+
for (const key of conditions) {
|
|
130
|
+
const nested = value[key];
|
|
131
|
+
if (nested !== void 0) {
|
|
132
|
+
const resolved = resolveExportsValue(nested, preferReactServer);
|
|
133
|
+
if (resolved) return resolved;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Resolve a package name to its directory and parsed package.json.
|
|
141
|
+
* Handles packages with strict `exports` fields that don't expose `./package.json`
|
|
142
|
+
* by first resolving the main entry, then walking up to find the package root.
|
|
143
|
+
*/
|
|
144
|
+
async function resolvePackageInfo(packageName, projectRoot) {
|
|
145
|
+
try {
|
|
146
|
+
const req = createRequire(path.join(projectRoot, "package.json"));
|
|
147
|
+
try {
|
|
148
|
+
const pkgJsonPath = req.resolve(`${packageName}/package.json`);
|
|
149
|
+
return {
|
|
150
|
+
pkgDir: path.dirname(pkgJsonPath),
|
|
151
|
+
pkgJson: JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"))
|
|
152
|
+
};
|
|
153
|
+
} catch {
|
|
154
|
+
try {
|
|
155
|
+
const mainEntry = req.resolve(packageName);
|
|
156
|
+
let dir = path.dirname(mainEntry);
|
|
157
|
+
for (let i = 0; i < 10; i++) {
|
|
158
|
+
const candidate = path.join(dir, "package.json");
|
|
159
|
+
try {
|
|
160
|
+
const parsed = JSON.parse(await fs.readFile(candidate, "utf-8"));
|
|
161
|
+
if (parsed.name === packageName) return {
|
|
162
|
+
pkgDir: dir,
|
|
163
|
+
pkgJson: parsed
|
|
164
|
+
};
|
|
165
|
+
} catch {}
|
|
166
|
+
const parent = path.dirname(dir);
|
|
167
|
+
if (parent === dir) break;
|
|
168
|
+
dir = parent;
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Resolve a package name to its ESM entry file path.
|
|
181
|
+
* Checks `exports["."]` → `module` → `main`, then falls back to require.resolve.
|
|
182
|
+
* Pass `preferReactServer: true` in the RSC environment to prefer the "react-server"
|
|
183
|
+
* export condition over "node"/"import" when resolving the barrel entry.
|
|
184
|
+
*/
|
|
185
|
+
async function resolvePackageEntry(packageName, projectRoot, preferReactServer) {
|
|
186
|
+
try {
|
|
187
|
+
const info = await resolvePackageInfo(packageName, projectRoot);
|
|
188
|
+
if (!info) return null;
|
|
189
|
+
const { pkgDir, pkgJson } = info;
|
|
190
|
+
if (pkgJson.exports) {
|
|
191
|
+
const dotExport = pkgJson.exports["."];
|
|
192
|
+
if (dotExport) {
|
|
193
|
+
const entryPath = resolveExportsValue(dotExport, preferReactServer);
|
|
194
|
+
if (entryPath) return path.resolve(pkgDir, entryPath).split(path.sep).join("/");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const entryField = pkgJson.module ?? pkgJson.main;
|
|
198
|
+
if (typeof entryField === "string") return path.resolve(pkgDir, entryField).split(path.sep).join("/");
|
|
199
|
+
return createRequire(path.join(projectRoot, "package.json")).resolve(packageName).split(path.sep).join("/");
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Build a map of exported names → source sub-module for a barrel file.
|
|
206
|
+
*
|
|
207
|
+
* Internal recursive helper used by buildBarrelExportMap. Parses a single file's
|
|
208
|
+
* AST and populates `exportMap` with resolved entries. Handles:
|
|
209
|
+
* - `export * as Name from "sub-pkg"` — namespace re-export
|
|
210
|
+
* - `export { A, B } from "sub-pkg"` — named re-export
|
|
211
|
+
* - `import * as X; export { X }` — indirect namespace re-export
|
|
212
|
+
* - `export * from "./sub"` — wildcard: recursively parse sub-module and merge exports
|
|
213
|
+
*
|
|
214
|
+
* Returns an empty map when the file cannot be read or has a parse error, so that
|
|
215
|
+
* recursive wildcard calls degrade gracefully without aborting the whole barrel walk.
|
|
216
|
+
*
|
|
217
|
+
* @param initialContent - Pre-read file content for `filePath`. If provided, skips the
|
|
218
|
+
* `readFile` call for the entry file — avoids a redundant read when the caller
|
|
219
|
+
* already has the content in hand.
|
|
220
|
+
*/
|
|
221
|
+
async function buildExportMapFromFile(filePath, readFile, cache, visited, initialContent) {
|
|
222
|
+
if (visited.has(filePath)) return /* @__PURE__ */ new Map();
|
|
223
|
+
visited.add(filePath);
|
|
224
|
+
const cached = cache.get(filePath);
|
|
225
|
+
if (cached) return cached;
|
|
226
|
+
const content = initialContent ?? await readFile(filePath);
|
|
227
|
+
if (!content) return /* @__PURE__ */ new Map();
|
|
228
|
+
let ast;
|
|
229
|
+
try {
|
|
230
|
+
ast = parseAst(content);
|
|
231
|
+
} catch {
|
|
232
|
+
return /* @__PURE__ */ new Map();
|
|
233
|
+
}
|
|
234
|
+
const exportMap = /* @__PURE__ */ new Map();
|
|
235
|
+
const importBindings = /* @__PURE__ */ new Map();
|
|
236
|
+
const fileDir = path.dirname(filePath);
|
|
237
|
+
/**
|
|
238
|
+
* Normalize a source specifier: resolve relative paths to absolute so that
|
|
239
|
+
* entries in the export map always store absolute paths for file references.
|
|
240
|
+
* Bare package specifiers (e.g. "@radix-ui/react-slot") are returned unchanged.
|
|
241
|
+
*/
|
|
242
|
+
function normalizeSource(source) {
|
|
243
|
+
return source.startsWith(".") ? path.resolve(fileDir, source).split(path.sep).join("/") : source;
|
|
244
|
+
}
|
|
245
|
+
for (const node of ast.body) switch (node.type) {
|
|
246
|
+
case "ImportDeclaration": {
|
|
247
|
+
const rawSource = typeof node.source?.value === "string" ? node.source.value : null;
|
|
248
|
+
if (!rawSource) break;
|
|
249
|
+
const source = normalizeSource(rawSource);
|
|
250
|
+
for (const spec of node.specifiers ?? []) switch (spec.type) {
|
|
251
|
+
case "ImportNamespaceSpecifier":
|
|
252
|
+
importBindings.set(spec.local.name, {
|
|
253
|
+
source,
|
|
254
|
+
isNamespace: true
|
|
255
|
+
});
|
|
256
|
+
break;
|
|
257
|
+
case "ImportSpecifier":
|
|
258
|
+
if (spec.imported) {
|
|
259
|
+
const name = astName(spec.imported);
|
|
260
|
+
if (name !== null) importBindings.set(spec.local.name, {
|
|
261
|
+
source,
|
|
262
|
+
isNamespace: false,
|
|
263
|
+
originalName: name
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
case "ImportDefaultSpecifier":
|
|
268
|
+
importBindings.set(spec.local.name, {
|
|
269
|
+
source,
|
|
270
|
+
isNamespace: false,
|
|
271
|
+
originalName: "default"
|
|
272
|
+
});
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "ExportAllDeclaration": {
|
|
278
|
+
const rawSource = typeof node.source?.value === "string" ? node.source.value : null;
|
|
279
|
+
if (!rawSource) break;
|
|
280
|
+
if (node.exported) {
|
|
281
|
+
const name = astName(node.exported);
|
|
282
|
+
if (name !== null) exportMap.set(name, {
|
|
283
|
+
source: normalizeSource(rawSource),
|
|
284
|
+
isNamespace: true
|
|
285
|
+
});
|
|
286
|
+
} else if (rawSource.startsWith(".")) {
|
|
287
|
+
const subPath = path.resolve(fileDir, rawSource).split(path.sep).join("/");
|
|
288
|
+
const candidates = [
|
|
289
|
+
subPath,
|
|
290
|
+
`${subPath}.js`,
|
|
291
|
+
`${subPath}.mjs`,
|
|
292
|
+
`${subPath}.cjs`,
|
|
293
|
+
`${subPath}.ts`,
|
|
294
|
+
`${subPath}.tsx`,
|
|
295
|
+
`${subPath}.jsx`,
|
|
296
|
+
`${subPath}.mts`,
|
|
297
|
+
`${subPath}.cts`,
|
|
298
|
+
`${subPath}/index.js`,
|
|
299
|
+
`${subPath}/index.mjs`,
|
|
300
|
+
`${subPath}/index.cjs`,
|
|
301
|
+
`${subPath}/index.ts`,
|
|
302
|
+
`${subPath}/index.tsx`,
|
|
303
|
+
`${subPath}/index.jsx`,
|
|
304
|
+
`${subPath}/index.mts`,
|
|
305
|
+
`${subPath}/index.cts`
|
|
306
|
+
];
|
|
307
|
+
for (const candidate of candidates) {
|
|
308
|
+
const candidateContent = await readFile(candidate);
|
|
309
|
+
if (candidateContent !== null) {
|
|
310
|
+
const subMap = await buildExportMapFromFile(candidate, readFile, cache, visited, candidateContent);
|
|
311
|
+
for (const [name, entry] of subMap) if (!exportMap.has(name)) exportMap.set(name, entry);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
case "ExportNamedDeclaration": {
|
|
319
|
+
const rawSource = typeof node.source?.value === "string" ? node.source.value : null;
|
|
320
|
+
if (rawSource) {
|
|
321
|
+
const source = normalizeSource(rawSource);
|
|
322
|
+
for (const spec of node.specifiers ?? []) if (spec.exported) {
|
|
323
|
+
const exported = astName(spec.exported);
|
|
324
|
+
const local = astName(spec.local);
|
|
325
|
+
if (exported !== null) exportMap.set(exported, {
|
|
326
|
+
source,
|
|
327
|
+
isNamespace: false,
|
|
328
|
+
originalName: local ?? void 0
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
} else if (node.specifiers && node.specifiers.length > 0) for (const spec of node.specifiers) {
|
|
332
|
+
if (!spec.exported) continue;
|
|
333
|
+
const exported = astName(spec.exported);
|
|
334
|
+
const local = astName(spec.local);
|
|
335
|
+
if (exported === null || local === null) continue;
|
|
336
|
+
const binding = importBindings.get(local);
|
|
337
|
+
if (binding) exportMap.set(exported, {
|
|
338
|
+
source: binding.source,
|
|
339
|
+
isNamespace: binding.isNamespace,
|
|
340
|
+
originalName: binding.isNamespace ? void 0 : binding.originalName
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
else if (node.declaration) {
|
|
344
|
+
const decl = node.declaration;
|
|
345
|
+
if (decl.id?.name) exportMap.set(decl.id.name, {
|
|
346
|
+
source: filePath,
|
|
347
|
+
isNamespace: false
|
|
348
|
+
});
|
|
349
|
+
else if (decl.declarations) {
|
|
350
|
+
for (const d of decl.declarations) if (d.id?.name) exportMap.set(d.id.name, {
|
|
351
|
+
source: filePath,
|
|
352
|
+
isNamespace: false
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
cache.set(filePath, exportMap);
|
|
360
|
+
return exportMap;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Build a map of exported names → source sub-module for a barrel package.
|
|
364
|
+
*
|
|
365
|
+
* Parses the barrel entry file AST and extracts the export map.
|
|
366
|
+
* Handles: `export * as X from`, `export { A } from`, `import * as X; export { X }`,
|
|
367
|
+
* and `export * from "./sub"` (recursively resolves wildcard re-exports).
|
|
368
|
+
*
|
|
369
|
+
* Returns null if the entry cannot be resolved, the file cannot be read, or
|
|
370
|
+
* the file has a parse error. Returns an empty map if the file is valid but
|
|
371
|
+
* exports nothing.
|
|
372
|
+
*/
|
|
373
|
+
async function buildBarrelExportMap(packageName, resolveEntry, readFile, cache) {
|
|
374
|
+
const entryPath = resolveEntry(packageName);
|
|
375
|
+
if (!entryPath) return null;
|
|
376
|
+
const exportMapCache = cache ?? /* @__PURE__ */ new Map();
|
|
377
|
+
const cached = exportMapCache.get(entryPath);
|
|
378
|
+
if (cached) return cached;
|
|
379
|
+
const content = await readFile(entryPath);
|
|
380
|
+
if (!content) return null;
|
|
381
|
+
return await buildExportMapFromFile(entryPath, readFile, exportMapCache, /* @__PURE__ */ new Set(), content);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Creates the vinext:optimize-imports Vite plugin.
|
|
385
|
+
*
|
|
386
|
+
* @param nextConfig - Resolved Next.js config (may be undefined before config hook runs).
|
|
387
|
+
* @param getRoot - Returns the current project root (set by the vinext:config hook).
|
|
388
|
+
*/
|
|
389
|
+
function createOptimizeImportsPlugin(getNextConfig, getRoot) {
|
|
390
|
+
const barrelCaches = {
|
|
391
|
+
exportMapCache: /* @__PURE__ */ new Map(),
|
|
392
|
+
subpkgOrigin: /* @__PURE__ */ new Map()
|
|
393
|
+
};
|
|
394
|
+
const entryPathCache = /* @__PURE__ */ new Map();
|
|
395
|
+
let optimizedPackages = /* @__PURE__ */ new Set();
|
|
396
|
+
let quotedPackages = [];
|
|
397
|
+
const registeredBarrels = /* @__PURE__ */ new Set();
|
|
398
|
+
return {
|
|
399
|
+
name: "vinext:optimize-imports",
|
|
400
|
+
buildStart() {
|
|
401
|
+
const nextConfig = getNextConfig();
|
|
402
|
+
optimizedPackages = new Set([...DEFAULT_OPTIMIZE_PACKAGES, ...nextConfig?.optimizePackageImports ?? []]);
|
|
403
|
+
quotedPackages = [...optimizedPackages].flatMap((pkg) => [`"${pkg}"`, `'${pkg}'`]);
|
|
404
|
+
entryPathCache.clear();
|
|
405
|
+
barrelCaches.exportMapCache.clear();
|
|
406
|
+
barrelCaches.subpkgOrigin.clear();
|
|
407
|
+
registeredBarrels.clear();
|
|
408
|
+
},
|
|
409
|
+
async resolveId(source) {
|
|
410
|
+
if (this.environment?.name === "client") return;
|
|
411
|
+
const envName = this.environment?.name ?? "ssr";
|
|
412
|
+
const barrelEntry = barrelCaches.subpkgOrigin.get(envName)?.get(source) ?? barrelCaches.subpkgOrigin.get(envName === "rsc" ? "ssr" : "rsc")?.get(source);
|
|
413
|
+
if (!barrelEntry) return;
|
|
414
|
+
return await this.resolve(source, barrelEntry, { skipSelf: true }) ?? void 0;
|
|
415
|
+
},
|
|
416
|
+
transform: {
|
|
417
|
+
filter: { id: { include: /\.(tsx?|jsx?|mjs)$/ } },
|
|
418
|
+
async handler(code, id) {
|
|
419
|
+
const env = this.environment;
|
|
420
|
+
if (env?.name === "client") return null;
|
|
421
|
+
const preferReactServer = env?.name === "rsc";
|
|
422
|
+
if (id.startsWith("\0")) return null;
|
|
423
|
+
const packages = optimizedPackages;
|
|
424
|
+
let hasBarrelImport = false;
|
|
425
|
+
for (const quoted of quotedPackages) if (code.includes(quoted)) {
|
|
426
|
+
hasBarrelImport = true;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
if (!hasBarrelImport) return null;
|
|
430
|
+
let ast;
|
|
431
|
+
try {
|
|
432
|
+
ast = parseAst(code);
|
|
433
|
+
} catch {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
const s = new MagicString(code);
|
|
437
|
+
let hasChanges = false;
|
|
438
|
+
const root = getRoot();
|
|
439
|
+
for (const node of ast.body) {
|
|
440
|
+
if (node.type !== "ImportDeclaration") continue;
|
|
441
|
+
const importSource = typeof node.source?.value === "string" ? node.source.value : null;
|
|
442
|
+
if (!importSource || !packages.has(importSource)) continue;
|
|
443
|
+
const cacheKey = `${preferReactServer ? "rsc" : "ssr"}:${importSource}`;
|
|
444
|
+
let barrelEntry = entryPathCache.get(cacheKey);
|
|
445
|
+
if (barrelEntry === void 0) {
|
|
446
|
+
barrelEntry = await resolvePackageEntry(importSource, root, preferReactServer);
|
|
447
|
+
entryPathCache.set(cacheKey, barrelEntry ?? null);
|
|
448
|
+
}
|
|
449
|
+
const exportMap = await buildBarrelExportMap(importSource, () => barrelEntry ?? null, readFileSafe, barrelCaches.exportMapCache);
|
|
450
|
+
if (!exportMap || !barrelEntry) continue;
|
|
451
|
+
const envKey = preferReactServer ? "rsc" : "ssr";
|
|
452
|
+
const registeredKey = `${envKey}:${barrelEntry}`;
|
|
453
|
+
if (!registeredBarrels.has(registeredKey)) {
|
|
454
|
+
registeredBarrels.add(registeredKey);
|
|
455
|
+
let envOriginMap = barrelCaches.subpkgOrigin.get(envKey);
|
|
456
|
+
if (!envOriginMap) {
|
|
457
|
+
envOriginMap = /* @__PURE__ */ new Map();
|
|
458
|
+
barrelCaches.subpkgOrigin.set(envKey, envOriginMap);
|
|
459
|
+
}
|
|
460
|
+
for (const entry of exportMap.values()) if (!entry.source.startsWith("/") && !entry.source.startsWith(".") && !envOriginMap.has(entry.source)) envOriginMap.set(entry.source, barrelEntry);
|
|
461
|
+
}
|
|
462
|
+
const specifiers = [];
|
|
463
|
+
let allResolved = true;
|
|
464
|
+
for (const spec of node.specifiers ?? []) {
|
|
465
|
+
switch (spec.type) {
|
|
466
|
+
case "ImportSpecifier": {
|
|
467
|
+
if (!spec.imported) {
|
|
468
|
+
allResolved = false;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
const imported = astName(spec.imported);
|
|
472
|
+
if (imported === null) {
|
|
473
|
+
allResolved = false;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
specifiers.push({
|
|
477
|
+
local: spec.local.name,
|
|
478
|
+
imported
|
|
479
|
+
});
|
|
480
|
+
if (!exportMap.has(imported)) allResolved = false;
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
case "ImportDefaultSpecifier":
|
|
484
|
+
specifiers.push({
|
|
485
|
+
local: spec.local.name,
|
|
486
|
+
imported: "default"
|
|
487
|
+
});
|
|
488
|
+
if (!exportMap.has("default")) allResolved = false;
|
|
489
|
+
break;
|
|
490
|
+
case "ImportNamespaceSpecifier":
|
|
491
|
+
allResolved = false;
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
if (!allResolved) break;
|
|
495
|
+
}
|
|
496
|
+
if (!allResolved || specifiers.length === 0) {
|
|
497
|
+
if (allResolved === false) {
|
|
498
|
+
for (const spec of node.specifiers ?? []) if (spec.type === "ImportSpecifier" && spec.imported) {
|
|
499
|
+
const imported = astName(spec.imported);
|
|
500
|
+
if (imported !== null && !exportMap.has(imported)) {
|
|
501
|
+
console.debug(`[vinext:optimize-imports] skipping "${importSource}": could not resolve specifier "${imported}" in barrel export map`);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
} else if (spec.type === "ImportDefaultSpecifier" && !exportMap.has("default")) {
|
|
505
|
+
console.debug(`[vinext:optimize-imports] skipping "${importSource}": default export not found in barrel export map`);
|
|
506
|
+
break;
|
|
507
|
+
} else if (spec.type === "ImportNamespaceSpecifier") break;
|
|
508
|
+
}
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
512
|
+
for (const { local, imported } of specifiers) {
|
|
513
|
+
const entry = exportMap.get(imported);
|
|
514
|
+
if (!entry) continue;
|
|
515
|
+
const resolvedSource = entry.source;
|
|
516
|
+
const key = `${resolvedSource}::${entry.isNamespace}`;
|
|
517
|
+
let group = bySource.get(key);
|
|
518
|
+
if (!group) {
|
|
519
|
+
group = {
|
|
520
|
+
source: resolvedSource,
|
|
521
|
+
locals: [],
|
|
522
|
+
isNamespace: entry.isNamespace
|
|
523
|
+
};
|
|
524
|
+
bySource.set(key, group);
|
|
525
|
+
}
|
|
526
|
+
group.locals.push({
|
|
527
|
+
local,
|
|
528
|
+
originalName: entry.isNamespace ? void 0 : entry.originalName
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
const replacements = [];
|
|
532
|
+
for (const { source, locals, isNamespace } of bySource.values()) if (isNamespace) for (const { local } of locals) replacements.push(`import * as ${local} from ${JSON.stringify(source)}`);
|
|
533
|
+
else {
|
|
534
|
+
const defaultLocals = [];
|
|
535
|
+
const namedSpecs = [];
|
|
536
|
+
for (const { local, originalName } of locals) if (originalName === "default") defaultLocals.push(local);
|
|
537
|
+
else if (originalName !== void 0 && originalName !== local) namedSpecs.push(`${originalName} as ${local}`);
|
|
538
|
+
else namedSpecs.push(local);
|
|
539
|
+
for (const local of defaultLocals) replacements.push(`import ${local} from ${JSON.stringify(source)}`);
|
|
540
|
+
if (namedSpecs.length > 0) replacements.push(`import { ${namedSpecs.join(", ")} } from ${JSON.stringify(source)}`);
|
|
541
|
+
}
|
|
542
|
+
s.overwrite(node.start, node.end, replacements.join(";\n") + ";");
|
|
543
|
+
hasChanges = true;
|
|
544
|
+
}
|
|
545
|
+
if (!hasChanges) return null;
|
|
546
|
+
return {
|
|
547
|
+
code: s.toString(),
|
|
548
|
+
map: s.generateMap({ hires: "boundary" })
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
555
|
+
export { DEFAULT_OPTIMIZE_PACKAGES, buildBarrelExportMap, createOptimizeImportsPlugin };
|
|
556
|
+
|
|
557
|
+
//# sourceMappingURL=optimize-imports.js.map
|