该错误本质是上下文错配:在非模块环境(如未加type="module"的script标签或file://协议下)使用import语法,浏览器按传统脚本解析导致语法被拒;正确做法是确保script标签声明type="module"、路径带./或/前缀及.js扩展名、通过本地服务器运行。

type="module 不是加个属性就让脚本能用 import,它直接切换了整个执行模型——作用域、路径解析、执行时机、错误行为全都不一样。
为什么写了 import 却报 SyntaxError: Cannot use import statement outside a module
这几乎一定是上下文错配:
- 在没写
type="module"的<script></script>里写了import,浏览器按传统脚本执行,语法直接被拒 - 写了
<script type="module">import('./a.js')</script>,但import()是动态导入,只能在模块上下文中调用——也就是说,这个<script></script>标签本身必须是type="module",否则报错 - 本地双击打开 HTML(
file://协议),Chrome/Firefox 会静默拒绝加载模块脚本,控制台只显示 CORS 错误或空错误,根本不会发请求
import 路径必须带 .js 扩展名和 ./ 或 / 前缀
ES 模块规范强制路径静态化,浏览器不做任何自动补全:
-
import { foo } from './utils.js'✅ 显式扩展名 + 相对路径前缀 -
import { foo } from './utils'❌ 报Failed to load module script -
import { foo } from 'lodash'❌ 裸导入不被允许,除非配了<script type="importmap"></script> -
import { foo } from 'utils.js'❌ 裸路径,浏览器无法判断是相对还是绝对,直接SyntaxError
路径基准永远是当前 HTML 文件位置,不是当前 JS 文件位置。
立即学习“前端免费学习笔记(深入)”;
模块脚本默认 defer,但和传统 defer 有本质区别
它不只是“延迟执行”,而是整套生命周期重定义:
- 下载与 HTML 解析并行,但执行一定在 DOM 构建完成之后(等价于监听
DOMContentLoaded) - 多个
type="module"脚本严格按 HTML 中出现顺序执行,哪怕它们是异步加载的——这是模块图决定的,不是靠 DOM 顺序模拟 - 每个模块顶层声明的
const、function都不会挂到window上,作用域完全隔离 -
document.currentScript在模块中始终是null,别依赖它
跨模块共享配置或初始化逻辑容易踩坑
常见错误:把初始化逻辑拆成两个 <script></script> 标签,一个普通 <script></script> 设全局配置,一个 type="module" 试图读取,结果 undefined。
- 如果必须共享数据,用
window显式挂载:window.APP_CONFIG = { apiBase: '/v1' },模块里读window.APP_CONFIG - 更推荐方式:把配置也做成模块,统一
import,避免隐式依赖 - 模块脚本不会阻塞 HTML 解析,所以不要指望它能读取尚未解析到的 DOM 元素,除非监听
DOMContentLoaded或用defer等效时机 - 开发时务必用本地服务器(如
python3 -m http.server或 Vite),别直接双击打开 HTML
最常被忽略的一点:模块加载含解析 / 实例化 / 执行三阶段,任意一环失败都会中断后续依赖;而传统脚本出错往往只是单文件挂掉。



















