利用webpack4搭建vue服务器端渲染SSR(一)
栏目: JavaScript · 发布时间: 6年前
内容简介:构建服务器端渲染(SSR)我们可以利用按照官网的步骤,执行从这段代码我们应该可以明白
- 为什么使用服务器渲染? :point_right:官方解释
- 应该对VueSSR指南简单了解:point_right:官方文档
- 应该对webpack简单了解:point_right:官方文档
- Node.js框架Koa简单了解:point_right:官方文档
正文
构建服务器端渲染(SSR)我们可以利用 vue-server-renderer
插件更简单的构建SSR。官方的一段代码:
// 第 1 步:创建一个 Vue 实例 const Vue = require('vue') const app = new Vue({ template: `<div>Hello World</div>` }) // 第 2 步:创建一个 renderer const renderer = require('vue-server-renderer').createRenderer() // 第 3 步:将 Vue 实例渲染为 HTML renderer.renderToString(app, (err, html) => { if (err) throw err console.log(html) // => <div data-server-rendered="true">Hello World</div> }) 复制代码
按照官网的步骤,执行 node server.js
可以看到控制台打印 <div data-server-rendered="true">Hello World</div>
:
从这段代码我们应该可以明白 vue-server-renderer
的作用是拿到vue实例并渲染成html结构,但它不仅仅只做着一件事,后面会介绍其他配置参数和配合webpack进行构建。
拿到html结构渲染到页面上是我们接下来要做的事情,这里官方事例用的是express搭建服务器,我这里采用Koa,为什么用Koa?我不会express 。Koa起一个服务非常简单,我们还需要借助Koa-router来做路由的处理。
npm i koa koa-router -S 复制代码
修改 server.js
:
const Vue = require('vue') const Koa = require('koa') const Router = require('koa-router') const renderer = require('vue-server-renderer').createRenderer() // 第 1 步:创建koa、koa-router 实例 const app = new Koa() const router = new Router() // 第 2 步:路由中间件 router.get('*', async (ctx, next) => { // 创建Vue实例 const app = new Vue({ data: { url: ctx.url }, template: `<div>访问的 URL 是: {{ url }}</div>` }) // 有错误返回500,无错误返回html结构 try { const html = await renderer.renderToString(app) ctx.status = 200 ctx.body = ` <!DOCTYPE html> <html lang="en"> <head><title>Hello</title></head> <body>${html}</body> </html> ` } catch (error) { console.log(error) ctx.status = 500 ctx.body = 'Internal Server Error' } }) app .use(router.routes()) .use(router.allowedMethods()) // 第 3 步:启动服务,通过http://localhost:3000/访问 app.listen(3000, () => { console.log(`server started at localhost:3000`) }) 复制代码
从上段代码我们就可以看出服务器端渲染的基本原理了,其实说白了,无服务器端渲染时,前端打包后的html只是包含head部分,body部分都是通过动态插入到id为 #app
的dom中。如图:
而服务器端渲染(SSR)就是服务器来提前编译Vue生成HTML返回给web浏览器,这样网络爬虫爬取的内容就是网站上所有可呈现的内容。
为了可以个性化页面,我们可以把html结构抽成一个模板template,通过双花括号 {{}}
进行传值,新建 index.template.html
按照官网编写如下代码:
<!DOCTYPE html> <html lang="en"> <head> <!-- 三花括号不进行html转义 --> {{{ meta }}} <title>{{ title }}</title> </head> <body> <!--vue-ssr-outlet--> </body> </html> 复制代码
server.js
需要通过Node模块 fs
读取模板,作为 vue-server-renderer
的template参数传入
const renderer = require('vue-server-renderer').createRenderer({ // 读取传入template参数 template: require('fs').readFileSync('./index.template.html', 'utf-8') }) // ... router.get('*', async (ctx, next) => { // title、meta会插入模板中 const context = { title: ctx.url, meta: ` <meta charset="UTF-8"> <meta name="descript" content="基于webpack、koa搭建的SSR"> ` } try { // 传入context渲染上下文对象 const html = await renderer.renderToString(app, context) ctx.status = 200 // 传入了template, html结构会插入到<!--vue-ssr-outlet--> ctx.body = html } catch (error) { ctx.status = 500 ctx.body = 'Internal Server Error' } }) // ... 复制代码
可以看到我们的标题和meta都被插入啦!:clap::clap::clap:。到这里,我们才实现了最基本的用法,接下来我们终于要使用webpack来构建我们项目。
Node.js服务器是一个长期运行的进程、当我们的代码进入该进程时,它将进行一次取值并留存在内存中。这意味着如果创建一个单例对象,它将在每个传入的请求之间共享,所以我们需要为 为每个请求创建一个新的根 Vue 实例
不仅vue实例,接下来要用到的vuex、vue-router也是如此。我们利用webpack需要分别对客户端代码和服务器端代码分别打包, 服务器需要「服务器 bundle」然后用于服务器端渲染(SSR),而「客户端 bundle」会发送给浏览器,用于混合静态标记。贴一下官方构建图:
我们可以大致的理解为服务器端、客户端通过俩个入口 Server entry
、 Clinet entry
获取源代码,再通过webpack打包变成俩个bundle vue-ssr-server-bundle.json
、 vue-ssr-client-manifest.json
,配合生成完成HTML,而 app.js
是俩个入口通用的代码部分,其作用是暴露出vue实例。所以我们可以按照官方建议整理文件目录,并按照 官方事例代码 编写,其中起服务的 server.js
我们用的是Koa,所以可以先不用改。
生成的俩个bundle其实是作为参数传入到 createBundleRenderer()
函数中,然后在renderToString变成html结构,与 createRenderer
不同的是前者是通过bundle参数获取vue组件编译,后者是需要在 renderToString
时传入vue实例:point_right:文档。我们先编写webpack成功生成bundle后,再去编写server.js,这样有利于我们更好的理解和测试。
首先我们建立build文件夹,用于存放webpack相关配置,在vue-cli3之前,vue init 初始化后的项目都是有build文件夹的,可以清楚看到webpack配置。而vue-cli3后,使用webpack4,并将配置隐藏了起来,如果想了解webpack4构建vue单页面应用可以去我的github上查看:point_right: 地址 。我们可以模仿vue-cli,创建通用配置webpack.base.conf.js、客户端配置webpack.client.conf.js、服务端配置webpack.server.conf.js。文件目录为
├── build │ ├── webpack.base.conf.js # 基本webpack配置 │ ├── webpack.client.conf.js # 客户端webpack配置 │ └── webpack.server.conf.js # 服务器端webpack配置 ├── src ├── index.template.html └── server.js 复制代码
webpack.base.conf.js
配置主要定义通用的rules,例如vue-loader对.vue文件编译,对js文件babel编译,处理图片、字体等。其基本配置如下:
const path = require('path') // vue-loader v15版本需要引入此插件 const VueLoaderPlugin = require('vue-loader/lib/plugin') // 用于返回文件相对于根目录的绝对路径 const resolve = dir => path.posix.join(__dirname, '..', dir) module.exports = { // 入口暂定客户端入口,服务端配置需要更改它 entry: resolve('src/entry-client.js'), // 生成文件路径、名字、引入公共路径 output: { path: resolve('dist'), filename: '[name].js', publicPath: '/' }, resolve: { // 对于.js、.vue引入不需要写后缀 extensions: ['.js', '.vue'], // 引入components、assets可以简写,可根据需要自行更改 alias: { 'components': resolve('src/components'), 'assets': resolve('src/assets') } }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: { // 配置哪些引入路径按照模块方式查找 transformAssetUrls: { video: ['src', 'poster'], source: 'src', img: 'src', image: 'xlink:href' } } }, { test: /\.js$/, // 利用babel-loader编译js,使用更高的特性,排除npm下载的.vue组件 loader: 'babel-loader', exclude: file => ( /node_modules/.test(file) && !/\.vue\.js/.test(file) ) }, { test: /\.(png|jpe?g|gif|svg)$/, // 处理图片 use: [ { loader: 'url-loader', options: { limit: 10000, name: 'static/img/[name].[hash:7].[ext]' } } ] }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, // 处理字体 loader: 'url-loader', options: { limit: 10000, name: 'static/fonts/[name].[hash:7].[ext]' } } ] }, plugins: [ new VueLoaderPlugin() ] } 复制代码
webpack.client.conf.js
主要是对客户端代码进行打包,它是通过 webpack-merge
实现对基础配置的合并,其中要实现对css样式的处理,此处我用了stylus,同时要下载对应的stylus-loader来处理。在这里我们先不考虑开发环境,后面会详细介绍开发环境的webpack配置。
const path = require('path') const webpack = require('webpack') const merge = require('webpack-merge') const baseWebpackConfig = require('./webpack.base.conf') // css样式提取单独文件 const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 服务端渲染用到的插件、默认生成JSON文件(vue-ssr-client-manifest.json) const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') module.exports = merge(baseWebpackConfig, { mode: 'production', output: { // chunkhash是根据内容生成的hash, 易于缓存, // 开发环境不需要生成hash,目前先不考虑开发环境,后面详细介绍 filename: 'static/js/[name].[chunkhash].js', chunkFilename: 'static/js/[id].[chunkhash].js' }, module: { rules: [ { test: /\.styl(us)?$/, // 利用mini-css-extract-plugin提取css, 开发环境也不是必须 use: [MiniCssExtractPlugin.loader, 'css-loader', 'stylus-loader'] }, ] }, devtool: false, plugins: [ // webpack4.0版本以上采用MiniCssExtractPlugin 而不使用extract-text-webpack-plugin new MiniCssExtractPlugin({ filename: 'static/css/[name].[contenthash].css', chunkFilename: 'static/css/[name].[contenthash].css' }), // 当vendor模块不再改变时, 根据模块的相对路径生成一个四位数的hash作为模块id new webpack.HashedModuleIdsPlugin(), new VueSSRClientPlugin() ] }) 复制代码
编写完,我们需要在package.json定义命令来执行webpack打包命令。如果没有该文件,需要通过 npm init
初始化生成
"scripts": { "build:client": "webpack --config build/webpack.client.conf.js", # 打包客户端代码 "build:server": "webpack --config build/webpack.server.conf.js", # 打包服务端代码 "start": "node server.js" # 启动服务 } 复制代码
我们现在可以通过 npm run build:client
执行打包命令,需要注意的是,执行命令之前要把依赖的npm包下载好,目前所需要到的依赖见下图:
当打包命令执行完毕后,我们会发现多了一个dist文件夹,其中除了静态文件以外,生成了用于服务端渲染的JSON文件:vue-ssr-client-manifest.json。
同理,我们需要编写服务端webpack配置,同样打包生成vue-ssr-server-bundle.json。配置代码如下:
const path = require('path') const webpack = require('webpack') const merge = require('webpack-merge') const nodeExternals = require('webpack-node-externals') const baseWebpackConfig = require('./webpack.base.conf') const VueServerPlugin = require('vue-server-renderer/server-plugin') module.exports = merge(baseWebpackConfig, { mode: 'production', target: 'node', devtool: 'source-map', entry: path.join(__dirname, '../src/entry-server.js'), output: { libraryTarget: 'commonjs2', filename: 'server-bundle.js', }, // 这里有个坑... 服务端也需要编译样式,但不能使用mini-css-extract-plugin, // 因为它会使用document,但服务端并没document,导致打包报错。详情见 // https://github.com/webpack-contrib/mini-css-extract-plugin/issues/48#issuecomment-375288454 module: { rules: [ { test: /\.styl(us)?$/, use: ['css-loader/locals', 'stylus-loader'] } ] }, // 不要外置化 webpack 需要处理的依赖模块 externals: nodeExternals({ whitelist: /\.css$/ }), plugins: [ new webpack.DefinePlugin({ 'process.env.VUE_ENV': '"server"' }), // 默认文件名为 `vue-ssr-server-bundle.json` new VueServerPlugin() ] }) 复制代码
我们新建一个 build:server
命令来运行打包服务器端代码。最后会在dist文件下生成vue-ssr-server-bundle.json,同时我们可以新建 build
命令来一起执行俩端的配置。
好了,现在我们可以修改我们的server.js来实现整个服务器端渲染流程。我们需要获取俩个JSON文件、html模板作为参数传入 createBundleRenderer
,vue实例不再需要,context需要url,因为服务端端入口(entry-server.js) 需要获取访问的路径来匹配对应的vue组件。部分改动代码如下:
/* 将createRenderer替换成createBundleRenderer,不同之处在上面提到过... */ const { createBundleRenderer } = require('vue-server-renderer') // ... // 获取客户端、服务器端生成的json文件、html模板文件 const serverBundle = require('./dist/vue-ssr-server-bundle.json') const clientManifest = require('./dist/vue-ssr-client-manifest.json') const template = require('fs').readFileSync('./index.template.html', 'utf-8') // 传入 json文件和template, 渲染上下文url需要传入,服务端需要匹配路由 router.get('*', async (ctx, next) => { const renderer = createBundleRenderer(serverBundle, { runInNewContext: false, // 推荐 template, // 页面模板 clientManifest // 客户端构建 manifest }) const context = { url: ctx.url, // ... } // ... 复制代码
改动后,我们运行 npm run start
,发现页面已经成功渲染出来,但这时有个问题,加载的资源都失败了,文件存在于dist中,很显然,一定是路径不对导致的。这时我们可以通过koa-send来实现静态资源的发送。我们需要在server.js中加入这行代码:
const send = require('koa-send') // 引入/static/下的文件都通过koa-send转发到dist文件目录下 router.get('/static/*', async (ctx, next) => { await send(ctx, ctx.path, { root: __dirname + '/dist' }); }) 复制代码
再重新运行,打开控制台可以看到资源加载成功,并且加载的doc里面包含页面上所有内容。:clap:
结束语
但我们目前还没有涉及到需要异步加载的数据渲染,以及如何在开发中进行编码,缓存路由等问题。在下一篇文章我会带大家先实现开发环境的webpack配置,以及修改server.js,这样,就不需要我们每次都要打包来测试我们代码,为接下来学习数据预取,缓存路由等做好准备。一起加油吧。感谢观看~
以上所述就是小编给大家介绍的《利用webpack4搭建vue服务器端渲染SSR(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- React服务端渲染(项目搭建)
- 使用 Puppeteer 搭建统一海报渲染服务
- TypeScript + Webpack + Koa 搭建 React 服务端渲染
- Octane渲染入门-渲染设置图文版
- 通过分析 WPF 的渲染脏区优化渲染性能
- React 服务器端渲染和客户端渲染效果对比
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。