在前端开发中,为了更好的SEO和更快的首次渲染速度,服务端渲染逐渐成为了前端开发的趋势。而在本文中,我们将分享一些实战经验,来帮助大家使用Koa2搭建Vue服务端渲染应用。
内容概述
在本文中,我们将探讨以下内容:
- Vue的SSR模式与CSR模式的区别:我们将介绍什么是SSR模式和CSR模式,以及二者之间的核心区别。
- 使用Koa2构建Vue服务端渲染应用:我们将介绍如何使用Koa2来构建一个Vue服务端渲染应用,从包含WebPack的开发环境搭建到实际应用的构建和部署。
- 实践经验分享:我们将分享一些构建Vue服务端渲染应用的实践经验以及解决方案,从而帮助大家更好地了解如何构建出高质量的Vue服务端渲染应用。
Vue的SSR模式与CSR模式的区别
首先,让我们来探讨一下Vue的SSR模式和CSR模式之间的区别。
CSR模式
客户端渲染(Client-Side Rendering,缩写为CSR)是一种常见的前端渲染模式。在传统的CSR模式中,首先浏览器会发送一个HTML文件请求给后端服务器,后端服务器会返回该HTML文件及其相应的CSS和JS文件。之后,浏览器再通过这些CSS和JS文件来生成和渲染网页。
在CSR模式中,由于所有渲染过程都在用户的浏览器中进行,通常需要在网络传输和浏览器解析时间上消耗大量的时间,等待时间较长。对于搜索引擎爬虫而言,获取渲染后的内容也存在困难。
SSR模式
服务器渲染(Server-Side Rendering,缩写为SSR)是一种新兴的前端渲染模式。由于该模式对搜索引擎爬虫更为友好,让搜索引擎能够更好地抓取到我们的页面内容,因此目前也越来越受到前端开发者的青睐。
在SSR模式中,渲染过程会在服务器上完成,并将最终生成的HTML文件发送到用户浏览器中。这样在浏览器中的加载和解析时间将大大缩短,同时也节省了网络传输时间。
但是,SSR模式在服务器端渲染时通常需要更多的计算和I/O操作,此外还需要调整一些组件代码以适应SSR模式。
使用Koa2构建Vue服务端渲染应用
接下来,我们将介绍如何使用Koa2搭建Vue服务端渲染应用。
开发环境搭建
- 安装Node.js
在开始搭建我们的Vue服务端渲染应用之前,我们需要先安装Node.js。Node.js是基于V8引擎的,它能够使JavaScript在服务器端运行,也是使用Vue进行SSR构建的必备环境。
- 初始化Koa2项目
我们先通过npm
命令初始化一个Koa2项目:
$ npm init -y
- 安装依赖
安装所有依赖的npm包:
$ npm install koa koa-static koa-router koa-bodyparser koa-compress vuex vue-server-renderer vue-router babel-core babel-preset-env webpack webpack-dev-middleware webpack-hot-middleware babel-loader vue-loader vue-template-compiler cross-env nodemon rimraf --save
其中,主要的包含:
koa
和koa-static
:用于处理HTTP请求和静态文件koa-router
:用于处理HTTP路由koa-bodyparser
:用于解析HTTP请求的bodykoa-compress
:用于压缩HTTP的响应数据vuex
:Vue的状态管理库vue-server-renderer
:将Vue组件渲染成HTML的工具vue-router
:Vue的路由管理库babel-core
、babel-preset-env
和babel-loader
:用于编译ES6新特性的JavaScript代码cross-env
:用于在跨平台的环境下设置NODE_ENVnodemon
:用于监控文件变化并自动重启服务rimraf
:用于删除目录和文件,跨平台且更好用的命令行rm -rf
的封装
此外,我们还需要安装webpack
和webpack-dev-middleware
、webpack-hot-middleware
,以及vue-loader
、vue-template-compiler
来处理Vue组件。
- 配置NPM脚本
我们在package.json
的scripts
字段中添加以下脚本:
"scripts": { "dev": "nodemon index.js", "build": "cross-env NODE_ENV=production webpack --config ./webpack.production.config.js --progress --hide-modules", "start": "cross-env NODE_ENV=production node index.js", "clean": "rimraf ./dist ./build" },
其中:
dev
:启动开发模式build
:构建生产环境代码start
:启动生产环境代码
- 目录结构设计
接下来,我们来设计我们的目录结构。在根目录下,我们新建src和build两个目录:
$ mkdir src build
在src目录下,新建client和server两个目录:
$ mkdir src/client src/server
在client目录下,我们建立Vue的客户端代码,包含Vue的路由管理和组件等。而在server目录下,我们建立的是我们的服务端代码,包括服务器的入口文件和各种配置文件。
在build目录下,我们将来存放Webpack构建的相关配置及输出的数据。
具体的目录结构如下:
. ├── package.json ├── build ├── src │ ├── client │ └── server └── index.js
- 配置Webpack
我们需要创建一个名为webpack.base.config.js
的Webpack配置文件,该文件用来配置客户端应用程序的构建和打包工具以及实现客户端和服务器端资产共享。并通过Vue-loader
来解析代码和Vue组件,通过Hot-load和静态资源保持一致进行打包。
以下是我们的webpack.base.config.js
文件的代码:
// javascriptcn.com 代码示例 const path = require('path') const webpack = require('webpack') const VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { mode: 'development', optimization: { splitChunks: { cacheGroups: { commons: { test: /[\\/]node_modules[\\/]/, name: 'vendor', chunks: 'all' } } } }, entry: { app: './src/client/main.js' }, output: { filename: '[name].js', path: path.resolve(__dirname, '../build'), publicPath: '/build/' }, resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': path.resolve(__dirname, '../src/client') } }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: { hotReload: true } }, { test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } }, exclude: /node_modules/ }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: 'images/[name].[hash:7].[ext]' } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: 'fonts/[name].[hash:7].[ext]' } } ] }, plugins: [ new VueLoaderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() ] }
- 开发服务器的配置
我们需要在文件夹中创建一个名为dev-server.js
的文件来完成开发模块的配置,其中我们使用http-proxy-middleware
和webpack-dev-middleware
来向Koa提供中间件的方式来构建一个Koa开发服务器的核心。
以下是我们的dev-server.js
文件的代码:
// javascriptcn.com 代码示例 const path = require('path') const express = require('express') const webpack = require('webpack') const devMiddleware = require('webpack-dev-middleware') const hotMiddleware = require('webpack-hot-middleware') const { createProxyMiddleware } = require('http-proxy-middleware') const webpackConfig = require('../webpack.base.config') const koa = require('koa') const app = new koa() const port = process.env.PORT || 8888 const compiler = webpack(webpackConfig) const serverOptions = { contentBase: `http://localhost:${port}`, quiet: true, noInfo: true, hot: true, publicPath: webpackConfig.output.publicPath, headers: { 'Access-Control-Allow-Origin': '*' }, stats: { colors: true } } app.use(createProxyMiddleware('/api', { target: 'your-api-server', changeOrigin: true, pathRewrite: { '^/api': '' } })) app.use(devMiddleware(compiler, serverOptions)) app.use(hotMiddleware(compiler, { heartbeat: 5000, log: console.log, path: '/__webpack_hmr', heartbeatTimeout: 20000 })) app.listen(port, err => { if (err) { console.log(err) } else { console.log(`Listening on: http://localhost:${port}`) } })
- 服务端的配置
在src/server文件夹内建立一个名为index.js
的文件。这将是我们应用程序的入口点,它需要使用Koa并在其中设置Vue SSR的中间件。
以下是我们的index.js
文件的代码:
// javascriptcn.com 代码示例 const fs = require('fs') const path = require('path') const Koa = require('koa') const koaRouter = require('koa-router') const koaStatic = require('koa-static') const koaCompress = require('koa-compress') const koaBodyParser = require('koa-bodyparser') const { createBundleRenderer } = require('vue-server-renderer') const template = fs.readFileSync('./src/client/index.template.html', 'utf-8') const renderer = require('vue-server-renderer').createRenderer() const server = new Koa() server.use(koaCompress()) server.use(koaBodyParser()) server.use(koaStatic('./build')) server.use(async (ctx, next) => { try { ctx.data = { meta: '', css: '', js: '', app: '', initialState: {} } const context = { url: ctx.url } const bundleRenderer = createBundleRenderer('./build/vue-ssr-server-bundle.json', { runInNewContext: false, template: template, clientManifest: require('./build/vue-ssr-client-manifest.json') }) ctx.data.meta = await new Promise(resolve => { renderer.renderToString(context, (err, html) => { if (err) throw err resolve(html) }) }) ctx.data.app = await new Promise(resolve => { bundleRenderer.renderToString(context, (err, html) => { if (err) throw err resolve(html) }) }) } catch (err) { console.log(err) ctx.status = 500 ctx.body = 'Server Error' } await next() }) server.use(async ctx => { const { data } = ctx const html = ` <!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>Koa Vue SSR</title> ${data.meta} <link rel="stylesheet" href="${data.css}"> </head> <body> <div id="app">${data.app}</div> ${data.js} </body> </html> ` ctx.body = html }) server.listen(3000, () => { console.log('Server running on http://localhost:3000') })
实践经验分享
在搭建Vue服务端渲染应用的过程中,我们可以发现一些有价值的实践经验和解决方案,这里我们分享一下:
1. Webpack的代码分离
在使用Webpack进行打包时,我们可以通过代码分离技术来将应用程序的核心模块和第三方模块分离开来。这可以大大减小打包完成的应用程序的体积。
例如,我们可以使用splitChunks
选项来实现代码分离:
// javascriptcn.com 代码示例 optimization: { splitChunks: { chunks: 'all', // 对所有代码进行分离处理 cacheGroups: { commons: { test: /[\\/]node_modules[\\/]/, name: 'vendor', chunks: 'all' // 最小的复用模块必须被使用两次以上 } } } }
2. 使用router.beforeResolve()提前加载数据
在Vue SSR中使用router.beforeResolve()
可以实现在路由切换前加载数据,从而提高渲染速度。
例如,我们可以这样来加载数据:
// javascriptcn.com 代码示例 router.beforeResolve(async (to, from, next) => { try { const matched = router.getMatchedComponents(to.path) const prevMatched = router.getMatchedComponents(from.path) let diffed = false const activated = matched.filter((c, i) => { return diffed || (diffed = (prevMatched[i] !== c)) }) if (!activated.length) { return next() } let promises = activated.map(c => { const componentOptions = c.options || {} if (componentOptions.asyncData) { return componentOptions.asyncData({ store }) } }) promises = promises.filter(p => p) if (!promises.length) { return next() } const results = await Promise.all(promises) results.forEach(data => { store.commit('set', data) }) next() } catch (err) { console.log(err) next() } })
3. 精细化的CSS代码拆分
在Vue SSR中,很多人会将所有CSS样式放在一个文件中,并在应用程序执行时将该文件呈现到客户端,这样可能会导致不必要的样式打包到应用程序中。
我们可以通过以下方式来实现精细化的CSS代码拆分:
// javascriptcn.com 代码示例 import isProd from './config' import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import Meta from 'vue-meta' Vue.use(Meta) import { createRenderer } from 'vue-server-renderer' import createMiddleWare from './middleware.js' const renderer = create Renderer({ template: '<!--vue-ssr-outlet-->', clientManifest: require('../../build/vue-ssr-client-manifest.json') }) export default context => new Promise((resolve, reject) => { const middleware = createMiddleWare() middleware(context, () => { console.log(context.state) const app = new Vue({ router, store, render: h => h(App) }) renderer.renderToString(app, (err, html) => { if (err) { reject(err) return } resolve(html) }) }) })
总结
通过这篇文章,我们了解到了如何使用Koa2搭建Vue服务端渲染应用,并分享了一些在实践中遇到的问题以及应对方法。我们相信这些经验和解决方案将会对我们的Vue服务端渲染应用开发有所帮助。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/652e32ed7d4982a6ebf3fdec