@dom-expressions/jsx-compiler 0.50.0-next.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/index.js +226 -0
  4. package/package.json +56 -0
  5. package/types.d.ts +41 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018-2019 Ryan Carniato
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # @dom-expressions/jsx-compiler
2
+
3
+ Experimental AST-native JSX to DOM Expressions compiler implemented with Oxc.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @dom-expressions/jsx-compiler
9
+ ```
10
+
11
+ The package ships prebuilt native binaries as per-platform packages
12
+ (`@dom-expressions/jsx-compiler-darwin-arm64`, `-darwin-x64`, `-linux-x64-gnu`,
13
+ `-linux-arm64-gnu`, `-win32-x64-msvc`). Your package manager installs the one
14
+ matching your platform automatically through `optionalDependencies`. On other
15
+ platforms, build from source with `pnpm run build` inside
16
+ `packages/jsx-compiler` (requires a Rust toolchain).
17
+
18
+ ## Usage
19
+
20
+ This package exposes a compiler backend API. It is not a Vite, Rollup, or Babel
21
+ plugin by itself; integrations should call `transform()` once per source module.
22
+
23
+ ```js
24
+ const { transform } = require("@dom-expressions/jsx-compiler");
25
+
26
+ const result = transform(`const view = <div>Hello</div>;`, {
27
+ filename: "App.jsx",
28
+ moduleName: "dom",
29
+ generate: "dom"
30
+ });
31
+
32
+ console.log(result.code);
33
+ ```
34
+
35
+ `transformAsync()` is also available for integration points that expect a
36
+ promise-returning transform:
37
+
38
+ ```js
39
+ const { transformAsync } = require("@dom-expressions/jsx-compiler");
40
+
41
+ const result = await transformAsync(source, {
42
+ filename: "App.jsx",
43
+ moduleName: "dom",
44
+ generate: "dom"
45
+ });
46
+ ```
47
+
48
+ ### Solid-Style DOM
49
+
50
+ Solid's DOM compiler preset uses DOM output with custom-element context
51
+ capture enabled. This compiler defaults `contextToCustomElements` to `true` to
52
+ match that behavior.
53
+
54
+ ```js
55
+ const result = transform(source, {
56
+ filename: "App.jsx",
57
+ moduleName: "dom",
58
+ generate: "dom",
59
+ hydratable: true,
60
+ builtIns: ["For", "Show"]
61
+ });
62
+ ```
63
+
64
+ Use `dev: true` with `hydratable: true` to emit dev hydration walk validation
65
+ helpers such as `getFirstChild` / `getNextSibling`.
66
+
67
+ ### SSR
68
+
69
+ ```js
70
+ const result = transform(source, {
71
+ filename: "entry-server.jsx",
72
+ moduleName: "dom/server",
73
+ generate: "ssr",
74
+ hydratable: true,
75
+ builtIns: ["For", "Show"]
76
+ });
77
+ ```
78
+
79
+ ### Universal
80
+
81
+ ```js
82
+ const result = transform(source, {
83
+ filename: "scene.jsx",
84
+ moduleName: "renderer",
85
+ generate: "universal"
86
+ });
87
+ ```
88
+
89
+ ### Dynamic Renderers
90
+
91
+ Dynamic mode uses the universal renderer as the fallback and can route a
92
+ configured set of native tags to the DOM renderer.
93
+
94
+ ```js
95
+ const result = transform(source, {
96
+ filename: "hybrid.jsx",
97
+ moduleName: "renderer",
98
+ generate: "dynamic",
99
+ renderers: [
100
+ {
101
+ name: "dom",
102
+ moduleName: "dom",
103
+ elements: ["div", "span", "button", "input"]
104
+ }
105
+ ]
106
+ });
107
+ ```
108
+
109
+ ### Source Maps
110
+
111
+ Pass `sourceMap: true` to receive a JSON source map string in `result.map`.
112
+
113
+ ```js
114
+ const result = transform(source, {
115
+ filename: "App.jsx",
116
+ moduleName: "dom",
117
+ sourceMap: true
118
+ });
119
+
120
+ console.log(result.map);
121
+ ```
122
+
123
+ ### Options
124
+
125
+ Supported options track the Babel plugin where currently implemented:
126
+
127
+ - `filename`
128
+ - `moduleName`
129
+ - `generate`: `"dom"`, `"ssr"`, `"universal"`, or `"dynamic"`
130
+ - `hydratable`
131
+ - `dev`
132
+ - `sourceMap`
133
+ - `contextToCustomElements`
134
+ - `delegateEvents`
135
+ - `delegatedEvents`
136
+ - `omitQuotes`
137
+ - `omitAttributeSpacing`
138
+ - `inlineStyles`
139
+ - `effectWrapper`: `"effect"` or `false`
140
+ - paired wrapperless mode: `wrapConditionals: false` with `memoWrapper: false`
141
+ - `staticMarker`
142
+ - `validate`
143
+ - `omitNestedClosingTags`
144
+ - `omitLastClosingTag`
145
+ - `builtIns`
146
+ - `requireImportSource`
147
+ - `renderers`
148
+
149
+ ## Current Scope
150
+
151
+ This package is the AST-native compiler backend. It currently has checked fixture coverage for
152
+ the DOM, hydratable DOM, dev hydratable DOM, SSR, hydratable SSR, universal, dynamic, no-inline-styles, and wrapperless renderer paths.
153
+
154
+ - `generate: "dom"`
155
+ - `generate: "ssr"`
156
+ - `generate: "universal"`
157
+ - `generate: "dynamic"`
158
+ - native elements, components, fragments, refs, spreads, dynamic text, events, and
159
+ attribute handling covered by the checked fixture suites for those targets
160
+ - Solid-compatible defaults such as `contextToCustomElements: true`
161
+ - option coverage for `hydratable`, `dev`, `delegateEvents`, `delegatedEvents`,
162
+ `omitQuotes`, `omitAttributeSpacing`, `inlineStyles`, `effectWrapper: false`,
163
+ paired `wrapConditionals: false` / `memoWrapper: false`, `requireImportSource`,
164
+ `staticMarker`, `validate`, `omitNestedClosingTags`, `omitLastClosingTag`,
165
+ `builtIns`, and dynamic `renderers`
166
+ - source maps for the implemented path
167
+
168
+ ## Not Implemented Yet
169
+
170
+ The compiler intentionally rejects unsupported features instead of pretending to support them:
171
+
172
+ - DOM `namespaceElements` sections that the current Oxc parser rejects before transform
173
+ (for example, hyphenated JSX member segments)
174
+ - arbitrary custom renderer names beyond dynamic DOM renderer override plus universal fallback
175
+ - custom `effectWrapper` / `memoWrapper` helper names
176
+ - unpaired `wrapConditionals: false` or `memoWrapper: false`
177
+ - unknown/custom namespaced DOM attributes outside known runtime namespaces such as `xlink`
178
+
179
+ ## Architecture
180
+
181
+ The implementation is AST-native:
182
+
183
+ 1. Parse with Oxc.
184
+ 2. Transform JSX nodes with `VisitMut`.
185
+ 3. Build replacement expressions and helper declarations with `AstBuilder`.
186
+ 4. Codegen once with Oxc.
187
+
188
+ The module layout mirrors the Babel plugin shape where possible:
189
+
190
+ - `src/config.rs`
191
+ - `src/shared/ast.rs`
192
+ - `src/shared/transform.rs` for shared traversal and target dispatch
193
+ - `src/shared/component.rs`
194
+ - `src/shared/utils.rs`
195
+ - `src/dom/element.rs`
196
+ - `src/dom/template.rs`
197
+ - `src/ssr/mod.rs`
198
+ - `src/ssr/transform.rs`
199
+ - `src/universal/mod.rs`
200
+ - `src/universal/transform.rs`
201
+
package/index.js ADDED
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const native = requireNative();
7
+
8
+ function transform(code, options) {
9
+ if (typeof code !== "string") {
10
+ throw new TypeError(
11
+ "@dom-expressions/jsx-compiler transform() expects source code as a string"
12
+ );
13
+ }
14
+
15
+ const nativeOptions = validateOptions(code, options);
16
+ if (nativeOptions?.skip) {
17
+ return {
18
+ code,
19
+ map: null
20
+ };
21
+ }
22
+ const result = native.transform(code, nativeOptions);
23
+ return {
24
+ code: result.code,
25
+ map: result.map ?? null
26
+ };
27
+ }
28
+
29
+ function transformAsync(code, options) {
30
+ return Promise.resolve().then(() => transform(code, options));
31
+ }
32
+
33
+ const nativeOptionKeys = new Set([
34
+ "filename",
35
+ "moduleName",
36
+ "generate",
37
+ "hydratable",
38
+ "dev",
39
+ "sourceMap",
40
+ "contextToCustomElements",
41
+ "delegateEvents",
42
+ "delegatedEvents",
43
+ "omitQuotes",
44
+ "omitAttributeSpacing",
45
+ "inlineStyles",
46
+ "effectWrapper",
47
+ "wrapConditionals",
48
+ "memoWrapper",
49
+ "staticMarker",
50
+ "omitNestedClosingTags",
51
+ "omitLastClosingTag",
52
+ "builtIns",
53
+ "renderers"
54
+ ]);
55
+
56
+ const compatibleBabelDefaults = new Map([]);
57
+
58
+ function validateOptions(code, options) {
59
+ if (options == null) return options;
60
+ if (typeof options !== "object" || Array.isArray(options)) {
61
+ throw new TypeError(
62
+ "@dom-expressions/jsx-compiler transform() expects options to be an object"
63
+ );
64
+ }
65
+
66
+ const wrapperless = options.wrapConditionals === false || options.memoWrapper === false;
67
+ if (wrapperless) {
68
+ if (options.wrapConditionals !== false || options.memoWrapper !== false) {
69
+ throw new Error(
70
+ "@dom-expressions/jsx-compiler only supports wrapperless mode when `wrapConditionals: false` and `memoWrapper: false` are used together"
71
+ );
72
+ }
73
+ }
74
+
75
+ const nativeOptions = {};
76
+ for (const [key, value] of Object.entries(options)) {
77
+ if (key === "requireImportSource") {
78
+ if (value === false) continue;
79
+ if (typeof value !== "string") {
80
+ throw new TypeError(
81
+ "@dom-expressions/jsx-compiler `requireImportSource` option must be false or a string"
82
+ );
83
+ }
84
+ if (!hasJsxImportSource(code, value)) {
85
+ return { skip: true };
86
+ }
87
+ continue;
88
+ }
89
+ if (key === "effectWrapper") {
90
+ if (value === "effect") continue;
91
+ if (value === false) {
92
+ nativeOptions.effectWrapper = false;
93
+ continue;
94
+ }
95
+ throw new Error(
96
+ '@dom-expressions/jsx-compiler only supports `effectWrapper: false` or the default `"effect"`'
97
+ );
98
+ }
99
+ if (key === "wrapConditionals") {
100
+ if (value === true) continue;
101
+ if (value === false) {
102
+ nativeOptions.wrapConditionals = false;
103
+ continue;
104
+ }
105
+ throw new TypeError(
106
+ "@dom-expressions/jsx-compiler `wrapConditionals` option must be boolean"
107
+ );
108
+ }
109
+ if (key === "memoWrapper") {
110
+ if (value === "memo") continue;
111
+ if (value === false) {
112
+ nativeOptions.memoWrapper = false;
113
+ continue;
114
+ }
115
+ throw new Error(
116
+ '@dom-expressions/jsx-compiler only supports `memoWrapper: false` or the default `"memo"`'
117
+ );
118
+ }
119
+ if (key === "validate") {
120
+ if (typeof value !== "boolean") {
121
+ throw new TypeError("@dom-expressions/jsx-compiler `validate` option must be boolean");
122
+ }
123
+ continue;
124
+ }
125
+ if (nativeOptionKeys.has(key)) {
126
+ if (key === "renderers") validateRenderers(value);
127
+ nativeOptions[key] = value;
128
+ continue;
129
+ }
130
+ if (compatibleBabelDefaults.has(key)) {
131
+ const defaultValue = compatibleBabelDefaults.get(key);
132
+ if (sameOptionValue(value, defaultValue)) continue;
133
+ throw new Error(
134
+ `@dom-expressions/jsx-compiler does not support non-default \`${key}\` options yet`
135
+ );
136
+ }
137
+ throw new Error(`@dom-expressions/jsx-compiler received unknown option \`${key}\``);
138
+ }
139
+ return nativeOptions;
140
+ }
141
+
142
+ function hasJsxImportSource(code, source) {
143
+ const pattern = /@jsxImportSource\s+([^\s*]+)/g;
144
+ let match;
145
+ while ((match = pattern.exec(code))) {
146
+ if (match[1] === source) return true;
147
+ }
148
+ return false;
149
+ }
150
+
151
+ function validateRenderers(renderers) {
152
+ if (renderers == null) return;
153
+ if (!Array.isArray(renderers)) {
154
+ throw new TypeError("@dom-expressions/jsx-compiler `renderers` option must be an array");
155
+ }
156
+
157
+ for (const renderer of renderers) {
158
+ if (typeof renderer !== "object" || renderer == null || Array.isArray(renderer)) {
159
+ throw new TypeError("@dom-expressions/jsx-compiler renderer entries must be objects");
160
+ }
161
+ for (const key of Object.keys(renderer)) {
162
+ if (key !== "name" && key !== "moduleName" && key !== "elements") {
163
+ throw new Error(
164
+ `@dom-expressions/jsx-compiler received unknown renderer option \`${key}\``
165
+ );
166
+ }
167
+ }
168
+ if (renderer.name !== "dom") {
169
+ throw new Error(
170
+ "@dom-expressions/jsx-compiler dynamic renderers only support the `dom` renderer override"
171
+ );
172
+ }
173
+ }
174
+ }
175
+
176
+ function sameOptionValue(value, defaultValue) {
177
+ if (Array.isArray(defaultValue)) {
178
+ return Array.isArray(value) && value.length === defaultValue.length;
179
+ }
180
+ return value === defaultValue;
181
+ }
182
+
183
+ function platformArchSuffix() {
184
+ const { platform, arch } = process;
185
+ if (platform === "darwin" && (arch === "x64" || arch === "arm64")) return `darwin-${arch}`;
186
+ if (platform === "linux" && (arch === "x64" || arch === "arm64")) return `linux-${arch}-gnu`;
187
+ if (platform === "win32" && arch === "x64") return "win32-x64-msvc";
188
+ return null;
189
+ }
190
+
191
+ function requireNative() {
192
+ const explicit = process.env.JSX_DOM_EXPRESSIONS_COMPILER_NATIVE;
193
+ if (explicit) return require(explicit);
194
+
195
+ const suffix = platformArchSuffix();
196
+
197
+ // Local builds (napi build output) take precedence for development.
198
+ const localCandidates = [];
199
+ if (suffix) localCandidates.push(`jsx-compiler.${suffix}.node`);
200
+ localCandidates.push("jsx-compiler.node");
201
+ for (const file of localCandidates) {
202
+ const full = path.join(__dirname, file);
203
+ if (fs.existsSync(full)) return require(full);
204
+ }
205
+
206
+ if (suffix) {
207
+ try {
208
+ return require(`@dom-expressions/jsx-compiler-${suffix}`);
209
+ } catch (error) {
210
+ if (error.code !== "MODULE_NOT_FOUND") throw error;
211
+ }
212
+ }
213
+
214
+ throw new Error(
215
+ `Could not find the native @dom-expressions/jsx-compiler binary for ${process.platform}-${process.arch}` +
216
+ (suffix
217
+ ? ` (expected the @dom-expressions/jsx-compiler-${suffix} package or a local build)`
218
+ : " (no prebuilt binary is published for this platform)") +
219
+ ". Run `pnpm run build` in packages/jsx-compiler to build from source."
220
+ );
221
+ }
222
+
223
+ module.exports = {
224
+ transform,
225
+ transformAsync
226
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@dom-expressions/jsx-compiler",
3
+ "description": "A JSX to DOM Expressions compiler implemented with Oxc",
4
+ "version": "0.50.0-next.15",
5
+ "author": "Ryan Carniato",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ryansolid/dom-expressions.git",
10
+ "directory": "packages/jsx-compiler"
11
+ },
12
+ "main": "index.js",
13
+ "types": "types.d.ts",
14
+ "sideEffects": false,
15
+ "exports": {
16
+ ".": {
17
+ "types": "./types.d.ts",
18
+ "require": "./index.js",
19
+ "default": "./index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "index.js",
24
+ "types.d.ts",
25
+ "README.md"
26
+ ],
27
+ "napi": {
28
+ "binaryName": "jsx-compiler",
29
+ "targets": [
30
+ "x86_64-apple-darwin",
31
+ "aarch64-apple-darwin",
32
+ "x86_64-unknown-linux-gnu",
33
+ "aarch64-unknown-linux-gnu",
34
+ "x86_64-pc-windows-msvc"
35
+ ]
36
+ },
37
+ "devDependencies": {
38
+ "@napi-rs/cli": "^3.6.2"
39
+ },
40
+ "optionalDependencies": {
41
+ "@dom-expressions/jsx-compiler-darwin-x64": "0.50.0-next.15",
42
+ "@dom-expressions/jsx-compiler-darwin-arm64": "0.50.0-next.15",
43
+ "@dom-expressions/jsx-compiler-linux-x64-gnu": "0.50.0-next.15",
44
+ "@dom-expressions/jsx-compiler-linux-arm64-gnu": "0.50.0-next.15",
45
+ "@dom-expressions/jsx-compiler-win32-x64-msvc": "0.50.0-next.15"
46
+ },
47
+ "scripts": {
48
+ "build": "napi build --release --strip --manifest-path ./Cargo.toml",
49
+ "build:debug": "napi build --manifest-path ./Cargo.toml",
50
+ "lint": "cargo clippy --manifest-path ./Cargo.toml -- -D warnings",
51
+ "test": "pnpm run build:debug && jest --no-cache",
52
+ "artifacts": "napi artifacts",
53
+ "napi:version": "napi version && node ./sync-optional-deps.mjs",
54
+ "create-npm-dirs": "napi create-npm-dirs"
55
+ }
56
+ }
package/types.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ export interface TransformOptions {
2
+ filename?: string;
3
+ moduleName?: string;
4
+ generate?: "dom" | "ssr" | "universal" | "dynamic";
5
+ hydratable?: boolean;
6
+ dev?: boolean;
7
+ sourceMap?: boolean;
8
+ contextToCustomElements?: boolean;
9
+ delegateEvents?: boolean;
10
+ delegatedEvents?: string[];
11
+ omitQuotes?: boolean;
12
+ omitAttributeSpacing?: boolean;
13
+ inlineStyles?: boolean;
14
+ effectWrapper?: "effect" | false;
15
+ wrapConditionals?: boolean;
16
+ memoWrapper?: "memo" | false;
17
+ staticMarker?: string;
18
+ validate?: boolean;
19
+ omitNestedClosingTags?: boolean;
20
+ omitLastClosingTag?: boolean;
21
+ builtIns?: string[];
22
+ requireImportSource?: false | string;
23
+ renderers?: RendererOption[];
24
+ }
25
+
26
+ export interface RendererOption {
27
+ name: string;
28
+ moduleName?: string;
29
+ elements: string[];
30
+ }
31
+
32
+ export interface TransformResult {
33
+ code: string;
34
+ map?: string | null;
35
+ }
36
+
37
+ export function transform(code: string, options?: TransformOptions | null): TransformResult;
38
+ export function transformAsync(
39
+ code: string,
40
+ options?: TransformOptions | null
41
+ ): Promise<TransformResult>;