Skip to content

Commit 5a2e031

Browse files
feat: add SolidJS integration (#540) (#546)
* feat: add SolidJS integration Adds `@ox-content/vite-plugin-solid`, mirroring the Vue, React, and Svelte integrations, plus a `Solid` target for the renderer's framework codegen. Solid's JSX is compile-time only — there is no runtime element factory like React's `createElement` or Vue's `h` — so the two consumers need different output shapes: - The Vite plugin emits Solid JSX and lets `vite-plugin-solid` compile it. That is the same arrangement solid-mdx uses, and it keeps the environment-sensitive `generate: 'dom' | 'ssr'` decision with the plugin that owns it. - The Rust/N-API `renderFrameworkComponentCode` path has to produce code that runs without a compiler, so it targets `solid-js/h`, Solid's hyperscript entry point. The JSX approach costs two setup rules that the sibling integrations do not have: `oxContentSolid()` must precede `solid()` (both are `enforce: 'pre'`, so array order decides who sees the Markdown first), and `solid()` needs the Markdown extensions in its `extensions` option. Both mistakes otherwise fail as an opaque Babel syntax error, so `verifySolidPlugin` checks ordering when the config resolves and detects uncompiled output in a `post` transform, reporting each with the fix. Closes #540 * fix: bump brace-expansion to patched versions Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com> * ci: publish solid vite plugin preview via pkg-pr-new Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com> * fix: address Solid integration review feedback Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com> * test: cover @ox-content/vite-plugin-solid in the package dry-run The check validates that every packed tarball's `files`/`exports` resolve to files that actually ship. A newly published package that is not listed is exactly the case it exists to catch. --------- Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
1 parent bef2d8c commit 5a2e031

54 files changed

Lines changed: 2826 additions & 153 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/nightly.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ jobs:
303303
./npm/unplugin-ox-content \
304304
./npm/vite-plugin-ox-content \
305305
./npm/vite-plugin-ox-content-react \
306+
./npm/vite-plugin-ox-content-solid \
306307
./npm/vite-plugin-ox-content-svelte \
307308
./npm/vite-plugin-ox-content-vue
308309

.github/workflows/publish.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,18 @@ jobs:
398398
TARBALL=$(vp exec -- pnpm pack --pack-destination /tmp | tail -1)
399399
npm publish "$TARBALL" --provenance --access public --tag "$NPM_TAG"
400400
401+
- name: Publish @ox-content/vite-plugin-solid
402+
working-directory: npm/vite-plugin-ox-content-solid
403+
run: |
404+
PKG_NAME=$(node -p "require('./package.json').name")
405+
PKG_VERSION=$(node -p "require('./package.json').version")
406+
if npm view "${PKG_NAME}@${PKG_VERSION}" version >/dev/null 2>&1; then
407+
echo "Skipping ${PKG_NAME}@${PKG_VERSION} (already published)"
408+
exit 0
409+
fi
410+
TARBALL=$(vp exec -- pnpm pack --pack-destination /tmp | tail -1)
411+
npm publish "$TARBALL" --provenance --access public --tag "$NPM_TAG"
412+
401413
- name: Publish @ox-content/vite-plugin-svelte
402414
working-directory: npm/vite-plugin-ox-content-svelte
403415
run: |

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ npm install @ox-content/vite-plugin-react
139139

140140
# Svelte
141141
npm install @ox-content/vite-plugin-svelte
142+
143+
# Solid (vite-plugin-solid compiles the generated JSX, so it is required)
144+
npm install @ox-content/vite-plugin-solid solid-js vite-plugin-solid
142145
```
143146

144147
### i18n Static Checker (CLI)

crates/ox_content_napi/index.d.ts

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

crates/ox_content_napi/src/framework_codegen.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn props_to_map(value: serde_json::Value) -> FxHashMap<String, serde_json::Value
3636

3737
/// Renders already-produced Markdown HTML into framework-native component code.
3838
///
39-
/// The `target` argument accepts `"react"`, `"vue"`, and `"svelte"`.
39+
/// The `target` argument accepts `"react"`, `"solid"`, `"vue"`, and `"svelte"`.
4040
/// `mode` defaults to `"expression"` for backward compatibility.
4141
#[napi(js_name = "renderFrameworkComponentCode")]
4242
pub fn render_framework_component_code(

crates/ox_content_napi/src/tests/snapshots/ox_content_napi__tests__runtime_features__javascript_wrapper_declarations.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1841,7 +1841,7 @@ export declare function render(astJson: string): RenderResult
18411841
/**
18421842
* Renders already-produced Markdown HTML into framework-native component code.
18431843
*
1844-
* The `target` argument accepts `"react"`, `"vue"`, and `"svelte"`.
1844+
* The `target` argument accepts `"react"`, `"solid"`, `"vue"`, and `"svelte"`.
18451845
* `mode` defaults to `"expression"` for backward compatibility.
18461846
*/
18471847
export declare function renderFrameworkComponentCode(html: string, target: string, islands?: Array<JsFrameworkComponentIsland> | undefined | null, mode?: string | undefined | null): string

crates/ox_content_renderer/src/frameworks.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub struct FrameworkComponentIsland {
3030
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3131
pub enum FrameworkCodegenTarget {
3232
React,
33+
Solid,
3334
Svelte,
3435
Vue,
3536
}
@@ -38,6 +39,7 @@ impl FrameworkCodegenTarget {
3839
pub fn as_str(self) -> &'static str {
3940
match self {
4041
Self::React => "react",
42+
Self::Solid => "solid",
4143
Self::Svelte => "svelte",
4244
Self::Vue => "vue",
4345
}
@@ -56,6 +58,7 @@ impl FromStr for FrameworkCodegenTarget {
5658
fn from_str(value: &str) -> Result<Self, Self::Err> {
5759
match value {
5860
"react" => Ok(Self::React),
61+
"solid" | "solid-js" | "solidjs" => Ok(Self::Solid),
5962
"svelte" => Ok(Self::Svelte),
6063
"vue" => Ok(Self::Vue),
6164
_ => Err(FrameworkCodegenError::UnsupportedTarget { target: String::from(value) }),
@@ -179,9 +182,9 @@ fn render_framework_expression(
179182
islands: &[FrameworkComponentIsland],
180183
) -> Result<String, FrameworkCodegenError> {
181184
match target {
182-
FrameworkCodegenTarget::React | FrameworkCodegenTarget::Vue => {
183-
Ok(render_framework_component_code(html, target, islands))
184-
}
185+
FrameworkCodegenTarget::React
186+
| FrameworkCodegenTarget::Solid
187+
| FrameworkCodegenTarget::Vue => Ok(render_framework_component_code(html, target, islands)),
185188
FrameworkCodegenTarget::Svelte => Err(FrameworkCodegenError::UnsupportedModeForTarget {
186189
mode: FrameworkCodegenMode::Expression,
187190
target,
@@ -195,6 +198,7 @@ fn render_framework_render_function(
195198
) -> Result<String, FrameworkCodegenError> {
196199
match target {
197200
FrameworkCodegenTarget::React => Ok(react::render_function_module(expression)),
201+
FrameworkCodegenTarget::Solid => Ok(solid::render_function_module(expression)),
198202
FrameworkCodegenTarget::Vue => Ok(vue::render_function_module(expression)),
199203
FrameworkCodegenTarget::Svelte => Err(FrameworkCodegenError::UnsupportedModeForTarget {
200204
mode: FrameworkCodegenMode::RenderFunction,
@@ -213,6 +217,10 @@ fn render_framework_component(
213217
let expression = render_framework_component_code(html, target, islands);
214218
Ok(react::component_module(&expression))
215219
}
220+
FrameworkCodegenTarget::Solid => {
221+
let expression = render_framework_component_code(html, target, islands);
222+
Ok(solid::component_module(&expression))
223+
}
216224
FrameworkCodegenTarget::Vue => {
217225
let expression = render_framework_component_code(html, target, islands);
218226
Ok(vue::component_module(&expression))
@@ -224,6 +232,7 @@ fn render_framework_component(
224232
fn render_inner_html_component(html: &str, target: FrameworkCodegenTarget) -> String {
225233
match target {
226234
FrameworkCodegenTarget::React => react::inner_html_component_module(html),
235+
FrameworkCodegenTarget::Solid => solid::inner_html_component_module(html),
227236
FrameworkCodegenTarget::Svelte => svelte::inner_html_component_module(html),
228237
FrameworkCodegenTarget::Vue => vue::inner_html_component_module(html),
229238
}

crates/ox_content_renderer/src/frameworks/render.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{
22
parser::{HtmlElement, HtmlNode},
3-
react, shared, vue, FrameworkCodegenTarget, FrameworkComponentIsland,
3+
react, shared, solid, vue, FrameworkCodegenTarget, FrameworkComponentIsland,
44
};
55
use smallvec::SmallVec;
66

@@ -17,6 +17,7 @@ impl FrameworkCodegen<'_> {
1717

1818
match self.target {
1919
FrameworkCodegenTarget::React => react::render_root(&children),
20+
FrameworkCodegenTarget::Solid => solid::render_root(&children),
2021
FrameworkCodegenTarget::Svelte => {
2122
unreachable!("svelte component output does not use the VDOM renderer")
2223
}
@@ -51,6 +52,7 @@ impl FrameworkCodegen<'_> {
5152

5253
match self.target {
5354
FrameworkCodegenTarget::React => react::render_element(element, &children),
55+
FrameworkCodegenTarget::Solid => solid::render_element(element, &children),
5456
FrameworkCodegenTarget::Svelte => {
5557
unreachable!("svelte component output does not use the VDOM renderer")
5658
}
@@ -61,6 +63,7 @@ impl FrameworkCodegen<'_> {
6163
fn render_island(&self, island: &FrameworkComponentIsland) -> String {
6264
match self.target {
6365
FrameworkCodegenTarget::React => react::render_island(island),
66+
FrameworkCodegenTarget::Solid => solid::render_island(island),
6467
FrameworkCodegenTarget::Svelte => {
6568
unreachable!("svelte component output does not use the VDOM renderer")
6669
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: crates/ox_content_renderer/src/frameworks/tests.rs
3+
expression: "format!(\"single:\\n{single}\\n\\nmany:\\n{many}\")"
4+
---
5+
single:
6+
h('div', { class: 'ox-content' }, h("p", null, h("span", null, "one")))
7+
8+
many:
9+
h('div', { class: 'ox-content' }, h("p", null, h("span", null, "one"), h("span", null, "two")))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
source: crates/ox_content_renderer/src/frameworks/tests.rs
3+
expression: "format!(\"component:\\n{component}\\n\\nrender_function:\\n{render_function}\\n\\ninner_html:\\n{inner_html}\")"
4+
---
5+
component:
6+
import h from 'solid-js/h';
7+
8+
export default function MarkdownContent() {
9+
return h('div', { class: 'ox-content' }, h("p", null, "Hello"));
10+
}
11+
12+
13+
render_function:
14+
import h from 'solid-js/h';
15+
16+
export function renderMarkdownContent() {
17+
return h('div', { class: 'ox-content' }, h("p", null, "Hello"));
18+
}
19+
20+
21+
inner_html:
22+
import h from 'solid-js/h';
23+
24+
const rawHtml = "\x3Cp>Hello\x3C/p>";
25+
26+
export default function MarkdownContent() {
27+
return h('div', { class: 'ox-content', innerHTML: rawHtml });
28+
}

0 commit comments

Comments
 (0)