Skip to content

Commit 5435303

Browse files
authored
fix: Use 'main' as default render entry (#10)
* refactor: Use destructoring for constructor parameters * feat: Allow choice of parallel and serial rendering * fix: Add timing to render messaging * fix: Use 'main' as default render entry * fix: Rename parallelRender to renderConcurrency enum
1 parent 8d35879 commit 5435303

File tree

12 files changed

+176
-69
lines changed

12 files changed

+176
-69
lines changed

README.md

+40-6
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,28 @@ See [examples](#examples) for more details.
110110

111111
# Options
112112

113+
## Option: renderEntry _string_
114+
115+
**default:** "main"
116+
117+
The entry to use when rendering. Override when using [object syntax](https://webpack.js.org/concepts/entry-points/#object-syntax) in webpack entry.
118+
119+
```js
120+
new HtmlRenderPlugin({
121+
renderEntry: "myRender"
122+
});
123+
```
124+
125+
**webpack.config.js**
126+
127+
```
128+
{
129+
entry: {
130+
myRender: './src/myRender.js',
131+
}
132+
}
133+
```
134+
113135
## Option: renderDirectory _string_
114136

115137
The location to create rendered files. Defaults to the rendered assets output.
@@ -185,6 +207,18 @@ In this example, the resulting files will be
185207

186208
- **en-au/about/us/index.html**
187209

210+
## Option: renderConcurrency _string ("serial"|"parallel")_
211+
212+
**default:** `"serial"`
213+
214+
By default each file will be rendered one after the other, creating a simple sequential build log. When renders with significant asynchronous work you may want to have each render run in parallel.
215+
216+
```
217+
new HtmlRenderPlugin({
218+
renderConcurrency: 'parallel'
219+
});
220+
```
221+
188222
# Examples
189223

190224
## Example: Basic
@@ -217,7 +251,7 @@ module.exports = [
217251
output: {
218252
filename: "client-[name]-[contenthash].js",
219253
}
220-
entry: { client: path.resolve("src", "client.js") }
254+
entry: path.resolve("src", "client.js")
221255
},
222256
{
223257
name: "render",
@@ -227,7 +261,7 @@ module.exports = [
227261
libraryTarget: "umd2",
228262
filename: "render-[name]-[contenthash].js",
229263
},
230-
entry: { render: path.resolve("src", "render.js") },
264+
entry: render: path.resolve("src", "render.js"),
231265
}),
232266
]
233267
```
@@ -284,7 +318,7 @@ module.exports = [
284318
output: {
285319
filename: "client-[name]-[contenthash].js",
286320
}
287-
entry: { main: path.resolve("src", "client.js") }
321+
entry: path.resolve("src", "client.js")
288322
},
289323
{
290324
name: "render",
@@ -294,7 +328,7 @@ module.exports = [
294328
libraryTarget: "umd2",
295329
filename: "render-[name]-[contenthash].js",
296330
},
297-
entry: { render: path.resolve("src", "render.js") },
331+
entry: path.resolve("src", "render.js"),
298332
}),
299333
]
300334
```
@@ -424,7 +458,7 @@ module.exports = ({ liveReload, mode }) => {
424458
},
425459
name: "client",
426460
target: "web",
427-
entry: { client: clientEntry },
461+
entry: clientEntry,
428462
plugins: [
429463
new ReactLoadablePlugin({
430464
filename: "react-loadable-manifest.json"
@@ -441,7 +475,7 @@ module.exports = ({ liveReload, mode }) => {
441475
},
442476
name: "render",
443477
target: "node",
444-
entry: { render: routes.renderEntry }
478+
entry: routes.renderEntry
445479
})
446480
];
447481
};

src/index.js

+43-33
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,39 @@ const returnEmptyObject = () => ({});
77
const defaultTransformFilePath = ({ route }) => route;
88

99
module.exports = class HtmlRenderPlugin {
10-
constructor(opts = {}) {
11-
this.clientBuilding = false;
12-
this.renderBuilding = false;
13-
this.log = (...args) =>
14-
(opts.log || console.log)(chalk.blue("HtmlRenderPlugin:"), ...args);
15-
this.logError = (...args) =>
16-
(opts.log || console.log)(chalk.red("🚨 HtmlRenderPlugin:"), ...args);
17-
this.renderEntry = opts.renderEntry || "render";
18-
this.routes = opts.routes || [""];
19-
this.verbose = opts.verbose || false;
20-
this.transformFilePath = opts.transformFilePath || defaultTransformFilePath;
21-
this.mapStatsToParams = opts.mapStatsToParams || returnEmptyObject;
22-
this.renderDirectory = opts.renderDirectory || "dist";
10+
constructor({
11+
log = console.log,
12+
verbose = false,
13+
routes = [""],
14+
mapStatsToParams = returnEmptyObject,
15+
renderDirectory = "dist",
16+
renderConcurrency = false,
17+
transformFilePath = defaultTransformFilePath,
18+
renderEntry = "main"
19+
} = {}) {
20+
this.log = log;
21+
this.mapStatsToParams = mapStatsToParams;
22+
this.renderDirectory = renderDirectory;
23+
this.renderEntry = renderEntry;
24+
this.routes = routes;
25+
this.transformFilePath = transformFilePath;
26+
this.verbose = verbose;
27+
this.renderConcurrency = renderConcurrency;
28+
2329
this.onRender = this.onRender.bind(this);
30+
this.logError = this.logError.bind(this);
31+
this.trace = this.trace.bind(this);
2432
}
25-
async onRender() {
33+
logError(...args) {
34+
this.log(chalk.red("🚨 HtmlRenderPlugin:"), ...args);
35+
}
36+
trace(...args) {
2637
if (this.verbose) {
27-
this.log(`Starting render`);
38+
this.log(chalk.blue("HtmlRenderPlugin:"), ...args);
2839
}
40+
}
41+
async onRender() {
42+
this.trace(`Starting render`);
2943

3044
const renderStats = this.renderCompilation.getStats();
3145

@@ -37,17 +51,17 @@ module.exports = class HtmlRenderPlugin {
3751

3852
try {
3953
await renderHtml({
40-
routes: this.routes,
54+
mapStatsToParams: this.mapStatsToParams,
55+
renderConcurrency: this.renderConcurrency,
4156
renderCompilation: this.renderCompilation,
42-
webpackStats,
43-
renderStats: renderStats.toJson(),
44-
transformFilePath: this.transformFilePath,
4557
renderCompiler: this.renderCompiler,
4658
renderDirectory: this.renderDirectory,
4759
renderEntry: this.renderEntry,
48-
mapStatsToParams: this.mapStatsToParams,
49-
verbose: this.verbose,
50-
log: this.log
60+
renderStats: renderStats.toJson(),
61+
routes: this.routes,
62+
trace: this.trace,
63+
transformFilePath: this.transformFilePath,
64+
webpackStats
5165
});
5266
} catch (error) {
5367
this.logError("An error occured rendering HTML", error);
@@ -92,21 +106,17 @@ module.exports = class HtmlRenderPlugin {
92106
childCompiler.hooks.afterEmit.tapPromise(
93107
hookOptions,
94108
async compilation => {
95-
if (this.verbose) {
96-
this.log(`Compiler emitted assets for ${childCompiler.name}`);
97-
}
98109
this.clientCompilation = compilation;
99110
if (compilersRunning > 0 || compilersToRun > 0) {
100-
if (this.verbose) {
101-
this.log(
102-
`Assets emitted for ${
103-
childCompiler.name
104-
}. Waiting for ${compilersRunning ||
105-
compilersToRun} other compilers`
106-
);
107-
}
111+
this.trace(
112+
`Assets emitted for ${
113+
childCompiler.name
114+
}. Waiting for ${compilersRunning ||
115+
compilersToRun} other compilers`
116+
);
108117
return;
109118
}
119+
this.trace(`Assets emitted for ${childCompiler.name}`);
110120
return this.onRender();
111121
}
112122
);

src/renderHtml.js

+19-19
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@ const path = require("path");
22
const chalk = require("chalk");
33
const createRenderer = require("./createRenderer");
44

5+
const timeSince = startTime => `${(Date.now() - startTime) / 1000}s`;
6+
57
module.exports = async function renderHtml({
8+
mapStatsToParams,
9+
renderConcurrency,
610
routes,
711
renderEntry,
812
renderDirectory,
913
renderStats,
10-
webpackStats,
1114
renderCompiler,
1215
renderCompilation,
13-
mapStatsToParams,
16+
trace,
1417
transformFilePath,
15-
verbose,
16-
log
18+
webpackStats
1719
}) {
1820
const renderFile = renderStats.assetsByChunkName[renderEntry];
19-
if (verbose) {
20-
log("Render file:", { renderFile });
21-
}
21+
trace("Render file:", { renderFile });
2222
if (!renderFile) {
2323
throw new Error(
2424
`Unable to find renderEntry ${renderEntry} in assets ${Object.keys(
@@ -36,9 +36,7 @@ module.exports = async function renderHtml({
3636
`Unable to find render function. File ${renderFile}. Recieved ${typeof renderFunc}.`
3737
);
3838
}
39-
if (verbose) {
40-
log(`Renderer created`);
41-
}
39+
trace(`Renderer created`);
4240

4341
async function emitFile(dir, content) {
4442
await new Promise((resolve, reject) =>
@@ -61,11 +59,10 @@ module.exports = async function renderHtml({
6159
}
6260

6361
async function render(routeValue) {
62+
const startRenderTime = Date.now();
6463
const routeData =
6564
typeof routeValue === "string" ? { route: routeValue } : routeValue;
66-
if (verbose) {
67-
log(`Starting render`, routeData);
68-
}
65+
trace(`Starting render`, routeData);
6966
if (typeof routeData.route !== "string") {
7067
throw new Error(
7168
`Missing route in ${JSON.stringify(
@@ -76,9 +73,7 @@ module.exports = async function renderHtml({
7673
const relativeFilePath = transformFilePath(routeData);
7774
const includesHtmlInFilePath = relativeFilePath.substr(-5) === ".html";
7875
if (!path.isAbsolute(renderDirectory)) {
79-
console.log({ renderDirectory });
8076
renderDirectory = path.resolve(renderDirectory);
81-
console.log({ renderDirectory });
8277
}
8378
const newFilePath = includesHtmlInFilePath
8479
? path.join(renderDirectory, relativeFilePath)
@@ -111,9 +106,14 @@ module.exports = async function renderHtml({
111106
}
112107

113108
await emitFile(newFilePath, renderResult);
114-
if (verbose) {
115-
log(`Successfully created asset ${newFilePath}`);
116-
}
109+
trace(
110+
`Successfully created ${newFilePath} (${timeSince(startRenderTime)})`
111+
);
112+
}
113+
if (renderConcurrency === "parallel") {
114+
return Promise.all(routes.map(render));
115+
}
116+
for (const route of routes) {
117+
await render(route);
117118
}
118-
return Promise.all(routes.map(render));
119119
};

tests/test-cases/async/__snapshots__/async.test.js.snap

+41
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,44 @@ Object {
1414
</html>",
1515
}
1616
`;
17+
18+
exports[`Render asyncronously should render a multiple files at once 1`] = `
19+
Object {
20+
"pageA": Object {
21+
"index.html": "<html>
22+
<body>
23+
Rendered with:&nbsp;
24+
<code>[
25+
{
26+
\\"route\\": \\"pageA\\"
27+
}
28+
]</code>
29+
</body>
30+
</html>",
31+
},
32+
"pageB": Object {
33+
"index.html": "<html>
34+
<body>
35+
Rendered with:&nbsp;
36+
<code>[
37+
{
38+
\\"route\\": \\"pageB\\"
39+
}
40+
]</code>
41+
</body>
42+
</html>",
43+
},
44+
"pageC": Object {
45+
"index.html": "<html>
46+
<body>
47+
Rendered with:&nbsp;
48+
<code>[
49+
{
50+
\\"route\\": \\"pageC\\"
51+
}
52+
]</code>
53+
</body>
54+
</html>",
55+
},
56+
}
57+
`;

tests/test-cases/async/async.test.js

+21
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,25 @@ describe("Render asyncronously", () => {
2323
done();
2424
});
2525
});
26+
it("should render a multiple files at once", async done => {
27+
const compiler = webpack(config);
28+
29+
const memoryFs = new MemoryFS();
30+
compiler.outputFileSystem = memoryFs;
31+
32+
compiler.apply(
33+
new HtmlRenderPlugin({
34+
parallelRender: true,
35+
routes: ["pageA", "pageB", "pageC"],
36+
renderDirectory
37+
})
38+
);
39+
40+
compiler.run(error => {
41+
expect(error).toBe(null);
42+
const contents = getDirContentsSync(renderDirectory, { fs: memoryFs });
43+
expect(contents).toMatchSnapshot();
44+
done();
45+
});
46+
});
2647
});

tests/test-cases/async/webpack.config.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module.exports = [
1111
name: "client",
1212
target: "web",
1313
mode: "production",
14-
entry: { client: paths.clientEntry },
14+
entry: paths.clientEntry,
1515
output: {
1616
filename: "client-[name]-[contenthash].js"
1717
}
@@ -21,7 +21,7 @@ module.exports = [
2121
name: "render",
2222
target: "node",
2323
mode: "production",
24-
entry: { render: paths.renderEntry },
24+
entry: paths.renderEntry,
2525
output: {
2626
libraryExport: "default",
2727
library: "static",

tests/test-cases/basic/basic.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ describe("Render HTML", () => {
6464
done();
6565
});
6666
});
67+
6768
it("should render routes with extra information", async done => {
6869
const compiler = webpack(config);
6970

0 commit comments

Comments
 (0)