Преглед на файлове

support source map in bundle renderer

Evan You преди 9 години
родител
ревизия
068095579b

+ 1 - 1
build/config.js

@@ -84,7 +84,7 @@ const builds = {
     entry: path.resolve(__dirname, '../src/entries/web-server-renderer.js'),
     dest: path.resolve(__dirname, '../packages/vue-server-renderer/build.js'),
     format: 'cjs',
-    external: ['stream', 'module', 'vm', 'he', 'de-indent']
+    external: ['he', 'de-indent', 'source-map']
   },
   // Weex runtime factory
   'weex-factory': {

+ 7 - 0
flow/modules.js

@@ -9,6 +9,13 @@ declare module 'source-map' {
     addMapping(mapping: Object): void;
     toString(): string;
   }
+  declare class SourceMapConsumer {
+    originalPositionFor(position: { line: number; column: number; }): {
+      source: ?string;
+      line: ?number;
+      column: ?number;
+    };
+  }
 }
 
 declare module 'lru-cache' {

+ 1 - 1
src/core/components/keep-alive.js

@@ -3,7 +3,7 @@
 import { callHook } from 'core/instance/lifecycle'
 import { getFirstComponentChild } from 'core/vdom/helpers/index'
 
-const patternTypes = [String, RegExp]
+const patternTypes: Array<Function> = [String, RegExp]
 
 function getComponentName (opts: ?VNodeComponentOptions): ?string {
   return opts && (opts.Ctor.options.name || opts.tag)

+ 1 - 1
src/platforms/web/server/modules/dom-props.js

@@ -25,7 +25,7 @@ export default function renderDOMProps (node: VNodeWithData): string {
     if (key === 'innerHTML') {
       setText(node, props[key], true)
     } else if (key === 'textContent') {
-      setText(node, props[key])
+      setText(node, props[key], false)
     } else {
       const attr = propsToAttrMap[key] || key.toLowerCase()
       if (isRenderableAttr(attr) &&

+ 12 - 3
src/server/create-bundle-renderer.js

@@ -1,8 +1,10 @@
 /* @flow */
 
 import runInVm from './run-in-vm'
-import { PassThrough } from 'stream'
 import type { Renderer, RenderOptions } from './create-renderer'
+import { createSourceMapConsumers, rewriteErrorTrace } from './source-map-support'
+
+const PassThrough = require('stream').PassThrough
 
 const INVALID_MSG =
   'Invalid server-rendering bundle format. Should be a string ' +
@@ -24,16 +26,18 @@ type RenderBundle = string | {
 export function createBundleRendererCreator (createRenderer: () => Renderer) {
   return (bundle: RenderBundle, rendererOptions?: RenderOptions) => {
     const renderer = createRenderer(rendererOptions)
-    let files, entry
+    let files, entry, maps
     if (typeof bundle === 'object') {
       entry = bundle.entry
       files = bundle.files
+      maps = createSourceMapConsumers(bundle.maps)
       if (typeof entry !== 'string' || typeof files !== 'object') {
         throw new Error(INVALID_MSG)
       }
     } else if (typeof bundle === 'string') {
       entry = '__vue_ssr_bundle__'
       files = { '__vue_ssr_bundle__': bundle }
+      maps = {}
     } else {
       throw new Error(INVALID_MSG)
     }
@@ -45,18 +49,23 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) {
         }
         runInVm(entry, files, context).then(app => {
           renderer.renderToString(app, cb)
-        }).catch(cb)
+        }).catch(err => {
+          rewriteErrorTrace(err, maps)
+          cb(err)
+        })
       },
       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 => {
           process.nextTick(() => {
+            rewriteErrorTrace(err, maps)
             res.emit('error', err)
           })
         })

+ 43 - 0
src/server/source-map-support.js

@@ -0,0 +1,43 @@
+/* @flow */
+
+const SourceMapConsumer = require('source-map').SourceMapConsumer
+
+const filenameRE = /\(([^)]+\.js):(\d+):(\d+)\)$/
+
+export function createSourceMapConsumers (rawMaps: Object) {
+  const maps = {}
+  Object.keys(rawMaps).forEach(file => {
+    maps[file] = new SourceMapConsumer(rawMaps[file])
+  })
+  return maps
+}
+
+export function rewriteErrorTrace (e: Error, mapConsumers: {
+  [key: string]: SourceMapConsumer
+}) {
+  e.stack = e.stack.split('\n').map(line => {
+    return rewriteTraceLine(line, mapConsumers)
+  }).join('\n')
+}
+
+function rewriteTraceLine (trace: string, mapConsumers: {
+  [key: string]: SourceMapConsumer
+}) {
+  const m = trace.match(filenameRE)
+  const map = m && mapConsumers[m[1]]
+  if (m != null && map) {
+    const originalPosition = map.originalPositionFor({
+      line: Number(m[2]),
+      column: Number(m[3])
+    })
+    if (originalPosition.source != null) {
+      const { source, line, column } = originalPosition
+      const mappedPosition = `(${source.replace(/^webpack:\/\/\//, '')}:${line}:${column})`
+      return trace.replace(filenameRE, mappedPosition)
+    } else {
+      return trace
+    }
+  } else {
+    return trace
+  }
+}