From c2c87419fead45ff25168924d2c0e85f083f4a0a Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 1 Feb 2017 13:20:48 -0500 Subject: [PATCH] test bundleRenderer bundle format support + source map --- .babelrc | 2 +- package.json | 3 +- src/server/create-bundle-renderer.js | 39 ++++++++------- src/server/source-map-support.js | 10 ++-- test/ssr/fixtures/comp.js | 5 ++ test/ssr/fixtures/split.js | 25 ++++++++++ test/ssr/ssr-bundle-render.spec.js | 71 ++++++++++++++++++++++++++-- yarn.lock | 12 ++++- 8 files changed, 137 insertions(+), 30 deletions(-) create mode 100644 test/ssr/fixtures/comp.js create mode 100644 test/ssr/fixtures/split.js diff --git a/.babelrc b/.babelrc index 311db760265..605b14091c4 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,6 @@ { "presets": ["es2015", "flow-vue"], - "plugins": ["transform-vue-jsx"], + "plugins": ["transform-vue-jsx", "syntax-dynamic-import"], "ignore": [ "dist/*.js", "packages/**/*.js" diff --git a/package.json b/package.json index 0e94eea424e..4a0cfb292a5 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "babel-helper-vue-jsx-merge-props": "^2.0.2", "babel-loader": "^6.2.4", "babel-plugin-istanbul": "^3.0.0", + "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-syntax-jsx": "^6.18.0", "babel-plugin-transform-vue-jsx": "^3.2.0", "babel-preset-es2015": "^6.9.0", @@ -110,10 +111,10 @@ "selenium-server": "^2.53.1", "typescript": "^2.0.9", "uglify-js": "^2.6.2", + "vue-ssr-webpack-plugin": "^1.0.0", "webpack": "^2.2.0", "weex-js-runtime": "^0.17.0-alpha4", "weex-vdom-tester": "^0.1.4" }, - "dependencies": {}, "unpkg": "dist/vue.js" } diff --git a/src/server/create-bundle-renderer.js b/src/server/create-bundle-renderer.js index 80f8dd2184d..160db6df877 100644 --- a/src/server/create-bundle-renderer.js +++ b/src/server/create-bundle-renderer.js @@ -47,31 +47,36 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) { cb = context context = {} } - runInVm(entry, files, context).then(app => { - renderer.renderToString(app, cb) - }).catch(err => { - if (err instanceof Error) { - rewriteErrorTrace(err, maps) - } + runInVm(entry, files, context).catch(err => { + rewriteErrorTrace(err, maps) cb(err) + }).then(app => { + if (app) { + renderer.renderToString(app, (err, res) => { + rewriteErrorTrace(err, maps) + cb(err, res) + }) + } }) }, renderToStream: (context?: Object) => { const res = new PassThrough() - runInVm(entry, files, context).then(app => { - const renderStream = renderer.renderToStream(app) - renderStream.on('error', err => { - rewriteErrorTrace(err, maps) - res.emit('error', err) - }) - renderStream.pipe(res) - }).catch(err => { + runInVm(entry, files, context).catch(err => { + rewriteErrorTrace(err, maps) + // avoid emitting synchronously before user can + // attach error listener process.nextTick(() => { - if (err instanceof Error) { - rewriteErrorTrace(err, maps) - } res.emit('error', err) }) + }).then(app => { + if (app) { + const renderStream = renderer.renderToStream(app) + renderStream.on('error', err => { + rewriteErrorTrace(err, maps) + res.emit('error', err) + }) + renderStream.pipe(res) + } }) return res } diff --git a/src/server/source-map-support.js b/src/server/source-map-support.js index a4586eff59a..1010532e49c 100644 --- a/src/server/source-map-support.js +++ b/src/server/source-map-support.js @@ -12,12 +12,14 @@ export function createSourceMapConsumers (rawMaps: Object) { return maps } -export function rewriteErrorTrace (e: Error, mapConsumers: { +export function rewriteErrorTrace (e: any, mapConsumers: { [key: string]: SourceMapConsumer }) { - e.stack = e.stack.split('\n').map(line => { - return rewriteTraceLine(line, mapConsumers) - }).join('\n') + if (e && typeof e.stack === 'string') { + e.stack = e.stack.split('\n').map(line => { + return rewriteTraceLine(line, mapConsumers) + }).join('\n') + } } function rewriteTraceLine (trace: string, mapConsumers: { diff --git a/test/ssr/fixtures/comp.js b/test/ssr/fixtures/comp.js new file mode 100644 index 00000000000..f9a779021c6 --- /dev/null +++ b/test/ssr/fixtures/comp.js @@ -0,0 +1,5 @@ +module.exports = { + render (h) { + return h('div', 'async') + } +} diff --git a/test/ssr/fixtures/split.js b/test/ssr/fixtures/split.js new file mode 100644 index 00000000000..4a510aa632e --- /dev/null +++ b/test/ssr/fixtures/split.js @@ -0,0 +1,25 @@ +import Vue from '../../../dist/vue.runtime.common.js' + +// async component! +const Foo = () => import('./comp') + +export default context => { + return new Promise(resolve => { + context.msg = 'hello' + const vm = new Vue({ + render (h) { + return h('div', [ + context.url, + h(Foo) + ]) + } + }) + + // simulate router.onReady + Foo().then(comp => { + // resolve now to make the render sync + Foo.resolved = Vue.extend(comp) + resolve(vm) + }) + }) +} diff --git a/test/ssr/ssr-bundle-render.spec.js b/test/ssr/ssr-bundle-render.spec.js index e0adddd52a3..a46be0dedca 100644 --- a/test/ssr/ssr-bundle-render.spec.js +++ b/test/ssr/ssr-bundle-render.spec.js @@ -1,6 +1,7 @@ import path from 'path' import webpack from 'webpack' import MemoeryFS from 'memory-fs' +import VueSSRPlugin from 'vue-ssr-webpack-plugin' import { createBundleRenderer } from '../../packages/vue-server-renderer' const rendererCache = {} @@ -8,9 +9,14 @@ function createRenderer (file, cb, options) { if (!options && rendererCache[file]) { return cb(rendererCache[file]) } - const compiler = webpack({ + + const asBundle = !!(options && options.asBundle) + if (options) delete options.asBundle + + const config = { target: 'node', entry: path.resolve(__dirname, 'fixtures', file), + devtool: asBundle ? '#source-map' : false, output: { path: '/', filename: 'bundle.js', @@ -18,15 +24,23 @@ function createRenderer (file, cb, options) { }, module: { rules: [{ test: /\.js$/, loader: 'babel-loader' }] - } - }) + }, + plugins: asBundle + ? [new VueSSRPlugin()] + : [] + } + + const compiler = webpack(config) const fs = new MemoeryFS() compiler.outputFileSystem = fs + compiler.run((err, stats) => { expect(err).toBeFalsy() expect(stats.errors).toBeFalsy() - const code = fs.readFileSync('/bundle.js', 'utf-8') - const renderer = rendererCache[file] = createBundleRenderer(code, options) + const bundle = asBundle + ? JSON.parse(fs.readFileSync('/vue-ssr-bundle.json', 'utf-8')) + : fs.readFileSync('/bundle.js', 'utf-8') + const renderer = rendererCache[file] = createBundleRenderer(bundle, options) cb(renderer) }) } @@ -162,4 +176,51 @@ describe('SSR: bundle renderer', () => { }) }, options) }) + + it('renderToString (bundle format with code split)', done => { + createRenderer('split.js', renderer => { + const context = { url: '/test' } + renderer.renderToString(context, (err, res) => { + expect(err).toBeNull() + expect(res).toBe('
/test
async
') + done() + }) + }, { asBundle: true }) + }) + + it('renderToStream (bundle format with code split)', done => { + createRenderer('split.js', renderer => { + const context = { url: '/test' } + const stream = renderer.renderToStream(context) + let res = '' + stream.on('data', chunk => { + res += chunk.toString() + }) + stream.on('end', () => { + expect(res).toBe('
/test
async
') + done() + }) + }, { asBundle: true }) + }) + + it('renderToString catch error (bundle format with source map)', done => { + createRenderer('error.js', renderer => { + renderer.renderToString(err => { + expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6') + expect(err.message).toBe('foo') + done() + }) + }, { asBundle: true }) + }) + + it('renderToString catch error (bundle format with source map)', done => { + createRenderer('error.js', 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() + }) + }, { asBundle: true }) + }) }) diff --git a/yarn.lock b/yarn.lock index f232425b840..03222819094 100644 --- a/yarn.lock +++ b/yarn.lock @@ -256,13 +256,13 @@ async@1.x, async@^1.4.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@2.0.1, async@^2.0.0: +async@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/async/-/async-2.0.1.tgz#b709cc0280a9c36f09f4536be823c838a9049e25" dependencies: lodash "^4.8.0" -async@^2.1.2: +async@^2.0.0, async@^2.1.2: version "2.1.4" resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" dependencies: @@ -459,6 +459,10 @@ babel-plugin-syntax-class-properties@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" +babel-plugin-syntax-dynamic-import@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" + babel-plugin-syntax-flow@^6.18.0, babel-plugin-syntax-flow@^6.8.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" @@ -4903,6 +4907,10 @@ void-elements@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" +vue-ssr-webpack-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/vue-ssr-webpack-plugin/-/vue-ssr-webpack-plugin-1.0.0.tgz#00b35a6b7d23689c65c1af838ef416b0a772b8f5" + watchpack@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.2.0.tgz#15d4620f1e7471f13fcb551d5c030d2c3eb42dbb"