@adonisjs/vite 5.1.0 → 5.1.1

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.
@@ -0,0 +1,472 @@
1
+ import { n as makeAttributes, r as uniqBy } from "./utils-CdZpa_tV.js";
2
+ import { join } from "node:path";
3
+ import string from "@poppinss/utils/string";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ //#region src/vite.ts
6
+ const STYLE_FILE_REGEX = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\?)/;
7
+ /**
8
+ * Vite class exposes the APIs to generate tags and URLs for
9
+ * assets processed using vite.
10
+ */
11
+ var Vite = class {
12
+ /**
13
+ * We cache the manifest file content in production
14
+ * to avoid reading the file multiple times
15
+ */
16
+ #manifestCache;
17
+ #options;
18
+ #devServer;
19
+ /**
20
+ * Indicates whether the Vite manifest file exists on disk
21
+ */
22
+ hasManifestFile;
23
+ get useDevServer() {
24
+ return !!this.#devServer;
25
+ }
26
+ /**
27
+ * Creates a new Vite instance for managing asset compilation and serving
28
+ *
29
+ * @param options - Configuration options for Vite integration
30
+ *
31
+ * @example
32
+ * const vite = new Vite({
33
+ * buildDirectory: 'build',
34
+ * assetsUrl: '/assets',
35
+ * manifestFile: 'build/manifest.json'
36
+ * })
37
+ */
38
+ constructor(options) {
39
+ this.#options = options;
40
+ this.#options.assetsUrl = (this.#options.assetsUrl || "/").replace(/\/$/, "");
41
+ this.hasManifestFile = existsSync(this.#options.manifestFile);
42
+ }
43
+ /**
44
+ * Reads the file contents as JSON
45
+ */
46
+ #readFileAsJSON(filePath) {
47
+ return JSON.parse(readFileSync(filePath, "utf-8"));
48
+ }
49
+ /**
50
+ * Generates a JSON element with a custom toString implementation
51
+ */
52
+ #generateElement(element) {
53
+ return {
54
+ ...element,
55
+ toString() {
56
+ const attributes = `${makeAttributes(element.attributes)}`;
57
+ if (element.tag === "link") return `<${element.tag} ${attributes}/>`;
58
+ return `<${element.tag} ${attributes}>${element.children.join("\n")}</${element.tag}>`;
59
+ }
60
+ };
61
+ }
62
+ /**
63
+ * Returns the script needed for the HMR working with Vite
64
+ */
65
+ #getViteHmrScript(attributes) {
66
+ return this.#generateElement({
67
+ tag: "script",
68
+ attributes: {
69
+ type: "module",
70
+ src: "/@vite/client",
71
+ ...attributes
72
+ },
73
+ children: []
74
+ });
75
+ }
76
+ /**
77
+ * Check if the given path is a CSS path
78
+ */
79
+ #isCssPath(path) {
80
+ return path.match(STYLE_FILE_REGEX) !== null;
81
+ }
82
+ /**
83
+ * If the module is a style module
84
+ */
85
+ #isStyleModule(mod) {
86
+ if (this.#isCssPath(mod.url) || mod.id && /\?vue&type=style/.test(mod.id)) return true;
87
+ return false;
88
+ }
89
+ /**
90
+ * Unwrap attributes from the user defined function or return
91
+ * the attributes as it is
92
+ */
93
+ #unwrapAttributes(src, url, attributes) {
94
+ if (typeof attributes === "function") return attributes({
95
+ src,
96
+ url
97
+ });
98
+ return attributes;
99
+ }
100
+ /**
101
+ * Create a style tag for the given path
102
+ */
103
+ #makeStyleTag(src, url, attributes) {
104
+ const customAttributes = this.#unwrapAttributes(src, url, this.#options?.styleAttributes);
105
+ return this.#generateElement({
106
+ tag: "link",
107
+ attributes: {
108
+ rel: "stylesheet",
109
+ ...customAttributes,
110
+ ...attributes,
111
+ href: url
112
+ }
113
+ });
114
+ }
115
+ /**
116
+ * Create a script tag for the given path
117
+ */
118
+ #makeScriptTag(src, url, attributes) {
119
+ const customAttributes = this.#unwrapAttributes(src, url, this.#options?.scriptAttributes);
120
+ return this.#generateElement({
121
+ tag: "script",
122
+ attributes: {
123
+ type: "module",
124
+ ...customAttributes,
125
+ ...attributes,
126
+ src: url
127
+ },
128
+ children: []
129
+ });
130
+ }
131
+ /**
132
+ * Generate an asset URL for a given asset path
133
+ */
134
+ #generateAssetUrl(path) {
135
+ return `${this.#options.assetsUrl}/${path}`;
136
+ }
137
+ /**
138
+ * Generate a HTML tag for the given asset
139
+ */
140
+ #generateTag(asset, attributes) {
141
+ let url = "";
142
+ if (this.useDevServer) url = `/${asset}`;
143
+ else url = this.#generateAssetUrl(asset);
144
+ if (this.#isCssPath(asset)) return this.#makeStyleTag(asset, url, attributes);
145
+ return this.#makeScriptTag(asset, url, attributes);
146
+ }
147
+ /**
148
+ * Collect CSS files from the module graph recursively
149
+ */
150
+ #collectCss(mod, styleUrls, visitedModules, importer) {
151
+ if (!mod.url) return;
152
+ /**
153
+ * Prevent visiting the same module twice
154
+ */
155
+ if (visitedModules.has(mod.url)) return;
156
+ visitedModules.add(mod.url);
157
+ if (this.#isStyleModule(mod) && (!importer || !this.#isStyleModule(importer))) if (mod.url.startsWith("/")) styleUrls.add(mod.url);
158
+ else if (mod.url.startsWith("\0")) styleUrls.add(`/@id/__x00__${mod.url.substring(1)}`);
159
+ else styleUrls.add(`/@id/${mod.url}`);
160
+ mod.importedModules.forEach((dep) => this.#collectCss(dep, styleUrls, visitedModules, mod));
161
+ }
162
+ /**
163
+ * Generate style and script tags for the given entrypoints
164
+ * Also adds the @vite/client script
165
+ */
166
+ async #generateEntryPointsTagsForDevMode(entryPoints, attributes) {
167
+ const server = this.getDevServer();
168
+ const tags = entryPoints.map((entrypoint) => this.#generateTag(entrypoint, attributes));
169
+ const jsEntrypoints = entryPoints.filter((entrypoint) => !this.#isCssPath(entrypoint));
170
+ /**
171
+ * If the module graph is empty, that means we didn't execute the entrypoint
172
+ * yet : we just started the AdonisJS dev server.
173
+ * So let's execute the entrypoints to populate the module graph
174
+ */
175
+ if (server?.moduleGraph.idToModuleMap.size === 0) await Promise.allSettled(jsEntrypoints.map((entrypoint) => server.warmupRequest(`/${entrypoint}`)));
176
+ /**
177
+ * We need to collect the CSS files imported by the entrypoints
178
+ * Otherwise, we gonna have a FOUC each time we full reload the page
179
+ */
180
+ const preloadUrls = /* @__PURE__ */ new Set();
181
+ const visitedModules = /* @__PURE__ */ new Set();
182
+ const cssTagsElement = /* @__PURE__ */ new Set();
183
+ /**
184
+ * Let's search for the CSS files by browsing the module graph
185
+ * generated by Vite.
186
+ */
187
+ for (const entryPoint of jsEntrypoints) {
188
+ const filePath = join(server.config.root, entryPoint);
189
+ const entryMod = server.moduleGraph.getModuleById(string.toUnixSlash(filePath));
190
+ if (entryMod) this.#collectCss(entryMod, preloadUrls, visitedModules);
191
+ }
192
+ Array.from(preloadUrls).map((href) => this.#generateElement({
193
+ tag: "link",
194
+ attributes: {
195
+ rel: "stylesheet",
196
+ href
197
+ }
198
+ })).forEach((element) => cssTagsElement.add(element));
199
+ const viteHmr = this.#getViteHmrScript(attributes);
200
+ return [...cssTagsElement, viteHmr].concat(tags).sort((tag) => tag.tag === "link" ? -1 : 1);
201
+ }
202
+ /**
203
+ * Get a chunk from the manifest file for a given file name
204
+ */
205
+ #chunk(manifest, entrypoint) {
206
+ const chunk = manifest[entrypoint];
207
+ if (!chunk) throw new Error(`Cannot find "${entrypoint}" chunk in the manifest file`);
208
+ return chunk;
209
+ }
210
+ /**
211
+ * Get a list of chunks for a given filename
212
+ */
213
+ #chunksByFile(manifest, file) {
214
+ return Object.entries(manifest).filter(([, chunk]) => chunk.file === file).map(([_, chunk]) => chunk);
215
+ }
216
+ /**
217
+ * Generate preload tag for a given url
218
+ */
219
+ #makePreloadTagForUrl(url) {
220
+ const attributes = this.#isCssPath(url) ? {
221
+ rel: "preload",
222
+ as: "style",
223
+ href: url
224
+ } : {
225
+ rel: "modulepreload",
226
+ href: url
227
+ };
228
+ return this.#generateElement({
229
+ tag: "link",
230
+ attributes
231
+ });
232
+ }
233
+ /**
234
+ * Generate style and script tags for the given entrypoints
235
+ * using the manifest file
236
+ */
237
+ #generateEntryPointsTagsWithManifest(entryPoints, attributes) {
238
+ const manifest = this.manifest();
239
+ const tags = [];
240
+ const preloads = [];
241
+ for (const entryPoint of entryPoints) {
242
+ /**
243
+ * 1. We generate tags + modulepreload for the entrypoint
244
+ */
245
+ const chunk = this.#chunk(manifest, entryPoint);
246
+ preloads.push({ path: this.#generateAssetUrl(chunk.file) });
247
+ tags.push({
248
+ path: chunk.file,
249
+ tag: this.#generateTag(chunk.file, {
250
+ ...attributes,
251
+ integrity: chunk.integrity
252
+ })
253
+ });
254
+ /**
255
+ * 2. We go through the CSS files that are imported by the entrypoint
256
+ * and generate tags + preload for them
257
+ */
258
+ for (const css of chunk.css || []) {
259
+ preloads.push({ path: this.#generateAssetUrl(css) });
260
+ tags.push({
261
+ path: css,
262
+ tag: this.#generateTag(css)
263
+ });
264
+ }
265
+ /**
266
+ * 3. We go through every import of the entrypoint and generate preload
267
+ */
268
+ for (const importNode of chunk.imports || []) {
269
+ preloads.push({ path: this.#generateAssetUrl(manifest[importNode].file) });
270
+ /**
271
+ * 4. Finally, we generate tags + preload for the CSS files imported by the import
272
+ * of the entrypoint
273
+ */
274
+ for (const css of manifest[importNode].css || []) {
275
+ const subChunk = this.#chunksByFile(manifest, css);
276
+ preloads.push({ path: this.#generateAssetUrl(css) });
277
+ tags.push({
278
+ path: this.#generateAssetUrl(css),
279
+ tag: this.#generateTag(css, {
280
+ ...attributes,
281
+ integrity: subChunk[0]?.integrity
282
+ })
283
+ });
284
+ }
285
+ }
286
+ }
287
+ /**
288
+ * And finally, we return the preloads + script and link tags
289
+ */
290
+ return uniqBy(preloads, "path").sort((preload) => this.#isCssPath(preload.path) ? -1 : 1).map((preload) => this.#makePreloadTagForUrl(preload.path)).concat(tags.map(({ tag }) => tag));
291
+ }
292
+ /**
293
+ * Generate HTML tags (script and link) for the specified entry points
294
+ *
295
+ * In development mode, includes HMR script and dynamically discovers CSS files.
296
+ * In production mode, uses the manifest file to generate optimized tags with preloading.
297
+ *
298
+ * @param entryPoints - Single entry point or array of entry points to generate tags for
299
+ * @param attributes - Additional HTML attributes to apply to the generated tags
300
+ *
301
+ * @example
302
+ * // Generate tags for a single entry point
303
+ * const tags = await vite.generateEntryPointsTags('app.js')
304
+ *
305
+ * @example
306
+ * // Generate tags for multiple entry points with custom attributes
307
+ * const tags = await vite.generateEntryPointsTags(
308
+ * ['app.js', 'admin.js'],
309
+ * { defer: true }
310
+ * )
311
+ */
312
+ async generateEntryPointsTags(entryPoints, attributes) {
313
+ entryPoints = Array.isArray(entryPoints) ? entryPoints : [entryPoints];
314
+ if (this.useDevServer) return this.#generateEntryPointsTagsForDevMode(entryPoints, attributes);
315
+ return this.#generateEntryPointsTagsWithManifest(entryPoints, attributes);
316
+ }
317
+ /**
318
+ * Returns the base URL for serving static assets
319
+ *
320
+ * @example
321
+ * const url = vite.assetsUrl()
322
+ * // Returns: '/assets' or '/build' depending on configuration
323
+ */
324
+ assetsUrl() {
325
+ return this.#options.assetsUrl;
326
+ }
327
+ /**
328
+ * Returns the full URL path to a specific asset file
329
+ *
330
+ * In development mode, returns the asset path with leading slash.
331
+ * In production mode, uses the manifest file to return the versioned/hashed asset URL.
332
+ *
333
+ * @param asset - The relative path to the asset file
334
+ *
335
+ * @example
336
+ * const path = vite.assetPath('images/logo.png')
337
+ * // Dev: '/images/logo.png'
338
+ * // Prod: '/assets/images/logo-abc123.png'
339
+ */
340
+ assetPath(asset) {
341
+ if (this.useDevServer) return `/${asset}`;
342
+ const chunk = this.#chunk(this.manifest(), asset);
343
+ return this.#generateAssetUrl(chunk.file);
344
+ }
345
+ /**
346
+ * Returns the parsed Vite manifest file contents
347
+ *
348
+ * The manifest file contains information about compiled assets including
349
+ * file paths, integrity hashes, and import dependencies.
350
+ *
351
+ * @throws Will throw an exception when running in development mode
352
+ *
353
+ * @example
354
+ * const manifest = vite.manifest()
355
+ * console.log(manifest['app.js'].file) // 'assets/app-abc123.js'
356
+ */
357
+ manifest() {
358
+ if (this.useDevServer) throw new Error("Cannot read the manifest file when running in dev mode");
359
+ if (!this.hasManifestFile) throw new Error("Missing manifest file. Make sure to first create a build");
360
+ if (!this.#manifestCache) this.#manifestCache = this.#readFileAsJSON(this.#options.manifestFile);
361
+ return this.#manifestCache;
362
+ }
363
+ /**
364
+ * Creates and initializes the Vite development server
365
+ *
366
+ * Lazy loads Vite APIs to avoid importing them in production.
367
+ * The server runs in middleware mode and is configured for custom app integration.
368
+ *
369
+ * @param options - Additional Vite configuration options to merge with defaults
370
+ *
371
+ * @example
372
+ * await vite.createDevServer({
373
+ * root: './src',
374
+ * server: { port: 3000 }
375
+ * })
376
+ */
377
+ async createDevServer(options) {
378
+ const { createServer } = await import("vite");
379
+ const hmrPort = Number(process.env.VITE_HMR_PORT);
380
+ /**
381
+ * We do not await the server creation since it will
382
+ * slow down the boot process of AdonisJS
383
+ */
384
+ this.#devServer = await createServer({
385
+ server: {
386
+ middlewareMode: true,
387
+ ...hmrPort && !Number.isNaN(hmrPort) ? { hmr: { port: hmrPort } } : {}
388
+ },
389
+ appType: "custom",
390
+ ...options
391
+ });
392
+ }
393
+ /**
394
+ * Creates a server-side module runner for executing modules in Node.js context
395
+ *
396
+ * Only available in development mode as it requires the Vite dev server.
397
+ * Useful for server-side rendering and module transformation.
398
+ *
399
+ * @param options - Configuration options for the module runner
400
+ *
401
+ * @example
402
+ * const runner = await vite.createModuleRunner({
403
+ * hmr: { port: 24678 }
404
+ * })
405
+ * const mod = await runner.import('./app.js')
406
+ */
407
+ async createModuleRunner(options = {}) {
408
+ const { createServerModuleRunner } = await import("vite");
409
+ return createServerModuleRunner(this.#devServer.environments.ssr, options);
410
+ }
411
+ /**
412
+ * Gracefully stops the Vite development server
413
+ *
414
+ * Waits for the server creation promise to complete before closing.
415
+ *
416
+ * @example
417
+ * await vite.stopDevServer()
418
+ */
419
+ async stopDevServer() {
420
+ await this.#devServer?.close();
421
+ }
422
+ /**
423
+ * Returns the Vite development server instance
424
+ *
425
+ * Only available in development mode after calling createDevServer().
426
+ * Returns undefined in production or if the server hasn't been created yet.
427
+ *
428
+ * @example
429
+ * const server = vite.getDevServer()
430
+ * if (server) {
431
+ * console.log('Dev server is running on', server.config.server.port)
432
+ * }
433
+ */
434
+ getDevServer() {
435
+ return this.#devServer;
436
+ }
437
+ /**
438
+ * Generates the React Hot Module Replacement (HMR) script for development
439
+ *
440
+ * Only returns a script element in development mode. In production mode,
441
+ * returns null since HMR is not needed.
442
+ *
443
+ * @param attributes - Additional HTML attributes to apply to the script tag
444
+ *
445
+ * @example
446
+ * const hmrScript = vite.getReactHmrScript({ async: true })
447
+ * if (hmrScript) {
448
+ * console.log(hmrScript.toString()) // <script type="module" async>...<\/script>
449
+ * }
450
+ */
451
+ getReactHmrScript(attributes) {
452
+ if (!this.useDevServer) return null;
453
+ return this.#generateElement({
454
+ tag: "script",
455
+ attributes: {
456
+ type: "module",
457
+ ...attributes
458
+ },
459
+ children: [
460
+ "",
461
+ `import RefreshRuntime from '/@react-refresh'`,
462
+ `RefreshRuntime.injectIntoGlobalHook(window)`,
463
+ `window.$RefreshReg$ = () => {}`,
464
+ `window.$RefreshSig$ = () => (type) => type`,
465
+ `window.__vite_plugin_react_preamble_installed__ = true`,
466
+ ""
467
+ ]
468
+ });
469
+ }
470
+ };
471
+ //#endregion
472
+ export { Vite as t };
@@ -0,0 +1,71 @@
1
+ //#region src/vite_middleware.ts
2
+ /**
3
+ * Middleware for proxying requests between AdonisJS and Vite development server
4
+ *
5
+ * Since Vite dev server is integrated within the AdonisJS process, this
6
+ * middleware is used to proxy the requests to it.
7
+ *
8
+ * Some of the requests are directly handled by the Vite dev server,
9
+ * like the one for the assets, while others are passed down to the
10
+ * AdonisJS server.
11
+ */
12
+ var ViteMiddleware = class {
13
+ /**
14
+ * Creates a new ViteMiddleware instance
15
+ *
16
+ * @param vite - The Vite instance containing the dev server
17
+ *
18
+ * @example
19
+ * const middleware = new ViteMiddleware(viteInstance)
20
+ */
21
+ constructor(vite) {
22
+ this.vite = vite;
23
+ }
24
+ /**
25
+ * Handles HTTP requests by proxying them to the Vite dev server when appropriate
26
+ *
27
+ * @param request - The HTTP request object from AdonisJS context
28
+ * @param response - The HTTP response object from AdonisJS context
29
+ * @param next - Function to call the next middleware in the chain
30
+ *
31
+ * @example
32
+ * await middleware.handle(ctx, next)
33
+ */
34
+ async handle({ request, response }, next) {
35
+ const devServer = this.vite.getDevServer();
36
+ if (!devServer) return next();
37
+ return new Promise((resolve, reject) => {
38
+ function done(error) {
39
+ response.response.removeListener("finish", done);
40
+ if (error) reject(error);
41
+ else resolve();
42
+ }
43
+ /**
44
+ * When vite handles the request, we will resolve this promise after the
45
+ * response is sent.
46
+ */
47
+ response.response.addListener("finish", done);
48
+ /**
49
+ * Even if the request is not handled by Vite, we will relay headers
50
+ * to Node.js.
51
+ */
52
+ response.relayHeaders();
53
+ devServer.middlewares.handle(request.request, response.response, async () => {
54
+ /**
55
+ * This callback is invoked when Vite does not handle the request. In that
56
+ * case, we will call next and resolve this promise. Also we remove the
57
+ * unneeded "finish" listener
58
+ */
59
+ response.response.removeListener("finish", done);
60
+ try {
61
+ await next();
62
+ done();
63
+ } catch (error) {
64
+ done(error);
65
+ }
66
+ });
67
+ });
68
+ }
69
+ };
70
+ //#endregion
71
+ export { ViteMiddleware as t };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adonisjs/vite",
3
3
  "description": "Vite plugin for AdonisJS",
4
- "version": "5.1.0",
4
+ "version": "5.1.1",
5
5
  "engines": {
6
6
  "node": ">=24.0.0"
7
7
  },
@@ -30,7 +30,7 @@
30
30
  "precompile": "npm run lint && npm run clean",
31
31
  "clean": "del-cli build",
32
32
  "copy:templates": "copyfiles --up 1 \"stubs/**/*.stub\" build",
33
- "compile": "tsup-node && tsc --emitDeclarationOnly --declaration",
33
+ "compile": "tsdown && tsc --emitDeclarationOnly --declaration",
34
34
  "build": "npm run compile",
35
35
  "postbuild": "npm run copy:templates",
36
36
  "prebenchmark": "npm run build",
@@ -68,7 +68,7 @@
68
68
  "prettier": "^3.8.1",
69
69
  "release-it": "^19.2.4",
70
70
  "supertest": "^7.2.2",
71
- "tsup": "^8.5.1",
71
+ "tsdown": "^0.21.2",
72
72
  "typedoc": "^0.28.17",
73
73
  "typescript": "~5.9.3",
74
74
  "vite": "^7.3.1"
@@ -140,7 +140,7 @@
140
140
  }
141
141
  }
142
142
  },
143
- "tsup": {
143
+ "tsdown": {
144
144
  "entry": [
145
145
  "./src/hooks/build_hook.ts",
146
146
  "./providers/vite_provider.ts",
@@ -156,7 +156,8 @@
156
156
  "format": "esm",
157
157
  "dts": false,
158
158
  "sourcemap": false,
159
- "target": "esnext"
159
+ "target": "esnext",
160
+ "fixedExtension": false
160
161
  },
161
162
  "c8": {
162
163
  "reporter": [
@@ -1,56 +0,0 @@
1
- // src/vite_middleware.ts
2
- var ViteMiddleware = class {
3
- /**
4
- * Creates a new ViteMiddleware instance
5
- *
6
- * @param vite - The Vite instance containing the dev server
7
- *
8
- * @example
9
- * const middleware = new ViteMiddleware(viteInstance)
10
- */
11
- constructor(vite) {
12
- this.vite = vite;
13
- this.#devServer = this.vite.getDevServer();
14
- }
15
- #devServer;
16
- /**
17
- * Handles HTTP requests by proxying them to the Vite dev server when appropriate
18
- *
19
- * @param request - The HTTP request object from AdonisJS context
20
- * @param response - The HTTP response object from AdonisJS context
21
- * @param next - Function to call the next middleware in the chain
22
- *
23
- * @example
24
- * await middleware.handle(ctx, next)
25
- */
26
- async handle({ request, response }, next) {
27
- if (!this.#devServer) {
28
- return next();
29
- }
30
- return new Promise((resolve, reject) => {
31
- function done(error) {
32
- response.response.removeListener("finish", done);
33
- if (error) {
34
- reject(error);
35
- } else {
36
- resolve();
37
- }
38
- }
39
- response.response.addListener("finish", done);
40
- response.relayHeaders();
41
- this.#devServer.middlewares.handle(request.request, response.response, async () => {
42
- response.response.removeListener("finish", done);
43
- try {
44
- await next();
45
- done();
46
- } catch (error) {
47
- done(error);
48
- }
49
- });
50
- });
51
- }
52
- };
53
-
54
- export {
55
- ViteMiddleware
56
- };