解决动态 import 类型 Any 痼癖

特定条件才执行的逻辑,或为了启动性能需要用到动态 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与编程艺术"

相关推荐
Dumbness1 小时前
后台管理员把自己锁在门外:RBAC 里「自己的权限只能由别人来减」
安全·node.js
ZoeLandia2 小时前
React03:useState 与不可变更新
前端·react.js·typescript·react
攻城狮白玉3 小时前
Node.js 发布紧急安全更新:10 个漏洞,8 枚危险证书被拉黑。附AI修复提示词
node.js
console.log('npc')6 小时前
从0到1实现任务评价统计功能:React + TypeScript 实战指南(小白注释版)
前端·react.js·typescript
ZoeLandia6 小时前
React04:事件处理与受控表单
开发语言·前端·react.js·typescript
ZoeLandia7 小时前
React05:useEffect 与副作用
开发语言·前端·react.js·typescript
Json____18 小时前
用 FastAPI + Vue3 打造一套完整的在线考试系统:从组卷到阅卷的业务闭环实践
后端·node.js·fastapi·it学习·wwwoop.com
Flynt20 小时前
Cloudflare 用 Rust 重写了 TypeScript:我拿同一批代码跑了跑,报错一模一样,退出码不一样
rust·typescript·前端工程化
落花看风雪20 小时前
从零开始搭建后台系统(vue3+TypeScript+Vite)01
typescript·vue·js