babel-plugin-rbl 0.0.1

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.
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org>
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # babel-plugin-rbl
2
+
3
+ Lets you write [react-babylon-lite](https://github.com/brianzinn/react-babylon-lite) scenes with
4
+ lowercase JSX intrinsics — `<box/>`, `<pbrMaterial/>`, `<arcRotateCamera/>` — instead of imported
5
+ components, **without giving up tree-shaking**. It rewrites each known lowercase tag into a real
6
+ component import at build time, so the module graph stays statically analyzable and unused feature
7
+ areas still contribute zero bytes.
8
+
9
+ ```tsx
10
+ // You write this:
11
+ import 'react-babylon-lite/intrinsics';
12
+ import { Canvas } from 'react-babylon-lite';
13
+
14
+ <Canvas>
15
+ <arcRotateCamera radius={7} />
16
+ <hemisphericLight direction={[0, 1, 0]} />
17
+ <box size={2}>
18
+ <pbrMaterial baseColorFactor="#ff6a00" />
19
+ </box>
20
+ </Canvas>;
21
+ ```
22
+
23
+ ```tsx
24
+ // The plugin turns the tags into this (conceptually) before your React transform runs:
25
+ import { Box as _Box, PbrMaterial as _PbrMaterial /* … */ } from 'react-babylon-lite/components';
26
+
27
+ <_Box size={2}>
28
+ <_PbrMaterial baseColorFactor="#ff6a00" />
29
+ </_Box>;
30
+ ```
31
+
32
+ Because the output is ordinary named imports, the bundle is byte-for-byte what you'd get by
33
+ importing the components yourself — proven by the same CI tree-shake gate the library uses. There
34
+ is **no runtime name registry** (unlike an `extend()`-injection approach), so nothing defeats dead-
35
+ code elimination.
36
+
37
+ ```bash
38
+ pnpm add -D babel-plugin-rbl
39
+ ```
40
+
41
+ ## Setup
42
+
43
+ Pick the entry point that matches your toolchain. Both do the identical rewrite.
44
+
45
+ ### Vite (recommended — works with oxc/SWC, `@vitejs/plugin-react` v6+, and Babel)
46
+
47
+ `@vitejs/plugin-react` v6+ is oxc-based and has **no Babel hook**, so use the standalone
48
+ `enforce: 'pre'` transform from the `/vite` subpath. It must run **before** the React plugin:
49
+
50
+ ```js
51
+ // vite.config.js
52
+ import react from '@vitejs/plugin-react';
53
+ import reactBabylonLite from 'babel-plugin-rbl/vite';
54
+ import { defineConfig } from 'vite';
55
+
56
+ export default defineConfig({
57
+ plugins: [
58
+ reactBabylonLite(), // <box/> → <_Box/> first…
59
+ react(), // …then the normal JSX/TS transform
60
+ ],
61
+ });
62
+ ```
63
+
64
+ It only touches `.jsx`/`.tsx` files that actually contain a known tag (cheap regex pre-filter),
65
+ emits sourcemaps, and leaves the rewritten code as JSX/TS for your React plugin to compile as usual.
66
+
67
+ ### Babel toolchain (`babel.config.js`, or a Babel-based `@vitejs/plugin-react`)
68
+
69
+ If you already run Babel, add the plugin directly:
70
+
71
+ ```js
72
+ // babel.config.js
73
+ export default {
74
+ plugins: ['babel-plugin-rbl'],
75
+ };
76
+ ```
77
+
78
+ ```js
79
+ // or via a Babel-based @vitejs/plugin-react
80
+ react({ babel: { plugins: ['babel-plugin-rbl'] } });
81
+ ```
82
+
83
+ ## TypeScript
84
+
85
+ Add the types-only import once (anywhere in your app, or in a `.d.ts`) so TypeScript accepts the
86
+ lowercase tags and types their props (`position` → `Vec3Input`, etc.):
87
+
88
+ ```ts
89
+ import 'react-babylon-lite/intrinsics';
90
+ ```
91
+
92
+ It has zero runtime cost — it only augments the global JSX namespace. Without it, TypeScript reports
93
+ the lowercase tags as unknown intrinsic elements.
94
+
95
+ ## What gets rewritten
96
+
97
+ - Only the **known lowercase tags** are rewritten — the 30 elements generated from the
98
+ react-babylon-lite codegen manifest (the same list `react-babylon-lite/intrinsics` types), e.g.
99
+ `box`, `sphere`, `ground`, `arcRotateCamera`, `hemisphericLight`, `directionalLight`,
100
+ `standardMaterial`, `pbrMaterial`, `gridMaterial`, `pcfDirectionalShadowGenerator`, `transformNode`, …
101
+ - Lowercase tags that **aren't** in the map (DOM elements like `<div/>`) and all capitalized
102
+ components pass through untouched.
103
+ - One import is added per component per file (deduped).
104
+
105
+ The plugin takes no options — imports always resolve from `react-babylon-lite/components`.
106
+
107
+ ## License
108
+
109
+ [Unlicense](https://unlicense.org/) (public domain).
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "babel-plugin-rbl",
3
+ "version": "0.0.1",
4
+ "description": "Rewrites lowercase JSX intrinsics like <box/> into tree-shakeable react-babylon-lite component imports",
5
+ "author": "Brian Zinn <542756+brianzinn@users.noreply.github.com>",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/brianzinn/react-babylon-lite.git",
9
+ "directory": "packages/babel-plugin"
10
+ },
11
+ "homepage": "https://github.com/brianzinn/react-babylon-lite#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/brianzinn/react-babylon-lite/issues"
14
+ },
15
+ "keywords": [
16
+ "babel-plugin",
17
+ "react",
18
+ "babylon-lite",
19
+ "react-babylon-lite"
20
+ ],
21
+ "type": "module",
22
+ "main": "./src/index.js",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./src/index.d.ts",
26
+ "import": "./src/index.js"
27
+ },
28
+ "./vite": {
29
+ "types": "./src/vite.d.ts",
30
+ "import": "./src/vite.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "src"
36
+ ],
37
+ "license": "Unlicense",
38
+ "dependencies": {
39
+ "@babel/core": "^7.25.0",
40
+ "@babel/helper-module-imports": "^7.25.9",
41
+ "@babel/plugin-syntax-jsx": "^7.25.0",
42
+ "@babel/plugin-syntax-typescript": "^7.25.0"
43
+ },
44
+ "devDependencies": {
45
+ "vitest": "^4.1.10"
46
+ },
47
+ "scripts": {
48
+ "test": "vitest run"
49
+ }
50
+ }
@@ -0,0 +1,34 @@
1
+ // GENERATED FILE — do not edit. Regenerate with `pnpm codegen` (tools/codegen).
2
+ /** Lowercase JSX element name → named export of react-babylon-lite/components. */
3
+ export const elements = {
4
+ arcRotateCamera: 'ArcRotateCamera',
5
+ box: 'Box',
6
+ capsule: 'Capsule',
7
+ csmDirectionalShadowGenerator: 'CsmDirectionalShadowGenerator',
8
+ cylinder: 'Cylinder',
9
+ directionalLight: 'DirectionalLight',
10
+ disc: 'Disc',
11
+ esmDirectionalShadowGenerator: 'EsmDirectionalShadowGenerator',
12
+ extrudeShape: 'ExtrudeShape',
13
+ freeCamera: 'FreeCamera',
14
+ geospatialCamera: 'GeospatialCamera',
15
+ gridMaterial: 'GridMaterial',
16
+ ground: 'Ground',
17
+ hemisphericLight: 'HemisphericLight',
18
+ meshFromData: 'MeshFromData',
19
+ pbrMaterial: 'PbrMaterial',
20
+ pcfDirectionalShadowGenerator: 'PcfDirectionalShadowGenerator',
21
+ pcfSpotlightShadowGenerator: 'PcfSpotlightShadowGenerator',
22
+ plane: 'Plane',
23
+ pointLight: 'PointLight',
24
+ polyhedron: 'Polyhedron',
25
+ ribbon: 'Ribbon',
26
+ shaderMaterial: 'ShaderMaterial',
27
+ sphere: 'Sphere',
28
+ spotLight: 'SpotLight',
29
+ standardMaterial: 'StandardMaterial',
30
+ torus: 'Torus',
31
+ torusKnot: 'TorusKnot',
32
+ transformNode: 'TransformNode',
33
+ tube: 'Tube',
34
+ };
package/src/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /** Babel plugin: rewrites known lowercase JSX tags into react-babylon-lite component imports. */
2
+ declare function reactBabylonLiteBabelPlugin(): {
3
+ name: string;
4
+ visitor: Record<string, unknown>;
5
+ };
6
+ export default reactBabylonLiteBabelPlugin;
package/src/index.js ADDED
@@ -0,0 +1,43 @@
1
+ import { addNamed } from '@babel/helper-module-imports';
2
+ import { elements } from './element-map.mjs';
3
+
4
+ const SOURCE = 'react-babylon-lite/components';
5
+
6
+ /**
7
+ * Rewrites known lowercase JSX tags (<box/>) into imports of the matching react-babylon-lite
8
+ * component (<_Box/> + `import { Box as _Box } from 'react-babylon-lite/components'`). The
9
+ * import graph stays statically analyzable, so tree-shaking works exactly as with hand-written
10
+ * component imports — no runtime name registry. Unknown lowercase tags (DOM elements) and
11
+ * capitalized components pass through untouched.
12
+ *
13
+ * Pair with `import 'react-babylon-lite/intrinsics'` (types-only) so TypeScript accepts the tags.
14
+ */
15
+ export default function reactBabylonLiteBabelPlugin() {
16
+ return {
17
+ name: 'react-babylon-lite',
18
+ visitor: {
19
+ JSXOpeningElement(path, state) {
20
+ const nameNode = path.node.name;
21
+ if (nameNode.type !== 'JSXIdentifier') return;
22
+ const tag = nameNode.name;
23
+ if (!/^[a-z]/.test(tag)) return;
24
+ const exportName = elements[tag];
25
+ if (exportName === undefined) return;
26
+
27
+ // One import per component per file.
28
+ const imports = (state.rblImports ??= new Map());
29
+ let localName = imports.get(exportName);
30
+ if (localName === undefined) {
31
+ localName = addNamed(path, exportName, SOURCE, { nameHint: exportName }).name;
32
+ imports.set(exportName, localName);
33
+ }
34
+
35
+ nameNode.name = localName;
36
+ const closing = path.parent.closingElement;
37
+ if (closing !== null && closing !== undefined && closing.name.type === 'JSXIdentifier') {
38
+ closing.name.name = localName;
39
+ }
40
+ },
41
+ },
42
+ };
43
+ }
package/src/vite.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Standalone Vite plugin (enforce: 'pre') for setups without a babel hook (oxc/SWC-based React
3
+ * plugins). Structurally assignable to Vite's Plugin type.
4
+ */
5
+ declare function reactBabylonLiteVite(): {
6
+ name: string;
7
+ enforce: 'pre';
8
+ transform(code: string, id: string): Promise<{ code: string; map: unknown } | null>;
9
+ };
10
+ export default reactBabylonLiteVite;
package/src/vite.js ADDED
@@ -0,0 +1,40 @@
1
+ import { transformAsync } from '@babel/core';
2
+ import jsxSyntax from '@babel/plugin-syntax-jsx';
3
+ import typescriptSyntax from '@babel/plugin-syntax-typescript';
4
+ import { elements } from './element-map.mjs';
5
+ import reactBabylonLiteBabelPlugin from './index.js';
6
+
7
+ // Cheap pre-filter: only files that mention a known lowercase tag get the babel pass.
8
+ const TAG_PATTERN = new RegExp(`<(?:${Object.keys(elements).join('|')})[\\s/>]`);
9
+
10
+ /**
11
+ * Standalone Vite plugin (works with rolldown/oxc-based @vitejs/plugin-react v6+, which has no
12
+ * babel hook, and with SWC setups). Runs as an `enforce: 'pre'` transform that ONLY renames
13
+ * known lowercase JSX tags into component imports — output is still JSX/TS, so the regular
14
+ * React plugin compiles it as usual.
15
+ *
16
+ * Usage: `plugins: [reactBabylonLite(), react(), ...]`.
17
+ */
18
+ export default function reactBabylonLiteVite() {
19
+ return {
20
+ name: 'react-babylon-lite',
21
+ enforce: 'pre',
22
+ async transform(code, id) {
23
+ const filename = id.split('?')[0];
24
+ if (!/\.[jt]sx$/.test(filename)) return null;
25
+ if (!TAG_PATTERN.test(code)) return null;
26
+
27
+ const syntaxPlugins = filename.endsWith('.tsx')
28
+ ? [[typescriptSyntax, { isTSX: true }], jsxSyntax]
29
+ : [jsxSyntax];
30
+ const result = await transformAsync(code, {
31
+ configFile: false,
32
+ babelrc: false,
33
+ filename,
34
+ sourceMaps: true,
35
+ plugins: [...syntaxPlugins, reactBabylonLiteBabelPlugin],
36
+ });
37
+ return result?.code != null ? { code: result.code, map: result.map } : null;
38
+ },
39
+ };
40
+ }