import * as fs from 'fs-extra';
import * as _ from 'lodash';
import * as path from 'path';
import * as ts from 'typescript';
import { vai } from '../../node';
export default async (instance: typeof vai) => {
instance.project.onCreateEntry((analyseInfo, entry) => {
if (!instance.projectConfig.useServiceWorker) {
return;
}
if (!instance.projectConfig.clientServerRender) {
return;
}
entry.pipeEntryHeader(header => {
return `
${header}
import { renderToString } from "react-dom/server"
import StaticRouter from "react-router-dom/StaticRouter"
import { escapeRegExp, trimEnd } from "lodash"
`;
});
entry.pipeEntryRender(
render => `
${render}
if (navigator.serviceWorker) {
navigator.serviceWorker.addEventListener("message", event => {
if (event.data.type === "getServerRenderContent") {
const baseHrefRegex = new RegExp(escapeRegExp("${instance.projectConfig.baseHref}"), "g")
const matchRouterPath = event.data.pathname.replace(baseHrefRegex, "")
const loadableMap = pageLoadableMap.get(matchRouterPath === "/" ? "/" : trimEnd(matchRouterPath, "/"))
if (loadableMap) {
loadableMap.preload().then(() => {
const ssrResult = renderToString(
);
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
type: 'serverRenderContent',
pathname: event.data.pathname,
content: ssrResult
});
}
})
}
}
})
}
`
);
});
instance.build.afterProdBuild(stats => {
if (!instance.projectConfig.useServiceWorker) {
return;
}
if (!instance.projectConfig.clientServerRender) {
return;
}
instance.serviceWorker.pipeAfterProdBuild(
str => `
${str}
var SSR_BUNDLE_PREFIX = "__ssr_bundle__"
var SSR_BUNDLE_VERSION = SSR_BUNDLE_PREFIX + "${stats.hash}";
var currentCacheSsrRequest = null
var currentCacheSsrOriginHtml = null
/**
* Delete all bundle caches except current SSR_BUNDLE_VERSION.
*/
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => cacheName.startsWith(SSR_BUNDLE_PREFIX))
.filter(cacheName => cacheName !== SSR_BUNDLE_VERSION)
.map(cacheName => caches.delete(cacheName))
)
})
)
})
// Get ssr content from client, and save to cache.
self.addEventListener('message', event => {
if (event.data && event.data.type && event.data.type === 'serverRenderContent') {
var responseInit = {
status: 200,
statusText: 'OK',
headers: { 'Content-Type': 'text/html;charset=utf-8' }
};
var ssrFlag = ""
var injectBodyContent = \`
\${event.data.content}
\\n \${ssrFlag} \\n\`
var htmlAddContent = currentCacheSsrOriginHtml.replace(/(\\)/g, \`$1\${injectBodyContent}\`)
const ssrResponse = new Response(
htmlAddContent,
responseInit
);
caches.open(SSR_BUNDLE_VERSION).then(cache => {
cache.put(currentCacheSsrRequest, ssrResponse);
})
}
});
// Replace entry html to ssr result.
self.addEventListener('fetch', event => {
if (
event.request.mode === 'navigate' &&
event.request.method === 'GET' &&
event.request.headers.get('accept').includes('text/html')
) {
event.respondWith(
caches.open(SSR_BUNDLE_VERSION).then(cache => {
return cache.match(event.request).then(response => {
if (response) {
return response;
}
return fetch(event.request).then(response => {
const newResponse = response.clone();
return newResponse
.text()
.then(text => {
currentCacheSsrRequest = event.request
currentCacheSsrOriginHtml = text
// Tell client, i want ssrContent!
setTimeout(() => {
self.clients.matchAll().then(clients => {
if (!clients || !clients.length) {
return
}
clients.forEach(client => {
client.postMessage({
type: "getServerRenderContent",
pathname: new URL(event.request.url, location).pathname
})
})
})
}, 1000)
return response
})
.catch(err => response);
});
});
})
);
}
});
`
);
});
};