Skip to content

22.10 import()的浏览器实现代码可能有误 #638

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
xianshenglu opened this issue Apr 4, 2018 · 6 comments
Closed

22.10 import()的浏览器实现代码可能有误 #638

xianshenglu opened this issue Apr 4, 2018 · 6 comments

Comments

@xianshenglu
Copy link
Contributor

阮老师,
你好,我在第22章,Module 的语法,第10小节中,发现import()的浏览器实现代码可能有问题,截图如下:
image
代码:

function importModule(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
    script.type = "module";
    script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;

    script.onload = () => {
      resolve(window[tempGlobal]);
      delete window[tempGlobal];
      script.remove();
    };

    script.onerror = () => {
      reject(new Error("Failed to load module script with URL " + url));
      delete window[tempGlobal];
      script.remove();
    };

    document.documentElement.appendChild(script);
  });
}

我觉得有两点可能需要修改:

  1. 最后一行代码的用法我几乎没见过,觉得可能有问题,即 document.documentElement.appendChild(script);为什么不用 document.body.appendChild(script);

  2. 这里是重点:
    上述代码中,script.onloadscript.onerror永远不会被触发,因为script没有指定src,采用的是行内脚本,最终导致了返回的promise永远不会resolve,这就导致上面的代码是失效的,老师可以本地测试下;当然如果采用src就需要另外的文件了,似乎也不太方便;

如果把script.onloadscript.onerror去掉,直接resolve(window[tempGlobal])也是失败的,因为window[tempGlobal]undefined,所以猜测这行代码import * as m from "${url}";是异步执行的,只有等待import动作结束返回结果并把值赋给window[tempGlobal]时,才能resolve(window[tempGlobal]),但是我并没有找到方法可以检测到import动作完成,所以只能设个定时器检测window[tempGlobal],所以最终,我把代码改成下面这样:

function importModule(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    const tempGlobal =
      "__tempModuleLoadingVariable" +
      Math.random()
        .toString(32)
        .substring(2);
    script.type = "module";
    script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;

    // script.onload = () => {
    //   resolve(window[tempGlobal]);
    //   delete window[tempGlobal];
    //   script.remove();
    // };

    // script.onerror = () => {
    //   reject(new Error("Failed to load module script with URL " + url));
    //   delete window[tempGlobal];
    //   script.remove();
    // };

    // document.documentElement.appendChild(script);
    document.body.appendChild(script);
    let checkImportResult = function fn(
      interval = 10,
      timeout = 5000,
      timeConsumed = interval
    ) {
      interval = interval > 0 ? interval : 10;
      timeout = timeout > interval ? timeout : interval * 2;
      if (window[tempGlobal] === undefined) {
        if (timeConsumed > timeout) {
          script.remove();
          reject(new Error("Failed to load module script with URL " + url));
        } else {
          timeConsumed += interval;
          timer = setTimeout(fn, interval, interval, timeout, timeConsumed);
        }
      } else {
        resolve(window[tempGlobal]);
        delete window[tempGlobal];
        script.remove();
      }
    };
    let timer = setTimeout(checkImportResult, 0);
  });
}

这样的情况下,我本地测试时成功的,测试的代码:
importModule("./test2.js").then(exportObj => console.log(exportObj));
嗯,我说完了。

@hax
Copy link

hax commented Apr 4, 2018

不需要用setTimeout。只要把 textContent 换成 src = `data:text/javascript,${code}` ,不过注意url得写成绝对路径。另外一种方式是不用 onload,而直接在代码里回调一个全局钩子而不是写一个全局变量。

PS. 其实不建议书里放 importModule,对于理解并没有额外帮助。

@xianshenglu
Copy link
Contributor Author

function importModule(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    const tempGlobal =
      "__tempModuleLoadingVariable" +
      Math.random()
        .toString(32)
        .substring(2);
    script.type = "module";
    script.src = `data:text/javascript,import * as m from "${url}"; window.${tempGlobal} = m;`;

    script.onload = () => {
      resolve(window[tempGlobal]);
      delete window[tempGlobal];
      script.remove();
    };

    script.onerror = () => {
      reject(new Error("Failed to load module script with URL " + url));
      delete window[tempGlobal];
      script.remove();
    };

    document.body.appendChild(script);
  });
}

importModule("http://localhost:800/github/test/test/test2.js").then(exportObj =>
  console.log(exportObj)
);

@xianshenglu
Copy link
Contributor Author

xianshenglu commented Apr 4, 2018

第一种方式,测试可以;第二种方式,**另外一种方式是不用 onload,而直接在代码里回调一个全局钩子而不是写一个全局变量。**方便给下代码?

@hax
Copy link

hax commented Apr 4, 2018

@xianshenglu 就跟 jsonp 的实现原理类似。

@ruanyf
Copy link
Owner

ruanyf commented Apr 9, 2018

这段代码其实是提案给出的。https://github.com/tc39/proposal-dynamic-import#using-host-specific-mechanisms

我测了一下,onloadonerror确实不会触发,暂时找不出原因。

贺老师提出的全局钩子是可行的替代方法。

我把这一段删了。

@ruanyf ruanyf closed this as completed Apr 9, 2018
@hax
Copy link

hax commented Apr 11, 2018

@ruanyf 我去开了个issue:tc39/proposal-dynamic-import#61

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants