這是一篇教程,從建立項目到改造項目css
在放你作demo的地方,建立一個項目html
vue create vue-ssr // 若是你安裝了vue-cli4,選擇vue2的版本,如下的改進過程是按vue2來作的
通過漫長的等待,下載好文件開始咱們的改造之路前端
進入vue-ssr文件夾,使用命令vue
vue ui
把vue-router裝上
node
先安裝幾個依賴插件webpack
// 安不上用cnpm,yarn,npx npm i vue-server-renderer express -D npm install webpack-node-externals lodash.merge -D npm i cross-env -D
"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" }
// 服務器渲染的兩個插件,控制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}, ] }) }
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 }; }
import {createApp} from './main.js'; const {app, router} = createApp(); router.onReady(()=>{ app.$mount("#app"); })
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); }) }
// 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("渲染服務器啓動成功"); });
<!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
node index.js // 若是顯示: `渲染服務器啓動成功`, 在瀏覽器打開 `localhost:3000` 端口,就能看到咱們的頁面
整完這,你再去玩兒nuxt,你感受好多了,由於nuxt不用配路由,本身生成,連路由傳參都設定好了web