
特定条件才执行的逻辑,或为了启动性能需要用到动态 import。
具体场景:动态 import 源码内资源或特定情形下执行某段代码。
动态导入虽好,但是会丢失类型,即类型被推倒为 any,容易引发线上问题。
本文提出一种简单方法,既保留性能又让其具备精确类型,完美解决。
从我经常使用动态 import 的两个场景入手。
/1/ 动态读取 package.json
CLI 应用开发中经常会有 -v 打印版本号需求,写死版本不灵活每次发版都得改,就可以用到动态 import:
ts
// temp.ts
import { pathToFileURL } from "node:url"
import { parseArgs } from "node:util"
const { values } = parseArgs({
options: {
version: { type: "boolean", default: false, short: "v" },
},
})
if (values.version) {
const pkgJSON = (
await import(new URL("./package.json", import.meta.url).href, {
// await import("./package.json", {
with: { type: "json" },
})
).default
console.log(pkgJSON.name, `v${pkgJSON.version}`)
}
输出:
ts
❯ node temp.ts -v
walking-log v1.1.3
为什么动态读取,因为只用用户输入 -v 才会打印当前包的版本号,如此脚本启动性能更快 🚀。
如果多次读取 json 可进一步封装成函数:
ts
// utils/lit-lodash.ts
export async function importJSON<T>(filepath: string): Promise<T> {
return (await import(filepath, { with: { type: "json" } })).default
}
dynamic import vs static import
动态读取的优点是切换目录执行依然可靠,但损失了类型推导。
注意到前面代码注释的 // await import("./package.json" 了吗?
开启这行注释切换到静态导入,TS 能自动推导 pkgJSON 类型为实际项目的 package.json,而非 any。
这是静态导入的优势。
能否二者皆得,让动态导入也有能有精确类型?
答案是可以的。
ts
const pkgJSON: typeof import("./package.json") = (
await import(new URL("./package.json", import.meta.url).href, {
with: { type: "json" },
})
).default
眼尖的同学会发现其实就加了个类型 typeof import("./package.json"):
diff
- const pkgJSON
+ const pkgJSON: typeof import("./package.json") = (
await import(new URL("./package.json", import.meta.url).href, {
with: { type: "json" },
})
).default
为什么说启动性能无损,因为加入的是类型,运行时会剥离(strip types)。
本文所有代码需运行在 Node.js v22.18.0+。
/2/ 动态动态 import 同目录模块
举一个我的另一个项目,新闻自动生成脚本。里面有这样的代码:
ts
import { main } from "./news-generator/index-cctv-news.ts"
await main()
如果对启动性能有追求可以改成动态 import:
js
const mod = await import(new URL("./news-generator/index-cctv-news.ts", import.meta.url).href)
console.log("mod:", mod)
// 特定条件下执行
await mod.main()
打印看看导出结构:
js
❯ node temp.ts
mod: [Module: null prototype] {
PREFIX: 'cctv-news',
deduplicateByTitle: [Function: deduplicateByTitle],
getMarkdownAbsoluteFilepath: [Function: getMarkdownAbsoluteFilepath],
main: [AsyncFunction: main]
}
执行没问题,能解析到模块内 export 出来的函数和变量。
mod 类型依然是 any,导致输入 mod.mai| 无法自动提示。
同样可以用上述手段解决:
ts
const mod: typeof import("./news-generator/index-cctv-news.ts") = await import(
new URL("./news-generator/index-cctv-news.ts", import.meta.url).href
)
大家能看懂吧。
DRY 强迫症患者:能否将多次出现的
"./news-generator/index-cctv-news.ts"用变量承载? 答案"不能"。将报错"SyntaxError ERR_INVALID_TYPESCRIPT_SYNTAX: literal in an import type should be string literal"
/总结/
通过给动态导入增加静态导入类型定义,解决动态导入类型缺失问题。
文章对你是否有启发,或有没有踩过类似的坑?评论区聊聊 👇。
本系列每 1 个观点都追溯到 1 行源码、1 段官方文档。
欢迎关注"JavaScript与编程艺术"