script 标签与 ES6 模块


如果浏览器支持 ES6 模块(可参考〈ECMAScript modules in browsers〉),例如 Chrome 61 之后,可以支持 ES6 模块,那么可以将<script>type属性设定为"module",表示这是一个 ES6 模块,例如:

<script type="module" src="js/util.js"></script>

type设定为"module"时,<script>也会是defer的,因此指定的 .js 会以非同步方式下载,并在 DOM 树生成与其他非defer的 .js 执行完后才(依序)执行。

<script type="module"></script>之间也可以编写 JavaScript 代码,也才可以编写import语句,例如,如果有个 hello.js 作为 ES6 模块:

function hello(name) {
    return `Hello! ${name}!`;
}

export {hello};

可以编写底下的 HTML,必须注意的是,在浏览器中,模块的import from中,必须加上 .js 副文件名:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <script type="module">
        import {hello} from './hello.js';
        window.onload = function() {
            let welcome = document.getElementById('welcome');
            welcome.innerHTML = hello(prompt('Input your name'));
        };
    </script>
</head>
<body>
    <span id="welcome"></span>
</body>
</html>

若代码中有import语句时,浏览器以非同步方式下载模块,若有多个import语句,全部下载完成后再依import的顺序执行,然后才执行模块顶层的代码。

无论import几次,或者<script type="module" src="./hello.js"></script>多次,模块都只会被加载与执行一次。

type被设为"module"<script></script>中,可以使用type被设为"text/javascript"<script></script>中的程序相关定义:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <script type="text/javascript">
        function hello(name) {
            return `Hello! ${name}!`;
        }
    </script>
    <script type="module">
        window.onload = function() {
            let welcome = document.getElementById('welcome');
            welcome.innerHTML = hello(prompt('Input your name'));
        };
    </script>
</head>
<body>
    <span id="welcome"></span>
</body>
</html>

按此看执行结果

然而,反过来不行:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <script type="module">
        function hello(name) {
            return `Hello! ${name}!`;
        }
    </script>
    <script type="text/javascript">
        window.onload = function() {
            let welcome = document.getElementById('welcome');
            welcome.innerHTML = hello(prompt('Input your name')); // ReferenceError
        };
    </script>
</head>
<body>
    <span id="welcome"></span>
</body>
</html>

两个type"module"<script></script>,被视为两个独立的模块,因此也没办法直接取用定义:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <script type="module">
        function hello(name) {
            return `Hello! ${name}!`;
        }
    </script>
    <script type="module">
        window.onload = function() {
            let welcome = document.getElementById('welcome');
            welcome.innerHTML = hello(prompt('Input your name')); // ReferenceError 
        };
    </script>
</head>
<body>
    <span id="welcome"></span>
</body>
</html>

当然,如果可以使用type = "module",会有一个主要的模块,在该模块中使用import,如果真的有需求,必须从type = "text/javascript"<script></script>中使用 ES6 的模块的话,是有些 ES6 Module Loader 的 polyfill 程序库可以使用。

另一个方式是放弃使用 ES6 模块,一个可以处理type = "module"的浏览器,会忽略<script nomodule></script>,因此,当这么编写时:

<script type="module" src="js/util.js"></script>
<script nomodule src="js/util_fallback.js"></script>

支持 ES6 模块的浏览器会使用 util.js,而不支持的浏览器会使用 util_fallback.js。


展开阅读全文