diff --git a/.changeset/beige-books-sell.md b/.changeset/beige-books-sell.md new file mode 100644 index 0000000..d929e35 --- /dev/null +++ b/.changeset/beige-books-sell.md @@ -0,0 +1,5 @@ +--- +"@marko/vite": patch +--- + +Avoid FOUC for dev mode page reloads. diff --git a/src/components/vite.marko b/src/components/vite.marko index 5a5120a..f4a718b 100644 --- a/src/components/vite.marko +++ b/src/components/vite.marko @@ -8,11 +8,35 @@ static function renderAssets(slot) { const writtenEntries = (this[slotWrittenEntriesKey] = entries.length); for (let i = lastWrittenEntry; i < writtenEntries; i++) { - const entry = entries[i]; - const parts = - typeof __MARKO_MANIFEST__ === "object" - ? __MARKO_MANIFEST__[entry]?.[slot] - : entry[slot]; + let entry = entries[i]; + + if (typeof __MARKO_MANIFEST__ === "object") { + entry = __MARKO_MANIFEST__[entry] || {}; + } else if (slot === "head") { + // In dev mode we have is a list entries of the top level modules that need to be imported. + // To avoid FOUC we will hide the page until all of these modules are loaded. + const { entries } = entry; + if (entries) { + let sep = ""; + html += `((root=document.documentElement)=>{`; + html += "root.style.visibility='hidden';"; + html += "document.currentScript.remove();"; + html += "Promise.allSettled(["; + + for (const id of entries) { + html += `${sep}import(${JSON.stringify(this.___viteBasePath + id)})`; + sep = ","; + } + + html += "]).then(()=>{"; + html += "root.style.visibility='';"; + html += + "if(root.getAttribute('style')==='')root.removeAttribute('style')"; + html += "})})()"; + } + } + + const parts = entry[slot]; if (parts) { for (const part of parts) { @@ -40,7 +64,9 @@ $ if (!out.global.___viteRenderAssets) { <__flush_here_and_after__> $ out.global.___flushedMBP = true; - $!{`${out.global.___viteBaseVar}=${JSON.stringify(input.base)}`} + $!{`${ + out.global.___viteBaseVar + }=${JSON.stringify(input.base)}`} $!{out.global.___viteRenderAssets(input.slot)} diff --git a/src/index.ts b/src/index.ts index 5302aea..f3c5b8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -645,10 +645,13 @@ export default function markoPlugin(opts: Options = {}): vite.Plugin[] { } if (chunk?.type === "asset") { - browserManifest[entryId] = await generateDocManifest( - basePath, - chunk.source.toString() - ); + browserManifest[entryId] = { + ...(await generateDocManifest( + basePath, + chunk.source.toString() + )), + entries: undefined, // clear out entries for prod builds. + } as any; delete bundle[chunkId]; } else { diff --git a/src/manifest-generator.ts b/src/manifest-generator.ts index 6e62722..0903c54 100644 --- a/src/manifest-generator.ts +++ b/src/manifest-generator.ts @@ -5,6 +5,7 @@ import serialize from "./serializer"; type SerializedOrNull = null | ReturnType; export interface DocManifest { + entries: string[]; "head-prepend": SerializedOrNull; head: SerializedOrNull; "body-prepend": SerializedOrNull; @@ -25,6 +26,7 @@ export function generateDocManifest( } const htmlChildren = dom.find(isElement)!.childNodes; + const entries: string[] = []; const headPrepend: Node[] = []; const head: Node[] = []; const bodyPrepend: Node[] = []; @@ -49,10 +51,11 @@ export function generateDocManifest( ); resolve({ - "head-prepend": serializeOrNull(basePath, headPrepend), - head: serializeOrNull(basePath, head), - "body-prepend": serializeOrNull(basePath, bodyPrepend), - body: serializeOrNull(basePath, body), + entries, + "head-prepend": serializeOrNull(basePath, headPrepend, entries), + head: serializeOrNull(basePath, head, entries), + "body-prepend": serializeOrNull(basePath, bodyPrepend, entries), + body: serializeOrNull(basePath, body, entries), }); }) ); @@ -67,8 +70,8 @@ export function generateInputDoc(entry: string) { )}>`; } -function serializeOrNull(basePath: string, nodes: Node[]) { - const result = serialize(basePath, nodes); +function serializeOrNull(basePath: string, nodes: Node[], entries: string[]) { + const result = serialize(basePath, nodes, entries); if (result.length) { return result; } diff --git a/src/serializer.ts b/src/serializer.ts index f05cf26..5cd8c87 100644 --- a/src/serializer.ts +++ b/src/serializer.ts @@ -26,6 +26,7 @@ const voidElements = new Set([ export default function serialize( basePath: string, nodes: Node[], + entries: string[], parts?: (string | InjectType)[] ) { let curString = parts ? (parts.pop() as string) : ""; @@ -69,13 +70,17 @@ export default function serialize( if (attr.value === "") { curString += ` ${attr.name}`; } else if (attr.name === urlAttr) { + const id = stripBasePath(basePath, attr.value).replace(/^\.\//, ""); + + if (tag.name === "script") { + entries.push(id); + } + curString += ` ${attr.name}="`; parts.push( curString, InjectType.PublicPath, - stripBasePath(basePath, attr.value) - .replace(/"/g, "'") - .replace(/^\.\//, "") + '"' + id.replace(/"/g, "'") + '"' ); curString = ""; } else { @@ -87,7 +92,7 @@ export default function serialize( if (tag.children.length) { parts.push(curString); - serialize(basePath, tag.children, parts); + serialize(basePath, tag.children, entries, parts); curString = parts.pop() as string; }