@withl5e/l5e 0.3.0 → 0.3.2
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/dist/entry-server.js +3 -3
- package/dist/{generateMetadata-Cn2uJjg_.js → generateMetadata-DmoLxT6v.js} +2 -2
- package/dist/{generateMetadata-Cn2uJjg_.js.map → generateMetadata-DmoLxT6v.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/island.js +1 -1
- package/dist/{jsx-runtime-Bokflh8Q.js → jsx-runtime-5m2ZiNtU.js} +92 -82
- package/dist/jsx-runtime-5m2ZiNtU.js.map +1 -0
- package/dist/jsx-runtime.js +1 -1
- package/dist/{render-DTXfJu0d.js → render-BaTAJ4Ha.js} +2 -2
- package/dist/{render-DTXfJu0d.js.map → render-BaTAJ4Ha.js.map} +1 -1
- package/dist/seo.js +2 -2
- package/dist/server.js +360 -367
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/core/bundler.ts +201 -205
- package/src/core/jsx-runtime.ts +23 -4
- package/src/core/server.ts +10 -3
- package/dist/jsx-runtime-Bokflh8Q.js.map +0 -1
package/src/core/bundler.ts
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'node:fs/promises';
|
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
|
-
import type { OutputOptions, RollupOptions } from 'rollup';
|
|
7
|
+
import type { OutputOptions, Plugin, RollupOptions } from 'rollup';
|
|
8
8
|
|
|
9
9
|
let rollupModulePromise: Promise<typeof import('rollup')> | null = null;
|
|
10
10
|
|
|
@@ -34,12 +34,40 @@ interface BundledFile {
|
|
|
34
34
|
mimeType: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
interface BundleResult {
|
|
38
|
+
hash: string;
|
|
39
|
+
filename: string;
|
|
40
|
+
content: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const EMPTY_RESULT: BundleResult = { hash: '', filename: '', content: '' };
|
|
44
|
+
|
|
37
45
|
// Memory map để lưu bundled files
|
|
38
46
|
const bundledFilesMap = new Map<string, BundledFile>();
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Single-flight map: cacheKey → promise của lần bundle đang chạy (hoặc đã xong).
|
|
50
|
+
* Vì promise được giữ lại sau khi resolve, map này vừa là in-flight dedup vừa là
|
|
51
|
+
* result cache. Bundle lỗi bị xoá khỏi map để request sau được thử lại.
|
|
52
|
+
*/
|
|
53
|
+
const bundlePromises = new Map<string, Promise<BundleResult>>();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Chạy `work` đúng một lần cho mỗi cacheKey, kể cả khi nhiều request đến đồng thời.
|
|
57
|
+
*/
|
|
58
|
+
function dedupe(cacheKey: string, work: () => Promise<BundleResult>): Promise<BundleResult> {
|
|
59
|
+
const pending = bundlePromises.get(cacheKey);
|
|
60
|
+
if (pending) {
|
|
61
|
+
return pending;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const promise = work().catch((error) => {
|
|
65
|
+
bundlePromises.delete(cacheKey);
|
|
66
|
+
throw error;
|
|
67
|
+
});
|
|
68
|
+
bundlePromises.set(cacheKey, promise);
|
|
69
|
+
return promise;
|
|
70
|
+
}
|
|
43
71
|
|
|
44
72
|
/**
|
|
45
73
|
* Generate hash từ content
|
|
@@ -48,167 +76,184 @@ function generateHash(content: string): string {
|
|
|
48
76
|
return createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
49
77
|
}
|
|
50
78
|
|
|
79
|
+
// Rollup coi id bắt đầu bằng \0 là virtual — nó sẽ không cố đọc từ đĩa.
|
|
80
|
+
const VIRTUAL_ENTRY_ID = '\0l5e:bundle-entry';
|
|
81
|
+
|
|
51
82
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
83
|
+
* Entry của mỗi lần bundle chỉ là một danh sách import. Giữ nó trong memory thay
|
|
84
|
+
* vì ghi ra đĩa: hai request đồng thời cùng một tập script sinh ra cùng nội dung
|
|
85
|
+
* entry, nên file tạm dùng chung path sẽ bị request này xoá trong lúc rollup của
|
|
86
|
+
* request kia còn đang đọc.
|
|
54
87
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
88
|
+
function virtualEntryPlugin(entryContent: string): Plugin {
|
|
89
|
+
return {
|
|
90
|
+
name: 'l5e-virtual-entry',
|
|
91
|
+
resolveId(source) {
|
|
92
|
+
return source === VIRTUAL_ENTRY_ID ? VIRTUAL_ENTRY_ID : null;
|
|
93
|
+
},
|
|
94
|
+
load(id) {
|
|
95
|
+
return id === VIRTUAL_ENTRY_ID ? entryContent : null;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Rewrite vendor/chunk/global imports thành web path và để chúng external.
|
|
102
|
+
* Global files (*.global.*) đã được client.global.ts load — bundle lại sẽ tạo
|
|
103
|
+
* module instance trùng (vd nanostores).
|
|
104
|
+
*/
|
|
105
|
+
function vendorPathRewriterPlugin(distClientDir: string): Plugin {
|
|
106
|
+
const toWebPath = (absolutePath: string) =>
|
|
107
|
+
'/' + path.relative(distClientDir, absolutePath).replace(/\\/g, '/');
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
name: 'vendor-path-rewriter',
|
|
111
|
+
resolveId(source, importer) {
|
|
112
|
+
if (
|
|
113
|
+
!source.includes('vendor-') &&
|
|
114
|
+
!source.includes('chunk-') &&
|
|
115
|
+
!source.includes('.global')
|
|
116
|
+
) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (path.isAbsolute(source)) {
|
|
121
|
+
// e.g. C:\...\dist\client\assets\vendor-react-XXX.js -> /assets/vendor-react-XXX.js
|
|
122
|
+
return { id: toWebPath(source), external: true };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (importer && source.startsWith('.')) {
|
|
126
|
+
// Relative path như ./auth.global-BOVr81Z5.js — resolve từ importer
|
|
127
|
+
return { id: toWebPath(path.resolve(path.dirname(importer), source)), external: true };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function runScriptBundle(
|
|
136
|
+
uniquePaths: string[],
|
|
58
137
|
distClientDir: string,
|
|
59
|
-
): Promise<
|
|
60
|
-
|
|
61
|
-
|
|
138
|
+
): Promise<BundleResult> {
|
|
139
|
+
const entryContent = uniquePaths
|
|
140
|
+
.map((p) => {
|
|
141
|
+
const filePath = p.startsWith('/')
|
|
142
|
+
? path.join(distClientDir, p.substring(1))
|
|
143
|
+
: path.join(distClientDir, p);
|
|
144
|
+
return `import ${JSON.stringify(filePath)};`;
|
|
145
|
+
})
|
|
146
|
+
.join('\n');
|
|
147
|
+
|
|
148
|
+
const rollupOptions: RollupOptions = {
|
|
149
|
+
input: VIRTUAL_ENTRY_ID,
|
|
150
|
+
plugins: [virtualEntryPlugin(entryContent), vendorPathRewriterPlugin(distClientDir)],
|
|
151
|
+
external: (id) => {
|
|
152
|
+
// External node_modules
|
|
153
|
+
if (!id.startsWith('.') && !path.isAbsolute(id) && id !== VIRTUAL_ENTRY_ID) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Vendor/chunk/global do plugin resolveId lo phần rewrite path
|
|
158
|
+
return false;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const outputOptions: OutputOptions = {
|
|
163
|
+
format: 'es',
|
|
164
|
+
inlineDynamicImports: false,
|
|
165
|
+
entryFileNames: 'bundle-[hash].js',
|
|
166
|
+
chunkFileNames: 'bundle-[hash].js',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const { rollup } = await loadRollup();
|
|
170
|
+
const bundle = await rollup(rollupOptions);
|
|
171
|
+
let output;
|
|
172
|
+
try {
|
|
173
|
+
({ output } = await bundle.generate(outputOptions));
|
|
174
|
+
} finally {
|
|
175
|
+
await bundle.close();
|
|
62
176
|
}
|
|
63
177
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Tạo cache key từ sorted unique paths
|
|
68
|
-
const cacheKey = `scripts:${uniquePaths.sort().join(',')}`;
|
|
69
|
-
|
|
70
|
-
// Kiểm tra cache - return entry chunk info if already bundled
|
|
71
|
-
const cachedEntryFileName = bundleCache.get(cacheKey);
|
|
72
|
-
if (cachedEntryFileName) {
|
|
73
|
-
const entryFile = bundledFilesMap.get(cachedEntryFileName);
|
|
74
|
-
if (entryFile) {
|
|
75
|
-
return {
|
|
76
|
-
hash: entryFile.hash,
|
|
77
|
-
filename: entryFile.filename,
|
|
78
|
-
content: entryFile.content,
|
|
79
|
-
};
|
|
178
|
+
for (const chunk of output) {
|
|
179
|
+
if (chunk.type !== 'chunk') {
|
|
180
|
+
continue;
|
|
80
181
|
}
|
|
182
|
+
bundledFilesMap.set(chunk.fileName, {
|
|
183
|
+
content: chunk.code || '',
|
|
184
|
+
hash: generateHash(chunk.code || ''),
|
|
185
|
+
filename: chunk.fileName,
|
|
186
|
+
mimeType: 'application/javascript',
|
|
187
|
+
});
|
|
81
188
|
}
|
|
82
189
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// Sử dụng rollup để bundle nếu cần (resolve imports, etc)
|
|
88
|
-
// Tạo temp entry file
|
|
89
|
-
const hash = generateHash(uniquePaths.join('\n'));
|
|
90
|
-
const tempDir = path.join(rootDir, '.temp-bundle');
|
|
91
|
-
await fs.mkdir(tempDir, { recursive: true }).catch(() => {});
|
|
92
|
-
|
|
93
|
-
entryFile = path.join(tempDir, `entry-${hash}.js`);
|
|
94
|
-
// Tạo entry file import tất cả scripts
|
|
95
|
-
const entryContent = uniquePaths
|
|
96
|
-
.map((p, i) => {
|
|
97
|
-
const filePath = p.startsWith('/')
|
|
98
|
-
? path.join(distClientDir, p.substring(1))
|
|
99
|
-
: path.join(distClientDir, p);
|
|
100
|
-
return `import ${JSON.stringify(filePath)};`;
|
|
101
|
-
})
|
|
102
|
-
.join('\n');
|
|
103
|
-
|
|
104
|
-
await fs.writeFile(entryFile, entryContent, 'utf-8');
|
|
105
|
-
console.log(`[bundler] Wrote entry file to ${entryFile}`);
|
|
106
|
-
console.log(`[bundler] Entry content: ${entryContent}`);
|
|
107
|
-
// Rollup config để bundle
|
|
108
|
-
const rollupOptions: RollupOptions = {
|
|
109
|
-
input: entryFile,
|
|
110
|
-
plugins: [
|
|
111
|
-
{
|
|
112
|
-
name: 'vendor-path-rewriter',
|
|
113
|
-
resolveId(source, importer, _options) {
|
|
114
|
-
// Handle vendor/chunk/global files: convert absolute paths to web paths
|
|
115
|
-
// Global files (*.global.*) are already loaded by client.global.ts —
|
|
116
|
-
// re-bundling them would create duplicate module instances (e.g. nanostores)
|
|
117
|
-
if (
|
|
118
|
-
source.includes('vendor-') ||
|
|
119
|
-
source.includes('chunk-') ||
|
|
120
|
-
source.includes('.global')
|
|
121
|
-
) {
|
|
122
|
-
console.log(`[bundler] Resolving source: ${source}`);
|
|
123
|
-
if (path.isAbsolute(source)) {
|
|
124
|
-
console.log(`[bundler] Resolving absolute path: ${source}`);
|
|
125
|
-
// e.g., C:\...\dist\client\assets\vendor-react-XXX.js -> /assets/vendor-react-XXX.js
|
|
126
|
-
const relativePath = path.relative(distClientDir, source);
|
|
127
|
-
const webPath = '/' + relativePath.replace(/\\/g, '/');
|
|
128
|
-
return { id: webPath, external: true };
|
|
129
|
-
} else if (importer && source.startsWith('.')) {
|
|
130
|
-
console.log(
|
|
131
|
-
`[bundler] Resolving relative path: ${source} from importer: ${importer}`,
|
|
132
|
-
);
|
|
133
|
-
// Relative path like ./auth.global-BOVr81Z5.js — resolve from importer
|
|
134
|
-
const resolved = path.resolve(path.dirname(importer), source);
|
|
135
|
-
const relativePath = path.relative(distClientDir, resolved);
|
|
136
|
-
const webPath = '/' + relativePath.replace(/\\/g, '/');
|
|
137
|
-
return { id: webPath, external: true };
|
|
138
|
-
} else {
|
|
139
|
-
console.log(`[bundler] Resolving source: ${source}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return null; // Let other plugins/external handle
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
external: (id) => {
|
|
147
|
-
// External node_modules
|
|
148
|
-
if (!id.startsWith('.') && !path.isAbsolute(id)) {
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Let plugin handle vendor/chunk/global files (don't mark external here)
|
|
153
|
-
if (id.includes('vendor-') || id.includes('chunk-') || id.includes('.global')) {
|
|
154
|
-
return false; // Let plugin's resolveId handle path rewriting
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return false;
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const outputOptions: OutputOptions = {
|
|
162
|
-
format: 'es',
|
|
163
|
-
inlineDynamicImports: false,
|
|
164
|
-
entryFileNames: 'bundle-[hash].js',
|
|
165
|
-
chunkFileNames: 'bundle-[hash].js',
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const { rollup } = await loadRollup();
|
|
169
|
-
const bundle = await rollup(rollupOptions);
|
|
170
|
-
const { output } = await bundle.generate(outputOptions);
|
|
171
|
-
await bundle.close();
|
|
190
|
+
const entryChunk = output[0];
|
|
191
|
+
if (entryChunk?.type !== 'chunk') {
|
|
192
|
+
throw new Error('[bundler] rollup produced no entry chunk');
|
|
193
|
+
}
|
|
172
194
|
|
|
173
|
-
|
|
195
|
+
return {
|
|
196
|
+
hash: generateHash(entryChunk.code || ''),
|
|
197
|
+
filename: entryChunk.fileName,
|
|
198
|
+
content: entryChunk.code || '',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
174
201
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
bundledFilesMap.set(o.fileName, bundledFile);
|
|
187
|
-
});
|
|
202
|
+
/**
|
|
203
|
+
* Bundle JavaScript files từ dist/client thành 1 file
|
|
204
|
+
* Trong production, các file đã được build sẵn trong dist/client
|
|
205
|
+
*/
|
|
206
|
+
export async function bundleScripts(
|
|
207
|
+
scriptPaths: string[],
|
|
208
|
+
distClientDir: string,
|
|
209
|
+
): Promise<BundleResult> {
|
|
210
|
+
if (scriptPaths.length === 0) {
|
|
211
|
+
return EMPTY_RESULT;
|
|
212
|
+
}
|
|
188
213
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (entryChunk?.type === 'chunk') {
|
|
192
|
-
bundleCache.set(cacheKey, entryChunk.fileName);
|
|
193
|
-
}
|
|
214
|
+
const uniquePaths = [...new Set(scriptPaths)].sort();
|
|
215
|
+
const cacheKey = `scripts:${uniquePaths.join(',')}`;
|
|
194
216
|
|
|
195
|
-
|
|
196
|
-
return
|
|
197
|
-
hash: generateHash(output[0]?.code || ''),
|
|
198
|
-
filename: output[0]?.fileName || '',
|
|
199
|
-
content: output[0]?.code || '',
|
|
200
|
-
};
|
|
217
|
+
try {
|
|
218
|
+
return await dedupe(cacheKey, () => runScriptBundle(uniquePaths, distClientDir));
|
|
201
219
|
} catch (error) {
|
|
202
220
|
console.error('[bundler] Error bundling scripts:', error);
|
|
203
|
-
return
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
return EMPTY_RESULT;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function runCssBundle(
|
|
226
|
+
uniquePaths: string[],
|
|
227
|
+
distClientDir: string,
|
|
228
|
+
): Promise<BundleResult> {
|
|
229
|
+
const cssContents: string[] = [];
|
|
230
|
+
|
|
231
|
+
for (const cssPath of uniquePaths) {
|
|
232
|
+
// cssPath có thể là "/assets/xxx.css" hoặc từ manifest
|
|
233
|
+
const filePath = cssPath.startsWith('/')
|
|
234
|
+
? path.join(distClientDir, cssPath.substring(1))
|
|
235
|
+
: path.join(distClientDir, cssPath);
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
239
|
+
cssContents.push(`/* ${cssPath} */\n${content}\n`);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
console.warn(`[bundler] Failed to read CSS file: ${cssPath}`, err);
|
|
210
242
|
}
|
|
211
243
|
}
|
|
244
|
+
|
|
245
|
+
const bundledContent = cssContents.join('\n\n');
|
|
246
|
+
const hash = generateHash(bundledContent);
|
|
247
|
+
const filename = `bundle-${hash}.css`;
|
|
248
|
+
|
|
249
|
+
bundledFilesMap.set(filename, {
|
|
250
|
+
content: bundledContent,
|
|
251
|
+
hash,
|
|
252
|
+
filename,
|
|
253
|
+
mimeType: 'text/css',
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return { hash, filename, content: bundledContent };
|
|
212
257
|
}
|
|
213
258
|
|
|
214
259
|
/**
|
|
@@ -217,70 +262,20 @@ export async function bundleScripts(
|
|
|
217
262
|
*/
|
|
218
263
|
export async function bundleCss(
|
|
219
264
|
cssPaths: string[],
|
|
220
|
-
rootDir: string,
|
|
221
265
|
distClientDir: string,
|
|
222
|
-
): Promise<
|
|
266
|
+
): Promise<BundleResult> {
|
|
223
267
|
if (cssPaths.length === 0) {
|
|
224
|
-
return
|
|
268
|
+
return EMPTY_RESULT;
|
|
225
269
|
}
|
|
226
270
|
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
// Tạo cache key từ sorted unique paths
|
|
231
|
-
const cacheKey = `css:${uniquePaths.sort().join(',')}`;
|
|
232
|
-
|
|
233
|
-
// Kiểm tra cache - return cached file if already bundled
|
|
234
|
-
const cachedFileName = cssCache.get(cacheKey);
|
|
235
|
-
if (cachedFileName) {
|
|
236
|
-
const cachedFile = bundledFilesMap.get(cachedFileName);
|
|
237
|
-
if (cachedFile) {
|
|
238
|
-
return {
|
|
239
|
-
hash: cachedFile.hash,
|
|
240
|
-
filename: cachedFile.filename,
|
|
241
|
-
content: cachedFile.content,
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}
|
|
271
|
+
const uniquePaths = [...new Set(cssPaths)].sort();
|
|
272
|
+
const cacheKey = `css:${uniquePaths.join(',')}`;
|
|
245
273
|
|
|
246
274
|
try {
|
|
247
|
-
|
|
248
|
-
const cssContents: string[] = [];
|
|
249
|
-
|
|
250
|
-
for (const cssPath of uniquePaths) {
|
|
251
|
-
// cssPath có thể là "/assets/xxx.css" hoặc từ manifest
|
|
252
|
-
const filePath = cssPath.startsWith('/')
|
|
253
|
-
? path.join(distClientDir, cssPath.substring(1))
|
|
254
|
-
: path.join(distClientDir, cssPath);
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
258
|
-
cssContents.push(`/* ${cssPath} */\n${content}\n`);
|
|
259
|
-
} catch (err) {
|
|
260
|
-
console.warn(`[bundler] Failed to read CSS file: ${cssPath}`, err);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const bundledContent = cssContents.join('\n\n');
|
|
265
|
-
const hash = generateHash(bundledContent);
|
|
266
|
-
const filename = `bundle-${hash}.css`;
|
|
267
|
-
|
|
268
|
-
// Lưu vào map với key = filename
|
|
269
|
-
const bundledFile: BundledFile = {
|
|
270
|
-
content: bundledContent,
|
|
271
|
-
hash,
|
|
272
|
-
filename,
|
|
273
|
-
mimeType: 'text/css',
|
|
274
|
-
};
|
|
275
|
-
bundledFilesMap.set(filename, bundledFile);
|
|
276
|
-
|
|
277
|
-
// Cache filename for deduplication
|
|
278
|
-
cssCache.set(cacheKey, filename);
|
|
279
|
-
|
|
280
|
-
return { hash, filename, content: bundledContent };
|
|
275
|
+
return await dedupe(cacheKey, () => runCssBundle(uniquePaths, distClientDir));
|
|
281
276
|
} catch (error) {
|
|
282
277
|
console.error('[bundler] Error bundling CSS:', error);
|
|
283
|
-
return
|
|
278
|
+
return EMPTY_RESULT;
|
|
284
279
|
}
|
|
285
280
|
}
|
|
286
281
|
|
|
@@ -296,4 +291,5 @@ export function getBundledFile(filename: string): BundledFile | undefined {
|
|
|
296
291
|
*/
|
|
297
292
|
export function clearBundledFiles(): void {
|
|
298
293
|
bundledFilesMap.clear();
|
|
294
|
+
bundlePromises.clear();
|
|
299
295
|
}
|
package/src/core/jsx-runtime.ts
CHANGED
|
@@ -107,11 +107,26 @@ function createRequestContext(requestInfo: RequestInfo): RenderContext {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Chuẩn hoá path asset về dạng web path có leading `/`.
|
|
112
|
+
* Dev sinh thẻ thẳng từ path này, còn prod strip leading `/` để tra manifest —
|
|
113
|
+
* không normalize thì `'src/a.css'` và `'/src/a.css'` là hai entry khác nhau ở
|
|
114
|
+
* dev nhưng lại trỏ cùng một manifest key ở prod.
|
|
115
|
+
*/
|
|
116
|
+
function normalizeAssetPath(path: string): string {
|
|
117
|
+
const normalized = path.trim().replace(/\\/g, '/');
|
|
118
|
+
return normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
export function useClientJs(path: string): string {
|
|
111
|
-
if (typeof path === 'string' && path.length > 0) {
|
|
122
|
+
if (typeof path === 'string' && path.trim().length > 0) {
|
|
112
123
|
const renderContext = renderStore.getStore();
|
|
113
124
|
if (renderContext) {
|
|
114
|
-
|
|
125
|
+
const normalized = normalizeAssetPath(path);
|
|
126
|
+
// Dedupe theo path, giữ thứ tự lần gọi đầu tiên
|
|
127
|
+
if (!renderContext.clientJsRegistry.some((entry) => entry.path === normalized)) {
|
|
128
|
+
renderContext.clientJsRegistry.push({ path: normalized, from: 'Unknown' });
|
|
129
|
+
}
|
|
115
130
|
}
|
|
116
131
|
}
|
|
117
132
|
return '';
|
|
@@ -153,10 +168,14 @@ export function getSsrIslands(): SsrIslandEntry[] {
|
|
|
153
168
|
}
|
|
154
169
|
|
|
155
170
|
export function useCss(path: string): string {
|
|
156
|
-
if (typeof path === 'string' && path.length > 0) {
|
|
171
|
+
if (typeof path === 'string' && path.trim().length > 0) {
|
|
157
172
|
const renderContext = renderStore.getStore();
|
|
158
173
|
if (renderContext) {
|
|
159
|
-
|
|
174
|
+
const normalized = normalizeAssetPath(path);
|
|
175
|
+
// Dedupe theo path — thứ tự lần gọi đầu tiên quyết định thứ tự cascade
|
|
176
|
+
if (!renderContext.cssRegistry.some((entry) => entry.path === normalized)) {
|
|
177
|
+
renderContext.cssRegistry.push({ path: normalized, from: 'Unknown' });
|
|
178
|
+
}
|
|
160
179
|
}
|
|
161
180
|
}
|
|
162
181
|
return '';
|
package/src/core/server.ts
CHANGED
|
@@ -285,12 +285,12 @@ async function createPageResponse({
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
if (mappedScripts.length > 0) {
|
|
288
|
-
const bundledScript = await bundleScripts(mappedScripts,
|
|
288
|
+
const bundledScript = await bundleScripts(mappedScripts, distClientDir);
|
|
289
289
|
scriptSrcList = bundledScript.filename ? [`/${bundledScript.filename}`] : mappedScripts;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
if (mappedCssFiles.length > 0) {
|
|
293
|
-
const bundledCss = await bundleCss(mappedCssFiles,
|
|
293
|
+
const bundledCss = await bundleCss(mappedCssFiles, distClientDir);
|
|
294
294
|
if (bundledCss.filename) {
|
|
295
295
|
cssSrcList = [`/${bundledCss.filename}`];
|
|
296
296
|
}
|
|
@@ -330,7 +330,10 @@ async function createPageResponse({
|
|
|
330
330
|
|
|
331
331
|
let cssHtml = '';
|
|
332
332
|
if (!isProduction) {
|
|
333
|
-
|
|
333
|
+
// Registry đã dedupe, nhưng vẫn lọc lại ở đây để dev không bao giờ ra thẻ trùng
|
|
334
|
+
cssHtml = [...new Set(cssSrcList)]
|
|
335
|
+
.map((src) => `<link rel="stylesheet" href="${src}">`)
|
|
336
|
+
.join('');
|
|
334
337
|
}
|
|
335
338
|
|
|
336
339
|
let allScripts = [...globalScripts, ...scriptSrcList];
|
|
@@ -341,6 +344,10 @@ async function createPageResponse({
|
|
|
341
344
|
allScripts = ['/src/client.global.ts', ...allScripts];
|
|
342
345
|
}
|
|
343
346
|
|
|
347
|
+
// useClientJs('/src/client.global.ts') do user tự gọi sẽ trùng với entry
|
|
348
|
+
// được prepend ở trên — registry không bắt được ca này nên dedupe lại
|
|
349
|
+
allScripts = [...new Set(allScripts)];
|
|
350
|
+
|
|
344
351
|
if (islandEntries.length > 0) {
|
|
345
352
|
const islandMap: Record<string, string> = {};
|
|
346
353
|
for (const island of islandEntries) {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-runtime-Bokflh8Q.js","sources":["../src/core/head-priority.ts","../src/seo/mergeMetadata.ts","../src/core/const.ts","../src/core/jsx-runtime.ts"],"sourcesContent":["/**\n * Priority constants for Head component rendering order\n * Lower numbers render first (higher priority)\n */\nexport const HEAD_PRIORITY = {\n CRITICAL: 0, // charset, viewport - must be first\n HIGH: 10, // title, description, canonical\n MEDIUM: 50, // meta tags, robots\n SEO: 80, // OpenGraph, Twitter\n LOW: 100, // Custom head elements\n SCRIPTS: 200, // Scripts\n STYLES: 300, // Styles\n} as const;\n\nexport type HeadPriority = (typeof HEAD_PRIORITY)[keyof typeof HEAD_PRIORITY] | number;\n","import type { Metadata } from './types';\n\n/**\n * Merges parent and child metadata objects\n * Follows Next.js shallow merge pattern with deep merge for nested objects\n *\n * @param parent - Parent metadata (from layout/global loader)\n * @param child - Child metadata (from page/view loader)\n * @returns Merged metadata object\n */\nexport function mergeMetadata(\n parent: Metadata | null | undefined,\n child: Metadata | null | undefined,\n): Metadata {\n // If no parent, return child (or empty object)\n if (!parent) {\n return child || {};\n }\n\n // If no child, return parent\n if (!child) {\n return parent;\n }\n\n // Shallow merge base properties (child overrides parent)\n const merged: Metadata = {\n ...parent,\n ...child,\n };\n\n // Deep merge for nested objects\n // OpenGraph: merge nested properties\n if (child.openGraph || parent.openGraph) {\n if (child.openGraph && parent.openGraph) {\n merged.openGraph = {\n ...parent.openGraph,\n ...child.openGraph,\n // Deep merge for nested arrays/objects in openGraph\n images: child.openGraph.images ?? parent.openGraph.images,\n videos: child.openGraph.videos ?? parent.openGraph.videos,\n audio: child.openGraph.audio ?? parent.openGraph.audio,\n alternateLocale: child.openGraph.alternateLocale ?? parent.openGraph.alternateLocale,\n authors: child.openGraph.authors ?? parent.openGraph.authors,\n tags: child.openGraph.tags ?? parent.openGraph.tags,\n };\n } else {\n merged.openGraph = child.openGraph || parent.openGraph;\n }\n }\n\n // Twitter: merge nested properties\n if (child.twitter || parent.twitter) {\n if (child.twitter && parent.twitter) {\n merged.twitter = {\n ...parent.twitter,\n ...child.twitter,\n // Deep merge for nested objects\n images: child.twitter.images ?? parent.twitter.images,\n app: child.twitter.app\n ? {\n ...parent.twitter.app,\n ...child.twitter.app,\n id: {\n ...parent.twitter.app?.id,\n ...child.twitter.app.id,\n },\n url: {\n ...parent.twitter.app?.url,\n ...child.twitter.app.url,\n },\n }\n : parent.twitter.app,\n };\n } else {\n merged.twitter = child.twitter || parent.twitter;\n }\n }\n\n // Icons: merge nested properties\n if (child.icons || parent.icons) {\n if (child.icons && parent.icons) {\n merged.icons = {\n icon: child.icons.icon ?? parent.icons.icon,\n shortcut: child.icons.shortcut ?? parent.icons.shortcut,\n apple: child.icons.apple ?? parent.icons.apple,\n other: child.icons.other ?? parent.icons.other,\n };\n } else {\n merged.icons = child.icons || parent.icons;\n }\n }\n\n // Verification: merge nested properties\n if (child.verification || parent.verification) {\n if (child.verification && parent.verification) {\n merged.verification = {\n ...parent.verification,\n ...child.verification,\n // Deep merge for other verification tags\n other: child.verification.other\n ? {\n ...parent.verification.other,\n ...child.verification.other,\n }\n : parent.verification.other,\n me: child.verification.me ?? parent.verification.me,\n };\n } else {\n merged.verification = child.verification || parent.verification;\n }\n }\n\n // AppLinks: merge nested properties\n if (child.appLinks || parent.appLinks) {\n if (child.appLinks && parent.appLinks) {\n merged.appLinks = {\n ios: child.appLinks.ios\n ? {\n ...parent.appLinks.ios,\n ...child.appLinks.ios,\n }\n : parent.appLinks.ios,\n android: child.appLinks.android\n ? {\n ...parent.appLinks.android,\n ...child.appLinks.android,\n }\n : parent.appLinks.android,\n web: child.appLinks.web\n ? {\n ...parent.appLinks.web,\n ...child.appLinks.web,\n }\n : parent.appLinks.web,\n };\n } else {\n merged.appLinks = child.appLinks || parent.appLinks;\n }\n }\n\n // FormatDetection: merge nested properties\n if (child.formatDetection || parent.formatDetection) {\n if (child.formatDetection && parent.formatDetection) {\n merged.formatDetection = {\n ...parent.formatDetection,\n ...child.formatDetection,\n };\n } else {\n merged.formatDetection = child.formatDetection || parent.formatDetection;\n }\n }\n\n // Viewport: merge if both are objects\n if (child.viewport && parent.viewport) {\n if (typeof child.viewport === 'object' && typeof parent.viewport === 'object') {\n merged.viewport = {\n ...parent.viewport,\n ...child.viewport,\n } as Metadata['viewport'];\n } else {\n // If either is string, child overrides\n merged.viewport = child.viewport;\n }\n }\n\n // Robots: merge if both are objects\n if (child.robots && parent.robots) {\n if (typeof child.robots === 'object' && typeof parent.robots === 'object') {\n merged.robots = {\n ...parent.robots,\n ...child.robots,\n } as Metadata['robots'];\n } else {\n // If either is string, child overrides\n merged.robots = child.robots;\n }\n }\n\n // Array fields: child overrides parent (shallow merge behavior)\n // These are already handled by spread operator above\n // But we explicitly handle them for clarity:\n merged.keywords = child.keywords ?? parent.keywords;\n merged.themeColor = child.themeColor ?? parent.themeColor;\n merged.archives = child.archives ?? parent.archives;\n merged.assets = child.assets ?? parent.assets;\n\n // Other: merge objects\n if (child.other || parent.other) {\n if (child.other && parent.other) {\n merged.other = {\n ...parent.other,\n ...child.other,\n };\n } else {\n merged.other = child.other || parent.other;\n }\n }\n\n return merged;\n}\n","// Marker object để đánh dấu raw HTML không escape\nexport const RAW_HTML_MARKER = Symbol('rawHtml');\n","import { mergeMetadata } from '../seo/mergeMetadata';\nimport type { Metadata } from '../seo/types';\nimport { RAW_HTML_MARKER } from './const';\nimport { RequestInfo } from './entry-server';\nimport { HEAD_PRIORITY, type HeadPriority } from './head-priority';\n\nexport type JSXChild =\n | string\n | number\n | boolean\n | null\n | undefined\n | JSXNode\n | JSXChild[]\n | RawHtmlObject\n | HtmlContentObject;\n\nexport type RawHtmlObject = {\n [RAW_HTML_MARKER]: true;\n content: string;\n};\n\nexport type HtmlContentObject = {\n htmlContent: string;\n};\n\nexport type RenderedNode = {\n string: string;\n};\n\nexport type JSXNode = {\n type: string | ((props: any) => JSXChild);\n props: Record<string, any>;\n children: JSXChild[];\n};\n\nexport function jsxFactory(type: any, props: any, ...children: any): JSXNode {\n return { type, props: props || {}, children: children.flat() };\n}\n\nexport function Fragment({\n children,\n ...props\n}: { children?: JSXChild; setHtml?: unknown } & Record<string, any>): JSXChild {\n // Hỗ trợ setHtml cho Fragment\n if (props.setHtml !== undefined) {\n // Trả về object đặc biệt để không bị escape\n return { [RAW_HTML_MARKER]: true, content: props.setHtml?.toString() || '' };\n }\n return children;\n}\n\n// AsyncLocalStorage for render context (request-level)\nimport { AsyncLocalStorage } from 'async_hooks';\n\ninterface HeadEntry {\n content: JSXChild;\n priority: number; // Số càng nhỏ, render càng sớm\n source?: string; // Để debug (ví dụ: 'layout', 'page', 'seo')\n}\n\ninterface IslandEntry {\n key: string; // \"Counter_a3f2\" — registry key\n src: string; // \"src/views/.../Counter.tsx\" — manifest-compatible path\n name: string; // \"Counter\" — export name\n}\n\n/**\n * A pending server-side render request for an `ssr` island.\n * Collected during the synchronous render pass and filled in afterwards by\n * entry-server (which can `await import()` the component + call renderToString).\n */\ninterface SsrIslandEntry {\n token: string; // unique placeholder token embedded in the HTML body\n src: string; // \"src/views/.../Counter.tsx\" — manifest-compatible path (no leading slash)\n name: string; // \"Counter\" — export name\n props: Record<string, any>;\n}\n\ninterface RenderContext {\n clientJsRegistry: Array<{ path: string; from: string }>;\n cssRegistry: Array<{ path: string; from: string }>;\n islandRegistry: IslandEntry[];\n ssrIslands: SsrIslandEntry[];\n cacheTags: Set<string>;\n headRegistry: HeadEntry[]; // Thay vì JSXChild[]\n metadataStack: Metadata[]; // Stack để track metadata hierarchy\n schemaRegistry: Array<Record<string, any>>; // Schema.org structured data từ loaders\n request: RequestInfo;\n viewName?: string; // View name from route handler\n}\n\nconst renderStore = new AsyncLocalStorage<RenderContext>();\n\n// Create context for each request\nfunction createRequestContext(requestInfo: RequestInfo): RenderContext {\n return {\n clientJsRegistry: [],\n cssRegistry: [],\n islandRegistry: [],\n ssrIslands: [],\n cacheTags: new Set(),\n headRegistry: [],\n metadataStack: [],\n schemaRegistry: [],\n request: requestInfo,\n };\n}\n\nexport function useClientJs(path: string): string {\n if (typeof path === 'string' && path.length > 0) {\n const renderContext = renderStore.getStore();\n if (renderContext) {\n renderContext.clientJsRegistry.push({ path, from: 'Unknown' });\n }\n }\n return '';\n}\n\nexport function registerIsland(key: string, src: string, name: string): void {\n const renderContext = renderStore.getStore();\n if (renderContext) {\n // Dedupe by key\n if (!renderContext.islandRegistry.some((e) => e.key === key)) {\n renderContext.islandRegistry.push({ key, src, name });\n }\n }\n}\n\nexport function getIslandEntries(): IslandEntry[] {\n const context = renderStore.getStore();\n if (!context) return [];\n return context.islandRegistry.slice();\n}\n\n/**\n * Register a pending SSR island render. Returns a unique placeholder token that\n * the caller embeds (as raw HTML) in the island's body. entry-server replaces\n * the token with the server-rendered component HTML after the sync render pass.\n */\nexport function registerSsrIsland(src: string, name: string, props: Record<string, any>): string {\n const renderContext = renderStore.getStore();\n if (!renderContext) return '';\n const token = `__L5E_SSR_${renderContext.ssrIslands.length}__`;\n renderContext.ssrIslands.push({ token, src, name, props });\n return token;\n}\n\nexport function getSsrIslands(): SsrIslandEntry[] {\n const context = renderStore.getStore();\n if (!context) return [];\n return context.ssrIslands.slice();\n}\n\nexport function useCss(path: string): string {\n if (typeof path === 'string' && path.length > 0) {\n const renderContext = renderStore.getStore();\n if (renderContext) {\n renderContext.cssRegistry.push({ path, from: 'Unknown' });\n }\n }\n return '';\n}\n\n// Wrapper to run render in async context\nexport function runInRenderContext<T>(\n renderFn: () => T | Promise<T>,\n requestInfo: RequestInfo,\n viewName?: string,\n): Promise<T> {\n const context = createRequestContext(requestInfo);\n if (viewName) {\n context.viewName = viewName;\n }\n return renderStore.run(context, () => Promise.resolve(renderFn()));\n}\n\n// Set view name in current render context\nexport function setViewName(viewName: string): void {\n const context = renderStore.getStore();\n if (context) {\n context.viewName = viewName;\n }\n}\n\n// Get entries from current context\nexport function getClientJsEntries(): Array<{ path: string; from: string }> {\n const context = renderStore.getStore();\n if (!context) return [];\n return context.clientJsRegistry.slice();\n}\n\n// Get cache tags from current context\nexport function getCacheTags(): string[] {\n const context = renderStore.getStore();\n if (!context) return [];\n return Array.from(context.cacheTags);\n}\n\n// Add cache tags to current context\nexport function addCacheTag(tag: string | string[] | Record<string, boolean>): void {\n const context = renderStore.getStore();\n if (!context) return;\n\n if (Array.isArray(tag)) {\n tag.forEach((t) => {\n if (typeof t === 'string' && t.trim()) {\n context.cacheTags.add(t.trim());\n }\n });\n } else if (typeof tag === 'string' && tag.trim()) {\n context.cacheTags.add(tag.trim());\n } else if (typeof tag === 'object' && tag !== null) {\n Object.entries(tag).forEach(([key, value]) => {\n if (value && typeof key === 'string' && key.trim()) {\n context.cacheTags.add(key.trim());\n }\n });\n }\n}\n\n// Get CSS entries from current context\nexport function getCssEntries(): Array<{ path: string; from: string }> {\n const context = renderStore.getStore();\n if (!context) return [];\n return context.cssRegistry.slice();\n}\n\n// Head component to collect head elements with priority support\nexport function Head({\n children,\n priority = HEAD_PRIORITY.LOW, // Default priority\n}: {\n children?: JSXChild;\n priority?: HeadPriority;\n}): null {\n const renderContext = renderStore.getStore();\n if (renderContext && children) {\n renderContext.headRegistry.push({\n content: children,\n priority: typeof priority === 'number' ? priority : HEAD_PRIORITY.LOW,\n source: 'manual',\n });\n\n // Sort theo priority sau mỗi lần push để đảm bảo thứ tự đúng\n renderContext.headRegistry.sort((a, b) => a.priority - b.priority);\n }\n return null;\n}\n\n// Get head content from current context (already sorted by priority)\nexport function getHeadContent(): JSXChild[] {\n const context = renderStore.getStore();\n if (!context) return [];\n // Đã được sort trong Head component, chỉ cần map để lấy content\n return context.headRegistry.map((entry) => entry.content);\n}\n\n// Push metadata to stack (for hierarchical metadata support)\nexport function pushMetadata(metadata: Metadata): void {\n const context = renderStore.getStore();\n if (context && metadata) {\n context.metadataStack.push(metadata);\n }\n}\n\n// Resolve and merge all metadata from stack (root → leaf)\nexport function resolveMetadata(): Metadata | null {\n const context = renderStore.getStore();\n if (!context || context.metadataStack.length === 0) {\n return null;\n }\n\n // Merge from root → leaf (reduce left to right)\n return context.metadataStack.reduce(\n (acc, current) => mergeMetadata(acc, current),\n null as Metadata | null,\n );\n}\n\n// Push schema to registry (for schema markup from loaders)\n// Accepts schema-dts types (WithContext<T> or array of schemas)\nexport function pushSchema(schema: any | Array<any>): void {\n const context = renderStore.getStore();\n if (!context) return;\n\n if (Array.isArray(schema)) {\n context.schemaRegistry.push(...schema);\n } else {\n context.schemaRegistry.push(schema);\n }\n}\n\n// Get all schemas from registry\nexport function getSchemas(): Array<Record<string, any>> {\n const context = renderStore.getStore();\n if (!context) return [];\n return context.schemaRegistry.slice();\n}\n\n// Hook to get render request context\nexport function useRequest() {\n const context = renderStore.getStore();\n\n if (!context) {\n throw new Error('useRequest called outside of render context');\n }\n\n return {\n request: context.request,\n view: context.viewName,\n locals: (context.request.locals ?? {}) as Record<string, unknown>,\n params: (context.request.params ?? {}) as Record<string, any>,\n\n // Add cache tags\n addCacheTag: (tag: string | string[] | Record<string, boolean>) => {\n addCacheTag(tag);\n },\n\n // Get all cache tags\n getCacheTags: () => {\n return Array.from(context.cacheTags);\n },\n };\n}\n\n/**\n * Checks if a value is a valid JSX element (JSXNode)\n * Similar to React.isValidElement\n */\nexport function isValidElement(value: any): value is JSXNode {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'type' in value &&\n 'props' in value &&\n 'children' in value &&\n (typeof value.type === 'string' || typeof value.type === 'function')\n );\n}\n\n/**\n * Clones a JSX element with new props and/or children\n * Similar to React.cloneElement\n */\nexport function cloneElement(\n element: JSXNode,\n props?: Record<string, any>,\n ...children: JSXChild[]\n): JSXNode {\n const newProps = { ...element.props, ...props };\n const newChildren = children.length > 0 ? children.flat() : element.children;\n\n return {\n type: element.type,\n props: newProps,\n children: newChildren,\n };\n}\n"],"names":["HEAD_PRIORITY","mergeMetadata","parent","child","merged","RAW_HTML_MARKER","jsxFactory","type","props","children","Fragment","renderStore","AsyncLocalStorage","createRequestContext","requestInfo","useClientJs","path","renderContext","registerIsland","key","src","name","e","getIslandEntries","context","registerSsrIsland","token","getSsrIslands","useCss","runInRenderContext","renderFn","viewName","setViewName","getClientJsEntries","getCacheTags","addCacheTag","tag","t","value","getCssEntries","Head","priority","a","b","getHeadContent","entry","pushMetadata","metadata","resolveMetadata","acc","current","pushSchema","schema","getSchemas","useRequest","isValidElement","cloneElement","element","newProps","newChildren"],"mappings":"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"}
|