服务端渲染模式需要对同一份 vue 文件构建出两份 JSBundle 文件出来,一份给 Node 渲染使用,一份给浏览器渲染使用,但 Node 和浏览器文件初始化代码是不一样的,这就需要我们针对入口代码进行分别实现。这里提供三种实现方案,请根据项目需要选择合适的方案。

方案一: 完全自定义入口代码逻辑

这里仅提供代码基本实现,请根据项目实际情况进行修改。

  • 编写 vue 服务端公共入口 ${app_root}/app/web/framework/app.js
import Vue from 'vue';
import { sync } from 'vuex-router-sync';
import './vue/filter';
import './vue/directive';

export default class App {
  constructor(config) {
    this.config = config;
  }

  bootstrap() {
    if (EASY_ENV_IS_NODE) {
      return this.server();
    }
    return this.client();
  }

  create(initState) {
    const { index, options, createStore, createRouter } = this.config;
    const store = createStore(initState);
    const router = createRouter();
    sync(store, router);
    return {
      ...index,
      ...options,
      router,
      store
    };
  }

  client() {
    Vue.prototype.$http = require('axios');
    const options = this.create(window.__INITIAL_STATE__);
    const app = new Vue(options);
    app.$mount('#app');
    return app;
  }

  server() {
    return context => {
      const options = this.create();
      const { store, router } = options;
      router.push(context.state.url);
      return new Promise((resolve, reject) => {
        router.onReady(() => {
          const matchedComponents = router.getMatchedComponents();
          if (!matchedComponents) {
            return reject({ code: '404' });
          }
          return Promise.all(
            matchedComponents.map(component => {
              if (component.methods && component.methods.fetchApi) {
                  return component.methods.fetchApi(store);
              }
              return null;
            })
          ).then(() => {
            context.state = {
              ...store.state,
              ...context.state
            };
            return resolve(new Vue(options));
          });
        });
      });
    };
  }
  • 新建 ${app_root}/app/web/page/home/home.js webpack entry 入口文件

这种方式适合需要自定义实现入口代码的比较少页面入口项目,如果项目有多个单独页面,就需要编写下面类似的重复代码,但通过公共代码抽离,问题也不是太大,能满足所有自定义要求,这个完全交给项目自己去实现。


'use strict';
import App from 'framework/app.js';
import index from './index.vue';
import createStore from './store';
import createRouter from './router';

const options = { base: '/' };

export default new App({
  index,
  options,
  createStore,
  createRouter,
}).bootstrap();

详细实现请见:https://github.com/easy-team/egg-vue-webpack-boilerplate/tree/feature/green/spa

方案二: 自定义入口代码模板化

easywebpack 提供了通过 配置 entry.loader 实现入口代码模板化,并且代码模板完全有项目自己实现. 项目只需要实现对应的 loader 即可。这里仅提供代码基本实现,请根据实际项目情况进行修改。

  • 编写 webpack 服务端模式构建 loader 代码 ${app_root}/app/web/framework/vue/entry/server-loader.js
'use strict';
module.exports = function(source) {
  this.cacheable();
  return `
    import Vue from 'vue';
    import vm from '${this.resourcePath.replace(/\\/g, '\\\\')}';
    export default function(context) {
      const store = typeof vm.store === 'function' ? vm.store(context.state) : vm.store;
      const router = typeof vm.router === 'function' ? vm.router() : vm.router;
      if (store && router) {
        const sync = require('vuex-router-sync').sync;
        sync(store, router);
        router.push(context.state.url);
        return new Promise((resolve, reject) => {
          router.onReady(() => {
            const matchedComponents = router.getMatchedComponents();
            if (!matchedComponents) {
              return reject({ code: '404' });
            }
            return Promise.all(
              matchedComponents.map(component => {
                if (component.methods && component.methods.fetchApi) {
                  return component.methods.fetchApi(store);
                }
                return null;
              })
            ).then(() => {
              context.state = { ...store.state, ...context.state };
              const instanceOptions = {
                ...vm,
                store,
                router,
              };
              return resolve(new Vue(instanceOptions));
            });
          });
        });
      }
      const VueApp = Vue.extend(vm);
      const instanceOptions = {
        ...vm,
        data: context.state
      };
      return new VueApp(instanceOptions);
    };
  `;
};
  • 编写 webpack 浏览器模式构建 loader 代码 ${app_root}/app/web/framework/vue/entry/client-loader.js
'use strict';
module.exports = function(source) {
  return `
    import Vue from 'vue';
    import vm from '${this.resourcePath.replace(/\\/g, '\\\\')}';
    const initState = window.__INITIAL_STATE__ || {};
    const context = { state: initState };
    const store = typeof vm.store === 'function' ? vm.store(initState) : vm.store;
    const router = typeof vm.router === 'function' ? vm.router() : vm.router;
    const data = typeof vm.data === 'function' ? vm.data() :  {};
    const options = store && router ? {
      ...vm, 
      store,
      router
    } : { 
      ...vm,
      ...{
        data() {
          return { ...initState, ...data};
        }
      } 
    };
    const app = new Vue(options);
    app.$mount('#app');
  `;
};
  • Webpack entry loader 配置, 这样就不用写单独的 js 入口文件, vue 文件作为 entry 就可以直接构建出完整的 JSBundle 文件。easywebpack 直接根据 include 目录下的 vue 文件 和 entry loader 构建出完整的 JSBundle 文件。
'use strict';
module.exports = {
  egg: true,
  framework: 'vue',
  entry: {
    include: ['app/web/page'],
    exclude: ['app/web/page/[a-z]+/component', 'app/web/page/test'],
    loader: {
      client: 'app/web/framework/vue/entry/client-loader.js',
      server: 'app/web/framework/vue/entry/server-loader.js',
    }
  }
 }

具体例子可以参考:https://github.com/easy-team/egg-vue-webpack-boilerplate/tree/feature/green/multi

方案三: 使用框架内置初始化模板loader

easywebpack@4.8.0 开始支持默认 entry node-glob 配置模式。node-glob 模式会自动遍历 app/web/page 目录的所有 .vue 文件作为 entry 入口,排除 component|components|view|views 目录下的文件。 如果 entry 是已 .vue 文件,则使用 vue-entry-loader 作为模板入口。

注意:只有 entry 文件是 .vue 文件(非.js)时,才会自动使用 **vue-entry-loader 模板。**

  • 统一使用 .vue 文件作为 entry 入口
// webpack.config.js
module.exports = {
  // 注意 只有 entry 文件是 .vue 文件(非.js)时,才会自动使用 vue-entry-loader模板
  entry: 'app/web/page/**!(component|components|view|views)/*.vue'
}
  • js 和 .vue 文件 entry 混合配置
module.exports = {
  entry: {
    app: 'app/web/page/app/index.js', // js 文件需要自己实现初始化逻辑,这个时候可以结合方案一
    list: 'app/web/page/list/index.vue' // 自动使用 vue-entry-loader模板
  }
};

entry 更多配置

/easywebpack/entry


Author: sky
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source sky !
 Previous
Service Worker Service Worker
Egg + Vue/React SSR 使用 service workereasywebpack 默认生成的 service-worker.js 是在 ${app_root}/public/service-worker.js这里. 这样 service-worker.js 访问路径是 http://127.0.0.1:7001/public/service-worker.js。将 service worker 文件注册为 /public/service-worker.js,那么,service...
2020-05-31 sky
Next 
渲染模式 渲染模式
Egg + Vue 渲染模式目前 egg-view-vue-ssr 支持 服务端渲染模式 和 前端渲染模式 两种渲染模式Egg + Vue 服务端 Node 渲染模式这里服务端渲染指的是编写的 Vue 组件在 Node 服务端直接编译成完整的HTML, 然后直接输出给浏览器。MVVM 服务端渲染相比前端渲染,支持SEO,更快的首屏渲染,相比传统的模板引擎,更好的组件化,前后端模板共用。 同时 MVVM 数据驱动方式有着更快的开发效率。总体来说,MVVM 框架的服务端渲染技术比较适合有一定交互性,且对SEO...
2020-05-31 sky