dinou 6.1.0 → 7.0.0
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/CHANGELOG.md +99 -0
- package/README.md +9461 -1080
- package/cli.js +392 -169
- package/dinou/adapters/bun.js +62 -0
- package/dinou/adapters/cloudflare.js +65 -0
- package/dinou/adapters/deno.js +48 -0
- package/dinou/adapters/netlify.js +85 -0
- package/dinou/adapters/node.mjs +31 -0
- package/dinou/bun/build.mjs +1077 -0
- package/dinou/cloudflare/build.mjs +1121 -0
- package/dinou/cloudflare/shims/child_process.js +14 -0
- package/dinou/cloudflare/shims/empty.js +2 -0
- package/dinou/cloudflare/shims/fs.js +99 -0
- package/dinou/cloudflare/shims/os.js +29 -0
- package/dinou/config.js +1 -0
- package/dinou/config.mjs +2 -0
- package/dinou/constants.js +29 -4
- package/dinou/core/babel-esm-loader.js +66 -37
- package/dinou/core/bailout-proxy.js +61 -0
- package/dinou/core/build-static-pages.js +309 -157
- package/dinou/core/cache-revalidate.js +471 -144
- package/dinou/core/cache-slot.js +387 -0
- package/dinou/core/client-redirect.jsx +12 -7
- package/dinou/core/client-webpack.jsx +457 -82
- package/dinou/core/client.jsx +452 -82
- package/dinou/core/config.js +11 -0
- package/dinou/core/dinou-paths.js +52 -0
- package/dinou/core/edge-ssr.js +122 -0
- package/dinou/core/export-static.mjs +64 -0
- package/dinou/core/generate-route-types.js +252 -0
- package/dinou/core/generate-static-page.js +55 -94
- package/dinou/core/generate-static-rsc.js +47 -15
- package/dinou/core/generate-static.js +212 -10
- package/dinou/core/generating-isg.js +5 -0
- package/dinou/core/get-asset-from-manifest.js +9 -3
- package/dinou/core/get-error-jsx.js +221 -41
- package/dinou/core/get-file-path-and-dynamic-params.js +9 -13
- package/dinou/core/get-jsx.js +172 -95
- package/dinou/core/handler.js +2296 -0
- package/dinou/core/http-adapter.js +115 -0
- package/dinou/core/import-module.js +83 -53
- package/dinou/core/layout-functions.js +181 -0
- package/dinou/core/link.jsx +18 -6
- package/dinou/core/load-dinou-config.js +60 -0
- package/dinou/core/manifest-provider.js +261 -0
- package/dinou/core/navigation-utils.js +4 -0
- package/dinou/core/navigation.js +50 -39
- package/dinou/core/parse-exports.js +207 -2
- package/dinou/core/ppr-context.js +105 -0
- package/dinou/core/ppr-runtime.js +263 -0
- package/dinou/core/redirect.jsx +20 -19
- package/dinou/core/render-jsx-to-client-jsx.js +133 -15
- package/dinou/core/request-context.js +47 -13
- package/dinou/core/resolve-module-ppr.js +32 -0
- package/dinou/core/revalidating.js +28 -9
- package/dinou/core/route-generator.js +107 -0
- package/dinou/core/rsc-renderer.js +90 -0
- package/dinou/core/run-ssg.js +19 -0
- package/dinou/core/scan-dependency-components.js +218 -0
- package/dinou/core/server-function-proxy-webpack.js +3 -0
- package/dinou/core/server-function-proxy.js +3 -0
- package/dinou/core/slot.js +302 -0
- package/dinou/core/status-manifest.js +41 -1
- package/dinou/core/storage-adapter.js +522 -0
- package/dinou/core/url-resolver.js +16 -16
- package/dinou/core/vfs.js +139 -68
- package/dinou/deno/build.mjs +1112 -0
- package/dinou/deno/seed-kv.js +115 -0
- package/dinou/esbuild/build.mjs +51 -1
- package/dinou/esbuild/dev.mjs +290 -224
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -2
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild.mjs +104 -16
- package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +31 -19
- package/dinou/esbuild/helpers-esbuild/strip-namespace.mjs +9 -0
- package/dinou/esbuild/helpers-esbuild/swc-disk-cache.mjs +225 -0
- package/dinou/esbuild/helpers-esbuild/update-manifest-for-module.mjs +7 -0
- package/dinou/esbuild/helpers-esbuild/write.mjs +111 -2
- package/dinou/esbuild/plugins-esbuild/assets-plugin.mjs +236 -161
- package/dinou/esbuild/plugins-esbuild/css-processor-plugin.mjs +101 -5
- package/dinou/esbuild/plugins-esbuild/react-client-manifest-plugin.mjs +63 -24
- package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +177 -56
- package/dinou/esbuild/plugins-esbuild/skip-missing-entry-points-plugin.mjs +3 -0
- package/dinou/esbuild/plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs +182 -137
- package/dinou/esbuild/plugins-postcss/postcss-extract-plugin.js +38 -11
- package/dinou/esbuild/react-refresh/esm-hmr/client.mjs +55 -8
- package/dinou/esbuild/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +238 -101
- package/dinou/esbuild/react-refresh/is-react-refresh-boundary.mjs +18 -4
- package/dinou/index.d.ts +315 -11
- package/dinou/index.js +12 -6
- package/dinou/index.mjs +9 -0
- package/dinou/node/build.mjs +195 -0
- package/dinou/node/bundle-dual-engine.mjs +1012 -0
- package/dinou/node/dev.mjs +2589 -0
- package/dinou/node/port-selector.mjs +164 -0
- package/dinou/node/terminal-status.mjs +345 -0
- package/dinou/package.json +17 -14
- package/dinou/rollup/react-refresh/esm-hmr/client.js +66 -10
- package/dinou/rollup/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/rollup/react-refresh/is-react-refresh-boundary.js +24 -5
- package/dinou/rollup/react-refresh/react-refresh-runtime.js +13 -6
- package/dinou/rollup/react-refresh/react-refresh-wrap-modules.js +56 -11
- package/dinou/rollup/react-refresh/rollup-plugin-esm-hmr.js +176 -48
- package/dinou/rollup/rollup-plugins/dinou-asset-plugin.js +9 -5
- package/dinou/rollup/rollup-plugins/rollup-plugin-memory.js +66 -0
- package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +468 -123
- package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +44 -12
- package/dinou/rollup/rollup-plugins/rollup-plugin-swc.js +100 -0
- package/dinou/rollup/rollup.config.js +155 -43
- package/dinou/server.d.ts +114 -12
- package/dinou/server.js +35 -10
- package/dinou/server.mjs +30 -9
- package/dinou/webpack/helpers/get-webpack-entries.js +14 -19
- package/dinou/webpack/loaders/server-functions-loader.js +22 -1
- package/dinou/webpack/loaders/swc-loader.js +76 -0
- package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -0
- package/dinou/webpack/plugins/server-functions-plugin.js +75 -19
- package/dinou/webpack/plugins/webpack-memory-plugin.js +66 -0
- package/dinou/webpack/webpack.config.js +227 -29
- package/eject.js +173 -94
- package/package.json +110 -107
- package/dinou/core/asset-require-hook.js +0 -58
- package/dinou/core/client-error-webpack.jsx +0 -432
- package/dinou/core/client-error.jsx +0 -432
- package/dinou/core/concurrency-manager.js +0 -48
- package/dinou/core/context-proxy.js +0 -68
- package/dinou/core/generate-static-pages.js +0 -130
- package/dinou/core/generate-static-rscs.js +0 -151
- package/dinou/core/get-ssg-metadata.js +0 -31
- package/dinou/core/register-loader.mjs +0 -11
- package/dinou/core/register-paths.js +0 -27
- package/dinou/core/render-app-to-html.js +0 -430
- package/dinou/core/render-html.js +0 -407
- package/dinou/core/server.js +0 -1542
- package/dinou/esbuild/plugins-esbuild/write-metafile-plugin.mjs +0 -16
|
@@ -4,10 +4,23 @@ import createScopedName from "../../core/createScopedName.js";
|
|
|
4
4
|
import { regex } from "../../core/asset-extensions.js";
|
|
5
5
|
import { getAbsPathWithExt } from "../../core/get-abs-path-with-ext.js";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
|
+
import stripNamespace from "../helpers-esbuild/strip-namespace.mjs";
|
|
7
8
|
|
|
8
9
|
const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
const normKey = (p) => {
|
|
12
|
+
if (!p) return "";
|
|
13
|
+
let s = path.resolve(p).replace(/\\/g, "/");
|
|
14
|
+
if (process.platform === "win32") {
|
|
15
|
+
s = s.replace(/^([a-zA-Z]):/, (_, d) => d.toLowerCase() + ":");
|
|
16
|
+
}
|
|
17
|
+
return s;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function assetsPlugin({ include = regex, changedIds } = {}) {
|
|
21
|
+
let isInitial = true;
|
|
22
|
+
let cachedRenames = new Map();
|
|
23
|
+
|
|
11
24
|
return {
|
|
12
25
|
name: "assets-plugin",
|
|
13
26
|
setup(build) {
|
|
@@ -19,16 +32,30 @@ export default function assetsPlugin({ include = regex } = {}) {
|
|
|
19
32
|
build.initialOptions.assetNames = "assets/[name]-[hash]";
|
|
20
33
|
|
|
21
34
|
// Handle asset loading with different namespaces
|
|
22
|
-
build.onResolve({ filter: include }, (args) => {
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
build.onResolve({ filter: include }, async (args) => {
|
|
36
|
+
if (args.pluginData?.inAssetPlugin) return null;
|
|
37
|
+
let resolvedAlias =
|
|
25
38
|
args.kind === "entry-point"
|
|
26
39
|
? args.path
|
|
27
40
|
: getAbsPathWithExt(args.path, {
|
|
28
41
|
parentURL: pathToFileURL(args.importer).href,
|
|
29
42
|
});
|
|
30
43
|
|
|
31
|
-
|
|
44
|
+
if (!resolvedAlias) {
|
|
45
|
+
try {
|
|
46
|
+
const res = await build.resolve(args.path, {
|
|
47
|
+
resolveDir: args.resolveDir,
|
|
48
|
+
kind: args.kind,
|
|
49
|
+
pluginData: { inAssetPlugin: true },
|
|
50
|
+
});
|
|
51
|
+
if (res && res.path) {
|
|
52
|
+
resolvedAlias = res.path;
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!resolvedAlias) return null;
|
|
58
|
+
|
|
32
59
|
if (args.kind === "entry-point") {
|
|
33
60
|
return {
|
|
34
61
|
path: resolvedAlias,
|
|
@@ -42,199 +69,247 @@ export default function assetsPlugin({ include = regex } = {}) {
|
|
|
42
69
|
};
|
|
43
70
|
});
|
|
44
71
|
|
|
72
|
+
// Cache asset file buffers in memory
|
|
73
|
+
const assetCache = new Map();
|
|
74
|
+
const emittedAssets = new Map();
|
|
75
|
+
|
|
45
76
|
// Loader for normal assets
|
|
46
77
|
build.onLoad({ filter: /.*/, namespace: "dinou-asset" }, async (args) => {
|
|
47
|
-
|
|
48
|
-
|
|
78
|
+
let contents = assetCache.get(args.path);
|
|
79
|
+
if (!contents) {
|
|
80
|
+
contents = await fs.readFile(args.path);
|
|
81
|
+
assetCache.set(args.path, contents);
|
|
82
|
+
}
|
|
83
|
+
const ext = path.extname(args.path);
|
|
84
|
+
const base = path.basename(args.path, ext);
|
|
85
|
+
const scoped = createScopedName(base, args.path);
|
|
86
|
+
const assetUrl = `/assets/${scoped}${ext}`;
|
|
87
|
+
const newLocal = `assets/${scoped}${ext}`;
|
|
88
|
+
|
|
89
|
+
emittedAssets.set(newLocal, {
|
|
90
|
+
path: path.join(outdir, newLocal),
|
|
91
|
+
contents,
|
|
92
|
+
get text() {
|
|
93
|
+
return new TextDecoder().decode(this.contents);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
contents: `export default "${assetUrl}";`,
|
|
99
|
+
loader: "js",
|
|
100
|
+
watchFiles: [args.path],
|
|
101
|
+
};
|
|
49
102
|
});
|
|
50
103
|
|
|
51
104
|
// Loader for asset entry points
|
|
52
105
|
build.onLoad(
|
|
53
106
|
{ filter: /.*/, namespace: "dinou-asset-entry" },
|
|
54
107
|
async (args) => {
|
|
55
|
-
|
|
56
|
-
|
|
108
|
+
let contents = assetCache.get(args.path);
|
|
109
|
+
if (!contents) {
|
|
110
|
+
contents = await fs.readFile(args.path);
|
|
111
|
+
assetCache.set(args.path, contents);
|
|
112
|
+
}
|
|
113
|
+
return { contents, loader: "file", watchFiles: [args.path] };
|
|
57
114
|
}
|
|
58
115
|
);
|
|
59
116
|
|
|
60
117
|
build.onEnd(async (result) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const sourceFile = inputPath
|
|
76
|
-
.replace(/^dinou-asset:/, "")
|
|
77
|
-
.replace(/^dinou-asset-entry:/, "");
|
|
78
|
-
if (!include.test(sourceFile)) continue;
|
|
79
|
-
|
|
80
|
-
const ext = path.extname(sourceFile);
|
|
81
|
-
if (!oldRelPath.endsWith(ext)) continue;
|
|
82
|
-
const base = path.basename(sourceFile, ext);
|
|
83
|
-
const scoped = createScopedName(base, sourceFile);
|
|
84
|
-
const newLocal = `assets/${scoped}${ext}`;
|
|
85
|
-
const oldLocal = normalizeRel(path.relative(outdir, oldRelPath));
|
|
86
|
-
|
|
87
|
-
renames.set(oldLocal, newLocal);
|
|
88
|
-
processedSourceFiles.add(sourceFile);
|
|
89
|
-
}
|
|
118
|
+
const tAssets0 = Date.now();
|
|
119
|
+
try {
|
|
120
|
+
if (!result.metafile || !result.outputFiles?.length) return;
|
|
121
|
+
|
|
122
|
+
const normalizeRel = (p) => p.replace(/\\/g, "/");
|
|
123
|
+
const isIncremental = !isInitial && changedIds && changedIds.size > 0;
|
|
124
|
+
const hasAssetChanged = isIncremental && Array.from(changedIds).some((id) => include.test(id));
|
|
125
|
+
|
|
126
|
+
// Ensure JS-loaded assets are included in output files
|
|
127
|
+
for (const asset of emittedAssets.values()) {
|
|
128
|
+
if (!result.outputFiles.some((f) => f.path === asset.path)) {
|
|
129
|
+
result.outputFiles.push(asset);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
90
132
|
|
|
91
|
-
|
|
92
|
-
for (const [outputPath, info] of Object.entries(
|
|
93
|
-
result.metafile.outputs
|
|
94
|
-
)) {
|
|
95
|
-
// Only search in JavaScript chunks
|
|
96
|
-
if (!outputPath.endsWith(".js") || info.entryPoint) continue;
|
|
133
|
+
let renames = cachedRenames;
|
|
97
134
|
|
|
98
|
-
|
|
99
|
-
|
|
135
|
+
// Only re-scan all asset outputs on initial build or if an asset file actually changed
|
|
136
|
+
if (isInitial || hasAssetChanged) {
|
|
137
|
+
renames = new Map();
|
|
138
|
+
const processedSourceFiles = new Set();
|
|
100
139
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
140
|
+
// First pass: normal assets (individual files)
|
|
141
|
+
for (const [oldRelPath, info] of Object.entries(
|
|
142
|
+
result.metafile.outputs
|
|
143
|
+
)) {
|
|
144
|
+
if (info.entryPoint || Object.keys(info.inputs).length !== 1)
|
|
145
|
+
continue;
|
|
146
|
+
|
|
147
|
+
const inputPath = Object.keys(info.inputs)[0];
|
|
148
|
+
const sourceFile = inputPath
|
|
149
|
+
.replace(/^dinou-asset:/, "")
|
|
150
|
+
.replace(/^dinou-asset-entry:/, "");
|
|
151
|
+
if (!include.test(sourceFile)) continue;
|
|
107
152
|
|
|
108
|
-
try {
|
|
109
153
|
const ext = path.extname(sourceFile);
|
|
154
|
+
if (!oldRelPath.endsWith(ext)) continue;
|
|
110
155
|
const base = path.basename(sourceFile, ext);
|
|
111
156
|
const scoped = createScopedName(base, sourceFile);
|
|
112
157
|
const newLocal = `assets/${scoped}${ext}`;
|
|
158
|
+
const oldLocal = normalizeRel(path.relative(outdir, oldRelPath));
|
|
113
159
|
|
|
114
|
-
|
|
115
|
-
const assetContent = await fs.readFile(sourceFile);
|
|
116
|
-
|
|
117
|
-
// Create a new output file for this asset
|
|
118
|
-
const newOutputFile = {
|
|
119
|
-
path: path.join(outdir, newLocal),
|
|
120
|
-
contents: assetContent,
|
|
121
|
-
get text() {
|
|
122
|
-
return new TextDecoder().decode(this.contents);
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
result.outputFiles.push(newOutputFile);
|
|
160
|
+
renames.set(oldLocal, newLocal);
|
|
127
161
|
processedSourceFiles.add(sourceFile);
|
|
162
|
+
}
|
|
128
163
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const chunkFile = result.outputFiles.find(
|
|
135
|
-
(f) =>
|
|
136
|
-
normalizeRel(path.relative(process.cwd(), f.path)) ===
|
|
137
|
-
outputPath
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
if (chunkFile) {
|
|
141
|
-
let chunkContent = new TextDecoder().decode(chunkFile.contents);
|
|
142
|
-
|
|
143
|
-
// MORE PRECISE APPROACH: Find the specific comment for this asset
|
|
144
|
-
const assetComment = `// ${inputPath}`;
|
|
145
|
-
const commentIndex = chunkContent.indexOf(assetComment);
|
|
146
|
-
|
|
147
|
-
if (commentIndex !== -1) {
|
|
148
|
-
// Find the next line containing the variable assignment
|
|
149
|
-
const nextLineStart =
|
|
150
|
-
chunkContent.indexOf("\n", commentIndex) + 1;
|
|
151
|
-
const nextLineEnd = chunkContent.indexOf("\n", nextLineStart);
|
|
152
|
-
const assignmentLine = chunkContent.substring(
|
|
153
|
-
nextLineStart,
|
|
154
|
-
nextLineEnd
|
|
155
|
-
);
|
|
164
|
+
// Pre-index outputFiles for O(1) lookup
|
|
165
|
+
const outputFilesByRelPath = new Map();
|
|
166
|
+
for (const f of result.outputFiles) {
|
|
167
|
+
outputFilesByRelPath.set(normalizeRel(path.relative(process.cwd(), f.path)), f);
|
|
168
|
+
}
|
|
156
169
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
170
|
+
// Second pass: find assets that are inside chunks
|
|
171
|
+
for (const [outputPath, info] of Object.entries(
|
|
172
|
+
result.metafile.outputs
|
|
173
|
+
)) {
|
|
174
|
+
if (!outputPath.endsWith(".js") || info.entryPoint) continue;
|
|
175
|
+
|
|
176
|
+
for (const inputPath of Object.keys(info.inputs)) {
|
|
177
|
+
if (!inputPath.startsWith("dinou-asset:")) continue;
|
|
178
|
+
|
|
179
|
+
const sourceFile = inputPath.replace(/^dinou-asset:/, "");
|
|
180
|
+
if (
|
|
181
|
+
!include.test(sourceFile) ||
|
|
182
|
+
processedSourceFiles.has(sourceFile)
|
|
183
|
+
)
|
|
184
|
+
continue;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const ext = path.extname(sourceFile);
|
|
188
|
+
const base = path.basename(sourceFile, ext);
|
|
189
|
+
const scoped = createScopedName(base, sourceFile);
|
|
190
|
+
const newLocal = `assets/${scoped}${ext}`;
|
|
191
|
+
|
|
192
|
+
const assetContent = await fs.readFile(sourceFile);
|
|
193
|
+
|
|
194
|
+
const newOutputFile = {
|
|
195
|
+
path: path.join(outdir, newLocal),
|
|
196
|
+
contents: assetContent,
|
|
197
|
+
get text() {
|
|
198
|
+
return new TextDecoder().decode(this.contents);
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
result.outputFiles.push(newOutputFile);
|
|
203
|
+
processedSourceFiles.add(sourceFile);
|
|
204
|
+
|
|
205
|
+
const chunkFile = outputFilesByRelPath.get(outputPath);
|
|
206
|
+
|
|
207
|
+
if (chunkFile) {
|
|
208
|
+
let chunkContent = new TextDecoder().decode(chunkFile.contents);
|
|
209
|
+
const assetComment = `// ${inputPath}`;
|
|
210
|
+
const commentIndex = chunkContent.indexOf(assetComment);
|
|
211
|
+
|
|
212
|
+
if (commentIndex !== -1) {
|
|
213
|
+
const nextLineStart = chunkContent.indexOf("\n", commentIndex) + 1;
|
|
214
|
+
const nextLineEnd = chunkContent.indexOf("\n", nextLineStart);
|
|
215
|
+
const assignmentLine = chunkContent.substring(nextLineStart, nextLineEnd);
|
|
216
|
+
const varMatch = assignmentLine.match(/var (\w+)_default = "([^"]+)"/);
|
|
217
|
+
|
|
218
|
+
if (varMatch) {
|
|
219
|
+
const varName = varMatch[1];
|
|
220
|
+
const newAssignmentLine = `var ${varName}_default = "/${newLocal}";`;
|
|
221
|
+
chunkContent =
|
|
222
|
+
chunkContent.substring(0, nextLineStart) +
|
|
223
|
+
newAssignmentLine +
|
|
224
|
+
chunkContent.substring(nextLineEnd);
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
const assetName = path.basename(sourceFile);
|
|
228
|
+
const escapedAssetName = escapeRegExp(assetName);
|
|
229
|
+
const pattern = new RegExp(
|
|
230
|
+
`(var \\w+_default = ")([^"]*${escapedAssetName}[^"]*)(";)`,
|
|
231
|
+
"g"
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
if (pattern.test(chunkContent)) {
|
|
235
|
+
chunkContent = chunkContent.replace(
|
|
236
|
+
pattern,
|
|
237
|
+
`$1/${newLocal}$3`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
chunkFile.contents = new TextEncoder().encode(chunkContent);
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error(
|
|
246
|
+
`Error extracting asset ${sourceFile} from chunk:`,
|
|
247
|
+
error
|
|
160
248
|
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
161
252
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const oldPath = varMatch[2];
|
|
253
|
+
cachedRenames = renames;
|
|
254
|
+
}
|
|
165
255
|
|
|
166
|
-
|
|
167
|
-
const newAssignmentLine = `var ${varName}_default = "/${newLocal}";`;
|
|
168
|
-
chunkContent =
|
|
169
|
-
chunkContent.substring(0, nextLineStart) +
|
|
170
|
-
newAssignmentLine +
|
|
171
|
-
chunkContent.substring(nextLineEnd);
|
|
256
|
+
if (renames.size === 0) return;
|
|
172
257
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// Fallback: search by filename in the path
|
|
179
|
-
const assetName = path.basename(sourceFile);
|
|
180
|
-
const escapedAssetName = escapeRegExp(assetName);
|
|
181
|
-
const pattern = new RegExp(
|
|
182
|
-
`(var \\w+_default = ")([^"]*${escapedAssetName}[^"]*)(";)`,
|
|
183
|
-
"g"
|
|
184
|
-
);
|
|
258
|
+
// Update references in JS/CSS files:
|
|
259
|
+
// On incremental rebuild, only process output files belonging to changed modules!
|
|
260
|
+
for (const file of result.outputFiles) {
|
|
261
|
+
const relPath = normalizeRel(path.relative(process.cwd(), file.path));
|
|
262
|
+
if (!relPath.endsWith(".js") && !relPath.endsWith(".css")) continue;
|
|
185
263
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
264
|
+
if (isIncremental && !hasAssetChanged) {
|
|
265
|
+
const outputInfo = result.metafile.outputs[relPath];
|
|
266
|
+
const inputFiles = Object.keys(outputInfo?.inputs || {});
|
|
267
|
+
const touchesChanged = inputFiles.some((m) => {
|
|
268
|
+
const clean = stripNamespace(m);
|
|
269
|
+
return changedIds.has(normKey(clean));
|
|
270
|
+
});
|
|
271
|
+
if (!touchesChanged) continue;
|
|
272
|
+
}
|
|
193
273
|
|
|
194
|
-
|
|
274
|
+
let content = new TextDecoder().decode(file.contents);
|
|
275
|
+
let modified = false;
|
|
276
|
+
for (const [oldLocal, newLocal] of renames) {
|
|
277
|
+
if (!content.includes(oldLocal)) continue;
|
|
278
|
+
const patterns = [
|
|
279
|
+
[`"./${escapeRegExp(oldLocal)}"`, `"/${newLocal}"`],
|
|
280
|
+
[`"${escapeRegExp(oldLocal)}"`, `"${newLocal}"`],
|
|
281
|
+
[`'./${escapeRegExp(oldLocal)}'`, `'/${newLocal}'`],
|
|
282
|
+
[`'${escapeRegExp(oldLocal)}'`, `'${newLocal}'`],
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
for (const [oldPattern, newPattern] of patterns) {
|
|
286
|
+
const reg = new RegExp(oldPattern, "g");
|
|
287
|
+
if (reg.test(content)) {
|
|
288
|
+
content = content.replace(reg, newPattern);
|
|
289
|
+
modified = true;
|
|
290
|
+
}
|
|
195
291
|
}
|
|
196
|
-
} catch (error) {
|
|
197
|
-
console.error(
|
|
198
|
-
`Error extracting asset ${sourceFile} from chunk:`,
|
|
199
|
-
error
|
|
200
|
-
);
|
|
201
292
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// Update references in JS/CSS files (existing code)
|
|
206
|
-
for (const file of result.outputFiles) {
|
|
207
|
-
const relPath = normalizeRel(path.relative(process.cwd(), file.path));
|
|
208
|
-
if (!relPath.endsWith(".js") && !relPath.endsWith(".css")) continue;
|
|
209
|
-
|
|
210
|
-
let content = new TextDecoder().decode(file.contents);
|
|
211
|
-
for (const [oldLocal, newLocal] of renames) {
|
|
212
|
-
const patterns = [
|
|
213
|
-
[`"./${escapeRegExp(oldLocal)}"`, `"/${newLocal}"`],
|
|
214
|
-
[`"${escapeRegExp(oldLocal)}"`, `"${newLocal}"`],
|
|
215
|
-
[`'./${escapeRegExp(oldLocal)}'`, `'/${newLocal}'`],
|
|
216
|
-
[`'${escapeRegExp(oldLocal)}'`, `'${newLocal}'`],
|
|
217
|
-
];
|
|
218
|
-
|
|
219
|
-
for (const [oldPattern, newPattern] of patterns) {
|
|
220
|
-
content = content.replace(
|
|
221
|
-
new RegExp(oldPattern, "g"),
|
|
222
|
-
newPattern
|
|
223
|
-
);
|
|
293
|
+
if (modified) {
|
|
294
|
+
file.contents = new TextEncoder().encode(content);
|
|
224
295
|
}
|
|
225
296
|
}
|
|
226
|
-
file.contents = new TextEncoder().encode(content);
|
|
227
|
-
}
|
|
228
297
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
298
|
+
// Update paths in outputFiles
|
|
299
|
+
if (!isIncremental) {
|
|
300
|
+
for (const file of result.outputFiles) {
|
|
301
|
+
const relPath = normalizeRel(path.relative(process.cwd(), file.path));
|
|
302
|
+
const oldLocal = normalizeRel(path.relative(outdir, relPath));
|
|
303
|
+
const newLocal = renames.get(oldLocal);
|
|
234
304
|
|
|
235
|
-
|
|
236
|
-
|
|
305
|
+
if (newLocal) {
|
|
306
|
+
file.path = path.join(outdir, newLocal);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
237
309
|
}
|
|
310
|
+
} finally {
|
|
311
|
+
isInitial = false;
|
|
312
|
+
globalThis.__DINOU_ASSETS_TIME__ = Date.now() - tAssets0;
|
|
238
313
|
}
|
|
239
314
|
});
|
|
240
315
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
+
import fsSync from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import tailwindcss from "@tailwindcss/postcss";
|
|
4
5
|
import autoprefixer from "autoprefixer";
|
|
@@ -11,19 +12,94 @@ import { pathToFileURL } from "node:url";
|
|
|
11
12
|
import resolve from "resolve";
|
|
12
13
|
import createPostCSSExtractPlugin from "../plugins-postcss/postcss-extract-plugin.js";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
const cssCache = new Map();
|
|
16
|
+
let cssCacheLoaded = false;
|
|
17
|
+
let cssCacheDirty = false;
|
|
18
|
+
|
|
19
|
+
function getCssCachePath() {
|
|
20
|
+
const dir = path.resolve(process.cwd(), ".dinou/cache");
|
|
21
|
+
return { dir, file: path.join(dir, "css-dev-cache.json") };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function loadCssCache() {
|
|
25
|
+
if (cssCacheLoaded) return;
|
|
26
|
+
cssCacheLoaded = true;
|
|
27
|
+
try {
|
|
28
|
+
const { file } = getCssCachePath();
|
|
29
|
+
if (fsSync.existsSync(file)) {
|
|
30
|
+
const parsed = JSON.parse(fsSync.readFileSync(file, "utf8"));
|
|
31
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
32
|
+
cssCache.set(k, v);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function saveCssCache() {
|
|
39
|
+
if (!cssCacheDirty) return;
|
|
40
|
+
cssCacheDirty = false;
|
|
41
|
+
try {
|
|
42
|
+
const { dir, file } = getCssCachePath();
|
|
43
|
+
if (!fsSync.existsSync(dir)) {
|
|
44
|
+
fsSync.mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
const data = {};
|
|
47
|
+
for (const [k, v] of cssCache.entries()) {
|
|
48
|
+
data[k] = v;
|
|
49
|
+
}
|
|
50
|
+
await fs.writeFile(file, JSON.stringify(data), "utf8");
|
|
51
|
+
} catch (e) {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default function cssProcessorPlugin({ outdir = ".dinou/public", hmrEngine } = {}) {
|
|
55
|
+
const { finalize, plugin: extractor, addExtractedCss } = createPostCSSExtractPlugin({
|
|
16
56
|
outputFile: `${outdir}/styles.css`,
|
|
17
57
|
});
|
|
18
58
|
|
|
59
|
+
let isInitial = true;
|
|
60
|
+
let hasCssChange = false;
|
|
61
|
+
let postCssTotalTime = 0;
|
|
62
|
+
let postCssCount = 0;
|
|
63
|
+
|
|
19
64
|
return {
|
|
20
65
|
name: "css-processor",
|
|
21
66
|
setup(build) {
|
|
67
|
+
build.onStart(() => {
|
|
68
|
+
hasCssChange = false;
|
|
69
|
+
postCssTotalTime = 0;
|
|
70
|
+
postCssCount = 0;
|
|
71
|
+
});
|
|
72
|
+
|
|
22
73
|
build.onLoad({ filter: /\.css$/ }, async (args) => {
|
|
23
74
|
const filePath = args.path;
|
|
75
|
+
loadCssCache();
|
|
76
|
+
const stat = await fs.stat(filePath);
|
|
77
|
+
const cached = cssCache.get(filePath);
|
|
78
|
+
|
|
79
|
+
const isModule = filePath.endsWith(".module.css");
|
|
80
|
+
if (isModule && cached && cached.mtime === stat.mtimeMs) {
|
|
81
|
+
if (cached.extractedCss && typeof addExtractedCss === "function") {
|
|
82
|
+
addExtractedCss(cached.extractedCss);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
contents: `export default ${JSON.stringify(cached.map || {})};`,
|
|
86
|
+
loader: "js",
|
|
87
|
+
watchFiles: [filePath],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
hasCssChange = true;
|
|
92
|
+
const tPostCss0 = Date.now();
|
|
24
93
|
const source = await fs.readFile(filePath, "utf8");
|
|
25
94
|
|
|
26
95
|
let map = {};
|
|
96
|
+
let extractedForFile = "";
|
|
97
|
+
const fileCapturePlugin = {
|
|
98
|
+
postcssPlugin: "file-capture",
|
|
99
|
+
OnceExit(root) {
|
|
100
|
+
extractedForFile = root.toString();
|
|
101
|
+
},
|
|
102
|
+
};
|
|
27
103
|
|
|
28
104
|
await postcss([
|
|
29
105
|
postcssImport({
|
|
@@ -32,11 +108,9 @@ export default function cssProcessorPlugin({ outdir = ".dinou/public" } = {}) {
|
|
|
32
108
|
parentURL: pathToFileURL(basedir).href,
|
|
33
109
|
});
|
|
34
110
|
if (resolvedAlias) {
|
|
35
|
-
console.log("ALIAS RESOLVED:", resolvedAlias);
|
|
36
111
|
return resolvedAlias;
|
|
37
112
|
}
|
|
38
113
|
if (id.startsWith("tailwindcss/")) {
|
|
39
|
-
console.log("TAILWIND INTERNAL:", id);
|
|
40
114
|
return resolve.sync(id, { basedir, extensions: [".css"] });
|
|
41
115
|
}
|
|
42
116
|
try {
|
|
@@ -58,24 +132,46 @@ export default function cssProcessorPlugin({ outdir = ".dinou/public" } = {}) {
|
|
|
58
132
|
map = json;
|
|
59
133
|
},
|
|
60
134
|
}),
|
|
135
|
+
fileCapturePlugin,
|
|
61
136
|
extractor,
|
|
62
137
|
]).process(source, { from: filePath });
|
|
63
138
|
|
|
139
|
+
postCssTotalTime += Date.now() - tPostCss0;
|
|
140
|
+
postCssCount++;
|
|
141
|
+
globalThis.__DINOU_POSTCSS_TIME__ = postCssTotalTime;
|
|
142
|
+
globalThis.__DINOU_POSTCSS_COUNT__ = postCssCount;
|
|
143
|
+
|
|
144
|
+
cssCache.set(filePath, {
|
|
145
|
+
mtime: stat.mtimeMs,
|
|
146
|
+
map,
|
|
147
|
+
extractedCss: extractedForFile,
|
|
148
|
+
});
|
|
149
|
+
cssCacheDirty = true;
|
|
150
|
+
|
|
64
151
|
if (filePath.endsWith(".module.css")) {
|
|
65
|
-
// console.log(`[CSS MODULE] ${path.basename(filePath)} →`, map);
|
|
66
152
|
return {
|
|
67
153
|
contents: `export default ${JSON.stringify(map)};`,
|
|
68
154
|
loader: "js",
|
|
155
|
+
watchFiles: [filePath],
|
|
69
156
|
};
|
|
70
157
|
} else {
|
|
71
158
|
return {
|
|
72
159
|
contents: `/* global: ${path.basename(filePath)} */`,
|
|
73
160
|
loader: "js",
|
|
161
|
+
watchFiles: [filePath],
|
|
74
162
|
};
|
|
75
163
|
}
|
|
76
164
|
});
|
|
77
165
|
build.onEnd(() => {
|
|
78
166
|
finalize();
|
|
167
|
+
saveCssCache().catch(() => {});
|
|
168
|
+
globalThis.__DINOU_POSTCSS_TIME__ = postCssTotalTime;
|
|
169
|
+
globalThis.__DINOU_POSTCSS_COUNT__ = postCssCount;
|
|
170
|
+
if (!isInitial && hasCssChange && hmrEngine?.value?.broadcastMessage) {
|
|
171
|
+
hmrEngine.value.broadcastMessage({ type: "style-update", url: "/styles.css" });
|
|
172
|
+
}
|
|
173
|
+
isInitial = false;
|
|
174
|
+
hasCssChange = false;
|
|
79
175
|
});
|
|
80
176
|
},
|
|
81
177
|
};
|