最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - DOMContentLoaded never fires inside a module - Stack Overflow

programmeradmin1浏览0评论

I'm confused regarding events not firing in a particular ES6 module loading scenario. Here is a Codepen project to support my examples:

Consider this very simple module app.js:

console.log('in module');
document.addEventListener('DOMContentLoaded', (event) => {
  console.log('DCL:2');
});

I'm using dynamic ES6 imports from script tags in the head of the document:

<script>
  (async () => {
    const module = await import("./js/app.js");
    console.log('async done');
   })();
</script>

So, this calling code is a non-module (sync script) dynamically loading a module. Because it is in the head of the document and not async or defer, I expect it to run immediately, and to do so in a blocking way.

That's my expectation, yet not what is happening. In particular, the problem is that the event in the module is never fired. And it looks to be because it's too late to listen to it, the event has already happened. How can this be, given the blocking script in the head of the document? It looks to be non-blocking, yet I don't understand why.

To be clear, the discussion isn't whether this is a good idea or not, I'm above all interested in the conceptual answer of why this doesn't work. Or to rephrase the question: how can a non-module script dynamically load a module in a synchronous way?

I'm confused regarding events not firing in a particular ES6 module loading scenario. Here is a Codepen project to support my examples: https://codepen.io/fchristant/project/editor/AYQkGJ

Consider this very simple module app.js:

console.log('in module');
document.addEventListener('DOMContentLoaded', (event) => {
  console.log('DCL:2');
});

I'm using dynamic ES6 imports from script tags in the head of the document:

<script>
  (async () => {
    const module = await import("./js/app.js");
    console.log('async done');
   })();
</script>

So, this calling code is a non-module (sync script) dynamically loading a module. Because it is in the head of the document and not async or defer, I expect it to run immediately, and to do so in a blocking way.

That's my expectation, yet not what is happening. In particular, the problem is that the event in the module is never fired. And it looks to be because it's too late to listen to it, the event has already happened. How can this be, given the blocking script in the head of the document? It looks to be non-blocking, yet I don't understand why.

To be clear, the discussion isn't whether this is a good idea or not, I'm above all interested in the conceptual answer of why this doesn't work. Or to rephrase the question: how can a non-module script dynamically load a module in a synchronous way?

Share Improve this question asked May 5, 2020 at 11:38 FerFer 4,19618 gold badges62 silver badges105 bronze badges 6
  • 2 If you want to load the script in a synchronous way, then you should just add it to the document in a <script> tag. The advantage of modules is that you can use different functions from other scripts in a single script by importing them and calling them there. In your case, your file is downloaded, but there is no exported function in your app.js script, so there is nothing to call or to use. – Emiel Zuurbier Commented May 5, 2020 at 11:46
  • What should I export then? The code inside the module clearly is running, as "in module" is printed. Yet by the time it runs, domcontentloaded has already happened. I still don't have an answer as to why this is. – Fer Commented May 5, 2020 at 11:52
  • 1 Importing your module asynchronously tells the browser that it should not wait for the module before emitting DOMContentLoaded event. This is almost the same as adding the async attribute to a SCRIPT tag. If you want your module to handle the DOMContentLoaded event - you should not use async importing. – IVO GELOV Commented May 5, 2020 at 11:57
  • async functions are non-blocking - so there is no "blocking script in the head" – Jaromanda X Commented May 5, 2020 at 11:58
  • You could put the domcontentloaded event listener in your main script and import your module when the event has occurred. – Emiel Zuurbier Commented May 5, 2020 at 11:59
 |  Show 1 more ment

3 Answers 3

Reset to default 6

Wait for the domcontentloaded event to happen in your index.html file. In the callback of the event import the module that you want to run whenever the event has fired.

// app.js
export function start() {
  console.log('in module');
  console.log('DCL:2');
}
<!-- index.html -->
<script>
  document.addEventListener('DOMContentLoaded', async (event) => {
    const module = await import("./js/app.js");
    module.start();
    console.log('async done');
  });
</script>

Or if you don't need any modules and you simply want everything to load one after another before the DOM has been rendered, just use the traditional render-blocking <script> tags.

<script src="./js/app.js"></script>
<script>
  console.log('module loaded');
</script>

You could use a helper function to check document.readyState and then either addEventListener or run code immediately.

  • onDCL1() can be used with a callback fn (A.1)
  • onDCL2() is Promise based
    • … traditional .then() chain (B.1)
    • … or await for it (C.1)

We can "fake" and dispatch our own DOMContentLoaded event and verify that else block works (A.2. , B.2. , C.2.)

class App {
  static onDCL1(callback) {
    if (document.readyState !== 'loading')
      callback(`onDCL1 ➜ readyState: '${document.readyState}'`);
    else
      document.addEventListener('DOMContentLoaded',
        e => callback(`onDCL1 ➜ ... waited readyState: '${document.readyState}'`));
  }

  static onDCL2() {
    return new Promise(resolve => {
      if (document.readyState !== 'loading')
        resolve(`onDCL2 ➜ readyState: '${document.readyState}'`);
      else
        document.addEventListener('DOMContentLoaded',
          e => resolve(`onDCL2 ➜ ... waited readyState: '${document.readyState}'`));
    });
  }
}

(async() => {
  console.log(`readyState: '${document.readyState}'`);

  // A.1
  App.onDCL1(payload => console.log(`A.1.| ${payload}`)); // run callback
  // B.1
  App.onDCL2().then(payload => console.log(`B.1.| ${payload}`)); // Promise chain
  // C.1
  let payload = await App.onDCL2(); // await  for Promise. Hint: no 'async' required, since we already return a Promise
  console.log(`C.1.| ${payload}`);


  // have to wait for C.1. (`await`) ....
  // ... ment 2 lines of "C.1" will show 'END' bevor Promise B.1. gets resolved async (~1ms)
  console.log('END 1');

  setTimeout(async() => {
    // dispatch "fake" event in 100ms, (we need to addEventListeners first
    // ... since C.2 has `await` we do this here
    setTimeout(() => {
      window.document.dispatchEvent(new Event("DOMContentLoaded", {
        bubbles: true,
        cancelable: true
      }));
    }, 100);

    // A.2
    App.onDCL1(payload => console.log(`A.2.| ${payload}`)); // run callback
    // B.2
    App.onDCL2().then(payload => console.log(`B.2.| ${payload}`)); // Promise chain
    // C.2
    let payload = await App.onDCL2(); // await  for Promise
    console.log(`C.2.| ${payload}`);

    console.log('END 2');
  }, 300);
})();

You could also use an interval to run the code once the module has loaded:

const interval = setInterval(() => {
    if(document.readyState!=="loading") {
        clearInterval(interval);
        ... your code here ...
    }
},250)
发布评论

评论列表(0)

  1. 暂无评论