<script>直接引入模块化的js文件踩坑记录
场景描述
需求的起因很简单,想写一个es6模块化验证demo,在index.html中通过<script>标签引入main.js,在main.js中,通过import的方式引入写好的func1模块,并请求,拿到返回值后显示在页面上。
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>引入js</title>
</head>
<body>
<script src="./main.js" type="text/javascript"></script>
</body>
</html>
// main.js
import {func1} from "./func1";
window.onload = function() {
func1();
}
// func1.js
export function func1() {
document.write("hello world !");
}
看起来没啥问题,执行起来浏览器会报错“Uncaught SyntaxError: Cannot use import statement outside a module”。
翻译过来就是,无法在模块外使用import关键字。
那使用script标签的新属性,type="module",直接在引入的时候把main.js声明成一个module就可以?
// index.html
<body>
<script src="./main.js" type="module"></script>
</body>
结果报错跨域问题,如下:
"Access to script at 'file:///F:/xxxx/main.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https."
我们知道,可以通过script标签引入来自不同域的代码块,这也是jsonp跨域的原理,那么为什么type="module"时,就不支持跨域了呢?
简单来说,就是当给script标签设置属性type="module"时,会使用同源策略(请求头中可以找到这个配置:Sec-Fetch-Mode: cors),但是同源策略一般针对http/https协议,加载本地文件走的是file协议打开本地文件,Origin是null,因此报错。
知识点
为什么无法在模块外使用import关键字?
module模块是相对于script脚本存在的。脚本只有全局作用域,而模块有自己的独立作用域;脚本从上到下顺序执行,模块需要预加载才能确定执行顺序。因此,当浏览器以默认脚本方式加载了模块时,会报语法错误,本质上是因为浏览器对脚本和模块的支持方式不同,没法在支持脚本的模式里面识别import关键字。
存在的另一种情况是nodejs环境,nodejs环境,在es6的module标准化出来之前,是遵从CommonJS定义的,自然没办法解析import语句。但是现在新版本的Node环境已经逐步放开了对 ES Module的支持。
解决方案
方案一 将js模块打包成单一脚本文件引入
打包工具很多,以webpack举例。npm init进行webpack环境的初始化,项目中有了package.json,可以写个wepack.config.js配置一个项目出入口和打包后的目录,最后把打包好的js文件在index.html中以默认方式引入。
方案二 创建本地文件服务器
这个方法仍然以模块方式引入js,但是将file://xxx通过文件服务器映射成http://localhost:8080/xxx,然后就可以正常引入啦。
具体操作有很多种方式,比如在vscode中装一个liveserver插件,开启一个小的本地文件服务;比如安装gulp等等。