vite-plugin-opal 0.1.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/README.md +253 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +548 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# vite-plugin-opal
|
|
2
|
+
|
|
3
|
+
Vite plugin for compiling Ruby files using [Opal](https://opalrb.com/). Write Ruby code and run it in the browser with Vite's fast development experience.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- β‘οΈ **Fast Compilation**: Compile `.rb` files on-demand during development
|
|
8
|
+
- π₯ **Hot Module Replacement**: Instant updates when Ruby code changes
|
|
9
|
+
- πΊοΈ **Source Maps**: Debug Ruby code in browser DevTools
|
|
10
|
+
- π¦ **Automatic Runtime**: Opal runtime is injected automatically
|
|
11
|
+
- π **Dependency Tracking**: Handles `require` statements and tracks dependencies
|
|
12
|
+
- πΎ **Smart Caching**: Caches compilation results based on file modification time
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install vite-plugin-opal
|
|
18
|
+
# or
|
|
19
|
+
pnpm add vite-plugin-opal
|
|
20
|
+
# or
|
|
21
|
+
yarn add vite-plugin-opal
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
You also need to have Ruby and the `opal-vite` gem installed:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
gem install opal-vite
|
|
28
|
+
# or add to Gemfile
|
|
29
|
+
gem 'opal-vite'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### vite.config.ts
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { defineConfig } from 'vite'
|
|
38
|
+
import opal from 'vite-plugin-opal'
|
|
39
|
+
|
|
40
|
+
export default defineConfig({
|
|
41
|
+
plugins: [
|
|
42
|
+
opal({
|
|
43
|
+
loadPaths: ['./src'],
|
|
44
|
+
sourceMap: true,
|
|
45
|
+
debug: false
|
|
46
|
+
})
|
|
47
|
+
]
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Ruby Code (src/main.rb)
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
puts "Hello from Ruby!"
|
|
55
|
+
|
|
56
|
+
class Counter
|
|
57
|
+
def initialize
|
|
58
|
+
@count = 0
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def increment
|
|
62
|
+
@count += 1
|
|
63
|
+
puts "Count: #{@count}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
counter = Counter.new
|
|
68
|
+
counter.increment
|
|
69
|
+
counter.increment
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### JavaScript Loader (src/main_loader.js)
|
|
73
|
+
|
|
74
|
+
**Important**: Due to Vite's module resolution, you cannot directly import `.rb` files in HTML. Use a JavaScript loader:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// src/main_loader.js
|
|
78
|
+
import './main.rb'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### HTML (index.html)
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<!DOCTYPE html>
|
|
85
|
+
<html>
|
|
86
|
+
<head>
|
|
87
|
+
<title>Opal + Vite</title>
|
|
88
|
+
</head>
|
|
89
|
+
<body>
|
|
90
|
+
<h1>Opal + Vite</h1>
|
|
91
|
+
<script type="module" src="/src/main_loader.js"></script>
|
|
92
|
+
</body>
|
|
93
|
+
</html>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Options
|
|
97
|
+
|
|
98
|
+
### `gemPath`
|
|
99
|
+
|
|
100
|
+
- Type: `string`
|
|
101
|
+
- Default: `'opal-vite'`
|
|
102
|
+
|
|
103
|
+
Path to the `opal-vite` gem. Use a relative or absolute path for local development.
|
|
104
|
+
|
|
105
|
+
### `sourceMap`
|
|
106
|
+
|
|
107
|
+
- Type: `boolean`
|
|
108
|
+
- Default: `true`
|
|
109
|
+
|
|
110
|
+
Enable source map generation for debugging.
|
|
111
|
+
|
|
112
|
+
### `loadPaths`
|
|
113
|
+
|
|
114
|
+
- Type: `string[]`
|
|
115
|
+
- Default: `['./src']`
|
|
116
|
+
|
|
117
|
+
Directories to search for Ruby files when using `require`.
|
|
118
|
+
|
|
119
|
+
### `arityCheck`
|
|
120
|
+
|
|
121
|
+
- Type: `boolean`
|
|
122
|
+
- Default: `false`
|
|
123
|
+
|
|
124
|
+
Enable arity checking in compiled code.
|
|
125
|
+
|
|
126
|
+
### `freezing`
|
|
127
|
+
|
|
128
|
+
- Type: `boolean`
|
|
129
|
+
- Default: `true`
|
|
130
|
+
|
|
131
|
+
Enable object freezing for immutability.
|
|
132
|
+
|
|
133
|
+
### `debug`
|
|
134
|
+
|
|
135
|
+
- Type: `boolean`
|
|
136
|
+
- Default: `false`
|
|
137
|
+
|
|
138
|
+
Enable debug logging.
|
|
139
|
+
|
|
140
|
+
## How It Works
|
|
141
|
+
|
|
142
|
+
### Development Mode
|
|
143
|
+
|
|
144
|
+
1. Vite dev server starts and loads the plugin
|
|
145
|
+
2. When a `.rb` file is imported, the plugin's `load` hook is triggered
|
|
146
|
+
3. Plugin spawns a Ruby child process to compile the file using Opal
|
|
147
|
+
4. Compiled JavaScript is returned with source maps
|
|
148
|
+
5. HMR watches for file changes and invalidates the module
|
|
149
|
+
6. Browser receives update and hot-reloads the module
|
|
150
|
+
|
|
151
|
+
### Production Mode
|
|
152
|
+
|
|
153
|
+
1. `vite build` processes all entry points
|
|
154
|
+
2. Ruby files are compiled to optimized JavaScript
|
|
155
|
+
3. Source maps can optionally be generated
|
|
156
|
+
4. Assets are fingerprinted and added to manifest
|
|
157
|
+
|
|
158
|
+
## Advanced Usage
|
|
159
|
+
|
|
160
|
+
### Requiring Other Files
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# src/lib/helper.rb
|
|
164
|
+
module Helper
|
|
165
|
+
def self.greet(name)
|
|
166
|
+
"Hello, #{name}!"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# src/app.rb
|
|
171
|
+
require 'lib/helper'
|
|
172
|
+
|
|
173
|
+
puts Helper.greet("World")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Using Native JavaScript
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
require 'native'
|
|
180
|
+
|
|
181
|
+
# Access DOM
|
|
182
|
+
element = Native(`document.getElementById('app')`)
|
|
183
|
+
element[:textContent] = 'Updated by Ruby!'
|
|
184
|
+
|
|
185
|
+
# Use JavaScript libraries
|
|
186
|
+
`console.log('From Ruby!')`
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Multi-file Dependencies
|
|
190
|
+
|
|
191
|
+
The plugin automatically tracks dependencies and ensures proper load order:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
# lib/calculator.rb
|
|
195
|
+
class Calculator
|
|
196
|
+
def add(a, b); a + b; end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# lib/formatter.rb
|
|
200
|
+
require 'lib/calculator'
|
|
201
|
+
|
|
202
|
+
class Formatter
|
|
203
|
+
def format_sum(a, b)
|
|
204
|
+
calc = Calculator.new
|
|
205
|
+
"#{a} + #{b} = #{calc.add(a, b)}"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# app.rb
|
|
210
|
+
require 'lib/formatter'
|
|
211
|
+
puts Formatter.new.format_sum(5, 3)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Troubleshooting
|
|
215
|
+
|
|
216
|
+
### Ruby not found
|
|
217
|
+
|
|
218
|
+
Ensure Ruby is in your PATH:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
which ruby
|
|
222
|
+
ruby --version
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Opal gem not found
|
|
226
|
+
|
|
227
|
+
Install the opal-vite gem:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
gem install opal-vite
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### HMR not working
|
|
234
|
+
|
|
235
|
+
- Ensure Vite dev server is running
|
|
236
|
+
- Use JavaScript loaders for `.rb` files
|
|
237
|
+
- Check browser console for errors
|
|
238
|
+
|
|
239
|
+
### Compilation errors
|
|
240
|
+
|
|
241
|
+
- Check Ruby syntax with `ruby -c yourfile.rb`
|
|
242
|
+
- Verify all required files are in load paths
|
|
243
|
+
- Some Ruby features are not supported by Opal
|
|
244
|
+
|
|
245
|
+
## Related Projects
|
|
246
|
+
|
|
247
|
+
- [opal-vite](../../gems/opal-vite) - Core Ruby gem
|
|
248
|
+
- [opal-vite-rails](../../gems/opal-vite-rails) - Rails integration
|
|
249
|
+
- [Opal](https://opalrb.com/) - Ruby to JavaScript compiler
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface OpalPluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Path to the opal-vite gem
|
|
6
|
+
* @default 'opal-vite'
|
|
7
|
+
*/
|
|
8
|
+
gemPath?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Enable source map generation
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
sourceMap?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Load paths for Ruby requires
|
|
16
|
+
* @default ['./src']
|
|
17
|
+
*/
|
|
18
|
+
loadPaths?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Enable arity checking
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
arityCheck?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Enable object freezing
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
freezing?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Enable debug logging
|
|
31
|
+
* @default false
|
|
32
|
+
*/
|
|
33
|
+
debug?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare function opalPlugin(options?: OpalPluginOptions): Plugin;
|
|
37
|
+
|
|
38
|
+
export { type OpalPluginOptions, opalPlugin as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import * as path2 from 'path';
|
|
4
|
+
import * as chokidar from 'chokidar';
|
|
5
|
+
|
|
6
|
+
// src/compiler.ts
|
|
7
|
+
var OpalCompiler = class {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
10
|
+
this.runtimeCache = null;
|
|
11
|
+
this.options = {
|
|
12
|
+
gemPath: options.gemPath || "opal-vite",
|
|
13
|
+
sourceMap: options.sourceMap !== false,
|
|
14
|
+
loadPaths: options.loadPaths || ["./src"],
|
|
15
|
+
arityCheck: options.arityCheck || false,
|
|
16
|
+
freezing: options.freezing !== false,
|
|
17
|
+
debug: options.debug || false
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async compile(filePath) {
|
|
21
|
+
const cached = this.cache.get(filePath);
|
|
22
|
+
if (cached) {
|
|
23
|
+
try {
|
|
24
|
+
const stat3 = await fs.stat(filePath);
|
|
25
|
+
if (stat3.mtimeMs <= cached.mtime) {
|
|
26
|
+
this.log(`Cache hit: ${filePath}`);
|
|
27
|
+
return {
|
|
28
|
+
code: cached.code,
|
|
29
|
+
map: cached.map,
|
|
30
|
+
dependencies: cached.dependencies
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
} catch (e) {
|
|
34
|
+
this.cache.delete(filePath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
this.log(`Compiling: ${filePath}`);
|
|
38
|
+
const result = await this.compileViaRuby(filePath);
|
|
39
|
+
try {
|
|
40
|
+
const stat3 = await fs.stat(filePath);
|
|
41
|
+
this.cache.set(filePath, {
|
|
42
|
+
...result,
|
|
43
|
+
mtime: stat3.mtimeMs
|
|
44
|
+
});
|
|
45
|
+
} catch (e) {
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
async getOpalRuntime() {
|
|
50
|
+
if (this.runtimeCache) {
|
|
51
|
+
return this.runtimeCache;
|
|
52
|
+
}
|
|
53
|
+
this.log("Loading Opal runtime");
|
|
54
|
+
const runtime = await this.getRuntimeViaRuby();
|
|
55
|
+
this.runtimeCache = runtime;
|
|
56
|
+
return runtime;
|
|
57
|
+
}
|
|
58
|
+
clearCache(filePath) {
|
|
59
|
+
if (filePath) {
|
|
60
|
+
this.cache.delete(filePath);
|
|
61
|
+
this.log(`Cache cleared: ${filePath}`);
|
|
62
|
+
} else {
|
|
63
|
+
this.cache.clear();
|
|
64
|
+
this.log("Cache cleared (all)");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async compileViaRuby(filePath) {
|
|
68
|
+
return new Promise((resolve4, reject) => {
|
|
69
|
+
const args = [
|
|
70
|
+
"-I",
|
|
71
|
+
this.resolveGemLibPath(),
|
|
72
|
+
"-r",
|
|
73
|
+
"opal-vite",
|
|
74
|
+
"-e",
|
|
75
|
+
this.getCompilerScript(),
|
|
76
|
+
filePath
|
|
77
|
+
];
|
|
78
|
+
this.log(`Spawning Ruby: ruby ${args.join(" ")}`);
|
|
79
|
+
const ruby = spawn("ruby", args);
|
|
80
|
+
let stdout = "";
|
|
81
|
+
let stderr = "";
|
|
82
|
+
ruby.stdout.on("data", (data) => {
|
|
83
|
+
stdout += data.toString();
|
|
84
|
+
});
|
|
85
|
+
ruby.stderr.on("data", (data) => {
|
|
86
|
+
stderr += data.toString();
|
|
87
|
+
});
|
|
88
|
+
ruby.on("close", (code) => {
|
|
89
|
+
if (code !== 0) {
|
|
90
|
+
reject(new Error(`Opal compilation failed:
|
|
91
|
+
${stderr}`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const result = JSON.parse(stdout);
|
|
96
|
+
resolve4(result);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
reject(new Error(`Failed to parse compiler output:
|
|
99
|
+
${stdout}
|
|
100
|
+
|
|
101
|
+
Error: ${e}`));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
ruby.on("error", (err) => {
|
|
105
|
+
reject(new Error(`Failed to spawn Ruby process: ${err.message}`));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async getRuntimeViaRuby() {
|
|
110
|
+
return new Promise((resolve4, reject) => {
|
|
111
|
+
const args = [
|
|
112
|
+
"-I",
|
|
113
|
+
this.resolveGemLibPath(),
|
|
114
|
+
"-r",
|
|
115
|
+
"opal-vite",
|
|
116
|
+
"-e",
|
|
117
|
+
"puts Opal::Vite::Compiler.runtime_code"
|
|
118
|
+
];
|
|
119
|
+
const ruby = spawn("ruby", args);
|
|
120
|
+
let stdout = "";
|
|
121
|
+
let stderr = "";
|
|
122
|
+
ruby.stdout.on("data", (data) => {
|
|
123
|
+
stdout += data.toString();
|
|
124
|
+
});
|
|
125
|
+
ruby.stderr.on("data", (data) => {
|
|
126
|
+
stderr += data.toString();
|
|
127
|
+
});
|
|
128
|
+
ruby.on("close", (code) => {
|
|
129
|
+
if (code === 0) {
|
|
130
|
+
resolve4(stdout);
|
|
131
|
+
} else {
|
|
132
|
+
reject(new Error(`Failed to get Opal runtime:
|
|
133
|
+
${stderr}`));
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
ruby.on("error", (err) => {
|
|
137
|
+
reject(new Error(`Failed to spawn Ruby process: ${err.message}`));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
getCompilerScript() {
|
|
142
|
+
return `
|
|
143
|
+
require 'opal-vite'
|
|
144
|
+
file_path = ARGV[0]
|
|
145
|
+
Opal::Vite.compile_for_vite(file_path)
|
|
146
|
+
`.trim();
|
|
147
|
+
}
|
|
148
|
+
resolveGemLibPath() {
|
|
149
|
+
if (this.options.gemPath.startsWith(".") || this.options.gemPath.startsWith("/")) {
|
|
150
|
+
return path2.resolve(this.options.gemPath, "lib");
|
|
151
|
+
}
|
|
152
|
+
return this.options.gemPath;
|
|
153
|
+
}
|
|
154
|
+
log(message) {
|
|
155
|
+
if (this.options.debug) {
|
|
156
|
+
console.log(`[vite-plugin-opal] ${message}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var OpalResolver = class {
|
|
161
|
+
constructor(options = {}) {
|
|
162
|
+
this.resolveCache = /* @__PURE__ */ new Map();
|
|
163
|
+
this.options = {
|
|
164
|
+
gemPath: options.gemPath || "opal-vite",
|
|
165
|
+
sourceMap: options.sourceMap !== false,
|
|
166
|
+
loadPaths: options.loadPaths || ["./src"],
|
|
167
|
+
arityCheck: options.arityCheck || false,
|
|
168
|
+
freezing: options.freezing !== false,
|
|
169
|
+
debug: options.debug || false
|
|
170
|
+
};
|
|
171
|
+
this.loadPaths = this.options.loadPaths;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Resolve a Ruby file import
|
|
175
|
+
* Supports:
|
|
176
|
+
* - Absolute paths: /path/to/file.rb
|
|
177
|
+
* - Relative paths: ./file.rb, ../file.rb
|
|
178
|
+
* - Load path resolution: file (searches in loadPaths)
|
|
179
|
+
*/
|
|
180
|
+
async resolve(id, importer) {
|
|
181
|
+
const cacheKey = `${id}|${importer || ""}`;
|
|
182
|
+
if (this.resolveCache.has(cacheKey)) {
|
|
183
|
+
return this.resolveCache.get(cacheKey);
|
|
184
|
+
}
|
|
185
|
+
let resolved = null;
|
|
186
|
+
if (id.endsWith(".rb") || !this.hasExtension(id)) {
|
|
187
|
+
resolved = await this.resolveAbsolute(id) || await this.resolveRelative(id, importer) || await this.resolveFromLoadPaths(id);
|
|
188
|
+
}
|
|
189
|
+
this.resolveCache.set(cacheKey, resolved);
|
|
190
|
+
if (resolved && this.options.debug) {
|
|
191
|
+
console.log(`[vite-plugin-opal] Resolved: ${id} -> ${resolved}`);
|
|
192
|
+
}
|
|
193
|
+
return resolved;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Clear the resolution cache
|
|
197
|
+
*/
|
|
198
|
+
clearCache(filePath) {
|
|
199
|
+
if (filePath) {
|
|
200
|
+
for (const [key, value] of this.resolveCache.entries()) {
|
|
201
|
+
if (value === filePath || key.startsWith(filePath)) {
|
|
202
|
+
this.resolveCache.delete(key);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
this.resolveCache.clear();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get all load paths
|
|
211
|
+
*/
|
|
212
|
+
getLoadPaths() {
|
|
213
|
+
return [...this.loadPaths];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Add a load path
|
|
217
|
+
*/
|
|
218
|
+
addLoadPath(loadPath) {
|
|
219
|
+
if (!this.loadPaths.includes(loadPath)) {
|
|
220
|
+
this.loadPaths.push(loadPath);
|
|
221
|
+
this.clearCache();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async resolveAbsolute(id) {
|
|
225
|
+
if (!path2.isAbsolute(id)) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
if (await this.fileExists(id)) {
|
|
229
|
+
return id;
|
|
230
|
+
}
|
|
231
|
+
if (!id.endsWith(".rb")) {
|
|
232
|
+
const withExt = `${id}.rb`;
|
|
233
|
+
if (await this.fileExists(withExt)) {
|
|
234
|
+
return withExt;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
async resolveRelative(id, importer) {
|
|
240
|
+
if (!id.startsWith(".") || !importer) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const importerDir = path2.dirname(importer);
|
|
244
|
+
const resolved = path2.resolve(importerDir, id);
|
|
245
|
+
if (await this.fileExists(resolved)) {
|
|
246
|
+
return resolved;
|
|
247
|
+
}
|
|
248
|
+
if (!id.endsWith(".rb")) {
|
|
249
|
+
const withExt = `${resolved}.rb`;
|
|
250
|
+
if (await this.fileExists(withExt)) {
|
|
251
|
+
return withExt;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
async resolveFromLoadPaths(id) {
|
|
257
|
+
const baseId = id.endsWith(".rb") ? id.slice(0, -3) : id;
|
|
258
|
+
for (const loadPath of this.loadPaths) {
|
|
259
|
+
const fullPath = path2.resolve(loadPath, id);
|
|
260
|
+
if (await this.fileExists(fullPath)) {
|
|
261
|
+
return fullPath;
|
|
262
|
+
}
|
|
263
|
+
const withExt = path2.resolve(loadPath, `${baseId}.rb`);
|
|
264
|
+
if (await this.fileExists(withExt)) {
|
|
265
|
+
return withExt;
|
|
266
|
+
}
|
|
267
|
+
const indexPath = path2.resolve(loadPath, baseId, "index.rb");
|
|
268
|
+
if (await this.fileExists(indexPath)) {
|
|
269
|
+
return indexPath;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
async fileExists(filePath) {
|
|
275
|
+
try {
|
|
276
|
+
const stat3 = await fs.stat(filePath);
|
|
277
|
+
return stat3.isFile();
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
hasExtension(filePath) {
|
|
283
|
+
const ext = path2.extname(filePath);
|
|
284
|
+
return ext !== "";
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
var OpalHMRManager = class {
|
|
288
|
+
constructor(server, compiler, resolver, options) {
|
|
289
|
+
this.dependencyGraph = /* @__PURE__ */ new Map();
|
|
290
|
+
this.server = server;
|
|
291
|
+
this.compiler = compiler;
|
|
292
|
+
this.resolver = resolver;
|
|
293
|
+
this.options = options;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Setup HMR file watching
|
|
297
|
+
*/
|
|
298
|
+
setup() {
|
|
299
|
+
this.log("Setting up HMR for .rb files");
|
|
300
|
+
this.watcher = chokidar.watch("**/*.rb", {
|
|
301
|
+
ignored: /node_modules/,
|
|
302
|
+
persistent: true,
|
|
303
|
+
cwd: this.server.config.root,
|
|
304
|
+
ignoreInitial: true
|
|
305
|
+
// Don't trigger on initial scan
|
|
306
|
+
});
|
|
307
|
+
this.watcher.on("change", async (filePath) => {
|
|
308
|
+
await this.handleFileChange(filePath);
|
|
309
|
+
});
|
|
310
|
+
this.watcher.on("add", async (filePath) => {
|
|
311
|
+
this.log(`New file detected: ${filePath}`);
|
|
312
|
+
await this.handleFileChange(filePath);
|
|
313
|
+
});
|
|
314
|
+
this.watcher.on("unlink", (filePath) => {
|
|
315
|
+
this.log(`File removed: ${filePath}`);
|
|
316
|
+
const absolutePath = path2.resolve(this.server.config.root, filePath);
|
|
317
|
+
this.compiler.clearCache(absolutePath);
|
|
318
|
+
this.resolver.clearCache(absolutePath);
|
|
319
|
+
this.dependencyGraph.delete(absolutePath);
|
|
320
|
+
});
|
|
321
|
+
this.server.httpServer?.on("close", () => {
|
|
322
|
+
this.cleanup();
|
|
323
|
+
});
|
|
324
|
+
this.log("HMR setup complete");
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Cleanup resources
|
|
328
|
+
*/
|
|
329
|
+
cleanup() {
|
|
330
|
+
if (this.watcher) {
|
|
331
|
+
this.log("Cleaning up HMR watcher");
|
|
332
|
+
this.watcher.close();
|
|
333
|
+
this.watcher = void 0;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Handle file change and trigger HMR update
|
|
338
|
+
*/
|
|
339
|
+
async handleFileChange(filePath) {
|
|
340
|
+
const absolutePath = path2.resolve(this.server.config.root, filePath);
|
|
341
|
+
this.log(`File changed: ${filePath}`);
|
|
342
|
+
try {
|
|
343
|
+
this.compiler.clearCache(absolutePath);
|
|
344
|
+
this.resolver.clearCache(absolutePath);
|
|
345
|
+
const module = this.server.moduleGraph.getModuleById(absolutePath);
|
|
346
|
+
if (!module) {
|
|
347
|
+
this.log(`Module not found in graph: ${filePath}`, "warn");
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const modulesToUpdate = /* @__PURE__ */ new Set([module]);
|
|
351
|
+
await this.collectDependentModules(module, modulesToUpdate);
|
|
352
|
+
for (const mod of modulesToUpdate) {
|
|
353
|
+
this.server.moduleGraph.invalidateModule(mod);
|
|
354
|
+
this.log(`Invalidated: ${mod.url}`);
|
|
355
|
+
}
|
|
356
|
+
const updates = Array.from(modulesToUpdate).map((mod) => ({
|
|
357
|
+
type: "js-update",
|
|
358
|
+
path: mod.url,
|
|
359
|
+
acceptedPath: mod.url,
|
|
360
|
+
timestamp: Date.now()
|
|
361
|
+
}));
|
|
362
|
+
this.server.ws.send({
|
|
363
|
+
type: "update",
|
|
364
|
+
updates
|
|
365
|
+
});
|
|
366
|
+
this.log(`\u2713 HMR update sent for ${updates.length} module(s)`, "success");
|
|
367
|
+
} catch (error) {
|
|
368
|
+
this.handleError(filePath, error);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Collect all modules that depend on the given module
|
|
373
|
+
*/
|
|
374
|
+
async collectDependentModules(module, result) {
|
|
375
|
+
for (const importer of module.importers) {
|
|
376
|
+
if (!result.has(importer)) {
|
|
377
|
+
result.add(importer);
|
|
378
|
+
await this.collectDependentModules(importer, result);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Handle compilation or HMR errors
|
|
384
|
+
*/
|
|
385
|
+
handleError(filePath, error) {
|
|
386
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
387
|
+
this.log(`Error processing ${filePath}: ${errorMessage}`, "error");
|
|
388
|
+
this.server.ws.send({
|
|
389
|
+
type: "error",
|
|
390
|
+
err: {
|
|
391
|
+
message: `Opal compilation failed for ${filePath}`,
|
|
392
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
393
|
+
id: filePath,
|
|
394
|
+
frame: this.extractErrorFrame(errorMessage),
|
|
395
|
+
plugin: "vite-plugin-opal",
|
|
396
|
+
loc: this.extractErrorLocation(errorMessage)
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Extract error frame from error message
|
|
402
|
+
*/
|
|
403
|
+
extractErrorFrame(errorMessage) {
|
|
404
|
+
const lines = errorMessage.split("\n");
|
|
405
|
+
const relevantLines = lines.slice(0, 10).join("\n");
|
|
406
|
+
return relevantLines || void 0;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Extract error location from error message
|
|
410
|
+
*/
|
|
411
|
+
extractErrorLocation(errorMessage) {
|
|
412
|
+
const match = errorMessage.match(/(.+):(\d+):(\d+)/);
|
|
413
|
+
if (match) {
|
|
414
|
+
return {
|
|
415
|
+
file: match[1],
|
|
416
|
+
line: parseInt(match[2], 10),
|
|
417
|
+
column: parseInt(match[3], 10)
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return void 0;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Log HMR messages
|
|
424
|
+
*/
|
|
425
|
+
log(message, level = "info") {
|
|
426
|
+
if (!this.options.debug && level === "info") {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const prefix = "[vite-plugin-opal:hmr]";
|
|
430
|
+
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
431
|
+
const colors = {
|
|
432
|
+
info: "\x1B[36m",
|
|
433
|
+
// Cyan
|
|
434
|
+
warn: "\x1B[33m",
|
|
435
|
+
// Yellow
|
|
436
|
+
error: "\x1B[31m",
|
|
437
|
+
// Red
|
|
438
|
+
success: "\x1B[32m"
|
|
439
|
+
// Green
|
|
440
|
+
};
|
|
441
|
+
const reset = "\x1B[0m";
|
|
442
|
+
const color = colors[level];
|
|
443
|
+
const levelText = level.toUpperCase().padEnd(7);
|
|
444
|
+
console.log(`${color}${prefix} ${timestamp} [${levelText}]${reset} ${message}`);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Get dependency graph information (for debugging)
|
|
448
|
+
*/
|
|
449
|
+
getDependencyGraph() {
|
|
450
|
+
return new Map(this.dependencyGraph);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// src/index.ts
|
|
455
|
+
var VIRTUAL_RUNTIME_ID = "/@opal-runtime";
|
|
456
|
+
var VIRTUAL_RUNTIME_PREFIX = "\0" + VIRTUAL_RUNTIME_ID;
|
|
457
|
+
function opalPlugin(options = {}) {
|
|
458
|
+
const compiler = new OpalCompiler(options);
|
|
459
|
+
const resolver = new OpalResolver(options);
|
|
460
|
+
let server;
|
|
461
|
+
let hmrManager;
|
|
462
|
+
return {
|
|
463
|
+
name: "vite-plugin-opal",
|
|
464
|
+
enforce: "pre",
|
|
465
|
+
// Run before other plugins
|
|
466
|
+
// Mark .rb files as valid imports
|
|
467
|
+
async resolveId(id, importer) {
|
|
468
|
+
if (id === VIRTUAL_RUNTIME_ID) {
|
|
469
|
+
if (options.debug) {
|
|
470
|
+
console.log(`[vite-plugin-opal] Resolved virtual runtime: ${VIRTUAL_RUNTIME_PREFIX}`);
|
|
471
|
+
}
|
|
472
|
+
return VIRTUAL_RUNTIME_PREFIX;
|
|
473
|
+
}
|
|
474
|
+
if (id.endsWith(".rb") || !id.includes(".") && !id.startsWith("/")) {
|
|
475
|
+
const resolved = await resolver.resolve(id, importer);
|
|
476
|
+
if (options.debug) {
|
|
477
|
+
console.log(`[vite-plugin-opal] resolveId: ${id} -> ${resolved}`);
|
|
478
|
+
}
|
|
479
|
+
return resolved;
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
},
|
|
483
|
+
// Load and compile .rb files
|
|
484
|
+
async load(id) {
|
|
485
|
+
if (id === VIRTUAL_RUNTIME_PREFIX) {
|
|
486
|
+
if (options.debug) {
|
|
487
|
+
console.log(`[vite-plugin-opal] Loading Opal runtime...`);
|
|
488
|
+
}
|
|
489
|
+
const runtime = await compiler.getOpalRuntime();
|
|
490
|
+
if (options.debug) {
|
|
491
|
+
console.log(`[vite-plugin-opal] Opal runtime loaded: ${runtime.length} bytes`);
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
code: runtime,
|
|
495
|
+
map: null
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
if (id.endsWith(".rb")) {
|
|
499
|
+
if (options.debug) {
|
|
500
|
+
console.log(`[vite-plugin-opal] load: Compiling ${id}`);
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
const result = await compiler.compile(id);
|
|
504
|
+
if (options.debug) {
|
|
505
|
+
console.log(`[vite-plugin-opal] load: Compiled ${id} -> ${result.code.length} bytes`);
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
code: result.code,
|
|
509
|
+
map: result.map ? JSON.parse(result.map) : null
|
|
510
|
+
};
|
|
511
|
+
} catch (error) {
|
|
512
|
+
console.error(`[vite-plugin-opal] Compilation error for ${id}:`, error);
|
|
513
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
},
|
|
518
|
+
// Auto-inject Opal runtime into HTML
|
|
519
|
+
transformIndexHtml(html) {
|
|
520
|
+
const runtimeScript = `<script type="module" src="${VIRTUAL_RUNTIME_ID}"></script>`;
|
|
521
|
+
if (html.includes("</head>")) {
|
|
522
|
+
return html.replace("</head>", ` ${runtimeScript}
|
|
523
|
+
</head>`);
|
|
524
|
+
} else if (html.includes("<head>")) {
|
|
525
|
+
return html.replace("<head>", `<head>
|
|
526
|
+
${runtimeScript}`);
|
|
527
|
+
} else {
|
|
528
|
+
return `${runtimeScript}
|
|
529
|
+
${html}`;
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
// Setup HMR for .rb files
|
|
533
|
+
configureServer(_server) {
|
|
534
|
+
server = _server;
|
|
535
|
+
hmrManager = new OpalHMRManager(server, compiler, resolver, options);
|
|
536
|
+
hmrManager.setup();
|
|
537
|
+
return () => {
|
|
538
|
+
if (hmrManager) {
|
|
539
|
+
hmrManager.cleanup();
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export { opalPlugin as default };
|
|
547
|
+
//# sourceMappingURL=index.js.map
|
|
548
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compiler.ts","../src/resolver.ts","../src/hmr.ts","../src/index.ts"],"names":["stat","resolve","path","fs2","path3"],"mappings":"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","file":"index.js","sourcesContent":["import { spawn } from 'child_process'\nimport * as fs from 'fs/promises'\nimport * as path from 'path'\nimport type { OpalPluginOptions, CompileResult, CacheEntry } from './types'\n\nexport class OpalCompiler {\n private options: Required<OpalPluginOptions>\n private cache: Map<string, CacheEntry> = new Map()\n private runtimeCache: string | null = null\n\n constructor(options: OpalPluginOptions = {}) {\n this.options = {\n gemPath: options.gemPath || 'opal-vite',\n sourceMap: options.sourceMap !== false,\n loadPaths: options.loadPaths || ['./src'],\n arityCheck: options.arityCheck || false,\n freezing: options.freezing !== false,\n debug: options.debug || false\n }\n }\n\n async compile(filePath: string): Promise<CompileResult> {\n // Check cache\n const cached = this.cache.get(filePath)\n if (cached) {\n try {\n const stat = await fs.stat(filePath)\n if (stat.mtimeMs <= cached.mtime) {\n this.log(`Cache hit: ${filePath}`)\n return {\n code: cached.code,\n map: cached.map,\n dependencies: cached.dependencies\n }\n }\n } catch (e) {\n // File might have been deleted, remove from cache\n this.cache.delete(filePath)\n }\n }\n\n // Compile\n this.log(`Compiling: ${filePath}`)\n const result = await this.compileViaRuby(filePath)\n\n // Cache the result\n try {\n const stat = await fs.stat(filePath)\n this.cache.set(filePath, {\n ...result,\n mtime: stat.mtimeMs\n })\n } catch (e) {\n // Ignore if we can't stat the file\n }\n\n return result\n }\n\n async getOpalRuntime(): Promise<string> {\n if (this.runtimeCache) {\n return this.runtimeCache\n }\n\n this.log('Loading Opal runtime')\n const runtime = await this.getRuntimeViaRuby()\n this.runtimeCache = runtime\n return runtime\n }\n\n clearCache(filePath?: string): void {\n if (filePath) {\n this.cache.delete(filePath)\n this.log(`Cache cleared: ${filePath}`)\n } else {\n this.cache.clear()\n this.log('Cache cleared (all)')\n }\n }\n\n private async compileViaRuby(filePath: string): Promise<CompileResult> {\n return new Promise((resolve, reject) => {\n const args = [\n '-I', this.resolveGemLibPath(),\n '-r', 'opal-vite',\n '-e', this.getCompilerScript(),\n filePath\n ]\n\n this.log(`Spawning Ruby: ruby ${args.join(' ')}`)\n\n const ruby = spawn('ruby', args)\n\n let stdout = ''\n let stderr = ''\n\n ruby.stdout.on('data', (data) => {\n stdout += data.toString()\n })\n\n ruby.stderr.on('data', (data) => {\n stderr += data.toString()\n })\n\n ruby.on('close', (code) => {\n if (code !== 0) {\n reject(new Error(`Opal compilation failed:\\n${stderr}`))\n return\n }\n\n try {\n const result = JSON.parse(stdout)\n resolve(result)\n } catch (e) {\n reject(new Error(`Failed to parse compiler output:\\n${stdout}\\n\\nError: ${e}`))\n }\n })\n\n ruby.on('error', (err) => {\n reject(new Error(`Failed to spawn Ruby process: ${err.message}`))\n })\n })\n }\n\n private async getRuntimeViaRuby(): Promise<string> {\n return new Promise((resolve, reject) => {\n const args = [\n '-I', this.resolveGemLibPath(),\n '-r', 'opal-vite',\n '-e', 'puts Opal::Vite::Compiler.runtime_code'\n ]\n\n const ruby = spawn('ruby', args)\n\n let stdout = ''\n let stderr = ''\n\n ruby.stdout.on('data', (data) => {\n stdout += data.toString()\n })\n\n ruby.stderr.on('data', (data) => {\n stderr += data.toString()\n })\n\n ruby.on('close', (code) => {\n if (code === 0) {\n resolve(stdout)\n } else {\n reject(new Error(`Failed to get Opal runtime:\\n${stderr}`))\n }\n })\n\n ruby.on('error', (err) => {\n reject(new Error(`Failed to spawn Ruby process: ${err.message}`))\n })\n })\n }\n\n private getCompilerScript(): string {\n return `\n require 'opal-vite'\n file_path = ARGV[0]\n Opal::Vite.compile_for_vite(file_path)\n `.trim()\n }\n\n private resolveGemLibPath(): string {\n // Try to resolve the gem path\n // In development, this points to our local gem\n // In production, bundler will handle it\n if (this.options.gemPath.startsWith('.') || this.options.gemPath.startsWith('/')) {\n return path.resolve(this.options.gemPath, 'lib')\n }\n return this.options.gemPath\n }\n\n private log(message: string): void {\n if (this.options.debug) {\n console.log(`[vite-plugin-opal] ${message}`)\n }\n }\n}\n","import * as path from 'path'\nimport * as fs from 'fs/promises'\nimport type { OpalPluginOptions } from './types'\n\nexport class OpalResolver {\n private options: Required<OpalPluginOptions>\n private loadPaths: string[]\n private resolveCache: Map<string, string | null> = new Map()\n\n constructor(options: OpalPluginOptions = {}) {\n this.options = {\n gemPath: options.gemPath || 'opal-vite',\n sourceMap: options.sourceMap !== false,\n loadPaths: options.loadPaths || ['./src'],\n arityCheck: options.arityCheck || false,\n freezing: options.freezing !== false,\n debug: options.debug || false\n }\n this.loadPaths = this.options.loadPaths\n }\n\n /**\n * Resolve a Ruby file import\n * Supports:\n * - Absolute paths: /path/to/file.rb\n * - Relative paths: ./file.rb, ../file.rb\n * - Load path resolution: file (searches in loadPaths)\n */\n async resolve(id: string, importer?: string): Promise<string | null> {\n // Check cache first\n const cacheKey = `${id}|${importer || ''}`\n if (this.resolveCache.has(cacheKey)) {\n return this.resolveCache.get(cacheKey)!\n }\n\n let resolved: string | null = null\n\n // Only resolve .rb files or files without extension\n if (id.endsWith('.rb') || !this.hasExtension(id)) {\n // Try different resolution strategies\n resolved = await this.resolveAbsolute(id) ||\n await this.resolveRelative(id, importer) ||\n await this.resolveFromLoadPaths(id)\n }\n\n // Cache the result\n this.resolveCache.set(cacheKey, resolved)\n\n if (resolved && this.options.debug) {\n console.log(`[vite-plugin-opal] Resolved: ${id} -> ${resolved}`)\n }\n\n return resolved\n }\n\n /**\n * Clear the resolution cache\n */\n clearCache(filePath?: string): void {\n if (filePath) {\n // Clear cache entries related to this file\n for (const [key, value] of this.resolveCache.entries()) {\n if (value === filePath || key.startsWith(filePath)) {\n this.resolveCache.delete(key)\n }\n }\n } else {\n this.resolveCache.clear()\n }\n }\n\n /**\n * Get all load paths\n */\n getLoadPaths(): string[] {\n return [...this.loadPaths]\n }\n\n /**\n * Add a load path\n */\n addLoadPath(loadPath: string): void {\n if (!this.loadPaths.includes(loadPath)) {\n this.loadPaths.push(loadPath)\n this.clearCache() // Clear cache when load paths change\n }\n }\n\n private async resolveAbsolute(id: string): Promise<string | null> {\n if (!path.isAbsolute(id)) {\n return null\n }\n\n // Try as-is\n if (await this.fileExists(id)) {\n return id\n }\n\n // Try with .rb extension\n if (!id.endsWith('.rb')) {\n const withExt = `${id}.rb`\n if (await this.fileExists(withExt)) {\n return withExt\n }\n }\n\n return null\n }\n\n private async resolveRelative(id: string, importer?: string): Promise<string | null> {\n if (!id.startsWith('.') || !importer) {\n return null\n }\n\n const importerDir = path.dirname(importer)\n const resolved = path.resolve(importerDir, id)\n\n // Try as-is\n if (await this.fileExists(resolved)) {\n return resolved\n }\n\n // Try with .rb extension\n if (!id.endsWith('.rb')) {\n const withExt = `${resolved}.rb`\n if (await this.fileExists(withExt)) {\n return withExt\n }\n }\n\n return null\n }\n\n private async resolveFromLoadPaths(id: string): Promise<string | null> {\n // Remove .rb extension for load path search\n const baseId = id.endsWith('.rb') ? id.slice(0, -3) : id\n\n for (const loadPath of this.loadPaths) {\n // Try with original id\n const fullPath = path.resolve(loadPath, id)\n if (await this.fileExists(fullPath)) {\n return fullPath\n }\n\n // Try with .rb extension\n const withExt = path.resolve(loadPath, `${baseId}.rb`)\n if (await this.fileExists(withExt)) {\n return withExt\n }\n\n // Try as a directory with index.rb\n const indexPath = path.resolve(loadPath, baseId, 'index.rb')\n if (await this.fileExists(indexPath)) {\n return indexPath\n }\n }\n\n return null\n }\n\n private async fileExists(filePath: string): Promise<boolean> {\n try {\n const stat = await fs.stat(filePath)\n return stat.isFile()\n } catch {\n return false\n }\n }\n\n private hasExtension(filePath: string): boolean {\n const ext = path.extname(filePath)\n return ext !== ''\n }\n}\n","import type { ViteDevServer, ModuleNode, Update } from 'vite'\nimport type { OpalCompiler } from './compiler'\nimport type { OpalResolver } from './resolver'\nimport type { OpalPluginOptions } from './types'\nimport * as path from 'path'\nimport * as chokidar from 'chokidar'\n\nexport interface HMRManager {\n setup(): void\n cleanup(): void\n handleFileChange(filePath: string): Promise<void>\n}\n\nexport class OpalHMRManager implements HMRManager {\n private server: ViteDevServer\n private compiler: OpalCompiler\n private resolver: OpalResolver\n private options: OpalPluginOptions\n private watcher?: chokidar.FSWatcher\n private dependencyGraph: Map<string, Set<string>> = new Map()\n\n constructor(\n server: ViteDevServer,\n compiler: OpalCompiler,\n resolver: OpalResolver,\n options: OpalPluginOptions\n ) {\n this.server = server\n this.compiler = compiler\n this.resolver = resolver\n this.options = options\n }\n\n /**\n * Setup HMR file watching\n */\n setup(): void {\n this.log('Setting up HMR for .rb files')\n\n // Watch .rb files for changes\n this.watcher = chokidar.watch('**/*.rb', {\n ignored: /node_modules/,\n persistent: true,\n cwd: this.server.config.root,\n ignoreInitial: true // Don't trigger on initial scan\n })\n\n this.watcher.on('change', async (filePath: string) => {\n await this.handleFileChange(filePath)\n })\n\n this.watcher.on('add', async (filePath: string) => {\n this.log(`New file detected: ${filePath}`)\n await this.handleFileChange(filePath)\n })\n\n this.watcher.on('unlink', (filePath: string) => {\n this.log(`File removed: ${filePath}`)\n const absolutePath = path.resolve(this.server.config.root, filePath)\n this.compiler.clearCache(absolutePath)\n this.resolver.clearCache(absolutePath)\n this.dependencyGraph.delete(absolutePath)\n })\n\n // Cleanup on server close\n this.server.httpServer?.on('close', () => {\n this.cleanup()\n })\n\n this.log('HMR setup complete')\n }\n\n /**\n * Cleanup resources\n */\n cleanup(): void {\n if (this.watcher) {\n this.log('Cleaning up HMR watcher')\n this.watcher.close()\n this.watcher = undefined\n }\n }\n\n /**\n * Handle file change and trigger HMR update\n */\n async handleFileChange(filePath: string): Promise<void> {\n const absolutePath = path.resolve(this.server.config.root, filePath)\n\n this.log(`File changed: ${filePath}`)\n\n try {\n // Clear caches for the changed file\n this.compiler.clearCache(absolutePath)\n this.resolver.clearCache(absolutePath)\n\n // Get the module from the module graph\n const module = this.server.moduleGraph.getModuleById(absolutePath)\n\n if (!module) {\n this.log(`Module not found in graph: ${filePath}`, 'warn')\n return\n }\n\n // Collect all modules that need to be updated\n const modulesToUpdate = new Set<ModuleNode>([module])\n\n // Find dependent modules (modules that import this one)\n await this.collectDependentModules(module, modulesToUpdate)\n\n // Invalidate all affected modules\n for (const mod of modulesToUpdate) {\n this.server.moduleGraph.invalidateModule(mod)\n this.log(`Invalidated: ${mod.url}`)\n }\n\n // Build HMR updates\n const updates: Update[] = Array.from(modulesToUpdate).map(mod => ({\n type: 'js-update' as const,\n path: mod.url,\n acceptedPath: mod.url,\n timestamp: Date.now()\n }))\n\n // Send HMR update to browser\n this.server.ws.send({\n type: 'update',\n updates\n })\n\n this.log(`β HMR update sent for ${updates.length} module(s)`, 'success')\n } catch (error) {\n this.handleError(filePath, error)\n }\n }\n\n /**\n * Collect all modules that depend on the given module\n */\n private async collectDependentModules(\n module: ModuleNode,\n result: Set<ModuleNode>\n ): Promise<void> {\n // Get modules that import this module\n for (const importer of module.importers) {\n if (!result.has(importer)) {\n result.add(importer)\n // Recursively collect their dependents\n await this.collectDependentModules(importer, result)\n }\n }\n }\n\n /**\n * Handle compilation or HMR errors\n */\n private handleError(filePath: string, error: unknown): void {\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n this.log(`Error processing ${filePath}: ${errorMessage}`, 'error')\n\n // Send error overlay to browser\n this.server.ws.send({\n type: 'error',\n err: {\n message: `Opal compilation failed for ${filePath}`,\n stack: error instanceof Error ? error.stack : undefined,\n id: filePath,\n frame: this.extractErrorFrame(errorMessage),\n plugin: 'vite-plugin-opal',\n loc: this.extractErrorLocation(errorMessage)\n }\n })\n }\n\n /**\n * Extract error frame from error message\n */\n private extractErrorFrame(errorMessage: string): string | undefined {\n // Try to extract relevant code frame from error\n const lines = errorMessage.split('\\n')\n const relevantLines = lines.slice(0, 10).join('\\n')\n return relevantLines || undefined\n }\n\n /**\n * Extract error location from error message\n */\n private extractErrorLocation(errorMessage: string): { file?: string; line?: number; column?: number } | undefined {\n // Try to parse location from Opal error messages\n // Example: \"file.rb:10:5: error message\"\n const match = errorMessage.match(/(.+):(\\d+):(\\d+)/)\n if (match) {\n return {\n file: match[1],\n line: parseInt(match[2], 10),\n column: parseInt(match[3], 10)\n }\n }\n return undefined\n }\n\n /**\n * Log HMR messages\n */\n private log(message: string, level: 'info' | 'warn' | 'error' | 'success' = 'info'): void {\n if (!this.options.debug && level === 'info') {\n return\n }\n\n const prefix = '[vite-plugin-opal:hmr]'\n const timestamp = new Date().toLocaleTimeString()\n\n const colors = {\n info: '\\x1b[36m', // Cyan\n warn: '\\x1b[33m', // Yellow\n error: '\\x1b[31m', // Red\n success: '\\x1b[32m' // Green\n }\n const reset = '\\x1b[0m'\n\n const color = colors[level]\n const levelText = level.toUpperCase().padEnd(7)\n\n console.log(`${color}${prefix} ${timestamp} [${levelText}]${reset} ${message}`)\n }\n\n /**\n * Get dependency graph information (for debugging)\n */\n getDependencyGraph(): Map<string, Set<string>> {\n return new Map(this.dependencyGraph)\n }\n}\n","import type { Plugin, ViteDevServer } from 'vite'\nimport { OpalCompiler } from './compiler'\nimport { OpalResolver } from './resolver'\nimport { OpalHMRManager } from './hmr'\nimport type { OpalPluginOptions } from './types'\nimport * as path from 'path'\n\nconst VIRTUAL_RUNTIME_ID = '/@opal-runtime'\nconst VIRTUAL_RUNTIME_PREFIX = '\\0' + VIRTUAL_RUNTIME_ID\n\nexport default function opalPlugin(options: OpalPluginOptions = {}): Plugin {\n const compiler = new OpalCompiler(options)\n const resolver = new OpalResolver(options)\n let server: ViteDevServer | undefined\n let hmrManager: OpalHMRManager | undefined\n\n return {\n name: 'vite-plugin-opal',\n enforce: 'pre', // Run before other plugins\n\n // Mark .rb files as valid imports\n async resolveId(id: string, importer?: string) {\n // Handle virtual runtime module\n if (id === VIRTUAL_RUNTIME_ID) {\n if (options.debug) {\n console.log(`[vite-plugin-opal] Resolved virtual runtime: ${VIRTUAL_RUNTIME_PREFIX}`)\n }\n return VIRTUAL_RUNTIME_PREFIX\n }\n\n // Handle .rb files and files without extension (Opal style requires)\n if (id.endsWith('.rb') || (!id.includes('.') && !id.startsWith('/'))) {\n const resolved = await resolver.resolve(id, importer)\n if (options.debug) {\n console.log(`[vite-plugin-opal] resolveId: ${id} -> ${resolved}`)\n }\n return resolved\n }\n\n return null\n },\n\n // Load and compile .rb files\n async load(id: string) {\n // Handle virtual runtime module\n if (id === VIRTUAL_RUNTIME_PREFIX) {\n if (options.debug) {\n console.log(`[vite-plugin-opal] Loading Opal runtime...`)\n }\n const runtime = await compiler.getOpalRuntime()\n if (options.debug) {\n console.log(`[vite-plugin-opal] Opal runtime loaded: ${runtime.length} bytes`)\n }\n return {\n code: runtime,\n map: null\n }\n }\n\n // Handle .rb files\n if (id.endsWith('.rb')) {\n if (options.debug) {\n console.log(`[vite-plugin-opal] load: Compiling ${id}`)\n }\n try {\n const result = await compiler.compile(id)\n if (options.debug) {\n console.log(`[vite-plugin-opal] load: Compiled ${id} -> ${result.code.length} bytes`)\n }\n return {\n code: result.code,\n map: result.map ? JSON.parse(result.map) : null\n }\n } catch (error) {\n console.error(`[vite-plugin-opal] Compilation error for ${id}:`, error)\n this.error(error instanceof Error ? error.message : String(error))\n }\n }\n\n return null\n },\n\n // Auto-inject Opal runtime into HTML\n transformIndexHtml(html: string) {\n // Inject runtime before closing </head> tag\n const runtimeScript = `<script type=\"module\" src=\"${VIRTUAL_RUNTIME_ID}\"></script>`\n\n if (html.includes('</head>')) {\n return html.replace('</head>', ` ${runtimeScript}\\n</head>`)\n } else if (html.includes('<head>')) {\n return html.replace('<head>', `<head>\\n ${runtimeScript}`)\n } else {\n // No head tag, inject at the beginning\n return `${runtimeScript}\\n${html}`\n }\n },\n\n // Setup HMR for .rb files\n configureServer(_server: ViteDevServer) {\n server = _server\n\n // Initialize HMR manager\n hmrManager = new OpalHMRManager(server, compiler, resolver, options)\n hmrManager.setup()\n\n return () => {\n // Cleanup function called when server closes\n if (hmrManager) {\n hmrManager.cleanup()\n }\n }\n }\n }\n}\n\nexport type { OpalPluginOptions } from './types'\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-opal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vite plugin for Opal - Compile Ruby to JavaScript",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"test": "vitest",
|
|
22
|
+
"clean": "rm -rf dist"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"vite",
|
|
26
|
+
"vite-plugin",
|
|
27
|
+
"opal",
|
|
28
|
+
"ruby",
|
|
29
|
+
"javascript"
|
|
30
|
+
],
|
|
31
|
+
"author": "stofu1234",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/stofu1234/opal-vite.git",
|
|
36
|
+
"directory": "packages/vite-plugin-opal"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/stofu1234/opal-vite#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/stofu1234/opal-vite/issues"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"vite": "^5.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.10.6",
|
|
47
|
+
"tsup": "^8.0.1",
|
|
48
|
+
"typescript": "^5.3.3",
|
|
49
|
+
"vite": "^5.0.10",
|
|
50
|
+
"vitest": "^1.1.0"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"chokidar": "^3.5.3"
|
|
54
|
+
}
|
|
55
|
+
}
|