turbopack_json/
lib.rs

1//! JSON asset support for turbopack.
2//!
3//! JSON assets are parsed to ensure they contain valid JSON.
4//!
5//! When imported from ES modules, they produce a module that exports the
6//! JSON value as an object.
7
8#![feature(min_specialization)]
9#![feature(arbitrary_self_types)]
10#![feature(arbitrary_self_types_pointers)]
11
12use std::fmt::Write;
13
14use anyhow::{Error, Result, bail};
15use turbo_rcstr::RcStr;
16use turbo_tasks::{ResolvedVc, ValueToString, Vc};
17use turbo_tasks_fs::{FileContent, FileJsonContent, glob::Glob};
18use turbopack_core::{
19    asset::{Asset, AssetContent},
20    chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext},
21    ident::AssetIdent,
22    module::Module,
23    module_graph::ModuleGraph,
24    source::Source,
25};
26use turbopack_ecmascript::{
27    chunk::{
28        EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkPlaceable,
29        EcmascriptChunkType, EcmascriptExports,
30    },
31    runtime_functions::TURBOPACK_EXPORT_VALUE,
32};
33
34#[turbo_tasks::function]
35fn modifier() -> Vc<RcStr> {
36    Vc::cell("json".into())
37}
38
39#[turbo_tasks::value]
40pub struct JsonModuleAsset {
41    source: ResolvedVc<Box<dyn Source>>,
42}
43
44#[turbo_tasks::value_impl]
45impl JsonModuleAsset {
46    #[turbo_tasks::function]
47    pub fn new(source: ResolvedVc<Box<dyn Source>>) -> Vc<Self> {
48        Self::cell(JsonModuleAsset { source })
49    }
50}
51
52#[turbo_tasks::value_impl]
53impl Module for JsonModuleAsset {
54    #[turbo_tasks::function]
55    fn ident(&self) -> Vc<AssetIdent> {
56        self.source.ident().with_modifier(modifier())
57    }
58}
59
60#[turbo_tasks::value_impl]
61impl Asset for JsonModuleAsset {
62    #[turbo_tasks::function]
63    fn content(&self) -> Vc<AssetContent> {
64        self.source.content()
65    }
66}
67
68#[turbo_tasks::value_impl]
69impl ChunkableModule for JsonModuleAsset {
70    #[turbo_tasks::function]
71    fn as_chunk_item(
72        self: ResolvedVc<Self>,
73        _module_graph: Vc<ModuleGraph>,
74        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
75    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
76        Vc::upcast(JsonChunkItem::cell(JsonChunkItem {
77            module: self,
78            chunking_context,
79        }))
80    }
81}
82
83#[turbo_tasks::value_impl]
84impl EcmascriptChunkPlaceable for JsonModuleAsset {
85    #[turbo_tasks::function]
86    fn get_exports(&self) -> Vc<EcmascriptExports> {
87        EcmascriptExports::Value.cell()
88    }
89
90    #[turbo_tasks::function]
91    fn is_marked_as_side_effect_free(&self, _side_effect_free_packages: Vc<Glob>) -> Vc<bool> {
92        Vc::cell(true)
93    }
94}
95
96#[turbo_tasks::value]
97struct JsonChunkItem {
98    module: ResolvedVc<JsonModuleAsset>,
99    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
100}
101
102#[turbo_tasks::value_impl]
103impl ChunkItem for JsonChunkItem {
104    #[turbo_tasks::function]
105    fn asset_ident(&self) -> Vc<AssetIdent> {
106        self.module.ident()
107    }
108
109    #[turbo_tasks::function]
110    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
111        Vc::upcast(*self.chunking_context)
112    }
113
114    #[turbo_tasks::function]
115    async fn ty(&self) -> Result<Vc<Box<dyn ChunkType>>> {
116        Ok(Vc::upcast(
117            Vc::<EcmascriptChunkType>::default().resolve().await?,
118        ))
119    }
120
121    #[turbo_tasks::function]
122    fn module(&self) -> Vc<Box<dyn Module>> {
123        Vc::upcast(*self.module)
124    }
125}
126
127#[turbo_tasks::value_impl]
128impl EcmascriptChunkItem for JsonChunkItem {
129    #[turbo_tasks::function]
130    async fn content(&self) -> Result<Vc<EcmascriptChunkItemContent>> {
131        // We parse to JSON and then stringify again to ensure that the
132        // JSON is valid.
133        let content = self.module.content().file_content();
134        let data = content.parse_json().await?;
135        match &*data {
136            FileJsonContent::Content(data) => {
137                let js_str_content = serde_json::to_string(&data.to_string())?;
138                let inner_code = format!("{TURBOPACK_EXPORT_VALUE}(JSON.parse({js_str_content}));");
139
140                Ok(EcmascriptChunkItemContent {
141                    inner_code: inner_code.into(),
142                    ..Default::default()
143                }
144                .into())
145            }
146            FileJsonContent::Unparseable(e) => {
147                let mut message = "Unable to make a module from invalid JSON: ".to_string();
148                if let FileContent::Content(content) = &*content.await? {
149                    let text = content.content().to_str()?;
150                    e.write_with_content(&mut message, text.as_ref())?;
151                } else {
152                    write!(message, "{e}")?;
153                }
154
155                Err(Error::msg(message))
156            }
157            FileJsonContent::NotFound => {
158                bail!(
159                    "JSON file not found: {}",
160                    self.module.ident().to_string().await?
161                );
162            }
163        }
164    }
165}
166
167pub fn register() {
168    turbo_tasks::register();
169    turbo_tasks_fs::register();
170    turbopack_core::register();
171    turbopack_ecmascript::register();
172    include!(concat!(env!("OUT_DIR"), "/register.rs"));
173}