-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathnext-with-federated-prerender.js
152 lines (140 loc) · 4.58 KB
/
next-with-federated-prerender.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
const fs = require("fs");
const path = require("path");
const { Template } = require("webpack");
const VirtualModulesPlugin = require("webpack-virtual-modules");
function generateVirtualModule(name, exposes, devStatsFile, stats) {
return Template.asString([
"// THIS FILE IS AUTOGENERATED, DO NOT TOUCH",
`import path from "path";`,
`import { createElement } from "react";`,
`import { renderToStaticMarkup } from "react-dom/server";`,
"",
// Generate the request map
"const requestMap = {",
Template.indent(
Object.entries(exposes).map(
([key, mod]) =>
`[${JSON.stringify(key)}]: () => import(${JSON.stringify(mod)}),`
)
),
"};",
"",
// Get the exposes from the stats file
devStatsFile
? `const exposes = __non_webpack_require__(path.resolve(__dirname, ${JSON.stringify(
devStatsFile
)})).federatedModules.find(f => f.remote === ${JSON.stringify(
name
)}).exposes;`
: `const exposes = ${stats}.federatedModules.find(f => f.remote === ${JSON.stringify(
name
)}).exposes`,
"",
// Get the chunks for an exposed module
`function getChunksForExposed(exposed) {
return exposes[exposed].reduce((p, c) => {
p.push(...c.chunks);
return p;
}, []);
}`,
"",
// Generate the route handler
"export default async function nextFederatedPrerender(req, res) {",
Template.indent([
// Bail if not a post method
`if (req.method !== "POST") {`,
Template.indent(["res.status(405);", "res.send();", "return;"]),
"}",
"",
"const mod = req.body.module;",
// Bail if not in the request map
`if (!requestMap[mod]) {`,
Template.indent(["res.status(404);", "res.send();", "return;"]),
"}",
"try {",
Template.indent([
// Get the component from the module
"const chunks = getChunksForExposed(mod);",
"let Component = await requestMap[mod]();",
"Component = (Component && Component.default) || Component;",
// Render it to HTML with the props and a placeholder marker for the children.
"const html = renderToStaticMarkup(createElement(Component, req.body.props, `\u200Cchildren\u200C`));",
"res.status(200);",
"res.json({ chunks, html });",
]),
"} catch (err) {",
Template.indent([
"console.error(err);",
"res.status(500);",
"res.send();",
"return;",
]),
"}",
]),
"}",
]);
}
class FederatedPrerenderPlugin {
/**
*
* @param {{
* dir?: string;
* exposes: import("webpack").container.ModuleFederationPluginOptions["exposes"];
* statsFile: string;
* name: string;
* }} options
*/
constructor(options) {
this.options = options;
}
/**
*
* @param {import("webpack").Compiler} compiler
*/
apply(compiler) {
const dir = this.options.dir || process.cwd();
const exposes = this.options.exposes || {};
const embedStatsFile = this.options.embedStatsFile;
const devStatsFile = this.options.devStatsFile;
const name = this.options.name;
// Get the location of the module. This is so the relative imports defined in exposes work.
const virtualMod = path.join(dir, "__federated-prerender.js");
const stats = embedStatsFile
? fs.readFileSync(embedStatsFile, "utf-8")
: null;
// Assign it to an alias for easier import in the server.
compiler.options.resolve.alias = compiler.options.resolve.alias || {};
compiler.options.resolve.alias["federated-prerender"] = virtualMod;
// Generate the code for the prerender module.
const code = generateVirtualModule(name, exposes, devStatsFile, stats);
fs.writeFileSync(virtualMod, code);
// Use the virutal modules plugin to add it to the build.
new VirtualModulesPlugin({ [virtualMod]: code }).apply(compiler);
// compiler.hooks.afterDone.tap(
// 'RerenderPlugin',
// () => {
// fs.unlinkSync(virtualMod)
// }
// );
}
}
function nextWithFederatedPrerender(options) {
return (nextConfig = {}) => ({
...nextConfig,
webpack(webpackConfig, webpackOptions) {
if (webpackOptions.isServer) {
webpackConfig.plugins.unshift(
new FederatedPrerenderPlugin({
...options,
dir: options.dir || webpackOptions.dir,
})
);
}
if (typeof nextConfig.webpack === "function") {
return nextConfig.webpack(webpackConfig, webpackOptions);
}
return webpackConfig;
},
});
}
module.exports = nextWithFederatedPrerender;