Просмотр исходного кода

test bundleRenderer bundle format support + source map

Evan You 9 лет назад
Родитель
Сommit
c2c87419fe

+ 1 - 1
.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"

+ 2 - 1
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"
 }

+ 22 - 17
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
       }

+ 6 - 4
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: {

+ 5 - 0
test/ssr/fixtures/comp.js

@@ -0,0 +1,5 @@
+module.exports = {
+  render (h) {
+    return h('div', 'async')
+  }
+}

+ 25 - 0
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)
+    })
+  })
+}

+ 66 - 5
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('<div server-rendered="true">/test<div>async</div></div>')
+        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('<div server-rendered="true">/test<div>async</div></div>')
+        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 })
+  })
 })

+ 10 - 2
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"