@loydjs/vite 1.0.0 → 1.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/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@loydjs/vite",
3
- "version": "1.0.0",
4
- "description": "Loyd vite Vite/Rollup AOT plugin",
3
+ "version": "1.1.0",
4
+ "description": "Loyd vite - Vite/Rollup AOT plugin",
5
5
  "keywords": [
6
6
  "loyd",
7
7
  "validation",
8
8
  "typescript",
9
- "schema"
9
+ "vite"
10
10
  ],
11
11
  "license": "MIT",
12
12
  "type": "module",
@@ -32,16 +32,19 @@
32
32
  ],
33
33
  "sideEffects": false,
34
34
  "dependencies": {
35
- "@loydjs/core": "1.0.0",
36
- "@loydjs/compiler": "1.0.0"
35
+ "magic-string": "^0.30.21",
36
+ "@loydjs/compiler": "1.1.0",
37
+ "@loydjs/core": "1.1.0"
37
38
  },
38
39
  "peerDependencies": {
39
40
  "vite": ">=5.0.0"
40
41
  },
41
42
  "devDependencies": {
42
- "vite": "^5.4.11",
43
- "typescript": "^5.7.2",
43
+ "@types/acorn": "^6.0.4",
44
+ "acorn": "^8.16.0",
44
45
  "tsup": "^8.3.5",
46
+ "typescript": "^5.7.2",
47
+ "vite": "^5.4.11",
45
48
  "vitest": "^2.1.8"
46
49
  },
47
50
  "publishConfig": {
package/src/plugin.ts CHANGED
@@ -1,41 +1,107 @@
1
- import { type AotTransformOptions, hasLoydImports, transformLoydImports } from "./transform.js";
1
+ import type { LoydSchema } from "@loydjs/core";
2
+ import { hasLoydImports, registerModuleSchemas, transformLoydImports } from "./transform.js";
3
+ import type { AotTransformOptions } from "./transform.js";
4
+
2
5
  export interface LoydVitePluginOptions extends AotTransformOptions {
3
6
  enabled?: boolean;
4
7
  verbose?: boolean;
5
8
  cacheDir?: string;
9
+ /**
10
+ * @example
11
+ * loydPlugin({
12
+ * schemas: {
13
+ * UserSchema: UserSchema,
14
+ * PostSchema: PostSchema,
15
+ * }
16
+ * })
17
+ */
18
+ schemas?: Record<string, LoydSchema<unknown>>;
19
+ forceAot?: boolean;
6
20
  }
21
+
7
22
  export function loydPlugin(options: LoydVitePluginOptions = {}): unknown {
8
- const { enabled = true, verbose = false, ...transformOptions } = options;
23
+ const {
24
+ enabled = true,
25
+ verbose = false,
26
+ schemas = {},
27
+ forceAot = false,
28
+ ...transformOptions
29
+ } = options;
30
+
31
+ let isBuild = false;
9
32
  let transformedCount = 0;
33
+ let skippedCount = 0;
34
+
35
+ const globalSchemas = new Map<string, LoydSchema<unknown>>(Object.entries(schemas));
36
+
10
37
  return {
11
38
  name: "loyd-vite-plugin",
12
39
  enforce: "pre" as const,
40
+
13
41
  configResolved(config: { command: string; mode: string }) {
14
- if (verbose)
15
- console.log(
16
- `[@loydjs/vite] ${enabled ? "enabled" : "disabled"} — ${config.command === "build" ? "production (AOT)" : "development (JIT)"}`,
17
- );
42
+ isBuild = config.command === "build";
43
+
44
+ if (verbose) {
45
+ const mode = isBuild || forceAot ? "AOT" : "JIT";
46
+ const status = enabled ? `enabled [${mode}]` : "disabled";
47
+ console.log(`[@loydjs/vite] ${status} — Vite ${config.command} (${config.mode})`);
48
+
49
+ if (globalSchemas.size > 0) {
50
+ console.log(
51
+ `[@loydjs/vite] Pre-registered schemas: ${[...globalSchemas.keys()].join(", ")}`,
52
+ );
53
+ }
54
+ }
18
55
  },
56
+
19
57
  transform(code: string, id: string): { code: string; map?: string } | null {
20
- if (
21
- !enabled ||
22
- !/\.[jt]sx?$/.test(id) ||
23
- id.includes("node_modules") ||
24
- !hasLoydImports(code)
25
- )
58
+ if (!enabled) return null;
59
+ if (!/\.[jt]sx?$/.test(id)) return null;
60
+ if (id.includes("node_modules")) return null;
61
+ if (!hasLoydImports(code)) return null;
62
+
63
+ if (!isBuild && !forceAot) {
64
+ return null;
65
+ }
66
+
67
+ if (globalSchemas.size > 0) {
68
+ registerModuleSchemas(id, globalSchemas);
69
+ }
70
+
71
+ const r = transformLoydImports(code, id, {
72
+ ...transformOptions,
73
+ verbose,
74
+ });
75
+
76
+ if (!r) {
77
+ skippedCount++;
26
78
  return null;
27
- const r = transformLoydImports(code, id, { ...transformOptions, verbose });
28
- if (!r) return null;
79
+ }
80
+
29
81
  transformedCount++;
30
- if (verbose) console.log(`[@loydjs/vite] Transformed: ${id}`);
82
+
83
+ if (verbose) {
84
+ console.log(`[@loydjs/vite] AOT-transformed: ${id}`);
85
+ }
86
+
31
87
  return { code: r.code, map: r.map };
32
88
  },
89
+
33
90
  buildEnd() {
34
- if (enabled && verbose && transformedCount > 0)
35
- console.log(`[@loydjs/vite] Transformed ${transformedCount} files`);
91
+ if (!enabled || !verbose) return;
92
+
93
+ if (transformedCount > 0 || skippedCount > 0) {
94
+ console.log(
95
+ `[@loydjs/vite] Build complete — ${transformedCount} files AOT-transformed, ${skippedCount} files left to JIT`,
96
+ );
97
+ }
36
98
  },
99
+
37
100
  handleHotUpdate({ file }: { file: string }) {
38
- if (enabled && verbose && hasLoydImports(file)) console.log(`[@loydjs/vite] HMR: ${file}`);
101
+ if (!enabled || !verbose) return;
102
+ if (hasLoydImports(file)) {
103
+ console.log(`[@loydjs/vite] HMR: ${file} (JIT mode)`);
104
+ }
39
105
  },
40
106
  };
41
107
  }
package/src/transform.ts CHANGED
@@ -1,3 +1,9 @@
1
+ import { generateInlineValidator } from "@loydjs/compiler";
2
+ import type { LoydSchema } from "@loydjs/core";
3
+ import { parse as parseAcorn } from "acorn";
4
+ import type { CallExpression, Identifier, Node, Program, VariableDeclaration } from "acorn";
5
+ import MagicString from "magic-string";
6
+
1
7
  export interface AotTransformOptions {
2
8
  outDir?: string;
3
9
  sourcemap?: boolean;
@@ -5,16 +11,28 @@ export interface AotTransformOptions {
5
11
  exclude?: string[];
6
12
  verbose?: boolean;
7
13
  }
14
+
8
15
  export interface AotTransformResult {
9
16
  code: string;
10
17
  map?: string;
11
18
  generatedFiles: string[];
12
19
  }
20
+
13
21
  export type AotTransformFn = (
14
22
  source: string,
15
23
  filename: string,
16
24
  options?: AotTransformOptions,
17
25
  ) => AotTransformResult | null;
26
+
27
+ const _moduleSchemas = new Map<string, Map<string, LoydSchema<unknown>>>();
28
+
29
+ export function registerModuleSchemas(
30
+ moduleId: string,
31
+ schemas: Map<string, LoydSchema<unknown>>,
32
+ ): void {
33
+ _moduleSchemas.set(moduleId, schemas);
34
+ }
35
+
18
36
  export function hasLoydImports(source: string): boolean {
19
37
  return (
20
38
  source.includes("@loydjs/schema") ||
@@ -22,11 +40,169 @@ export function hasLoydImports(source: string): boolean {
22
40
  source.includes("@loydjs/compiler")
23
41
  );
24
42
  }
43
+
25
44
  export function transformLoydImports(
26
45
  source: string,
27
- _filename: string,
28
- _options: AotTransformOptions = {},
46
+ filename: string,
47
+ options: AotTransformOptions = {},
29
48
  ): AotTransformResult | null {
30
49
  if (!hasLoydImports(source)) return null;
31
- return { code: `/* @loydjs/vite: AOT-ready */\n${source}`, generatedFiles: [] };
50
+
51
+ if (!source.includes("compile(")) return null;
52
+
53
+ let ast: Program;
54
+
55
+ try {
56
+ ast = parseAcorn(source, {
57
+ ecmaVersion: 2022,
58
+ sourceType: "module",
59
+ }) as Program;
60
+ } catch {
61
+ return transformLoydImportsFallback(source, filename, options);
62
+ }
63
+
64
+ const ms = new MagicString(source);
65
+ const schemas = _moduleSchemas.get(filename) ?? new Map<string, LoydSchema<unknown>>();
66
+ const generatedFiles: string[] = [];
67
+ let modified = false;
68
+
69
+ walkAst(ast, (node: Node) => {
70
+ if (node.type !== "VariableDeclaration") return;
71
+
72
+ const decl = node as VariableDeclaration;
73
+ for (const declarator of decl.declarations) {
74
+ if (
75
+ declarator.type !== "VariableDeclarator" ||
76
+ !declarator.init ||
77
+ declarator.init.type !== "CallExpression"
78
+ )
79
+ continue;
80
+
81
+ const call = declarator.init as CallExpression;
82
+ const callee = call.callee as Identifier;
83
+
84
+ if (callee.type !== "Identifier" || callee.name !== "compile") continue;
85
+ if (call.arguments.length === 0) continue;
86
+
87
+ const schemaArg = call.arguments[0] as Identifier;
88
+ if (schemaArg.type !== "Identifier") continue;
89
+
90
+ const schemaName = schemaArg.name;
91
+ const schema = schemas.get(schemaName);
92
+
93
+ if (!schema) {
94
+ if (options.verbose) {
95
+ console.log(
96
+ `[@loydjs/vite] Cannot resolve schema "${schemaName}" statically in ${filename}, falling back to JIT`,
97
+ );
98
+ }
99
+ continue;
100
+ }
101
+
102
+ const varNode = declarator.id as Identifier;
103
+ if (varNode.type !== "Identifier") continue;
104
+
105
+ const varName = varNode.name;
106
+
107
+ try {
108
+ const inlined = generateInlineValidator(schema, varName);
109
+
110
+ ms.overwrite(
111
+ (node as Node & { start: number }).start,
112
+ (node as Node & { end: number }).end,
113
+ inlined,
114
+ );
115
+ modified = true;
116
+
117
+ if (options.verbose) {
118
+ console.log(
119
+ `[@loydjs/vite] AOT-inlined validator for "${varName}" (${schema._type}) in ${filename}`,
120
+ );
121
+ }
122
+ } catch (err) {
123
+ if (options.verbose) {
124
+ console.warn(`[@loydjs/vite] Failed to inline "${varName}" in ${filename}:`, err);
125
+ }
126
+ }
127
+ }
128
+ });
129
+
130
+ if (!modified) return null;
131
+
132
+ if (!ms.toString().includes("compile(")) {
133
+ removeCompileImport(ms, ast, source);
134
+ }
135
+
136
+ const result: AotTransformResult = {
137
+ code: ms.toString(),
138
+ generatedFiles,
139
+ };
140
+
141
+ if (options.sourcemap !== false) {
142
+ result.map = ms.generateMap({ source: filename, includeContent: true }).toString();
143
+ }
144
+
145
+ return result;
146
+ }
147
+
148
+ function transformLoydImportsFallback(
149
+ source: string,
150
+ _filename: string,
151
+ _options: AotTransformOptions,
152
+ ): AotTransformResult | null {
153
+ return {
154
+ code: `/* @loydjs/vite: AOT-ready (JIT fallback) */\n${source}`,
155
+ generatedFiles: [],
156
+ };
157
+ }
158
+
159
+ //Remove compile import
160
+
161
+ function removeCompileImport(ms: MagicString, ast: Program, _source: string): void {
162
+ walkAst(ast, (node: Node) => {
163
+ if (node.type !== "ImportDeclaration") return;
164
+
165
+ const imp = node as Node & {
166
+ source: { value: string };
167
+ specifiers: Array<Node & { imported: Identifier; local: Identifier }>;
168
+ start: number;
169
+ end: number;
170
+ };
171
+
172
+ if (imp.source.value !== "@loydjs/compiler") return;
173
+
174
+ const remaining = imp.specifiers.filter((s) => s.imported?.name !== "compile");
175
+
176
+ if (remaining.length === 0) {
177
+ ms.remove(imp.start, imp.end + 1); // +1 pour le \n
178
+ } else {
179
+ const names = remaining
180
+ .map((s) =>
181
+ s.imported.name === s.local.name
182
+ ? s.imported.name
183
+ : `${s.imported.name} as ${s.local.name}`,
184
+ )
185
+ .join(", ");
186
+ ms.overwrite(imp.start, imp.end, `import { ${names} } from "@loydjs/compiler"`);
187
+ }
188
+ });
189
+ }
190
+
191
+ function walkAst(node: Node, visitor: (node: Node) => void): void {
192
+ visitor(node);
193
+
194
+ for (const key of Object.keys(node)) {
195
+ const child = (node as unknown as Record<string, unknown>)[key];
196
+ if (child && typeof child === "object") {
197
+ if (Array.isArray(child)) {
198
+ for (const item of child) {
199
+ if (item && typeof item === "object" && "type" in item) {
200
+ walkAst(item as Node, visitor);
201
+ }
202
+ }
203
+ } else if ("type" in child) {
204
+ walkAst(child as Node, visitor);
205
+ }
206
+ }
207
+ }
32
208
  }