Browse Source

ssr: resolve require() calls relative to bundle (fix #4936)

dev
Evan You 8 years ago
parent
commit
8d88512837
  1. 1
      package.json
  2. 41
      packages/vue-server-renderer/README.md
  3. 1
      packages/vue-server-renderer/package.json
  4. 27
      src/server/create-bundle-renderer.js
  5. 27
      src/server/create-bundle-runner.js
  6. 1
      src/server/create-renderer.js
  7. 4
      yarn.lock

1
package.json

@ -101,6 +101,7 @@
"nightwatch": "^0.9.9",
"nightwatch-helpers": "^1.2.0",
"phantomjs-prebuilt": "^2.1.1",
"resolve": "^1.2.0",
"rollup": "^0.41.4",
"rollup-plugin-alias": "^1.2.0",
"rollup-plugin-babel": "^2.4.0",

41
packages/vue-server-renderer/README.md

@ -76,12 +76,31 @@ app.get('/', (req, res) => {
---
### createBundleRenderer([bundle](#creating-the-server-bundle), [[rendererOptions](#renderer-options)])
### createBundleRenderer(bundle, [[rendererOptions](#renderer-options)])
Creates a `bundleRenderer` instance using pre-compiled application bundle (see [Creating the Server Bundle](#creating-the-server-bundle)). For each render call, the code will be re-run in a new context using Node.js' `vm` module. This ensures your application state is discrete between requests, and you don't need to worry about structuring your application in a limiting pattern just for the sake of SSR.
Creates a `bundleRenderer` instance using pre-compiled application bundle. The `bundle` argument can be one of the following:
- An absolute path to generated bundle file (`.js` or `.json`). Must start with `/` to be treated as a file path.
- A bundle object generated by `vue-ssr-webpack-plugin`.
- A string of JavaScript code.
See [Creating the Server Bundle](#creating-the-server-bundle) for more details.
For each render call, the code will be re-run in a new context using Node.js' `vm` module. This ensures your application state is discrete between requests, and you don't need to worry about structuring your application in a limiting pattern just for the sake of SSR.
``` js
const bundleRenderer = require('vue-server-renderer').createBundleRenderer(bundle)
const createBundleRenderer = require('vue-server-renderer').createBundleRenderer
// absolute filepath
let renderer = createBundleRenderer('/path/to/bundle.json')
// bundle object
let renderer = createBundleRenderer({ ... })
// code (not recommended for lack of source map support)
let renderer = createBundleRenderer(bundledCode)
```
---
@ -190,6 +209,16 @@ const renderer = createRenderer({
---
### basedir
> New in 2.2.0
Explicitly declare the base directory for the server bundle to resolve node_modules from. This is only needed if your generated bundle file is placed in a different location from where the externalized NPM dependencies are installed.
Note that the `basedir` is automatically inferred if you use `vue-ssr-webpack-plugin` or provide an absolute path to `createBundleRenderer` as the first argument, so in most cases you don't need to provide this option. However, this option does allow you to explicitly overwrite the inferred value.
---
### directives
Allows you to provide server-side implementations for your custom directives:
@ -220,7 +249,7 @@ Instead, it's more straightforward to run our app "fresh", in a sandboxed contex
<img width="973" alt="screen shot 2016-08-11 at 6 06 57 pm" src="https://cloud.githubusercontent.com/assets/499550/17607895/786a415a-5fee-11e6-9c11-45a2cfdf085c.png">
The application bundle can be either a string of bundled code, or a special object of the following type:
The application bundle can be either a string of bundled code (not recommended due to lack of source map support), or a special object of the following type:
``` js
type RenderBundle = {
@ -230,9 +259,9 @@ type RenderBundle = {
}
```
Although theoretically you can use any build tool to generate the bundle, it is recommended to use webpack + `vue-loader` + [vue-ssr-webpack-plugin](https://github.com/vuejs/vue-ssr-webpack-plugin) for this purpose. This setup works seamlessly even if you use webpack's on-demand code splitting features such as dynamic `import()`.
Although theoretically you can use any build tool to generate the bundle, it is recommended to use webpack + `vue-loader` + [vue-ssr-webpack-plugin](https://github.com/vuejs/vue-ssr-webpack-plugin) for this purpose. The plugin will automatically turn the build output into a single JSON file that you can then pass to `createBundleRenderer`. This setup works seamlessly even if you use webpack's on-demand code splitting features such as dynamic `import()`.
The usual workflow is setting up a base webpack configuration file for the client-side, then modify it to generate the server-side bundle with the following changes:
The typical workflow is setting up a base webpack configuration file for the client-side, then modify it to generate the server-side bundle with the following changes:
1. Set `target: 'node'` and `output: { libraryTarget: 'commonjs2' }` in your webpack config.

1
packages/vue-server-renderer/package.json

@ -20,6 +20,7 @@
"dependencies": {
"he": "^1.1.0",
"de-indent": "^1.0.2",
"resolve": "^1.2.0",
"source-map": "0.5.6",
"vue-ssr-html-stream": "^2.1.0"
},

27
src/server/create-bundle-renderer.js

@ -4,6 +4,8 @@ import { createBundleRunner } from './create-bundle-runner'
import type { Renderer, RenderOptions } from './create-renderer'
import { createSourceMapConsumers, rewriteErrorTrace } from './source-map-support'
const fs = require('fs')
const path = require('path')
const PassThrough = require('stream').PassThrough
const INVALID_MSG =
@ -18,6 +20,7 @@ const INVALID_MSG =
// The render bundle can either be a string (single bundled file)
// or a bundle manifest object generated by vue-ssr-webpack-plugin.
type RenderBundle = {
basedir?: string;
entry: string;
files: { [filename: string]: string; };
maps: { [filename: string]: string; };
@ -31,9 +34,29 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) {
const renderer = createRenderer(rendererOptions)
let files, entry, maps
let basedir = rendererOptions && rendererOptions.basedir
// load bundle if given filepath
if (typeof bundle === 'string' && bundle.charAt(0) === '/') {
if (fs.existsSync(bundle)) {
basedir = basedir || path.dirname(bundle)
bundle = fs.readFileSync(bundle, 'utf-8')
if (/\.json$/.test(bundle)) {
try {
bundle = JSON.parse(bundle)
} catch (e) {
throw new Error(`Invalid JSON bundle file: ${bundle}`)
}
}
} else {
throw new Error(`Cannot locate bundle file: ${bundle}`)
}
}
if (typeof bundle === 'object') {
entry = bundle.entry
files = bundle.files
basedir = basedir || bundle.basedir
maps = createSourceMapConsumers(bundle.maps)
if (typeof entry !== 'string' || typeof files !== 'object') {
throw new Error(INVALID_MSG)
@ -45,7 +68,9 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) {
} else {
throw new Error(INVALID_MSG)
}
const run = createBundleRunner(entry, files)
const run = createBundleRunner(entry, files, basedir)
return {
renderToString: (context?: Object, cb: (err: ?Error, res: ?string) => void) => {
if (typeof context === 'function') {

27
src/server/create-bundle-runner.js

@ -1,26 +1,28 @@
const NativeModule = require('module')
const vm = require('vm')
const path = require('path')
const resolve = require('resolve')
const NativeModule = require('module')
function createContext (context) {
const sandbox = {
Buffer,
clearImmediate,
clearInterval,
clearTimeout,
setImmediate,
setInterval,
setTimeout,
console,
process,
setTimeout,
setInterval,
setImmediate,
clearTimeout,
clearInterval,
clearImmediate,
__VUE_SSR_CONTEXT__: context
}
sandbox.global = sandbox
return sandbox
}
function compileModule (files) {
function compileModule (files, basedir) {
const compiledScripts = {}
const reoslvedModules = {}
function getCompiledScript (filename) {
if (compiledScripts[filename]) {
@ -48,6 +50,11 @@ function compileModule (files) {
file = path.join('.', file)
if (files[file]) {
return evaluateModule(file, context, evaluatedModules)
} else if (basedir) {
return require(
reoslvedModules[file] ||
(reoslvedModules[file] = resolve.sync(file, { basedir }))
)
} else {
return require(file)
}
@ -63,8 +70,8 @@ function compileModule (files) {
return evaluateModule
}
export function createBundleRunner (entry, files) {
const evaluate = compileModule(files)
export function createBundleRunner (entry, files, basedir) {
const evaluate = compileModule(files, basedir)
return (_context = {}) => new Promise((resolve, reject) => {
const context = createContext(_context)
const res = evaluate(entry, context, {})

1
src/server/create-renderer.js

@ -23,6 +23,7 @@ export type RenderOptions = {
isUnaryTag?: Function;
cache?: RenderCache;
template?: string;
basedir?: string;
};
export function createRenderer ({

4
yarn.lock

@ -4163,6 +4163,10 @@ resolve@1.1.x, resolve@^1.1.6:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c"
restore-cursor@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"

Loading…
Cancel
Save