marko-js/writable-dom

Proposal about remove script blocking for better performance

kuitos opened this issue · 4 comments

kuitos commented

Description

Hi there👋
Thanks for your great works about such an elegant library, I have greatly benefited from it.

Recently I discovered a small issue while reading the source code, which is that script insertion is also blocking.

writable-dom/src/index.ts

Lines 156 to 162 in 020d50b

((node.tagName === "SCRIPT" &&
node.src &&
!(
node.noModule ||
node.type === "module" ||
node.hasAttribute("async") ||
node.hasAttribute("defer")

This behavior is somewhat inconsistent with the browser's behavior. When parsing a series of non-async scripts, the browser could automatically ensure that their execution follows the order of the DOM, without requiring us to manually control it through blocking. Manual control should only be necessary for CSS loading.

Why

Theoretically, this fix would be a certain improvement in performance.

Possible Implementation & Open Questions

Just removing the blocking check for scripts should be enough, unless there are some special scenarios that I am not aware of.

Is this something you're interested in working on?

Yes

Could you explain how you expect the execution to be? Blocking scripts need to be in executed In order after any previously rendered style sheets and scripts, which is what this implementation does. To my knowledge this works the same as browsers.

If you want scripts to execute eagerly you can use async, or module scripts.

kuitos commented

Blocking scripts need to be in executed In order after any previously rendered style sheets and scripts

Yes it is, but for blocking scripts browsers could ensure the executing order without any manual controlling.

Here are the examples that show the differences between them:

dynamic scripts load in browsers

  1. we have two external script where a.js downloads with 1s delay but b.js without ayn delay
// a.js with 1s delay while downloading
console.log('a');

// b.js
console.log('b');
  1. we simulate the behavior of the browser with parallel inserts
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<script>
  const scriptA = document.createElement('script')
  scriptA.src = '/a.js';
  scriptA.async = false;

  const scriptB = document.createElement('script')
  scriptB.src = '/b.js';
  scriptB.async = false;

  // parallel insert script to document
  document.body.appendChild(scriptA);
  document.body.appendChild(scriptB);

</script>
</body>
</html>
  1. we can find that the console logs a then b in orders although b.js downloaded first
    image
    image

We don't need to manual control the executing order between a.js and b.js, browsers could automatically guarantee their execution follows the order of DOM inserting, when the script element async attribute set as false.

In writable-dom implementation, these scripts work like that:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<script>
  const scriptA = document.createElement('script')
  scriptA.src = '/a.js';
  scriptA.async = false;
  
  // after a.js downloaded and executed then we insert script b.js to document
  scriptA.onload = () => {
      const scriptB = document.createElement('script')
      scriptB.src = '/b.js';
      scriptB.async = false;
      document.body.appendChild(scriptB);
  }

  document.body.appendChild(scriptA);
</script>
</body>
</html>

scriptB must wait until scriptA downloaded and executed then it could be inserted into document.

Here I made a codesandbox example to explain what I mean, pls take a look😃

Ah I think you're missing that writable dom is two phase. It inserts preloads while blocked for any down the line assets. So in your scenario, even when b is blocked by a, b is still downloaded as soon as possible (just not executed).

kuitos commented

I actually do know about the preload mechanism😄.

But just now, I realized I made a mistake. I confused dynamic script loading with the normal HTML parsing script flow. In dynamic script loading scenarios, async false can ensure both execution order and earliest possible insertion. But in the standard HTML document rendering flow, synchronous scripts will block further document parsing, which is consistent with the implementation of writable-dom.

Thank you for your patient explanation, I am closing this issue~