vitrify 0.1.1 → 0.2.2
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 +14 -10
- package/dist/bin/cli.js +14 -12
- package/dist/bin/dev.js +5 -3
- package/dist/bin/run.js +5 -2
- package/dist/{vue → frameworks/vue}/fastify-ssr-plugin.js +0 -0
- package/dist/{vue → frameworks/vue}/prerender.js +1 -1
- package/dist/{vue → frameworks/vue}/server.js +0 -0
- package/dist/index.js +73 -20
- package/dist/plugins/quasar.js +19 -14
- package/dist/types/app-urls.d.ts +7 -6
- package/dist/types/bin/run.d.ts +2 -2
- package/dist/types/{vue → frameworks/vue}/fastify-ssr-plugin.d.ts +1 -1
- package/dist/types/{vue → frameworks/vue}/prerender.d.ts +1 -1
- package/dist/types/{vue → frameworks/vue}/server.d.ts +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/vitrify-config.d.ts +4 -1
- package/package.json +16 -18
- package/src/node/app-urls.ts +38 -0
- package/src/node/bin/build.ts +87 -0
- package/src/node/bin/cli.ts +157 -0
- package/src/node/bin/dev.ts +138 -0
- package/src/node/bin/run.ts +47 -0
- package/src/node/bin/test.ts +24 -0
- package/src/node/frameworks/vue/fastify-ssr-plugin.ts +137 -0
- package/src/node/frameworks/vue/prerender.ts +49 -0
- package/src/node/frameworks/vue/server.ts +38 -0
- package/src/node/helpers/logger.ts +142 -0
- package/src/node/helpers/routes.ts +29 -0
- package/src/node/helpers/ssr.ts.bak +52 -0
- package/src/node/helpers/utils.ts +37 -0
- package/src/node/index.ts +402 -0
- package/src/node/plugins/index.ts +12 -0
- package/src/node/plugins/quasar.ts +387 -0
- package/src/node/vitrify-config.ts +79 -0
- package/src/vite/vue/ssr/fastify-ssr-plugin.ts +1 -1
- package/src/vite/vue/ssr/prerender.ts +1 -1
- package/src/vite/vue/ssr/server.ts +6 -3
- package/src/vite/vue/components.d.ts +0 -25
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/node --experimental-specifier-resolution=node
|
|
2
|
+
import { baseConfig } from '../index.js'
|
|
3
|
+
// import { promises as fs } from 'fs'
|
|
4
|
+
// import { routesToPaths } from '../helpers/routes.js'
|
|
5
|
+
import { build as viteBuild } from 'vite'
|
|
6
|
+
// import { SsrFunction } from '../vitrify-config.js'
|
|
7
|
+
|
|
8
|
+
// export const prerender = async ({
|
|
9
|
+
// outDir,
|
|
10
|
+
// templatePath,
|
|
11
|
+
// manifestPath,
|
|
12
|
+
// entryServerPath,
|
|
13
|
+
// injectSsrContext
|
|
14
|
+
// }: {
|
|
15
|
+
// outDir: string
|
|
16
|
+
// templatePath: string
|
|
17
|
+
// manifestPath: string
|
|
18
|
+
// entryServerPath: string
|
|
19
|
+
// injectSsrContext: SsrFunction
|
|
20
|
+
// }) => {
|
|
21
|
+
// let template
|
|
22
|
+
// let manifest
|
|
23
|
+
// const promises = []
|
|
24
|
+
// template = (await fs.readFile(templatePath)).toString()
|
|
25
|
+
// manifest = await fs.readFile(manifestPath)
|
|
26
|
+
// let { render, getRoutes } = await import(entryServerPath)
|
|
27
|
+
// const routes = await getRoutes()
|
|
28
|
+
// const paths = routesToPaths(routes).filter(
|
|
29
|
+
// (i) => !i.includes(':') && !i.includes('*')
|
|
30
|
+
// )
|
|
31
|
+
// for (let url of paths) {
|
|
32
|
+
// const filename =
|
|
33
|
+
// (url.endsWith('/') ? 'index' : url.replace(/^\//g, '')) + '.html'
|
|
34
|
+
// console.log(`Generating ${filename}`)
|
|
35
|
+
// const ssrContext = {
|
|
36
|
+
// req: { headers: {}, url },
|
|
37
|
+
// res: {}
|
|
38
|
+
// }
|
|
39
|
+
// const [appHtml, preloadLinks] = await render(url, manifest, ssrContext)
|
|
40
|
+
|
|
41
|
+
// let html = template
|
|
42
|
+
// .replace(`<!--preload-links-->`, preloadLinks)
|
|
43
|
+
// .replace(`<!--app-html-->`, appHtml)
|
|
44
|
+
|
|
45
|
+
// html = injectSsrContext(html, ssrContext)
|
|
46
|
+
|
|
47
|
+
// promises.push(fs.writeFile(outDir + filename, html, 'utf-8'))
|
|
48
|
+
// }
|
|
49
|
+
// return Promise.all(promises)
|
|
50
|
+
// }
|
|
51
|
+
|
|
52
|
+
export async function build(opts: {
|
|
53
|
+
ssr?: 'client' | 'server' | 'ssg'
|
|
54
|
+
base?: string
|
|
55
|
+
outDir?: string
|
|
56
|
+
appDir?: URL
|
|
57
|
+
publicDir?: URL
|
|
58
|
+
}) {
|
|
59
|
+
const config = await baseConfig({
|
|
60
|
+
command: 'build',
|
|
61
|
+
mode: 'production',
|
|
62
|
+
ssr: opts?.ssr,
|
|
63
|
+
appDir: opts.appDir,
|
|
64
|
+
publicDir: opts.publicDir
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
config.build = {
|
|
68
|
+
...config.build,
|
|
69
|
+
minify: false,
|
|
70
|
+
outDir: opts.outDir,
|
|
71
|
+
emptyOutDir: !!opts.outDir
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (opts.base) {
|
|
75
|
+
config.define = {
|
|
76
|
+
...config.define,
|
|
77
|
+
__BASE_URL__: `'${opts.base}'`
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return viteBuild({
|
|
82
|
+
configFile: false,
|
|
83
|
+
base: opts.base,
|
|
84
|
+
// logLevel: 'silent',
|
|
85
|
+
...config
|
|
86
|
+
})
|
|
87
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import cac from 'cac'
|
|
3
|
+
import { getAppDir, parsePath } from '../app-urls.js'
|
|
4
|
+
import { printHttpServerUrls } from '../helpers/logger.js'
|
|
5
|
+
import type { ViteDevServer } from 'vite'
|
|
6
|
+
import type { Server } from 'net'
|
|
7
|
+
|
|
8
|
+
const cli = cac('vitrify')
|
|
9
|
+
cli
|
|
10
|
+
.command('build')
|
|
11
|
+
.option('-m, --mode [mode]', 'Build mode', { default: 'csr' })
|
|
12
|
+
.option('--base [base]', 'Base public path')
|
|
13
|
+
.option('--outDir [outDir]', 'Output directory')
|
|
14
|
+
.option('--appDir [appDir]', 'App directory')
|
|
15
|
+
.option('--publicDir [publicDir]', 'Public directory')
|
|
16
|
+
.option('--productName [productName]', 'Product name')
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
const { build } = await import('./build.js')
|
|
19
|
+
let appDir: URL
|
|
20
|
+
let prerender, ssrFunctions
|
|
21
|
+
if (options.appDir) {
|
|
22
|
+
if (options.appDir.slice(-1) !== '/') options.appDir += '/'
|
|
23
|
+
appDir = new URL(`file://${options.appDir}`)
|
|
24
|
+
} else {
|
|
25
|
+
appDir = getAppDir()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const baseOutDir =
|
|
29
|
+
parsePath(options.outDir, appDir) || new URL('dist/', appDir)
|
|
30
|
+
|
|
31
|
+
const args: {
|
|
32
|
+
base: string
|
|
33
|
+
appDir?: URL
|
|
34
|
+
publicDir?: URL
|
|
35
|
+
} = {
|
|
36
|
+
base: options.base,
|
|
37
|
+
appDir,
|
|
38
|
+
publicDir: parsePath(options.publicDir, appDir)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
switch (options.mode) {
|
|
42
|
+
case 'csr':
|
|
43
|
+
await build({
|
|
44
|
+
...args,
|
|
45
|
+
outDir: new URL('spa/', baseOutDir).pathname
|
|
46
|
+
})
|
|
47
|
+
break
|
|
48
|
+
case 'ssr':
|
|
49
|
+
await build({
|
|
50
|
+
ssr: 'client',
|
|
51
|
+
...args,
|
|
52
|
+
outDir: new URL('ssr/client/', baseOutDir).pathname
|
|
53
|
+
})
|
|
54
|
+
await build({
|
|
55
|
+
ssr: 'server',
|
|
56
|
+
...args,
|
|
57
|
+
outDir: new URL('ssr/server/', baseOutDir).pathname
|
|
58
|
+
})
|
|
59
|
+
break
|
|
60
|
+
case 'ssg':
|
|
61
|
+
await build({
|
|
62
|
+
ssr: 'client',
|
|
63
|
+
...args,
|
|
64
|
+
outDir: new URL('static/', baseOutDir).pathname
|
|
65
|
+
})
|
|
66
|
+
await build({
|
|
67
|
+
ssr: 'server',
|
|
68
|
+
...args,
|
|
69
|
+
outDir: new URL('ssr/server/', baseOutDir).pathname
|
|
70
|
+
})
|
|
71
|
+
;({ prerender, ssrFunctions } = await import(
|
|
72
|
+
new URL('ssr/server/prerender.mjs', baseOutDir).pathname
|
|
73
|
+
))
|
|
74
|
+
prerender({
|
|
75
|
+
outDir: new URL('static/', baseOutDir).pathname,
|
|
76
|
+
templatePath: new URL('static/index.html', baseOutDir).pathname,
|
|
77
|
+
manifestPath: new URL('static/ssr-manifest.json', baseOutDir)
|
|
78
|
+
.pathname,
|
|
79
|
+
entryServerPath: new URL('ssr/server/entry-server.mjs', baseOutDir)
|
|
80
|
+
.pathname,
|
|
81
|
+
ssrFunctions
|
|
82
|
+
})
|
|
83
|
+
break
|
|
84
|
+
default:
|
|
85
|
+
console.log('Invalid build mode')
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
cli
|
|
91
|
+
.command('dev')
|
|
92
|
+
.option('-m, --mode [mode]', 'Development server mode', { default: 'csr' })
|
|
93
|
+
.option(
|
|
94
|
+
'--host [host]',
|
|
95
|
+
'Specify which IP addresses the server should listen on',
|
|
96
|
+
{ default: '127.0.0.1' }
|
|
97
|
+
)
|
|
98
|
+
.option('--appDir [appDir]', 'Application directory')
|
|
99
|
+
.option('--publicDir [publicDir]', 'Public directory')
|
|
100
|
+
.action(async (options) => {
|
|
101
|
+
let server: Server
|
|
102
|
+
let vite: ViteDevServer
|
|
103
|
+
if (options.host === true) {
|
|
104
|
+
options.host = '0.0.0.0'
|
|
105
|
+
}
|
|
106
|
+
const { createServer } = await import('./dev.js')
|
|
107
|
+
const cwd = (await import('../app-urls.js')).getCwd()
|
|
108
|
+
switch (options.mode) {
|
|
109
|
+
case 'ssr':
|
|
110
|
+
;({ server, vite } = await createServer({
|
|
111
|
+
mode: 'ssr',
|
|
112
|
+
host: options.host,
|
|
113
|
+
appDir: parsePath(options.appDir, cwd),
|
|
114
|
+
publicDir: parsePath(options.publicDir, cwd)
|
|
115
|
+
}))
|
|
116
|
+
break
|
|
117
|
+
default:
|
|
118
|
+
;({ server, vite } = await createServer({
|
|
119
|
+
host: options.host,
|
|
120
|
+
appDir: parsePath(options.appDir, cwd),
|
|
121
|
+
publicDir: parsePath(options.publicDir, cwd)
|
|
122
|
+
}))
|
|
123
|
+
break
|
|
124
|
+
}
|
|
125
|
+
console.log('Dev server running at:')
|
|
126
|
+
printHttpServerUrls(server, vite.config)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
cli.command('test').action(async (options) => {
|
|
130
|
+
const { test } = await import('./test.js')
|
|
131
|
+
|
|
132
|
+
let appDir: URL
|
|
133
|
+
if (options.appDir) {
|
|
134
|
+
if (options.appDir.slice(-1) !== '/') options.appDir += '/'
|
|
135
|
+
appDir = new URL(`file://${options.appDir}`)
|
|
136
|
+
} else {
|
|
137
|
+
appDir = getAppDir()
|
|
138
|
+
}
|
|
139
|
+
await test({
|
|
140
|
+
appDir
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
cli.command('run <file>').action(async (file, options) => {
|
|
145
|
+
const { run } = await import('./run.js')
|
|
146
|
+
const filePath = new URL(file, `file://${process.cwd()}/`)
|
|
147
|
+
await run(filePath.pathname)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Default
|
|
151
|
+
cli.command('').action((command, options) => {
|
|
152
|
+
cli.outputHelp()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
cli.help()
|
|
156
|
+
|
|
157
|
+
cli.parse()
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { ViteDevServer, LogLevel } from 'vite'
|
|
2
|
+
import { searchForWorkspaceRoot } from 'vite'
|
|
3
|
+
import { baseConfig } from '../index.js'
|
|
4
|
+
import type { Server } from 'net'
|
|
5
|
+
import type { FastifyInstance } from 'fastify/types/instance'
|
|
6
|
+
import fastify from 'fastify'
|
|
7
|
+
import { readFileSync } from 'fs'
|
|
8
|
+
|
|
9
|
+
export async function createServer({
|
|
10
|
+
port = 3000,
|
|
11
|
+
logLevel = 'info',
|
|
12
|
+
mode = 'csr',
|
|
13
|
+
framework = 'vue',
|
|
14
|
+
host,
|
|
15
|
+
appDir,
|
|
16
|
+
publicDir
|
|
17
|
+
}: {
|
|
18
|
+
port?: number
|
|
19
|
+
logLevel?: LogLevel
|
|
20
|
+
mode?: 'csr' | 'ssr'
|
|
21
|
+
framework?: 'vue'
|
|
22
|
+
host?: string
|
|
23
|
+
appDir?: URL
|
|
24
|
+
publicDir?: URL
|
|
25
|
+
}) {
|
|
26
|
+
const { getAppDir, getCliDir, getCwd } = await import('../app-urls.js')
|
|
27
|
+
const cwd = getCwd()
|
|
28
|
+
const cliDir = getCliDir()
|
|
29
|
+
if (!appDir) appDir = getAppDir()
|
|
30
|
+
const { fastifySsrPlugin } = await import(
|
|
31
|
+
`../${framework}/fastify-ssr-plugin.js`
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @type {import('vite').ViteDevServer}
|
|
36
|
+
*/
|
|
37
|
+
const config = await baseConfig({
|
|
38
|
+
ssr: mode === 'ssr' ? 'server' : undefined,
|
|
39
|
+
command: 'dev',
|
|
40
|
+
mode: 'development',
|
|
41
|
+
appDir,
|
|
42
|
+
publicDir
|
|
43
|
+
})
|
|
44
|
+
config.logLevel = logLevel
|
|
45
|
+
config.server = {
|
|
46
|
+
port,
|
|
47
|
+
middlewareMode: mode === 'ssr' ? 'ssr' : undefined,
|
|
48
|
+
fs: {
|
|
49
|
+
allow: [
|
|
50
|
+
searchForWorkspaceRoot(process.cwd()),
|
|
51
|
+
searchForWorkspaceRoot(appDir.pathname),
|
|
52
|
+
searchForWorkspaceRoot(cliDir.pathname)
|
|
53
|
+
// appDir.pathname,
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
watch: {
|
|
57
|
+
// During tests we edit the files too fast and sometimes chokidar
|
|
58
|
+
// misses change events, so enforce polling for consistency
|
|
59
|
+
usePolling: true,
|
|
60
|
+
interval: 100
|
|
61
|
+
},
|
|
62
|
+
host
|
|
63
|
+
}
|
|
64
|
+
const vite = await (
|
|
65
|
+
await import('vite')
|
|
66
|
+
).createServer({
|
|
67
|
+
configFile: false,
|
|
68
|
+
...config
|
|
69
|
+
})
|
|
70
|
+
const { productName = 'Product name' } = JSON.parse(
|
|
71
|
+
readFileSync(new URL('package.json', appDir).pathname, {
|
|
72
|
+
encoding: 'utf-8'
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
let app: ViteDevServer | FastifyInstance
|
|
77
|
+
let server: Server
|
|
78
|
+
if (mode === 'ssr') {
|
|
79
|
+
console.log('SSR mode')
|
|
80
|
+
app = fastify()
|
|
81
|
+
await app.register(fastifySsrPlugin, {
|
|
82
|
+
appDir,
|
|
83
|
+
cliDir,
|
|
84
|
+
vite,
|
|
85
|
+
productName
|
|
86
|
+
})
|
|
87
|
+
// await app.register(middie)
|
|
88
|
+
// app.use(vite.middlewares)
|
|
89
|
+
|
|
90
|
+
// app.get('*', async (req, res) => {
|
|
91
|
+
// try {
|
|
92
|
+
// // const url = req.originalUrl
|
|
93
|
+
// const url = req.raw.url
|
|
94
|
+
// let template
|
|
95
|
+
// let render
|
|
96
|
+
// const ssrContext = {
|
|
97
|
+
// req,
|
|
98
|
+
// res
|
|
99
|
+
// }
|
|
100
|
+
// // always read fresh template in dev
|
|
101
|
+
// // template = readFileSync(resolve('index.html'), 'utf-8')
|
|
102
|
+
// template = readFileSync(new URL('index.html', cliDir)).toString()
|
|
103
|
+
|
|
104
|
+
// // template = await vite.transformIndexHtml(url, template)
|
|
105
|
+
// const entryUrl = new URL('ssr/entry-server.ts', cliDir).pathname
|
|
106
|
+
// render = (await vite.ssrLoadModule(entryUrl)).render
|
|
107
|
+
// let manifest
|
|
108
|
+
// // TODO: https://github.com/vitejs/vite/issues/2282
|
|
109
|
+
// try {
|
|
110
|
+
// manifest = {}
|
|
111
|
+
// } catch (e) {
|
|
112
|
+
// manifest = {}
|
|
113
|
+
// }
|
|
114
|
+
|
|
115
|
+
// const [appHtml, preloadLinks] = await render(url, manifest, ssrContext)
|
|
116
|
+
// const html = template
|
|
117
|
+
// .replace(`<!--preload-links-->`, preloadLinks)
|
|
118
|
+
// .replace(`<!--app-html-->`, appHtml)
|
|
119
|
+
// .replace('<!--product-name-->', productName)
|
|
120
|
+
|
|
121
|
+
// res.code(200)
|
|
122
|
+
// res.type('text/html')
|
|
123
|
+
// res.send(html)
|
|
124
|
+
// // res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
|
|
125
|
+
// } catch (e: any) {
|
|
126
|
+
// console.error(e.stack)
|
|
127
|
+
// vite && vite.ssrFixStacktrace(e)
|
|
128
|
+
// res.code(500)
|
|
129
|
+
// res.send(e.stack)
|
|
130
|
+
// }
|
|
131
|
+
// })
|
|
132
|
+
await app.listen(port || 3000, host)
|
|
133
|
+
server = app.server
|
|
134
|
+
} else {
|
|
135
|
+
server = (await vite.listen()).httpServer as Server
|
|
136
|
+
}
|
|
137
|
+
return { server, vite }
|
|
138
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { promises as fs } from 'fs'
|
|
2
|
+
import readline from 'readline'
|
|
3
|
+
import { getAppDir, getCliDir, getProjectURLs } from '../app-urls.js'
|
|
4
|
+
|
|
5
|
+
const rl = readline.createInterface({
|
|
6
|
+
input: process.stdin,
|
|
7
|
+
output: process.stdout
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export interface VitrifyContext {
|
|
11
|
+
vitrify: {
|
|
12
|
+
version: string
|
|
13
|
+
}
|
|
14
|
+
projectURLs: ReturnType<typeof getProjectURLs>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function run(filePath: string) {
|
|
18
|
+
const { run } = await import(filePath)
|
|
19
|
+
const appDir = getAppDir()
|
|
20
|
+
const cliDir = getCliDir()
|
|
21
|
+
const projectURLs = getProjectURLs(appDir, cliDir)
|
|
22
|
+
const pkg = JSON.parse(
|
|
23
|
+
(await fs.readFile(projectURLs.cli('package.json'), 'utf-8')).toString()
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if (!run)
|
|
27
|
+
throw new Error(
|
|
28
|
+
`${filePath} does not have an export named run. Aborting...`
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
rl.question(
|
|
32
|
+
`
|
|
33
|
+
The script ${filePath}
|
|
34
|
+
will now be executed by vitrify.
|
|
35
|
+
Make sure you trust the content of this script before proceeding.
|
|
36
|
+
Press enter to proceed or CTRL+C to abort.`,
|
|
37
|
+
async (answer) => {
|
|
38
|
+
await run({
|
|
39
|
+
vitrify: {
|
|
40
|
+
version: pkg.version
|
|
41
|
+
},
|
|
42
|
+
resolve: projectURLs
|
|
43
|
+
})
|
|
44
|
+
rl.close()
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { startVitest } from 'vitest/node'
|
|
2
|
+
import { baseConfig } from '../index.js'
|
|
3
|
+
export async function test(opts: { appDir: URL }) {
|
|
4
|
+
const config = await baseConfig({
|
|
5
|
+
appDir: opts.appDir,
|
|
6
|
+
command: 'test',
|
|
7
|
+
mode: 'development'
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
await startVitest(
|
|
11
|
+
[],
|
|
12
|
+
{
|
|
13
|
+
root: opts.appDir.pathname,
|
|
14
|
+
dir: opts.appDir.pathname,
|
|
15
|
+
|
|
16
|
+
globals: true,
|
|
17
|
+
environment: 'happy-dom'
|
|
18
|
+
// include: [
|
|
19
|
+
// `${opts.appDir.pathname}**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}`
|
|
20
|
+
// ]
|
|
21
|
+
},
|
|
22
|
+
config
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FastifyPluginCallback,
|
|
3
|
+
FastifyRequest,
|
|
4
|
+
FastifyReply
|
|
5
|
+
} from 'fastify'
|
|
6
|
+
import fastifyStatic from 'fastify-static'
|
|
7
|
+
import { readFileSync } from 'fs'
|
|
8
|
+
// import { injectSsrContext } from '../helpers/ssr.js'
|
|
9
|
+
import type { ViteDevServer } from 'vite'
|
|
10
|
+
import type { SsrFunction } from '../../vitrify-config.js'
|
|
11
|
+
|
|
12
|
+
export interface FastifySsrOptions {
|
|
13
|
+
baseUrl?: string
|
|
14
|
+
provide?: (
|
|
15
|
+
req: FastifyRequest,
|
|
16
|
+
res: FastifyReply
|
|
17
|
+
) => Promise<Record<string, unknown>>
|
|
18
|
+
vite?: ViteDevServer
|
|
19
|
+
cliDir?: URL
|
|
20
|
+
appDir?: URL
|
|
21
|
+
productName?: string
|
|
22
|
+
ssrFunctions?: SsrFunction[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const fastifySsrPlugin: FastifyPluginCallback<FastifySsrOptions> = async (
|
|
26
|
+
fastify,
|
|
27
|
+
options,
|
|
28
|
+
done
|
|
29
|
+
) => {
|
|
30
|
+
if (import.meta.env.MODE === 'development') {
|
|
31
|
+
if (!options.vite) throw new Error('Option vite cannot be undefined')
|
|
32
|
+
const middie = (await import('middie')).default
|
|
33
|
+
await fastify.register(middie)
|
|
34
|
+
fastify.use(options.vite.middlewares)
|
|
35
|
+
|
|
36
|
+
fastify.get('*', async (req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
// const url = req.originalUrl
|
|
39
|
+
const url = req.raw.url
|
|
40
|
+
const ssrContext = {
|
|
41
|
+
req,
|
|
42
|
+
res
|
|
43
|
+
}
|
|
44
|
+
// always read fresh template in dev
|
|
45
|
+
// template = readFileSync(resolve('index.html'), 'utf-8')
|
|
46
|
+
const template = readFileSync(
|
|
47
|
+
new URL('index.html', options.cliDir)
|
|
48
|
+
).toString()
|
|
49
|
+
|
|
50
|
+
// template = await vite.transformIndexHtml(url, template)
|
|
51
|
+
const entryUrl = new URL('ssr/entry-server.ts', options.cliDir).pathname
|
|
52
|
+
const render = (await options.vite!.ssrLoadModule(entryUrl)).render
|
|
53
|
+
let manifest
|
|
54
|
+
// TODO: https://github.com/vitejs/vite/issues/2282
|
|
55
|
+
try {
|
|
56
|
+
manifest = {}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
manifest = {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const [appHtml, preloadLinks] = await render(url, manifest, ssrContext)
|
|
62
|
+
const html = template
|
|
63
|
+
.replace(`<!--preload-links-->`, preloadLinks)
|
|
64
|
+
.replace(`<!--app-html-->`, appHtml)
|
|
65
|
+
.replace('<!--product-name-->', options.productName || 'Product name')
|
|
66
|
+
|
|
67
|
+
res.code(200)
|
|
68
|
+
res.type('text/html')
|
|
69
|
+
res.send(html)
|
|
70
|
+
// res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
|
|
71
|
+
} catch (e: any) {
|
|
72
|
+
console.error(e.stack)
|
|
73
|
+
options.vite && options.vite.ssrFixStacktrace(e)
|
|
74
|
+
res.code(500)
|
|
75
|
+
res.send(e.stack)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
} else {
|
|
79
|
+
options.baseUrl = options.baseUrl || '/'
|
|
80
|
+
fastify.register(fastifyStatic, {
|
|
81
|
+
root: new URL('./dist/ssr/client', options.appDir).pathname,
|
|
82
|
+
wildcard: false,
|
|
83
|
+
index: false,
|
|
84
|
+
prefix: options.baseUrl
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
fastify.get(`${options.baseUrl}*`, async (req, res) => {
|
|
88
|
+
const url = req.raw.url
|
|
89
|
+
const provide = options.provide ? await options.provide(req, res) : {}
|
|
90
|
+
const ssrContext: Record<string, any> = {
|
|
91
|
+
req,
|
|
92
|
+
res,
|
|
93
|
+
provide
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// template = readFileSync(new URL('../client/index.html', import.meta.url).pathname).toString()
|
|
97
|
+
// manifest = JSON.parse(readFileSync(new URL('../client/ssr-manifest.json', import.meta.url)).toString())
|
|
98
|
+
// render = (await import(new URL('./entry-server.mjs', import.meta.url).pathname)).render
|
|
99
|
+
const template = readFileSync(
|
|
100
|
+
new URL('./dist/ssr/client/index.html', options.appDir).pathname
|
|
101
|
+
).toString()
|
|
102
|
+
const manifest = JSON.parse(
|
|
103
|
+
readFileSync(
|
|
104
|
+
new URL('./dist/ssr/client/ssr-manifest.json', options.appDir)
|
|
105
|
+
).toString()
|
|
106
|
+
)
|
|
107
|
+
const render = (
|
|
108
|
+
await import(
|
|
109
|
+
new URL('./dist/ssr/server/entry-server.mjs', options.appDir).pathname
|
|
110
|
+
)
|
|
111
|
+
).render
|
|
112
|
+
|
|
113
|
+
const [appHtml, preloadLinks] = await render(url, manifest, ssrContext)
|
|
114
|
+
|
|
115
|
+
if (!ssrContext.initialState) ssrContext.initialState = {}
|
|
116
|
+
ssrContext.initialState.provide = provide
|
|
117
|
+
|
|
118
|
+
let html = template
|
|
119
|
+
.replace(`<!--preload-links-->`, preloadLinks)
|
|
120
|
+
.replace(`<!--app-html-->`, appHtml)
|
|
121
|
+
|
|
122
|
+
if (options.ssrFunctions?.length) {
|
|
123
|
+
for (const ssrFunction of options.ssrFunctions) {
|
|
124
|
+
html = ssrFunction(html, ssrContext)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
res.code(200)
|
|
129
|
+
res.type('text/html')
|
|
130
|
+
res.send(html)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
done()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { fastifySsrPlugin }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { promises as fs } from 'fs'
|
|
2
|
+
import { routesToPaths } from '../../helpers/routes.js'
|
|
3
|
+
import type { SsrFunction } from '../../vitrify-config.js'
|
|
4
|
+
|
|
5
|
+
export const prerender = async ({
|
|
6
|
+
outDir,
|
|
7
|
+
templatePath,
|
|
8
|
+
manifestPath,
|
|
9
|
+
entryServerPath,
|
|
10
|
+
ssrFunctions
|
|
11
|
+
}: {
|
|
12
|
+
outDir: string
|
|
13
|
+
templatePath: string
|
|
14
|
+
manifestPath: string
|
|
15
|
+
entryServerPath: string
|
|
16
|
+
ssrFunctions: SsrFunction[]
|
|
17
|
+
}) => {
|
|
18
|
+
const promises = []
|
|
19
|
+
const template = (await fs.readFile(templatePath)).toString()
|
|
20
|
+
const manifest = await fs.readFile(manifestPath)
|
|
21
|
+
const { render, getRoutes } = await import(entryServerPath)
|
|
22
|
+
const routes = await getRoutes()
|
|
23
|
+
const paths = routesToPaths(routes).filter(
|
|
24
|
+
(i) => !i.includes(':') && !i.includes('*')
|
|
25
|
+
)
|
|
26
|
+
for (const url of paths) {
|
|
27
|
+
const filename =
|
|
28
|
+
(url.endsWith('/') ? 'index' : url.replace(/^\//g, '')) + '.html'
|
|
29
|
+
console.log(`Generating ${filename}`)
|
|
30
|
+
const ssrContext = {
|
|
31
|
+
req: { headers: {}, url },
|
|
32
|
+
res: {}
|
|
33
|
+
}
|
|
34
|
+
const [appHtml, preloadLinks] = await render(url, manifest, ssrContext)
|
|
35
|
+
|
|
36
|
+
let html = template
|
|
37
|
+
.replace(`<!--preload-links-->`, preloadLinks)
|
|
38
|
+
.replace(`<!--app-html-->`, appHtml)
|
|
39
|
+
|
|
40
|
+
if (ssrFunctions?.length) {
|
|
41
|
+
for (const ssrFunction of ssrFunctions) {
|
|
42
|
+
html = ssrFunction(html, ssrContext)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
promises.push(fs.writeFile(outDir + filename, html, 'utf-8'))
|
|
47
|
+
}
|
|
48
|
+
return Promise.all(promises)
|
|
49
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify'
|
|
2
|
+
import fastify from 'fastify'
|
|
3
|
+
import type { SsrFunction } from '../../vitrify-config.js'
|
|
4
|
+
// import { setup } from 'virtual:fastify-setup'
|
|
5
|
+
import { fastifySsrPlugin } from './fastify-ssr-plugin.js'
|
|
6
|
+
// import { getPkgJsonDir } from '../app-urls.js'
|
|
7
|
+
|
|
8
|
+
export const createApp = ({
|
|
9
|
+
setup,
|
|
10
|
+
appDir,
|
|
11
|
+
baseUrl,
|
|
12
|
+
ssrFunctions
|
|
13
|
+
}: {
|
|
14
|
+
setup: (fastify: FastifyInstance) => any
|
|
15
|
+
appDir: URL
|
|
16
|
+
baseUrl?: string
|
|
17
|
+
ssrFunctions?: SsrFunction[]
|
|
18
|
+
}) => {
|
|
19
|
+
const app = fastify({
|
|
20
|
+
logger: true
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
app.register(fastifySsrPlugin, {
|
|
24
|
+
baseUrl,
|
|
25
|
+
appDir,
|
|
26
|
+
ssrFunctions
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
setup(app)
|
|
30
|
+
|
|
31
|
+
return app
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// const app = createApp({
|
|
35
|
+
// setup
|
|
36
|
+
// })
|
|
37
|
+
|
|
38
|
+
// app.listen(process.env.PORT || 3000, process.env.HOST || '127.0.0.1')
|