1 | const pathConfig = require('config').get('path')
|
2 | const projectConfig = require('config').get('project')
|
3 |
|
4 | const {
|
5 | pathConfig: { template: templatePath }
|
6 | } = require('./../config')
|
7 | const fs = require('fs')
|
8 | const fse = require('fs-extra')
|
9 | const path = require('path')
|
10 | const webpack = require('webpack')
|
11 | const clientConfig = require('./webpack.client.config')
|
12 | const serverConfig = require('./webpack.server.config')
|
13 | const webpackDevMiddleware = require('webpack-dev-middleware')
|
14 | const webpackHotMiddleware = require('webpack-hot-middleware')
|
15 | const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin')
|
16 | const { createBundleRenderer } = require('vue-server-renderer')
|
17 | const koaSend = require('koa-send')
|
18 |
|
19 | let singleEntry
|
20 | let clientManifestAll
|
21 |
|
22 | module.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 |
|
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 |
|
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 | }
|