Vue: Provide Server-Side-Rendering helper using more purely JavaScript

Created on 11 Apr 2017  ·  23Comments  ·  Source: vuejs/vue

What problem does this feature solve?

Current Server-Side-Rendering implementation is bond to Node.js, makes it hard to use in Nashorn (lightweight high-performance JavaScript runtime in Java with a native JVM) or other JavaScript runtime.

Foundations needed by Vue like vm and fs modules may be passed into renderer or runner as parameter which implements some interfaces.

What does the proposed API look like?

feature request

Most helpful comment

FYI: in 2.4 we already shipped a pure js build of vue-server-renderer, but we haven't announced it yet. It only supports the base renderer, but is decoupled from Node.js APIs. Feel free to try integrating it in other contexts.

All 23 comments

Added a note about Nashorn

I would love to see that happen.

I'm trying to use Vue.js with nashorn too.
It will be great if Vue.js SSR added interfaces such a FileSystem or Engine to externalize
some functionalities that are bind to node.js

I've made some research:

  1. path in all places can be abstracted
    currently used path.extname, path.join, path.isAbsolute, path.dirname

  2. fs in all places can be abstracted too
    currently used fs.existsSync, fs.readFileSync

  3. module
    only one usage NativeModule.wrap
    According to docs and soruce code this only wrap script code with

(function (exports, require, module, __filename, __dirname) {
// Your module code actually lives in here
});

so this is can be replaced with function if we are not on node.js

  1. resolve , vm used in create-bundle-runner.js
    This file can be somehow abstracted :) not sure how and what interface could look

  2. stream, source-map-support TODO :)

I made small modifications in source code to get rid of stream and path
my entry:

//import 'core-js/modules/es6.array.find.js';
//import 'core-js/modules/es6.object.assign.js';
//import 'nashorn-polyfill'
//import 'event-loop'
import  Vue from './web-runtime-with-compiler'
export { Vue }
export { createRenderer/*, createBundleRenderer*/} from './web-server-renderer'

result sizes:

  1. only createRenderer() and removed path and stream requires (commented stream functions and replaced path with own simple implementation)
    after browserify -> 430KB
  2. only createRenderer() (commented out bundleRenderer form entry)
    after browserify run -> 580KB
  3. createRenderer() and createBundleRenderer() (not modified at all)
    after browserify -> 700KB

@wojtask9 suprised to see you go further! Would you mind share us a demo code?

Yes of course. I can share my code. But you must be patient (because of Easter Holiday)
JS part I'll try share tomorrow evening and java part probably on Tuesday.

@wojtask9 Thanks! I will also try to design and implement some helper for Nashorn, to see how it will be going.

@wojtask9 Hey, we're interested in seeing your solution as well. Can you post it somewhere?

yeah. I'm finishing it. have been busy for the last days.
Currently trying to run ssr tests on nashorn to be sure if everything is OK.
Please be patient :)

You can find "my" code here:
https://github.com/wojtask9/vue

packages/vue-server-renderer-nashorn/vue-nashorn.js -> complete JS file with all required dependencies and polyfills for Nashorn.

I tried to do modify Vue sources as less as possible.
People can build Vue for Nashorn typing npm run build:ssr-nashorn
For jasmine tests in Nashorn type npm run test:ssr-nashorn (on windows you must change path to jjs.exe in package.json)

Currently only renderToString works.
renderToStream have some issues with threads but should be resolved soon.

createBundleRenderer is currently commented because first I must resolve issues with Streams but on my TODO :)

If you have any questions I'll try answer :)

Ok i fixed streams and other issues.

There is only one left issue that I'm aware of ( read -> everything works)
For big data (like benchmark/ssr/common.js) renderToString and renderToStream doesn't return output.
If I change rows and columns in generateGrid to 10,10 from 1000, 10 everything is OK.

Not sure where the problem is :/

Another thing that make me wonder is event-loop. Currently setTimeout is invoked in thread that is different from main-thread. This is difference between node.js and nashorn.
I'll made implementation that behaves like node.js but currently it is more complicated.
But first I'll focus on issue mentioned above.

Now everything is solved.
MAX_STACK_DEPTH was too big for nashorn (original 1000 and currently on nashorn 190).

Performance (on the same linux machine)
nashorn full version 1.8.0_121-b13
--- renderToString ---
Complete time: 20982.00ms

--- renderToStream ---
first chunk: 1202.00ms
complete: 28488.00ms

node.js v6.10.0
--- renderToString ---
Complete time: 2164.08ms

--- renderToStream ---
first chunk: 90.15ms
complete: 2005.17ms

so it is rather slow :/ but why?

The result is consistent to what i tried using React. repo

Benchmark using JMH framework shows that typically nashorn version server side render is 1000% slower than Node.js version. I thought of wasting time in create/destroy nashorn context.

Since Vue.js use vm module and React.js not, I expected better performance ssr using Vue.js.

Run SSR in Nashorn is more robust than in Node.js. Thread-control, ability to terminate render at any time, cache across thread.

@wojtask9 how about share your benchmark code to see if something can be optimized?

I honestly think this is simply because V8 produces much better optimized machine code than Nashorn... not much can be done in Vue/React to solve that :/

Nashorn directly map js to JVM bytecode, implementation now may not great enough as V8. I believe things will get better. It's valuable to make Vue.js SSR not bond to a specific JavaScript Runtime. Nashorn (etc. Browser's Service-Worker, Embedded JavaScript Engine)

Define some abstract interface and provide Node.js implementation as default will be great.

@denghongcai yeah, that's probably what we will do in the future - but for now we want to focus on providing a good, stable and performant Node-based SSR solution first.

@denghongcai
benchmarks added (to my fork of Vue.js)
javac NashornVue.java && java NashornVue or use script ./benchmarks/nashorn/nashorn-benchmark.js

Vue with my packackage doesn't use vm or any external modules (only bundled process and utils but without process there isn't any difference ).

I think nashorn doesn't optimize recursive calls at beginning and doesn't like huge stacks.
After 5 iterations performance is good (probably JIT goes in).

results (nashorn)
--- renderToString ---
0 Complete time: 13288.00ms
1 Complete time: 9400.00ms
2 Complete time: 8476.00ms
3 Complete time: 6587.00ms
4 Complete time: 6690.00ms
5 Complete time: 2805.00ms
6 Complete time: 2318.00ms
7 Complete time: 5442.00ms
8 Complete time: 2382.00ms
9 Complete time: 2859.00ms

Maybe there is room for improvements in Vue. For example instead of recursion use iterate version?

image

I removed recursion from next() function (some async test are falling but benchrmarks don't use asyncComponents and currently it's out of my scope). Now StackTraces looks nicer and I see performance improvement (sadly no difference using node.js)

Some results (btw i generate dynamic data on every loop to be sure that nashorn don't optimize whole scripts).
After 5th loop I see results are better than with node.js :)
Not sure if my benchmarks are correct or simply nashorn optimizes code very well.

Loading benchmarks
--- renderToString ---

#0 Complete time: 11637.00ms
#1 Complete time: 7140.00ms
#2 Complete time: 1692.00ms
#3 Complete time: 1419.00ms
#4 Complete time: 1481.00ms
#5 Complete time: 781.00ms
#6 Complete time: 889.00ms
#7 Complete time: 937.00ms
#8 Complete time: 556.00ms
#9 Complete time: 323.00ms
#8 Complete time: 654.00ms
#9 Complete time: 1021.00ms
#10 Complete time: 604.00ms
#11 Complete time: 576.00ms
#12 Complete time: 554.00ms
#13 Complete time: 1119.00ms
#14 Complete time: 572.00ms
#15 Complete time: 561.00ms
#16 Complete time: 630.00ms
#17 Complete time: 1083.00ms
#18 Complete time: 500.00ms
#19 Complete time: 470.00ms
#20 Complete time: 519.00ms
#21 Complete time: 1330.00ms
#22 Complete time: 455.00ms
#23 Complete time: 457.00ms
#24 Complete time: 524.00ms
#25 Complete time: 444.00ms
#26 Complete time: 507.00ms
#27 Complete time: 446.00ms
#28 Complete time: 491.00ms
#29 Complete time: 535.00ms
#30 Complete time: 474.00ms
#31 Complete time: 454.00ms
#32 Complete time: 593.00ms
#33 Complete time: 284.00ms
#34 Complete time: 249.00ms
#35 Complete time: 315.00ms
#36 Complete time: 272.00ms
#37 Complete time: 287.00ms
#38 Complete time: 269.00ms
#39 Complete time: 292.00ms

@wojtask9 I can't find your optimized code in your repo...

I just get a hint that you're definitely from alibaba. hahah

@denghongcai
I forgot about your last comment. I'll push my changes probably next weekend because currently I'm very busy.
I have also other small improvements (bundle size reduction), working Buffers/Streams with java API and working on bundleRenderer in nashorn

FYI: in 2.4 we already shipped a pure js build of vue-server-renderer, but we haven't announced it yet. It only supports the base renderer, but is decoupled from Node.js APIs. Feel free to try integrating it in other contexts.

@yyx990803,

Given your comment above, if I were to write something simple like this:
const renderer = require('vue-server-renderer').createRenderer();
and run it through webpack, I shouldn't be getting messages like:
Module not found: Error: Can't resolve 'fs', correct?

@sowhatdoido fs a native Node module. You can't just webpack it. Use vue-server-renderer/basic instead.

@yyx990803 Ah! Sorry, I thought when you said the base renderer, you meant createRenderer vs createBundleRenderer. Thanks for your help!

Was this page helpful?
0 / 5 - 0 ratings