先记住这个答案
可以,但有条件。ECMAScript 规范允许 import() 的参数是任意表达式,变量、模板字符串、三元表达式都行,运行时求值后当作模块路径去加载。问题出在打包工具这一层——Webpack、Vite、Rollup 需要在编译期分析出 import() 可能加载哪些文件,好把它们打进对应的 chunk。如果路径是一个纯变量,打包工具完全无法推断,要么报警告、要么直接忽略。
- 规范层面
import()接受任意表达式 - 打包工具需要静态分析,纯变量路径无法打包
- 模板字符串留一部分固定前缀,打包工具才能确定范围
- 不经过打包的环境(Node.js 原生 ESM)没有这个限制
打包工具怎么处理部分动态的路径
在 Webpack 和 Vite 这类打包环境里,import() 的路径越"静态",打包工具越能精准地拆出 chunk。实际项目里最常见的做法是用模板字符串,保留固定的目录前缀,只把文件名部分留给变量。
// lang 是变量,但目录 ./locale/ 和后缀 .js 是固定的
const lang = getUserLang() // 'zh-CN'
// Webpack 能分析出:./locale/ 目录下所有 .js 文件都可能被加载
const messages = await import(`./locale/${lang}.js`)
// 纯变量路径,Webpack 无法分析,打包时会报 warning
const mod = await import(somePath) // 不推荐Webpack 看到 import(./locale/${lang}.js) 时,会把 ./locale/ 目录下所有匹配的 .js 文件都打进一个叫 context module 的包里。运行时根据变量值挑出对应的模块。这样做的代价是,即使用户只用中文,英文、日文等所有语言包也会被下载。
Webpack 的魔法注释可以缩小范围
Webpack 提供了几个特殊注释,写在 import() 的括号里,可以控制打包行为。
webpackInclude 过滤候选文件JavaScriptconst lang = getUserLang()
const messages = await import(
/* webpackInclude: /\/(zh-CN|en-US|ja-JP)\.js$/ */
/* webpackChunkName: "locale" */
`./locale/${lang}.js`
)
// 只有 zh-CN、en-US、ja-JP 三个文件会被打进 chunk
// 其余语言包不会出现在产物里webpackInclude 用正则过滤候选文件,webpackChunkName 给 chunk 起个名字方便调试。这比把整个目录打进去要好得多,尤其是目录下文件很多的时候。
Vite 用的是 import.meta.glob,思路类似但写法不同。import.meta.glob('./locale/*.js') 返回一个路径到加载函数的映射对象,需要手动调用对应的函数来触发加载。
不经过打包工具的环境没有限制
在 Node.js 原生 ESM 或者浏览器直接用 <script type="module"> 加载模块时,没有打包工具参与,import() 的路径就是普通的字符串,运行时求值后直接请求对应的 URL 或文件路径。这时用纯变量完全没问题。
比如在 Node.js 里写一个插件加载器,从配置文件读到插件路径,然后 import(pluginPath) 加载,不需要任何特殊写法。浏览器里也是一样,只要 URL 对得上、服务器返回正确的 MIME 类型,变量路径就能正常加载。
所以"能不能用变量"这个问题,答案取决于你的代码最终怎么运行。经过打包工具的项目要遵守打包工具的规则,不经过的就没有限制。
容易答错的地方
- 以为语法层面就不允许变量
- 有人看到 Webpack 报了 warning 就以为这是语言层面的限制。其实 ECMAScript 规范明确说
import()接受 AssignmentExpression,就是任何赋值表达式都行。限制来自打包工具的静态分析能力,不是语言本身。 - 以为模板字符串路径和字符串拼接效果一样
- 对打包工具来说效果确实类似,但对代码可读性和维护性差别很大。
import(`./locale/${lang}.js`)能让打包工具识别出目录结构,而import('./locale/' + lang + '.js')在某些打包工具版本里可能分析不出来。模板字符串是更推荐的写法。 - 把太大的目录交给动态
import() import(./components/${name}.js)如果./components/下有 200 个文件,打包工具会把 200 个文件全部打进 context chunk。这时应该用webpackInclude缩小范围,或者换一种代码组织方式。
面试官还会怎么问?
Vite 里怎么实现类似 Webpack 的动态 import()?
Vite 推荐用 import.meta.glob('./locale/*.js'),它返回一个对象,key 是文件路径,value 是返回 Promise 的加载函数。需要时调用对应的函数即可。好处是 Vite 能精确知道候选文件,每个文件单独打 chunk。
import() 的路径可以是绝对 URL 吗?
可以。浏览器里 import('https://cdn.example.com/lib.js') 会从 CDN 加载模块。Node.js 从 18.x 开始也支持 import('https://...')(需要开启实验性标志)。不过打包工具一般不会处理绝对 URL,它们会原样保留。
import() 路径写错了,报错信息和静态 import 一样吗?
不一样。静态 import 路径找不到时,引擎在模块加载阶段就报错,页面直接起不来。import() 路径错误时返回的 Promise reject,只有在 await 或 .catch 的时候才会暴露错误。如果没有捕获,就变成一个 unhandled rejection。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。