前言
在博客开发的过程中,有这样一个需求想解决,就是在SSR开发环境中,服务端的代码是是直接通过webpack打包成文件(因为里面包含同构的代码,就是服务端与客户端共享前端的组件代码),写到磁盘里,然后在启动打包好的入口文件来启动服务。但是我不想在开发环境把文件打包到磁盘中,想直接打包在内存中,这样不仅能优化速度,还不会因开发环境产生多余文件。还有就是webpack对require的处理,会导致路径映射的问题,包括对require变量的问题。所以我就想只有组件相关的代码进行webpack编译,别的无关的服务端代码不进行webpack编译处理。
但是这中间有个问题一直悬而不决,就是如何引入内存中的文件。包括在引入这个文件后,如何把关联的文件一起引入,如通过require(module)
引入的模块,于是我想到以前在给vue做ssr的时候用到的vue-server-renderer
这个库,这个是没有直接打出文件,而是把文件打入了内存中。但是他却能获取到文件,并执行文件获取到结果。于是就开启了这次的研究之旅。
![](https://assets.open.dzblog.cn/images/markdown/1621306138394.webp)
实现
先讲下项目这块的实现流程,然后在讲下vue-server-renderer
这个包是如何解决这个问题的,以此在react中的实现。
|-- webpack
| |-- webpack.client.js // entry => clilent-main.js
| |-- webpack.server.js // entry => server-main.js
|-- client // 客户端代码
| |-- app.js
| |-- client-main.js // 客户端打包入口
| |-- server-main.js // server端打包代码入口
|-- server // server端代码
| |-- ssr.js // ssr启动入口
-
client-main.js
, 客户端打包一份代码,就是正常的打包, 打包出对应的文件。import React, { useEffect, useState } from 'react' import ReactDom from 'react-dom' import App from './app' loadableReady(() => { ReactDom.hydrate( <Provider store={ store}> <App /> </Provider>, document.getElementById('app') ) })
-
server-main.js
,因为是SSR,所以在服务端也需要打包一份对应的js文件,用于ssr渲染。我这里是打算在这块直接处理完组件相关的数据,返回html,到时候服务端直接引入这个文件,获取html返回给前端就行。这是我的项目的处理,vue官方demo会有点区别,他是直接返回的app实例(new Vue(...)
, 然后在vue-server-renderer
库中解析这个实例,最后同样也是返回解析好的html字符串。这里会有点区别,原理还是一样。// 返回一个函数,这样可以传入一些参数,用来传入服务端的一些数据 import { renderToString } from 'react-dom/server' export default async (context: IContext, options: RendererOptions = { }) => { // 获取组件数据 ... // 获取当前url对应的组件dom信息 const appHtml = renderToString( extractor.collectChunks( <Provider store={ store}> <StaticRouter location={ context.url} context={ context as any}> <HelmetProvider context={ helmetContext}> <App /> </HelmetProvider> </StaticRouter> </Provider> ) ) // 渲染模板 const html = renderToString( <HTML>{ appHtml}</HTML> ) context.store = store return html }
3. `ssr.js`, 因为这些文件我都是打在内存中的。所以我需要解析内存中的文件,来获取`server-main.js`中的函数,执行他,返回html给前端。
```typescript
// start方法是执行webpack的node端代码,用于把编译的文件打入内存中。
import { start } from '@root/scripts/setup'
// 执行他,createBundleRenderer方法就是用来解析在server端打包的代码
start(app, ({ loadableStats, serverManifest, inputFileSystem }) => {
renderer = createBundleRenderer({
loadableStats,
serverManifest,
inputFileSystem
})
})
// 执行server-main.js中的函数并获取html
const html = await renderer.renderToString(context)
ctx.body = html
客户端的好说,通过创建html模板,然后把当前路由对应的资源(js, css,…)引入,访问的时候,浏览器直接拉取资源就行(这块是通过@loadable/webpack-plugin
、@loadable/server
、@loadable/component
来进行资源的加载与获取,此处不做过多介绍,此文重点不在这个)。
这块的重点就是如何在内存中解析server-main.js
这个被打包出来的需要在服务端引用的代码。
我们来看vue ssr的官方代码: vue-hackernews-2.0
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(base, {
target: 'node',
devtool: '#source-map',
entry: './src/server-main.js',
output: {
filename: 'server-bundle.js',
libraryTarget: 'commonjs2'
},
plugins: [
new VueSSRServerPlugin()
]
})
上面用到了一个vue-server-renderer/server-plugin
, 这个插件的主要功能是干嘛呢,其实就是对webpack中的资源做了下处理,把其中的js资源全部打在了一个json文件中。
源码