登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

JavaScript import attributes 怎样加载 JSON 模块

来源:17golang原创

时间:2026-10-21 18:14:08 406浏览 收藏

如果你是在原生浏览器模块或 Node ESM 中加载 JSON,最稳妥的写法是把类型写在导入属性里:import data from "./data.json" with { type: "json" };。浏览器还要求脚本本身是模块,并且 JSON 响应的 MIME 类型应为 application/json。Vite 这类构建工具则会在构建阶段接管 JSON 文件,所以它的写法和原生运行时不能直接互换。

记住一句话:原生环境用 with { type: "json" } 明确告诉运行时“这是 JSON”;Vite 项目里不写 with 也可能正常,是因为 JSON 已经被构建器转换进模块图。

一、先看触发信号:你到底在哪个环境加载 JSON

我排查这类问题时,第一步不是修改语法,而是确认代码最终由谁加载。直接挂在 HTML 的 中,属于浏览器原生模块;带 "type": "module" 的 Node 项目或 .mjs 文件,属于 Node ESM;经过 Vite 开发服务器和生产构建的源码,则由构建器先处理。

这三个环境都写着 import,但 JSON 的“类型检查发生在哪里”并不相同。原生浏览器按照响应的 MIME 类型识别模块,文件名后缀不能代替服务端返回头;Node ESM 也把 JSON 的 type 属性视为必需条件。Vite 的 JSON 章节则明确提供了直接导入和根字段命名导入,这属于它的构建能力。

因此,看到下面这些信号时先停一下:浏览器报“期望 JavaScript 模块却收到 application/json”,Node 报缺少 JSON 类型属性,或者 Vite 中同一份 JSON 可以直接写命名导入。它们不是同一个故障。

二、原生环境的最小写法:给 JSON 标明类型

在浏览器原生模块里,准备一个与脚本同源的 data.json:

{
  "name": "Shilpa",
  "theme": "light"
}

然后在模块脚本中使用默认导入。JSON 模块只有一个默认导出,不能把对象字段直接当成 ES 命名导出:

服务端还要把这个文件作为 application/json 返回。with 属性会让运行时按 JSON 资源发起请求并检查媒体类型;如果服务器误返回 text/javascript,导入应失败,而不是把 JSON 文件当成代码执行。

原生 JavaScript JSON 模块的脚本类型、MIME 与默认导出关系图
图1:原生 JavaScript 加载 JSON 时,模块脚本、with 属性、MIME 与默认导出的静态关系示意图,不是截图,也不是运行证据。

三、需要按需读取时,用动态 import 的第二个参数

配置只在某个页面分支真正打开时才需要,可以用动态导入。属性不写在路径字符串里,而是放到第二个参数的 with 对象中:

async function loadThemeConfig() {
  // 动态导入仍然要明确 JSON 类型
  const module = await import("./theme.json", {
    with: { type: "json" },
  });

  // JSON 模块的值位于 default,而不是任意命名导出
  return module.default;
}

loadThemeConfig().then((config) => {
  // 只在配置加载成功后使用主题字段
  document.documentElement.dataset.theme = config.name;
});

静态导入和动态导入都会被当前环境缓存。如果你要读取“每次请求都可能变化”的积分、新闻或后台配置,不要把动态 import() 当成实时接口;应使用 fetch(),再根据响应处理 JSON。动态导入的优势是延后加载模块,不是绕过缓存。

四、Vite 为什么可以省略 with:它接管了构建阶段

在 Vite 源码中,下面这种写法是官方文档展示的能力:

// Vite 在构建阶段把 JSON 转成模块
import config from "./config.json";

// Vite 还支持根字段命名导入,适合只使用少量字段
import { apiBase } from "./config.json";

console.log(config.mode, apiBase);

这并不表示浏览器原生 import 忽然允许省略属性。开发阶段由 Vite 解析文件、生成可供模块图使用的代码;生产构建后,浏览器看到的是构建产物和普通 JavaScript 模块。Vite 的命名导入也是构建器提供的便利,不能据此推断原生 JSON 模块拥有同样的命名导出。

我通常按下面的边界做选择:如果代码直接交给浏览器或 Node 加载,保留 with { type: "json" };如果代码只在 Vite 管理的源码图中,按项目现有约定使用直接导入;如果同一段源码既要被原生运行又要被 Vite 打包,则优先统一成默认导入,并把环境差异写进构建约定,而不是让调用方猜。

浏览器 Node 原生运行时与 Vite 构建器 JSON 导入边界对比图
图2:原生运行时与 Vite 构建器处理 JSON 导入的边界对比示意图,不是截图,也不是运行证据。

五、按错误信号处理:修语法、修 MIME,还是改用 fetch

浏览器提示模块类型不匹配:先检查 HTML 是否使用了 type="module",再检查 JSON 响应的 Content-Type 是否为 application/json,最后确认静态导入带有 with { type: "json" }。文件名写成 .json 不能替代这三项。

旧代码还在写 assert { type: "json" }:导入属性的前一版语法使用过 assert,当前文档以 with 为标准写法。不要把两种语法拼在一起;按目标运行时和构建链的当前文档统一替换。

Node 报 JSON 类型缺失:确认项目处于 ESM 模式,并把静态或动态导入都改成带 type: "json" 的形式。Node 的原生规则与 Vite 的源码转换规则不能互相证明。

数据会变化却读到旧值:模块导入有生命周期内的缓存语义。把实时数据改成 fetch(),在请求后调用 response.json();不要通过给 URL 随意追加查询参数来掩盖数据来源和缓存策略。

六、最后用一张表做值班式复盘

场景推荐写法优先检查
浏览器原生模块import data from "./data.json" with { type: "json" }module 脚本、MIME、同源/CORS
Node ESM静态或动态导入都带 type: "json"ESM 模式、属性语法、文件路径
Vite 源码按 Vite 规则直接导入 JSON构建器版本、命名导入是否只是构建能力
实时数据fetch() 后解析 JSON缓存、请求失败、响应类型

把“原生加载”和“构建时转换”分清后,with { type: "json" } 就不再是需要死记的特殊符号:它是原生模块的类型边界;而 Vite 的省略,是构建器替你完成了文件转换。

相关问题

JSON 模块能否直接写 import { name } from "./data.json"?在原生 JSON 模块语义里不要这样写,使用默认导出后再读取字段;Vite 的命名导入是构建器能力。

什么时候应该放弃 import,改用 fetch?当数据需要按请求刷新、需要处理 HTTP 状态码,或不应被模块缓存生命周期绑定时,使用 fetch() 更清楚。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>