Skip to content

Commit

Permalink
ssr: resolve require() calls relative to bundle (fix #4936)
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Feb 15, 2017
1 parent 6977109 commit 8d88512
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 17 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
41 changes: 35 additions & 6 deletions packages/vue-server-renderer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```

---
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = {
Expand All @@ -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.

Expand Down
1 change: 1 addition & 0 deletions packages/vue-server-renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
27 changes: 26 additions & 1 deletion src/server/create-bundle-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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; };
Expand All @@ -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)
Expand All @@ -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') {
Expand Down
27 changes: 17 additions & 10 deletions src/server/create-bundle-runner.js
Original file line number Diff line number Diff line change
@@ -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]) {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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, {})
Expand Down
1 change: 1 addition & 0 deletions src/server/create-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type RenderOptions = {
isUnaryTag?: Function;
cache?: RenderCache;
template?: string;
basedir?: string;
};

export function createRenderer ({
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4163,6 +4163,10 @@ [email protected], 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"
Expand Down

0 comments on commit 8d88512

Please sign in to comment.