打包完成后打开浏览器,控制台突然报出 Unexpected token export,而源码在本地开发环境运行得好好的。排查一圈,最终发现问题不在业务代码,而在 webpack 的 output.scriptType 配置上。这个配置项平时不显眼,但一旦设错,可能让整个应用在部分浏览器里直接停止执行。

output.scriptType 控制的是标签属性,不是输出格式
先厘清一个容易混淆的点:output.scriptType 不会改变 webpack 生成的 bundle 内部结构,它只影响 HTML 文件里 script 标签的 type 属性。也就是说,它决定浏览器用哪种模式去加载和执行已经打包好的 JavaScript 文件。真正控制 bundle 是否输出 ES Module 语法的是 output.module 或 experiments.outputModule 等配置。
在 webpack 5 中,output.scriptType 可接收 module 和 text/javascript 两个常见值。当配置为 module 时,html-webpack-plugin 会在注入产物时生成带 type=module 属性的脚本标签;当配置为 text/javascript 时,则生成传统脚本标签,或者直接省略 type 属性,因为浏览器默认就按 text/javascript 处理。
下面是一份简单配置,展示如何显式设置 scriptType:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
scriptType: 'module'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};
如果要输出普通的传统脚本,只需要把 scriptType 改成 text/javascript。注意这里的改动不会让 bundle 自动变成 CommonJS 或 IIFE 格式,产物内容仍然由入口和模块系统决定。
两种类型在浏览器里的执行差异
浏览器对 type=module 脚本和传统脚本的处理规则有很大不同。模块脚本默认具有 defer 行为,也就是下载脚本时不会阻塞 HTML 解析,等文档解析完成后再按照依赖顺序执行;传统脚本如果没有加 defer 或 async,会在下载完成后立即执行,并阻断后续 HTML 的解析。这个差异会影响首屏性能和脚本执行顺序。
模块脚本还强制开启严格模式,并且拥有独立作用域,顶层 this 指向 undefined;传统脚本在非严格模式下顶层 this 指向 window,顶层声明的变量会自动变成全局变量。假如你的业务代码或第三方库依赖顶层 this 指向 window,直接改成 module 加载就可能出现运行时错误。
此外,模块脚本受跨域策略约束,通过 script 标签加载时需要一个有效的 CORS 响应头;传统脚本则不受此限制。动态导入 import() 在模块脚本中可以自然使用,而在传统脚本中某些旧浏览器无法解析,需要额外的运行时 polyfill。webpack 在打包时会根据配置注入模块加载代码,如果 scriptType 和内部格式不匹配,就可能触发 Unexpected token 之类的语法错误。
正确配置 module 脚本输出
如果你确实希望浏览器以原生 ES Module 方式加载 bundle,不能只设置 output.scriptType,还需要开启 output.module。webpack 5 允许输出 ES Module 格式,但要求 experiments.outputModule 开启,或者使用 library.type 为 module。下面是完整配置:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.mjs',
scriptType: 'module',
module: true
},
experiments: {
outputModule: true
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};
这样生成的 HTML 中,脚本标签会类似下面这样:
<script defer src="bundle.mjs" type="module"></script>
而使用 text/javascript 时,生成的标签通常是:
<script src="bundle.js" type="text/javascript"></script>
可以看到,模块脚本中即使没有显式写 defer,浏览器也会把它当成 defer 处理。这是由 HTML 规范决定的,不是 webpack 自动添加的属性。理解这一点有助于排查脚本执行时机相关的问题。
兼容性与回退方案
选择哪种 scriptType,本质上取决于目标浏览器是否支持 ES Module。现代浏览器对 type=module 支持良好,但早期浏览器,如 IE 11 和部分旧版 Android WebView,完全不认识 type=module 属性,会直接跳过下载和执行,导致页面空白。如果你的用户群体还包含这些浏览器,就需要考虑回退方案。
常见的做法是同时输出两份产物:一份 ES Module 给现代浏览器,另一份传统脚本给旧浏览器。借助 nomodule 属性可以实现这个策略。现代浏览器会忽略 nomodule 脚本,旧浏览器不认识 type=module 因而不会下载模块脚本,但会执行 nomodule 的传统脚本。下面是一个 HTML 模板示例:
<script src="bundle.mjs" type="module"></script> <script src="bundle.legacy.js" nomodule></script>
webpack 本身不会自动生成这套双份构建,需要在配置中通过多入口、多配置数组或者使用专门的插件来产出 legacy 版本。通常现代版本可以用更小的体积和更高效的模块加载,旧版本则保留完整的 polyfill 和运行时。
如果暂时没有条件维护两份构建,而你又必须兼容旧浏览器,最稳妥的选择是继续使用 text/javascript。虽然失去了原生模块脚本的一些优化,但至少能保证脚本在所有目标浏览器中正常执行。用错误的 scriptType 强行加载不兼容的产物,比不使用该配置带来的风险更大。
总结与选择建议
简而言之,output.scriptType 不是用来改变 webpack 打包格式的开关,而是告诉浏览器用哪种模式解释生成的脚本。module 适合明确只面向现代浏览器、需要原生 ES Module 特性的项目;text/javascript 则更适合需要广泛兼容的传统应用。
在配置时,先确定你的 bundle 内部格式,再决定 scriptType。要使用 module,请同时开启 output.module 或确保产物本身是合法的 ES Module。可以通过浏览器开发者工具查看 Network 面板中脚本的 Type 列,或检查 HTML 源码中 script 标签的 type 属性,来确认配置是否真正生效。
最后,如果在升级 webpack 或调整构建配置后出现页面空白、脚本不执行或全局变量丢失,建议第一时间检查 scriptType 与产物格式是否匹配。这个细节虽然小,但往往能直接定位到问题根源。
Webpackoutput.scriptTypetext/javascript修改时间:2026-09-24 03:00:12