vitrify 0.1.0 → 0.2.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.
Files changed (46) hide show
  1. package/README.md +86 -0
  2. package/dist/app-urls.js +36 -0
  3. package/dist/bin/build.js +73 -0
  4. package/dist/bin/cli.js +139 -0
  5. package/dist/bin/dev.js +108 -0
  6. package/dist/bin/run.js +29 -0
  7. package/dist/frameworks/vue/fastify-ssr-plugin.js +91 -0
  8. package/dist/frameworks/vue/prerender.js +29 -0
  9. package/dist/frameworks/vue/server.js +20 -0
  10. package/dist/helpers/logger.js +108 -0
  11. package/dist/helpers/routes.js +24 -0
  12. package/dist/helpers/utils.js +24 -0
  13. package/dist/index.js +341 -0
  14. package/dist/plugins/index.js +1 -0
  15. package/dist/plugins/quasar.js +299 -0
  16. package/dist/types/app-urls.d.ts +12 -0
  17. package/dist/types/bin/build.d.ts +8 -0
  18. package/dist/types/bin/cli.d.ts +2 -0
  19. package/dist/types/bin/dev.d.ts +15 -0
  20. package/dist/types/bin/run.d.ts +8 -0
  21. package/dist/types/bin/test.d.ts +3 -0
  22. package/dist/types/frameworks/vue/fastify-ssr-plugin.d.ts +14 -0
  23. package/dist/types/frameworks/vue/prerender.d.ts +8 -0
  24. package/dist/types/frameworks/vue/server.d.ts +9 -0
  25. package/dist/types/helpers/logger.d.ts +23 -0
  26. package/dist/types/helpers/routes.d.ts +2 -0
  27. package/dist/types/helpers/utils.d.ts +5 -0
  28. package/dist/types/index.d.ts +15 -0
  29. package/dist/types/plugins/index.d.ts +7 -0
  30. package/dist/types/plugins/quasar.d.ts +16 -0
  31. package/dist/types/vitrify-config.d.ts +67 -0
  32. package/dist/vitrify-config.js +1 -0
  33. package/package.json +94 -19
  34. package/src/node/frameworks/vue/fastify-ssr-plugin.ts +137 -0
  35. package/src/node/frameworks/vue/prerender.ts +49 -0
  36. package/src/node/frameworks/vue/server.ts +38 -0
  37. package/src/vite/vue/csr/entry.ts +8 -0
  38. package/src/vite/vue/index.html +16 -0
  39. package/src/vite/vue/main.ts +89 -0
  40. package/src/vite/vue/ssr/entry-client.ts +9 -0
  41. package/src/vite/vue/ssr/entry-server.ts +97 -0
  42. package/src/vite/vue/ssr/fastify-ssr-plugin.ts +120 -0
  43. package/src/vite/vue/ssr/prerender.ts +4 -0
  44. package/src/vite/vue/ssr/server.ts +18 -0
  45. package/src/vite/vue/ssr/server.ts.bak +61 -0
  46. package/src/vite/vue/ssr/tsconfig.json +9 -0
package/package.json CHANGED
@@ -1,23 +1,42 @@
1
1
  {
2
2
  "name": "vitrify",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "license": "MIT",
5
5
  "author": "Stefan van Herwijnen",
6
- "description": "Pre-configured Vite CLI",
6
+ "description": "Pre-configured Vite CLI for your framework",
7
+ "type": "module",
7
8
  "bin": {
8
- "vitrify": "bin/vitrify.js"
9
+ "vitrify": "./dist/bin/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": "./dist/index.js",
13
+ "./*": "./dist/*.js",
14
+ "./build": "./dist/bin/build.js",
15
+ "./dev": "./dist/bin/dev.js",
16
+ "./helpers/*": "./dist/helpers/*.js",
17
+ "./vite/*": "./src/vite/*"
18
+ },
19
+ "typesVersions": {
20
+ "*": {
21
+ "*": [
22
+ "./dist/types/index.d.ts"
23
+ ],
24
+ "helpers/*": [
25
+ "./dist/types/helpers/*.d.ts"
26
+ ],
27
+ "build": [
28
+ "./dist/types/bin/build.d.ts"
29
+ ],
30
+ "dev": [
31
+ "./dist/types/bin/dev.d.ts"
32
+ ],
33
+ "help": [
34
+ "./dist/types/bin/help.d.ts"
35
+ ]
36
+ }
9
37
  },
10
- "main": "dist/node/index.js",
11
- "types": "dist/node/index.d.ts",
12
- "files": [
13
- "bin",
14
- "dist",
15
- "client.d.ts",
16
- "src/client",
17
- "types"
18
- ],
19
38
  "engines": {
20
- "node": ">=12.2.0"
39
+ "node": ">=16.0.0"
21
40
  },
22
41
  "repository": {
23
42
  "type": "git",
@@ -28,9 +47,65 @@
28
47
  "url": "https://github.com/simsustech/vitrify/issues"
29
48
  },
30
49
  "homepage": "https://github.com/simsustech/vitrify/tree/main/#readme",
31
- "dependencies": {},
32
- "optionalDependencies": {},
33
- "devDependencies": {},
34
- "peerDependencies": {},
35
- "scripts": {}
36
- }
50
+ "scripts": {
51
+ "build": "tsc",
52
+ "test": "echo \"Error: no test specified\" && exit 0"
53
+ },
54
+ "dependencies": {
55
+ "@quasar/extras": "^1.13.5",
56
+ "@vitejs/plugin-vue": "^2.3.1",
57
+ "@vue/compiler-sfc": "^3.2.33",
58
+ "@vue/server-renderer": "^3.2.33",
59
+ "builtin-modules": "^3.2.0",
60
+ "cac": "^6.7.12",
61
+ "chalk": "^5.0.1",
62
+ "cross-env": "^7.0.3",
63
+ "fastify": "^3.28.0",
64
+ "fastify-static": "^4.6.1",
65
+ "glob": "^8.0.1",
66
+ "happy-dom": "^2.55.0",
67
+ "import-meta-resolve": "^1.1.1",
68
+ "local-pkg": "^0.4.1",
69
+ "magic-string": "^0.26.1",
70
+ "merge-deep": "^3.0.3",
71
+ "middie": "^6.0.0",
72
+ "readline": "^1.3.0",
73
+ "sass": "1.50.0",
74
+ "vite": "^2.9.5",
75
+ "vitest": "^0.9.3"
76
+ },
77
+ "devDependencies": {
78
+ "@types/glob": "^7.2.0",
79
+ "@types/merge-deep": "^3.0.0",
80
+ "@types/node": "^17.0.24",
81
+ "@types/ws": "^8.5.3",
82
+ "@vue/runtime-core": "^3.2.33",
83
+ "quasar": "^2.6.6",
84
+ "rollup": "^2.70.1",
85
+ "typescript": "^4.6.3",
86
+ "unplugin-vue-components": "^0.19.3",
87
+ "vite": "^2.9.5",
88
+ "vue": "^3.2.33",
89
+ "vue-router": "^4.0.14"
90
+ },
91
+ "peerDependencies": {
92
+ "fastify": "^3.28.0",
93
+ "fastify-plugin": "^3.0.1",
94
+ "fastify-sensible": "^3.1.2",
95
+ "fastify-static": "^4.6.1",
96
+ "quasar": "^2.6.6",
97
+ "vue": "^3.2.33",
98
+ "vue-router": "^4.0.14"
99
+ },
100
+ "publishConfig": {
101
+ "access": "public",
102
+ "registry": "https://registry.npmjs.org/"
103
+ },
104
+ "files": [
105
+ "dist",
106
+ "src/node/frameworks",
107
+ "src/vite",
108
+ "!dist/**/*.test.js",
109
+ "!dist/**/test.js"
110
+ ]
111
+ }
@@ -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')
@@ -0,0 +1,8 @@
1
+ import { createApp } from '../main'
2
+
3
+ createApp().then(({ app, router }) => {
4
+ // wait until router is ready before mounting to ensure hydration match
5
+ router.isReady().then(() => {
6
+ app.mount('#app')
7
+ })
8
+ })
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title><!--product-name--></title>
7
+ <!--preload-links-->
8
+ </head>
9
+
10
+ <body>
11
+ <!-- Do not add whitespace or newlines to #app, this will cause hydration errors -->
12
+ <div id="app"><!--app-html--></div>
13
+ <!--entry-script-->
14
+ <!--initial-state-->
15
+ </body>
16
+ </html>
@@ -0,0 +1,89 @@
1
+ import App from 'src/App.vue'
2
+ import createRouter from 'src/router'
3
+ import {
4
+ createSSRApp,
5
+ createApp as createVueApp,
6
+ h,
7
+ onMounted,
8
+ getCurrentInstance
9
+ } from 'vue'
10
+ // import { Quasar, useQuasar } from 'quasar'
11
+ // import quasarPlugins from 'virtual:quasar-plugins'
12
+ // import bootFunctions from 'virtual:quasar-boot'
13
+ import bootFunctions from 'virtual:boot-functions'
14
+ import onMountedHooks from 'virtual:on-mounted-hooks'
15
+ // import 'virtual:quasar-extras'
16
+ // import * as directives from 'quasar/directives'
17
+ import routes from 'src/router/routes'
18
+ import 'virtual:global-css'
19
+ import * as staticImports from 'virtual:static-imports'
20
+
21
+ interface ssrContext {
22
+ ssr: boolean
23
+ provide?: Record<string, unknown>
24
+ [key: string]: unknown
25
+ }
26
+
27
+ export async function createApp(
28
+ ssr?: 'client' | 'server',
29
+ ssrContext?: ssrContext
30
+ ) {
31
+ let app
32
+ const RootComponent = {
33
+ name: 'AppWrapper',
34
+ setup(props) {
35
+ const instance = getCurrentInstance()
36
+
37
+ onMounted(async () => {
38
+ for (let fn of onMountedHooks) {
39
+ await fn(instance, staticImports)
40
+ }
41
+ // onAppMounted()
42
+ // const { proxy: { $q } } = getCurrentInstance()
43
+ // $q.onSSRHydrated !== void 0 && $q.onSSRHydrated()
44
+ })
45
+
46
+ return () => h(App, props)
47
+ }
48
+ }
49
+ if (ssr) {
50
+ app = createSSRApp(RootComponent)
51
+ } else {
52
+ app = createVueApp(RootComponent)
53
+ }
54
+ const router = createRouter()
55
+ app.use(router)
56
+
57
+ // Workaround to fix hydration errors when serving html files directly
58
+ router.beforeEach((to, from, next) => {
59
+ if (to.path.endsWith('.html')) {
60
+ next({ path: to.path.replace('.html', '') })
61
+ }
62
+
63
+ next()
64
+ })
65
+
66
+ // app.use(Quasar, {
67
+ // plugins: quasarPlugins,
68
+ // directives
69
+ // }, ssrContext)
70
+
71
+ let provide: Record<string, unknown> = {}
72
+ if (import.meta.env.SSR) {
73
+ if (ssrContext?.provide) {
74
+ provide = ssrContext?.provide
75
+ }
76
+ } else {
77
+ // @ts-ignore
78
+ provide = window.__INITIAL_STATE__?.provide
79
+ }
80
+ for (let key in provide) {
81
+ app.provide(key, provide[key])
82
+ }
83
+
84
+ for (let fn of bootFunctions) {
85
+ await fn({ app, ssrContext, staticImports })
86
+ }
87
+
88
+ return { app, router, routes }
89
+ }
@@ -0,0 +1,9 @@
1
+ import { createApp } from '../main.js'
2
+
3
+ // const { app, router } = createApp()
4
+ createApp('client').then(({ app, router }) => {
5
+ // wait until router is ready before mounting to ensure hydration match
6
+ router.isReady().then(() => {
7
+ app.mount('#app')
8
+ })
9
+ })
@@ -0,0 +1,97 @@
1
+ import { createApp } from '../main.js'
2
+ import { renderToString } from '@vue/server-renderer'
3
+ // import * as ApolloSSR from '@vue/apollo-ssr'
4
+ // import { ApolloClients } from '@vue/apollo-composable'
5
+ // import serialize from 'serialize-javascript'
6
+
7
+ const initializeApp = async (url, ssrContext) => {
8
+ const onRenderedList = []
9
+ Object.assign(ssrContext, {
10
+ _modules: new Set(),
11
+ _meta: {},
12
+ onRendered: (fn) => {
13
+ onRenderedList.push(fn)
14
+ }
15
+ })
16
+
17
+ const { app, router, routes } = await createApp('server', ssrContext)
18
+ // set the router to the desired URL before rendering
19
+
20
+ router.push({ path: url })
21
+ ssrContext.initialState = {}
22
+
23
+ onRenderedList.forEach((fn) => {
24
+ fn()
25
+ })
26
+
27
+ await router.isReady()
28
+
29
+ return { app, router, routes }
30
+ }
31
+
32
+ export const getRoutes = async () =>
33
+ (
34
+ await initializeApp('/', {
35
+ ssr: false,
36
+ req: { headers: {}, url: '/' },
37
+ res: {}
38
+ })
39
+ ).routes
40
+
41
+ export async function render(url, manifest, ssrContext) {
42
+ const { app, router } = await initializeApp(url, ssrContext)
43
+
44
+ // passing SSR context object which will be available via useSSRContext()
45
+ // @vitejs/plugin-vue injects code into a component's setup() that registers
46
+ // itself on ctx.modules. After the render, ctx.modules would contain all the
47
+ // components that have been instantiated during this render call.
48
+ const ctx = {
49
+ __qMetaList: []
50
+ }
51
+ let html = await renderToString(app, ctx)
52
+ // html = injectSsrContext(html, ssrContext)
53
+ // the SSR manifest generated by Vite contains module -> chunk/asset mapping
54
+ // which we can then use to determine what files need to be preloaded for this
55
+ // request.
56
+ const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
57
+
58
+ return [html, preloadLinks]
59
+ }
60
+
61
+ function renderPreloadLinks(modules, manifest) {
62
+ let links = ''
63
+ const seen = new Set()
64
+ modules.forEach((id) => {
65
+ const files = manifest[id]
66
+ if (files) {
67
+ files.forEach((file) => {
68
+ if (!seen.has(file)) {
69
+ seen.add(file)
70
+ links += renderPreloadLink(file)
71
+ }
72
+ })
73
+ }
74
+ })
75
+ return links
76
+ }
77
+
78
+ function renderPreloadLink(file) {
79
+ if (file.endsWith('.js')) {
80
+ return `<link rel="modulepreload" crossorigin href="${file}">`
81
+ } else if (file.endsWith('.css')) {
82
+ return `<link rel="stylesheet" href="${file}">`
83
+ } else if (file.endsWith('.woff')) {
84
+ return ` <link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
85
+ } else if (file.endsWith('.woff2')) {
86
+ return ` <link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
87
+ } else if (file.endsWith('.gif')) {
88
+ return ` <link rel="preload" href="${file}" as="image" type="image/gif">`
89
+ } else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) {
90
+ return ` <link rel="preload" href="${file}" as="image" type="image/jpeg">`
91
+ } else if (file.endsWith('.png')) {
92
+ return ` <link rel="preload" href="${file}" as="image" type="image/png">`
93
+ } else {
94
+ // TODO
95
+ return ''
96
+ }
97
+ }