vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.97k stars 33.68k forks source link

[SSR] Add option to output the server entry through webpack #9313

Open Kjir opened 5 years ago

Kjir commented 5 years ago

What problem does this feature solve?

The guide for Server-Side rendering creates 2 webpack builds, one for the client and one for the server. Then you create a file that imports the outputs from those 2 builds and returns the responses to whatever framework you use (e.g. express). Let's call this file the server handler. The issue with this approach is that you would need a third webpack build to process the server handler through webpack as well. You might want to do that for a few different reasons:

  1. To have the same flow for all your files
  2. Because you use typescript/babel/minification or other transformations
  3. To use the same style of imports (i.e. ES modules)

What does the proposed API look like?

I can think of a few possible solutions:

1. Define the name of the server handler to emit

There could be a configuration option for VueSSRServerPlugin that defines one additional entry to emit in the Webpack configuration. This would mean that there could be at most 2 entries, one is used for creating the JSON bundle, the other one to emit the server handler

// webpack.config.js
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')

module.exports = {
  // Point entry to your app's server entry file
  entry: {
     main: '/path/to/server-entry.js',
     handler: '/path/to/server-handler.js'
  },
  resolve: {
    alias: {
      'client-manifest': '/path/to/dist/client/vue-ssr-client-manifest.json'
    }
  },
  plugins: [
    new VueSSRServerPlugin({ serverHandler: 'handler' })
  ]
}

The handler would look something like this:

// server-handler.js
import clientManifest from 'client-manifest';
// This alias could be created by the VueSSRServerPlugin itself
import serverBundle from 'server-bundle';
import { createBundleRenderer } from 'vue-server-renderer';

const template = `[...]`;

const renderer = createBundleRenderer(serverBundle, {
  template,
  clientManifest,
  runInNewContext: false
});

export function handler(event, context) {
  // use the renderer in here
}

The upside of this approach is that it should be relatively easy to accomplish this. Another upside is that this would be backwards compatible. The downside is that there would be quite a bit of configuration necessary to make it look nice — to avoid importing build outputs directly in code, which would add a dependency on our webpack configuration in the code.

2. Provide an alias that resolves to the renderer instead of emitting a JSON bundle

Another possible approach would be to change completely the way the VueSSRServerPlugin works to reduce the additional webpack configuration necessary. Here's how the webpack configuration would look like:

// webpack.config.js
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')

module.exports = {
  // Point entry to your app's server entry file
  entry: '/path/to/server-handler.js',
  plugins: [
    new VueSSRServerPlugin({
      clientManifest: '/path/to/dist/client/vue-ssr-client-manifest.json',
      serverEntry: '/path/to/server-entry.js',
      template: '/path/to/template.html'
    })
  ]
}

And here is how the server handler would look like:

// alias defined by the plugin, returns the renderer
import renderer from 'vue-ssr-server-renderer'

export function handler(event, context) {
  // use renderer here
}

The clear upside is the reduction of the boilerplate. The first big downside is that this would not be backwards compatible. It could be implemented as a new, different plugin. The second downside is that there might be other use cases I haven't considered. createBundleRenderer takes other arguments as well, and you might not want to instantiate it at the top level. A smaller downside is that some pieces are connected in webpack rather than in the code, making it unclear where the template is referenced from.

3. Use a loader to transform the server entry

This would be similar to the first proposal, but it would change the way createBundleRenderer works.

Starting from the server handler this time, here is how the usage would look like:

import App from '/path/to/server-entry.js'
import clientManifest from 'client-manifest';
// This function has the client manifest already pulled in by the plugin
import { createAppRenderer } from 'vue-server-renderer';

const template = `[...]`;

const renderer = createAppRenderer(app, {
  clientManifest,
  template,
  runInNewContext: false
});

export function handler(event, context) {
  // use renderer here
}

And the webpack configuration would be something like this:

// webpack.config.js

module.exports = {
  // Point entry to your app's server entry file
  entry: '/path/to/server-handler.js',
  module: {
    rules: [
      {
        test: /entry-server\.js$/,
        loader: 'vue-ssr-loader'
      }
    ]
  },
  resolve: {
    alias: {
      'client-manifest': '/path/to/dist/client/vue-ssr-client-manifest.json'
    }
  }
}

This approach would have the upside of not requiring magic to happen on the webpack entries, making it possible to have multiple entrypoints. The second upside would be to just configure a loader where appropriate, instead of adding a plugin. The first downside is that we still have to include the client manifest from the build. The second downside is that is could not be feasible with a webpack loader.

So which one?

I believe this problem needs a deeper reflection on the implications for all possible use cases, so someone with a better understanding of the usages of this plugin should trace the path to follow. Other ideas could be possible that would in the future also lead to simpler usages that hide the complexity from the users.

Kjir commented 5 years ago

I had to create this issue "manually" since using the tool I get 414 Request-URI Too Large 😭 There should be a feature request label, which I can't add myself apparently....

zwmmm commented 5 years ago

More than how to configure SSR entrance

CaptainYouz commented 4 years ago

any news on this one ? In my use case, I'm trying to set the runInNewContext from the vue.config.js, without having to create 2 webpack build files.