必须添加type="module"属性才能启用ES模块语法,否则浏览器将脚本视为传统模式,不支持import/export,直接报SyntaxError;且需通过HTTP服务运行、路径带扩展名、导出导入严格匹配。

因为普通 <script> 标签默认运行在“脚本(script)上下文”中,而 import 和 export 是 ES6 模块(module)语法,只在模块上下文中合法。
执行环境不匹配
浏览器把普通 <script> 当作传统脚本处理:变量提升、全局作用域、顺序执行。它不解析模块语法,遇到 import 就直接报 Uncaught SyntaxError: Cannot use import statement outside a module——这不是拼写或路径问题,是语法层不被识别。
- 普通 script:无模块语义,不支持
import/export,也不做静态依赖分析 - 模块 script:
type="module"告诉浏览器启用模块系统,此时才允许使用import/export,并启用严格模式、顶层this为undefined、自动 defer 加载等特性
模块与脚本的底层差异
JavaScript 引擎对两种源码类型有根本区分:
- 脚本(Script):可立即执行,变量默认挂到全局;没有导入导出机制
-
模块(Module):必须显式声明(
type="module"),有独立作用域,依赖关系静态可分析,且默认defer,不会污染全局
ES6 模块设计目标就是“编译时确定依赖”,而传统脚本是“运行时执行”,二者无法混用。
为什么不能加个 type="module" 就万事大吉?
加了 type="module" 是必要条件,但还不够:
- 跨域限制:本地双击打开 HTML(
file://协议)会触发 CORS,模块加载失败;必须通过 HTTP 服务(如 Live Server、Vite 预览、npx serve)提供资源 - 路径必须相对或绝对:模块路径需明确,不能省略扩展名(如
./utils.js),也不能用变量拼接(import('./' + name + '.js')会报错) - 导出/导入需严格对应:用
export default导出,就得用import X from;用export { foo },就得用import { foo },括号和写法不匹配也会报错
动态 import() 也不能随便写
即使在 type="module" 脚本里,import() 也必须由 JS 主动调用(比如在事件回调、函数体内),不能放在顶层——否则就退化成静态导入,失去“按需”意义。而且路径必须是字符串字面量,构建工具才能提前拆包。
- ❌ 错误:
<script type="module">import('./chart.js');</script>(顶层调用,等效静态导入) - ✅ 正确:
<script type="module" src="app.js"></script>,然后在app.js中写button.addEventListener('click', () => import('./chart.js'))

















