@praxisjs/vite-plugin 4.0.5 → 4.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/CHANGELOG.md +28 -0
- package/dist/decorators.d.ts +15 -0
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +437 -16
- package/dist/decorators.js.map +1 -1
- package/dist/hmr.d.ts +5 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +106 -0
- package/dist/hmr.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -18
- package/dist/index.js.map +1 -1
- package/dist/keyed-lists.d.ts +27 -0
- package/dist/keyed-lists.d.ts.map +1 -0
- package/dist/keyed-lists.js +248 -0
- package/dist/keyed-lists.js.map +1 -0
- package/dist/mapped-output.d.ts +32 -0
- package/dist/mapped-output.d.ts.map +1 -0
- package/dist/mapped-output.js +107 -0
- package/dist/mapped-output.js.map +1 -0
- package/dist/templates.d.ts +51 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +707 -0
- package/dist/templates.js.map +1 -0
- package/package.json +13 -5
- package/src/decorators.ts +456 -16
- package/src/hmr.ts +108 -0
- package/src/index.ts +22 -25
- package/src/keyed-lists.ts +282 -0
- package/src/mapped-output.ts +116 -0
- package/src/templates.ts +729 -0
- package/dist/__tests__/css-esbuild-error.test.d.ts +0 -2
- package/dist/__tests__/css-esbuild-error.test.d.ts.map +0 -1
- package/dist/__tests__/css-esbuild-error.test.js +0 -25
- package/dist/__tests__/css-esbuild-error.test.js.map +0 -1
- package/dist/__tests__/css-tsconfig-error.test.d.ts +0 -2
- package/dist/__tests__/css-tsconfig-error.test.d.ts.map +0 -1
- package/dist/__tests__/css-tsconfig-error.test.js +0 -39
- package/dist/__tests__/css-tsconfig-error.test.js.map +0 -1
- package/dist/__tests__/css-tsconfig-no-config.test.d.ts +0 -2
- package/dist/__tests__/css-tsconfig-no-config.test.d.ts.map +0 -1
- package/dist/__tests__/css-tsconfig-no-config.test.js +0 -38
- package/dist/__tests__/css-tsconfig-no-config.test.js.map +0 -1
- package/dist/__tests__/css.test.d.ts +0 -2
- package/dist/__tests__/css.test.d.ts.map +0 -1
- package/dist/__tests__/css.test.js +0 -288
- package/dist/__tests__/css.test.js.map +0 -1
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -301
- package/dist/__tests__/index.test.js.map +0 -1
- package/src/__tests__/css-esbuild-error.test.ts +0 -39
- package/src/__tests__/css-tsconfig-error.test.ts +0 -53
- package/src/__tests__/css-tsconfig-no-config.test.ts +0 -52
- package/src/__tests__/css.test.ts +0 -381
- package/src/__tests__/index.test.ts +0 -351
- package/tsconfig.json +0 -11
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
import { describe, it, expect, vi } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { praxisjs, decoratorLoweringPlugin } from "../index";
|
|
5
|
-
|
|
6
|
-
// `ts.transpileModule`'s own types mark `diagnostics` and `sourceMapText` as
|
|
7
|
-
// optional regardless of the options passed in — wrapping it lets a couple
|
|
8
|
-
// of tests below simulate that contract even though it never happens with
|
|
9
|
-
// the fixed options `decoratorLoweringPlugin()` always calls it with.
|
|
10
|
-
vi.mock("typescript", async (importOriginal) => {
|
|
11
|
-
const actual = (await importOriginal()) as typeof import("typescript") & {
|
|
12
|
-
default: typeof import("typescript");
|
|
13
|
-
};
|
|
14
|
-
return {
|
|
15
|
-
...actual,
|
|
16
|
-
default: {
|
|
17
|
-
...actual.default,
|
|
18
|
-
transpileModule: vi.fn(actual.default.transpileModule),
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
interface TransformResult {
|
|
24
|
-
code: string;
|
|
25
|
-
map?: unknown;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface NamedPlugin {
|
|
29
|
-
name?: string;
|
|
30
|
-
enforce?: string;
|
|
31
|
-
config?: () => unknown;
|
|
32
|
-
transform?: (code: string, id: string) => string | TransformResult | null | undefined;
|
|
33
|
-
handleHotUpdate?: (ctx: { file: string; server: unknown }) => void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function transformedCode(result: string | TransformResult | null | undefined): string | null | undefined {
|
|
37
|
-
if (result == null || typeof result === "string") return result;
|
|
38
|
-
return result.code;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function findPlugin(plugins: ReturnType<typeof praxisjs>, name: string): NamedPlugin {
|
|
42
|
-
const plugin = (plugins as NamedPlugin[]).find((p) => p.name === name);
|
|
43
|
-
if (!plugin) throw new Error(`plugin "${name}" not found`);
|
|
44
|
-
return plugin;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
describe("praxisjs() plugin factory", () => {
|
|
48
|
-
it("returns three plugins by default (decorators + core + hmr)", () => {
|
|
49
|
-
const plugins = praxisjs();
|
|
50
|
-
expect(plugins).toHaveLength(3);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("returns two plugins when hmr is disabled", () => {
|
|
54
|
-
const plugins = praxisjs({ hmr: false });
|
|
55
|
-
expect(plugins).toHaveLength(2);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("core plugin has the correct name", () => {
|
|
59
|
-
const core = findPlugin(praxisjs(), "praxisjs:core");
|
|
60
|
-
expect(core.name).toBe("praxisjs:core");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("core plugin enforces 'pre'", () => {
|
|
64
|
-
const core = findPlugin(praxisjs(), "praxisjs:core");
|
|
65
|
-
expect(core.enforce).toBe("pre");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("hmr plugin has the correct name", () => {
|
|
69
|
-
const hmr = findPlugin(praxisjs(), "praxisjs:hmr");
|
|
70
|
-
expect(hmr.name).toBe("praxisjs:hmr");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("hmr plugin enforces 'post'", () => {
|
|
74
|
-
const hmr = findPlugin(praxisjs(), "praxisjs:hmr");
|
|
75
|
-
expect(hmr.enforce).toBe("post");
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("omits the hmr plugin when hmr is disabled", () => {
|
|
79
|
-
const plugins = praxisjs({ hmr: false }) as NamedPlugin[];
|
|
80
|
-
expect(plugins.find((p) => p.name === "praxisjs:hmr")).toBeUndefined();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("includes the decorator-lowering plugin", () => {
|
|
84
|
-
const plugins = praxisjs();
|
|
85
|
-
expect(findPlugin(plugins, "praxisjs:decorators")).toBeDefined();
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe("decoratorLoweringPlugin() standalone export", () => {
|
|
90
|
-
it("is exported so it can be reused outside of praxisjs(), e.g. in vitest.config.ts", () => {
|
|
91
|
-
const plugin = decoratorLoweringPlugin() as NamedPlugin;
|
|
92
|
-
expect(plugin.name).toBe("praxisjs:decorators");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("enforces 'pre' so it runs before Vite's built-in esbuild TS transform", () => {
|
|
96
|
-
// Regression test: without `enforce: "pre"` this plugin runs in the
|
|
97
|
-
// "normal" tier, after esbuild has already parsed the file. esbuild's
|
|
98
|
-
// TC39 decorator support silently drops class fields that carry a
|
|
99
|
-
// decorator but have no initializer (e.g. `@Styled(X) $s!: X;`) instead
|
|
100
|
-
// of lowering them, so `ts.transpileModule` below never even sees the
|
|
101
|
-
// field — it just ends up `undefined` at runtime with no build error.
|
|
102
|
-
const plugin = decoratorLoweringPlugin() as NamedPlugin;
|
|
103
|
-
expect(plugin.enforce).toBe("pre");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("lowers decorators the same way whether reused standalone or via praxisjs()", () => {
|
|
107
|
-
const code = `
|
|
108
|
-
function Dec() { return function (v, ctx) {}; }
|
|
109
|
-
class Foo {
|
|
110
|
-
@Dec()
|
|
111
|
-
accent = "red";
|
|
112
|
-
}
|
|
113
|
-
`;
|
|
114
|
-
const standalone = decoratorLoweringPlugin() as NamedPlugin;
|
|
115
|
-
const result = transformedCode(standalone.transform?.(code, "component.ts"));
|
|
116
|
-
expect(result).toBeTruthy();
|
|
117
|
-
expect(result).not.toContain("@Dec()");
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe("praxisjs:decorators transform()", () => {
|
|
122
|
-
it("lowers a real decorator into runtime-executable code", () => {
|
|
123
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
124
|
-
const code = `
|
|
125
|
-
function Dec() { return function (v, ctx) {}; }
|
|
126
|
-
class Foo {
|
|
127
|
-
@Dec()
|
|
128
|
-
accent = "red";
|
|
129
|
-
}
|
|
130
|
-
`;
|
|
131
|
-
const result = transformedCode(decorators.transform?.(code, "component.ts"));
|
|
132
|
-
expect(result).toBeTruthy();
|
|
133
|
-
expect(result).not.toContain("@Dec()");
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("returns null for a file with '@' only in a comment (no real decorator)", () => {
|
|
137
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
138
|
-
const code = `
|
|
139
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
|
-
export function identity(signal) { return signal; }
|
|
141
|
-
`;
|
|
142
|
-
const result = decorators.transform?.(code, "utils.ts");
|
|
143
|
-
expect(result).toBeNull();
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("returns null for non-JS/TS files", () => {
|
|
147
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
148
|
-
const result = decorators.transform?.("@import 'x.css';", "styles.css");
|
|
149
|
-
expect(result).toBeNull();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("lowers a real decorator in a .tsx file, leaving JSX untouched", () => {
|
|
153
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
154
|
-
const code = `
|
|
155
|
-
function Dec() { return function (v, ctx) {}; }
|
|
156
|
-
class Foo {
|
|
157
|
-
@Dec()
|
|
158
|
-
accent = "red";
|
|
159
|
-
render() { return <div>{this.accent}</div>; }
|
|
160
|
-
}
|
|
161
|
-
`;
|
|
162
|
-
const result = transformedCode(decorators.transform?.(code, "component.tsx"));
|
|
163
|
-
expect(result).toBeTruthy();
|
|
164
|
-
expect(result).not.toContain("@Dec()");
|
|
165
|
-
expect(result).toContain("<div>{this.accent}</div>");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("lowers a real decorator in a .jsx file, leaving JSX untouched", () => {
|
|
169
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
170
|
-
const code = `
|
|
171
|
-
function Dec() { return function (v, ctx) {}; }
|
|
172
|
-
class Foo {
|
|
173
|
-
@Dec()
|
|
174
|
-
accent = "red";
|
|
175
|
-
render() { return <div>{this.accent}</div>; }
|
|
176
|
-
}
|
|
177
|
-
`;
|
|
178
|
-
const result = transformedCode(decorators.transform?.(code, "component.jsx"));
|
|
179
|
-
expect(result).toBeTruthy();
|
|
180
|
-
expect(result).not.toContain("@Dec()");
|
|
181
|
-
expect(result).toContain("<div>{this.accent}</div>");
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("lowers a real decorator in a plain .js file", () => {
|
|
185
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
186
|
-
const code = `
|
|
187
|
-
function Dec() { return function (v, ctx) {}; }
|
|
188
|
-
class Foo {
|
|
189
|
-
@Dec()
|
|
190
|
-
accent = "red";
|
|
191
|
-
}
|
|
192
|
-
`;
|
|
193
|
-
const result = transformedCode(decorators.transform?.(code, "component.js"));
|
|
194
|
-
expect(result).toBeTruthy();
|
|
195
|
-
expect(result).not.toContain("@Dec()");
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("keeps a field decorator's addInitializer running after super() in a derived class", () => {
|
|
199
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
200
|
-
const code = `
|
|
201
|
-
function Dec() {
|
|
202
|
-
return function (value, ctx) {
|
|
203
|
-
ctx.addInitializer(function () { this.order.push("field:" + ctx.name); });
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
class Base {
|
|
207
|
-
constructor(order) { this.order = order; this.order.push("super"); }
|
|
208
|
-
}
|
|
209
|
-
class Comp extends Base {
|
|
210
|
-
@Dec()
|
|
211
|
-
accent = "red";
|
|
212
|
-
constructor(order) {
|
|
213
|
-
super(order);
|
|
214
|
-
order.push("after-super");
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
`;
|
|
218
|
-
const result = transformedCode(decorators.transform?.(code, "component.ts"));
|
|
219
|
-
expect(result).toBeTruthy();
|
|
220
|
-
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
221
|
-
const factory = new Function(`${result as string}\nreturn Comp;`);
|
|
222
|
-
const Comp = factory() as new (order: string[]) => unknown;
|
|
223
|
-
const order: string[] = [];
|
|
224
|
-
new Comp(order);
|
|
225
|
-
expect(order).toEqual(["super", "field:accent", "after-super"]);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("throws on invalid decorator syntax instead of silently emitting broken code", () => {
|
|
229
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
230
|
-
const code = `
|
|
231
|
-
class Foo {
|
|
232
|
-
@ accent = "red";
|
|
233
|
-
}
|
|
234
|
-
`;
|
|
235
|
-
// An absolute id (as Vite always passes) makes TypeScript canonicalize
|
|
236
|
-
// the file name while formatting the diagnostic message.
|
|
237
|
-
expect(() => decorators.transform?.(code, "/src/component.ts")).toThrow();
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it("returns a null map when ts.transpileModule omits sourceMapText", () => {
|
|
241
|
-
const transpileModule = ts.transpileModule as unknown as ReturnType<typeof vi.fn>;
|
|
242
|
-
transpileModule.mockReturnValueOnce({
|
|
243
|
-
outputText: "class Foo {}",
|
|
244
|
-
diagnostics: [],
|
|
245
|
-
sourceMapText: undefined,
|
|
246
|
-
});
|
|
247
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
248
|
-
const result = decorators.transform?.("class Foo { @Dec() x = 1; }", "component.ts");
|
|
249
|
-
expect(result).toEqual({ code: "class Foo {}", map: null });
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it("does not throw when ts.transpileModule omits diagnostics", () => {
|
|
253
|
-
const transpileModule = ts.transpileModule as unknown as ReturnType<typeof vi.fn>;
|
|
254
|
-
transpileModule.mockReturnValueOnce({
|
|
255
|
-
outputText: "class Foo {}",
|
|
256
|
-
diagnostics: undefined,
|
|
257
|
-
sourceMapText: undefined,
|
|
258
|
-
});
|
|
259
|
-
const decorators = findPlugin(praxisjs(), "praxisjs:decorators");
|
|
260
|
-
expect(() =>
|
|
261
|
-
decorators.transform?.("class Foo { @Dec() x = 1; }", "component.ts"),
|
|
262
|
-
).not.toThrow();
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
describe("praxisjs:core config()", () => {
|
|
267
|
-
it("sets oxc target to es2022", () => {
|
|
268
|
-
const core = findPlugin(praxisjs(), "praxisjs:core");
|
|
269
|
-
const config = core.config?.();
|
|
270
|
-
expect(config).toEqual({ oxc: { target: "es2022" } });
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
describe("praxisjs:core transform()", () => {
|
|
275
|
-
it("returns null for .tsx files when autoImport is enabled", () => {
|
|
276
|
-
const core = findPlugin(praxisjs({ autoImport: true }), "praxisjs:core");
|
|
277
|
-
const result = core.transform?.("const x = 1;", "component.tsx");
|
|
278
|
-
expect(result).toBeNull();
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it("returns null for .jsx files when autoImport is enabled", () => {
|
|
282
|
-
const core = findPlugin(praxisjs({ autoImport: true }), "praxisjs:core");
|
|
283
|
-
const result = core.transform?.("const x = 1;", "component.jsx");
|
|
284
|
-
expect(result).toBeNull();
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it("returns undefined (no-op) for non-.tsx/.jsx files", () => {
|
|
288
|
-
const core = findPlugin(praxisjs({ autoImport: true }), "praxisjs:core");
|
|
289
|
-
const result = core.transform?.("const x = 1;", "module.ts");
|
|
290
|
-
expect(result).toBeUndefined();
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it("returns undefined when autoImport is false", () => {
|
|
294
|
-
const core = findPlugin(praxisjs({ autoImport: false }), "praxisjs:core");
|
|
295
|
-
const result = core.transform?.("const x = 1;", "component.tsx");
|
|
296
|
-
expect(result).toBeUndefined();
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it("returns undefined when code already imports @praxisjs/jsx/jsx-runtime", () => {
|
|
300
|
-
const core = findPlugin(praxisjs({ autoImport: true }), "praxisjs:core");
|
|
301
|
-
const code = 'import "@praxisjs/jsx/jsx-runtime"; const x = 1;';
|
|
302
|
-
const result = core.transform?.(code, "component.tsx");
|
|
303
|
-
expect(result).toBeUndefined();
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
describe("praxisjs:hmr handleHotUpdate()", () => {
|
|
308
|
-
function makeMockServer() {
|
|
309
|
-
return {
|
|
310
|
-
ws: {
|
|
311
|
-
send: vi.fn(),
|
|
312
|
-
},
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
it("sends custom event for .ts files", () => {
|
|
317
|
-
const hmr = findPlugin(praxisjs({ hmr: true }), "praxisjs:hmr");
|
|
318
|
-
const server = makeMockServer();
|
|
319
|
-
hmr.handleHotUpdate?.({ file: "src/app.ts", server });
|
|
320
|
-
expect(server.ws.send).toHaveBeenCalledWith({
|
|
321
|
-
type: "custom",
|
|
322
|
-
event: "praxisjs:component-update",
|
|
323
|
-
data: { file: "src/app.ts" },
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it("sends custom event for .tsx files", () => {
|
|
328
|
-
const hmr = findPlugin(praxisjs({ hmr: true }), "praxisjs:hmr");
|
|
329
|
-
const server = makeMockServer();
|
|
330
|
-
hmr.handleHotUpdate?.({ file: "src/component.tsx", server });
|
|
331
|
-
expect(server.ws.send).toHaveBeenCalledWith({
|
|
332
|
-
type: "custom",
|
|
333
|
-
event: "praxisjs:component-update",
|
|
334
|
-
data: { file: "src/component.tsx" },
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it("does not send event for non-.ts/.tsx files", () => {
|
|
339
|
-
const hmr = findPlugin(praxisjs({ hmr: true }), "praxisjs:hmr");
|
|
340
|
-
const server = makeMockServer();
|
|
341
|
-
hmr.handleHotUpdate?.({ file: "src/style.css", server });
|
|
342
|
-
expect(server.ws.send).not.toHaveBeenCalled();
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it("does not send event for .js files", () => {
|
|
346
|
-
const hmr = findPlugin(praxisjs({ hmr: true }), "praxisjs:hmr");
|
|
347
|
-
const server = makeMockServer();
|
|
348
|
-
hmr.handleHotUpdate?.({ file: "src/utils.js", server });
|
|
349
|
-
expect(server.ws.send).not.toHaveBeenCalled();
|
|
350
|
-
});
|
|
351
|
-
});
|