Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0ad8217

Browse files
authored
feat(plugin/json): support stringify: 'auto' (#3103)
<!-- Thank you for contributing! --> ### Description Add support for `stringify: 'auto'`. <!-- Please insert your description here and provide especially info about the "what" this PR is solving -->
1 parent 979565e commit 0ad8217

7 files changed

Lines changed: 208 additions & 126 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/rolldown_binding/src/options/plugin/binding_builtin_plugin.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rolldown_plugin_alias::{Alias, AliasPlugin};
77
use rolldown_plugin_build_import_analysis::BuildImportAnalysisPlugin;
88
use rolldown_plugin_dynamic_import_vars::DynamicImportVarsPlugin;
99
use rolldown_plugin_import_glob::{ImportGlobPlugin, ImportGlobPluginConfig};
10-
use rolldown_plugin_json::JsonPlugin;
10+
use rolldown_plugin_json::{JsonPlugin, JsonPluginStringify};
1111
use rolldown_plugin_load_fallback::LoadFallbackPlugin;
1212
use rolldown_plugin_manifest::{ManifestPlugin, ManifestPluginConfig};
1313
use rolldown_plugin_module_preload_polyfill::ModulePreloadPolyfillPlugin;
@@ -88,14 +88,35 @@ pub struct BindingModulePreloadPolyfillPluginConfig {
8888
}
8989

9090
#[napi_derive::napi(object)]
91-
#[derive(Debug, Deserialize, Default)]
92-
#[serde(rename_all = "camelCase")]
91+
#[derive(Debug, Default)]
9392
pub struct BindingJsonPluginConfig {
94-
pub stringify: Option<bool>,
93+
pub stringify: Option<BindingJsonPluginStringify>,
9594
pub is_build: Option<bool>,
9695
pub named_exports: Option<bool>,
9796
}
9897

98+
#[derive(Debug)]
99+
#[napi(transparent)]
100+
pub struct BindingJsonPluginStringify(napi::Either<bool, String>);
101+
102+
impl TryFrom<BindingJsonPluginStringify> for JsonPluginStringify {
103+
type Error = napi::Error;
104+
105+
fn try_from(value: BindingJsonPluginStringify) -> Result<Self, Self::Error> {
106+
Ok(match value {
107+
BindingJsonPluginStringify(napi::Either::A(true)) => JsonPluginStringify::True,
108+
BindingJsonPluginStringify(napi::Either::A(false)) => JsonPluginStringify::False,
109+
BindingJsonPluginStringify(napi::Either::B(s)) if s == "auto" => JsonPluginStringify::Auto,
110+
BindingJsonPluginStringify(napi::Either::B(s)) => {
111+
return Err(napi::Error::new(
112+
napi::Status::InvalidArg,
113+
format!("Invalid stringify option: {s}"),
114+
))
115+
}
116+
})
117+
}
118+
}
119+
99120
#[napi_derive::napi(object, object_to_js = false)]
100121
#[derive(Debug, Deserialize, Default)]
101122
#[serde(rename_all = "camelCase")]
@@ -376,7 +397,7 @@ impl TryFrom<BindingBuiltinPlugin> for Arc<dyn Pluginable> {
376397
BindingJsonPluginConfig::default()
377398
};
378399
Arc::new(JsonPlugin {
379-
stringify: config.stringify.unwrap_or_default(),
400+
stringify: config.stringify.map(TryInto::try_into).transpose()?.unwrap_or_default(),
380401
is_build: config.is_build.unwrap_or_default(),
381402
named_exports: config.named_exports.unwrap_or_default(),
382403
})

crates/rolldown_plugin_json/Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ doctest = false
1515
workspace = true
1616

1717
[dependencies]
18-
memchr = { workspace = true }
19-
rolldown_common = { workspace = true }
20-
rolldown_plugin = { workspace = true }
21-
rolldown_utils = { workspace = true }
22-
serde_json = { workspace = true }
18+
memchr = { workspace = true }
19+
rolldown_common = { workspace = true }
20+
rolldown_plugin = { workspace = true }
21+
rolldown_sourcemap = { workspace = true }
22+
rolldown_utils = { workspace = true }
23+
serde_json = { workspace = true }

crates/rolldown_plugin_json/src/lib.rs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
use rolldown_common::ModuleType;
22
use rolldown_plugin::{HookTransformOutput, Plugin};
3+
use rolldown_sourcemap::SourceMap;
34
use serde_json::Value;
45
use std::borrow::Cow;
56

67
#[derive(Debug, Default)]
78
pub struct JsonPlugin {
8-
pub stringify: bool,
9+
pub stringify: JsonPluginStringify,
910
pub is_build: bool,
1011
pub named_exports: bool,
1112
}
1213

14+
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
15+
pub enum JsonPluginStringify {
16+
False,
17+
True,
18+
#[default]
19+
Auto,
20+
}
21+
1322
impl Plugin for JsonPlugin {
1423
fn name(&self) -> Cow<'static, str> {
1524
Cow::Borrowed("builtin:json")
@@ -25,27 +34,62 @@ impl Plugin for JsonPlugin {
2534
return Ok(None);
2635
}
2736
let code = strip_bom(args.code);
28-
let value = serde_json::from_str::<Value>(code)?;
29-
if self.stringify {
30-
let normalized_code = if self.is_build {
31-
let str = serde_json::to_string(&value)?;
32-
// TODO: perf: find better way than https://github.com/rolldown/vite/blob/3bf86e3f715c952a032b476b60c8c869e9c50f3f/packages/vite/src/node/plugins/json.ts#L55-L57
33-
let str = serde_json::to_string(&str)?;
34-
format!("export default /*#__PURE__*/ JSON.parse({str})")
35-
} else {
36-
format!("export default /*#__PURE__*/ JSON.parse({})", serde_json::to_string(code)?)
37-
};
38-
return Ok(Some(HookTransformOutput {
39-
code: Some(normalized_code),
40-
module_type: Some(ModuleType::Js),
41-
..Default::default()
42-
}));
37+
38+
if self.stringify != JsonPluginStringify::False {
39+
if self.named_exports && code.trim_start().starts_with('{') {
40+
let parsed = serde_json::from_str::<Value>(code)?;
41+
let parsed =
42+
parsed.as_object().expect("should be object because the value starts with `{`");
43+
44+
let mut code = String::new();
45+
let mut default_object_code = "{\n".to_owned();
46+
for (key, value) in parsed {
47+
if rolldown_utils::ecmascript::is_validate_assignee_identifier_name(key) {
48+
code += &format!("export const {key} = {};\n", &serialize_value(value)?);
49+
default_object_code += &format!(" {key},\n");
50+
} else {
51+
// TODO: escape key
52+
default_object_code += &format!(" \"{key}\": {},\n", &serialize_value(value)?);
53+
}
54+
}
55+
default_object_code += "}";
56+
57+
code += &format!("export default {default_object_code};\n");
58+
return Ok(Some(HookTransformOutput {
59+
code: Some(code),
60+
map: Some(SourceMap::default()),
61+
module_type: Some(ModuleType::Js),
62+
..Default::default()
63+
}));
64+
}
65+
66+
if self.stringify == JsonPluginStringify::True ||
67+
// use 10kB as a threshold for 'auto'
68+
// https://v8.dev/blog/cost-of-javascript-2019#:~:text=A%20good%20rule%20of%20thumb%20is%20to%20apply%20this%20technique%20for%20objects%20of%2010%20kB%20or%20larger
69+
code.len() > 10 * 1000
70+
{
71+
let normalized_code = if self.is_build {
72+
// TODO: perf: find better way than https://github.com/rolldown/vite/blob/3bf86e3f715c952a032b476b60c8c869e9c50f3f/packages/vite/src/node/plugins/json.ts#L55-L57
73+
let value = serde_json::from_str::<Value>(code)?;
74+
Cow::Owned(serde_json::to_string(&value)?)
75+
} else {
76+
Cow::Borrowed(code)
77+
};
78+
let normalized_code_string = serde_json::to_string(&normalized_code)?;
79+
return Ok(Some(HookTransformOutput {
80+
code: Some(format!("export default /*#__PURE__*/ JSON.parse({normalized_code_string})")),
81+
map: Some(SourceMap::default()),
82+
module_type: Some(ModuleType::Js),
83+
..Default::default()
84+
}));
85+
}
4386
}
4487

88+
let value = serde_json::from_str::<Value>(code)?;
4589
let output = to_esm(&value, self.named_exports);
46-
4790
Ok(Some(HookTransformOutput {
4891
code: Some(output),
92+
map: Some(SourceMap::default()),
4993
module_type: Some(ModuleType::Js),
5094
..Default::default()
5195
}))
@@ -99,6 +143,15 @@ fn is_special_query(ext: &str) -> bool {
99143
false
100144
}
101145

146+
fn serialize_value(value: &Value) -> Result<String, serde_json::Error> {
147+
let value_as_string = serde_json::to_string(value)?;
148+
if value.is_object() && !value.is_null() && value_as_string.len() > 10 * 1000 {
149+
Ok(format!("JSON.parse({})", serde_json::to_string(&value_as_string)?))
150+
} else {
151+
Ok(value_as_string)
152+
}
153+
}
154+
102155
fn to_esm(data: &Value, named_exports: bool) -> String {
103156
if !named_exports || !data.is_object() {
104157
return format!("export default {data};\n");
@@ -111,6 +164,7 @@ fn to_esm(data: &Value, named_exports: bool) -> String {
111164
default_export_rows.push(Cow::Borrowed(key));
112165
named_export_code += &format!("export const {key} = {value};\n");
113166
} else {
167+
// TODO: escape key
114168
default_export_rows.push(Cow::Owned(format!("\"{key}\": {value}",)));
115169
}
116170
}

packages/rolldown/src/binding.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,11 +344,14 @@ export interface BindingInputOptions {
344344
}
345345

346346
export interface BindingJsonPluginConfig {
347-
stringify?: boolean
347+
stringify?: BindingJsonPluginStringify
348348
isBuild?: boolean
349349
namedExports?: boolean
350350
}
351351

352+
export type BindingJsonPluginStringify =
353+
boolean | string
354+
352355
export interface BindingJsonSourcemap {
353356
file?: string
354357
mappings?: string

packages/rolldown/src/rolldown-binding.wasi-browser.js

Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -112,55 +112,56 @@ function __napi_rs_initialize_modules(__napiInstance) {
112112
__napiInstance.exports['__napi_register__BindingManifestPluginConfig_struct_53']?.()
113113
__napiInstance.exports['__napi_register__BindingModulePreloadPolyfillPluginConfig_struct_54']?.()
114114
__napiInstance.exports['__napi_register__BindingJsonPluginConfig_struct_55']?.()
115-
__napiInstance.exports['__napi_register__BindingTransformPluginConfig_struct_56']?.()
116-
__napiInstance.exports['__napi_register__BindingAliasPluginConfig_struct_57']?.()
117-
__napiInstance.exports['__napi_register__BindingAliasPluginAlias_struct_58']?.()
118-
__napiInstance.exports['__napi_register__BindingBuildImportAnalysisPluginConfig_struct_59']?.()
119-
__napiInstance.exports['__napi_register__BindingViteResolvePluginConfig_struct_60']?.()
120-
__napiInstance.exports['__napi_register__BindingViteResolvePluginResolveOptions_struct_61']?.()
121-
__napiInstance.exports['__napi_register__BindingReplacePluginConfig_struct_62']?.()
122-
__napiInstance.exports['__napi_register__BindingCallableBuiltinPlugin_struct_63']?.()
123-
__napiInstance.exports['__napi_register__BindingCallableBuiltinPlugin_impl_68']?.()
124-
__napiInstance.exports['__napi_register__BindingHookJsResolveIdOptions_struct_69']?.()
125-
__napiInstance.exports['__napi_register__BindingHookJsResolveIdOutput_struct_70']?.()
126-
__napiInstance.exports['__napi_register__BindingHookJsLoadOutput_struct_71']?.()
127-
__napiInstance.exports['__napi_register__BindingJsWatchChangeEvent_struct_72']?.()
128-
__napiInstance.exports['__napi_register__BindingPluginOrder_73']?.()
129-
__napiInstance.exports['__napi_register__BindingPluginHookMeta_struct_74']?.()
130-
__napiInstance.exports['__napi_register__ParallelJsPluginRegistry_struct_75']?.()
131-
__napiInstance.exports['__napi_register__ParallelJsPluginRegistry_impl_77']?.()
132-
__napiInstance.exports['__napi_register__register_plugins_78']?.()
133-
__napiInstance.exports['__napi_register__BindingLog_struct_79']?.()
134-
__napiInstance.exports['__napi_register__BindingLogLevel_80']?.()
135-
__napiInstance.exports['__napi_register__BindingModuleInfo_struct_81']?.()
136-
__napiInstance.exports['__napi_register__BindingModuleInfo_impl_83']?.()
137-
__napiInstance.exports['__napi_register__BindingNormalizedOptions_struct_84']?.()
138-
__napiInstance.exports['__napi_register__BindingNormalizedOptions_impl_114']?.()
139-
__napiInstance.exports['__napi_register__BindingOutputAsset_struct_115']?.()
140-
__napiInstance.exports['__napi_register__BindingOutputAsset_impl_122']?.()
141-
__napiInstance.exports['__napi_register__JsOutputAsset_struct_123']?.()
142-
__napiInstance.exports['__napi_register__BindingOutputChunk_struct_124']?.()
143-
__napiInstance.exports['__napi_register__BindingOutputChunk_impl_139']?.()
144-
__napiInstance.exports['__napi_register__JsOutputChunk_struct_140']?.()
145-
__napiInstance.exports['__napi_register__BindingOutputs_struct_141']?.()
146-
__napiInstance.exports['__napi_register__BindingOutputs_impl_145']?.()
147-
__napiInstance.exports['__napi_register__JsChangedOutputs_struct_146']?.()
148-
__napiInstance.exports['__napi_register__BindingError_struct_147']?.()
149-
__napiInstance.exports['__napi_register__PreRenderedChunk_struct_148']?.()
150-
__napiInstance.exports['__napi_register__RenderedChunk_struct_149']?.()
151-
__napiInstance.exports['__napi_register__BindingRenderedModule_struct_150']?.()
152-
__napiInstance.exports['__napi_register__BindingRenderedModule_impl_152']?.()
153-
__napiInstance.exports['__napi_register__AliasItem_struct_153']?.()
154-
__napiInstance.exports['__napi_register__ExtensionAliasItem_struct_154']?.()
155-
__napiInstance.exports['__napi_register__BindingSourcemap_struct_155']?.()
156-
__napiInstance.exports['__napi_register__BindingJsonSourcemap_struct_156']?.()
157-
__napiInstance.exports['__napi_register__BindingWatcherEvent_struct_157']?.()
158-
__napiInstance.exports['__napi_register__BindingWatcherEvent_impl_163']?.()
159-
__napiInstance.exports['__napi_register__BindingWatcherChangeData_struct_164']?.()
160-
__napiInstance.exports['__napi_register__BindingBundleEndEventData_struct_165']?.()
161-
__napiInstance.exports['__napi_register__BindingNotifyOption_struct_166']?.()
162-
__napiInstance.exports['__napi_register__BindingWatcher_struct_167']?.()
163-
__napiInstance.exports['__napi_register__BindingWatcher_impl_171']?.()
115+
__napiInstance.exports['__napi_register__BindingJsonPluginStringify_struct_56']?.()
116+
__napiInstance.exports['__napi_register__BindingTransformPluginConfig_struct_57']?.()
117+
__napiInstance.exports['__napi_register__BindingAliasPluginConfig_struct_58']?.()
118+
__napiInstance.exports['__napi_register__BindingAliasPluginAlias_struct_59']?.()
119+
__napiInstance.exports['__napi_register__BindingBuildImportAnalysisPluginConfig_struct_60']?.()
120+
__napiInstance.exports['__napi_register__BindingViteResolvePluginConfig_struct_61']?.()
121+
__napiInstance.exports['__napi_register__BindingViteResolvePluginResolveOptions_struct_62']?.()
122+
__napiInstance.exports['__napi_register__BindingReplacePluginConfig_struct_63']?.()
123+
__napiInstance.exports['__napi_register__BindingCallableBuiltinPlugin_struct_64']?.()
124+
__napiInstance.exports['__napi_register__BindingCallableBuiltinPlugin_impl_69']?.()
125+
__napiInstance.exports['__napi_register__BindingHookJsResolveIdOptions_struct_70']?.()
126+
__napiInstance.exports['__napi_register__BindingHookJsResolveIdOutput_struct_71']?.()
127+
__napiInstance.exports['__napi_register__BindingHookJsLoadOutput_struct_72']?.()
128+
__napiInstance.exports['__napi_register__BindingJsWatchChangeEvent_struct_73']?.()
129+
__napiInstance.exports['__napi_register__BindingPluginOrder_74']?.()
130+
__napiInstance.exports['__napi_register__BindingPluginHookMeta_struct_75']?.()
131+
__napiInstance.exports['__napi_register__ParallelJsPluginRegistry_struct_76']?.()
132+
__napiInstance.exports['__napi_register__ParallelJsPluginRegistry_impl_78']?.()
133+
__napiInstance.exports['__napi_register__register_plugins_79']?.()
134+
__napiInstance.exports['__napi_register__BindingLog_struct_80']?.()
135+
__napiInstance.exports['__napi_register__BindingLogLevel_81']?.()
136+
__napiInstance.exports['__napi_register__BindingModuleInfo_struct_82']?.()
137+
__napiInstance.exports['__napi_register__BindingModuleInfo_impl_84']?.()
138+
__napiInstance.exports['__napi_register__BindingNormalizedOptions_struct_85']?.()
139+
__napiInstance.exports['__napi_register__BindingNormalizedOptions_impl_115']?.()
140+
__napiInstance.exports['__napi_register__BindingOutputAsset_struct_116']?.()
141+
__napiInstance.exports['__napi_register__BindingOutputAsset_impl_123']?.()
142+
__napiInstance.exports['__napi_register__JsOutputAsset_struct_124']?.()
143+
__napiInstance.exports['__napi_register__BindingOutputChunk_struct_125']?.()
144+
__napiInstance.exports['__napi_register__BindingOutputChunk_impl_140']?.()
145+
__napiInstance.exports['__napi_register__JsOutputChunk_struct_141']?.()
146+
__napiInstance.exports['__napi_register__BindingOutputs_struct_142']?.()
147+
__napiInstance.exports['__napi_register__BindingOutputs_impl_146']?.()
148+
__napiInstance.exports['__napi_register__JsChangedOutputs_struct_147']?.()
149+
__napiInstance.exports['__napi_register__BindingError_struct_148']?.()
150+
__napiInstance.exports['__napi_register__PreRenderedChunk_struct_149']?.()
151+
__napiInstance.exports['__napi_register__RenderedChunk_struct_150']?.()
152+
__napiInstance.exports['__napi_register__BindingRenderedModule_struct_151']?.()
153+
__napiInstance.exports['__napi_register__BindingRenderedModule_impl_153']?.()
154+
__napiInstance.exports['__napi_register__AliasItem_struct_154']?.()
155+
__napiInstance.exports['__napi_register__ExtensionAliasItem_struct_155']?.()
156+
__napiInstance.exports['__napi_register__BindingSourcemap_struct_156']?.()
157+
__napiInstance.exports['__napi_register__BindingJsonSourcemap_struct_157']?.()
158+
__napiInstance.exports['__napi_register__BindingWatcherEvent_struct_158']?.()
159+
__napiInstance.exports['__napi_register__BindingWatcherEvent_impl_164']?.()
160+
__napiInstance.exports['__napi_register__BindingWatcherChangeData_struct_165']?.()
161+
__napiInstance.exports['__napi_register__BindingBundleEndEventData_struct_166']?.()
162+
__napiInstance.exports['__napi_register__BindingNotifyOption_struct_167']?.()
163+
__napiInstance.exports['__napi_register__BindingWatcher_struct_168']?.()
164+
__napiInstance.exports['__napi_register__BindingWatcher_impl_172']?.()
164165
}
165166
export const BindingBundleEndEventData = __napiModule.exports.BindingBundleEndEventData
166167
export const BindingCallableBuiltinPlugin = __napiModule.exports.BindingCallableBuiltinPlugin

0 commit comments

Comments
 (0)