html-render-webpack-plugin 0.0.0-feat-dx-followups-20261021095350
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +464 -0
- package/dist/RenderError.d.ts +3 -0
- package/dist/RenderError.js +11 -0
- package/dist/RenderError.js.map +1 -0
- package/dist/assets.d.ts +46 -0
- package/dist/assets.js +88 -0
- package/dist/assets.js.map +1 -0
- package/dist/common-types.d.ts +27 -0
- package/dist/common-types.js +3 -0
- package/dist/common-types.js.map +1 -0
- package/dist/createDevRouter.d.ts +11 -0
- package/dist/createDevRouter.js +92 -0
- package/dist/createDevRouter.js.map +1 -0
- package/dist/createRenderer.d.ts +8 -0
- package/dist/createRenderer.js +18 -0
- package/dist/createRenderer.js.map +1 -0
- package/dist/evaluateFromFileSystem.d.ts +3 -0
- package/dist/evaluateFromFileSystem.js +87 -0
- package/dist/evaluateFromFileSystem.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +250 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +3 -0
- package/dist/logging.js +14 -0
- package/dist/logging.js.map +1 -0
- package/dist/multiStats.d.ts +6 -0
- package/dist/multiStats.js +32 -0
- package/dist/multiStats.js.map +1 -0
- package/dist/renderRoutes.d.ts +13 -0
- package/dist/renderRoutes.js +107 -0
- package/dist/renderRoutes.js.map +1 -0
- package/dist/schemas/HtmlRenderWebpackPlugin.json +63 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2018 Jahred Hope
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# html-render-webpack-plugin
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/html-render-webpack-plugin)
|
|
4
|
+
|
|
5
|
+
Render HTML files with JavaScript during a webpack build.
|
|
6
|
+
|
|
7
|
+
- Supports [multiple configurations](https://webpack.js.org/configuration/configuration-types/#exporting-multiple-configurations)
|
|
8
|
+
- Supports [code-splitting](https://webpack.js.org/guides/code-splitting/) and [dynamic imports](https://webpack.js.org/guides/code-splitting/#dynamic-imports)
|
|
9
|
+
- Renders every route to a static file, or on demand through an Express dev router
|
|
10
|
+
- Passes client build stats into the renderer so hashed asset names are correct
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Node >= 22.13
|
|
15
|
+
- webpack ^5.0.0
|
|
16
|
+
- Express >= 4, only if you use `createDevRouter`
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add webpack html-render-webpack-plugin
|
|
22
|
+
# Only needed for the development server:
|
|
23
|
+
pnpm add express
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Express is an optional peer dependency. Build-only usage does not require it.
|
|
27
|
+
The published type declarations include the Express request and router types, so
|
|
28
|
+
TypeScript users do not need to install `@types/express` separately.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
A single webpack compilation can render HTML when it targets Node and exports a
|
|
33
|
+
function. This is the smallest useful setup.
|
|
34
|
+
|
|
35
|
+
**src/render.js**
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
export default function render({ route }) {
|
|
39
|
+
return `<!doctype html>
|
|
40
|
+
<html lang="en">
|
|
41
|
+
<body>
|
|
42
|
+
<h1>Rendered ${route}</h1>
|
|
43
|
+
</body>
|
|
44
|
+
</html>`;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**webpack.config.js**
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import path from "node:path";
|
|
52
|
+
|
|
53
|
+
import { HtmlRenderPlugin } from "html-render-webpack-plugin";
|
|
54
|
+
|
|
55
|
+
const htmlRenderPlugin = new HtmlRenderPlugin({
|
|
56
|
+
routes: ["/"],
|
|
57
|
+
renderDirectory: path.resolve("dist"),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export default {
|
|
61
|
+
name: "render",
|
|
62
|
+
target: "node",
|
|
63
|
+
mode: "production",
|
|
64
|
+
entry: path.resolve("src/render.js"),
|
|
65
|
+
output: {
|
|
66
|
+
// Keep the Node renderer out of the folder you deploy.
|
|
67
|
+
path: path.resolve(".render"),
|
|
68
|
+
filename: "render.js",
|
|
69
|
+
// The plugin evaluates the default export, so expose it as a UMD2 module.
|
|
70
|
+
library: { type: "umd2", export: "default" },
|
|
71
|
+
},
|
|
72
|
+
plugins: [htmlRenderPlugin.rendererPlugin],
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The build writes `dist/index.html`. The renderer bundle is written to
|
|
77
|
+
`.render/` and is not part of the static site.
|
|
78
|
+
|
|
79
|
+
CommonJS configs can use:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const { HtmlRenderPlugin } = require("html-render-webpack-plugin");
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Multiple configurations
|
|
86
|
+
|
|
87
|
+
Use two compilations when the renderer needs information from a browser build,
|
|
88
|
+
such as a hashed script filename.
|
|
89
|
+
|
|
90
|
+
Add `htmlRenderPlugin.statsCollectorPlugin` to every compilation whose stats the
|
|
91
|
+
renderer needs. Add `htmlRenderPlugin.rendererPlugin` to the compilation that
|
|
92
|
+
renders HTML.
|
|
93
|
+
|
|
94
|
+
**webpack.config.js**
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
import path from "node:path";
|
|
98
|
+
|
|
99
|
+
import {
|
|
100
|
+
HtmlRenderPlugin,
|
|
101
|
+
getEntrypointScriptUrls,
|
|
102
|
+
} from "html-render-webpack-plugin";
|
|
103
|
+
|
|
104
|
+
const htmlRenderPlugin = new HtmlRenderPlugin({
|
|
105
|
+
routes: ["/", "/about"],
|
|
106
|
+
renderDirectory: path.resolve("dist"),
|
|
107
|
+
// Keep the renderer boundary small: pass URLs, not the whole stats object.
|
|
108
|
+
mapStatsToParams: ({ webpackStats }) => ({
|
|
109
|
+
scripts: getEntrypointScriptUrls(webpackStats),
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export default [
|
|
114
|
+
{
|
|
115
|
+
name: "client",
|
|
116
|
+
target: "web",
|
|
117
|
+
mode: "production",
|
|
118
|
+
entry: path.resolve("src/client.js"),
|
|
119
|
+
output: {
|
|
120
|
+
path: path.resolve("dist"),
|
|
121
|
+
filename: "assets/[name].[contenthash].js",
|
|
122
|
+
chunkFilename: "assets/[name].[contenthash].js",
|
|
123
|
+
publicPath: "/",
|
|
124
|
+
},
|
|
125
|
+
plugins: [htmlRenderPlugin.statsCollectorPlugin],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "render",
|
|
129
|
+
target: "node",
|
|
130
|
+
mode: "production",
|
|
131
|
+
// Optional: the plugin already waits for every compilation. Declaring the
|
|
132
|
+
// dependency makes the intended build order explicit.
|
|
133
|
+
dependencies: ["client"],
|
|
134
|
+
entry: path.resolve("src/render.js"),
|
|
135
|
+
output: {
|
|
136
|
+
path: path.resolve(".render"),
|
|
137
|
+
filename: "[name].js",
|
|
138
|
+
library: { type: "umd2", export: "default" },
|
|
139
|
+
},
|
|
140
|
+
plugins: [htmlRenderPlugin.rendererPlugin],
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**src/render.js**
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
export default function render({ route, scripts }) {
|
|
149
|
+
return `<!doctype html>
|
|
150
|
+
<html lang="en">
|
|
151
|
+
<body>
|
|
152
|
+
<div id="app">Rendered ${route}</div>
|
|
153
|
+
${scripts
|
|
154
|
+
.map((src) => `<script src="${src}" defer></script>`)
|
|
155
|
+
.join("\n ")}
|
|
156
|
+
</body>
|
|
157
|
+
</html>`;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`getEntrypointScriptUrls(webpackStats, entrypoint = "main")` returns public URLs
|
|
162
|
+
for the JavaScript assets of an entrypoint. It handles hashed filenames,
|
|
163
|
+
`publicPath` values with or without a trailing slash, string assets and asset
|
|
164
|
+
objects. It accepts the stats of a single client compilation and the multi
|
|
165
|
+
compilation stats produced when several compilations use
|
|
166
|
+
`statsCollectorPlugin`. An unknown entrypoint name throws instead of returning an
|
|
167
|
+
empty list.
|
|
168
|
+
|
|
169
|
+
If you need more control, `mapStatsToParams` receives the route and the client
|
|
170
|
+
stats, and whatever it returns is merged into the renderer's parameters.
|
|
171
|
+
|
|
172
|
+
## Routes
|
|
173
|
+
|
|
174
|
+
`routes` accepts strings or objects. A `route` may start with or without a
|
|
175
|
+
leading slash. Paths are appended with `index.html` unless they already end in
|
|
176
|
+
`.html`.
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
const routes = [
|
|
180
|
+
"/", // dist/index.html
|
|
181
|
+
"/about", // dist/about/index.html
|
|
182
|
+
"contact", // dist/contact/index.html
|
|
183
|
+
{ route: "/blog/hello", title: "Hello", category: "news" },
|
|
184
|
+
];
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Extra keys on a route object are passed to the renderer, which makes routes a
|
|
188
|
+
convenient place for page metadata:
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
new HtmlRenderPlugin({
|
|
192
|
+
routes: [
|
|
193
|
+
{ route: "/", title: "Home", description: "Welcome" },
|
|
194
|
+
{ route: "/about", title: "About", description: "How it works" },
|
|
195
|
+
],
|
|
196
|
+
// mapStatsToParams also receives the route:
|
|
197
|
+
mapStatsToParams: ({ title, webpackStats }) => ({
|
|
198
|
+
title,
|
|
199
|
+
scripts: getEntrypointScriptUrls(webpackStats),
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The renderer receives `{ route, title, scripts, webpackStats, ... }` depending on
|
|
205
|
+
what `mapStatsToParams` returns.
|
|
206
|
+
|
|
207
|
+
## Options
|
|
208
|
+
|
|
209
|
+
### `routes`
|
|
210
|
+
|
|
211
|
+
**Default:** `[""]`
|
|
212
|
+
|
|
213
|
+
Routes to render. A file is created for each route. `""` and `"/"` both render
|
|
214
|
+
to `index.html`.
|
|
215
|
+
|
|
216
|
+
### `renderEntry`
|
|
217
|
+
|
|
218
|
+
**Default:** `"main"`
|
|
219
|
+
|
|
220
|
+
The webpack entry in the renderer compilation. Set this when the renderer uses
|
|
221
|
+
[object entry syntax](https://webpack.js.org/concepts/entry-points/#object-syntax).
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
new HtmlRenderPlugin({ renderEntry: "myRender" });
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### `renderDirectory`
|
|
228
|
+
|
|
229
|
+
**Default:** `"dist"`
|
|
230
|
+
|
|
231
|
+
The directory to write HTML files to, resolved from the current working
|
|
232
|
+
directory. This can differ from the renderer compilation's `output.path`; that
|
|
233
|
+
is the recommended setup, so the Node renderer bundle is not deployed with the
|
|
234
|
+
static site.
|
|
235
|
+
|
|
236
|
+
### `mapStatsToParams`
|
|
237
|
+
|
|
238
|
+
**Default:** `({ webpackStats }) => ({ webpackStats: webpackStats.toJson() })`
|
|
239
|
+
|
|
240
|
+
Builds the values passed to the render function for each route. The function
|
|
241
|
+
receives the route fields plus `webpackStats`, and its return value is merged
|
|
242
|
+
into the render parameters.
|
|
243
|
+
|
|
244
|
+
Prefer passing only what the renderer needs:
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
mapStatsToParams: ({ webpackStats }) => ({
|
|
248
|
+
scripts: getEntrypointScriptUrls(webpackStats),
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
In a single-compilation setup the renderer's own stats are used. If you do not
|
|
253
|
+
need stats at all, return an empty object:
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
mapStatsToParams: () => ({});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `transformFilePath`
|
|
260
|
+
|
|
261
|
+
**Default:** `(route) => route.route`
|
|
262
|
+
|
|
263
|
+
Changes the output path for a route.
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
new HtmlRenderPlugin({
|
|
267
|
+
routes: [
|
|
268
|
+
{ route: "/about", language: "en-us" },
|
|
269
|
+
{ route: "/about", language: "en-au" },
|
|
270
|
+
],
|
|
271
|
+
transformFilePath: ({ route, language }) => `${language}${route}`,
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This writes `en-us/about/index.html` and `en-au/about/index.html`.
|
|
276
|
+
|
|
277
|
+
### `renderConcurrency`
|
|
278
|
+
|
|
279
|
+
**Default:** `"serial"`
|
|
280
|
+
|
|
281
|
+
Use `"parallel"` when renders do a lot of asynchronous work.
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
new HtmlRenderPlugin({ renderConcurrency: "parallel" });
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### `skipAssets`
|
|
288
|
+
|
|
289
|
+
**Default:** `false`
|
|
290
|
+
|
|
291
|
+
When `true`, routes are not rendered to files after the build. Use this in watch
|
|
292
|
+
mode with `createDevRouter` or `renderWhenReady`, where pages are rendered on
|
|
293
|
+
demand.
|
|
294
|
+
|
|
295
|
+
### `transformExpressPath`
|
|
296
|
+
|
|
297
|
+
**Default:** `(route) => route.route`
|
|
298
|
+
|
|
299
|
+
Changes the path used by the dev router. Routes are normalized to a leading
|
|
300
|
+
slash, so `"about"` and `"/about"` both work. The default `""` route is served
|
|
301
|
+
at `/`. Return `undefined` to exclude a route from the dev router; an empty
|
|
302
|
+
string excludes every route except the default one.
|
|
303
|
+
|
|
304
|
+
```js
|
|
305
|
+
new HtmlRenderPlugin({
|
|
306
|
+
transformExpressPath: (route) =>
|
|
307
|
+
route.route === "/draft" ? undefined : `/static${route.route}`,
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### `getRouteFromRequest`
|
|
312
|
+
|
|
313
|
+
Used by the dev router to choose a route for a request. It must return one of the
|
|
314
|
+
configured routes, or `undefined` to pass the request to the next middleware.
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
new HtmlRenderPlugin({
|
|
318
|
+
routes: [
|
|
319
|
+
{ route: "/books/1", id: "1" },
|
|
320
|
+
{ route: "/books/2", id: "2" },
|
|
321
|
+
],
|
|
322
|
+
getRouteFromRequest: (req, routes) =>
|
|
323
|
+
routes.find((route) => route.id === req.params.id),
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### `extraGlobals`
|
|
328
|
+
|
|
329
|
+
Extra values to expose as free variables in the evaluated renderer bundle. Names
|
|
330
|
+
must be valid JavaScript identifiers and cannot be `exports`, `require`,
|
|
331
|
+
`module`, `__filename` or `__dirname`.
|
|
332
|
+
|
|
333
|
+
## Development server
|
|
334
|
+
|
|
335
|
+
The dev router renders configured routes on demand with the same renderer bundle
|
|
336
|
+
used by the production build. Use it with Express and
|
|
337
|
+
[`webpack-dev-middleware`](https://github.com/webpack/webpack-dev-middleware) so
|
|
338
|
+
client assets are served from memory.
|
|
339
|
+
|
|
340
|
+
Set `skipAssets: true` on the plugin when using the dev router. Share the same
|
|
341
|
+
plugin instance between the webpack configuration and the server.
|
|
342
|
+
|
|
343
|
+
**webpack.config.js**
|
|
344
|
+
|
|
345
|
+
```js
|
|
346
|
+
export const htmlRenderPlugin = new HtmlRenderPlugin({
|
|
347
|
+
routes: ["/", "/about"],
|
|
348
|
+
skipAssets: true,
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**dev-server.js**
|
|
353
|
+
|
|
354
|
+
```js
|
|
355
|
+
import express from "express";
|
|
356
|
+
import webpack from "webpack";
|
|
357
|
+
import devMiddleware from "webpack-dev-middleware";
|
|
358
|
+
|
|
359
|
+
import config, { htmlRenderPlugin } from "./webpack.config.js";
|
|
360
|
+
|
|
361
|
+
const compiler = webpack(config);
|
|
362
|
+
const app = express();
|
|
363
|
+
|
|
364
|
+
// Mount the renderer first, then serve client assets from webpack.
|
|
365
|
+
app.use(htmlRenderPlugin.createDevRouter());
|
|
366
|
+
app.use(devMiddleware(compiler, { publicPath: "/" }));
|
|
367
|
+
|
|
368
|
+
app.listen(3000, () => {
|
|
369
|
+
console.log("http://localhost:3000");
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
If you use webpack-dev-server v5 instead, mount the router in
|
|
374
|
+
`setupMiddlewares`:
|
|
375
|
+
|
|
376
|
+
```js
|
|
377
|
+
devServer: {
|
|
378
|
+
setupMiddlewares(middlewares, devServer) {
|
|
379
|
+
devServer.app.use(htmlRenderPlugin.createDevRouter());
|
|
380
|
+
return middlewares;
|
|
381
|
+
},
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The dev router does not add hot module replacement. It rerenders a route when it
|
|
386
|
+
is requested after a rebuild.
|
|
387
|
+
|
|
388
|
+
## Just-in-time rendering
|
|
389
|
+
|
|
390
|
+
`renderWhenReady` renders any route using the same renderer. It waits until the
|
|
391
|
+
build is ready, so it is safe to call during startup.
|
|
392
|
+
|
|
393
|
+
```js
|
|
394
|
+
app.get("/books/:id", (req, res) => {
|
|
395
|
+
res.send(await htmlRenderPlugin.renderWhenReady({ route: "/books/_id" }));
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Only use routes that also exist in the production build. Errors contain a
|
|
400
|
+
`webpackStats` property when stats are available.
|
|
401
|
+
|
|
402
|
+
## Output layout
|
|
403
|
+
|
|
404
|
+
A typical multi-compilation project looks like this:
|
|
405
|
+
|
|
406
|
+
```text
|
|
407
|
+
dist/ # deploy this
|
|
408
|
+
index.html
|
|
409
|
+
about/index.html
|
|
410
|
+
assets/main.<hash>.js
|
|
411
|
+
assets/<chunk>.<hash>.js
|
|
412
|
+
.render/ # renderer bundle, do not deploy
|
|
413
|
+
render.js
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Keep the renderer output outside `renderDirectory`. Avoid `output.clean` across
|
|
417
|
+
compilations that share an output directory; clean the directories yourself
|
|
418
|
+
before the build instead.
|
|
419
|
+
|
|
420
|
+
## TypeScript
|
|
421
|
+
|
|
422
|
+
The package ships declarations and exports the public option and route types:
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import {
|
|
426
|
+
HtmlRenderPlugin,
|
|
427
|
+
getEntrypointScriptUrls,
|
|
428
|
+
type BaseRoute,
|
|
429
|
+
type MapStatsToParams,
|
|
430
|
+
type Options,
|
|
431
|
+
} from "html-render-webpack-plugin";
|
|
432
|
+
|
|
433
|
+
interface Route extends BaseRoute {
|
|
434
|
+
title: string;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const mapStatsToParams: MapStatsToParams<Route> = ({
|
|
438
|
+
route,
|
|
439
|
+
title,
|
|
440
|
+
webpackStats,
|
|
441
|
+
}) => ({
|
|
442
|
+
route,
|
|
443
|
+
title,
|
|
444
|
+
scripts: getEntrypointScriptUrls(webpackStats),
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const options: Options<Route> = {
|
|
448
|
+
routes: [{ route: "/", title: "Home" }],
|
|
449
|
+
mapStatsToParams,
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const plugin = new HtmlRenderPlugin<Route>(options);
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Examples
|
|
456
|
+
|
|
457
|
+
The fixtures under [`tests/test-cases`](./tests/test-cases) are complete,
|
|
458
|
+
buildable examples, including single and multi-compilation setups, a dev server,
|
|
459
|
+
route metadata and asynchronous rendering.
|
|
460
|
+
|
|
461
|
+
## Migration
|
|
462
|
+
|
|
463
|
+
Upgrading from v1? See the
|
|
464
|
+
[v1 to v2 migration guide](./MIGRATION_GUIDE_v1_to_v2.md).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class RenderError extends Error {
|
|
4
|
+
constructor(error) {
|
|
5
|
+
super(`html-render-webpack-plugin: An error occurred during render: \n${error}`);
|
|
6
|
+
this.name = "RenderError";
|
|
7
|
+
Error.captureStackTrace(this, this.constructor);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.default = RenderError;
|
|
11
|
+
//# sourceMappingURL=RenderError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RenderError.js","sourceRoot":"","sources":["../src/RenderError.ts"],"names":[],"mappings":";;AAAA,MAAqB,WAAY,SAAQ,KAAK;IAC5C,YAAY,KAAY;QACtB,KAAK,CACH,kEAAkE,KAAK,EAAE,CAC1E,CAAC;QAEF,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAE1B,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IAClD,CAAC;CACF;AAVD,8BAUC"}
|
package/dist/assets.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { WebpackStats } from "./common-types";
|
|
2
|
+
interface StatsAssetLike {
|
|
3
|
+
name?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface WebpackStatsJson {
|
|
6
|
+
entrypoints?: Record<string, {
|
|
7
|
+
assets?: Array<string | StatsAssetLike>;
|
|
8
|
+
} | undefined>;
|
|
9
|
+
publicPath?: string;
|
|
10
|
+
/** Present on the object returned by `MultiStats.toJson()`. */
|
|
11
|
+
children?: WebpackStatsJson[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The parts of `webpack.MultiStats` this module uses. webpack does not export a
|
|
15
|
+
* public type for `MultiStats`.
|
|
16
|
+
*/
|
|
17
|
+
export interface MultiStatsLike {
|
|
18
|
+
toJson(options?: unknown): WebpackStatsJson | WebpackStatsJson[];
|
|
19
|
+
}
|
|
20
|
+
type StatsJsonInput = WebpackStatsJson | WebpackStatsJson[];
|
|
21
|
+
/**
|
|
22
|
+
* Get the public URLs of the JavaScript assets for one entrypoint.
|
|
23
|
+
*
|
|
24
|
+
* Accepts a single stats JSON object or an array of them. The object returned
|
|
25
|
+
* by `MultiStats.toJson()` for builds with more than one client compilation is
|
|
26
|
+
* supported through its `children` array.
|
|
27
|
+
*
|
|
28
|
+
* Pass `undefined` as the entrypoint to collect scripts from every entrypoint,
|
|
29
|
+
* which is useful for an error page that does not know which client entry failed.
|
|
30
|
+
* An unknown entrypoint name throws rather than returning an empty list.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getScriptUrlsFromStats(stats: StatsJsonInput, entrypoint?: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Get the public URLs of the JavaScript assets for an entrypoint.
|
|
35
|
+
*
|
|
36
|
+
* This is the common bridge from client stats to a renderer: the renderer can
|
|
37
|
+
* emit script tags without receiving the whole webpack stats object. It works
|
|
38
|
+
* for a single client compilation and for multiple client compilations.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* mapStatsToParams: ({ webpackStats }) => ({
|
|
42
|
+
* scripts: getEntrypointScriptUrls(webpackStats, "main"),
|
|
43
|
+
* })
|
|
44
|
+
*/
|
|
45
|
+
export declare function getEntrypointScriptUrls(webpackStats: WebpackStats | MultiStatsLike, entrypoint?: string): string[];
|
|
46
|
+
export {};
|
package/dist/assets.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getScriptUrlsFromStats = getScriptUrlsFromStats;
|
|
4
|
+
exports.getEntrypointScriptUrls = getEntrypointScriptUrls;
|
|
5
|
+
function getStatsList(stats) {
|
|
6
|
+
if (Array.isArray(stats)) {
|
|
7
|
+
return stats;
|
|
8
|
+
}
|
|
9
|
+
if (stats.children && stats.children.length > 0) {
|
|
10
|
+
return stats.children;
|
|
11
|
+
}
|
|
12
|
+
return [stats];
|
|
13
|
+
}
|
|
14
|
+
function getAssetName(asset) {
|
|
15
|
+
return typeof asset === "string" ? asset : asset.name;
|
|
16
|
+
}
|
|
17
|
+
function getPublicPath(stats) {
|
|
18
|
+
return typeof stats.publicPath === "string" && stats.publicPath !== "auto"
|
|
19
|
+
? stats.publicPath
|
|
20
|
+
: "/";
|
|
21
|
+
}
|
|
22
|
+
function joinPublicPath(publicPath, assetName) {
|
|
23
|
+
const base = publicPath === "" || publicPath.endsWith("/")
|
|
24
|
+
? publicPath
|
|
25
|
+
: `${publicPath}/`;
|
|
26
|
+
return `${base}${assetName.replace(/^\/+/, "")}`;
|
|
27
|
+
}
|
|
28
|
+
function getAssetNames(stats, entrypoint) {
|
|
29
|
+
var _a;
|
|
30
|
+
const entrypoints = (_a = stats.entrypoints) !== null && _a !== void 0 ? _a : {};
|
|
31
|
+
const groups = entrypoint === undefined
|
|
32
|
+
? Object.values(entrypoints)
|
|
33
|
+
: [entrypoints[entrypoint]];
|
|
34
|
+
return groups
|
|
35
|
+
.flatMap((group) => { var _a; return (_a = group === null || group === void 0 ? void 0 : group.assets) !== null && _a !== void 0 ? _a : []; })
|
|
36
|
+
.map(getAssetName)
|
|
37
|
+
.filter((name) => typeof name === "string" && name.endsWith(".js"));
|
|
38
|
+
}
|
|
39
|
+
function assertEntrypointExists(statsList, entrypoint) {
|
|
40
|
+
const available = new Set(statsList.flatMap((stats) => { var _a; return Object.keys((_a = stats.entrypoints) !== null && _a !== void 0 ? _a : {}); }));
|
|
41
|
+
if (!available.has(entrypoint)) {
|
|
42
|
+
const known = available.size > 0 ? [...available].join(", ") : "none";
|
|
43
|
+
throw new Error(`Unable to find entrypoint "${entrypoint}" in webpack stats. Available entrypoints: ${known}.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the public URLs of the JavaScript assets for one entrypoint.
|
|
48
|
+
*
|
|
49
|
+
* Accepts a single stats JSON object or an array of them. The object returned
|
|
50
|
+
* by `MultiStats.toJson()` for builds with more than one client compilation is
|
|
51
|
+
* supported through its `children` array.
|
|
52
|
+
*
|
|
53
|
+
* Pass `undefined` as the entrypoint to collect scripts from every entrypoint,
|
|
54
|
+
* which is useful for an error page that does not know which client entry failed.
|
|
55
|
+
* An unknown entrypoint name throws rather than returning an empty list.
|
|
56
|
+
*/
|
|
57
|
+
function getScriptUrlsFromStats(stats, entrypoint) {
|
|
58
|
+
const statsList = getStatsList(stats);
|
|
59
|
+
if (entrypoint !== undefined) {
|
|
60
|
+
assertEntrypointExists(statsList, entrypoint);
|
|
61
|
+
}
|
|
62
|
+
const urls = statsList.flatMap((statsJson) => {
|
|
63
|
+
const publicPath = getPublicPath(statsJson);
|
|
64
|
+
return getAssetNames(statsJson, entrypoint).map((name) => joinPublicPath(publicPath, name));
|
|
65
|
+
});
|
|
66
|
+
return [...new Set(urls)];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get the public URLs of the JavaScript assets for an entrypoint.
|
|
70
|
+
*
|
|
71
|
+
* This is the common bridge from client stats to a renderer: the renderer can
|
|
72
|
+
* emit script tags without receiving the whole webpack stats object. It works
|
|
73
|
+
* for a single client compilation and for multiple client compilations.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* mapStatsToParams: ({ webpackStats }) => ({
|
|
77
|
+
* scripts: getEntrypointScriptUrls(webpackStats, "main"),
|
|
78
|
+
* })
|
|
79
|
+
*/
|
|
80
|
+
function getEntrypointScriptUrls(webpackStats, entrypoint = "main") {
|
|
81
|
+
const statsJson = webpackStats.toJson({
|
|
82
|
+
all: false,
|
|
83
|
+
entrypoints: true,
|
|
84
|
+
publicPath: true,
|
|
85
|
+
});
|
|
86
|
+
return getScriptUrlsFromStats(statsJson, entrypoint);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type * as originalFs from "fs";
|
|
2
|
+
import type { Request } from "express";
|
|
3
|
+
import type { Stats } from "webpack";
|
|
4
|
+
export type WebpackStats = Stats;
|
|
5
|
+
export type BaseRoute = {
|
|
6
|
+
route: string;
|
|
7
|
+
};
|
|
8
|
+
export type Render<Route = BaseRoute> = (route: Route) => Promise<string>;
|
|
9
|
+
export type OnRendererReady<Route = BaseRoute> = (callback: (render: Render<Route>) => Promise<void>) => void;
|
|
10
|
+
export type Renderer = (...params: any[]) => Promise<string>;
|
|
11
|
+
export type SourceModules = Record<string, string>;
|
|
12
|
+
export type RouteInput<Route = BaseRoute> = Route | string;
|
|
13
|
+
export type Trace = (...values: any[]) => void;
|
|
14
|
+
export type Specifier = string;
|
|
15
|
+
export type ExtraGlobals = Record<string, unknown>;
|
|
16
|
+
export type RenderConcurrency = "parallel" | "serial";
|
|
17
|
+
export type MapStatsToParams<Route extends BaseRoute = BaseRoute> = (params: {
|
|
18
|
+
webpackStats: WebpackStats;
|
|
19
|
+
} & Route) => Record<string, unknown>;
|
|
20
|
+
export type TransformPath<Route = BaseRoute> = (route: Route) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Build the Express path for a route. Paths are normalized to a leading slash.
|
|
23
|
+
* Return `undefined` to exclude a route from the dev router.
|
|
24
|
+
*/
|
|
25
|
+
export type TransformExpressPath<Route = BaseRoute> = (route: Route) => string | undefined;
|
|
26
|
+
export type GetRouteFromRequest<Route> = (req: Request, routes: Route[]) => Route | undefined;
|
|
27
|
+
export type FileSystem = typeof originalFs;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common-types.js","sourceRoot":"","sources":["../src/common-types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Router } from "express";
|
|
2
|
+
import { OnRendererReady, BaseRoute, WebpackStats, TransformExpressPath, GetRouteFromRequest } from "./common-types";
|
|
3
|
+
interface Params<Route> {
|
|
4
|
+
routes: Route[];
|
|
5
|
+
getRouteFromRequest?: GetRouteFromRequest<Route>;
|
|
6
|
+
onRendererReady: OnRendererReady<Route>;
|
|
7
|
+
transformExpressPath: TransformExpressPath<Route>;
|
|
8
|
+
getClientStats: () => WebpackStats;
|
|
9
|
+
}
|
|
10
|
+
declare const _default: <Route extends BaseRoute>({ routes, getRouteFromRequest, onRendererReady, transformExpressPath, getClientStats, }: Params<Route>) => Router;
|
|
11
|
+
export = _default;
|