Skip to content

Commit c50424b

Browse files
committed
Fix blank page: anti-flash must exist in DOM before theme-init.js runs
ROOT CAUSE: inject.scripts (theme-init.js) was emitted BEFORE inject.headFragments (anti-flash cloak). When theme-init.js ran to remove the cloak, the <style id="less-anti-flash"> element didn't exist yet because the HTML parser hadn't reached it. The cloak was applied AFTER theme-init.js ran, and never removed -> page stays visibility:hidden forever. Fix: swap order in index.ts — headFragments first, then scripts. Also: - Restore PWA (was incorrectly disabled) - Fix SW: only intercept same-origin requests (cross-origin CDN/analytics pass through). networkFirst returns 503 fallback instead of throwing.
1 parent c9f2038 commit c50424b

3 files changed

Lines changed: 28 additions & 16 deletions

File tree

packages/adapter-vite/src/cli/ssg-render.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,8 @@ self.addEventListener('activate', (e) => e.waitUntil(
372372
));
373373
self.addEventListener('fetch', (e) => {
374374
const url = new URL(e.request.url);
375-
if (!url.protocol.startsWith('http')) return;
375+
// Only handle same-origin requests — cross-origin (CDN, analytics) pass through
376+
if (url.origin !== location.origin || !url.protocol.startsWith('http')) return;
376377
const isAsset = /\\.[a-z0-9]+$/i.test(url.pathname) && !url.pathname.includes('/api/');
377378
e.respondWith(
378379
(isAsset ? cacheFirst(e.request) : networkFirst(e.request))
@@ -400,7 +401,8 @@ async function networkFirst(req) {
400401
} catch {
401402
const cached = await caches.match(req);
402403
if (cached) return cached;
403-
throw new Error('offline');
404+
// Return a fallback rather than throwing — prevents unhandled rejections
405+
return new Response('offline', { status: 503 });
404406
}
405407
}`;
406408
writeFileSync(join(outputDir, 'sw.js'), swCode);

packages/adapter-vite/src/index.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,29 @@ export function less(options: FrameworkOptions = {}, externalCtx?: LessBuildCont
236236

237237
if (options.inject && !headExtras) {
238238
const fragments: string[] = [];
239+
240+
// headFragments FIRST (meta, styles, anti-flash) — must exist in DOM
241+
// before scripts that reference them (e.g. theme-init.js removes anti-flash).
242+
for (const frag of options.inject.headFragments || []) {
243+
// Security: warn if fragment contains inline <script> tags
244+
if (/<script[\s>]/i.test(frag)) {
245+
log.warn(
246+
'inject.headFragments contains <script> tags. Ensure this content is ' +
247+
'developer-controlled, not user-supplied, to prevent XSS. For safe URL injection, ' +
248+
'use inject.scripts instead.',
249+
);
250+
}
251+
fragments.push(frag);
252+
}
253+
254+
// Stylesheets second
239255
for (const href of options.inject.stylesheets || []) {
240256
validateSafeUrl(href, 'inject.stylesheets');
241257
const safeHref = escapeHtmlAttr(href);
242258
fragments.push(`<link rel="stylesheet" href="${safeHref}" />`);
243259
}
260+
261+
// Scripts last — depend on headFragments being in DOM
244262
for (const script of options.inject.scripts || []) {
245263
const isObjectScript = typeof script === 'object';
246264
const src = isObjectScript ? script.src : script;
@@ -264,17 +282,6 @@ export function less(options: FrameworkOptions = {}, externalCtx?: LessBuildCont
264282
.join(' ');
265283
fragments.push(`<script ${attrText}></script>`);
266284
}
267-
for (const frag of options.inject.headFragments || []) {
268-
// Security: warn if fragment contains inline <script> tags
269-
if (/<script[\s>]/i.test(frag)) {
270-
log.warn(
271-
'inject.headFragments contains <script> tags. Ensure this content is ' +
272-
'developer-controlled, not user-supplied, to prevent XSS. For safe URL injection, ' +
273-
'use inject.scripts instead.',
274-
);
275-
}
276-
fragments.push(frag);
277-
}
278285
headExtras = fragments.join('\n ');
279286
allowHeadExtrasScripts = true;
280287
}

www/vite.config.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,12 @@ export default defineConfig({
7171
ssr: {
7272
noExternal: ['@lessjs/ui'],
7373
},
74-
// PWA disabled for now — the service worker blocks goatcounter and
75-
// other cross-origin requests, causing console errors and page load issues.
76-
// pwa: { ... },
74+
pwa: {
75+
name: 'LessJS Framework — Less is More',
76+
shortName: 'LessJS',
77+
themeColor: '#000000',
78+
backgroundColor: '#ffffff',
79+
},
7780
viewTransition: true,
7881
speculation: true,
7982
inject: {

0 commit comments

Comments
 (0)