
最近在做一个纯前端的静态站点时发现每次加载 JSON 配置都要先fetch再res.json()代码虽然不复杂但写多了总觉得不够优雅。后来查资料发现浏览器已经原生支持 JSON 模块导入了也就是说我们可以像导入普通 ES Module 一样直接import一个.json文件不需要任何构建工具也不需要额外库。这个特性对前端开发来说确实方便了不少。这篇文章就来完整梳理一下浏览器原生 JSON 模块导入的来龙去脉包括核心语法、浏览器兼容性、完整可运行的示例项目以及实际开发中可能遇到的坑和排查思路。不管你是刚接触原生 JS 的新手还是在做工程化项目的进阶开发者都可以参考这篇文章。1. 背景与核心概念1.1 从“加载 JSON 需要几步”说起在浏览器原生 JSON 模块出现之前前端要读取一个 JSON 文件最常见的做法是使用fetch加JSON.parse也就是下面的流程用fetch发起 HTTP 请求获取 JSON 文件文本。用res.json()或者JSON.parse(text)把文本解析成 JavaScript 对象。在then或async/await回调里使用这个对象。这样写本身没有太大问题但在一些场景下会显得比较笨重配置文件比较多时每个文件都要写一遍fetch逻辑。代码里容易混入一堆异步回调。静态站点或演示项目里为了读一个 JSON 文件却要维护一套请求逻辑。原生 JSON 模块导入改变了这个局面。它允许你像下面这样直接导入一个 JSON 文件import data from ./data.json with { type: json }; console.log(data.name);浏览器会自动完成 JSON 文件的加载、解析和模块绑定你拿到手的就是一个可以直接使用的 JavaScript 对象。1.2 什么是 JSON 模块JSON 模块是浏览器原生 ES Module 体系的一种扩展。ES Module 本身就是浏览器原生支持的模块机制常见的模块类型是 JavaScript 文件。JSON 模块则允许将.json文件作为模块直接导入导入后得到的是 JSON 内容解析后的对象。它和普通 JavaScript 模块的主要区别在于对比项普通 JS 模块JSON 模块文件后缀.js.json导入语句import xxx from ./xxx.jsimport xxx from ./xxx.json with { type: json }内容形式JavaScript 代码JSON 文本是否需要额外处理不需要需要声明导入类型这里要注意的是JSON 模块的导入语句后面必须带上with { type: json }这是为了明确告诉浏览器这次导入的是一个 JSON 文件而不是一段 JavaScript 代码。1.3 JSON 模块解决了什么问题原生 JSON 模块导入最有价值的地方在于它把 JSON 文件的加载变成了一次“声明式”操作。你不需要关心底层是如何发请求、如何解析文本的只需要在导入语句里写清楚文件路径和类型剩下的交给浏览器。这个特性特别适合以下场景静态站点中的站点配置信息。前端演示页面里的模拟数据。本地工具类页面中的数据字典。不使用构建工具的原生 HTML JS 项目。对于使用 Vite、Webpack 的工程化项目来说它们很早之前就支持了 JSON 导入但在工程化项目的源码里import data from ./data.json这种写法是构建工具做了处理并不是浏览器原生支持。而原生 JSON 模块意味着你在纯浏览器环境下也可以直接这么写。2. 环境准备与浏览器兼容性2.1 浏览器兼容性说明JSON 模块导入属于比较新的浏览器特性目前并不是所有浏览器都支持。实际支持情况取决于浏览器对 Import Attributes 特性的实现。以我目前了解到的信息来看较新版本的 Chrome 和 Edge 已经支持。Firefox 和 Safari 的支持进度则需要根据你的实际浏览器版本进行验证。旧版本的浏览器会直接报语法错误。这里有一个非常重要的提醒大家在参考本文时不要假设自己使用的浏览器一定支持。建议先写一个最简单的示例在目标浏览器上跑一遍确认没问题再继续。如果你需要在不支持的浏览器中使用类似功能可以继续使用fetch方式或者借助构建工具来转换。2.2 本地开发环境要求因为涉及 ES Module 和浏览器原生特性本地开发时需要注意以下几点需要一个本地 HTTP 服务器不能直接双击打开index.html文件。因为浏览器的模块加载有 CORS 限制file://协议下直接加载模块会被浏览器拦截。可以使用 VS Code 的 Live Server 插件也可以使用 Node.js 的npx serve或者 Python 的python -m http.server 8080。JSON 文件的后缀必须是.json并且服务器返回的Content-Type应该是application/json。大多数本地静态服务器会自动处理。2.3 示例项目结构为了能直观地验证效果我准备了一个非常简单的项目结构json-module-demo/ ├── index.html ├── data.json └── main.js这个结构的核心思路是index.html通过script typemodule加载main.jsmain.js直接导入data.json然后把内容渲染到页面上。3. 原生 JSON 模块导入的核心语法3.1 静态导入语法静态导入是日常开发中最常用的方式写法如下import data from ./data.json with { type: json };这里的with { type: json }是核心所在。它表示这次导入附带了一个属性信息这个属性的名字是type值是字符串json。浏览器看到这个属性后就会用 JSON 模块的解析流程来处理目标文件而不是把它当作 JavaScript 来执行。这能避免一个潜在的安全问题如果某个.json文件里被写入了恶意脚本浏览器也不会把它当作代码执行。3.2 动态导入语法除了静态导入JSON 模块也支持动态导入。动态导入返回的是一个 Promise因此你可以根据条件来决定是否加载某个 JSON 文件。基本写法如下const data await import(./data.json, { with: { type: json } }); console.log(data.default);这里需要注意动态导入时返回的模块命名空间对象里default属性才是 JSON 解析后的对象。这和大多数 ES Module 的行为是一致的。3.3 断言的兼容写法在 JSON 模块的发展过程中早期的提案用的是assert关键字写法是// 早期写法可能已废弃 import data from ./data.json assert { type: json };后来标准演变为了with关键字也就是文章前面演示的写法。有些较老的浏览器可能还支持assert但在新版本中标准以with为主。实际开发时建议优先使用with写法同时留意你的目标浏览器支持的语法版本。由于这里存在一定差异最好的方法是先在浏览器控制台里跑一遍确认语法是否被支持。3.4 对比 fetch 方式为了让你更直观地理解 JSON 模块导入的便利性这里梳理一下两种方式的区别。使用fetch的方式async function loadConfig() { const response await fetch(./data.json); const data await response.json(); console.log(data.name); } loadConfig();使用 JSON 模块导入的方式import data from ./data.json with { type: json }; console.log(data.name);从代码量上看两种方式差别不算特别大但 JSON 模块导入有一个天然优势它属于静态分析的一部分。这意味着开发工具可以在代码运行前就知道项目依赖了哪些 JSON 文件进而做预检查。同时模块的加载时机由浏览器统一管理和页面里的其他模块更协调。不过fetch方式也有它的价值。比如你需要动态拼接 URL、需要从远程接口获取 JSON、需要携带自定义请求头时fetch更加灵活。所以这不是“谁替代谁”的关系而是按场景选择的问题。4. 完整实战案例在浏览器中导入 JSON 模块接下来我们从一个空目录开始搭建一个可以直接运行的示例。这个示例实现的功能很简单页面加载后通过原生 JSON 模块导入方式读取一个包含站点信息的 JSON 文件并把信息展示在页面上。4.1 创建项目结构在桌面或工作目录下新建一个文件夹命名为json-module-demo然后在里面创建三个文件json-module-demo/ ├── index.html ├── data.json └── main.js4.2 编写 HTML 入口文件index.html是整个示例的入口。它的任务有两个提供一个用于展示数据的容器以及通过script typemodule加载main.js。文件内容如下!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title浏览器原生 JSON 模块导入示例/title style body { font-family: Arial, sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; line-height: 1.6; } .card { border: 1px solid #ddd; border-radius: 8px; padding: 16px; margin-bottom: 16px; } /style /head body h1浏览器原生 JSON 模块导入/h1 div idapp正在加载 JSON 数据.../div script typemodule src./main.js/script /body /html这里要特别说明两点。第一script标签必须带上typemodule浏览器才会把内部的代码或者通过src引入的文件当作 ES Module 来处理JSON 模块导入也只有在模块环境下才能生效。第二页面加载是按顺序执行的。由于script typemodule默认是异步加载且会等待依赖加载完成所以main.js导入 JSON 模块后页面初始化时不会出现数据缺失的问题。4.3 编写 JSON 数据文件data.json是我们的数据源。为了让示例看起来更真实我模拟了一个博客站点的基础配置信息{ siteName: 前端开发笔记, slogan: 记录前端学习与技术实践, author: TechBlogger, articleCount: 128, tags: [原生 JS, ES Module, 浏览器 API], isPublished: true }这里的数据包含了字符串、数字、数组、布尔值等常见 JSON 类型可以用来展示 JSON 模块解析后对数据类型的保真程度。4.4 编写核心 JavaScript 代码main.js是我们的核心逻辑文件。它从data.json导入数据然后把数据内容渲染到页面的#app容器里。// 文件路径json-module-demo/main.js import siteConfig from ./data.json with { type: json }; function renderSiteConfig(config) { const app document.getElementById(app); if (!app) { return; } const tagText config.tags.map(tag #${tag}).join( ); app.innerHTML div classcard h2${config.siteName}/h2 p${config.slogan}/p p作者${config.author}/p p文章数量${config.articleCount}/p p标签${tagText}/p p发布状态${config.isPublished ? 已发布 : 未发布}/p /div ; } renderSiteConfig(siteConfig);这段代码的核心逻辑如下第一行使用原生 JSON 模块导入语法把data.json的内容导入为siteConfig对象。renderSiteConfig函数接收这个对象并把它渲染到页面中的#app容器里。渲染时对数组tags做了map处理让它以#tag的形式展示。布尔值isPublished通过三元表达式转换成更友好的文案。如果你在浏览器中打开这个页面应该会看到一张类似博客介绍卡片的内容。4.5 启动本地服务器并验证效果直接在文件管理器里双击index.html通常是不行的因为浏览器为了安全会限制模块在file://协议下的加载。我们需要启动一个本地静态服务器。这里推荐几种启动方式选择你习惯的即可。方式一使用 VS Code 的 Live Server 插件如果你用 VS Code可以安装 Live Server 插件然后在index.html上右键选择 “Open with Live Server”。方式二使用 Node.js 全局服务如果你安装了 Node.js可以在命令行里执行npx serve .然后浏览器访问命令行提示的地址通常是http://localhost:3000。方式三使用 Python 自带 HTTP 服务如果你安装了 Python可以在项目目录下执行python -m http.server 8080然后访问http://localhost:8080。启动服务器后在浏览器地址栏输入对应的地址如果一切正常页面上会显示 JSON 数据渲染出来的卡片并且不会出现任何请求报错。如果页面显示“正在加载 JSON 数据...”没有变化说明模块加载可能出了问题。这时可以按 F12 打开开发者工具查看 Console 面板中的报错信息。4.6 页面效果说明当模块加载成功时你会在页面上看到类似下面的内容浏览器原生 JSON 模块导入 前端开发笔记 记录前端学习与技术实践 作者TechBlogger 文章数量128 标签#原生 JS #ES Module #浏览器 API 发布状态已发布这说明data.json已经被浏览器原生解析并且数据被正常渲染。5. 常见问题与排查思路在实际使用过程中你可能会遇到下面几种常见问题。这里整理成表格方便快速定位。问题现象常见原因解决思路浏览器报语法错误浏览器版本不支持 Import Attributes 语法确认浏览器版本改用 fetch 方式或升级浏览器报错 “Failed to load module script”使用了file://协议访问页面启动本地 HTTP 服务器访问报错 MIME 类型错误服务器的 JSON 响应头不是application/json检查静态服务器配置或换用支持的服务器动态 import 拿不到数据把data当成了对象本身其实它是模块命名空间使用data.default获取解析后的对象与构建工具冲突构建工具对 JSON 导入的处理方式和原生不同确认构建工具版本和配置按工具文档处理下面逐个展开说明。5.1 浏览器不支持with { type: json }写法如果你的浏览器版本较旧或者某些浏览器还没有实现 Import Attributes控制台会出现语法错误。这种情况下建议先检查浏览器版本再决定是升级浏览器还是改用fetch方式。最简单稳妥的降级方案就是使用fetchasync function loadConfig() { const response await fetch(./data.json); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } return await response.json(); } loadConfig().then(config { console.log(config.siteName); });这种方式兼容性最好所有现代浏览器都支持。5.2 本地打开页面时报模块加载错误如果你直接双击index.html浏览器控制台通常会报类似下面的错误Access to script at file:///... from origin null has been blocked by CORS policy这是因为浏览器对 ES Module 有 CORS 限制。解决方法是使用本地 HTTP 服务器访问页面具体方式见前面的启动服务器步骤。5.3 动态导入时数据为空动态导入 JSON 模块时很多人容易把返回值直接当作数据对象实际它返回的是一个模块命名空间对象。正确写法是const mod await import(./data.json, { with: { type: json } }); console.log(mod.default);如果你写成了const mod await import(./data.json, { with: { type: json } }); console.log(mod); // 会看到一个模块对象而不是直接看到数据找到数据就需要访问mod.default这一点和静态导入的默认导出机制是一样的。5.4 在非模块脚本中使用导入JSON 模块导入只能出现在模块脚本中。如果你想在普通的script标签里使用导入语法浏览器会报错因为import语句在非模块环境中不合法。正确做法是确保你的脚本通过以下方式加载script typemodule src./main.js/script或者在内联脚本中声明script typemodule import data from ./data.json with { type: json }; console.log(data); /script5.5 与 Vite、Webpack 工程化项目的区别在 Vite 或 Webpack 项目中直接写import data from ./data.json通常也能工作这是构建工具帮你做了 JSON 解析。但在浏览器原生环境下如果省略with { type: json }浏览器可能无法识别该 JSON 文件应该如何解析。所以在写原生浏览器代码时必须带上完整的导入属性。如果你在构建工具项目里希望代码同时兼容浏览器原生特性和构建工具建议以构建工具的实际表现为主因为构建工具的处理逻辑不完全等同于浏览器。6. 最佳实践与工程建议6.1 合理选择 JSON 模块的适用场景JSON 模块很适合静态的、不经常变动的数据文件比如站点标题、描述、作者等基础配置。只需要在本地使用的数据字典。页面初始渲染时需要的固定数据。如果数据是动态的、需要频繁请求的或者依赖用户交互产生的那么应该使用fetch或其它数据请求库。JSON 模块的定位是“静态模块”不是“动态数据接口”。6.2 保持 JSON 文件的大小合理浏览器加载 JSON 模块时会把整个文件内容解析并保存在内存中。如果 JSON 文件非常大比如几百 MB那么直接导入会占用较多内存。这种场景下建议还是走fetch流式或分片处理或者在后端把数据拆分到不同接口里避免一次性加载过大文件。6.3 将常量数据改造成 JSON 模块在实际项目中很多人会在 JavaScript 文件里硬编码一些常量数组或配置对象比如export const carouselItems [ { title: 第一张图, url: /images/1.png }, { title: 第二张图, url: /images/2.png }, ];如果这些数据属于静态内容可以考虑把它们抽取到.json文件里然后通过原生 JSON 模块导入。这样数据和代码分离后续维护时只需要修改 JSON 文件不需要改动代码逻辑。6.4 注意 JSON 格式的严格性JSON 文件的格式非常严格以下几点很容易踩坑JSON 内容中不能有注释。字符串必须使用双引号不能使用单引号。最后一个属性后面不能有逗号。不支持undefined、NaN等 JavaScript 特殊值。如果你的 JSON 文件出现解析错误浏览器会在导入时报错需要仔细检查文件格式。6.5 处理导入失败的兜底逻辑虽然 JSON 模块是静态导入但浏览器仍然可能因为文件路径错误、文件不存在、MIME 类型错误等原因导致加载失败。在使用动态导入时建议加上异常处理let siteConfig; try { const mod await import(./data.json, { with: { type: json } }); siteConfig mod.default; } catch (error) { console.error(JSON 模块加载失败, error); siteConfig { siteName: 默认配置, slogan: 加载失败显示默认内容, }; }这样即使 JSON 文件加载失败页面也不会完全空白而是显示兜底配置。6.6 项目中使用时的兼容性策略如果你要在一个可能被多人访问的站点上使用 JSON 模块建议先确认大部分目标用户的浏览器版本支持该特性。如果无法确认可以采用渐进增强策略先尝试使用动态导入加异常处理如果语法不被支持浏览器会抛出语法错误这种情况下再降级到fetch方案。不过要注意语法错误发生在解析阶段try...catch不一定能捕获到静态导入的语法错误所以更稳妥的方式是优先使用fetch或者使用支持广泛的技术方案来构建站点。6.7 与常规构建工具配合的注意事项对于使用 Vite、Webpack、Rollup 这类构建工具的项目JSON 文件导入是早就支持的功能通常可以直接写import data from ./data.json;构建工具会自动把 JSON 转换为 JavaScript 模块。但在原生浏览器环境下必须带上with { type: json }。这里有一个现实问题如果你的代码格式是import data from ./data.json with { type: json };部分构建工具可能不认识这个语法或者需要对应版本的插件支持。所以如果要写跨环境的代码最好先在目标环境中验证一下。7. 总结与学习路线浏览器原生 JSON 模块导入是一个很实用的新特性它把“加载静态 JSON 数据”这个操作变成了 ES Module 体系的一部分。通过import data from ./data.json with { type: json }我们可以在纯浏览器环境中直接获取 JSON 内容不需要写fetch请求也不需要引入额外的库。本文从概念入手介绍了 JSON 模块是什么、解决了什么问题然后给出了完整的环境准备步骤和核心语法接着带大家从零搭建了一个可运行的示例项目最后整理了常见错误排查思路和工程实践建议。接下来你可以继续往这几个方向深入研究 ES Module 的整体体系包括静态导入、动态导入、导入映射和模块预加载。了解 Import Attributes 提案的后续进展看看未来还能导入哪些类型的资源。在实际项目中尝试用 JSON 模块优化静态配置文件感受声明式导入带来的代码简化。结合服务端渲染或构建工具研究如何在不同环境下安全使用 JSON 模块。如果你是在做演示项目、静态站点或实验性页面原生 JSON 模块导入绝对值得一试。如果本文对你有帮助可以收藏备用后续遇到相关问题时也方便随时查阅。