babel-plugin-reactylon 1.0.0
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.md +21 -0
- package/README.md +145 -0
- package/build/ParserUtils.js +102 -0
- package/build/index.js +172 -0
- package/package.json +42 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 Simone De Vittorio.
|
|
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,145 @@
|
|
|
1
|
+
# Babel Plugin Reactylon
|
|
2
|
+
|
|
3
|
+
A Babel plugin designed to enable tree shaking in a Reactylon application. It statically analyzes Reactylon JSX elements and automatically manages imports and registrations of the relative Babylon.js classes, ensuring only the necessary parts of the Babylon.js library are included in the final bundle.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Automatic detection of Babylon.js components used in JSX
|
|
7
|
+
- Automatic import of Babylon.js classes from `@babylonjs/core` e `@babylonjs/gui`
|
|
8
|
+
- Implicit side effects management (detect prototype-level side effects in Babylon.js and ensures proper runtime behaviour)
|
|
9
|
+
- Explicit side effects support (allows users to define custom side effects manually)
|
|
10
|
+
- Automatic registration (ensures that used Babylon.js classes are registered within Reactylon's internal registry)
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
### Babel
|
|
15
|
+
|
|
16
|
+
Add the plugin to your Babel configuration:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
// babel.config.js
|
|
20
|
+
module.exports = {
|
|
21
|
+
plugins: [
|
|
22
|
+
['babel-plugin-reactylon']
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Webpack
|
|
28
|
+
Add the plugin to your Webpack configuration. The following configuration demonstrates how to integrate the plugin using both `ts-loader` and `babel-loader`:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// webpack.config.js
|
|
32
|
+
module.exports = {
|
|
33
|
+
module: {
|
|
34
|
+
rules: [
|
|
35
|
+
{
|
|
36
|
+
test: /\.tsx?$/,
|
|
37
|
+
use: [
|
|
38
|
+
{
|
|
39
|
+
loader: 'ts-loader',
|
|
40
|
+
options: {
|
|
41
|
+
transpileOnly: true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
loader: "babel-loader",
|
|
46
|
+
options: {
|
|
47
|
+
presets: [
|
|
48
|
+
["@babel/preset-env"],
|
|
49
|
+
["@babel/preset-react", { runtime: "automatic" }],
|
|
50
|
+
"@babel/preset-typescript"
|
|
51
|
+
],
|
|
52
|
+
plugins: [
|
|
53
|
+
['babel-plugin-reactylon']
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
exclude: '/node_modules/',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Additional configurations
|
|
66
|
+
Additionally you can manually define extra side effects (see [Babylon.js ES6 support FAQ](https://doc.babylonjs.com/setup/frameworkPackages/es6Support/#faq)) or specify a custom `node_modules` path (useful if you are working with a monorepo setup).
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
['babel-plugin-reactylon', {
|
|
70
|
+
// additional side effects (optional)
|
|
71
|
+
sideEffects: [
|
|
72
|
+
'@babylonjs/core/Engines/Extensions/engine.query.js'
|
|
73
|
+
]
|
|
74
|
+
// node modules path (optional)
|
|
75
|
+
nodeModulesPath: '../../node_modules'
|
|
76
|
+
}]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## How it works
|
|
80
|
+
The plugin analyzes your JSX code by scanning for Reactylon components. When a component is detected, it automatically imports the corresponding Babylon.js class and registers it with Reactylon, making it available in the rendering context. To remain consistent with Babylon.js' modular architecture, the plugin selects the *deepest available class* implementation by recursively scanning the directory tree. This approach ensures that the most specific and optimized module is used, preventing unwanted side effects that could compromise tree shaking.
|
|
81
|
+
|
|
82
|
+
### Example
|
|
83
|
+
|
|
84
|
+
#### Input
|
|
85
|
+
|
|
86
|
+
```jsx
|
|
87
|
+
<>
|
|
88
|
+
<standardMaterial name='material'>
|
|
89
|
+
<texture url='texture-url' />
|
|
90
|
+
</standardMaterial>
|
|
91
|
+
<box />
|
|
92
|
+
<sphere />
|
|
93
|
+
<text3D />
|
|
94
|
+
<standardMaterial name='material-1' />
|
|
95
|
+
<standardMaterial name='material-2' />
|
|
96
|
+
<pointLight
|
|
97
|
+
name='point'
|
|
98
|
+
position={new Vector3(0, 1, 0)}
|
|
99
|
+
diffuse={Color3.Red()}
|
|
100
|
+
specular={Color3.Green()}
|
|
101
|
+
/>
|
|
102
|
+
<directionalLight
|
|
103
|
+
name='directional'
|
|
104
|
+
direction={new Vector3(0, -1, 0)}
|
|
105
|
+
diffuse={Color3.Red()}
|
|
106
|
+
specular={Color3.Green()}
|
|
107
|
+
/>
|
|
108
|
+
</>;
|
|
109
|
+
```
|
|
110
|
+
#### Output
|
|
111
|
+
```js
|
|
112
|
+
import { DirectionalLight as _DirectionalLight } from "@babylonjs/core/Lights/directionalLight";
|
|
113
|
+
import { PointLight as _PointLight } from "@babylonjs/core/Lights/pointLight";
|
|
114
|
+
import { CreateText as _CreateText } from "@babylonjs/core/Meshes/Builders/textBuilder";
|
|
115
|
+
import { CreateSphere as _CreateSphere } from "@babylonjs/core/Meshes/Builders/sphereBuilder";
|
|
116
|
+
import { CreateBox as _CreateBox } from "@babylonjs/core/Meshes/Builders/boxBuilder";
|
|
117
|
+
import { Texture as _Texture } from "@babylonjs/core/Materials/Textures/texture";
|
|
118
|
+
import { StandardMaterial as _StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";
|
|
119
|
+
import { register as _register } from "reactylon";
|
|
120
|
+
_register({
|
|
121
|
+
"standardMaterial": [_StandardMaterial, 0],
|
|
122
|
+
"texture": [_Texture, 0],
|
|
123
|
+
"box": [_CreateBox, 0],
|
|
124
|
+
"sphere": [_CreateSphere, 0],
|
|
125
|
+
"text3D": [_CreateText, 0],
|
|
126
|
+
"pointLight": [_PointLight, 0],
|
|
127
|
+
"directionalLight": [_DirectionalLight, 0]
|
|
128
|
+
});
|
|
129
|
+
<>
|
|
130
|
+
<standardMaterial name='material'>
|
|
131
|
+
<texture url='texture-url' />
|
|
132
|
+
</standardMaterial>
|
|
133
|
+
<box />
|
|
134
|
+
<sphere />
|
|
135
|
+
<text3D />
|
|
136
|
+
<standardMaterial name='material-1' />
|
|
137
|
+
<standardMaterial name='material-2' />
|
|
138
|
+
<pointLight name='point' position={new Vector3(0, 1, 0)} diffuse={Color3.Red()} specular={Color3.Green()} />
|
|
139
|
+
<directionalLight name='directional' direction={new Vector3(0, -1, 0)} diffuse={Color3.Red()} specular={Color3.Green()} />
|
|
140
|
+
</>;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Acknowledgements
|
|
144
|
+
This project is highly inspired by [React Three Babel](https://github.com/pmndrs/react-three-babel).
|
|
145
|
+
Special thanks to the authors for their work, which served as a foundation and reference for this implementation.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
class ParserUtils {
|
|
4
|
+
/**
|
|
5
|
+
* It extracts exports and side effects from a file
|
|
6
|
+
* @param filePath - file path to analyze
|
|
7
|
+
* @returns an object containing the discovered exports and side effects
|
|
8
|
+
*/
|
|
9
|
+
static getExportsAndSideEffects(filePath) {
|
|
10
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
11
|
+
const exports = new Set();
|
|
12
|
+
const sideEffects = new Set();
|
|
13
|
+
// Default export
|
|
14
|
+
if (/export\s+default\s+/g.test(content)) {
|
|
15
|
+
exports.add('default');
|
|
16
|
+
}
|
|
17
|
+
// Named export
|
|
18
|
+
const namedExports = content.match(/export\s+{([^}]+)}/g);
|
|
19
|
+
if (namedExports) {
|
|
20
|
+
namedExports.forEach(exp => {
|
|
21
|
+
//@ts-ignore
|
|
22
|
+
exp.replace(/export\s+{([^}]+)}/, (_, group) => {
|
|
23
|
+
group.split(',').forEach((e) => exports.add(e.trim()));
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// Other export
|
|
28
|
+
const individualExports = content.match(/export\s+(const|let|var|function|class)\s+([a-zA-Z0-9_]+)/g);
|
|
29
|
+
if (individualExports) {
|
|
30
|
+
individualExports.forEach(exp => {
|
|
31
|
+
//@ts-ignore
|
|
32
|
+
exp.replace(/export\s+(const|let|var|function|class)\s+([a-zA-Z0-9_]+)/, (_, __, name) => {
|
|
33
|
+
exports.add(name);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Component.prototype.method
|
|
38
|
+
const prototypeMatches = content.match(/(\w+)\.prototype\.(\w+)\s*=/g);
|
|
39
|
+
if (prototypeMatches) {
|
|
40
|
+
prototypeMatches.forEach(prototype => {
|
|
41
|
+
const methodName = prototype.match(/\.prototype\.(\w+)/)[1];
|
|
42
|
+
sideEffects.add(methodName);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Object.defineProperty(Class.prototype, "method", { })'
|
|
46
|
+
const definePropertyMatches = content.match(/Object\.defineProperty\(\s*(\w+)\.prototype\s*,\s*["'](\w+)["']/g);
|
|
47
|
+
if (definePropertyMatches) {
|
|
48
|
+
definePropertyMatches.forEach(prototype => {
|
|
49
|
+
const methodName = prototype.match(/Object\.defineProperty\(\s*\w+\.prototype\s*,\s*["'](\w+)["']/)[1];
|
|
50
|
+
sideEffects.add(methodName);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
exports: Array.from(exports),
|
|
55
|
+
sideEffects: Array.from(sideEffects)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* It scans a directory and it creates a map of exports and side effects
|
|
60
|
+
* @param dir - directory to scan
|
|
61
|
+
* @param exportsMap - exports map to populate
|
|
62
|
+
* @param sideEffectsMap - side effects map to populate
|
|
63
|
+
*/
|
|
64
|
+
static scanDirectory(dir, exportsMap, sideEffectsMap) {
|
|
65
|
+
const files = fs.readdirSync(dir);
|
|
66
|
+
files.forEach(file => {
|
|
67
|
+
const fullPath = path.join(dir, file);
|
|
68
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
69
|
+
this.scanDirectory(fullPath, exportsMap, sideEffectsMap);
|
|
70
|
+
}
|
|
71
|
+
else if (file.endsWith('.js') || file.endsWith('.mjs') || file.endsWith('.ts')) {
|
|
72
|
+
const importPath = (fullPath.replace(/\.(js|mjs|ts)$/, '')).replace(/^node_modules\//, '');
|
|
73
|
+
const { exports, sideEffects } = this.getExportsAndSideEffects(fullPath);
|
|
74
|
+
exports.forEach(exp => {
|
|
75
|
+
exportsMap[exp] = importPath;
|
|
76
|
+
});
|
|
77
|
+
sideEffects.forEach(sideEffect => {
|
|
78
|
+
sideEffectsMap[sideEffect] = importPath;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* High level function used to generate the maps of exports and side effects for a given module
|
|
85
|
+
* @param moduleName - module name to analyze
|
|
86
|
+
* @returns an object containing the discovered exports and side effects maps
|
|
87
|
+
*/
|
|
88
|
+
static generateExportsAndSideEffects(moduleName, nodeModulesPath) {
|
|
89
|
+
const modulePath = path.join(nodeModulesPath, moduleName);
|
|
90
|
+
if (!fs.existsSync(modulePath)) {
|
|
91
|
+
console.error(`The module "${moduleName}" doesn't exist.`);
|
|
92
|
+
}
|
|
93
|
+
const exportsMap = {};
|
|
94
|
+
const sideEffectsMap = {};
|
|
95
|
+
this.scanDirectory(modulePath, exportsMap, sideEffectsMap);
|
|
96
|
+
return {
|
|
97
|
+
exports: exportsMap,
|
|
98
|
+
sideEffects: sideEffectsMap
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export default ParserUtils;
|
package/build/index.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { types as t } from '@babel/core';
|
|
2
|
+
import { declare } from "@babel/helper-plugin-utils";
|
|
3
|
+
import { BabylonPackages, builders, CollidingComponents, ReversedCollidingComponents } from 'reactylon';
|
|
4
|
+
import ParserUtils from './ParserUtils.js';
|
|
5
|
+
const babylonImportsSpecifiers = new Map();
|
|
6
|
+
const sideEffects = [];
|
|
7
|
+
let lastImport = null;
|
|
8
|
+
let initialized = false;
|
|
9
|
+
let coreExportsMap = {};
|
|
10
|
+
let coreSideEffectsMap = {};
|
|
11
|
+
let guiExportsMap = {};
|
|
12
|
+
//let guiSideEffectsMap: Record<string, string> = {};
|
|
13
|
+
function capitalizeFirstLetter(str) {
|
|
14
|
+
return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
|
|
15
|
+
}
|
|
16
|
+
export default declare((api) => {
|
|
17
|
+
api.assertVersion(7);
|
|
18
|
+
return {
|
|
19
|
+
manipulateOptions(_, options) {
|
|
20
|
+
options.plugins.push('jsx');
|
|
21
|
+
},
|
|
22
|
+
pre() {
|
|
23
|
+
var _a;
|
|
24
|
+
if (!initialized) {
|
|
25
|
+
const options = this.opts;
|
|
26
|
+
const nodeModulesPath = options.nodeModulesPath || 'node_modules';
|
|
27
|
+
const coreExportsAndSideEffects = ParserUtils.generateExportsAndSideEffects('@babylonjs/core', nodeModulesPath);
|
|
28
|
+
coreExportsMap = coreExportsAndSideEffects.exports;
|
|
29
|
+
coreSideEffectsMap = coreExportsAndSideEffects.sideEffects;
|
|
30
|
+
const guiExportsAndSideEffects = ParserUtils.generateExportsAndSideEffects('@babylonjs/gui', nodeModulesPath);
|
|
31
|
+
guiExportsMap = guiExportsAndSideEffects.exports;
|
|
32
|
+
//guiSideEffectsMap = guiExportsAndSideEffects.sideEffects;
|
|
33
|
+
// Add explicit side effects (additional side effects declared by user)
|
|
34
|
+
(_a = options === null || options === void 0 ? void 0 : options.sideEffects) === null || _a === void 0 ? void 0 : _a.forEach((sideEffect) => {
|
|
35
|
+
sideEffects.push(t.importDeclaration([], t.stringLiteral(sideEffect)));
|
|
36
|
+
});
|
|
37
|
+
initialized = true;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
post() {
|
|
41
|
+
babylonImportsSpecifiers.clear();
|
|
42
|
+
lastImport = null;
|
|
43
|
+
},
|
|
44
|
+
visitor: {
|
|
45
|
+
ImportDeclaration(importPath) {
|
|
46
|
+
lastImport = importPath;
|
|
47
|
+
},
|
|
48
|
+
JSXOpeningElement(path) {
|
|
49
|
+
var _a;
|
|
50
|
+
// Don't include non-React JSX
|
|
51
|
+
// https://github.com/facebook/jsx/issues/13
|
|
52
|
+
const { name, attributes } = path.node;
|
|
53
|
+
if (t.isJSXNamespacedName(name))
|
|
54
|
+
return;
|
|
55
|
+
// Parse identifiers (e.g. <mesh />, <animated.mesh />)
|
|
56
|
+
let type = 'property' in name ? name.property.name : name.name;
|
|
57
|
+
const declaration = (_a = path.scope.getBinding(type)) === null || _a === void 0 ? void 0 : _a.path.node;
|
|
58
|
+
if (t.isVariableDeclarator(declaration)) {
|
|
59
|
+
if (t.isStringLiteral(declaration.init)) {
|
|
60
|
+
// const Comp = 'value'
|
|
61
|
+
// <Comp />
|
|
62
|
+
type = declaration.init.value;
|
|
63
|
+
}
|
|
64
|
+
else if (t.isTemplateLiteral(declaration.init)) {
|
|
65
|
+
// const Comp = `value${var}`
|
|
66
|
+
// <Comp />
|
|
67
|
+
type = declaration.init.quasis.map((n) => n.value.cooked).join('\\w*');
|
|
68
|
+
}
|
|
69
|
+
else if (t.isBinaryExpression(declaration.init) || t.isAssignmentExpression(declaration.init)) {
|
|
70
|
+
// const Comp = 'left' + 'right' || (left += 'right')
|
|
71
|
+
// <Comp />
|
|
72
|
+
type = [declaration.init.left, declaration.init.right]
|
|
73
|
+
.map((o) => (t.isStringLiteral(o) ? o.value : '\\w*'))
|
|
74
|
+
.join('');
|
|
75
|
+
}
|
|
76
|
+
else if (t.isConditionalExpression(declaration.init)) {
|
|
77
|
+
// const Comp = test ? 'consequent' : 'alternate'
|
|
78
|
+
// <Comp />
|
|
79
|
+
type = [declaration.init.consequent, declaration.init.alternate]
|
|
80
|
+
.map((o) => (t.isStringLiteral(o) ? o.value : '\\w*'))
|
|
81
|
+
.join('|');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// const Comp = var
|
|
85
|
+
// <Comp />
|
|
86
|
+
type = '\\w+';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Don't include colliding React JSX components
|
|
90
|
+
if (type in ReversedCollidingComponents)
|
|
91
|
+
return;
|
|
92
|
+
if (!babylonImportsSpecifiers.has(type)) {
|
|
93
|
+
const normalizedType = type in CollidingComponents ? CollidingComponents[type] : type;
|
|
94
|
+
const isBuilder = builders.includes(normalizedType);
|
|
95
|
+
const className = isBuilder ? `Create${capitalizeFirstLetter(normalizedType)}` : capitalizeFirstLetter(normalizedType);
|
|
96
|
+
if (className in coreExportsMap || className in guiExportsMap) {
|
|
97
|
+
const local = path.scope.generateUidIdentifier(className);
|
|
98
|
+
babylonImportsSpecifiers.set(type, t.importSpecifier(local, t.identifier(className)));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Add implicit side effects (derived from static parse of JSX Reactylon code)
|
|
102
|
+
const isPhysicsEngine = attributes.some(attr => t.isJSXAttribute(attr) && attr.name.name === "physicsOptions");
|
|
103
|
+
if (isPhysicsEngine) {
|
|
104
|
+
sideEffects.push(t.importDeclaration([], t.stringLiteral('@babylonjs/core/Physics/physicsEngineComponent.js')));
|
|
105
|
+
}
|
|
106
|
+
const isBoundingBox = attributes.some(attr => t.isJSXAttribute(attr) && attr.name.name === "showBoundingBox");
|
|
107
|
+
if (isBoundingBox) {
|
|
108
|
+
sideEffects.push(t.importDeclaration([], t.stringLiteral('@babylonjs/core/Rendering/boundingBoxRenderer.js')));
|
|
109
|
+
}
|
|
110
|
+
// add here other side effects (https://doc.babylonjs.com/setup/frameworkPackages/es6Support/#faq)
|
|
111
|
+
},
|
|
112
|
+
Program: {
|
|
113
|
+
exit(path) {
|
|
114
|
+
if (!babylonImportsSpecifiers.size)
|
|
115
|
+
return;
|
|
116
|
+
const babylonImports = [];
|
|
117
|
+
babylonImportsSpecifiers.forEach((importSpecifier, type) => {
|
|
118
|
+
let packageName = null;
|
|
119
|
+
const importName = importSpecifier.imported.name;
|
|
120
|
+
let importPath = '';
|
|
121
|
+
if (coreExportsMap[importName]) {
|
|
122
|
+
importPath = coreExportsMap[importName];
|
|
123
|
+
packageName = BabylonPackages.CORE;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
importPath = guiExportsMap[importName];
|
|
127
|
+
packageName = BabylonPackages.GUI;
|
|
128
|
+
}
|
|
129
|
+
const importDeclaration = t.importDeclaration([importSpecifier], t.stringLiteral(importPath));
|
|
130
|
+
babylonImports.push([type, importDeclaration, packageName]);
|
|
131
|
+
});
|
|
132
|
+
const registerSpecifier = t.importSpecifier(path.scope.generateUidIdentifier('register'), t.identifier('register'));
|
|
133
|
+
const registerImportDeclaration = t.importDeclaration([registerSpecifier], t.stringLiteral('reactylon'));
|
|
134
|
+
const registerCall = t.expressionStatement(t.callExpression(registerSpecifier.local, [
|
|
135
|
+
t.objectExpression(babylonImports.map(([type, importDeclaration, packageName]) => {
|
|
136
|
+
const importSpecifier = importDeclaration.specifiers[0];
|
|
137
|
+
return t.objectProperty(t.stringLiteral(type), t.arrayExpression([
|
|
138
|
+
importSpecifier.local,
|
|
139
|
+
t.numericLiteral(packageName)
|
|
140
|
+
]), false, false);
|
|
141
|
+
})),
|
|
142
|
+
]));
|
|
143
|
+
const babylonImportDeclarations = babylonImports.map(([, importDeclaration]) => importDeclaration);
|
|
144
|
+
// Add implicit prototype-level side effects (derived from static parse of JavaScript Babylon.js code)
|
|
145
|
+
path.traverse({
|
|
146
|
+
CallExpression(callPath) {
|
|
147
|
+
const callee = callPath.node.callee;
|
|
148
|
+
if (t.isMemberExpression(callee)) {
|
|
149
|
+
const property = callee.property;
|
|
150
|
+
if (t.isIdentifier(property)) {
|
|
151
|
+
if (property.name in coreSideEffectsMap) {
|
|
152
|
+
sideEffects.push(t.importDeclaration([], t.stringLiteral(coreSideEffectsMap[property.name])));
|
|
153
|
+
// callPath.stop();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
// Add imports and call register
|
|
160
|
+
for (const node of [registerCall, registerImportDeclaration, ...babylonImportDeclarations, ...sideEffects]) {
|
|
161
|
+
if (lastImport) {
|
|
162
|
+
lastImport.insertAfter(node);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
path.unshiftContainer('body', node);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "babel-plugin-reactylon",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Babel plugin to enable tree-shaking of Babylon.js classes within a Reactylon application.",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "rm -rf build && tsc",
|
|
9
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
10
|
+
"test:debug": "NODE_OPTIONS=--experimental-vm-modules node --inspect-brk node_modules/.bin/jest --runInBand"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@babel/helper-plugin-utils": "^7.26.5"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@babel/core": "^7.26.10",
|
|
17
|
+
"@babylonjs/core": "^8.0.0",
|
|
18
|
+
"@babylonjs/gui": "^8.0.0",
|
|
19
|
+
"@types/babel__helper-plugin-utils": "^7.10.3",
|
|
20
|
+
"@types/jest": "^29.5.14",
|
|
21
|
+
"@types/node": "^20.1.7",
|
|
22
|
+
"jest": "^29.7.0",
|
|
23
|
+
"ts-jest": "^29.3.1",
|
|
24
|
+
"typescript": "^5.8.3"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@babel/core": "^7.26.10",
|
|
28
|
+
"@babylonjs/core": "^7.0.0 || ^8.0.0",
|
|
29
|
+
"@babylonjs/gui": "^7.0.0 || ^8.0.0",
|
|
30
|
+
"reactylon": "^2.0.0"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"babel",
|
|
34
|
+
"react",
|
|
35
|
+
"babylonjs",
|
|
36
|
+
"reactylon",
|
|
37
|
+
"plugin",
|
|
38
|
+
"transform"
|
|
39
|
+
],
|
|
40
|
+
"author": "Simone De Vittorio",
|
|
41
|
+
"license": "MIT"
|
|
42
|
+
}
|