Skip to content

Commit

Permalink
Document and explain the scripts load order function
Browse files Browse the repository at this point in the history
  • Loading branch information
kuba-orlik committed Jul 4, 2024
1 parent 1466197 commit b158a24
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 15 deletions.
23 changes: 8 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Manager, MCEvent } from '@managed-components/types'
import * as cheerio from 'cheerio'
import { postponeScript } from './postponeScript'

type TElement = {
attributes: { [name: string]: string }
Expand Down Expand Up @@ -59,30 +60,22 @@ export const handler = ({ payload, client }: MCEvent) => {
client.execute(headTagAppender('link', attributes))
})

const wait_for: string[] = []
const idsOfScriptsToAwait: string[] = []
scripts.forEach(({ content, attributes }) => {
if (attributes?.src) {
if (!attributes?.onload) {
attributes.onload = ''
}
if (!attributes.async || attributes.defer) {
const order_id = crypto.randomUUID()
attributes['order-id'] = order_id
wait_for.push(order_id)
attributes.onload += `{document.dispatchEvent(new Event("loaded-${order_id}"))}`
const scriptID = crypto.randomUUID()
attributes['order-id'] = scriptID
idsOfScriptsToAwait.push(scriptID)
attributes.onload += `{document.dispatchEvent(new Event("loaded-${scriptID}"))}`
}
client.execute(headTagAppender('script', attributes))
} else if (content) {
if (wait_for.length) {
content = `{const loaded = ${JSON.stringify(
Object.fromEntries(wait_for.map(id => [id, false]))
)};
let called = false;
const call_if_ready = ()=>{if(!called && Object.values(loaded).every(e=>e)){{${content}}; called = true}};
${wait_for.map(
id =>
`document.addEventListener("loaded-${id}", ()=>{loaded["${id}"] = true; call_if_ready()})`
)}}`
if (idsOfScriptsToAwait.length) {
content = postponeScript(idsOfScriptsToAwait, content)
}
client.execute(content)
}
Expand Down
86 changes: 86 additions & 0 deletions src/postponeScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* If you load an index.html with such a snippet inside:
```
<script src="/some-third-party.js"></script>
<script>
SomeThirdParty.start()
</script>
```
The browser doesn't execute the second script until the first one is
fully loaded and parsed.
To my best knowledge, this isn't a feature of the DOM, but of the
browser's HTML parser/scanner.
As we're using DOM API to append scripts to the document after it's
been parsed, we no longer have this guarantee, so using the above HTML
snippet in CustomHTML component would result in the second script
running immediately and resulting in an arror, as the first script
didn't load yet.
With this function, we're making the given script only execute once
all the scripts of given IDs have finished loading and running
(onLoad). The handler in index.ts interates over scripts in each HTML
snippet and gives all scripts with an `src` a random ID and attaches
an onload listener that dispatches an event named `loaded-${scriptID}`.
The code that needs to wait for those scripts is then wrapped in
another code that only runs the original code once it register all the
events that signal that the necessary scripts have finished running.
so, for a simple `console.log('hello')` script that needs scripts
"id1" and "id2" to work, we'd get:
```
{
const loaded = { id1: false, id2: false };
let called = false;
const call_if_ready = () => {
if (!called && Object.values(loaded).every((e) => e)) {
{
console.log("hello");
}
called = true;
}
};
document.addEventListener("loaded-id1", () => {
loaded["id1"] = true;
call_if_ready();
});
document.addEventListener("loaded-id2", () => {
loaded["id2"] = true;
call_if_ready();
});
}
```
*/


Check failure on line 63 in src/postponeScript.ts

View workflow job for this annotation

GitHub Actions / build-test (21.x)

Replace `⏎⏎⏎export·function·postponeScript(idsOfScriptsToAwait:·string[],·script_content:·string` with `export·function·postponeScript(⏎··idsOfScriptsToAwait:·string[],⏎··script_content:·string⏎`


export function postponeScript(idsOfScriptsToAwait: string[], script_content: string) {
return `{const loaded = ${JSON.stringify(

Check failure on line 67 in src/postponeScript.ts

View workflow job for this annotation

GitHub Actions / build-test (21.x)

Delete `··`
Object.fromEntries(idsOfScriptsToAwait.map(id => [id, false]))

Check failure on line 68 in src/postponeScript.ts

View workflow job for this annotation

GitHub Actions / build-test (21.x)

Delete `····`
)};

Check failure on line 69 in src/postponeScript.ts

View workflow job for this annotation

GitHub Actions / build-test (21.x)

Delete `··`
let called = false;
const call_if_ready = () => {
if( !called && Object.values(loaded).every(e => e) ) {
{${script_content}};
called = true
}
};
${idsOfScriptsToAwait.map(
id => `document.addEventListener(
"loaded-${id}",
()=>{ loaded["${id}"] = true;
call_if_ready()
}).join(";")`
)}}`;

Check failure on line 85 in src/postponeScript.ts

View workflow job for this annotation

GitHub Actions / build-test (21.x)

Replace `)}}`;` with `······)}}``
}
2 changes: 2 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({})

0 comments on commit b158a24

Please # to comment.