mirror of
https://gitee.com/vuejs/vue.git
synced 2024-11-29 18:47:39 +08:00
test: all ssr tests passing
This commit is contained in:
parent
3a27b6b3e1
commit
898d1e19fc
19
.github/workflows/ci.yml
vendored
19
.github/workflows/ci.yml
vendored
@ -26,6 +26,25 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: pnpm run test:unit
|
||||
|
||||
ssr-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to 16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- name: Run SSR tests
|
||||
run: pnpm run test:ssr
|
||||
|
||||
# e2e-test:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
|
@ -23,7 +23,8 @@
|
||||
"build": "node scripts/build.js",
|
||||
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
|
||||
"test": "npm run lint && npm run ts-check && npm run test:types && npm run test:unit && npm run test:e2e",
|
||||
"test:unit": "vitest run",
|
||||
"test:unit": "vitest run test/unit",
|
||||
"test:ssr": "vitest run test/ssr",
|
||||
"test:e2e": "npm run build -- web-full-prod,web-server-basic-renderer && node test/e2e/runner.ts",
|
||||
"test:types": "tsc -p ./types/tsconfig.json",
|
||||
"lint": "eslint src scripts test",
|
||||
@ -75,7 +76,7 @@
|
||||
"de-indent": "^1.0.2",
|
||||
"escodegen": "^2.0.0",
|
||||
"eslint": "^8.14.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"fsevents": "2.3.2",
|
||||
"hash-sum": "^2.0.0",
|
||||
"he": "^1.2.0",
|
||||
@ -99,7 +100,7 @@
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vitest": "^0.12.6",
|
||||
"webpack": "^5.72.1",
|
||||
"webpack": "^4.46.0",
|
||||
"yorkie": "^2.0.0"
|
||||
},
|
||||
"config": {
|
||||
|
1963
pnpm-lock.yaml
1963
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -236,8 +236,7 @@ function genConfig(name) {
|
||||
|
||||
// built-in vars
|
||||
const vars = {
|
||||
__VERSION__: version,
|
||||
__SSR_TEST__: false
|
||||
__VERSION__: version
|
||||
}
|
||||
// feature flags
|
||||
Object.keys(featureFlags).forEach((key) => {
|
||||
|
@ -73,7 +73,7 @@ function logError(err, vm, info) {
|
||||
warn(`Error in ${info}: "${err.toString()}"`, vm)
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (inBrowser && typeof console !== 'undefined' && !__SSR_TEST__) {
|
||||
if (inBrowser && typeof console !== 'undefined') {
|
||||
console.error(err)
|
||||
} else {
|
||||
throw err
|
||||
|
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@ -1,5 +1,3 @@
|
||||
declare const __SSR_TEST__: boolean;
|
||||
|
||||
interface Window {
|
||||
__VUE_DEVTOOLS_GLOBAL_HOOK__: DevtoolsHook;
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
|
||||
import { createPromiseCallback } from '../util'
|
||||
import { createBundleRunner } from './create-bundle-runner'
|
||||
import type { Renderer, RenderOptions } from '../create-renderer'
|
||||
import {
|
||||
createSourceMapConsumers,
|
||||
rewriteErrorTrace,
|
||||
rewriteErrorTrace
|
||||
} from './source-map-support'
|
||||
|
||||
const fs = require('fs')
|
||||
@ -89,7 +88,7 @@ export function createBundleRendererCreator(
|
||||
)
|
||||
|
||||
return {
|
||||
renderToString: (context: Object | undefined, cb: any) => {
|
||||
renderToString: (context?: Object | undefined, cb?: any) => {
|
||||
if (typeof context === 'function') {
|
||||
cb = context
|
||||
context = {}
|
||||
@ -97,7 +96,7 @@ export function createBundleRendererCreator(
|
||||
|
||||
let promise
|
||||
if (!cb) {
|
||||
({ promise, cb } = createPromiseCallback())
|
||||
;({ promise, cb } = createPromiseCallback())
|
||||
}
|
||||
|
||||
run(context)
|
||||
@ -154,7 +153,7 @@ export function createBundleRendererCreator(
|
||||
})
|
||||
|
||||
return res
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
import RenderStream from './render-stream'
|
||||
import { createWriteFunction } from './write'
|
||||
import { createRenderFunction } from './render'
|
||||
@ -29,7 +28,9 @@ export type RenderOptions = {
|
||||
directives?: Object
|
||||
isUnaryTag?: Function
|
||||
cache?: RenderCache
|
||||
template?: string | ((content: string, context: any) => string)
|
||||
template?:
|
||||
| string
|
||||
| ((content: string, context: any) => string | Promise<string>)
|
||||
inject?: boolean
|
||||
basedir?: string
|
||||
shouldPreload?: Function
|
||||
@ -49,7 +50,7 @@ export function createRenderer({
|
||||
shouldPreload,
|
||||
shouldPrefetch,
|
||||
clientManifest,
|
||||
serializer,
|
||||
serializer
|
||||
}: RenderOptions = {}): Renderer {
|
||||
const render = createRenderFunction(modules, directives, isUnaryTag, cache)
|
||||
const templateRenderer = new TemplateRenderer({
|
||||
@ -60,7 +61,7 @@ export function createRenderer({
|
||||
// @ts-expect-error
|
||||
shouldPrefetch,
|
||||
clientManifest,
|
||||
serializer,
|
||||
serializer
|
||||
})
|
||||
|
||||
return {
|
||||
@ -80,7 +81,7 @@ export function createRenderer({
|
||||
// no callback, return Promise
|
||||
let promise
|
||||
if (!cb) {
|
||||
({ promise, cb } = createPromiseCallback())
|
||||
;({ promise, cb } = createPromiseCallback())
|
||||
}
|
||||
|
||||
let result = ''
|
||||
@ -158,6 +159,6 @@ export function createRenderer({
|
||||
}
|
||||
return templateStream
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
const path = require('path')
|
||||
const serialize = require('serialize-javascript')
|
||||
|
||||
@ -10,7 +9,9 @@ import type { ParsedTemplate } from './parse-template'
|
||||
import type { AsyncFileMapper } from './create-async-file-mapper'
|
||||
|
||||
type TemplateRendererOptions = {
|
||||
template?: string | ((content: string, context: any) => string)
|
||||
template?:
|
||||
| string
|
||||
| ((content: string, context: any) => string | Promise<string>)
|
||||
inject?: boolean
|
||||
clientManifest?: ClientManifest
|
||||
shouldPreload?: (file: string, type: string) => boolean
|
||||
@ -286,7 +287,7 @@ function normalizeFile(file: string): Resource {
|
||||
file,
|
||||
extension,
|
||||
fileWithoutQuery: withoutQuery,
|
||||
asType: getPreloadType(extension),
|
||||
asType: getPreloadType(extension)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,15 @@
|
||||
import path from 'path'
|
||||
import webpack from 'webpack'
|
||||
import MemoryFS from 'memory-fs'
|
||||
import { RenderOptions } from '../../src/server/create-renderer'
|
||||
import { createBundleRenderer } from 'web/entry-server-renderer'
|
||||
import VueSSRServerPlugin from 'server/webpack-plugin/server'
|
||||
|
||||
export function compileWithWebpack (file, extraConfig, cb) {
|
||||
const config = Object.assign({
|
||||
export function compileWithWebpack(
|
||||
file: string,
|
||||
extraConfig?: webpack.Configuration
|
||||
) {
|
||||
const config: webpack.Configuration = {
|
||||
mode: 'development',
|
||||
entry: path.resolve(__dirname, 'fixtures', file),
|
||||
module: {
|
||||
@ -21,15 +27,47 @@ export function compileWithWebpack (file, extraConfig, cb) {
|
||||
}
|
||||
]
|
||||
}
|
||||
}, extraConfig)
|
||||
}
|
||||
if (extraConfig) {
|
||||
Object.assign(config, extraConfig)
|
||||
}
|
||||
|
||||
const compiler = webpack(config)
|
||||
const fs = new MemoryFS()
|
||||
compiler.outputFileSystem = fs
|
||||
|
||||
compiler.run((err, stats) => {
|
||||
expect(err).toBeFalsy()
|
||||
expect(stats.errors).toBeFalsy()
|
||||
cb(fs)
|
||||
return new Promise<MemoryFS>((resolve, reject) => {
|
||||
compiler.run((err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(fs)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function createWebpackBundleRenderer(
|
||||
file: string,
|
||||
options?: RenderOptions & { asBundle?: boolean }
|
||||
) {
|
||||
const asBundle = !!(options && options.asBundle)
|
||||
if (options) delete options.asBundle
|
||||
|
||||
const fs = await compileWithWebpack(file, {
|
||||
target: 'node',
|
||||
devtool: asBundle ? 'source-map' : false,
|
||||
output: {
|
||||
path: '/',
|
||||
filename: 'bundle.js',
|
||||
libraryTarget: 'commonjs2'
|
||||
},
|
||||
externals: [require.resolve('../../dist/vue.runtime.common.js')],
|
||||
plugins: asBundle ? [new VueSSRServerPlugin()] : []
|
||||
})
|
||||
|
||||
const bundle = asBundle
|
||||
? JSON.parse(fs.readFileSync('/vue-ssr-server-bundle.json', 'utf-8'))
|
||||
: fs.readFileSync('/bundle.js', 'utf-8')
|
||||
return createBundleRenderer(bundle, options)
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import Vue from 'vue'
|
||||
import renderToString from 'web/entry-server-basic-renderer'
|
||||
|
||||
;(global as any).__SSR_TEST__ = true
|
||||
|
||||
describe('SSR: basicRenderer', () => {
|
||||
it('should work', done => {
|
||||
renderToString(new Vue({
|
||||
|
@ -1,112 +1,95 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import LRU from 'lru-cache'
|
||||
import { compileWithWebpack } from './compile-with-webpack'
|
||||
import { createBundleRenderer } from 'web/entry-server-renderer'
|
||||
import VueSSRServerPlugin from 'server/webpack-plugin/server'
|
||||
import { createWebpackBundleRenderer } from './compile-with-webpack'
|
||||
|
||||
;(global as any).__SSR_TEST__ = true
|
||||
|
||||
export function createRenderer (file, options, cb) {
|
||||
if (typeof options === 'function') {
|
||||
cb = options
|
||||
options = undefined
|
||||
}
|
||||
const asBundle = !!(options && options.asBundle)
|
||||
if (options) delete options.asBundle
|
||||
|
||||
compileWithWebpack(file, {
|
||||
target: 'node',
|
||||
devtool: asBundle ? 'source-map' : false,
|
||||
output: {
|
||||
path: '/',
|
||||
filename: 'bundle.js',
|
||||
libraryTarget: 'commonjs2'
|
||||
},
|
||||
externals: [require.resolve('../../dist/vue.runtime.common.js')],
|
||||
plugins: asBundle
|
||||
? [new VueSSRServerPlugin()]
|
||||
: []
|
||||
}, fs => {
|
||||
const bundle = asBundle
|
||||
? JSON.parse(fs.readFileSync('/vue-ssr-server-bundle.json', 'utf-8'))
|
||||
: fs.readFileSync('/bundle.js', 'utf-8')
|
||||
const renderer = createBundleRenderer(bundle, options)
|
||||
cb(renderer)
|
||||
})
|
||||
}
|
||||
|
||||
describe.skip('SSR: bundle renderer', () => {
|
||||
describe('SSR: bundle renderer', () => {
|
||||
createAssertions(true)
|
||||
createAssertions(false)
|
||||
})
|
||||
|
||||
function createAssertions (runInNewContext) {
|
||||
it('renderToString', done => {
|
||||
createRenderer('app.js', { runInNewContext }, renderer => {
|
||||
const context = { url: '/test' }
|
||||
renderer.renderToString(context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe('<div data-server-rendered="true">/test</div>')
|
||||
expect(context.msg).toBe('hello')
|
||||
done()
|
||||
})
|
||||
function createAssertions(runInNewContext) {
|
||||
it('renderToString', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('app.js', {
|
||||
runInNewContext
|
||||
})
|
||||
const context: any = { url: '/test' }
|
||||
const res = await renderer.renderToString(context)
|
||||
expect(res).toBe('<div data-server-rendered="true">/test</div>')
|
||||
expect(context.msg).toBe('hello')
|
||||
})
|
||||
|
||||
it('renderToStream', done => {
|
||||
createRenderer('app.js', { runInNewContext }, renderer => {
|
||||
const context = { url: '/test' }
|
||||
it('renderToStream', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('app.js', {
|
||||
runInNewContext
|
||||
})
|
||||
const context: any = { url: '/test' }
|
||||
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
const stream = renderer.renderToStream(context)
|
||||
let res = ''
|
||||
stream.on('data', chunk => {
|
||||
stream.on('data', (chunk) => {
|
||||
res += chunk.toString()
|
||||
})
|
||||
stream.on('error', reject)
|
||||
stream.on('end', () => {
|
||||
expect(res).toBe('<div data-server-rendered="true">/test</div>')
|
||||
expect(context.msg).toBe('hello')
|
||||
done()
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
|
||||
expect(res).toBe('<div data-server-rendered="true">/test</div>')
|
||||
expect(context.msg).toBe('hello')
|
||||
})
|
||||
|
||||
it('renderToString catch error', done => {
|
||||
createRenderer('error.js', { runInNewContext }, renderer => {
|
||||
renderer.renderToString(err => {
|
||||
expect(err.message).toBe('foo')
|
||||
done()
|
||||
})
|
||||
it('renderToString catch error', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('error.js', {
|
||||
runInNewContext
|
||||
})
|
||||
try {
|
||||
await renderer.renderToString()
|
||||
} catch (err: any) {
|
||||
expect(err.message).toBe('foo')
|
||||
}
|
||||
})
|
||||
|
||||
it('renderToString catch Promise rejection', done => {
|
||||
createRenderer('promise-rejection.js', { runInNewContext }, renderer => {
|
||||
renderer.renderToString(err => {
|
||||
expect(err.message).toBe('foo')
|
||||
done()
|
||||
})
|
||||
it('renderToString catch Promise rejection', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('promise-rejection.js', {
|
||||
runInNewContext
|
||||
})
|
||||
try {
|
||||
await renderer.renderToString()
|
||||
} catch (err: any) {
|
||||
expect(err.message).toBe('foo')
|
||||
}
|
||||
})
|
||||
|
||||
it('renderToStream catch error', done => {
|
||||
createRenderer('error.js', { runInNewContext }, renderer => {
|
||||
it('renderToStream catch error', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('error.js', {
|
||||
runInNewContext
|
||||
})
|
||||
|
||||
const err = await new Promise<Error>((resolve) => {
|
||||
const stream = renderer.renderToStream()
|
||||
stream.on('error', err => {
|
||||
expect(err.message).toBe('foo')
|
||||
done()
|
||||
})
|
||||
stream.on('error', resolve)
|
||||
})
|
||||
|
||||
expect(err.message).toBe('foo')
|
||||
})
|
||||
|
||||
it('renderToStream catch Promise rejection', done => {
|
||||
createRenderer('promise-rejection.js', { runInNewContext }, renderer => {
|
||||
it('renderToStream catch Promise rejection', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('promise-rejection.js', {
|
||||
runInNewContext
|
||||
})
|
||||
|
||||
const err = await new Promise<Error>((resolve) => {
|
||||
const stream = renderer.renderToStream()
|
||||
stream.on('error', err => {
|
||||
expect(err.message).toBe('foo')
|
||||
done()
|
||||
})
|
||||
stream.on('error', resolve)
|
||||
})
|
||||
|
||||
expect(err.message).toBe('foo')
|
||||
})
|
||||
|
||||
it('render with cache (get/set)', done => {
|
||||
it('render with cache (get/set)', async () => {
|
||||
const cache = {}
|
||||
const get = vi.fn()
|
||||
const set = vi.fn()
|
||||
@ -126,29 +109,25 @@ function createAssertions (runInNewContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
createRenderer('cache.js', options, renderer => {
|
||||
const expected = '<div data-server-rendered="true">/test</div>'
|
||||
const key = 'app::1'
|
||||
renderer.renderToString((err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe(expected)
|
||||
expect(get).toHaveBeenCalledWith(key)
|
||||
const setArgs = set.mock.calls[0]
|
||||
expect(setArgs[0]).toBe(key)
|
||||
expect(setArgs[1].html).toBe(expected)
|
||||
expect(cache[key].html).toBe(expected)
|
||||
renderer.renderToString((err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe(expected)
|
||||
expect(get.mock.calls.length).toBe(2)
|
||||
expect(set.mock.calls.length).toBe(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
const renderer = await createWebpackBundleRenderer('cache.js', options)
|
||||
const expected = '<div data-server-rendered="true">/test</div>'
|
||||
const key = 'app::1'
|
||||
const res = await renderer.renderToString()
|
||||
|
||||
expect(res).toBe(expected)
|
||||
expect(get).toHaveBeenCalledWith(key)
|
||||
const setArgs = set.mock.calls[0]
|
||||
expect(setArgs[0]).toBe(key)
|
||||
expect(setArgs[1].html).toBe(expected)
|
||||
expect(cache[key].html).toBe(expected)
|
||||
|
||||
const res2 = await renderer.renderToString()
|
||||
expect(res2).toBe(expected)
|
||||
expect(get.mock.calls.length).toBe(2)
|
||||
expect(set.mock.calls.length).toBe(1)
|
||||
})
|
||||
|
||||
it('render with cache (get/set/has)', done => {
|
||||
it('render with cache (get/set/has)', async () => {
|
||||
const cache = {}
|
||||
const has = vi.fn()
|
||||
const get = vi.fn()
|
||||
@ -162,7 +141,7 @@ function createAssertions (runInNewContext) {
|
||||
cb(!!cache[key])
|
||||
},
|
||||
// sync
|
||||
get: key => {
|
||||
get: (key) => {
|
||||
get(key)
|
||||
return cache[key]
|
||||
},
|
||||
@ -172,68 +151,60 @@ function createAssertions (runInNewContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
createRenderer('cache.js', options, renderer => {
|
||||
const expected = '<div data-server-rendered="true">/test</div>'
|
||||
const key = 'app::1'
|
||||
renderer.renderToString((err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe(expected)
|
||||
expect(has).toHaveBeenCalledWith(key)
|
||||
expect(get).not.toHaveBeenCalled()
|
||||
const setArgs = set.mock.calls[0]
|
||||
expect(setArgs[0]).toBe(key)
|
||||
expect(setArgs[1].html).toBe(expected)
|
||||
expect(cache[key].html).toBe(expected)
|
||||
renderer.renderToString((err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe(expected)
|
||||
expect(has.mock.calls.length).toBe(2)
|
||||
expect(get.mock.calls.length).toBe(1)
|
||||
expect(set.mock.calls.length).toBe(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
const renderer = await createWebpackBundleRenderer('cache.js', options)
|
||||
const expected = '<div data-server-rendered="true">/test</div>'
|
||||
const key = 'app::1'
|
||||
const res = await renderer.renderToString()
|
||||
expect(res).toBe(expected)
|
||||
expect(has).toHaveBeenCalledWith(key)
|
||||
expect(get).not.toHaveBeenCalled()
|
||||
const setArgs = set.mock.calls[0]
|
||||
expect(setArgs[0]).toBe(key)
|
||||
expect(setArgs[1].html).toBe(expected)
|
||||
expect(cache[key].html).toBe(expected)
|
||||
|
||||
const res2 = await renderer.renderToString()
|
||||
expect(res2).toBe(expected)
|
||||
expect(has.mock.calls.length).toBe(2)
|
||||
expect(get.mock.calls.length).toBe(1)
|
||||
expect(set.mock.calls.length).toBe(1)
|
||||
})
|
||||
|
||||
it('render with cache (nested)', done => {
|
||||
const cache = new LRU({ maxAge: Infinity })
|
||||
spyOn(cache, 'get').and.callThrough()
|
||||
spyOn(cache, 'set').and.callThrough()
|
||||
it('render with cache (nested)', async () => {
|
||||
const cache = new LRU({ ttl: 65535 }) as any
|
||||
vi.spyOn(cache, 'get')
|
||||
vi.spyOn(cache, 'set')
|
||||
const options = {
|
||||
cache,
|
||||
runInNewContext
|
||||
}
|
||||
createRenderer('nested-cache.js', options, renderer => {
|
||||
const expected = '<div data-server-rendered="true">/test</div>'
|
||||
const key = 'app::1'
|
||||
const context1 = { registered: [] }
|
||||
const context2 = { registered: [] }
|
||||
renderer.renderToString(context1, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe(expected)
|
||||
expect(cache.set.mock.calls.length).toBe(3) // 3 nested components cached
|
||||
const cached = cache.get(key)
|
||||
expect(cached.html).toBe(expected)
|
||||
expect(cache.get.mock.calls.length).toBe(1)
|
||||
const renderer = await createWebpackBundleRenderer(
|
||||
'nested-cache.js',
|
||||
options
|
||||
)
|
||||
const expected = '<div data-server-rendered="true">/test</div>'
|
||||
const key = 'app::1'
|
||||
const context1 = { registered: [] }
|
||||
const context2 = { registered: [] }
|
||||
const res = await renderer.renderToString(context1)
|
||||
expect(res).toBe(expected)
|
||||
expect(cache.set.mock.calls.length).toBe(3) // 3 nested components cached
|
||||
const cached = cache.get(key)
|
||||
expect(cached.html).toBe(expected)
|
||||
expect(cache.get.mock.calls.length).toBe(1)
|
||||
|
||||
// assert component usage registration for nested children
|
||||
expect(context1.registered).toEqual(['app', 'child', 'grandchild'])
|
||||
// assert component usage registration for nested children
|
||||
expect(context1.registered).toEqual(['app', 'child', 'grandchild'])
|
||||
|
||||
renderer.renderToString(context2, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe(expected)
|
||||
expect(cache.set.mock.calls.length).toBe(3) // no new cache sets
|
||||
expect(cache.get.mock.calls.length).toBe(2) // 1 get for root
|
||||
const res2 = await renderer.renderToString(context2)
|
||||
expect(res2).toBe(expected)
|
||||
expect(cache.set.mock.calls.length).toBe(3) // no new cache sets
|
||||
expect(cache.get.mock.calls.length).toBe(2) // 1 get for root
|
||||
|
||||
expect(context2.registered).toEqual(['app', 'child', 'grandchild'])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
expect(context2.registered).toEqual(['app', 'child', 'grandchild'])
|
||||
})
|
||||
|
||||
it('render with cache (opt-out)', done => {
|
||||
it('render with cache (opt-out)', async () => {
|
||||
const cache = {}
|
||||
const get = vi.fn()
|
||||
const set = vi.fn()
|
||||
@ -253,97 +224,103 @@ function createAssertions (runInNewContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
createRenderer('cache-opt-out.js', options, renderer => {
|
||||
const expected = '<div data-server-rendered="true">/test</div>'
|
||||
renderer.renderToString((err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe(expected)
|
||||
expect(get).not.toHaveBeenCalled()
|
||||
expect(set).not.toHaveBeenCalled()
|
||||
renderer.renderToString((err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe(expected)
|
||||
expect(get).not.toHaveBeenCalled()
|
||||
expect(set).not.toHaveBeenCalled()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
const renderer = await createWebpackBundleRenderer(
|
||||
'cache-opt-out.js',
|
||||
options
|
||||
)
|
||||
const expected = '<div data-server-rendered="true">/test</div>'
|
||||
const res = await renderer.renderToString()
|
||||
expect(res).toBe(expected)
|
||||
expect(get).not.toHaveBeenCalled()
|
||||
expect(set).not.toHaveBeenCalled()
|
||||
const res2 = await renderer.renderToString()
|
||||
expect(res2).toBe(expected)
|
||||
expect(get).not.toHaveBeenCalled()
|
||||
expect(set).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('renderToString (bundle format with code split)', done => {
|
||||
createRenderer('split.js', { runInNewContext, asBundle: true }, renderer => {
|
||||
const context = { url: '/test' }
|
||||
renderer.renderToString(context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toBe('<div data-server-rendered="true">/test<div>async test.woff2 test.png</div></div>')
|
||||
done()
|
||||
})
|
||||
it('renderToString (bundle format with code split)', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('split.js', {
|
||||
runInNewContext,
|
||||
asBundle: true
|
||||
})
|
||||
const context = { url: '/test' }
|
||||
const res = await renderer.renderToString(context)
|
||||
expect(res).toBe(
|
||||
'<div data-server-rendered="true">/test<div>async test.woff2 test.png</div></div>'
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToStream (bundle format with code split)', done => {
|
||||
createRenderer('split.js', { runInNewContext, asBundle: true }, renderer => {
|
||||
const context = { url: '/test' }
|
||||
it('renderToStream (bundle format with code split)', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('split.js', {
|
||||
runInNewContext,
|
||||
asBundle: true
|
||||
})
|
||||
const context = { url: '/test' }
|
||||
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
const stream = renderer.renderToStream(context)
|
||||
let res = ''
|
||||
stream.on('data', chunk => {
|
||||
stream.on('data', (chunk) => {
|
||||
res += chunk.toString()
|
||||
})
|
||||
stream.on('error', reject)
|
||||
stream.on('end', () => {
|
||||
expect(res).toBe('<div data-server-rendered="true">/test<div>async test.woff2 test.png</div></div>')
|
||||
done()
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
|
||||
expect(res).toBe(
|
||||
'<div data-server-rendered="true">/test<div>async test.woff2 test.png</div></div>'
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToString catch error (bundle format with source map)', done => {
|
||||
createRenderer('error.js', { runInNewContext, asBundle: true }, renderer => {
|
||||
renderer.renderToString(err => {
|
||||
expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
|
||||
expect(err.message).toBe('foo')
|
||||
done()
|
||||
})
|
||||
it('renderToString catch error (bundle format with source map)', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('error.js', {
|
||||
runInNewContext,
|
||||
asBundle: true
|
||||
})
|
||||
|
||||
try {
|
||||
await renderer.renderToString()
|
||||
} catch (err: any) {
|
||||
expect(err.stack).toContain('test/ssr/fixtures/error.js:1:0')
|
||||
expect(err.message).toBe('foo')
|
||||
}
|
||||
})
|
||||
|
||||
it('renderToString catch error (bundle format with source map)', done => {
|
||||
createRenderer('error.js', { runInNewContext, asBundle: true }, renderer => {
|
||||
it('renderToStream catch error (bundle format with source map)', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('error.js', {
|
||||
runInNewContext,
|
||||
asBundle: true
|
||||
})
|
||||
|
||||
const err = await new Promise<Error>((resolve) => {
|
||||
const stream = renderer.renderToStream()
|
||||
stream.on('error', err => {
|
||||
expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
|
||||
expect(err.message).toBe('foo')
|
||||
done()
|
||||
})
|
||||
stream.on('error', resolve)
|
||||
})
|
||||
|
||||
expect(err.stack).toContain('test/ssr/fixtures/error.js:1:0')
|
||||
expect(err.message).toBe('foo')
|
||||
})
|
||||
|
||||
it('renderToString return Promise', done => {
|
||||
createRenderer('app.js', { runInNewContext }, renderer => {
|
||||
const context = { url: '/test' }
|
||||
renderer.renderToString(context).then(res => {
|
||||
expect(res).toBe('<div data-server-rendered="true">/test</div>')
|
||||
expect(context.msg).toBe('hello')
|
||||
done()
|
||||
})
|
||||
it('renderToString w/ callback', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('app.js', {
|
||||
runInNewContext
|
||||
})
|
||||
const context: any = { url: '/test' }
|
||||
const res = await new Promise((r) =>
|
||||
renderer.renderToString(context, (_err, res) => r(res))
|
||||
)
|
||||
expect(res).toBe('<div data-server-rendered="true">/test</div>')
|
||||
expect(context.msg).toBe('hello')
|
||||
})
|
||||
|
||||
it('renderToString return Promise (error)', done => {
|
||||
createRenderer('error.js', { runInNewContext }, renderer => {
|
||||
renderer.renderToString().catch(err => {
|
||||
expect(err.message).toBe('foo')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('renderToString return Promise (Promise rejection)', done => {
|
||||
createRenderer('promise-rejection.js', { runInNewContext }, renderer => {
|
||||
renderer.renderToString().catch(err => {
|
||||
expect(err.message).toBe('foo')
|
||||
done()
|
||||
})
|
||||
it('renderToString error handling w/ callback', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('error.js', {
|
||||
runInNewContext
|
||||
})
|
||||
const err = await new Promise<Error>((r) => renderer.renderToString(r))
|
||||
expect(err.message).toBe('foo')
|
||||
})
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import Vue from 'vue'
|
||||
import { createRenderer } from 'web/entry-server-renderer'
|
||||
const { renderToStream } = createRenderer()
|
||||
|
||||
;(global as any).__SSR_TEST__ = true
|
||||
|
||||
describe('SSR: renderToStream', () => {
|
||||
it('should render to a stream', done => {
|
||||
const stream = renderToStream(new Vue({
|
||||
|
@ -1,10 +1,10 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import Vue from 'vue'
|
||||
import VM from 'vm'
|
||||
import { createRenderer } from 'web/entry-server-renderer'
|
||||
const { renderToString } = createRenderer()
|
||||
|
||||
;(global as any).__SSR_TEST__ = true
|
||||
|
||||
describe('SSR: renderToString', () => {
|
||||
it('static attributes', done => {
|
||||
renderVmWithOptions({
|
||||
|
@ -1,16 +1,19 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import Vue from 'vue'
|
||||
import { compileWithWebpack } from './compile-with-webpack'
|
||||
import {
|
||||
compileWithWebpack,
|
||||
createWebpackBundleRenderer
|
||||
} from './compile-with-webpack'
|
||||
import { createRenderer } from 'web/entry-server-renderer'
|
||||
import VueSSRClientPlugin from 'server/webpack-plugin/client'
|
||||
import { createRenderer as createBundleRenderer } from './ssr-bundle-render.spec.js'
|
||||
|
||||
;(global as any).__SSR_TEST__ = true
|
||||
import { RenderOptions } from '../../src/server/create-renderer'
|
||||
|
||||
const defaultTemplate = `<html><head></head><body><!--vue-ssr-outlet--></body></html>`
|
||||
const interpolateTemplate = `<html><head><title>{{ title }}</title></head><body><!--vue-ssr-outlet-->{{{ snippet }}}</body></html>`
|
||||
|
||||
function generateClientManifest (file, cb) {
|
||||
compileWithWebpack(file, {
|
||||
async function generateClientManifest(file: string) {
|
||||
const fs = await compileWithWebpack(file, {
|
||||
output: {
|
||||
path: '/',
|
||||
publicPath: '/',
|
||||
@ -21,30 +24,31 @@ function generateClientManifest (file, cb) {
|
||||
name: 'manifest'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new VueSSRClientPlugin()
|
||||
]
|
||||
}, fs => {
|
||||
cb(JSON.parse(fs.readFileSync('/vue-ssr-client-manifest.json', 'utf-8')))
|
||||
plugins: [new VueSSRClientPlugin()]
|
||||
})
|
||||
return JSON.parse(fs.readFileSync('/vue-ssr-client-manifest.json', 'utf-8'))
|
||||
}
|
||||
|
||||
function createRendererWithManifest (file, options, cb) {
|
||||
if (typeof options === 'function') {
|
||||
cb = options
|
||||
options = null
|
||||
}
|
||||
generateClientManifest(file, clientManifest => {
|
||||
createBundleRenderer(file, Object.assign({
|
||||
asBundle: true,
|
||||
template: defaultTemplate,
|
||||
clientManifest
|
||||
}, options), cb)
|
||||
})
|
||||
async function createRendererWithManifest(
|
||||
file: string,
|
||||
options?: RenderOptions
|
||||
) {
|
||||
const clientManifest = await generateClientManifest(file)
|
||||
return createWebpackBundleRenderer(
|
||||
file,
|
||||
Object.assign(
|
||||
{
|
||||
asBundle: true,
|
||||
template: defaultTemplate,
|
||||
clientManifest
|
||||
},
|
||||
options
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
describe.skip('SSR: template option', () => {
|
||||
it('renderToString', done => {
|
||||
describe('SSR: template option', () => {
|
||||
it('renderToString', async () => {
|
||||
const renderer = createRenderer({
|
||||
template: defaultTemplate
|
||||
})
|
||||
@ -55,21 +59,22 @@ describe.skip('SSR: template option', () => {
|
||||
state: { a: 1 }
|
||||
}
|
||||
|
||||
renderer.renderToString(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}${context.styles}</head><body>` +
|
||||
const res = await renderer.renderToString(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}${context.styles}</head><body>` +
|
||||
`<div data-server-rendered="true">hi</div>` +
|
||||
`<script>window.__INITIAL_STATE__={"a":1}</script>` +
|
||||
`</body></html>`
|
||||
)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToString with interpolation', done => {
|
||||
it('renderToString with interpolation', async () => {
|
||||
const renderer = createRenderer({
|
||||
template: interpolateTemplate
|
||||
})
|
||||
@ -82,12 +87,15 @@ describe.skip('SSR: template option', () => {
|
||||
state: { a: 1 }
|
||||
}
|
||||
|
||||
renderer.renderToString(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
const res = await renderer.renderToString(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
// double mustache should be escaped
|
||||
`<title><script>hacks</script></title>` +
|
||||
`${context.head}${context.styles}</head><body>` +
|
||||
@ -96,12 +104,10 @@ describe.skip('SSR: template option', () => {
|
||||
// triple should be raw
|
||||
`<div>foo</div>` +
|
||||
`</body></html>`
|
||||
)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToString with interpolation and context.rendered', done => {
|
||||
it('renderToString with interpolation and context.rendered', async () => {
|
||||
const renderer = createRenderer({
|
||||
template: interpolateTemplate
|
||||
})
|
||||
@ -112,17 +118,19 @@ describe.skip('SSR: template option', () => {
|
||||
head: '<meta name="viewport" content="width=device-width">',
|
||||
styles: '<style>h1 { color: red }</style>',
|
||||
state: { a: 0 },
|
||||
rendered: context => {
|
||||
rendered: (context) => {
|
||||
context.state.a = 1
|
||||
}
|
||||
}
|
||||
|
||||
renderer.renderToString(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
const res = await renderer.renderToString(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
// double mustache should be escaped
|
||||
`<title><script>hacks</script></title>` +
|
||||
`${context.head}${context.styles}</head><body>` +
|
||||
@ -131,74 +139,84 @@ describe.skip('SSR: template option', () => {
|
||||
// triple should be raw
|
||||
`<div>foo</div>` +
|
||||
`</body></html>`
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToString w/ template function', async () => {
|
||||
const renderer = createRenderer({
|
||||
template: (content, context) =>
|
||||
`<html><head>${context.head}</head>${content}</html>`
|
||||
})
|
||||
|
||||
const context = {
|
||||
head: '<meta name="viewport" content="width=device-width">'
|
||||
}
|
||||
|
||||
const res = await renderer.renderToString(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}</head><div data-server-rendered="true">hi</div></html>`
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToString w/ template function returning Promise', async () => {
|
||||
const renderer = createRenderer({
|
||||
template: (content, context) =>
|
||||
new Promise<string>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(`<html><head>${context.head}</head>${content}</html>`)
|
||||
}, 0)
|
||||
})
|
||||
})
|
||||
|
||||
const context = {
|
||||
head: '<meta name="viewport" content="width=device-width">'
|
||||
}
|
||||
|
||||
const res = await renderer.renderToString(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}</head><div data-server-rendered="true">hi</div></html>`
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToString w/ template function returning Promise w/ rejection', async () => {
|
||||
const renderer = createRenderer({
|
||||
template: () =>
|
||||
new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error(`foo`))
|
||||
}, 0)
|
||||
})
|
||||
})
|
||||
|
||||
const context = {
|
||||
head: '<meta name="viewport" content="width=device-width">'
|
||||
}
|
||||
|
||||
try {
|
||||
await renderer.renderToString(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('renderToString w/ template function', done => {
|
||||
const renderer = createRenderer({
|
||||
template: (content, context) => `<html><head>${context.head}</head>${content}</html>`
|
||||
})
|
||||
|
||||
const context = {
|
||||
head: '<meta name="viewport" content="width=device-width">'
|
||||
}
|
||||
|
||||
renderer.renderToString(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(`<html><head>${context.head}</head><div data-server-rendered="true">hi</div></html>`)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('renderToString w/ template function returning Promise', done => {
|
||||
const renderer = createRenderer({
|
||||
template: (content, context) => new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(`<html><head>${context.head}</head>${content}</html>`)
|
||||
}, 0)
|
||||
})
|
||||
})
|
||||
|
||||
const context = {
|
||||
head: '<meta name="viewport" content="width=device-width">'
|
||||
}
|
||||
|
||||
renderer.renderToString(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(`<html><head>${context.head}</head><div data-server-rendered="true">hi</div></html>`)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('renderToString w/ template function returning Promise w/ rejection', done => {
|
||||
const renderer = createRenderer({
|
||||
template: () => new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error(`foo`))
|
||||
}, 0)
|
||||
})
|
||||
})
|
||||
|
||||
const context = {
|
||||
head: '<meta name="viewport" content="width=device-width">'
|
||||
}
|
||||
|
||||
renderer.renderToString(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context, (err, res) => {
|
||||
} catch (err: any) {
|
||||
expect(err.message).toBe(`foo`)
|
||||
expect(res).toBeUndefined()
|
||||
done()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('renderToStream', done => {
|
||||
it('renderToStream', async () => {
|
||||
const renderer = createRenderer({
|
||||
template: defaultTemplate
|
||||
})
|
||||
@ -209,26 +227,33 @@ describe.skip('SSR: template option', () => {
|
||||
state: { a: 1 }
|
||||
}
|
||||
|
||||
const stream = renderer.renderToStream(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context)
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
const stream = renderer.renderToStream(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
|
||||
let res = ''
|
||||
stream.on('data', chunk => {
|
||||
res += chunk
|
||||
let res = ''
|
||||
stream.on('data', (chunk) => {
|
||||
res += chunk
|
||||
})
|
||||
stream.on('error', reject)
|
||||
stream.on('end', () => {
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
stream.on('end', () => {
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}${context.styles}</head><body>` +
|
||||
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}${context.styles}</head><body>` +
|
||||
`<div data-server-rendered="true">hi</div>` +
|
||||
`<script>window.__INITIAL_STATE__={"a":1}</script>` +
|
||||
`</body></html>`
|
||||
)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToStream with interpolation', done => {
|
||||
it('renderToStream with interpolation', async () => {
|
||||
const renderer = createRenderer({
|
||||
template: interpolateTemplate
|
||||
})
|
||||
@ -241,17 +266,26 @@ describe.skip('SSR: template option', () => {
|
||||
state: { a: 1 }
|
||||
}
|
||||
|
||||
const stream = renderer.renderToStream(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context)
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
const stream = renderer.renderToStream(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
|
||||
let res = ''
|
||||
stream.on('data', chunk => {
|
||||
res += chunk
|
||||
let res = ''
|
||||
stream.on('data', (chunk) => {
|
||||
res += chunk
|
||||
})
|
||||
stream.on('error', reject)
|
||||
stream.on('end', () => {
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
stream.on('end', () => {
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
// double mustache should be escaped
|
||||
`<title><script>hacks</script></title>` +
|
||||
`${context.head}${context.styles}</head><body>` +
|
||||
@ -260,12 +294,10 @@ describe.skip('SSR: template option', () => {
|
||||
// triple should be raw
|
||||
`<div>foo</div>` +
|
||||
`</body></html>`
|
||||
)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToStream with interpolation and context.rendered', done => {
|
||||
it('renderToStream with interpolation and context.rendered', async () => {
|
||||
const renderer = createRenderer({
|
||||
template: interpolateTemplate
|
||||
})
|
||||
@ -276,22 +308,31 @@ describe.skip('SSR: template option', () => {
|
||||
head: '<meta name="viewport" content="width=device-width">',
|
||||
styles: '<style>h1 { color: red }</style>',
|
||||
state: { a: 0 },
|
||||
rendered: context => {
|
||||
rendered: (context) => {
|
||||
context.state.a = 1
|
||||
}
|
||||
}
|
||||
|
||||
const stream = renderer.renderToStream(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context)
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
const stream = renderer.renderToStream(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
|
||||
let res = ''
|
||||
stream.on('data', chunk => {
|
||||
res += chunk
|
||||
let res = ''
|
||||
stream.on('data', (chunk) => {
|
||||
res += chunk
|
||||
})
|
||||
stream.on('error', reject)
|
||||
stream.on('end', () => {
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
stream.on('end', () => {
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
// double mustache should be escaped
|
||||
`<title><script>hacks</script></title>` +
|
||||
`${context.head}${context.styles}</head><body>` +
|
||||
@ -300,201 +341,211 @@ describe.skip('SSR: template option', () => {
|
||||
// triple should be raw
|
||||
`<div>foo</div>` +
|
||||
`</body></html>`
|
||||
)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('bundleRenderer + renderToString', done => {
|
||||
createBundleRenderer('app.js', {
|
||||
it('bundleRenderer + renderToString', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('app.js', {
|
||||
asBundle: true,
|
||||
template: defaultTemplate
|
||||
}, renderer => {
|
||||
const context = {
|
||||
head: '<meta name="viewport" content="width=device-width">',
|
||||
styles: '<style>h1 { color: red }</style>',
|
||||
state: { a: 1 },
|
||||
url: '/test'
|
||||
}
|
||||
renderer.renderToString(context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}${context.styles}</head><body>` +
|
||||
`<div data-server-rendered="true">/test</div>` +
|
||||
`<script>window.__INITIAL_STATE__={"a":1}</script>` +
|
||||
`</body></html>`
|
||||
)
|
||||
expect(context.msg).toBe('hello')
|
||||
done()
|
||||
})
|
||||
})
|
||||
const context: any = {
|
||||
head: '<meta name="viewport" content="width=device-width">',
|
||||
styles: '<style>h1 { color: red }</style>',
|
||||
state: { a: 1 },
|
||||
url: '/test'
|
||||
}
|
||||
const res = await renderer.renderToString(context)
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}${context.styles}</head><body>` +
|
||||
`<div data-server-rendered="true">/test</div>` +
|
||||
`<script>window.__INITIAL_STATE__={"a":1}</script>` +
|
||||
`</body></html>`
|
||||
)
|
||||
expect(context.msg).toBe('hello')
|
||||
})
|
||||
|
||||
it('bundleRenderer + renderToStream', done => {
|
||||
createBundleRenderer('app.js', {
|
||||
it('bundleRenderer + renderToStream', async () => {
|
||||
const renderer = await createWebpackBundleRenderer('app.js', {
|
||||
asBundle: true,
|
||||
template: defaultTemplate
|
||||
}, renderer => {
|
||||
const context = {
|
||||
head: '<meta name="viewport" content="width=device-width">',
|
||||
styles: '<style>h1 { color: red }</style>',
|
||||
state: { a: 1 },
|
||||
url: '/test'
|
||||
}
|
||||
})
|
||||
const context: any = {
|
||||
head: '<meta name="viewport" content="width=device-width">',
|
||||
styles: '<style>h1 { color: red }</style>',
|
||||
state: { a: 1 },
|
||||
url: '/test'
|
||||
}
|
||||
|
||||
const res = await new Promise((resolve) => {
|
||||
const stream = renderer.renderToStream(context)
|
||||
let res = ''
|
||||
stream.on('data', chunk => {
|
||||
stream.on('data', (chunk) => {
|
||||
res += chunk.toString()
|
||||
})
|
||||
stream.on('end', () => {
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}${context.styles}</head><body>` +
|
||||
`<div data-server-rendered="true">/test</div>` +
|
||||
`<script>window.__INITIAL_STATE__={"a":1}</script>` +
|
||||
`</body></html>`
|
||||
)
|
||||
expect(context.msg).toBe('hello')
|
||||
done()
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
|
||||
expect(res).toContain(
|
||||
`<html><head>${context.head}${context.styles}</head><body>` +
|
||||
`<div data-server-rendered="true">/test</div>` +
|
||||
`<script>window.__INITIAL_STATE__={"a":1}</script>` +
|
||||
`</body></html>`
|
||||
)
|
||||
expect(context.msg).toBe('hello')
|
||||
})
|
||||
|
||||
const expectedHTMLWithManifest = (options = {}) =>
|
||||
const expectedHTMLWithManifest = (options: any = {}) =>
|
||||
`<html><head>` +
|
||||
// used chunks should have preload
|
||||
`<link rel="preload" href="/manifest.js" as="script">` +
|
||||
`<link rel="preload" href="/main.js" as="script">` +
|
||||
`<link rel="preload" href="/0.js" as="script">` +
|
||||
`<link rel="preload" href="/test.css" as="style">` +
|
||||
// images and fonts are only preloaded when explicitly asked for
|
||||
(options.preloadOtherAssets ? `<link rel="preload" href="/test.png" as="image">` : ``) +
|
||||
(options.preloadOtherAssets ? `<link rel="preload" href="/test.woff2" as="font" type="font/woff2" crossorigin>` : ``) +
|
||||
// unused chunks should have prefetch
|
||||
(options.noPrefetch ? `` : `<link rel="prefetch" href="/1.js">`) +
|
||||
// css assets should be loaded
|
||||
`<link rel="stylesheet" href="/test.css">` +
|
||||
// used chunks should have preload
|
||||
`<link rel="preload" href="/manifest.js" as="script">` +
|
||||
`<link rel="preload" href="/main.js" as="script">` +
|
||||
`<link rel="preload" href="/0.js" as="script">` +
|
||||
`<link rel="preload" href="/test.css" as="style">` +
|
||||
// images and fonts are only preloaded when explicitly asked for
|
||||
(options.preloadOtherAssets
|
||||
? `<link rel="preload" href="/test.png" as="image">`
|
||||
: ``) +
|
||||
(options.preloadOtherAssets
|
||||
? `<link rel="preload" href="/test.woff2" as="font" type="font/woff2" crossorigin>`
|
||||
: ``) +
|
||||
// unused chunks should have prefetch
|
||||
(options.noPrefetch ? `` : `<link rel="prefetch" href="/1.js">`) +
|
||||
// css assets should be loaded
|
||||
`<link rel="stylesheet" href="/test.css">` +
|
||||
`</head><body>` +
|
||||
`<div data-server-rendered="true"><div>async test.woff2 test.png</div></div>` +
|
||||
// state should be inlined before scripts
|
||||
`<script>window.${options.stateKey || '__INITIAL_STATE__'}={"a":1}</script>` +
|
||||
// manifest chunk should be first
|
||||
`<script src="/manifest.js" defer></script>` +
|
||||
// async chunks should be before main chunk
|
||||
`<script src="/0.js" defer></script>` +
|
||||
`<script src="/main.js" defer></script>` +
|
||||
`<div data-server-rendered="true"><div>async test.woff2 test.png</div></div>` +
|
||||
// state should be inlined before scripts
|
||||
`<script>window.${
|
||||
options.stateKey || '__INITIAL_STATE__'
|
||||
}={"a":1}</script>` +
|
||||
// manifest chunk should be first
|
||||
`<script src="/manifest.js" defer></script>` +
|
||||
// async chunks should be before main chunk
|
||||
`<script src="/0.js" defer></script>` +
|
||||
`<script src="/main.js" defer></script>` +
|
||||
`</body></html>`
|
||||
|
||||
createClientManifestAssertions(true)
|
||||
createClientManifestAssertions(false)
|
||||
|
||||
function createClientManifestAssertions (runInNewContext) {
|
||||
it('bundleRenderer + renderToString + clientManifest ()', done => {
|
||||
createRendererWithManifest('split.js', { runInNewContext }, renderer => {
|
||||
renderer.renderToString({ state: { a: 1 }}, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(expectedHTMLWithManifest())
|
||||
done()
|
||||
})
|
||||
function createClientManifestAssertions(runInNewContext) {
|
||||
it('bundleRenderer + renderToString + clientManifest ()', async () => {
|
||||
const renderer = await createRendererWithManifest('split.js', {
|
||||
runInNewContext
|
||||
})
|
||||
const res = await renderer.renderToString({ state: { a: 1 } })
|
||||
expect(res).toContain(expectedHTMLWithManifest())
|
||||
})
|
||||
|
||||
it('bundleRenderer + renderToStream + clientManifest + shouldPreload', done => {
|
||||
createRendererWithManifest('split.js', {
|
||||
it('bundleRenderer + renderToStream + clientManifest + shouldPreload', async () => {
|
||||
const renderer = await createRendererWithManifest('split.js', {
|
||||
runInNewContext,
|
||||
shouldPreload: (file, type) => {
|
||||
if (type === 'image' || type === 'script' || type === 'font' || type === 'style') {
|
||||
if (
|
||||
type === 'image' ||
|
||||
type === 'script' ||
|
||||
type === 'font' ||
|
||||
type === 'style'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}, renderer => {
|
||||
const stream = renderer.renderToStream({ state: { a: 1 }})
|
||||
})
|
||||
const res = await new Promise((resolve) => {
|
||||
const stream = renderer.renderToStream({ state: { a: 1 } })
|
||||
let res = ''
|
||||
stream.on('data', chunk => {
|
||||
stream.on('data', (chunk) => {
|
||||
res += chunk.toString()
|
||||
})
|
||||
stream.on('end', () => {
|
||||
expect(res).toContain(expectedHTMLWithManifest({
|
||||
preloadOtherAssets: true
|
||||
}))
|
||||
done()
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
|
||||
expect(res).toContain(
|
||||
expectedHTMLWithManifest({
|
||||
preloadOtherAssets: true
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('bundleRenderer + renderToStream + clientManifest + shouldPrefetch', done => {
|
||||
createRendererWithManifest('split.js', {
|
||||
it('bundleRenderer + renderToStream + clientManifest + shouldPrefetch', async () => {
|
||||
const renderer = await createRendererWithManifest('split.js', {
|
||||
runInNewContext,
|
||||
shouldPrefetch: (file, type) => {
|
||||
if (type === 'script') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}, renderer => {
|
||||
const stream = renderer.renderToStream({ state: { a: 1 }})
|
||||
})
|
||||
|
||||
const res = await new Promise((resolve) => {
|
||||
const stream = renderer.renderToStream({ state: { a: 1 } })
|
||||
let res = ''
|
||||
stream.on('data', chunk => {
|
||||
stream.on('data', (chunk) => {
|
||||
res += chunk.toString()
|
||||
})
|
||||
stream.on('end', () => {
|
||||
expect(res).toContain(expectedHTMLWithManifest({
|
||||
noPrefetch: true
|
||||
}))
|
||||
done()
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
|
||||
expect(res).toContain(
|
||||
expectedHTMLWithManifest({
|
||||
noPrefetch: true
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('bundleRenderer + renderToString + clientManifest + inject: false', done => {
|
||||
createRendererWithManifest('split.js', {
|
||||
it('bundleRenderer + renderToString + clientManifest + inject: false', async () => {
|
||||
const renderer = await createRendererWithManifest('split.js', {
|
||||
runInNewContext,
|
||||
template: `<html>` +
|
||||
template:
|
||||
`<html>` +
|
||||
`<head>{{{ renderResourceHints() }}}{{{ renderStyles() }}}</head>` +
|
||||
`<body><!--vue-ssr-outlet-->{{{ renderState({ windowKey: '__FOO__', contextKey: 'foo' }) }}}{{{ renderScripts() }}}</body>` +
|
||||
`</html>`,
|
||||
`</html>`,
|
||||
inject: false
|
||||
}, renderer => {
|
||||
const context = { foo: { a: 1 }}
|
||||
renderer.renderToString(context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(expectedHTMLWithManifest({
|
||||
stateKey: '__FOO__'
|
||||
}))
|
||||
done()
|
||||
})
|
||||
})
|
||||
const context = { foo: { a: 1 } }
|
||||
const res = await renderer.renderToString(context)
|
||||
expect(res).toContain(
|
||||
expectedHTMLWithManifest({
|
||||
stateKey: '__FOO__'
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('bundleRenderer + renderToString + clientManifest + no template', done => {
|
||||
createRendererWithManifest('split.js', {
|
||||
it('bundleRenderer + renderToString + clientManifest + no template', async () => {
|
||||
const renderer = await createRendererWithManifest('split.js', {
|
||||
runInNewContext,
|
||||
template: null
|
||||
}, renderer => {
|
||||
const context = { foo: { a: 1 }}
|
||||
renderer.renderToString(context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
|
||||
const customOutput =
|
||||
`<html><head>${
|
||||
context.renderResourceHints() +
|
||||
context.renderStyles()
|
||||
}</head><body>${
|
||||
res +
|
||||
context.renderState({
|
||||
windowKey: '__FOO__',
|
||||
contextKey: 'foo'
|
||||
}) +
|
||||
context.renderScripts()
|
||||
}</body></html>`
|
||||
|
||||
expect(customOutput).toContain(expectedHTMLWithManifest({
|
||||
stateKey: '__FOO__'
|
||||
}))
|
||||
done()
|
||||
})
|
||||
template: null as any
|
||||
})
|
||||
const context: any = { foo: { a: 1 } }
|
||||
const res = await renderer.renderToString(context)
|
||||
|
||||
const customOutput = `<html><head>${
|
||||
context.renderResourceHints() + context.renderStyles()
|
||||
}</head><body>${
|
||||
res +
|
||||
context.renderState({
|
||||
windowKey: '__FOO__',
|
||||
contextKey: 'foo'
|
||||
}) +
|
||||
context.renderScripts()
|
||||
}</body></html>`
|
||||
|
||||
expect(customOutput).toContain(
|
||||
expectedHTMLWithManifest({
|
||||
stateKey: '__FOO__'
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('whitespace insensitive interpolation', done => {
|
||||
it('whitespace insensitive interpolation', async () => {
|
||||
const interpolateTemplate = `<html><head><title>{{title}}</title></head><body><!--vue-ssr-outlet-->{{{snippet}}}</body></html>`
|
||||
const renderer = createRenderer({
|
||||
template: interpolateTemplate
|
||||
@ -508,12 +559,14 @@ describe.skip('SSR: template option', () => {
|
||||
state: { a: 1 }
|
||||
}
|
||||
|
||||
renderer.renderToString(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
const res = await renderer.renderToString(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
// double mustache should be escaped
|
||||
`<title><script>hacks</script></title>` +
|
||||
`${context.head}${context.styles}</head><body>` +
|
||||
@ -522,12 +575,10 @@ describe.skip('SSR: template option', () => {
|
||||
// triple should be raw
|
||||
`<div>foo</div>` +
|
||||
`</body></html>`
|
||||
)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToString + nonce', done => {
|
||||
it('renderToString + nonce', async () => {
|
||||
const interpolateTemplate = `<html><head><title>hello</title></head><body><!--vue-ssr-outlet--></body></html>`
|
||||
const renderer = createRenderer({
|
||||
template: interpolateTemplate
|
||||
@ -538,23 +589,23 @@ describe.skip('SSR: template option', () => {
|
||||
nonce: '4AEemGb0xJptoIGFP3Nd'
|
||||
}
|
||||
|
||||
renderer.renderToString(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
const res = await renderer.renderToString(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
expect(res).toContain(
|
||||
`<html><head>` +
|
||||
`<title>hello</title>` +
|
||||
`</head><body>` +
|
||||
`<div data-server-rendered="true">hi</div>` +
|
||||
`<script nonce="4AEemGb0xJptoIGFP3Nd">window.__INITIAL_STATE__={"a":1}</script>` +
|
||||
`</body></html>`
|
||||
)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('renderToString + custom serializer', done => {
|
||||
it('renderToString + custom serializer', async () => {
|
||||
const expected = `{"foo":123}`
|
||||
const renderer = createRenderer({
|
||||
template: defaultTemplate,
|
||||
@ -565,15 +616,15 @@ describe.skip('SSR: template option', () => {
|
||||
state: { a: 1 }
|
||||
}
|
||||
|
||||
renderer.renderToString(new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}), context, (err, res) => {
|
||||
expect(err).toBeNull()
|
||||
expect(res).toContain(
|
||||
`<script>window.__INITIAL_STATE__=${expected}</script>`
|
||||
)
|
||||
done()
|
||||
})
|
||||
const res = await renderer.renderToString(
|
||||
new Vue({
|
||||
template: '<div>hi</div>'
|
||||
}),
|
||||
context
|
||||
)
|
||||
expect(res).toContain(
|
||||
`<script>window.__INITIAL_STATE__=${expected}</script>`
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -1,5 +1,3 @@
|
||||
;(global as any).__SSR_TEST__ = false
|
||||
|
||||
process.env.NEW_SLOT_SYNTAX = 'true'
|
||||
|
||||
import './helpers/shim-done'
|
||||
|
Loading…
Reference in New Issue
Block a user