vitrify 0.19.1 → 0.21.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/dist/app-urls.js CHANGED
@@ -17,7 +17,7 @@ export const getPkgJsonDir = (dir) => {
17
17
  }
18
18
  return getPkgJsonDir(new URL('..', dir));
19
19
  };
20
- export const getAppDir = () => getPkgJsonDir(new URL(`file://${process.cwd()}/`));
20
+ export const getAppDir = (dir) => getPkgJsonDir(dir ?? new URL(`file://${process.cwd()}/`));
21
21
  export const getCliDir = () => getPkgJsonDir(new URL('./', import.meta.url));
22
22
  export const getCliViteDir = (cliDir) => new URL('src/vite/', cliDir);
23
23
  export const getSrcDir = (appDir) => new URL('src/', appDir);
package/dist/bin/cli.js CHANGED
@@ -5,6 +5,7 @@ import { getAppDir, parsePath } from '../app-urls.js';
5
5
  import { printHttpServerUrls, exitLogs } from '../helpers/logger.js';
6
6
  import { build as esbuild } from 'esbuild';
7
7
  import { readdir } from 'fs/promises';
8
+ import { loadSSRAssets } from '../frameworks/vue/fastify-ssr-plugin.js';
8
9
  const cli = cac('vitrify');
9
10
  cli
10
11
  .command('build')
@@ -73,12 +74,18 @@ cli
73
74
  ...args,
74
75
  outDir: fileURLToPath(new URL('ssr/server/', baseOutDir))
75
76
  });
76
- ({ prerender, onRendered } = await import(new URL('ssr/server/prerender.mjs', baseOutDir).pathname));
77
+ ({ prerender } = await import(new URL('ssr/server/prerender.mjs', baseOutDir).pathname));
78
+ const { template, manifest, render, getRoutes, onRendered } = await loadSSRAssets({
79
+ mode: 'ssg',
80
+ distDir: baseOutDir
81
+ });
82
+ const routes = await getRoutes();
77
83
  prerender({
78
84
  outDir: fileURLToPath(new URL('static/', baseOutDir)),
79
- templatePath: fileURLToPath(new URL('static/index.html', baseOutDir)),
80
- manifestPath: fileURLToPath(new URL('static/.vite/ssr-manifest.json', baseOutDir)),
81
- entryServerPath: new URL('ssr/server/entry-server.mjs', baseOutDir),
85
+ template,
86
+ manifest,
87
+ render,
88
+ routes,
82
89
  onRendered
83
90
  });
84
91
  break;
@@ -2,6 +2,7 @@ import fastifyStatic from '@fastify/static';
2
2
  import { readFileSync } from 'fs';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { addOrReplaceAppDiv, appendToBody, appendToHead } from '../../helpers/utils.js';
5
+ import { getAppDir } from '../../app-urls.js';
5
6
  const fastifySsrPlugin = async (fastify, options) => {
6
7
  options.baseUrl = options.baseUrl || '/';
7
8
  options.mode = options.mode || process.env.MODE || import.meta.env.MODE;
@@ -32,11 +33,6 @@ const fastifySsrPlugin = async (fastify, options) => {
32
33
  try {
33
34
  const url = req.raw.url?.replace(options.baseUrl, '/');
34
35
  const provide = options.provide ? await options.provide(req, res) : {};
35
- const ssrContext = {
36
- req,
37
- res,
38
- provide
39
- };
40
36
  let template = readFileSync(new URL('index.html', frameworkDir)).toString();
41
37
  template = await vite.transformIndexHtml(url, template);
42
38
  const entryUrl = fileURLToPath(new URL('ssr/entry-server.ts', frameworkDir));
@@ -49,39 +45,16 @@ const fastifySsrPlugin = async (fastify, options) => {
49
45
  catch (e) {
50
46
  manifest = {};
51
47
  }
52
- // const cssModules = [entryUrl]
53
- // const matchedModules = componentsModules(cssModules, vite!)
54
- // const css = collectCss({
55
- // mods: matchedModules
56
- // })
57
- const [appHtml, preloadLinks] = await render(url, manifest, ssrContext);
58
- if (!ssrContext.initialState)
59
- ssrContext.initialState = {};
60
- ssrContext.initialState.provide = provide;
61
- const initialStateScript = `
62
- <script>
63
- __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
64
- </script>`;
65
- const renderHtml = (html) => {
66
- return appendToHead(preloadLinks, appendToBody(initialStateScript, addOrReplaceAppDiv(appHtml, html)));
67
- };
68
- let html = renderHtml(template);
69
- // let html = template
70
- // .replace(`<!--app-html-->`, appHtml)
71
- // .replace('<!--product-name-->', options.productName || 'Product name')
72
- // // .replace('<!--dev-ssr-css-->', css)
73
- // .replace(
74
- // '<!--initial-state-->',
75
- // `<script>
76
- // __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
77
- // </script>`
78
- // )
79
- // html = appendToHead(preloadLinks, html)
80
- if (options.onRendered?.length) {
81
- for (const ssrFunction of options.onRendered) {
82
- html = ssrFunction(html, ssrContext);
83
- }
84
- }
48
+ const html = await renderHtml({
49
+ request: req,
50
+ reply: res,
51
+ url: url ?? '/',
52
+ provide,
53
+ onRendered: options.onRendered,
54
+ template,
55
+ manifest,
56
+ render
57
+ });
85
58
  res.code(200);
86
59
  res.type('text/html');
87
60
  res.send(html);
@@ -108,42 +81,90 @@ const fastifySsrPlugin = async (fastify, options) => {
108
81
  fastify.get(`${options.baseUrl}*`, async (req, res) => {
109
82
  const url = req.raw.url?.replace(options.baseUrl, '/');
110
83
  const provide = options.provide ? await options.provide(req, res) : {};
111
- const ssrContext = {
112
- req,
113
- res,
114
- provide
115
- };
116
- const template = readFileSync(fileURLToPath(new URL('./dist/ssr/client/index.html', options.appDir))).toString();
117
- const manifest = JSON.parse(readFileSync(new URL('./dist/ssr/client/.vite/ssr-manifest.json', options.appDir)).toString());
118
- const render = (await import(fileURLToPath(new URL('./dist/ssr/server/entry-server.mjs', options.appDir)))).render;
119
- const [appHtml, preloadLinks] = await render(url, manifest, ssrContext);
120
- if (!ssrContext.initialState)
121
- ssrContext.initialState = {};
122
- ssrContext.initialState.provide = provide;
123
- const initialStateScript = `
124
- <script>
125
- __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
126
- </script>`;
127
- const renderHtml = (html) => {
128
- return appendToHead(preloadLinks, appendToBody(initialStateScript, addOrReplaceAppDiv(appHtml, html)));
129
- };
130
- let html = renderHtml(template);
131
- // let html = template.replace(`<!--app-html-->`, appHtml).replace(
132
- // '<!--initial-state-->',
133
- // `<script>
134
- // __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
135
- // </script>`
136
- // )
137
- // html = appendToHead(preloadLinks, html)
138
- if (options.onRendered?.length) {
139
- for (const ssrFunction of options.onRendered) {
140
- html = ssrFunction(html, ssrContext);
141
- }
142
- }
84
+ const { template, manifest, render, onRendered } = await loadSSRAssets({
85
+ distDir: new URL('./dist/', options.appDir)
86
+ });
87
+ const html = await renderHtml({
88
+ request: req,
89
+ reply: res,
90
+ url: url ?? '/',
91
+ provide,
92
+ onRendered,
93
+ template,
94
+ manifest,
95
+ render
96
+ });
143
97
  res.code(200);
144
98
  res.type('text/html');
145
99
  res.send(html);
146
100
  });
147
101
  }
148
102
  };
149
- export { fastifySsrPlugin };
103
+ const renderTemplate = ({ template, initialStateScript, appHtml, preloadLinks }) => {
104
+ return appendToHead(preloadLinks, appendToBody(initialStateScript, addOrReplaceAppDiv(appHtml, template)));
105
+ };
106
+ const renderHtml = async (options) => {
107
+ const ssrContext = {
108
+ req: options.request,
109
+ res: options.reply,
110
+ provide: options.provide
111
+ };
112
+ const onRendered = options.onRendered ?? [];
113
+ const [appHtml, preloadLinks] = await options.render(options.url, options.manifest, ssrContext);
114
+ if (!ssrContext.initialState)
115
+ ssrContext.initialState = {};
116
+ ssrContext.initialState.provide = options.provide;
117
+ const initialStateScript = `
118
+ <script>
119
+ __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
120
+ </script>`;
121
+ let html = renderTemplate({
122
+ template: options.template,
123
+ appHtml,
124
+ initialStateScript,
125
+ preloadLinks
126
+ });
127
+ if (onRendered?.length) {
128
+ for (const ssrFunction of onRendered) {
129
+ html = ssrFunction(html, ssrContext);
130
+ }
131
+ }
132
+ return html;
133
+ };
134
+ const loadSSRAssets = async ({ mode, distDir } = {
135
+ mode: 'ssr'
136
+ }) => {
137
+ const appDir = getAppDir(new URL(import.meta.url));
138
+ const baseOutDir = distDir || new URL('dist/', appDir);
139
+ let templatePath, manifestPath, entryServerPath;
140
+ const onRenderedPath = fileURLToPath(new URL('ssr/server/virtual_vitrify-hooks.mjs', baseOutDir));
141
+ if (mode === 'ssg') {
142
+ templatePath = fileURLToPath(new URL('static/index.html', baseOutDir));
143
+ manifestPath = fileURLToPath(new URL('static/.vite/ssr-manifest.json', baseOutDir));
144
+ entryServerPath = fileURLToPath(new URL('ssr/server/entry-server.mjs', baseOutDir));
145
+ }
146
+ else {
147
+ templatePath = fileURLToPath(new URL('ssr/client/index.html', baseOutDir));
148
+ manifestPath = fileURLToPath(new URL('ssr/client/.vite/ssr-manifest.json', baseOutDir));
149
+ entryServerPath = fileURLToPath(new URL('ssr/server/entry-server.mjs', baseOutDir));
150
+ }
151
+ try {
152
+ const template = readFileSync(templatePath).toString();
153
+ const manifest = JSON.parse(readFileSync(manifestPath).toString());
154
+ const entryServer = await import(entryServerPath);
155
+ const { render, getRoutes } = entryServer;
156
+ const onRendered = (await import(onRenderedPath)).onRendered;
157
+ return {
158
+ template,
159
+ manifest,
160
+ render,
161
+ getRoutes,
162
+ onRendered
163
+ };
164
+ }
165
+ catch (e) {
166
+ console.error(e);
167
+ throw new Error('Unable to load SSR asset files');
168
+ }
169
+ };
170
+ export { fastifySsrPlugin, renderHtml, loadSSRAssets };
@@ -1,12 +1,8 @@
1
1
  import { existsSync, promises as fs, mkdirSync } from 'fs';
2
2
  import { routesToPaths } from '../../helpers/routes.js';
3
- import { appendToHead, addOrReplaceAppDiv } from '../../helpers/utils.js';
4
- export const prerender = async ({ outDir, templatePath, manifestPath, entryServerPath, onRendered }) => {
3
+ import { renderHtml } from './fastify-ssr-plugin.js';
4
+ export const prerender = async ({ outDir, template, manifest, render, routes, onRendered }) => {
5
5
  const promises = [];
6
- const template = (await fs.readFile(templatePath)).toString();
7
- const manifest = JSON.parse((await fs.readFile(manifestPath)).toString());
8
- const { render, getRoutes } = await import(entryServerPath);
9
- const routes = await getRoutes();
10
6
  const paths = routesToPaths(routes).filter((i) => !i.includes(':') && !i.includes('*'));
11
7
  const beasties = new (await import('beasties')).default({
12
8
  path: outDir,
@@ -22,18 +18,16 @@ export const prerender = async ({ outDir, templatePath, manifestPath, entryServe
22
18
  }
23
19
  const filename = (url.endsWith('/') ? 'index' : url.replace(/^\//g, '')) + '.html';
24
20
  console.log(`Generating ${filename}`);
25
- const ssrContext = {
26
- req: { headers: {}, url },
27
- res: {}
28
- };
29
- const [appHtml, preloadLinks] = await render(url, manifest, ssrContext);
30
- let html = addOrReplaceAppDiv(appHtml, template);
31
- html = appendToHead(preloadLinks, html);
32
- if (onRendered?.length) {
33
- for (const ssrFunction of onRendered) {
34
- html = ssrFunction(html, ssrContext);
35
- }
36
- }
21
+ let html = await renderHtml({
22
+ url,
23
+ manifest,
24
+ provide: {},
25
+ render,
26
+ request: { headers: {}, url },
27
+ reply: {},
28
+ template,
29
+ onRendered
30
+ });
37
31
  html = await beasties.process(html);
38
32
  promises.push(fs.writeFile(outDir + filename, html, 'utf-8'));
39
33
  }
package/dist/index.js CHANGED
@@ -43,11 +43,12 @@ const manualChunkNames = [
43
43
  ];
44
44
  const moduleChunks = {
45
45
  vue: ['vue', '@vue', 'vue-router'],
46
- quasar: ['quasar', '@quasar']
46
+ quasar: ['quasar'],
47
+ atQuasar: ['@quasar']
47
48
  };
48
49
  const manualChunksFn = (manualChunkList) => {
49
50
  return (id) => {
50
- const matchedModule = Object.entries(moduleChunks).find(([chunkName, moduleNames]) => moduleNames.some((moduleName) => id.includes(moduleName + '/')));
51
+ const matchedModule = Object.entries(moduleChunks).find(([chunkName, moduleNames]) => moduleNames.some((moduleName) => new RegExp(`\/${moduleName}\/`).test(id)));
51
52
  if (id.includes('vitrify/src/')) {
52
53
  const name = id.split('/').at(-1)?.split('.').at(0);
53
54
  if (name && manualChunkNames.includes(name))
@@ -528,6 +529,7 @@ export const baseConfig = async ({ ssr, appDir, publicDir, base = '/', command =
528
529
  input: [
529
530
  fileURLToPath(new URL('ssr/entry-server.ts', frameworkDir)),
530
531
  fileURLToPath(new URL('ssr/prerender.ts', frameworkDir)),
532
+ fileURLToPath(new URL('ssr/fastify-ssr-plugin.ts', frameworkDir)),
531
533
  fileURLToPath(new URL('ssr/server.ts', frameworkDir))
532
534
  ],
533
535
  external,
@@ -1,6 +1,6 @@
1
1
  export declare const resolve: (packageName: string, base: URL, counter?: number) => URL;
2
2
  export declare const getPkgJsonDir: (dir: URL) => URL;
3
- export declare const getAppDir: () => URL;
3
+ export declare const getAppDir: (dir?: URL) => URL;
4
4
  export declare const getCliDir: () => URL;
5
5
  export declare const getCliViteDir: (cliDir: URL) => URL;
6
6
  export declare const getSrcDir: (appDir: URL) => URL;
@@ -1,11 +1,12 @@
1
1
  import type { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
2
2
  import type { ViteDevServer } from 'vite';
3
3
  import type { OnRenderedHook } from '../../vitrify-config.js';
4
+ type ProvideFn = (req: FastifyRequest, res: FastifyReply) => Promise<Record<string, unknown | {
5
+ value: unknown;
6
+ }>>;
4
7
  export interface FastifySsrOptions {
5
8
  baseUrl?: string;
6
- provide?: (req: FastifyRequest, res: FastifyReply) => Promise<Record<string, unknown | {
7
- value: unknown;
8
- }>>;
9
+ provide?: ProvideFn;
9
10
  vitrifyDir?: URL;
10
11
  vite?: ViteDevServer;
11
12
  onRendered?: OnRenderedHook[];
@@ -15,5 +16,28 @@ export interface FastifySsrOptions {
15
16
  host?: string;
16
17
  }
17
18
  declare const fastifySsrPlugin: FastifyPluginAsync<FastifySsrOptions>;
18
- export { fastifySsrPlugin };
19
+ declare const renderHtml: (options: {
20
+ url: string;
21
+ request: FastifyRequest | {
22
+ headers: Record<string, unknown>;
23
+ url: string;
24
+ };
25
+ reply: FastifyReply | Record<string, unknown>;
26
+ provide: Record<string, unknown>;
27
+ onRendered?: OnRenderedHook[];
28
+ template: string;
29
+ manifest: Record<string, unknown>;
30
+ render: any;
31
+ }) => Promise<string>;
32
+ declare const loadSSRAssets: ({ mode, distDir }?: {
33
+ mode?: "ssr" | "ssg";
34
+ distDir?: URL;
35
+ }) => Promise<{
36
+ template: string;
37
+ manifest: any;
38
+ render: any;
39
+ getRoutes: any;
40
+ onRendered: any;
41
+ }>;
42
+ export { fastifySsrPlugin, renderHtml, loadSSRAssets };
19
43
  export type FastifySsrPlugin = typeof fastifySsrPlugin;
@@ -1,8 +1,10 @@
1
1
  import type { OnRenderedHook } from 'src/node/vitrify-config.js';
2
- export declare const prerender: ({ outDir, templatePath, manifestPath, entryServerPath, onRendered }: {
2
+ import { type RouteRecordRaw } from 'vue-router';
3
+ export declare const prerender: ({ outDir, template, manifest, render, routes, onRendered }: {
3
4
  outDir: string;
4
- templatePath: string;
5
- manifestPath: string;
6
- entryServerPath: string;
5
+ template: string;
6
+ manifest: Record<string, unknown>;
7
+ render: unknown;
8
+ routes: RouteRecordRaw[];
7
9
  onRendered: OnRenderedHook[];
8
10
  }) => Promise<void[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitrify",
3
- "version": "0.19.1",
3
+ "version": "0.21.0",
4
4
  "license": "MIT",
5
5
  "author": "Stefan van Herwijnen",
6
6
  "description": "Vite as your Full Stack development tool",
@@ -52,7 +52,7 @@
52
52
  "@fastify/static": "^8.1.1",
53
53
  "@unocss/core": "^66.0.0",
54
54
  "@unocss/preset-uno": "^66.0.0",
55
- "@unocss/preset-web-fonts": "66.1.0-beta.10",
55
+ "@unocss/preset-web-fonts": "66.1.0-beta.13",
56
56
  "@unocss/preset-wind": "^66.0.0",
57
57
  "@vitejs/plugin-vue": "^5.2.3",
58
58
  "ajv": "^8.17.1",
@@ -60,23 +60,23 @@
60
60
  "cac": "^6.7.14",
61
61
  "chalk": "^5.4.1",
62
62
  "cross-env": "^7.0.3",
63
- "esbuild": "^0.25.2",
64
- "fastify": "^5.2.2",
65
- "glob": "^11.0.1",
66
- "happy-dom": "^17.4.4",
63
+ "esbuild": "^0.25.3",
64
+ "fastify": "^5.3.2",
65
+ "glob": "^11.0.2",
66
+ "happy-dom": "^17.4.6",
67
67
  "is-port-reachable": "^4.0.0",
68
68
  "magic-string": "^0.30.17",
69
69
  "merge-deep": "^3.0.3",
70
70
  "readline": "^1.3.0",
71
71
  "rollup-plugin-visualizer": "^5.14.0",
72
- "sass": "1.86.3",
72
+ "sass": "1.87.0",
73
73
  "ts-node": "^10.9.2",
74
74
  "unocss": "^66.0.0",
75
- "unplugin-vue-components": "^28.4.1",
76
- "vite": "^6.2.5",
75
+ "unplugin-vue-components": "^28.5.0",
76
+ "vite": "^6.3.4",
77
77
  "vite-plugin-pwa": "^1.0.0",
78
78
  "vitefu": "^1.0.6",
79
- "vitest": "^3.1.1",
79
+ "vitest": "^3.1.2",
80
80
  "workbox-window": "^7.3.0"
81
81
  },
82
82
  "devDependencies": {
@@ -87,25 +87,25 @@
87
87
  "@types/connect": "^3.4.38",
88
88
  "@types/glob": "^8.1.0",
89
89
  "@types/merge-deep": "^3.0.3",
90
- "@types/node": "^22.14.0",
90
+ "@types/node": "^22.15.3",
91
91
  "@types/ws": "^8.18.1",
92
92
  "@unocss/preset-icons": "^66.0.0",
93
93
  "@vue/runtime-core": "^3.5.13",
94
- "beasties": "^0.3.1",
94
+ "beasties": "^0.3.3",
95
95
  "css": "^3.0.0",
96
96
  "css-to-tailwind-translator": "^1.2.8",
97
97
  "quasar": "^2.18.1",
98
- "rollup": "^4.39.0",
98
+ "rollup": "^4.40.1",
99
99
  "typescript": "^5.8.3",
100
100
  "vue": "^3.5.13",
101
- "vue-router": "^4.5.0"
101
+ "vue-router": "^4.5.1"
102
102
  },
103
103
  "peerDependencies": {
104
- "@fastify/static": "^8.1.0",
105
- "fastify": "^5.2.1",
106
- "quasar": "^2.17.7",
104
+ "@fastify/static": "^8.1.1",
105
+ "fastify": "^5.3.2",
106
+ "quasar": "^2.18.1",
107
107
  "vue": "^3.5.13",
108
- "vue-router": "^4.5.0"
108
+ "vue-router": "^4.5.1"
109
109
  },
110
110
  "publishConfig": {
111
111
  "access": "public",
@@ -19,8 +19,8 @@ export const getPkgJsonDir = (dir: URL): URL => {
19
19
  }
20
20
  return getPkgJsonDir(new URL('..', dir))
21
21
  }
22
- export const getAppDir = () =>
23
- getPkgJsonDir(new URL(`file://${process.cwd()}/`))
22
+ export const getAppDir = (dir?: URL) =>
23
+ getPkgJsonDir(dir ?? new URL(`file://${process.cwd()}/`))
24
24
  export const getCliDir = () => getPkgJsonDir(new URL('./', import.meta.url))
25
25
  export const getCliViteDir = (cliDir: URL) => new URL('src/vite/', cliDir)
26
26
  export const getSrcDir = (appDir: URL) => new URL('src/', appDir)
@@ -8,6 +8,7 @@ import type { ResolvedConfig, ViteDevServer } from 'vite'
8
8
  import type { Server } from 'net'
9
9
  import type { FastifyInstance } from 'fastify'
10
10
  import { readdir } from 'fs/promises'
11
+ import { loadSSRAssets } from '../frameworks/vue/fastify-ssr-plugin.js'
11
12
 
12
13
  const cli = cac('vitrify')
13
14
  cli
@@ -85,17 +86,23 @@ cli
85
86
  ...args,
86
87
  outDir: fileURLToPath(new URL('ssr/server/', baseOutDir))
87
88
  })
88
- ;({ prerender, onRendered } = await import(
89
+ ;({ prerender } = await import(
89
90
  new URL('ssr/server/prerender.mjs', baseOutDir).pathname
90
91
  ))
91
92
 
93
+ const { template, manifest, render, getRoutes, onRendered } =
94
+ await loadSSRAssets({
95
+ mode: 'ssg',
96
+ distDir: baseOutDir
97
+ })
98
+ const routes = await getRoutes()
99
+
92
100
  prerender({
93
101
  outDir: fileURLToPath(new URL('static/', baseOutDir)),
94
- templatePath: fileURLToPath(new URL('static/index.html', baseOutDir)),
95
- manifestPath: fileURLToPath(
96
- new URL('static/.vite/ssr-manifest.json', baseOutDir)
97
- ),
98
- entryServerPath: new URL('ssr/server/entry-server.mjs', baseOutDir),
102
+ template,
103
+ manifest,
104
+ render,
105
+ routes,
99
106
  onRendered
100
107
  })
101
108
  break
@@ -9,12 +9,16 @@ import {
9
9
  } from '../../helpers/utils.js'
10
10
  import type { ViteDevServer } from 'vite'
11
11
  import type { OnRenderedHook } from '../../vitrify-config.js'
12
+ import { getAppDir } from '../../app-urls.js'
13
+
14
+ type ProvideFn = (
15
+ req: FastifyRequest,
16
+ res: FastifyReply
17
+ ) => Promise<Record<string, unknown | { value: unknown }>>
18
+
12
19
  export interface FastifySsrOptions {
13
20
  baseUrl?: string
14
- provide?: (
15
- req: FastifyRequest,
16
- res: FastifyReply
17
- ) => Promise<Record<string, unknown | { value: unknown }>>
21
+ provide?: ProvideFn
18
22
  vitrifyDir?: URL
19
23
  vite?: ViteDevServer
20
24
  // frameworkDir?: URL
@@ -66,12 +70,6 @@ const fastifySsrPlugin: FastifyPluginAsync<FastifySsrOptions> = async (
66
70
  const url = req.raw.url?.replace(options.baseUrl!, '/')
67
71
  const provide = options.provide ? await options.provide(req, res) : {}
68
72
 
69
- const ssrContext: Record<string, any> = {
70
- req,
71
- res,
72
- provide
73
- }
74
-
75
73
  let template = readFileSync(
76
74
  new URL('index.html', frameworkDir)
77
75
  ).toString()
@@ -90,47 +88,16 @@ const fastifySsrPlugin: FastifyPluginAsync<FastifySsrOptions> = async (
90
88
  manifest = {}
91
89
  }
92
90
 
93
- // const cssModules = [entryUrl]
94
- // const matchedModules = componentsModules(cssModules, vite!)
95
- // const css = collectCss({
96
- // mods: matchedModules
97
- // })
98
-
99
- const [appHtml, preloadLinks] = await render(url, manifest, ssrContext)
100
-
101
- if (!ssrContext.initialState) ssrContext.initialState = {}
102
- ssrContext.initialState.provide = provide
103
-
104
- const initialStateScript = `
105
- <script>
106
- __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
107
- </script>`
108
- const renderHtml = (html: string) => {
109
- return appendToHead(
110
- preloadLinks,
111
- appendToBody(initialStateScript, addOrReplaceAppDiv(appHtml, html))
112
- )
113
- }
114
-
115
- let html = renderHtml(template)
116
- // let html = template
117
- // .replace(`<!--app-html-->`, appHtml)
118
- // .replace('<!--product-name-->', options.productName || 'Product name')
119
- // // .replace('<!--dev-ssr-css-->', css)
120
- // .replace(
121
- // '<!--initial-state-->',
122
- // `<script>
123
- // __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
124
- // </script>`
125
- // )
126
-
127
- // html = appendToHead(preloadLinks, html)
128
-
129
- if (options.onRendered?.length) {
130
- for (const ssrFunction of options.onRendered) {
131
- html = ssrFunction(html, ssrContext)
132
- }
133
- }
91
+ const html = await renderHtml({
92
+ request: req,
93
+ reply: res,
94
+ url: url ?? '/',
95
+ provide,
96
+ onRendered: options.onRendered,
97
+ template,
98
+ manifest,
99
+ render
100
+ })
134
101
 
135
102
  res.code(200)
136
103
  res.type('text/html')
@@ -156,67 +123,148 @@ const fastifySsrPlugin: FastifyPluginAsync<FastifySsrOptions> = async (
156
123
  fastify.get(`${options.baseUrl}*`, async (req, res) => {
157
124
  const url = req.raw.url?.replace(options.baseUrl!, '/')
158
125
  const provide = options.provide ? await options.provide(req, res) : {}
159
- const ssrContext: Record<string, any> = {
160
- req,
161
- res,
162
- provide
163
- }
164
126
 
165
- const template = readFileSync(
166
- fileURLToPath(new URL('./dist/ssr/client/index.html', options.appDir))
167
- ).toString()
168
- const manifest = JSON.parse(
169
- readFileSync(
170
- new URL('./dist/ssr/client/.vite/ssr-manifest.json', options.appDir)
171
- ).toString()
172
- )
173
- const render = (
174
- await import(
175
- fileURLToPath(
176
- new URL('./dist/ssr/server/entry-server.mjs', options.appDir)
177
- )
178
- )
179
- ).render
127
+ const { template, manifest, render, onRendered } = await loadSSRAssets({
128
+ distDir: new URL('./dist/', options.appDir)
129
+ })
130
+
131
+ const html = await renderHtml({
132
+ request: req,
133
+ reply: res,
134
+ url: url ?? '/',
135
+ provide,
136
+ onRendered,
137
+ template,
138
+ manifest,
139
+ render
140
+ })
180
141
 
181
- const [appHtml, preloadLinks] = await render(url, manifest, ssrContext)
142
+ res.code(200)
143
+ res.type('text/html')
144
+ res.send(html)
145
+ })
146
+ }
147
+ }
182
148
 
183
- if (!ssrContext.initialState) ssrContext.initialState = {}
184
- ssrContext.initialState.provide = provide
149
+ const renderTemplate = ({
150
+ template,
151
+ initialStateScript,
152
+ appHtml,
153
+ preloadLinks
154
+ }: {
155
+ template: string
156
+ initialStateScript: string
157
+ appHtml: string
158
+ preloadLinks: string
159
+ }) => {
160
+ return appendToHead(
161
+ preloadLinks,
162
+ appendToBody(initialStateScript, addOrReplaceAppDiv(appHtml, template))
163
+ )
164
+ }
185
165
 
186
- const initialStateScript = `
187
- <script>
188
- __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
189
- </script>`
190
- const renderHtml = (html: string) => {
191
- return appendToHead(
192
- preloadLinks,
193
- appendToBody(initialStateScript, addOrReplaceAppDiv(appHtml, html))
194
- )
195
- }
166
+ const renderHtml = async (options: {
167
+ url: string
168
+ request: FastifyRequest | { headers: Record<string, unknown>; url: string }
169
+ reply: FastifyReply | Record<string, unknown>
170
+ provide: Record<string, unknown>
171
+ onRendered?: OnRenderedHook[]
172
+ template: string
173
+ manifest: Record<string, unknown>
174
+ render: any
175
+ }) => {
176
+ const ssrContext: Record<string, any> = {
177
+ req: options.request,
178
+ res: options.reply,
179
+ provide: options.provide
180
+ }
196
181
 
197
- let html = renderHtml(template)
182
+ const onRendered = options.onRendered ?? []
198
183
 
199
- // let html = template.replace(`<!--app-html-->`, appHtml).replace(
200
- // '<!--initial-state-->',
201
- // `<script>
202
- // __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
203
- // </script>`
204
- // )
184
+ const [appHtml, preloadLinks] = await options.render(
185
+ options.url,
186
+ options.manifest,
187
+ ssrContext
188
+ )
205
189
 
206
- // html = appendToHead(preloadLinks, html)
190
+ if (!ssrContext.initialState) ssrContext.initialState = {}
191
+ ssrContext.initialState.provide = options.provide
207
192
 
208
- if (options.onRendered?.length) {
209
- for (const ssrFunction of options.onRendered) {
210
- html = ssrFunction(html, ssrContext)
211
- }
212
- }
193
+ const initialStateScript = `
194
+ <script>
195
+ __INITIAL_STATE__ = '${JSON.stringify(ssrContext.initialState)}'
196
+ </script>`
213
197
 
214
- res.code(200)
215
- res.type('text/html')
216
- res.send(html)
217
- })
198
+ let html = renderTemplate({
199
+ template: options.template,
200
+ appHtml,
201
+ initialStateScript,
202
+ preloadLinks
203
+ })
204
+
205
+ if (onRendered?.length) {
206
+ for (const ssrFunction of onRendered) {
207
+ html = ssrFunction(html, ssrContext)
208
+ }
209
+ }
210
+
211
+ return html
212
+ }
213
+
214
+ const loadSSRAssets = async (
215
+ {
216
+ mode,
217
+ distDir
218
+ }: {
219
+ mode?: 'ssr' | 'ssg'
220
+ distDir?: URL
221
+ } = {
222
+ mode: 'ssr'
223
+ }
224
+ ) => {
225
+ const appDir = getAppDir(new URL(import.meta.url))
226
+ const baseOutDir = distDir || new URL('dist/', appDir)
227
+
228
+ let templatePath, manifestPath, entryServerPath
229
+ const onRenderedPath = fileURLToPath(
230
+ new URL('ssr/server/virtual_vitrify-hooks.mjs', baseOutDir)
231
+ )
232
+ if (mode === 'ssg') {
233
+ templatePath = fileURLToPath(new URL('static/index.html', baseOutDir))
234
+ manifestPath = fileURLToPath(
235
+ new URL('static/.vite/ssr-manifest.json', baseOutDir)
236
+ )
237
+ entryServerPath = fileURLToPath(
238
+ new URL('ssr/server/entry-server.mjs', baseOutDir)
239
+ )
240
+ } else {
241
+ templatePath = fileURLToPath(new URL('ssr/client/index.html', baseOutDir))
242
+ manifestPath = fileURLToPath(
243
+ new URL('ssr/client/.vite/ssr-manifest.json', baseOutDir)
244
+ )
245
+ entryServerPath = fileURLToPath(
246
+ new URL('ssr/server/entry-server.mjs', baseOutDir)
247
+ )
248
+ }
249
+ try {
250
+ const template = readFileSync(templatePath).toString()
251
+ const manifest = JSON.parse(readFileSync(manifestPath).toString())
252
+ const entryServer = await import(entryServerPath)
253
+ const { render, getRoutes } = entryServer
254
+ const onRendered = (await import(onRenderedPath)).onRendered
255
+
256
+ return {
257
+ template,
258
+ manifest,
259
+ render,
260
+ getRoutes,
261
+ onRendered
262
+ }
263
+ } catch (e) {
264
+ console.error(e)
265
+ throw new Error('Unable to load SSR asset files')
218
266
  }
219
267
  }
220
268
 
221
- export { fastifySsrPlugin }
269
+ export { fastifySsrPlugin, renderHtml, loadSSRAssets }
222
270
  export type FastifySsrPlugin = typeof fastifySsrPlugin
@@ -1,26 +1,25 @@
1
1
  import { existsSync, promises as fs, mkdirSync } from 'fs'
2
2
  import type { OnRenderedHook } from 'src/node/vitrify-config.js'
3
3
  import { routesToPaths } from '../../helpers/routes.js'
4
- import { appendToHead, addOrReplaceAppDiv } from '../../helpers/utils.js'
4
+ import { renderHtml } from './fastify-ssr-plugin.js'
5
+ import { type RouteRecordRaw } from 'vue-router'
5
6
 
6
7
  export const prerender = async ({
7
8
  outDir,
8
- templatePath,
9
- manifestPath,
10
- entryServerPath,
9
+ template,
10
+ manifest,
11
+ render,
12
+ routes,
11
13
  onRendered
12
14
  }: {
13
15
  outDir: string
14
- templatePath: string
15
- manifestPath: string
16
- entryServerPath: string
16
+ template: string
17
+ manifest: Record<string, unknown>
18
+ render: unknown
19
+ routes: RouteRecordRaw[]
17
20
  onRendered: OnRenderedHook[]
18
21
  }) => {
19
22
  const promises = []
20
- const template = (await fs.readFile(templatePath)).toString()
21
- const manifest = JSON.parse((await fs.readFile(manifestPath)).toString())
22
- const { render, getRoutes } = await import(entryServerPath)
23
- const routes = await getRoutes()
24
23
  const paths = routesToPaths(routes).filter(
25
24
  (i) => !i.includes(':') && !i.includes('*')
26
25
  )
@@ -44,21 +43,17 @@ export const prerender = async ({
44
43
  const filename =
45
44
  (url.endsWith('/') ? 'index' : url.replace(/^\//g, '')) + '.html'
46
45
  console.log(`Generating ${filename}`)
47
- const ssrContext = {
48
- req: { headers: {}, url },
49
- res: {}
50
- }
51
- const [appHtml, preloadLinks] = await render(url, manifest, ssrContext)
52
-
53
- let html = addOrReplaceAppDiv(appHtml, template)
54
- html = appendToHead(preloadLinks, html)
55
-
56
- if (onRendered?.length) {
57
- for (const ssrFunction of onRendered) {
58
- html = ssrFunction(html, ssrContext)
59
- }
60
- }
61
46
 
47
+ let html = await renderHtml({
48
+ url,
49
+ manifest,
50
+ provide: {},
51
+ render,
52
+ request: { headers: {}, url },
53
+ reply: {},
54
+ template,
55
+ onRendered
56
+ })
62
57
  html = await beasties.process(html)
63
58
 
64
59
  promises.push(fs.writeFile(outDir + filename, html, 'utf-8'))
package/src/node/index.ts CHANGED
@@ -74,13 +74,16 @@ const manualChunkNames = [
74
74
 
75
75
  const moduleChunks = {
76
76
  vue: ['vue', '@vue', 'vue-router'],
77
- quasar: ['quasar', '@quasar']
77
+ quasar: ['quasar'],
78
+ atQuasar: ['@quasar']
78
79
  }
79
80
  const manualChunksFn = (manualChunkList?: string[]): ManualChunksOption => {
80
81
  return (id: string) => {
81
82
  const matchedModule = Object.entries(moduleChunks).find(
82
83
  ([chunkName, moduleNames]) =>
83
- moduleNames.some((moduleName) => id.includes(moduleName + '/'))
84
+ moduleNames.some((moduleName) =>
85
+ new RegExp(`\/${moduleName}\/`).test(id)
86
+ )
84
87
  )
85
88
  if (id.includes('vitrify/src/')) {
86
89
  const name = id.split('/').at(-1)?.split('.').at(0)
@@ -652,6 +655,7 @@ export const baseConfig = async ({
652
655
  input: [
653
656
  fileURLToPath(new URL('ssr/entry-server.ts', frameworkDir)),
654
657
  fileURLToPath(new URL('ssr/prerender.ts', frameworkDir)),
658
+ fileURLToPath(new URL('ssr/fastify-ssr-plugin.ts', frameworkDir)),
655
659
  fileURLToPath(new URL('ssr/server.ts', frameworkDir))
656
660
  ],
657
661
  external,
@@ -3,7 +3,6 @@ import { getAppDir } from '../../../node/app-urls.js'
3
3
  // import { setup } from 'virtual:fastify-setup'
4
4
  import { onSetup } from 'virtual:vitrify-hooks'
5
5
  import { fastifyCsrPlugin } from './fastify-csr-plugin'
6
- import type { ViteDevServer } from 'vite'
7
6
 
8
7
  // const appDir = getPkgJsonDir(import.meta.url)
9
8
  const getString = (str?: string) => str
@@ -7,12 +7,11 @@ import 'virtual:uno.css'
7
7
 
8
8
  import RootComponent from './RootComponent.vue'
9
9
  interface ssrContext {
10
- ssr: boolean
11
10
  provide?: Record<string, unknown>
12
11
  [key: string]: unknown
13
12
  }
14
13
 
15
- function capitalizeFirstLetter(string) {
14
+ function capitalizeFirstLetter(string: string) {
16
15
  return string.charAt(0).toUpperCase() + string.slice(1)
17
16
  }
18
17
 
@@ -1,11 +1,16 @@
1
- import { createApp } from '../main'
1
+ import { type FastifyReply, type FastifyRequest } from 'fastify'
2
+ import { createApp } from '../main.js'
3
+ import { renderToString as renderToStringVue } from 'vue/server-renderer'
2
4
 
3
- const initializeApp = async (url, ssrContext) => {
4
- const onRenderedList = []
5
+ const initializeApp = async (
6
+ url: string,
7
+ ssrContext: Record<string, unknown>
8
+ ) => {
9
+ const onRenderedList: (() => unknown)[] = []
5
10
  Object.assign(ssrContext, {
6
11
  _modules: new Set(),
7
12
  _meta: {},
8
- onRendered: (fn) => {
13
+ onRendered: (fn: () => unknown) => {
9
14
  onRenderedList.push(fn)
10
15
  }
11
16
  })
@@ -27,28 +32,43 @@ const initializeApp = async (url, ssrContext) => {
27
32
  export const getRoutes = async () =>
28
33
  (
29
34
  await initializeApp('/', {
30
- ssr: false,
31
35
  req: { headers: {}, url: '/' },
32
36
  res: {}
33
37
  })
34
38
  ).routes
35
39
 
36
- export async function render(url, manifest, ssrContext, renderToString) {
40
+ export async function render(
41
+ url: string,
42
+ manifest: Record<string, unknown>,
43
+ ssrContext: {
44
+ request: FastifyRequest | { headers: Record<string, unknown>; url: string }
45
+ reply: FastifyReply | Record<string, unknown>
46
+ provide: Record<string, unknown>
47
+ },
48
+ renderToString: typeof renderToStringVue
49
+ ) {
37
50
  if (!renderToString)
38
51
  renderToString = (await import('vue/server-renderer')).renderToString
39
52
  const { app, router } = await initializeApp(url, ssrContext)
40
53
 
41
- const ctx = {
54
+ const ctx: {
55
+ modules?: Map<unknown, unknown>
56
+ transports?: Record<string, unknown>
57
+ __qMetaList: unknown[]
58
+ } = {
42
59
  __qMetaList: []
43
60
  }
44
61
  const html = await renderToString(app, ctx)
45
62
 
46
- const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
63
+ const preloadLinks = renderPreloadLinks(ctx.modules!, manifest)
47
64
 
48
65
  return [html, preloadLinks]
49
66
  }
50
67
 
51
- function renderPreloadLinks(modules, manifest) {
68
+ function renderPreloadLinks(
69
+ modules: Map<unknown, unknown>,
70
+ manifest: Record<string, unknown>
71
+ ) {
52
72
  let links = ''
53
73
  const seen = new Set()
54
74
  modules.forEach((id) => {
@@ -1,3 +1,7 @@
1
- import { fastifySsrPlugin } from '../../../node/frameworks/vue/fastify-ssr-plugin.js'
1
+ import {
2
+ fastifySsrPlugin,
3
+ renderHtml,
4
+ loadSSRAssets
5
+ } from '../../../node/frameworks/vue/fastify-ssr-plugin.js'
2
6
 
3
- export { fastifySsrPlugin }
7
+ export { fastifySsrPlugin, renderHtml, loadSSRAssets }