Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 服务端渲染的配置和原理 #24

Open
WangShuXian6 opened this issue Jun 11, 2018 · 4 comments
Open

Vue 服务端渲染的配置和原理 #24

WangShuXian6 opened this issue Jun 11, 2018 · 4 comments
Labels

Comments

@WangShuXian6
Copy link
Owner

WangShuXian6 commented Jun 11, 2018

服务端渲染的配置和原理

vue-server-render

服务端渲染出首屏页面html文件,不含js.
服务端将js插入渲染出的页面,返回客户端

./build/webpack.config.server.js

// ./build/webpack.config.server.js
// 服务端渲染配置

// 安装服务端渲染库 npm i vue-server-renderer -S
const VueServerPlugin = require('vue-server-render/server-plugin')

const path = require('path')
const ExtractPlugin = require('extract-text-webpack-plugin')
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')


// const defaultPluins = [
//     new webpack.DefinePlugin({
//         'process.env': {
//             NODE_ENV: '"development"'
//         }
//     })
// ]

const config = {
    target: 'node', // 服务端node环境
    entry: path.join(__dirname, '../client/server-entry.js'),
    devtool: 'source-map',
    output: {
        libraryTarget: 'commonjs2', // 模块类型 // module.exports=
        filename: 'server-entry.js', // 服务端无需缓存
        path: path.join(__dirname, '../server-build')
    },
    externals: Object.keys(require('../package.json').dependencies), // vue,vue-router,vuex // 排除服务端打包的文件,直接使用node_modules内的
    module: {
        // node环境没有css,不能将css插入DOM中。
        rules: [
            {
                test: /\.styl/,
                use: ExtractPlugin.extract({
                    fallback: 'vue-style-loader',
                    use: [
                        'css-loader',
                        {
                            loader: 'postcss-loader',
                            options: {
                                sourceMap: true,
                            }
                        },
                        'stylus-loader'
                    ]
                })
            },
        ]
    },
    plugins: [
        new ExtractPlugin('styles.[contentHash:8].css'),
        // new webpack.HotModuleReplacementPlugin(),
        // new webpack.NoEmitOnErrorsPlugin(),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
            'process.env.VUE_ENV': '"server"', // vue-server-render 会用到
        }),
        // new VueServerPlugin(), // 服务端渲染,输出json文件 vue-ssr-server-bundle.json
        new VueServerPlugin({
            filename: 'vue-ssr-server-bundle'
        })
    ],
}

// dependencies 应用运行时环境 -S
// devDependencies 应用开发时工具环境,运行时不需要 -D

module.exports = config

./build/webpack.config.client.js

// ./build/webpack.config.client.js

const path = require('path')
const HTMLPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const merge = require('webpack-merge')
const ExtractPlugin = require('extract-text-webpack-plugin')
const baseConfig = require('./webpack.config.base')
const VueClientPlugin = require('vue-server-renderer/client-plugin')

const defaultPlugins = [
    new webpack.DefinePlugin({
        'process.env': {
            NODE_ENV: isDev ? '"development"' : '"production"'
        }
    }),
    new HTMLPlugin({
        template: path.join(__dirname, 'template.html')
    }),
    new VueClientPlugin()
]

plugins: defaultPluins.concat([
    new ExtractPlugin('styles.[contentHash:8].css'),
    new webpack.optimize, CommonChunkPlugin({
        name: 'vendor'
    }),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'runtime'
    })
])

./server/server.js

// ./server/server.js
// npm i koa -S
// npm i koa-router -S 
// npm i axios -S 服务端也会使用
// npm i memory-fs -D 只在开发环境使用, 类似nodejs fs,扩展了fs.不会把文件写入磁盘,而是写入内存,节省时间,提高效率
// 只有 nodejs可以提供服务端渲染

const Koa = require('koa')

// npm i koa-send -S   // 帮助发送静态资源文件
const send = require('koa-send')

const path = require('path')

const pageRouter = require('./routers/dev-ssr')

const app = new Koa()

// 服务端渲染区分正式与开发环境
const isDev = process.env.NODE_ENV === 'development'

// 记录日志中间件
app.use(async (ctx, next) => {
    try {
        console.log(`request with path ${ctx.path}`)
        await next()
    } catch (error) {
        console.log(error)
        ctx.status = 500 // 服务器发生错误,返回500
        if (isDev) {
            // 开发环境 直接显示在页面上  提供给开发者的页面
            ctx.body = error.message
        } else {
            // 正式环境 提供给用户的优化后的页面
            ctx.body = 'please try again later'
        }
    }
})

// 处理favicon.ico资源
app.use(async (ctx, next) => {
    if (ctx.path === '/favicon.ico') {
        await send(ctx, '/favicon.ico', { root: path.join(__dirname, '../') })
    } else {
        await next()
    }
})

app.use(pageRouter.routes())
    .use(pageRouter.allowedMethods())

const HOST = process.env.HOST || '0.0.0.0'
const PORT = process.env.PORT || 3333

app.listen(PORT, HOST, () => {
    console.log(`server is listening on ${HOST}:${PORT}`)
})

./nodemon.json

// ./nodemon.json
// 有修改时自动重启服务
// npm i nodemon -D
{
    "restartable": "rs", // 输入rs命令重启服务
    "ignore": [
        // 忽略对某些文件修改操作的监听 // 配置文件
        ".git",
        "node_modules/**/node_modules",
        ".eslintrc",
        "client",
        "build/webpack.config.client.js",
        "public"
    ],
    "verbose": true,
    "env": {
        "NODE_ENV": "development"
    },
    "ext": "js json ejs", // 监听的文件 
}

./server/routers/dev-ssr.js

// ./server/routers/dev-ssr.js
// 处理开发环境的服务端渲染

// 旧版nodejs 不支持import,服务端不需babel编译,直接使用require
const Router = require('koa-router')
const axios = require('axios')
const MemoryFS = require('memory-fs')
const path = require('path')
const fs = require('fs')

const webpack = require('webpack')
const VueServerRenderer = require('vue-server-renderer') // 服务端渲染

const serverRender = require('./server-render')
const serverConfig = require('../../build/webpack.config.server')

const serverCompiler = webpack(serverConfig) //在nodejs中使用webpack编译文件,生成服务端渲染需要的bundle

const mfs = new MemoryFS()
serverCompiler.outputFileSystem = mfs // 指定输出目录在mfs

let bundle // 记录每次生成的打包文件

// 每次文件变化时
serverCompiler.watch({}, (error, states) => {
    // 处理错误
    if (error) throw error
    states = states.toJson()

    // 非打包错误,例如eslint错误
    states.errors.forEach(error => console.log(error))
    states.warnings.forEach(warn => console.log(warn))

    // 读取bundle
    const bundlePath = path.join(
        serverConfig.output.path,
        'vue-ssr-server-bundle.json'
    )

    // bundle=mfs.readFileSync(bundlePath,'utf-8') // 返回字符串
    bundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8')) // 转成json
    console.log('new bundle')
})

const handleSSR = async (ctx) => {
    if (!bundle) {
        // 服务器刚刚启动,bundle尚未准备好
        ctx.body = 'wait!!!'
        return
    }

    // 获取webpack打包的js包,用于插入模版
    const clientManifestResp = await axios.get(
        'http://127.0.0.1:8000/public/vue-ssr-client-manifest.json'
    )

    // 实际需要的内容
    const clientManifest = clientManifestResp.data

    // 读取ejs模版文件
    const template = fs.readFileSync(
        path.join(__dirname, '../server.template.ejs'),
        'utf-8'
    )

    const renderer = VueServerRenderer.createBundleRenderer(bundle, {
        inject: false, //取消 vue默认注入
        clientManifest, // 自动生成带script标签的字符串
    })

    await serverRender(ctx, renderer, template)
}

const router = new Router()
router.get('*', handleSSR) // 所有请求都通过handleSSR处理

module.exports = router

./server/server.template.ejs

// ./server/server.template.ejs
// 帮助生成服务端渲染文件的模版
// npm i ejs -S

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>

    // <%= %> 会转义特殊字符
    <%- style %> // 不转译
</head>
<body>
    <div id="root">
        <%- appString %>
    </div>
    
    <%- scripts %>
</body>
</html>
// ./server/routers/ssr.js
// 处理正式环境的服务端渲染

./server/router/server-render.js

// ./server/router/server-render.js
// 服务端渲染

const ejs = require('ejs')

module.exports = async (ctx, renderer, template) => {
    ctx.headers['Content-Type'] = 'text/html' // 服务端渲染最终返回html页面

    const context = { url: ctx.path } // 用于传入vue-server-renderer里

    try {
        const appString = await renderer.renderToString(context)

        // 渲染
        const html = ejs.render(template, {
            appString,
            style: context.renderStyles(), // 带style标签的完整字符串
            scripts: context.renderScripts(),
        })

        ctx.body = html // 返回完整页面
    } catch (error) {
        console.log('render error:', error)
        throw error
    }
}

./clent/server-entry.js

// ./clent/server-entry.js
// 服务端渲染入口文件

import createApp from './create-app'

export default context => {
    return new Promise((resolve, reject) => {
        const { app, router, store } = createApp()

        router.push(context.url) // 服务端环境主动跳转页面,加载组件

        router.onReady(() => {
            // router.onReady一般在服务端渲染用到
            // router.push()后,等到所有异步操作执行完成才会调用本回调函数
            // 开始获取数据

            // 根据 context.url 匹配到响应组件
            const matchedComponents = router.getMatchedComponents()
            if (!matchedComponents.length) {
                // 没有匹配到组件,返回错误,并结束
                return reject(new Error('no component matched'))
            }
            resolve(app)
        })
    })
}

package.json

// package.json
{
    "script": {
        "dev:client": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.client.js",
        // "dev:server": "cross-env NODE_ENV=development node server/server.js"
        "dev:server": "nodemon server/server.js", // 启动后,有修改自动重启服务
        "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\" "
    }
}
// 开发时   npm run dev:server     ,npm run dev:client
// 访问 loaclhost:3333 服务端渲染
// npm i concurrently -D   // 一次启动两个服务,接受字符串参数

./create-app.js

// ./create-app.js

// 每次服务端渲染,会渲染一个新的app
// 如果单例渲染,会包含上次渲染的状态

import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'

import App from './app.vue'
import createStore from './store/store'
import createRouter from './config/router'

import './assets/styles/global.styl'

Vue.use(VueRouter)
Vue.use(Vuex)

// 每次调用都返回一个新对象,防止内存溢出
export default () => {
    const router = createRouter()
    const store = createStore()

    const app = new Vue({
        router,
        store,
        render: h => h(App)
    })

    return { app, router, store }
}

@WangShuXian6
Copy link
Owner Author

webpack.base.conf.js

// weback 通用配置

'use strict'
const path = require('path')
// utils主要用来处理css-loader和vue-style-loader的
const utils = require('./utils')
const config = require('../config')
// vue-loader.conf配置文件是用来解决各种css文件的,定义了诸如css,less,sass之类的和样式有关的loader
const vueLoaderConfig = require('./vue-loader.conf')
var webpack = require("webpack")

// 此函数是用来返回当前目录的平行目录的路径,因为有个'..'
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

const createLintingRule = () => ({
  // 检测文件后缀名
  test: /\.(js|vue)$/,
  loader: 'eslint-loader',
  // 加载之前进行预处理
  enforce: 'pre',
  include: [resolve('src'), resolve('test')],
  options: {
    formatter: require('eslint-friendly-formatter'),
    emitWarning: !config.dev.showEslintErrorsInOverlay
  }
})

// 导出
module.exports = {
  context: path.resolve(__dirname, '../'),
  // 定义入口文件
  entry: {
    app: './src/main.js'
  },
  target: 'web',
  // 定义输出相关
  output: {
    // 路径是config目录下的index.js中的build配置中的assetsRoot,也就是dist目录
    path: config.build.assetsRoot,
    // 文件名称这里使用默认的name也就是main
    filename: '[name].js',
    // 上线地址,也就是真正的文件引用路径,如果是production生产环境,其实这里都是 '/'
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  // 定义省略规则
  // resolve是webpack的内置选项,顾名思义,决定要做的事情,也就是说当使用 import "jquery",该如何去执行这件事情就是resolve配置项要做的,import jQuery from "./additional/dist/js/jquery" 这样会很麻烦,可以起个别名简化操作
  resolve: {
    extensions: ['.js', '.vue', '.json', '.css'],
    alias: {
      // 后面的$符号指精确匹配,也就是说只能使用 import vuejs from "vue" 这样的方式导入vue.esm.js文件,不能在后面跟上 vue/vue.js
      'vue$': 'vue/dist/vue.esm.js',
      // resolve('src') 其实在这里就是项目根目录中的src目录,使用 import somejs from "@/some.js" 就可以导入指定文件,是不是很高大上
      '@': resolve('src'),
    }
  },
  // module用来解析不同的模块 test 正则匹配,loader 处理模块,enforce 编译之前的处理,include 指定处理目录,options loader的一些参数
  module: {
    rules: [
      // 在开发环境下 对于以.js或.vue后缀结尾的文件(在src目录下或test目录下的文件),使用eslint进行文件语法检测。
      ...(config.dev.useEslint ? [createLintingRule()] : []),
      {
        // 对vue文件使用vue-loader,该loader是vue单文件组件的实现核心,专门用来解析.vue文件的
        test: /\.vue$/,
        loader: 'vue-loader',
        // 将vueLoaderConfig当做参数传递给vue-loader,就可以解析文件中的css相关文件
        options: vueLoaderConfig
      },
      {
        // 对jsx文件使用babel-loader转码
        test: /\.jsx$/,
        loader: 'babel-loader'
      },
      {
        // 对js文件使用babel-loader转码,该插件是用来解析es6等代码
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test')]
      },
      {
        // 对图片相关的文件使用 url-loader 插件,这个插件的作用是将一个足够小的文件生成一个64位的DataURL
        // 可能有些老铁还不知道 DataURL 是啥,当一个图片足够小,为了避免单独请求可以把图片的二进制代码变成64位的
        // DataURL,使用src加载,也就是把图片当成一串代码,避免请求,神不神奇??
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          // 限制 10000 个字节一下的图片才使用DataURL
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        // 音频文件后缀
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          // 小于10000字节时的时候处理
          limit: 10000,
          // 文件名为name.7位hash的
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        // 字体文件处理,和上面一样
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin('common.js'),
    new webpack.ProvidePlugin({
      jQuery: "jquery",
      $: "jquery"
    })
  ]
}

Uploading 第二章结束的代码-webpack3.zip…
Uploading 第二章结束的代码-webpack4.zip…
Uploading 免费课代码.zip…
Uploading OReilly Learning HTTP2.pdf…

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented Jun 11, 2018

使用vue-meta处理元信息

npm i vue-meta -S

./clent/server-entry.js

// ./clent/server-entry.js
// 服务端渲染入口文件

import createApp from './create-app'

export default context => {
    return new Promise((resolve, reject) => {
        const { app, router, store } = createApp()

        router.push(context.url) // 服务端环境主动跳转页面,加载组件

        router.onReady(() => {
            // router.onReady一般在服务端渲染用到
            // router.push()后,等到所有异步操作执行完成才会调用本回调函数
            // 开始获取数据

            // 根据 context.url 匹配到响应组件
            const matchedComponents = router.getMatchedComponents()
            if (!matchedComponents.length) {
                // 没有匹配到组件,返回错误,并结束
                return reject(new Error('no component matched'))
            }

            // 服务端渲染的时候使用vue-meta的方式
            context.meta = app.$meta()

            resolve(app)
        })
    })
}

./server/router/server-render.js

// ./server/router/server-render.js
// 服务端渲染

const ejs = require('ejs')

module.exports = async (ctx, renderer, template) => {
    ctx.headers['Content-Type'] = 'text/html' // 服务端渲染最终返回html页面

    const context = { url: ctx.path } // 用于传入vue-server-renderer里

    try {
        const appString = await renderer.renderToString(context)

        // 从context获取meta
        const { title } = context.meta.inject()

        // 渲染
        const html = ejs.render(template, {
            appString,
            style: context.renderStyles(), // 带style标签的完整字符串
            scripts: context.renderScripts(),
            title: title.text(), // 带标签
        })

        ctx.body = html // 返回完整页面
    } catch (error) {
        console.log('render error:', error)
        throw error
    }
}

./server/server.template.ejs

// ./server/server.template.ejs
// 帮助生成服务端渲染文件的模版
// npm i ejs -S

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    // <title>Document</title>
    <%- title %>

    // <%= %> 会转义特殊字符
    <%- style %> // 不转译
</head>
<body>
    <div id="root">
        <%- appString %>
    </div>
    
    <%- scripts %>
</body>
</html>

./client/client-entry.js

// ./client/client-entry.js
// 服务端渲染的客户端入口

import createApp from './create-app'

const { app, router } = createApp()

// 路由就绪后渲染
router.onReady(() => {
    app.$mount('#root')
})

App.vue

// App.vue
<template>
    <div>
        <p>{{ textA }}</p>
        <p>{{ textPlus }}</p>
    </div>
</template>

<script>
    // Actions,Mutations 帮助函数
    import {
        mapState,
        mapGetters,
        mapActions, // 对应异步操作
        mapMutations // 对应同步操作
    } from 'vuex'
    
    export default {
        metaInfo: {
            title: 'wang \'s App', // 进入页面时vue-meta会自动修改页面title值,下级组件的meta 会覆盖上级
        },
    
        methods: {
            ...mapActions(['updateCountAsync', 'a/add', 'textAction']), // b模块未声明命名空间,textAction无需写模块名
            ...mapMutations(['updateCount', 'a/updateText']), // 帮助函数声明模块化的 updateText mutation
            // Vuex 默认会将全部mutation,包括模块化的,放入全局mutations
            // 启用命名空间后,调用模块内的mutations --- a/updateText
        },
    
        mounted() {
            this['a/add']()
    
            this.textAction() // b模块未声明命名空间
        },
    
        computed: {
            // 模块后,带命名空间的调用
            // textA(){
            //     return this.$store.state.a.text
            // }
    
            // 使用帮助函数调用模块化的 state
            ...mapState({
                textA: state => state.a.text, // 必须使用方法返回
                textC: state => state.c.text, // 使用动态注册的模块c
            }),
    
            // 使用帮助函数调用模块化的 启用命名空间的 // 'a/textPlus'
            // 'a/textPlus' 无法在模版中直接使用
            // ...mapGetters(['fullName', 'a/textPlus'])
    
            // 使用帮助函数 重命名getters
            ...mapGetters({
                'fullName': 'fullName', // 全局的getter
                'textPlus': 'a/textPlus', // a模块启用命名空间的getter - 'a/textPlus' 重命名为 'textPlus',可在模版使用
            })
        }
    }
</script>

@WangShuXian6
Copy link
Owner Author

WangShuXian6 commented Jun 13, 2018

生产环境服务端渲染

./server/routers/static.js

// ./server/routers/static.js
// 处理静态路径的中间件

const Router = require('koa-router')
const send = require('koa-send')

const staticRouter = new Router({ prefix: '/public' }) // staticRouter 只处理 '/public' 开头的路径 

staticRouter.get('/*', async ctx => {
    await send(ctx, ctx.path)
})

module.exports = staticRouter

./server/server.js

// ./server/server.js
// npm i koa -S
// npm i koa-router -S 
// npm i axios -S 服务端也会使用
// npm i memory-fs -D 只在开发环境使用, 类似nodejs fs,扩展了fs.不会把文件写入磁盘,而是写入内存,节省时间,提高效率
// 只有 nodejs可以提供服务端渲染

const Koa = require('koa')

// npm i koa-send -S   // 帮助发送静态资源文件
const send = require('koa-send')

const path = require('path')

const staticRouter = require('./routers/static')

//const pageRouter = require('./routers/dev-ssr')

const app = new Koa()

// 服务端渲染区分正式与开发环境
const isDev = process.env.NODE_ENV === 'development'

// 记录日志中间件
app.use(async (ctx, next) => {
    try {
        console.log(`request with path ${ctx.path}`)
        await next()
    } catch (error) {
        console.log(error)
        ctx.status = 500 // 服务器发生错误,返回500
        if (isDev) {
            // 开发环境 直接显示在页面上  提供给开发者的页面
            ctx.body = error.message
        } else {
            // 正式环境 提供给用户的优化后的页面
            ctx.body = 'please try again later'
        }
    }
})

// 处理favicon.ico资源
app.use(async (ctx, next) => {
    if (ctx.path === '/favicon.ico') {
        await send(ctx, '/favicon.ico', { root: path.join(__dirname, '../') })
    } else {
        await next()
    }
})

app.use(staticRouter.routes())
    .use(staticRouter.allowedMethods())

let pageRouter
if (isDev) {
    pageRouter = require('./routers/dev-ssr')
} else {
    pageRouter = require('./routers/ssr')
}

app.use(pageRouter.routes())
    .use(pageRouter.allowedMethods())

const HOST = process.env.HOST || '0.0.0.0'
const PORT = process.env.PORT || 3333

app.listen(PORT, HOST, () => {
    console.log(`server is listening on ${HOST}:${PORT}`)
})

./server/routers/ssr.js

// ./server/routers/ssr.js
// 处理正式环境的服务端渲染

const Router = require('koa-router')
const path = require('path')
const fs = require('fs')
const serverRender = require('./server-render')
const VueServerRender = require('./vue-server-render')

const clientManifest = require('../../public/vue-ssr-client-manifest.json')

const renderer = VueServerRender.createBundleRenderer(
    path.join(__dirname, '../../server-build/vue-ssr-server-bundle.json'),
    {
        inject: false,
        clientManifest
    }
)

const template = fs.readFileSync(
    path.join(__dirname, '../server.template.ejs'), 'utf-8'
)

const pageRouter = new Router()

pageRouter.get('*', async (ctx) => {
    await serverRender(ctx, renderer, template)
})

module.exports = pageRouter

package.json

// package.json
{
    "script": {
        "dev:client": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.client.js",
        // "dev:server": "cross-env NODE_ENV=development node server/server.js"
        "dev:server": "nodemon server/server.js", // 启动后,有修改自动重启服务
        "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\" ",
        "build:client": "cross-env NODE_ENV=production webpack --config build/webpack.config.client.js",
        "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.config.server.js", // 服务端渲染
        "build": "npm run clean && npm run build:client && npm run build:server",
        "clean": "rimraf public && rimraf server-build",
        "start": "cross-env NODE_ENV=production node server/server.js", // 启动服务器
    }
}
// 开发时   npm run dev:server     ,npm run dev:client
// 访问 loaclhost:3333 服务端渲染
// npm i concurrently -D   // 一次启动两个服务,接受字符串参数

./build/webpack.config.base.js

// ./build/webpack.config.base.js

const config = {
    target: 'web',
    // entry: path.join(__dirname, 'client/index.js'),
    entry: path.join(__dirname, 'client/client-entry.js'), // 服务端渲染入口
    output: {
        filename: 'bundle.[hash:8].js',
        path: path.join(__dirname, '../public'),
        publicPath: 'http://127.0.0.1:8000/public/' // 指定整个静态资源的路径
    }
}

@WangShuXian6
Copy link
Owner Author

Vue服务端渲染 — Vue.js

https://ssr.vuejs.org/zh/

@WangShuXian6 WangShuXian6 mentioned this issue Jul 15, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant