UNPKG

5.04 kBJavaScriptView Raw
1const pathConfig = require('config').get('path')
2const projectConfig = require('config').get('project')
3
4const {
5 pathConfig: { template: templatePath }
6} = require('./../config')
7const fs = require('fs')
8const fse = require('fs-extra')
9const path = require('path')
10const webpack = require('webpack')
11const clientConfig = require('./webpack.client.config')
12const serverConfig = require('./webpack.server.config')
13const webpackDevMiddleware = require('webpack-dev-middleware')
14const webpackHotMiddleware = require('webpack-hot-middleware')
15const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin')
16const { createBundleRenderer } = require('vue-server-renderer')
17const koaSend = require('koa-send')
18
19let singleEntry // 临时存储入口
20let clientManifestAll // 临时存储所有打包后的文件
21
22module.exports = function (app, config) {
23 const dllJSON = require(path.resolve(pathConfig.dll, 'index.json'))
24 const dllVendor = Object.values(dllJSON)
25 .reduce((prev, cur) => {
26 let val = Object.values(cur)
27 prev = prev.concat(val)
28 return prev
29 }, [])
30 .map(v => v.substring(1))
31
32 const readFile = (fs, file) => {
33 try {
34 return fs.readFileSync(path.join(clientConfig.output.path, file), 'utf-8')
35 } catch (e) {}
36 }
37
38 const createSingleEntry = (name = 'news', end = 'client') => {
39 return [
40 pathConfig.root,
41 path.resolve(pathConfig.prepackPath, `./ssr/${name}/entry-${end}.js`),
42 name
43 ]
44 }
45
46 clientConfig.entry = {
47 global: ['webpack-hot-middleware/client', pathConfig.global]
48 }
49 serverConfig.entry = {
50 global: ['webpack-hot-middleware/client', pathConfig.global]
51 }
52
53 const template = fs.readFileSync(
54 path.resolve(templatePath, 'ssr/template.html'),
55 'utf-8'
56 )
57
58 const clientCompiler = webpack(clientConfig)
59 const clientDevInstance = webpackDevMiddleware(clientCompiler, {
60 publicPath: '/',
61 stats: clientConfig.stats
62 })
63 let clientPromise = function () {
64 return new Promise((resolve, reject) => {
65 clientDevInstance.waitUntilValid(function () {
66 let clientManifest = JSON.parse(
67 readFile(clientDevInstance.fileSystem, 'vue-ssr-client-manifest.json')
68 )
69 resolve(clientManifest)
70 })
71 })
72 }
73
74 const serverCompiler = webpack(serverConfig)
75 const serverDevInstance = webpackDevMiddleware(serverCompiler, {
76 publicPath: '/',
77 stats: serverConfig.stats
78 })
79 let serverPromise = function () {
80 return new Promise((resolve, reject) => {
81 serverDevInstance.waitUntilValid(function () {
82 let bundle = JSON.parse(
83 readFile(serverDevInstance.fileSystem, 'vue-ssr-server-bundle.json')
84 )
85 resolve(bundle)
86 })
87 })
88 }
89
90 /* 传入重置后 clientManifest & bundle */
91 function renderer (clientManifest, bundle, ctx) {
92 const context = {
93 url: ctx.originalUrl,
94 title: projectConfig.title,
95 __SSR_CONTENT__: ctx.__SSR_CONTENT__
96 }
97
98 return new Promise((res, rej) => {
99 createBundleRenderer(bundle, {
100 template,
101 clientManifest,
102 runInNewContext: 'true'
103 }).renderToString(context, (err, html) => {
104 if (err) {
105 console.log(err)
106 console.log(err.stack)
107 rej(err)
108 } else {
109 res(html)
110 }
111 })
112 })
113 }
114
115 app.use(async (ctx, next) => {
116 let reqPath = ctx.path
117 if (!reqPath.startsWith('/sogou/detail')) {
118 await next()
119 } else {
120 // #issure folder is path of entry
121 let folder = reqPath.split('/').filter(v => v)[2]
122
123 clientCompiler.apply(new SingleEntryPlugin(...createSingleEntry(folder)))
124 clientDevInstance.invalidate()
125
126 serverCompiler.apply(
127 new SingleEntryPlugin(...createSingleEntry(folder, 'server'))
128 )
129 serverDevInstance.invalidate()
130 try {
131 let [clientManifest, bundle] = await Promise.all([
132 clientPromise(),
133 serverPromise()
134 ])
135
136 clientManifestAll = clientManifest.all // 临时存储
137
138 let initial = dllVendor.concat(['js/global.js', `js/${folder}.js`])
139 Object.assign(clientManifest, { initial })
140 Object.assign(bundle, { entry: `js/${folder}-server.js` })
141 let html = await renderer(clientManifest, bundle, ctx)
142 ctx.body = html
143 } catch (e) {
144 ctx.body = e
145 }
146 }
147 })
148
149 app.use(async (ctx, next) => {
150 let reqPath = ctx.path
151
152 if (
153 clientManifestAll.some(v => {
154 return reqPath.includes(v)
155 })
156 ) {
157 ctx.status = 200
158 await clientDevInstance(ctx.req, ctx.res, async () => {
159 await next()
160 })
161 } else {
162 let maxage = 365 * 24 * 60 * 60 * 1000
163 const exists = await fse.pathExists(`${pathConfig.static}${reqPath}`)
164 let result
165 if (exists) {
166 result = await koaSend(ctx, reqPath, {
167 root: pathConfig.static,
168 maxage
169 })
170 }
171
172 if (!result) {
173 await next()
174 }
175 }
176 })
177
178 app.use(webpackHotMiddleware(clientCompiler))
179}