vite-plugin-tsl-operator
Version:
A Vite plugin to let you use standard JS operators (+, -, *, /, %, >, <, ==, &&, ||, !) with TSL Nodes in your Three.js project.
218 lines (163 loc) • 5.37 kB
Markdown
# vite-plugin-tsl-operator

A Vite plugin to let you use `+`, `-`, `*`, `/`, `%`, `+=`, `-=`, `*=`, `/=`, `%=`, `>`, `<`, `>=`, `<=`, `==`, `===`, `!=`, `!==`, `&&`, `||`, `!` with TSL Node in your Threejs project making the code more consise and easy to write, modify & read.
### Supported Operators
| Category | Operators |
|----------|-----------|
| Arithmetic | `+`, `-`, `*`, `/`, `%` |
| Assignment | `+=`, `-=`, `*=`, `/=`, `%=` |
| Comparison | `>`, `<`, `>=`, `<=`, `==`, `===`, `!=`, `!==` |
| Logical | `&&`, `\|\|`, `!` |
### Example
Instead of:
```js
Fn(()=>{
let x = float(1).sub(alpha.mul(color.r))
x = x.mul(4)
return x
})
```
You can now write :
```js
Fn(()=>{
let x = 1 - ( alpha * color.r )
x *= 4
return x
})
```
- [Installation](#installation)
- [Usage](#usage)
- [Options](#options)
- [How it works](#how-it-works)
- [TSL Loop Transformation](#tsl-loop-transformation)
- [About TSL](#about-tsl)
- [License](#license)
## Installation
```bash
pnpm i vite-plugin-tsl-operator
```
## Usage
Add the plugin to your Vite config :
```js
import { defineConfig } from 'vite'
import tslOperatorPlugin from 'vite-plugin-tsl-operator'
export default defineConfig({
//...
plugins: [
tslOperatorPlugin({logs:false})
//.. other plugins
]
})
```
## Options
| Option | Default | Description |
|--------|---------|-------------|
| `logs` | `false` | Log transformations to terminal |
| `autoImportMissingTSL` | `true` | Automatically add missing TSL imports (`float`, `int`, `Loop`) |
| `importSource` | `'three/tsl'` | Import source for auto-imports |
### Logging
```js
tslOperatorPlugin({ logs: true }) // log all files
tslOperatorPlugin({ logs: false }) // no logging (default)
tslOperatorPlugin({ logs: "MyShader.js" }) // log only this file
tslOperatorPlugin({ logs: ["File1.js", "File2.js"] }) // log only these files
tslOperatorPlugin({ logs: /shader/i }) // log files matching regex
```
<img width="593" alt="Screenshot 2025-02-08 at 12 55 26" src="https://github.com/user-attachments/assets/20861ec1-6c75-4d35-87da-61e3ed8a2ba9" />
### Auto Import
The plugin automatically adds missing imports when transformations require TSL types like `float`, `int`, or `Loop`.
```js
// Before transformation (no float import)
import { Fn, uv } from 'three/tsl'
Fn(() => 1 - uv())
// After transformation (float automatically added)
import { Fn, uv, float } from 'three/tsl'
Fn(() => float(1).sub(uv()))
```
To disable auto-import or use a different import source:
```js
tslOperatorPlugin({ autoImportMissingTSL: false }) // disable auto-import
tslOperatorPlugin({ importSource: 'three/webgpu' }) // use different source
```
Note : The transformation happened only when the file is call by the client or during build ( Vite optimization )
## How it works
The plugin walks your source and selectively transforms code.
It **only** looks inside `Fn(() => { ... })` blocks. Code outside is untouched.
```js
const opacity = uniform(0) // Ignored (Plain JS)
Fn(()=>{
return opacity * 3 // Transformed to opacity.mul(3)
})
```
> **Note**: Files inside `node_modules` are excluded.
### Smart Detection
The plugin automatically detects when to transform operators.
It uses **context-aware logic** to decide if an expression should be TSL or JavaScript.
**Pure numeric expressions are preserved:**
```js
Fn(() => {
const radius = 0.08
const halfRadius = radius * 0.5 // Stays as-is (pure JS math)
return smoothstep(radius, halfRadius, dist) // dist is TSL, but radius/halfRadius are numbers
})
```
Variables initialized with numeric literals are recognized as plain JavaScript numbers and won't be transformed when used in arithmetic with other numbers.
### Manual Overrides
If you have an edge case which is not cover by the smart-detection you can use `//@tsl` or `//@js` to force the behavior.
- **`//@tsl`** : Force transformation (useful for custom functions or callbacks).
- **`//@js`** : Disable transformation (keep as plain JS).
Directives apply to the **next line** or the **entire Fn** (if placed at the top).
```js
//@tsl
Fn(() => {
customNode( a > b ) // → customNode( a.greaterThan(b) )
})
Fn(() => {
//@js
const test = x + y // will not transform
const test = x + y // will transform to x.add(y)
})
```
### TSL Loop Transformation
Use `//@tsl` before `for`, `while`, or `do...while` loops to transform them into TSL `Loop()` constructs.
**For Loop:**
```js
Fn(() => {
//@tsl
for (let i = 0; i < 10; i++) {
sum += value
}
})
// Transforms to:
// Loop({ start: int(0), end: int(10), type: "int", condition: "<", name: "i" }, ({ i }) => {
// sum.addAssign(value)
// })
```
**While Loop:**
```js
Fn(() => {
//@tsl
while (x < 10) {
x += 1
}
})
// Transforms to:
// Loop(x.lessThan(10), () => {
// x.addAssign(1)
// })
```
**Do-While Loop:**
```js
Fn(() => {
//@tsl
do {
x += 1
} while (x < 10)
})
// Transforms to an IIFE (executes body once) + Loop
```
> **Note**: The plugin automatically infers `int` or `float` type based on the loop values.
## About TSL
Official wiki : [Three.js-Shading-Language](https://github.com/mrdoob/three.js/wiki/Three.js-Shading-Language)
### License
MIT