diff --git a/crates/lingui_macro/src/builder.rs b/crates/lingui_macro/src/builder.rs index 2e1855c1..183cfac8 100644 --- a/crates/lingui_macro/src/builder.rs +++ b/crates/lingui_macro/src/builder.rs @@ -1,10 +1,13 @@ use crate::ast_utils::{ get_jsx_attr, get_jsx_attr_value_as_string, is_jsx_elements_equal, omit_jsx_attrs, }; -use crate::options::LinguiOptions; +use crate::options::{JsxRuntime, LinguiOptions}; use crate::tokens::{CaseOrOffset, MsgArg, MsgToken}; use std::collections::HashSet; -use swc_core::{common::DUMMY_SP, ecma::ast::*}; +use swc_core::{ + common::DUMMY_SP, + ecma::{ast::*, utils::private_ident}, +}; fn is_numeric(s: &str) -> bool { !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()) @@ -240,6 +243,7 @@ impl<'a> MessageBuilder<'a> { }); } } else { + // todo: cloning the whole opening element just to compare duplicates later is wasteful self.elements_tracking.push((n.clone(), el.clone())); } @@ -259,19 +263,47 @@ impl<'a> MessageBuilder<'a> { } if !self.components.iter().any(|c| c.placeholder == name) { - // todo: it looks very dirty and bad to cloning this jsx values self.components.push(ValueWithPlaceholder { placeholder: name.clone(), - value: Box::new(Expr::JSXElement(Box::new(JSXElement { - opening: el, - closing: None, - children: vec![], - span: DUMMY_SP, - }))), + value: self.component_value(el), }); } } + /// Builds the value stored in `components` for a JSX element. + /// For Solid it is wrapped as `(_props) => `, + /// since the runtime renders it via `createComponent(element, { children })`. + fn component_value(&self, el: JSXOpeningElement) -> Box { + let element = |opening| { + Box::new(Expr::JSXElement(Box::new(JSXElement { + opening, + closing: None, + children: vec![], + span: DUMMY_SP, + }))) + }; + + match self.options.jsx_runtime { + JsxRuntime::React => element(el), + JsxRuntime::Solid => { + let props = private_ident!("_props"); + let mut opening = el; + opening + .attrs + .push(JSXAttrOrSpread::SpreadElement(SpreadElement { + dot3_token: DUMMY_SP, + expr: Box::new(Expr::Ident(props.clone())), + })); + + Box::new(Expr::Arrow(ArrowExpr { + params: vec![Pat::Ident(props.into())], + body: Box::new(ArrowFunctionBody::Expr(element(opening))), + ..Default::default() + })) + } + } + } + fn push_tag_closing(&mut self) { if let Some(name) = self.components_stack.pop() { self.push_msg(&format!("")); diff --git a/crates/lingui_macro/src/lib.rs b/crates/lingui_macro/src/lib.rs index c9198a0a..411df45c 100644 --- a/crates/lingui_macro/src/lib.rs +++ b/crates/lingui_macro/src/lib.rs @@ -588,8 +588,8 @@ where } pub use self::options::{ - DescriptorFields, LinguiJsOptions, LinguiOptions, MacroPackagesConfig, RuntimeModulesConfigMap, - RuntimeModulesConfigMapNormalized, + DescriptorFields, JsxRuntime, LinguiJsOptions, LinguiOptions, MacroPackagesConfig, + RuntimeModulesConfigMap, RuntimeModulesConfigMapNormalized, }; #[plugin_transform] diff --git a/crates/lingui_macro/src/options.rs b/crates/lingui_macro/src/options.rs index 687e0e2a..3dc3f120 100644 --- a/crates/lingui_macro/src/options.rs +++ b/crates/lingui_macro/src/options.rs @@ -29,6 +29,14 @@ impl DescriptorFields { } } +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy, Default)] +#[serde(rename_all = "lowercase")] +pub enum JsxRuntime { + #[default] + React, + Solid, +} + #[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Default)] #[serde(rename_all = "camelCase")] pub struct LinguiJsOptions { @@ -50,6 +58,8 @@ pub struct LinguiJsOptions { pub jsx_placeholder_attribute: Option, #[serde(default)] pub jsx_placeholder_defaults: Option>, + #[serde(default)] + pub jsx_runtime: Option, } #[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Default)] @@ -123,6 +133,7 @@ impl LinguiJsOptions { id_prefix_leader: self.id_prefix_leader.clone(), jsx_placeholder_attribute: self.jsx_placeholder_attribute.clone(), jsx_placeholder_defaults: self.jsx_placeholder_defaults.clone(), + jsx_runtime: self.jsx_runtime.unwrap_or_default(), macro_packages: MacroPackagesConfig { core: self .core_package @@ -184,6 +195,8 @@ pub struct LinguiOptions { #[serde(skip_serializing_if = "is_default")] pub jsx_placeholder_defaults: Option>, #[serde(skip_serializing_if = "is_default")] + pub jsx_runtime: JsxRuntime, + #[serde(skip_serializing_if = "is_default")] pub macro_packages: MacroPackagesConfig, #[serde(skip_serializing_if = "is_default")] pub runtime_modules: RuntimeModulesConfigMapNormalized, @@ -202,6 +215,7 @@ impl Default for LinguiOptions { id_prefix_leader: None, jsx_placeholder_attribute: None, jsx_placeholder_defaults: None, + jsx_runtime: JsxRuntime::default(), macro_packages: Default::default(), runtime_modules: Default::default(), } @@ -249,6 +263,7 @@ mod lib_tests { id_prefix_leader: None, jsx_placeholder_attribute: None, jsx_placeholder_defaults: None, + jsx_runtime: None, use_jsdoc_i18n_comment: None, } ) @@ -281,6 +296,7 @@ mod lib_tests { id_prefix_leader: None, jsx_placeholder_attribute: None, jsx_placeholder_defaults: None, + jsx_runtime: None, use_jsdoc_i18n_comment: None, core_package: None, jsx_package: None, @@ -314,6 +330,7 @@ mod lib_tests { id_prefix_leader: None, jsx_placeholder_attribute: None, jsx_placeholder_defaults: None, + jsx_runtime: None, use_jsdoc_i18n_comment: None, } ) @@ -514,6 +531,40 @@ mod lib_tests { assert!(!options.use_lingui_v5_id_generation); } + #[test] + fn test_jsx_runtime_config() { + let config = + serde_json::from_str::(r#"{ "jsxRuntime": "solid" }"#).unwrap(); + assert_eq!(config.jsx_runtime, Some(JsxRuntime::Solid)); + assert_eq!( + config.into_options("development").jsx_runtime, + JsxRuntime::Solid + ); + + let config = + serde_json::from_str::(r#"{ "jsxRuntime": "react" }"#).unwrap(); + assert_eq!( + config.into_options("development").jsx_runtime, + JsxRuntime::React + ); + } + + #[test] + fn test_jsx_runtime_default() { + let config = serde_json::from_str::(r#"{}"#).unwrap(); + assert_eq!(config.jsx_runtime, None); + assert_eq!( + config.into_options("development").jsx_runtime, + JsxRuntime::React + ); + } + + #[test] + fn test_jsx_runtime_invalid() { + let result = serde_json::from_str::(r#"{ "jsxRuntime": "vue" }"#); + assert!(result.is_err()); + } + #[test] fn test_empty_config() { let config = serde_json::from_str::(r#"{}"#).unwrap(); diff --git a/crates/lingui_macro/tests/jsx_solid.rs b/crates/lingui_macro/tests/jsx_solid.rs new file mode 100644 index 00000000..f8d8ce72 --- /dev/null +++ b/crates/lingui_macro/tests/jsx_solid.rs @@ -0,0 +1,133 @@ +use lingui_macro::{JsxRuntime, LinguiOptions}; + +#[macro_use] +mod common; + +fn solid() -> LinguiOptions { + LinguiOptions { + jsx_runtime: JsxRuntime::Solid, + ..Default::default() + } +} + +to!( + solid_basic_component_with_attrs, + solid(), + r#" +import { Trans } from "@lingui/react/macro"; +Hello docs.; + "# +); + +to!( + solid_self_closing_element, + solid(), + r#" +import { Trans } from "@lingui/react/macro"; +Hello
world
; + "# +); + +to!( + solid_nested_elements_and_values, + solid(), + r#" +import { Trans } from "@lingui/react/macro"; + +

Hello {name}

+
; + "# +); + +to!( + solid_member_expression_tag, + solid(), + r#" +import { Trans } from "@lingui/react/macro"; +Click here; + "# +); + +to!( + solid_existing_spread_attrs, + solid(), + r#" +import { Trans } from "@lingui/react/macro"; +Go there; + "# +); + +to!( + solid_named_placeholder_reused, + LinguiOptions { + jsx_runtime: JsxRuntime::Solid, + jsx_placeholder_attribute: Some("_t".into()), + ..Default::default() + }, + r#" +import { Trans } from "@lingui/react/macro"; + + first and second +; + "# +); + +to!( + solid_plural_inside_trans, + solid(), + r##" +import { Trans, Plural } from "@lingui/react/macro"; + + Hello world, +; + "## +); + +to!( + solid_element_inside_icu_case, + solid(), + r#" +import { Trans, Plural } from "@lingui/react/macro"; + + one} other="many" /> +; + "# +); + +to!( + solid_does_not_affect_t_and_use_lingui, + solid(), + r#" +import { t } from "@lingui/core/macro"; +import { useLingui } from "@lingui/react/macro"; +const a = t`Hello ${name}`; +function C() { + const { t } = useLingui(); + return t`Hi ${name}`; +} + "# +); + +to!( + solid_props_name_collision, + solid(), + r#" +import { Trans } from "@lingui/react/macro"; +function C(_props) { + return Hello docs.; +} + "# +); + +to_panic!( + solid_distinct_elements_same_placeholder, + LinguiOptions { + jsx_runtime: JsxRuntime::Solid, + jsx_placeholder_attribute: Some("_t".into()), + ..Default::default() + }, + r#" +import { Trans } from "@lingui/react/macro"; +one and two; + "# +); diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_basic_component_with_attrs.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_basic_component_with_attrs.snap new file mode 100644 index 00000000..fc160cba --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_basic_component_with_attrs.snap @@ -0,0 +1,18 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_runtime: solid +--- +import { Trans } from "@lingui/react/macro"; +Hello docs.; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { Trans as Trans_ } from "@lingui/react"; + + }, + message: "Hello <0>docs." +}}/>; diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_distinct_elements_same_placeholder.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_distinct_elements_same_placeholder.snap new file mode 100644 index 00000000..bed0f6bf --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_distinct_elements_same_placeholder.snap @@ -0,0 +1,16 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_placeholder_attribute: _t + jsx_runtime: solid +--- +import { Trans } from "@lingui/react/macro"; +one and two; + +↓ ↓ ↓ ↓ ↓ ↓ + +error: Multiple distinct JSX elements with the same placeholder name (`link`). Differentiate them by adding/modifying the `_t` attribute (e.g. ``). + --> input.tsx:2:43 + | +2 | one and two; + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_does_not_affect_t_and_use_lingui.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_does_not_affect_t_and_use_lingui.snap new file mode 100644 index 00000000..f1c4f2ae --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_does_not_affect_t_and_use_lingui.snap @@ -0,0 +1,34 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_runtime: solid +--- +import { t } from "@lingui/core/macro"; +import { useLingui } from "@lingui/react/macro"; +const a = t`Hello ${name}`; +function C() { + const { t } = useLingui(); + return t`Hi ${name}`; +} + +↓ ↓ ↓ ↓ ↓ ↓ + +import { useLingui as $_useLingui } from "@lingui/react"; +import { i18n as $_i18n } from "@lingui/core"; +const a = $_i18n._(/*i18n*/ { + id: "OVaF9k", + message: "Hello {name}", + values: { + name: name + } +}); +function C() { + const { _: $__ } = $_useLingui(); + return $__(/*i18n*/ { + id: "Kxwgoy", + message: "Hi {name}", + values: { + name: name + } + }); +} diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_element_inside_icu_case.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_element_inside_icu_case.snap new file mode 100644 index 00000000..06096531 --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_element_inside_icu_case.snap @@ -0,0 +1,23 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_runtime: solid +--- +import { Trans, Plural } from "@lingui/react/macro"; + + one} other="many" /> +; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { Trans as Trans_ } from "@lingui/react"; + + }, + message: "{count, plural, one {<0>one} other {many}}" +}}/>; diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_existing_spread_attrs.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_existing_spread_attrs.snap new file mode 100644 index 00000000..3821fc46 --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_existing_spread_attrs.snap @@ -0,0 +1,18 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_runtime: solid +--- +import { Trans } from "@lingui/react/macro"; +Go there; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { Trans as Trans_ } from "@lingui/react"; + + }, + message: "Go <0>there" +}}/>; diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_member_expression_tag.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_member_expression_tag.snap new file mode 100644 index 00000000..7026c5e9 --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_member_expression_tag.snap @@ -0,0 +1,18 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_runtime: solid +--- +import { Trans } from "@lingui/react/macro"; +Click here; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { Trans as Trans_ } from "@lingui/react"; + + }, + message: "Click <0>here" +}}/>; diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_named_placeholder_reused.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_named_placeholder_reused.snap new file mode 100644 index 00000000..8f751a00 --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_named_placeholder_reused.snap @@ -0,0 +1,21 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_placeholder_attribute: _t + jsx_runtime: solid +--- +import { Trans } from "@lingui/react/macro"; + + first and second +; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { Trans as Trans_ } from "@lingui/react"; + + }, + message: "first and second" +}}/>; diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_nested_elements_and_values.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_nested_elements_and_values.snap new file mode 100644 index 00000000..e675435c --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_nested_elements_and_values.snap @@ -0,0 +1,25 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_runtime: solid +--- +import { Trans } from "@lingui/react/macro"; + +

Hello {name}

+; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { Trans as Trans_ } from "@lingui/react"; +

, + 1: (_props)=>, + 2: (_props)=> + }, + message: "<0>Hello <1><2>{name}" +}}/>; diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_plural_inside_trans.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_plural_inside_trans.snap new file mode 100644 index 00000000..d1a2ed63 --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_plural_inside_trans.snap @@ -0,0 +1,23 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_runtime: solid +--- +import { Trans, Plural } from "@lingui/react/macro"; + + Hello world, +; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { Trans as Trans_ } from "@lingui/react"; + + }, + message: "Hello <0>world, {count, plural, one {# item} other {# items}}" +}}/>; diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_props_name_collision.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_props_name_collision.snap new file mode 100644 index 00000000..fe88e052 --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_props_name_collision.snap @@ -0,0 +1,22 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_runtime: solid +--- +import { Trans } from "@lingui/react/macro"; +function C(_props) { + return Hello docs.; +} + +↓ ↓ ↓ ↓ ↓ ↓ + +import { Trans as Trans_ } from "@lingui/react"; +function C(_props) { + return + }, + message: "Hello <0>docs." + }}/>; +} diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_self_closing_element.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_self_closing_element.snap new file mode 100644 index 00000000..2103d6af --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_self_closing_element.snap @@ -0,0 +1,18 @@ +--- +source: crates/lingui_macro/tests/jsx_solid.rs +info: + jsx_runtime: solid +--- +import { Trans } from "@lingui/react/macro"; +Hello
world
; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { Trans as Trans_ } from "@lingui/react"; +
+ }, + message: "Hello<0/>world" +}}/>; diff --git a/packages/lingui-macro/README.md b/packages/lingui-macro/README.md index 9d876453..6d816dfb 100644 --- a/packages/lingui-macro/README.md +++ b/packages/lingui-macro/README.md @@ -165,6 +165,12 @@ Sets the JSX attribute name used to provide explicit placeholder names inside `< Defines default placeholder names for JSX tags when no explicit placeholder attribute is present. +### `jsxRuntime` + +Accepts `"react"` (default) or `"solid"`. With `"solid"`, every JSX element inside `` is emitted as `(_props) => `, which `@lingui/solid` renders via `createComponent`. `t`, `msg`, `defineMessage` and `useLingui` are not affected. + +Use `defineConfig` from `@lingui/solid/config`, which sets `jsxPackage`, `jsxRuntime` and the Solid runtime modules. See the [Lingui Solid tutorial](https://lingui.dev/tutorials/solid). + ### `runtimeModules` Overrides the runtime imports used by the plugin. Unlike [the Babel macro configuration](https://lingui.dev/ref/conf#runtimeconfigmodule), this option must be passed as an object. diff --git a/packages/lingui-macro/e2e/fixtures/lingui-options/solid.config.js b/packages/lingui-macro/e2e/fixtures/lingui-options/solid.config.js new file mode 100644 index 00000000..405497f3 --- /dev/null +++ b/packages/lingui-macro/e2e/fixtures/lingui-options/solid.config.js @@ -0,0 +1,12 @@ +import {defineConfig} from "@lingui/conf"; + +export default defineConfig({ + locales: ["en"], + sourceLocale: "en", + runtimeConfigModule: { + Trans: ["@lingui/solid", "Trans"], + }, + macro: { + jsxRuntime: "solid", + }, +}) diff --git a/packages/lingui-macro/e2e/options.test.ts b/packages/lingui-macro/e2e/options.test.ts index e5af144f..bdb83fa3 100644 --- a/packages/lingui-macro/e2e/options.test.ts +++ b/packages/lingui-macro/e2e/options.test.ts @@ -27,6 +27,7 @@ describe("linguiMacroSwcPlugin", () => { "jsxPlaceholderDefaults": { "a": "link", }, + "jsxRuntime": undefined, "runtimeModules": { "Trans": [ "@acme/react", @@ -67,6 +68,7 @@ describe("linguiMacroSwcPlugin", () => { "a": "anchor", "strong": "bold", }, + "jsxRuntime": undefined, "runtimeModules": { "Trans": [ "@custom/react", @@ -86,6 +88,12 @@ describe("linguiMacroSwcPlugin", () => { `) }) + it("forwards macro.jsxRuntime from the config", () => { + const [, options] = linguiMacroSwcPlugin({}, {configPath: resolve(fixturesDir, "solid.config.js")}) + + expect(options).toMatchObject({jsxRuntime: "solid"}) + }) + it("merges overrides over mapped config", () => { expect( linguiMacroSwcPlugin( @@ -115,6 +123,7 @@ describe("linguiMacroSwcPlugin", () => { "a": "anchor", "strong": "bold", }, + "jsxRuntime": undefined, "runtimeModules": { "Trans": [ "@override/react", @@ -171,6 +180,7 @@ describe('mapOptions', () => { "a": "anchor", "strong": "bold", }, + "jsxRuntime": undefined, "runtimeModules": { "Trans": [ "@custom/react", diff --git a/packages/lingui-macro/src-js/map-options.ts b/packages/lingui-macro/src-js/map-options.ts index 6cbe41f4..3b7394b0 100644 --- a/packages/lingui-macro/src-js/map-options.ts +++ b/packages/lingui-macro/src-js/map-options.ts @@ -12,6 +12,12 @@ export type LinguiMacroOptions = { jsxPlaceholderAttribute?: string /** Default placeholder names for JSX tags when no explicit placeholder attribute is present. */ jsxPlaceholderDefaults?: Record + /** + * Controls which JSX runtime semantics the Lingui JSX macro emits. + * - `"react"` (default) — `` components are emitted as plain JSX elements. + * - `"solid"` — every component is emitted as `(_props) => `, as required by `@lingui/solid`. + */ + jsxRuntime?: 'react' | 'solid' /** Overrides the runtime imports used by the plugin. Unlike the Babel macro configuration, must be passed as an object. */ runtimeModules?: { i18n?: RuntimeModuleConfig @@ -65,6 +71,8 @@ export function mapOptions(linguiConfig: LinguiConfigNormalized, overrides?: Dee jsxPlaceholderAttribute: linguiConfig.macro.jsxPlaceholderAttribute, jsxPlaceholderDefaults: linguiConfig.macro.jsxPlaceholderDefaults, idPrefixLeader: linguiConfig.macro.idPrefixLeader, + // older @lingui/conf versions lack the key in their types but pass the value through + jsxRuntime: (linguiConfig.macro as {jsxRuntime?: 'react' | 'solid'}).jsxRuntime, ...overrides, runtimeModules: { ...linguiConfig.runtimeConfigModule, diff --git a/packages/native-tools/README.md b/packages/native-tools/README.md index fc1da369..c5d1e065 100644 --- a/packages/native-tools/README.md +++ b/packages/native-tools/README.md @@ -139,6 +139,10 @@ The `macro` options are documented in the [`@lingui/swc-plugin` README](https:// The parser syntax (ECMAScript / TypeScript) and JSX support are inferred from the filename, and decorators are enabled. When you pass `parser`, it is used as-is (only `jsx` / `tsx` is still OR-ed in from the filename), so remember to set `decorators: true` yourself if you need them. +#### `jsxRuntime: "solid"` + +Set `macro.jsxRuntime` to `"solid"` to emit every `` component as `(_props) => `, as `@lingui/solid` requires (it renders them via `createComponent`). The default is `"react"`. Use `defineConfig` from `@lingui/solid/config`, which sets `jsxPackage`, `jsxRuntime` and the Solid runtime modules. See the [Lingui Solid tutorial](https://lingui.dev/tutorials/solid). + #### `descriptorFields: "auto"` When `macro.descriptorFields` is not set (or is `"auto"`), it resolves from `process.env.NODE_ENV`: `"id-only"` in `production`, `"all"` otherwise — the same behavior as the Babel macro plugin. Set it explicitly to make the output independent of the environment. diff --git a/packages/native-tools/__test__/transform.spec.ts b/packages/native-tools/__test__/transform.spec.ts index f2b844a0..0bb229aa 100644 --- a/packages/native-tools/__test__/transform.spec.ts +++ b/packages/native-tools/__test__/transform.spec.ts @@ -38,6 +38,54 @@ const App = () => Hello world; expect(result.map).toBeDefined() }) + test('transforms nested elements in Trans into components', async () => { + const code = ` +import { Trans } from '@lingui/react/macro'; +const App = () => Hello
docs.; +` + const result = await transform(code, 'app.tsx') + + expect(result.code).toMatchInlineSnapshot(` + "import { Trans as Trans_ } from "@lingui/react"; + const App = ()=> + }, + message: "Hello <0>docs." + }}/>; + " + `) + expect(result.map).toBeDefined() + }) + + test('emits Solid components with macro.jsxRuntime "solid"', async () => { + const code = ` +import { Trans } from '@lingui/solid/macro'; +const App = () => Hello docs.; +` + const result = await transform(code, 'app.tsx', { + macro: { + jsxRuntime: 'solid', + jsxPackage: ['@lingui/solid/macro'], + runtimeModules: {Trans: ['@lingui/solid', 'Trans']}, + }, + }) + + expect(result.code).toMatchInlineSnapshot(` + "import { Trans as Trans_ } from "@lingui/solid"; + const App = ()=> + }, + message: "Hello <0>docs." + }}/>; + " + `) + expect(result.map).toBeDefined() + }) + test('keeps non-lingui code unchanged', async () => { const code = ` import { useState } from 'react';