@withl5e/l5e 0.3.3-alpha.0 → 0.3.3-alpha.1
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/global-style-DFvaBcuR.js +27 -0
- package/dist/global-style-DFvaBcuR.js.map +1 -0
- package/dist/server.js +294 -280
- package/dist/server.js.map +1 -1
- package/dist/vite-plugin.js +124 -119
- package/dist/vite-plugin.js.map +1 -1
- package/package.json +1 -1
- package/src/core/global-style.ts +35 -0
- package/src/core/server.ts +22 -9
- package/src/core/vite-plugin.ts +13 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const GLOBAL_STYLE_SOURCE = '/src/global.css';
|
|
5
|
+
export const GLOBAL_STYLE_MANIFEST_KEY = 'src/global.css';
|
|
6
|
+
|
|
7
|
+
export function findGlobalStyleInput(root: string): string | null {
|
|
8
|
+
const absolutePath = join(root, GLOBAL_STYLE_MANIFEST_KEY);
|
|
9
|
+
return existsSync(absolutePath) ? absolutePath : null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function withAssetBase(base: string, assetPath: string): string {
|
|
13
|
+
const cleanPath = assetPath.replace(/^\/+/, '');
|
|
14
|
+
const normalizedBase = base.endsWith('/') ? base : `${base}/`;
|
|
15
|
+
return `${normalizedBase}${cleanPath}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveGlobalStyleHref({
|
|
19
|
+
root,
|
|
20
|
+
manifest,
|
|
21
|
+
isProduction,
|
|
22
|
+
base,
|
|
23
|
+
}: {
|
|
24
|
+
root: string;
|
|
25
|
+
manifest?: Record<string, any>;
|
|
26
|
+
isProduction: boolean;
|
|
27
|
+
base: string;
|
|
28
|
+
}): string | null {
|
|
29
|
+
if (!isProduction) {
|
|
30
|
+
return findGlobalStyleInput(root) ? withAssetBase(base, GLOBAL_STYLE_SOURCE) : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const file = manifest?.[GLOBAL_STYLE_MANIFEST_KEY]?.file;
|
|
34
|
+
return typeof file === 'string' ? withAssetBase(base, file) : null;
|
|
35
|
+
}
|
package/src/core/server.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { bundleCss, bundleScripts, getBundledFile } from './bundler';
|
|
|
11
11
|
import type { RenderResult, RequestInfo } from './entry-server';
|
|
12
12
|
import { escapeProp } from './render';
|
|
13
13
|
import { createHeadersFromExpressRequest, parseCookies } from './request';
|
|
14
|
+
import { resolveGlobalStyleHref } from './global-style';
|
|
14
15
|
|
|
15
16
|
export interface ServerOptions {
|
|
16
17
|
root?: string;
|
|
@@ -213,6 +214,7 @@ async function createPageResponse({
|
|
|
213
214
|
root,
|
|
214
215
|
distClientDir,
|
|
215
216
|
isProduction,
|
|
217
|
+
assetBase,
|
|
216
218
|
}: {
|
|
217
219
|
rendered: RenderResult;
|
|
218
220
|
template: string;
|
|
@@ -220,6 +222,7 @@ async function createPageResponse({
|
|
|
220
222
|
root: string;
|
|
221
223
|
distClientDir: string;
|
|
222
224
|
isProduction: boolean;
|
|
225
|
+
assetBase: string;
|
|
223
226
|
}): Promise<globalThis.Response> {
|
|
224
227
|
const rawResponse = createRawResponse(rendered);
|
|
225
228
|
if (rawResponse) {
|
|
@@ -244,9 +247,23 @@ async function createPageResponse({
|
|
|
244
247
|
const swr: number | undefined = rendered.swr;
|
|
245
248
|
|
|
246
249
|
let extraHead = '';
|
|
250
|
+
const emittedStyles = new Set<string>();
|
|
251
|
+
const appendStylesheet = (href: string, crossorigin = false) => {
|
|
252
|
+
if (emittedStyles.has(href)) return;
|
|
253
|
+
emittedStyles.add(href);
|
|
254
|
+
extraHead += `<link rel="stylesheet"${crossorigin ? ' crossorigin' : ''} href="${escapeProp(href)}">`;
|
|
255
|
+
};
|
|
247
256
|
let globalScripts: string[] = [];
|
|
248
257
|
let islandRegistryScript = '';
|
|
249
258
|
|
|
259
|
+
const globalStyleHref = resolveGlobalStyleHref({
|
|
260
|
+
root,
|
|
261
|
+
manifest,
|
|
262
|
+
isProduction,
|
|
263
|
+
base: assetBase,
|
|
264
|
+
});
|
|
265
|
+
if (globalStyleHref) appendStylesheet(globalStyleHref, isProduction);
|
|
266
|
+
|
|
250
267
|
// Externalized island props → a single JSON script at the end of the document.
|
|
251
268
|
// serialize-javascript with isJSON escapes `<`, `>`, `&` (and U+2028/2029) so the
|
|
252
269
|
// props can't break out of the <script> block or inject markup (XSS). The runtime
|
|
@@ -324,7 +341,7 @@ async function createPageResponse({
|
|
|
324
341
|
if (globalEntry) {
|
|
325
342
|
if (globalEntry.css && globalEntry.css.length > 0) {
|
|
326
343
|
globalEntry.css.forEach((cssFile: string) => {
|
|
327
|
-
|
|
344
|
+
appendStylesheet(`/${cssFile}`, true);
|
|
328
345
|
});
|
|
329
346
|
}
|
|
330
347
|
if (globalEntry.file) {
|
|
@@ -346,18 +363,13 @@ async function createPageResponse({
|
|
|
346
363
|
}
|
|
347
364
|
|
|
348
365
|
if (cssSrcList.length > 0) {
|
|
349
|
-
|
|
350
|
-
.map((file) => `<link rel="stylesheet" crossorigin href="${file}">`)
|
|
351
|
-
.join('');
|
|
366
|
+
cssSrcList.forEach((file) => appendStylesheet(file, true));
|
|
352
367
|
}
|
|
353
368
|
}
|
|
354
369
|
|
|
355
|
-
let cssHtml = '';
|
|
356
370
|
if (!isProduction) {
|
|
357
371
|
// Registry đã dedupe, nhưng vẫn lọc lại ở đây để dev không bao giờ ra thẻ trùng
|
|
358
|
-
|
|
359
|
-
.map((src) => `<link rel="stylesheet" href="${src}">`)
|
|
360
|
-
.join('');
|
|
372
|
+
[...new Set(cssSrcList)].forEach((src) => appendStylesheet(src));
|
|
361
373
|
}
|
|
362
374
|
|
|
363
375
|
let allScripts = [...globalScripts, ...scriptSrcList];
|
|
@@ -394,7 +406,7 @@ async function createPageResponse({
|
|
|
394
406
|
const html = rendered.rawHtml
|
|
395
407
|
? rendered.html || ''
|
|
396
408
|
: templateWithLang
|
|
397
|
-
.replace(`<!--app-head-->`, () => (rendered.head ?? '') + extraHead
|
|
409
|
+
.replace(`<!--app-head-->`, () => (rendered.head ?? '') + extraHead)
|
|
398
410
|
.replace(`<!--app-html-->`, () => rendered.html ?? '')
|
|
399
411
|
.replace(`<!--app-scripts-->`, () => scriptsHtml);
|
|
400
412
|
|
|
@@ -747,6 +759,7 @@ export async function createServer(options: ServerOptions = {}): Promise<ServerC
|
|
|
747
759
|
root,
|
|
748
760
|
distClientDir,
|
|
749
761
|
isProduction,
|
|
762
|
+
assetBase: isProduction ? base : vite!.config.base,
|
|
750
763
|
});
|
|
751
764
|
};
|
|
752
765
|
|
package/src/core/vite-plugin.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from 'fs';
|
|
11
11
|
import { dirname, join, relative, resolve } from 'path';
|
|
12
12
|
import type { Plugin, UserConfig } from 'vite';
|
|
13
|
+
import { findGlobalStyleInput } from './global-style';
|
|
13
14
|
|
|
14
15
|
const VIRTUAL_L5E_VIEWS = 'virtual:l5e-views';
|
|
15
16
|
const VIRTUAL_L5E_ROUTE = 'virtual:l5e-route';
|
|
@@ -246,6 +247,12 @@ function discoverRollupInput(rootDir: string): {
|
|
|
246
247
|
console.log('[l5e] Detected src/client.global.ts and added as entry');
|
|
247
248
|
}
|
|
248
249
|
|
|
250
|
+
const globalStyleInput = findGlobalStyleInput(rootDir);
|
|
251
|
+
if (globalStyleInput) {
|
|
252
|
+
input['global-style'] = globalStyleInput;
|
|
253
|
+
console.log('[l5e] Detected src/global.css and added as global stylesheet');
|
|
254
|
+
}
|
|
255
|
+
|
|
249
256
|
// Scan all .tsx and .ts files
|
|
250
257
|
const tsFiles = scanTsFiles(srcDir);
|
|
251
258
|
|
|
@@ -297,6 +304,12 @@ function discoverRollupInput(rootDir: string): {
|
|
|
297
304
|
|
|
298
305
|
const absolutePath = resolve(rootDir, relativePath);
|
|
299
306
|
|
|
307
|
+
// src/global.css is already a dedicated convention entry. Keeping one
|
|
308
|
+
// Rollup input avoids collisions if a shared layout also calls useCss().
|
|
309
|
+
if (globalStyleInput && resolve(absolutePath) === resolve(globalStyleInput)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
300
313
|
// Only add if file exists
|
|
301
314
|
if (!existsSync(absolutePath)) {
|
|
302
315
|
console.warn(`[l5e] CSS file not found: ${absolutePath} (from ${cssPath})`);
|