Bladeren bron

test: all ssr tests passing

Evan You 4 jaren geleden
bovenliggende
commit
898d1e19fc

+ 19 - 0
.github/workflows/ci.yml

@@ -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:

+ 4 - 3
package.json

@@ -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": {

File diff suppressed because it is too large
+ 591 - 114
pnpm-lock.yaml


+ 1 - 2
scripts/config.js

@@ -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) => {

+ 1 - 1
src/core/util/error.ts

@@ -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

+ 0 - 2
src/global.d.ts

@@ -1,5 +1,3 @@
-declare const __SSR_TEST__: boolean;
-
 interface Window {
   __VUE_DEVTOOLS_GLOBAL_HOOK__: DevtoolsHook;
 }

+ 4 - 5
src/server/bundle-renderer/create-bundle-renderer.ts

@@ -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
-      },
+      }
     }
   }
 }

+ 7 - 6
src/server/create-renderer.ts

@@ -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
       }
-    },
+    }
   }
 }

+ 4 - 3
src/server/template-renderer/index.ts

@@ -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)
   }
 }
 

+ 45 - 7
test/ssr/compile-with-webpack.ts

@@ -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)
+}

+ 0 - 0
test/ssr/fixtures/app.ts → test/ssr/fixtures/app.js


+ 0 - 0
test/ssr/fixtures/async-bar.ts → test/ssr/fixtures/async-bar.js


+ 0 - 0
test/ssr/fixtures/async-foo.ts → test/ssr/fixtures/async-foo.js


+ 0 - 0
test/ssr/fixtures/cache-opt-out.ts → test/ssr/fixtures/cache-opt-out.js


+ 0 - 0
test/ssr/fixtures/cache.ts → test/ssr/fixtures/cache.js


+ 0 - 0
test/ssr/fixtures/error.ts → test/ssr/fixtures/error.js


+ 0 - 0
test/ssr/fixtures/nested-cache.ts → test/ssr/fixtures/nested-cache.js


+ 0 - 0
test/ssr/fixtures/promise-rejection.ts → test/ssr/fixtures/promise-rejection.js


+ 0 - 0
test/ssr/fixtures/split.ts → test/ssr/fixtures/split.js


+ 2 - 2
test/ssr/ssr-basic-renderer.spec.ts

@@ -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({

+ 196 - 219
test/ssr/ssr-bundle-render.spec.ts

@@ -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'
-
-;(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)
-  })
-}
+import { createWebpackBundleRenderer } from './compile-with-webpack'
 
-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)
-
-        // 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
-
-          expect(context2.registered).toEqual(['app', 'child', 'grandchild'])
-          done()
-        })
-      })
-    })
+    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'])
+
+    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'])
   })
 
-  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 => {
-      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()
-      })
+  it('renderToStream catch error (bundle format with source map)', async () => {
+    const renderer = await createWebpackBundleRenderer('error.js', {
+      runInNewContext,
+      asBundle: true
     })
-  })
 
-  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()
-      })
+    const err = await new Promise<Error>((resolve) => {
+      const stream = renderer.renderToStream()
+      stream.on('error', resolve)
     })
+
+    expect(err.stack).toContain('test/ssr/fixtures/error.js:1:0')
+    expect(err.message).toBe('foo')
   })
 
-  it('renderToString return Promise (error)', done => {
-    createRenderer('error.js', { runInNewContext }, renderer => {
-      renderer.renderToString().catch(err => {
-        expect(err.message).toBe('foo')
-        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 (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')
   })
 }

+ 2 - 2
test/ssr/ssr-stream.spec.ts

@@ -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({

+ 2 - 2
test/ssr/ssr-string.spec.ts

@@ -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({

+ 335 - 284
test/ssr/ssr-template.spec.ts

@@ -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>&lt;script&gt;hacks&lt;/script&gt;</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>&lt;script&gt;hacks&lt;/script&gt;</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>`
-      )
-      done()
-    })
+    )
   })
 
-  it('renderToString w/ template function', done => {
+  it('renderToString w/ template function', async () => {
     const renderer = createRenderer({
-      template: (content, context) => `<html><head>${context.head}</head>${content}</html>`
+      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()
-    })
+    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', done => {
+  it('renderToString w/ template function returning Promise', async () => {
     const renderer = createRenderer({
-      template: (content, context) => new Promise((resolve) => {
-        setTimeout(() => {
-          resolve(`<html><head>${context.head}</head>${content}</html>`)
-        }, 0)
-      })
+      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">'
     }
 
-    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()
-    })
+    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', done => {
+  it('renderToString w/ template function returning Promise w/ rejection', async () => {
     const renderer = createRenderer({
-      template: () => new Promise((resolve, reject) => {
-        setTimeout(() => {
-          reject(new Error(`foo`))
-        }, 0)
-      })
+      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) => {
+    try {
+      await renderer.renderToString(
+        new Vue({
+          template: '<div>hi</div>'
+        }),
+        context
+      )
+    } 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>&lt;script&gt;hacks&lt;/script&gt;</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>&lt;script&gt;hacks&lt;/script&gt;</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>&lt;script&gt;hacks&lt;/script&gt;</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>`
+      )
     })
   }
 })

+ 0 - 2
test/vitest.setup.ts

@@ -1,5 +1,3 @@
-;(global as any).__SSR_TEST__ = false
-
 process.env.NEW_SLOT_SYNTAX = 'true'
 
 import './helpers/shim-done'

Some files were not shown because too many files changed in this diff