Skip to content

Commit

Permalink
test: add test suite to stabilize ssr frameworks
Browse files Browse the repository at this point in the history
tests vitejs#2390 and vitejs#1875 amongst others
  • Loading branch information
brillout committed Apr 11, 2021
1 parent 0dc6e37 commit b55a8e3
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 0 deletions.
21 changes: 21 additions & 0 deletions packages/playground/ssr-framework/__tests__/serve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// @ts-check
// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
// the default e2e test serve behavior

const port = (exports.port = 9529)
const root = `${__dirname}/..`

exports.serve = async function serve(_, isProduction) {
const { startServer, build } = require('vue-framework')
if (isProduction) {
process.env.NODE_ENV = 'production'
await build(root, true)
}
const stopServer = await startServer(root, port, isProduction)
return {
// for test teardown
close() {
return stopServer()
}
}
}
53 changes: 53 additions & 0 deletions packages/playground/ssr-framework/__tests__/ssr-framework.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { getColor, isBuild, untilUpdated } from '../../testUtils'
import { port } from './serve'
import fetch from 'node-fetch'

const url = `http://localhost:${port}`

test('/', async () => {
await page.goto(url)
expect(await page.textContent('h1')).toBe('Home')
if (isBuild) {
expect(await getColor('h1')).toBe('black')
} else {
// During dev, the CSS is loaded from async chunk and we may have to wait
// when the test runs concurrently.
await untilUpdated(() => getColor('h1'), 'black')
}

// is rendered to HTML
const homeHtml = await (await fetch(url + '/')).text()
expect(homeHtml).toContain('count is: 0')
})

test('hydration', async () => {
expect(await page.textContent('button')).toContain('0')
await page.click('button')
expect(await page.textContent('button')).toContain('1')

// should not have hydration mismatch
browserLogs.forEach((msg) => {
expect(msg).not.toMatch('mismatch')
})
})

test('/about', async () => {
await page.goto(url + '/about')
expect(await page.textContent('h1')).toBe('About')
if (isBuild) {
expect(await getColor('h1')).toBe('red')
} else {
// During dev, the CSS is loaded from async chunk and we may have to wait
// when the test runs concurrently.
await untilUpdated(() => getColor('h1'), 'red')
}

// is rendered to HTML
const aboutHtml = await (await fetch(url + '/about')).text()
expect(aboutHtml).toContain('A colored page.')

// should not have hydration mismatch
browserLogs.forEach((msg) => {
expect(msg).not.toMatch('mismatch')
})
})
19 changes: 19 additions & 0 deletions packages/playground/ssr-framework/example/pages/About.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<div>
<p><a href="/">Home</a> <a href="/about">About</a></p>
<h1>About</h1>
A colored page.
</div>
</template>

<style scoped>
* {
color: red;
}
div {
display: flex;
flex-direction: column;
align-items: center;
}
</style>

20 changes: 20 additions & 0 deletions packages/playground/ssr-framework/example/pages/Home.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<div>
<p><a href="/">Home</a> <a href="/about">About</a></p>
<h1>Home</h1>
<button @click="state.count++">count is: {{ state.count }}</button>
</div>
</template>

<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
</script>

<style scoped>
div {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
18 changes: 18 additions & 0 deletions packages/playground/ssr-framework/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "test-ssr-framework",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vue-framework",
"build": "vue-framework build",
"serve": "vue-framework serve"
},
"dependencies": {
"@vue/server-renderer": "^3.0.6",
"vue": "^3.0.8",
"vue-framework": "file:./vue-framework/"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.0.8"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

require('../cli')
37 changes: 37 additions & 0 deletions packages/playground/ssr-framework/vue-framework/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const { getViteConfig } = require('./getViteConfig')
const hydrateFile = require.resolve('./hydrate')
const getPagesFile = require.resolve('./getPages')

module.exports.build = build

async function build(root, silent) {
const { build: viteBuild } = require('vite')

const configBase = getViteConfig(root)
if (silent) {
configBase.logLevel = 'error'
}

// client build
await viteBuild({
...configBase,
build: {
outDir: 'dist/client',
manifest: true,
polyfillDynamicImport: false,
rollupOptions: { input: hydrateFile }
}
})

// server build
await viteBuild({
...configBase,
build: {
outDir: 'dist/server',
manifest: true,
polyfillDynamicImport: false,
rollupOptions: { input: getPagesFile },
ssr: true
}
})
}
26 changes: 26 additions & 0 deletions packages/playground/ssr-framework/vue-framework/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { startServer } = require('./server')
const { build } = require('./build')

cli()

async function cli() {
const root = process.cwd()
const port = 3000
const cliCommand = process.argv[2]
if (cliCommand === undefined) {
await startServer(root, port, false)
console.log(`http://localhost:${port}`)
return
}
if (cliCommand === 'build') {
build(root)
return
}
if (cliCommand === 'serve') {
process.env.NODE_ENV = 'production'
await startServer(root, port, true)
console.log(`http://localhost:${port}`)
return
}
throw new Error(`Unknown command ${cliCommand}`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const { renderToString } = require('@vue/server-renderer')
const { relative } = require('path')
const { createSSRApp } = require('vue')
const hydrateFile = require.resolve('./hydrate')
const getPagesFile = require.resolve('./getPages')

module.exports.createPageRender = createPageRender

const env = {}

function createPageRender(viteServer, root, isProduction) {
Object.assign(env, { viteServer, root, isProduction })
return render
}

async function render(url) {
const routeMatch = await route(url)
if (routeMatch === null) return null
const { Page, pagePath } = routeMatch
const app = createSSRApp(Page)
const html = await renderToString(app)
return `<!DOCTYPE html>
<html>
<head>
<script>window.pagePath = '${pagePath}'</script>
<script async type="module" src="${getBrowserEntry()}"></script>
<link rel="icon" href="data:;base64,=">
</head>
<body>
<div id="app">${html}</div>
</body>
</html>
`
}

function getBrowserEntry() {
if (!env.isProduction) {
return hydrateFile
} else {
return findBuildEntry(hydrateFile)
}
}

function findBuildEntry(filePathAbsolute) {
const clientManifestPath = `${env.root}/dist/client/manifest.json`
const clientManifest = require(clientManifestPath)
const filePathRelative = relative(env.root, filePathAbsolute)
const { file } = clientManifest[filePathRelative]
return '/' + file
}

async function route(url) {
const pages = await getPages()
const fileName =
url === '/' ? 'Home' : url.split('')[1].toUpperCase() + url.slice(2)
const pageFiles = Object.keys(pages)
const pagePath = pageFiles.find((pageFile) =>
pageFile.endsWith(`${fileName}.vue`)
)
if (!pagePath) return null
const pageLoader = pages[pagePath]
const exports = await pageLoader()
const Page = exports.default
return { Page, pagePath }
}

async function getPages() {
let exports
if (!env.isProduction) {
exports = await env.viteServer.ssrLoadModule(getPagesFile)
} else {
exports = require(`${env.root}/dist/server/getPages.js`)
}
return await exports.getPages()
}
6 changes: 6 additions & 0 deletions packages/playground/ssr-framework/vue-framework/getPages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { getPages }

async function getPages() {
const pages = import.meta.glob('/**/pages/*.vue')
return pages
}
13 changes: 13 additions & 0 deletions packages/playground/ssr-framework/vue-framework/getViteConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const vuePlugin = require('@vitejs/plugin-vue')
const vueJsx = require('@vitejs/plugin-vue-jsx')

module.exports.getViteConfig = getViteConfig

function getViteConfig(root) {
return {
root,
configFile: false,
ssr: { external: ['vue-framework'] },
plugins: [vuePlugin(), vueJsx()]
}
}
13 changes: 13 additions & 0 deletions packages/playground/ssr-framework/vue-framework/hydrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { getPages } from './getPages'
import { createSSRApp } from 'vue'

hydrate()

async function hydrate() {
const pages = await getPages()
const { pagePath } = window
const exports = await pages[pagePath]()
const Page = exports.default
const app = createSSRApp(Page)
app.mount('#app')
}
4 changes: 4 additions & 0 deletions packages/playground/ssr-framework/vue-framework/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const { startServer } = require('./server')
const { build } = require('./build')
module.exports.startServer = startServer
module.exports.build = build
18 changes: 18 additions & 0 deletions packages/playground/ssr-framework/vue-framework/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "vue-framework",
"version": "1.0.0",
"main": "index.js",
"bin": {
"vue-framework": "bin/vue-framework.js"
},
"peerDependencies": {
"@vue/compiler-sfc": "^3.0.8",
"@vue/server-renderer": "^3.0.6",
"vue": "^3.0.8"
},
"devDependencies": {
"@vitejs/plugin-vue": "^1.0.0",
"@vitejs/plugin-vue-jsx": "^1.1.2",
"express": "^4.17.1"
}
}
40 changes: 40 additions & 0 deletions packages/playground/ssr-framework/vue-framework/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const express = require('express')
const { createPageRender } = require('./createPageRender.js')
const { getViteConfig } = require('./getViteConfig.js')
const vite = require('vite')

module.exports.startServer = startServer

async function startServer(root, port, isProduction) {
const app = express()

let viteServer
if (isProduction) {
app.use(express.static(`${root}/dist/client`, { index: false }))
} else {
viteServer = await vite.createServer({
...getViteConfig(root),
optimizeDeps: { entries: ['**/pages/*.vue'] },
server: { middlewareMode: true }
})
app.use(viteServer.middlewares)
}

const renderPage = createPageRender(viteServer, root, isProduction)
app.get('*', async (req, res, next) => {
const url = req.originalUrl
const html = await renderPage(url)
if (html === null) return next()
res.send(html)
})

return new Promise((resolve) => {
const server = app.listen(port, () => {
const stopServer = async () => {
server.close()
if (viteServer) await viteServer.close()
}
resolve(stopServer)
})
})
}
3 changes: 3 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7980,6 +7980,9 @@ void-elements@^3.1.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=

"vue-framework@file:./packages/playground/ssr-framework/vue-framework":
version "1.0.0"

vue-router@^4.0.3:
version "4.0.5"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.5.tgz#dd0a4134bc950c37aef64b973e9ee1008428d8fa"
Expand Down

0 comments on commit b55a8e3

Please sign in to comment.