vue-server-renderer實現vue項目改造服務端渲染

這是一篇教程,從建立項目到改造項目css

vue-cli建立一個項目

在放你作demo的地方,建立一個項目html

vue create vue-ssr
// 若是你安裝了vue-cli4,選擇vue2的版本,如下的改進過程是按vue2來作的

通過漫長的等待,下載好文件開始咱們的改造之路前端

文件目錄

進入vue-ssr文件夾,使用命令vue

vue ui

把vue-router裝上
image.pngnode

先安裝幾個依賴插件webpack

// 安不上用cnpm,yarn,npx
npm i vue-server-renderer express -D

npm install webpack-node-externals lodash.merge -D

npm i cross-env -D

修改package.json文件

"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
}
改爲
"scripts": {
    "build:client": "vue-cli-service build", 
    "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server", 
    "build": "npm run build:server && npm run build:client" 
}

根目錄下建立vue.config.js

// 服務器渲染的兩個插件,控制server和client
const VueSSRServerPlugin = require("vue-server-renderer/server-plugin"); // 生成服務端包
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin"); // 生成客戶端包

const nodeExternals = require("webpack-node-externals");
const merge = require("lodash.merge");

// 環境變量:決定入口是客戶端仍是服務端,WEBPACK_TARGET在啓動項中設置的,見package.json文件
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";

module.exports = {
  css: {
    extract: false
  },
  outputDir: "./dist/" + target,
  configureWebpack: () => ({
    // 將 entry 指向應用程序的 server / client 文件
    entry: `./src/entry-${target}.js`,
    // 對 bundle renderer 提供 source map 支持
    devtool: "source-map",
    // 這容許 webpack 以 Node 適用方式處理動態導入(dynamic import),
    // 而且還會在編譯 Vue 組件時告知 `vue-loader` 輸送面向服務器代碼(server-oriented code)。
    target: TARGET_NODE ? "node" : "web",
    node: TARGET_NODE ? undefined : false,
    output: {
      // 此處配置服務器端使用node的風格構建
      libraryTarget: TARGET_NODE ? "commonjs2" : undefined
    },
    // 外置化應用程序依賴模塊。能夠使服務器構建速度更快,並生成較小的 bundle 文件。
    externals: TARGET_NODE
      ? nodeExternals({
          // 不要外置化 webpack 須要處理的依賴模塊。
          // 能夠在這裏添加更多的文件類型。例如,未處理 *.vue 原始文件,
          // 你還應該將修改 `global`(例如 polyfill)的依賴模塊列入白名單(之前叫whitelist,爲了不美國的人種歧視,改爲了allowlist)
          allowlist: [/\.css$/]
        })
      : undefined,
    optimization: {
      splitChunks: TARGET_NODE ? false : undefined
    },
    // 這是將服務器的整個輸出構建爲單個 JSON 文件的插件。
    // 服務端默認文件名爲 `vue-ssr-server-bundle.json`
    // 客戶端默認文件名爲 `vue-ssr-client-manifest.json`
    plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
  }),
  chainWebpack: config => {
    config.module
      .rule("vue")
      .use("vue-loader")
      .tap(options => {
        merge(options, {
          optimizeSSR: false
        });
      });
  }
};

修改路由文件

import Vue from 'vue';
import Router from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';

Vue.use(Router);

// 這裏爲何不導出一個router實例?
// 每次用戶請求都須要建立新router實例,若是用戶請求屢次都用一個實例會形成數據污染
export function createRouter() {
    return new Router({
        // 必定要history模式,由於,hash模式更改路徑不會刷新,具體緣由自行查詢
        mode: 'history',
        routes: [
            {path: '/', name: 'Home',component: Home},
            {path: '/about', name: 'About', component: About},
        ]
    })
}

修改main.js文件

import Vue from "vue";
import App from "./App.vue";
import { createRouter } from "./router";
import store from "./store";

Vue.config.productionTip = false;

const router = createRouter();

// 這裏的掛載($mount("#app"))放到entry-client.js文件裏面,後面會說到
export function createApp() {
    const app = new Vue({
        router,
        store,
        render: (h) => h(App),
    });
    return { app, router };
}

在src下添加entry-client.js和entry-server.js文件

entry-client.js

import {createApp} from './main.js';

const {app, router} = createApp();

router.onReady(()=>{
    app.$mount("#app");
})

entry-server.js

import {createApp} from "./main.js";
// context實際上就是server/index.js裏面傳參,後面會說到server/index.js
export default context => {
    return new Promise((resolve, reject) => {
        const {app, router} = createApp();
        router.push(context.url)
        router.onReady(()=>{
            // 是否匹配到咱們要用的組件
            const matchs = router.getMatchedComponents();
            if(!matchs) {
                return reject({code: 404})
            }
            resolve(app);
        }, reject);
    })
}

在src下建立server/index.js

// nodejs服務器
const express = require("express");
const fs = require("fs");

// 建立express實例和vue實例
const app = express();

// 建立渲染器
const { createBundleRenderer } = require("vue-server-renderer");
const serverBundle = require("../../dist/server/vue-ssr-server-bundle.json");
const clientManifest = require("../../dist/client/vue-ssr-client-manifest.json");
// 這兒引入的文件是不一樣於index.html的問題,具體文件下面會講到
const template = fs.readFileSync("../../public/index.ssr.html", "utf-8"); // 宿主模板文件
const renderer = createBundleRenderer(serverBundle, {
    runInNewContext: false,
    template,
    clientManifest,
});

// 中間件處理靜態文件請求
app.use(express.static("../../dist/client", { index: false })); // 爲false是不讓它渲染成dist/client/index.html
// app.use(express.static('../dist/client'))

// 前端請求什麼我都不關心,全部的路由處理交給vue
app.get("*", async (req, res) => {
    try {
        const context = {
            url: req.url,
            title: "ssr test",
        };
        // nodejs流數據,文件太大,用renderToString會卡
        const stream = renderer.renderToStream(context);
        let buffer = [];
        stream.on("data", (chunk) => {
            buffer.push(chunk);
        });
        stream.on("end", () => {
            res.end(Buffer.concat(buffer));
        });
    } catch (error) {
        console.log(error);
        res.status(500).send("服務器內部錯誤");
    }
});

app.listen(3000, () => {
    console.log("渲染服務器啓動成功");
});

在public下面建立index.ssr.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!--vue-ssr-outlet-->
    <!--上面這個必定要留着,它是服務端渲染模版的標記,沒有就會報錯,不信能夠刪了試一下-->
</body>
</html>

而後全部的改造完成,運行命令

// 先構建兩個json文件
npm run build

再到server文件夾下運行

node index.js
// 若是顯示: `渲染服務器啓動成功`, 在瀏覽器打開 `localhost:3000` 端口,就能看到咱們的頁面

整完這,你再去玩兒nuxt,你感受好多了,由於nuxt不用配路由,本身生成,連路由傳參都設定好了web

相關文章
相關標籤/搜索