@ -0,0 +1,2 @@ | |||||
node_modules/ | |||||
dist/ |
@ -0,0 +1,32 @@ | |||||
{ | |||||
"name": "@wk-extra/www", | |||||
"private": true, | |||||
"version": "1.0.0", | |||||
"license": "MIT", | |||||
"scripts": { | |||||
"dev": "yarn server", | |||||
"prod": "yarn build && yarn server:prod", | |||||
"build": "vite build && vite build --ssr", | |||||
"render": "yarn build && vite-plugin-ssr prerender", | |||||
"server": "ts-node ./server", | |||||
"server:prod": "cross-env NODE_ENV=production ts-node ./server" | |||||
}, | |||||
"dependencies": { | |||||
"@vitejs/plugin-vue": "^2.2.2", | |||||
"@vue/compiler-sfc": "^3.2.31", | |||||
"@vue/server-renderer": "^3.2.31", | |||||
"compression": "^1.7.4", | |||||
"cross-env": "^7.0.3", | |||||
"express": "^4.17.3", | |||||
"vite": "^2.8.4", | |||||
"vite-plugin-ssr": "^0.3.64", | |||||
"vue": "^3.2.31" | |||||
}, | |||||
"devDependencies": { | |||||
"@types/compression": "^1.7.2", | |||||
"@types/express": "^4.17.13", | |||||
"@types/node": "^17.0.19", | |||||
"ts-node": "^10.5.0", | |||||
"typescript": "^4.5.5" | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
<template> | |||||
<h1>About</h1> | |||||
<p>A colored page.</p> | |||||
</template> | |||||
<style> | |||||
h1, | |||||
p { | |||||
color: green; | |||||
} | |||||
</style> |
@ -0,0 +1,8 @@ | |||||
<template> | |||||
<button type="button" @click="state.count++">Counter {{ state.count }}</button> | |||||
</template> | |||||
<script lang="ts" setup> | |||||
import { reactive } from 'vue' | |||||
const state = reactive({ count: 0 }) | |||||
</script> |
@ -0,0 +1,12 @@ | |||||
<template> | |||||
<h1>Welcome</h1> | |||||
This page is: | |||||
<ul> | |||||
<li>Rendered to HTML.</li> | |||||
<li>Interactive. <Counter /></li> | |||||
</ul> | |||||
</template> | |||||
<script lang="ts" setup> | |||||
import Counter from './Counter.vue' | |||||
</script> |
@ -0,0 +1,17 @@ | |||||
<template> | |||||
<a :class="{ active: pageContext.urlPathname === $attrs.href }"> | |||||
<slot /> | |||||
</a> | |||||
</template> | |||||
<style scoped> | |||||
a { | |||||
padding: 3px 10px; | |||||
} | |||||
a.active { | |||||
background-color: #eee; | |||||
} | |||||
</style> | |||||
<script lang="ts" setup> | |||||
import { usePageContext } from './usePageContext' | |||||
const pageContext = usePageContext() | |||||
</script> |
@ -0,0 +1,55 @@ | |||||
<template> | |||||
<div class="layout"> | |||||
<div class="navigation"> | |||||
<a href="/" class="logo"> | |||||
<img src="./logo.svg" height="64" width="64" alt="logo" /> | |||||
</a> | |||||
<Link href="/">Home</Link> | |||||
<Link href="/about">About</Link> | |||||
</div> | |||||
<div class="content"><slot /></div> | |||||
</div> | |||||
</template> | |||||
<script lang="ts" setup> | |||||
import Link from './Link.vue' | |||||
</script> | |||||
<style> | |||||
body { | |||||
margin: 0; | |||||
font-family: sans-serif; | |||||
} | |||||
* { | |||||
box-sizing: border-box; | |||||
} | |||||
a { | |||||
text-decoration: none; | |||||
} | |||||
</style> | |||||
<style scoped> | |||||
.layout { | |||||
display: flex; | |||||
max-width: 900px; | |||||
margin: auto; | |||||
} | |||||
.content { | |||||
padding: 20px; | |||||
border-left: 2px solid #eee; | |||||
padding-bottom: 50px; | |||||
min-height: 100vh; | |||||
} | |||||
.navigation { | |||||
padding: 20px; | |||||
flex-shrink: 0; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
line-height: 1.8em; | |||||
} | |||||
.logo { | |||||
margin-top: 20px; | |||||
margin-bottom: 10px; | |||||
} | |||||
</style> |
@ -0,0 +1,14 @@ | |||||
import { getPage } from 'vite-plugin-ssr/client' | |||||
import { createApp } from './app' | |||||
import type { PageContext } from './types' | |||||
import type { PageContextBuiltInClient } from 'vite-plugin-ssr/client' | |||||
hydrate() | |||||
async function hydrate() { | |||||
// We do Server Routing, but we can also do Client Routing by using `useClientRouter()` | |||||
// instead of `getPage()`, see https://vite-plugin-ssr.com/useClientRouter | |||||
const pageContext = await getPage<PageContextBuiltInClient & PageContext>() | |||||
const app = createApp(pageContext) | |||||
app.mount('#app') | |||||
} |
@ -0,0 +1,41 @@ | |||||
import { renderToString } from '@vue/server-renderer' | |||||
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr' | |||||
import { createApp } from './app' | |||||
import logoUrl from './logo.svg' | |||||
import type { PageContext } from './types' | |||||
import type { PageContextBuiltIn } from 'vite-plugin-ssr' | |||||
export { render } | |||||
// See https://vite-plugin-ssr.com/data-fetching | |||||
export const passToClient = ['pageProps', 'urlPathname'] | |||||
async function render(pageContext: PageContextBuiltIn & PageContext) { | |||||
const app = createApp(pageContext) | |||||
const appHtml = await renderToString(app) | |||||
// See https://vite-plugin-ssr.com/head | |||||
const { documentProps } = pageContext | |||||
const title = (documentProps && documentProps.title) || 'Vite SSR app' | |||||
const desc = (documentProps && documentProps.description) || 'App using Vite + vite-plugin-ssr' | |||||
const documentHtml = escapeInject`<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<meta charset="UTF-8" /> | |||||
<link rel="icon" href="${logoUrl}" /> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||||
<meta name="description" content="${desc}" /> | |||||
<title>${title}</title> | |||||
</head> | |||||
<body> | |||||
<div id="app">${dangerouslySkipEscape(appHtml)}</div> | |||||
</body> | |||||
</html>` | |||||
return { | |||||
documentHtml, | |||||
pageContext: { | |||||
// We can add some `pageContext` here, which is useful if we want to do page redirection https://vite-plugin-ssr.com/page-redirection | |||||
}, | |||||
} | |||||
} |
@ -0,0 +1,14 @@ | |||||
<template> | |||||
<div v-if="is404"> | |||||
<h1>404 Page Not Found</h1> | |||||
<p>This page could not be found.</p> | |||||
</div> | |||||
<div v-else> | |||||
<h1>500 Internal Server Error</h1> | |||||
<p>Something went wrong.</p> | |||||
</div> | |||||
</template> | |||||
<script lang="ts" setup> | |||||
defineProps(['is404']) | |||||
</script> |
@ -0,0 +1,30 @@ | |||||
import { createSSRApp, defineComponent, h } from 'vue' | |||||
import PageShell from './PageShell.vue' | |||||
import { setPageContext } from './usePageContext' | |||||
import type { PageContext } from './types' | |||||
export { createApp } | |||||
function createApp(pageContext: PageContext) { | |||||
const { Page, pageProps } = pageContext | |||||
const PageWithLayout = defineComponent({ | |||||
render() { | |||||
return h( | |||||
PageShell, | |||||
{}, | |||||
{ | |||||
default() { | |||||
return h(Page, pageProps || {}) | |||||
}, | |||||
}, | |||||
) | |||||
}, | |||||
}) | |||||
const app = createSSRApp(PageWithLayout) | |||||
// Make `pageContext` available from any Vue component | |||||
setPageContext(app, pageContext) | |||||
return app | |||||
} |
@ -0,0 +1,36 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<svg width="175" height="175" fill="none" version="1.1" viewBox="0 0 175 175" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> | |||||
<metadata> | |||||
<rdf:RDF> | |||||
<cc:Work rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> | |||||
<dc:title/> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<defs> | |||||
<linearGradient id="linearGradient880" x1="108.64" x2="115.51" y1="88.726" y2="136.2" gradientTransform="matrix(1.0498 0 0 1.0498 -2.9171 -2.9658)" gradientUnits="userSpaceOnUse"> | |||||
<stop stop-color="#ffea83" offset="0"/> | |||||
<stop stop-color="#FFDD35" offset=".083333"/> | |||||
<stop stop-color="#FFA800" offset="1"/> | |||||
</linearGradient> | |||||
<linearGradient id="paint2_linear" x1="48.975" x2="61.299" y1="3.9232" y2="158.04" gradientTransform="translate(-2.832e-5)" gradientUnits="userSpaceOnUse"> | |||||
<stop stop-color="#FFEA83" offset="0"/> | |||||
<stop stop-color="#FFDD35" offset=".083333"/> | |||||
<stop stop-color="#FFA800" offset="1"/> | |||||
</linearGradient> | |||||
<linearGradient id="paint0_linear-6" x1="-1.4492" x2="116.62" y1="-5.8123" y2="137.08" gradientTransform="translate(-2.832e-5)" gradientUnits="userSpaceOnUse"> | |||||
<stop stop-color="#41D1FF" offset="0"/> | |||||
<stop stop-color="#BD34FE" offset="1"/> | |||||
</linearGradient> | |||||
</defs> | |||||
<circle cx="87.5" cy="87.5" r="87.5" fill="#c4c4c4"/> | |||||
<circle cx="87.5" cy="87.5" r="87.5" fill="url(#paint0_linear-6)"/> | |||||
<g transform="translate(632.92 54.355)" fill="#d38787" stroke-width="1.0614"> | |||||
<path d="m-549.75 68.457c-5.7533-3.1217-6.1166-5.2295-6.1166-35.489 0-30.458 0.35464-32.448 6.3339-35.54 3.9943-2.0655 24.279-2.2805 26.735-0.28333 0.89718 0.72974 6.7203 6.6637 12.94 13.187l11.309 11.86v19.575c0 18.473-0.12956 19.74-2.3011 22.5-4.0223 5.1136-7.558 5.8565-27.65 5.8099-14.15-0.03287-19.008-0.40294-21.25-1.6191zm42.473-6.3594c2.27-1.59 2.359-2.2909 2.359-18.575v-16.923h-6.9521c-12.443 0-16.4-4.0845-16.4-16.93v-7.4828h-8.9464c-6.7178 0-9.3619 0.41549-10.614 1.668-2.5031 2.5031-2.5031 55.724 0 58.228 2.4502 2.4502 37.058 2.4636 40.553 0.01609zm-1.8867-42.165c0-0.16422-2.8659-3.1346-6.3686-6.6008l-6.3686-6.3022v4.9328c0 6.3185 1.8955 8.2687 8.0366 8.2687 2.5854 0 4.7007-0.13434 4.7007-0.29859zm-57.57 44.279c-5.6185-3.0486-6.1166-5.593-6.1166-31.243 0-18.891 0.31331-24.063 1.6101-26.571 1.809-3.4981 6.5048-6.3339 10.489-6.3339 2.4847 0 2.5814 0.19984 1.541 3.1843-0.61054 1.7514-1.7457 3.1843-2.5226 3.1843-0.77686 0-2.1631 0.75059-3.0805 1.668-2.4923 2.4923-2.4923 47.244 0 49.736 0.91739 0.9174 2.3036 1.668 3.0805 1.668 0.77688 0 1.912 1.4329 2.5226 3.1843 1.0562 3.0298 0.97108 3.1822-1.7537 3.1418-1.575-0.02331-4.1713-0.75194-5.7694-1.6191zm-16.983-4.2458c-5.4392-2.9512-6.1166-5.9415-6.1166-26.997 0-15.096 0.345-19.878 1.6101-22.325 1.7476-3.3796 6.4758-6.3339 10.137-6.3339 1.8666 0 2.1789 0.44955 1.6594 2.3882-0.35184 1.3135-0.64655 2.7465-0.65453 3.1843-8e-3 0.43784-0.69682 0.79608-1.5308 0.79608-0.83399 0-2.2669 0.75059-3.1843 1.668-2.4767 2.4767-2.4767 38.768 0 41.244 0.91741 0.91739 2.2946 1.668 3.0605 1.668 1.196 0 2.6402 2.995 2.6871 5.5726 0.0241 1.3294-4.5804 0.80962-7.6676-0.8655z" style="mix-blend-mode:lighten"/> | |||||
<path d="m-552.2 68.911c-5.7533-3.1217-6.1166-5.2295-6.1166-35.489 0-30.458 0.35463-32.448 6.3339-35.54 3.9943-2.0655 24.279-2.2805 26.735-0.28333 0.89718 0.72974 6.7203 6.6637 12.94 13.187l11.309 11.86v19.575c0 18.473-0.12957 19.74-2.3011 22.5-4.0223 5.1136-7.558 5.8565-27.65 5.8099-14.15-0.03287-19.008-0.40294-21.25-1.6191zm42.473-6.3594c2.27-1.59 2.359-2.2909 2.359-18.575v-16.923h-6.952c-12.443 0-16.4-4.0845-16.4-16.93v-7.4828h-8.9464c-6.7179 0-9.3619 0.41549-10.614 1.668-2.5031 2.5031-2.5031 55.724 0 58.228 2.4502 2.4502 37.058 2.4636 40.553 0.01609zm-1.8867-42.165c0-0.16422-2.8659-3.1346-6.3686-6.6008l-6.3686-6.3022v4.9328c0 6.3185 1.8955 8.2688 8.0366 8.2688 2.5854 0 4.7007-0.13434 4.7007-0.29859zm-57.57 44.279c-5.6185-3.0486-6.1166-5.593-6.1166-31.243 0-18.891 0.31331-24.063 1.6101-26.571 1.809-3.4981 6.5048-6.3339 10.489-6.3339 2.4847 0 2.5814 0.19984 1.541 3.1843-0.61054 1.7514-1.7457 3.1843-2.5226 3.1843-0.77687 0-2.1631 0.75059-3.0805 1.668-2.4923 2.4923-2.4923 47.244 0 49.736 0.91741 0.91739 2.3036 1.668 3.0805 1.668 0.77686 0 1.912 1.4329 2.5226 3.1843 1.0562 3.0298 0.97107 3.1822-1.7537 3.1418-1.575-0.02331-4.1713-0.75194-5.7694-1.6191zm-16.983-4.2458c-5.4392-2.9512-6.1166-5.9415-6.1166-26.997 0-15.096 0.34502-19.878 1.6101-22.325 1.7476-3.3796 6.4758-6.3339 10.137-6.3339 1.8666 0 2.1789 0.44955 1.6594 2.3882-0.35182 1.3135-0.64653 2.7465-0.65452 3.1843-8e-3 0.43784-0.69683 0.79608-1.5308 0.79608-0.83397 0-2.2669 0.75059-3.1843 1.668-2.4767 2.4767-2.4767 38.768 0 41.245 0.9174 0.91739 2.2946 1.668 3.0605 1.668 1.196 0 2.6402 2.995 2.6871 5.5726 0.0241 1.3294-4.5804 0.80962-7.6676-0.8655z" fill-opacity=".47466" style="mix-blend-mode:lighten"/> | |||||
</g> | |||||
<path d="m128.48 88.913-24.027 4.6784c-0.39475 0.07685-0.68766 0.40944-0.71076 0.80849l-1.4782 24.805c-0.0347 0.58371 0.50497 1.0372 1.0792 0.90602l6.6886-1.5338c0.62676-0.14383 1.1916 0.40419 1.0635 1.0299l-1.9874 9.6702c-0.13438 0.65091 0.48084 1.2073 1.1202 1.0142l4.1322-1.2472c0.64041-0.19317 1.2556 0.36535 1.1202 1.0162l-3.158 15.191c-0.19842 0.95011 1.074 1.4677 1.6042 0.653l0.35485-0.54382 19.578-38.827c0.32755-0.64985-0.23727-1.391-0.95641-1.2535l-6.8849 1.3207c-0.6467 0.12389-1.1979-0.47453-1.0152-1.1034l4.4944-15.482c0.18266-0.63012-0.36955-1.2295-1.0173-1.1034z" fill="url(#linearGradient880)" stroke-width="1.0498"/> | |||||
<rect x="3" y="3" width="169" height="169" rx="84.5" stroke="url(#paint2_linear)" stroke-width="6" style="mix-blend-mode:soft-light"/> | |||||
</svg> |
@ -0,0 +1,11 @@ | |||||
export type PageProps = {} | |||||
// The `pageContext` that are available in both on the server-side and browser-side | |||||
export type PageContext = { | |||||
Page: any | |||||
pageProps?: PageProps | |||||
documentProps?: { | |||||
title?: string | |||||
description?: string | |||||
} | |||||
urlPathname?: string | |||||
} |
@ -0,0 +1,21 @@ | |||||
// Hook `usePageContext()` to make `pageContext` available from any Vue component. | |||||
// See https://vite-plugin-ssr.com/pageContext-anywhere | |||||
import { inject } from 'vue' | |||||
import type { App, InjectionKey } from 'vue' | |||||
import { PageContext } from './types' | |||||
export { usePageContext } | |||||
export { setPageContext } | |||||
const key: InjectionKey<PageContext> = Symbol() | |||||
function usePageContext() { | |||||
const pageContext = inject(key) | |||||
if (!pageContext) throw new Error("setPageContext() not called in parent") | |||||
return pageContext | |||||
} | |||||
function setPageContext(app: App, pageContext: PageContext) { | |||||
app.provide(key, pageContext) | |||||
} |
@ -0,0 +1,43 @@ | |||||
import express from 'express' | |||||
import compression from 'compression' | |||||
import { createPageRenderer } from 'vite-plugin-ssr' | |||||
const isProduction = process.env.NODE_ENV === 'production' | |||||
const root = `${__dirname}/..` | |||||
startServer() | |||||
async function startServer() { | |||||
const app = express() | |||||
app.use(compression()) | |||||
let viteDevServer | |||||
if (isProduction) { | |||||
app.use(express.static(`${root}/dist/client`)) | |||||
} else { | |||||
const vite = require('vite') | |||||
viteDevServer = await vite.createServer({ | |||||
root, | |||||
server: { middlewareMode: 'ssr' }, | |||||
}) | |||||
app.use(viteDevServer.middlewares) | |||||
} | |||||
const renderPage = createPageRenderer({ viteDevServer, isProduction, root }) | |||||
app.get('*', async (req, res, next) => { | |||||
const url = req.originalUrl | |||||
const pageContextInit = { | |||||
url, | |||||
} | |||||
const pageContext = await renderPage(pageContextInit) | |||||
const { httpResponse } = pageContext | |||||
if (!httpResponse) return next() | |||||
const { body, statusCode, contentType } = httpResponse | |||||
res.status(statusCode).type(contentType).send(body) | |||||
}) | |||||
const port = process.env.PORT || 3000 | |||||
app.listen(port) | |||||
console.log(`Server running at http://localhost:${port}`) | |||||
} |
@ -0,0 +1,19 @@ | |||||
{ | |||||
"compilerOptions": { | |||||
"strict": true, | |||||
"module": "ES2020", | |||||
"moduleResolution": "Node", | |||||
"target": "ES2017", | |||||
"lib": ["DOM", "DOM.Iterable", "ESNext"], | |||||
"types": ["vite/client"], | |||||
"skipLibCheck": true, | |||||
"esModuleInterop": true, | |||||
"jsx": "preserve" | |||||
}, | |||||
"ts-node": { | |||||
"transpileOnly": true, | |||||
"compilerOptions": { | |||||
"module": "CommonJS" | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,9 @@ | |||||
import vue from '@vitejs/plugin-vue' | |||||
import ssr from 'vite-plugin-ssr/plugin' | |||||
import { UserConfig } from 'vite' | |||||
const config: UserConfig = { | |||||
plugins: [vue(), ssr()], | |||||
} | |||||
export default config |
@ -0,0 +1,4 @@ | |||||
declare module '*.vue' { | |||||
const Component: any | |||||
export default Component | |||||
} |
@ -0,0 +1,4 @@ | |||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. | |||||
# yarn lockfile v1 | |||||
@ -0,0 +1,23 @@ | |||||
{ | |||||
"folders": [ | |||||
{ | |||||
"path": "." | |||||
}, | |||||
{ | |||||
"path": "__packages__/server" | |||||
}, | |||||
{ | |||||
"path": "__packages__/www" | |||||
} | |||||
], | |||||
"settings": { | |||||
"files.associations": { | |||||
".husky/*": "shell" | |||||
}, | |||||
"sort-imports.on-save": true, | |||||
"editor.tabSize": 2, | |||||
"vetur.validation.template": false, | |||||
"vetur.validation.script": false, | |||||
"vetur.validation.style": false, | |||||
} | |||||
} |