@pyreon/vite-plugin 0.13.0 → 0.14.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.
@@ -36,60 +36,67 @@ function createBuildPlugin(opts?: PyreonPluginOptions) {
36
36
  return plugin
37
37
  }
38
38
 
39
- function transform(plugin: ReturnType<typeof pyreonPlugin>, code: string, id: string) {
39
+ async function transform(plugin: ReturnType<typeof pyreonPlugin>, code: string, id: string) {
40
40
  const transformHook = plugin.transform as (
41
- this: { warn: (msg: string) => void },
41
+ this: { warn: (msg: string) => void; resolve: (id: string, importer?: string, options?: { skipSelf: boolean }) => Promise<{ id: string } | null> },
42
42
  code: string,
43
43
  id: string,
44
- ) => { code: string; map: null } | undefined
44
+ ) => Promise<{ code: string; map: null } | undefined>
45
45
  const warnings: string[] = []
46
- return transformHook.call({ warn: (msg: string) => warnings.push(msg) }, code, id)
46
+ return transformHook.call(
47
+ {
48
+ warn: (msg: string) => warnings.push(msg),
49
+ resolve: async () => null, // no cross-module resolution in unit tests
50
+ },
51
+ code,
52
+ id,
53
+ )
47
54
  }
48
55
 
49
56
  // ─── HMR injection ──────────────────────────────────────────────────────────
50
57
 
51
58
  describe('HMR injection', () => {
52
- it('injects HMR accept for modules with component exports', () => {
59
+ it('injects HMR accept for modules with component exports', async () => {
53
60
  const plugin = createPlugin()
54
61
  const code = `
55
62
  import { h } from "@pyreon/core"
56
63
  export function App() { return h("div", null, "hello") }
57
64
  `
58
- const result = transform(plugin, code, '/src/App.tsx')
65
+ const result = await transform(plugin, code, '/src/App.tsx')
59
66
  expect(result).toBeDefined()
60
67
  expect(result!.code).toContain('import.meta.hot.accept()')
61
68
  })
62
69
 
63
- it('injects HMR for exported const components', () => {
70
+ it('injects HMR for exported const components', async () => {
64
71
  const plugin = createPlugin()
65
72
  const code = `
66
73
  import { h } from "@pyreon/core"
67
74
  export const Header = () => h("header", null, "nav")
68
75
  `
69
- const result = transform(plugin, code, '/src/Header.tsx')
76
+ const result = await transform(plugin, code, '/src/Header.tsx')
70
77
  expect(result).toBeDefined()
71
78
  expect(result!.code).toContain('import.meta.hot')
72
79
  })
73
80
 
74
- it('does not inject HMR for modules without component exports or signals', () => {
81
+ it('does not inject HMR for modules without component exports or signals', async () => {
75
82
  const plugin = createPlugin()
76
83
  // Only lowercase exports — no component-like names (uppercase first letter)
77
84
  const code = `
78
85
  export const formatDate = (d) => d.toISOString()
79
86
  export const maxItems = 100
80
87
  `
81
- const result = transform(plugin, code, '/src/utils.tsx')
88
+ const result = await transform(plugin, code, '/src/utils.tsx')
82
89
  expect(result).toBeDefined()
83
90
  expect(result!.code).not.toContain('import.meta.hot')
84
91
  })
85
92
 
86
- it('does not inject HMR in build mode', () => {
93
+ it('does not inject HMR in build mode', async () => {
87
94
  const plugin = createBuildPlugin()
88
95
  const code = `
89
96
  import { h } from "@pyreon/core"
90
97
  export function App() { return h("div", null, "hello") }
91
98
  `
92
- const result = transform(plugin, code, '/src/App.tsx')
99
+ const result = await transform(plugin, code, '/src/App.tsx')
93
100
  expect(result).toBeDefined()
94
101
  expect(result!.code).not.toContain('import.meta.hot')
95
102
  })
@@ -98,7 +105,7 @@ export function App() { return h("div", null, "hello") }
98
105
  // ─── Signal rewriting ────────────────────────────────────────────────────────
99
106
 
100
107
  describe('signal rewriting', () => {
101
- it('rewrites module-scope signal() to __hmr_signal()', () => {
108
+ it('rewrites module-scope signal() to __hmr_signal()', async () => {
102
109
  const plugin = createPlugin()
103
110
  const code = `
104
111
  import { signal } from "@pyreon/reactivity"
@@ -106,7 +113,7 @@ import { h } from "@pyreon/core"
106
113
  const count = signal(0)
107
114
  export function Counter() { return h("div", null, count()) }
108
115
  `
109
- const result = transform(plugin, code, '/src/Counter.tsx')
116
+ const result = await transform(plugin, code, '/src/Counter.tsx')
110
117
  expect(result).toBeDefined()
111
118
  expect(result!.code).toContain('__hmr_signal(')
112
119
  expect(result!.code).toContain('"count"')
@@ -114,19 +121,19 @@ export function Counter() { return h("div", null, count()) }
114
121
  expect(result!.code).toContain('__hmr_dispose')
115
122
  })
116
123
 
117
- it('rewrites exported signals', () => {
124
+ it('rewrites exported signals', async () => {
118
125
  const plugin = createPlugin()
119
126
  const code = `
120
127
  import { signal } from "@pyreon/reactivity"
121
128
  export const theme = signal("light")
122
129
  export function App() { return null }
123
130
  `
124
- const result = transform(plugin, code, '/src/theme.tsx')
131
+ const result = await transform(plugin, code, '/src/theme.tsx')
125
132
  expect(result).toBeDefined()
126
133
  expect(result!.code).toContain('__hmr_signal("/src/theme.tsx", "theme", signal, "light")')
127
134
  })
128
135
 
129
- it('does not rewrite signal() inside functions to __hmr_signal (but injects name)', () => {
136
+ it('does not rewrite signal() inside functions to __hmr_signal (but injects name)', async () => {
130
137
  const plugin = createPlugin()
131
138
  const code = `
132
139
  import { signal } from "@pyreon/reactivity"
@@ -136,7 +143,7 @@ export function Counter() {
136
143
  return h("div", null, local())
137
144
  }
138
145
  `
139
- const result = transform(plugin, code, '/src/Counter.tsx')
146
+ const result = await transform(plugin, code, '/src/Counter.tsx')
140
147
  expect(result).toBeDefined()
141
148
  // The signal inside the function body should NOT be rewritten to __hmr_signal
142
149
  expect(result!.code).not.toContain('__hmr_signal')
@@ -144,7 +151,7 @@ export function Counter() {
144
151
  expect(result!.code).toContain('signal(0, { name: "local" })')
145
152
  })
146
153
 
147
- it('rewrites multiple module-scope signals', () => {
154
+ it('rewrites multiple module-scope signals', async () => {
148
155
  const plugin = createPlugin()
149
156
  const code = `
150
157
  import { signal } from "@pyreon/reactivity"
@@ -152,13 +159,13 @@ const count = signal(0)
152
159
  const name = signal("world")
153
160
  export function App() { return null }
154
161
  `
155
- const result = transform(plugin, code, '/src/App.tsx')
162
+ const result = await transform(plugin, code, '/src/App.tsx')
156
163
  expect(result).toBeDefined()
157
164
  expect(result!.code).toContain('"count"')
158
165
  expect(result!.code).toContain('"name"')
159
166
  })
160
167
 
161
- it('handles signal with complex initial values', () => {
168
+ it('handles signal with complex initial values', async () => {
162
169
  const plugin = createPlugin()
163
170
  const code = `
164
171
  import { signal } from "@pyreon/reactivity"
@@ -166,21 +173,21 @@ const items = signal([1, 2, 3])
166
173
  const config = signal({ theme: "dark", size: 14 })
167
174
  export function App() { return null }
168
175
  `
169
- const result = transform(plugin, code, '/src/App.tsx')
176
+ const result = await transform(plugin, code, '/src/App.tsx')
170
177
  expect(result).toBeDefined()
171
178
  expect(result!.code).toContain('__hmr_signal')
172
179
  expect(result!.code).toContain('[1, 2, 3]')
173
180
  expect(result!.code).toContain('{ theme: "dark", size: 14 }')
174
181
  })
175
182
 
176
- it('does not rewrite signal in build mode', () => {
183
+ it('does not rewrite signal in build mode', async () => {
177
184
  const plugin = createBuildPlugin()
178
185
  const code = `
179
186
  import { signal } from "@pyreon/reactivity"
180
187
  const count = signal(0)
181
188
  export function App() { return null }
182
189
  `
183
- const result = transform(plugin, code, '/src/App.tsx')
190
+ const result = await transform(plugin, code, '/src/App.tsx')
184
191
  expect(result).toBeDefined()
185
192
  expect(result!.code).not.toContain('__hmr_signal')
186
193
  // No signal names in production builds
@@ -188,7 +195,7 @@ export function App() { return null }
188
195
  expect(result!.code).not.toContain('{ name:')
189
196
  })
190
197
 
191
- it('skips signal naming when options already provided', () => {
198
+ it('skips signal naming when options already provided', async () => {
192
199
  const plugin = createPlugin()
193
200
  const code = `
194
201
  import { signal } from "@pyreon/reactivity"
@@ -197,7 +204,7 @@ export function App() {
197
204
  return null
198
205
  }
199
206
  `
200
- const result = transform(plugin, code, '/src/App.tsx')
207
+ const result = await transform(plugin, code, '/src/App.tsx')
201
208
  expect(result).toBeDefined()
202
209
  // Should not double-inject name
203
210
  expect(result!.code).toContain('signal(0, { name: "custom" })')
@@ -207,38 +214,38 @@ export function App() {
207
214
  // ─── File extension filtering ────────────────────────────────────────────────
208
215
 
209
216
  describe('file extension filtering', () => {
210
- it('transforms .tsx files', () => {
217
+ it('transforms .tsx files', async () => {
211
218
  const plugin = createPlugin()
212
219
  const code = `export function App() { return null }`
213
- const result = transform(plugin, code, '/src/App.tsx')
220
+ const result = await transform(plugin, code, '/src/App.tsx')
214
221
  expect(result).toBeDefined()
215
222
  })
216
223
 
217
- it('transforms .jsx files', () => {
224
+ it('transforms .jsx files', async () => {
218
225
  const plugin = createPlugin()
219
226
  const code = `export function App() { return null }`
220
- const result = transform(plugin, code, '/src/App.jsx')
227
+ const result = await transform(plugin, code, '/src/App.jsx')
221
228
  expect(result).toBeDefined()
222
229
  })
223
230
 
224
- it('ignores .ts files', () => {
231
+ it('ignores .ts files', async () => {
225
232
  const plugin = createPlugin()
226
233
  const code = `export const x = 1`
227
- const result = transform(plugin, code, '/src/utils.ts')
234
+ const result = await transform(plugin, code, '/src/utils.ts')
228
235
  expect(result).toBeUndefined()
229
236
  })
230
237
 
231
- it('ignores .js files', () => {
238
+ it('ignores .js files', async () => {
232
239
  const plugin = createPlugin()
233
240
  const code = `export const x = 1`
234
- const result = transform(plugin, code, '/src/utils.js')
241
+ const result = await transform(plugin, code, '/src/utils.js')
235
242
  expect(result).toBeUndefined()
236
243
  })
237
244
 
238
- it('handles query strings in file paths', () => {
245
+ it('handles query strings in file paths', async () => {
239
246
  const plugin = createPlugin()
240
247
  const code = `export function App() { return null }`
241
- const result = transform(plugin, code, '/src/App.tsx?v=123')
248
+ const result = await transform(plugin, code, '/src/App.tsx?v=123')
242
249
  expect(result).toBeDefined()
243
250
  })
244
251
  })
@@ -246,31 +253,31 @@ describe('file extension filtering', () => {
246
253
  // ─── Compat mode ─────────────────────────────────────────────────────────────
247
254
 
248
255
  describe('compat mode', () => {
249
- it('skips Pyreon JSX transform in react compat mode', () => {
256
+ it('skips Pyreon JSX transform in react compat mode', async () => {
250
257
  const plugin = createPlugin({ compat: 'react' })
251
258
  const code = `
252
259
  import { useState } from "react"
253
260
  export function App() { const [x] = useState(0); return null }
254
261
  `
255
- const result = transform(plugin, code, '/src/App.tsx')
262
+ const result = await transform(plugin, code, '/src/App.tsx')
256
263
  expect(result).toBeUndefined()
257
264
  })
258
265
 
259
- it('skips transform in preact compat mode', () => {
266
+ it('skips transform in preact compat mode', async () => {
260
267
  const plugin = createPlugin({ compat: 'preact' })
261
- const result = transform(plugin, 'export function App() { return null }', '/src/App.tsx')
268
+ const result = await transform(plugin, 'export function App() { return null }', '/src/App.tsx')
262
269
  expect(result).toBeUndefined()
263
270
  })
264
271
 
265
- it('skips transform in vue compat mode', () => {
272
+ it('skips transform in vue compat mode', async () => {
266
273
  const plugin = createPlugin({ compat: 'vue' })
267
- const result = transform(plugin, 'export function App() { return null }', '/src/App.tsx')
274
+ const result = await transform(plugin, 'export function App() { return null }', '/src/App.tsx')
268
275
  expect(result).toBeUndefined()
269
276
  })
270
277
 
271
- it('skips transform in solid compat mode', () => {
278
+ it('skips transform in solid compat mode', async () => {
272
279
  const plugin = createPlugin({ compat: 'solid' })
273
- const result = transform(plugin, 'export function App() { return null }', '/src/App.tsx')
280
+ const result = await transform(plugin, 'export function App() { return null }', '/src/App.tsx')
274
281
  expect(result).toBeUndefined()
275
282
  })
276
283
  })
@@ -278,13 +285,13 @@ export function App() { const [x] = useState(0); return null }
278
285
  // ─── Plugin config ───────────────────────────────────────────────────────────
279
286
 
280
287
  describe('plugin config', () => {
281
- it('sets resolve.conditions: ["bun"] for workspace source resolution', () => {
288
+ it('sets resolve.conditions: ["bun"] for workspace source resolution', async () => {
282
289
  const plugin = pyreonPlugin()
283
290
  const config = getConfigHook(plugin)({}, { command: 'serve' }) as Record<string, any>
284
291
  expect(config.resolve.conditions).toEqual(['bun'])
285
292
  })
286
293
 
287
- it('sets JSX import source to @pyreon/core by default', () => {
294
+ it('sets JSX import source to @pyreon/core by default', async () => {
288
295
  const plugin = pyreonPlugin()
289
296
  const config = getConfigHook(plugin)({}, { command: 'serve' }) as {
290
297
  oxc: { jsx: { importSource: string } }
@@ -292,7 +299,7 @@ describe('plugin config', () => {
292
299
  expect(config.oxc.jsx.importSource).toBe('@pyreon/core')
293
300
  })
294
301
 
295
- it('sets JSX import source to compat package in compat mode', () => {
302
+ it('sets JSX import source to compat package in compat mode', async () => {
296
303
  const plugin = pyreonPlugin({ compat: 'react' })
297
304
  const config = getConfigHook(plugin)({}, { command: 'serve' }) as {
298
305
  oxc: { jsx: { importSource: string } }
@@ -300,7 +307,7 @@ describe('plugin config', () => {
300
307
  expect(config.oxc.jsx.importSource).toBe('@pyreon/react-compat')
301
308
  })
302
309
 
303
- it('excludes compat packages from optimizeDeps', () => {
310
+ it('excludes compat packages from optimizeDeps', async () => {
304
311
  const plugin = pyreonPlugin({ compat: 'react' })
305
312
  const config = getConfigHook(plugin)({}, { command: 'serve' }) as {
306
313
  optimizeDeps: { exclude: string[] }
@@ -309,7 +316,7 @@ describe('plugin config', () => {
309
316
  expect(config.optimizeDeps.exclude).toContain('react-dom')
310
317
  })
311
318
 
312
- it('adds SSR build config when isSsrBuild', () => {
319
+ it('adds SSR build config when isSsrBuild', async () => {
313
320
  const plugin = pyreonPlugin({ ssr: { entry: './src/entry-server.ts' } })
314
321
  const config = getConfigHook(plugin)({}, { command: 'build', isSsrBuild: true }) as {
315
322
  build: { ssr: boolean; rollupOptions: { input: string } }
@@ -331,7 +338,7 @@ describe('virtual module resolution', () => {
331
338
  expect(resolved).toBe('\0pyreon/hmr-runtime')
332
339
  })
333
340
 
334
- it('loads HMR runtime source for internal ID', () => {
341
+ it('loads HMR runtime source for internal ID', async () => {
335
342
  const plugin = createPlugin()
336
343
  const load = plugin.load as (id: string) => string | undefined
337
344
  const source = load('\0pyreon/hmr-runtime')
@@ -341,7 +348,7 @@ describe('virtual module resolution', () => {
341
348
  expect(source).toContain('__pyreon_hmr_registry__')
342
349
  })
343
350
 
344
- it('returns undefined for non-virtual IDs', () => {
351
+ it('returns undefined for non-virtual IDs', async () => {
345
352
  const plugin = createPlugin()
346
353
  const load = plugin.load as (id: string) => string | undefined
347
354
  expect(load('/src/App.tsx')).toBeUndefined()
@@ -356,12 +363,12 @@ describe('asset request filtering', () => {
356
363
  // For direct testing, we'd need to export it — instead we verify
357
364
  // the plugin's SSR middleware config exists when ssr option is set.
358
365
 
359
- it('configureServer returns middleware function when SSR enabled', () => {
366
+ it('configureServer returns middleware function when SSR enabled', async () => {
360
367
  const plugin = pyreonPlugin({ ssr: { entry: './src/entry-server.ts' } })
361
368
  expect(plugin.configureServer).toBeDefined()
362
369
  })
363
370
 
364
- it('configureServer is defined even without SSR (for context generation)', () => {
371
+ it('configureServer is defined even without SSR (for context generation)', async () => {
365
372
  const plugin = pyreonPlugin()
366
373
  expect(plugin.configureServer).toBeDefined()
367
374
  })