Skip to content

Commit

Permalink
fix encoding of chunks, fix chunk (re)loading for css (vercel/turbore…
Browse files Browse the repository at this point in the history
…po#6424)

### Description

We need to be consistent with url encoding
Look for existing link tags when loading CSS
allow querystring when detection existing chunks

### Testing Instructions

<!--
  Give a quick description of steps to test your changes.
-->


Closes PACK-1934
  • Loading branch information
sokra authored Nov 13, 2023
1 parent a21180f commit c030631
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,10 @@ function getOrInstantiateRuntimeModule(
* Returns the URL relative to the origin where a chunk can be fetched from.
*/
function getChunkRelativeUrl(chunkPath: ChunkPath): string {
return `${CHUNK_BASE_PATH}${chunkPath}`;
return `${CHUNK_BASE_PATH}${chunkPath}`
.split("/")
.map((p) => encodeURIComponent(p))
.join("/");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async function loadWebAssemblyModule(

// [TODO] need to match behavior as similar to UrlAssetReference
function resolveAbsolutePath(modulePath?: string) {
throw new Error('resolveAbsolutePath is not implemented in the DOM runtime');
throw new Error("resolveAbsolutePath is not implemented in the DOM runtime");
}

(() => {
Expand Down Expand Up @@ -97,7 +97,9 @@ function resolveAbsolutePath(modulePath?: string) {
const chunkUrl = getChunkRelativeUrl(chunkPath);

if (chunkPath.endsWith(".css")) {
const links = document.querySelectorAll(`link[href="${chunkUrl}"]`);
const links = document.querySelectorAll(
`link[href="${chunkUrl}"],link[href^="${chunkUrl}?"]`
);
for (const link of Array.from(links)) {
link.remove();
}
Expand All @@ -106,7 +108,9 @@ function resolveAbsolutePath(modulePath?: string) {
// runtime once evaluated.
// However, we still want to remove the script tag from the DOM to keep
// the HTML somewhat consistent from the user's perspective.
const scripts = document.querySelectorAll(`script[src="${chunkUrl}"]`);
const scripts = document.querySelectorAll(
`script[src="${chunkUrl}"],script[src^="${chunkUrl}?"]`
);
for (const script of Array.from(scripts)) {
script.remove();
}
Expand All @@ -122,15 +126,10 @@ function resolveAbsolutePath(modulePath?: string) {
return;
}

const encodedChunkPath = chunkPath
.split("/")
.map((p) => encodeURIComponent(p))
.join("/");

const chunkUrl = getChunkRelativeUrl(encodedChunkPath);
const chunkUrl = getChunkRelativeUrl(chunkPath);

const previousLinks = document.querySelectorAll(
`link[rel=stylesheet][href^="${chunkUrl}"]`
`link[rel=stylesheet][href="${chunkUrl}"],link[rel=stylesheet][href^="${chunkUrl}?"]`
);

if (previousLinks.length == 0) {
Expand Down Expand Up @@ -230,28 +229,50 @@ function resolveAbsolutePath(modulePath?: string) {
const chunkUrl = getChunkRelativeUrl(chunkPath);

if (chunkPath.endsWith(".css")) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = chunkUrl;
link.onerror = () => {
resolver.reject();
};
link.onload = () => {
const previousLinks = document.querySelectorAll(
`link[rel=stylesheet][href="${chunkUrl}"],link[rel=stylesheet][href^="${chunkUrl}?"]`
);
if (previousLinks.length > 0) {
// CSS chunks do not register themselves, and as such must be marked as
// loaded instantly.
resolver.resolve();
};
document.body.appendChild(link);
} else {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = chunkUrl;
link.onerror = () => {
resolver.reject();
};
link.onload = () => {
// CSS chunks do not register themselves, and as such must be marked as
// loaded instantly.
resolver.resolve();
};
document.body.appendChild(link);
}
} else if (chunkPath.endsWith(".js")) {
const script = document.createElement("script");
script.src = chunkUrl;
// We'll only mark the chunk as loaded once the script has been executed,
// which happens in `registerChunk`. Hence the absence of `resolve()` in
// this branch.
script.onerror = () => {
resolver.reject();
};
document.body.appendChild(script);
const previousScripts = document.querySelectorAll(
`script[src="${chunkUrl}"],script[src^="${chunkUrl}?"]`
);
if (previousScripts.length > 0) {
// There is this edge where the script already failed loading, but we
// can't detect that. The Promise will never resolve in this case.
for (const script of Array.from(previousScripts)) {
script.addEventListener("error", () => {
resolver.reject();
});
}
} else {
const script = document.createElement("script");
script.src = chunkUrl;
// We'll only mark the chunk as loaded once the script has been executed,
// which happens in `registerChunk`. Hence the absence of `resolve()` in
// this branch.
script.onerror = () => {
resolver.reject();
};
document.body.appendChild(script);
}
} else {
throw new Error(`can't infer type of chunk from path ${chunkPath}`);
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c030631

Please sign in to comment.