Skip to content

Commit

Permalink
feat: Add just-in-time rendering, TypeScript support and improved API
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Changes to webpackStats passed to mapStatsToParams and new plugin API. See [MIGRATION_GUIDE_v1_to_v2.md](./MIGRATION_GUIDE_v1_to_v2.md) for more information.

**Features**
- Add ability to create an Express Middleware to perform just-in-time rendering during development
- Add support for skipping rendering files at end of build
- Add support for *transformExpressPath* to much like *transformFilePath*
- Add support for manually calling renders with *renderWhenReady*
- Add TypeScript support

**Changes**
- Migrate to usage based naming.`htmlRenderPlugin.statsCollectorPlugin` for stat collection and `rendererPlugin` for rendering.
- Deprecate previous `render()` syntax
- Rewrite in Typescript.
- Rewrite in functional syntax. Still uses Class syntax to reduce breaking changes.
- Remove deprecated *multiCompiler* apply capability
- Pass *webpackStats* through by default to render function
- Removed *render* build from being added to *webpackStats* by default. This is now an opt-in behaviour
- Removed *verbose* option. Migrated to [debug](https://www.npmjs.com/package/debug) for debug logging.
  • Loading branch information
jahredhope authored Nov 27, 2019
1 parent a953ce9 commit 203a21b
Show file tree
Hide file tree
Showing 56 changed files with 3,476 additions and 2,264 deletions.
19 changes: 17 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
module.exports = {
parser: "babel-eslint",
extends: ["eslint:recommended"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: "plugin:@typescript-eslint/recommended",
overrides: [
{
files: ["**/*.js"],
rules: {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/explicit-function-return-type": "off"
}
}
],
parserOptions: {
ecmaVersion: 2018,
sourceType: "module"
Expand All @@ -11,6 +21,11 @@ module.exports = {
node: true
},
rules: {
"@typescript-eslint/ban-ts-ignore": "warn",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"no-console": "off",
"no-inner-declarations": "off"
}
Expand Down
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
/node_modules/
.git
yarn.lock
/yarn-error.log
60 changes: 60 additions & 0 deletions MIGRATION_GUIDE_v1_to_v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Migration Guide v1.x to v2.x

## WebpackStats nolonger includes the render config by default.

```js
plugins: [htmlRenderPlugin.rendererPlugin];
```

To recreate the previous behaviour pass the stats collector to render config

```js
plugins: [
htmlRenderPlugin.statsCollectorPlugin,
htmlRenderPlugin.rendererPlugin
];
```

Where builds have two configurations (client and render) this will now mean your stats file will be a standard Stats file and not a MultiStats file.

## Remove Support for "Alternative multiple configuration setup"

This method was deprecated in the previous major release and has now been removed.

Example:

```js
new HtmlRenderPlugin().apply(multiCompiler);
```

## Deprecate the calling of `render()` and passing in the plugin directly

This syntax is still supported until the next major release but the prefered syntax is listed below.

**Render**

Before:

```js
htmlRenderPlugin.render();
```

After:

```js
htmlRenderPlugin.rendererPlugin;
```

**Client**

Before:

```js
htmlRenderPlugin;
```

After:

```js
htmlRenderPlugin.statsCollectorPlugin;
```
156 changes: 79 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,15 @@ $ yarn add webpack html-render-webpack-plugin
```js
module.exports = {
...,
plugins: [new HtmlRenderPlugin().render()]
plugins: [new HtmlRenderPlugin().rendererPlugin]
};
```

# Multiple configurations

Apply `html-render-webpack-plugin` to your webpack [MultiCompiler](https://webpack.js.org/configuration/configuration-types/#exporting-multiple-configurations) to enable rendering based on all resulting webpack outputs.
If you use [multiple webpack configurations](https://webpack.js.org/configuration/configuration-types/#exporting-multiple-configurations) you may want to add information from other builds when rendering.

The code from the render build is used to generate the HTML, often using values from other builds such as asset names of created assets.

For example, you may wish to add a script tag where the name includes a hash:
For example, you may wish to add a script tag where the name includes a hash. The asset name comes from the output of one build (browser assets) whilst the render is performed in another build (node rendering).

**src/render.js**

Expand Down Expand Up @@ -56,7 +54,11 @@ See [the full example below](#example-client-assets).

## Multiple configuration setup

Pass an instance of HtmlRenderPlugin to each configuration that should be available during render. Call `.render()` on the configuration that should be used to render.
Add `htmlRenderPlugin.statsCollectorPlugin` to the plugins of all configurations you want to get stats for.

Add `htmlRenderPlugin.render` to the plugin of the configuration you want to use to render html.

HtmlRenderPlugin will then pass the [Webpack Stats](https://webpack.js.org/api/stats/) for those builds into your render function.

**webpack.config.js**

Expand All @@ -68,82 +70,25 @@ module.exports = [
{
name: "render",
target: "node", // Creates assets that render HTML that runs well in node
plugins: [htmlRenderPlugin.render()]
plugins: [htmlRenderPlugin.rendererPlugin]
},
{
name: "client",
target: "web", // Creates files that run on the browser
plugins: [htmlRenderPlugin]
plugins: [htmlRenderPlugin.statsCollectorPlugin]
}
];
```

See [examples](#examples) for more details.

### Alternative multiple configuration setup

Instead of applying the plugin to each configuration you can apply the plugin to the parent [MultiCompiler](https://webpack.js.org/configuration/configuration-types/#exporting-multiple-configurations).

This is not recommended and may eventually deprecated in future releases.

**webpack.config.js**

```js
module.exports = [
{
name: "render",
target: "node" // Creates assets that render HTML that runs well in node
},
{
name: "client",
target: "web" // Creates files that run on the browser
}
];
```

Use the [webpack Node API](https://webpack.js.org/api/node/) to create a `MultiCompiler`.

```js
const webpack = require("webpack");
const config = require("./webpack.config");
const multiCompiler = webpack(config);
```

Apply the plugin to your compiler.

```js
const HtmlRenderPlugin = require("html-render-webpack-plugin");

new HtmlRenderPlugin().apply(multiCompiler);
```

Start the build

```js
// Single build
multiCompiler.run((error, stats) => {});
```

```js
// Development server
const DevServer = require("webpack-dev-server");

const server = new DevServer(multiCompiler, {
compress: true
});
server.listen(8080, "localhost", () => {});
```

# Options

## Option: renderEntry _string_

**default:** "main"

The entry to use when rendering. Override when using [object syntax](https://webpack.js.org/concepts/entry-points/#object-syntax) in webpack entry.

```js
```
The [webpack entry](https://webpack.js.org/concepts/entry-points/) to use when rendering. Override when using [object syntax](https://webpack.js.org/concepts/entry-points/#object-syntax).

**webpack.config.js**

Expand All @@ -155,7 +100,7 @@ module.exports = {
plugins: [
new HtmlRenderPlugin({
renderEntry: "myRender"
}).render()
}).render
]
};
```
Expand Down Expand Up @@ -202,12 +147,12 @@ const routes = [

## Option: mapStatsToParams _Function_

**default:** `({webpackStats, ...route}) => ({})`
**default:** `({webpackStats, ...route}) => ({ webpackStats })`

mapStatsToParams should accept webpackStats and [route](#option-routes-arrayobjectstring) information and returns values to be passed into render.
The function is called individually for each render.

**Recommendation:** mapStatsToParams is an opportunity to limit what information is provided to your render function. Keeping the boundary between your build code and application code simple. Avoid passing all webpackStats into your render function, pull out only the information needed.
**Recommendation:** mapStatsToParams is an opportunity to limit what information is provided to your render function. Keeping the boundary between your build code and application code simple. Avoid passing all webpackStats into your render function, pull out only the information needed. It is recommended to override the default mapStatsToParams behaviour.

## Option: transformFilePath _Function_

Expand Down Expand Up @@ -247,6 +192,61 @@ new HtmlRenderPlugin({
});
```

## Option: skipAssets _Function_

**default:** `false`

After waiting for all builds to complete HtmlRenderPlugin will render all routes. For particularly large projects with a large number of routes this can take some time. For watch builds you may want to skip emitting assets, relying on `createDevRouter` instead.

## Option: transformExpressPath _Function_

**default:** `(route) => route.route ? route.route : route`

When creating a dev router each route will be attached to the router using it's `route` value.

If you want to use a different express route you can provide a `transformExpressPath` function.

# Dev Server

Create an [Express Middleware](https://expressjs.com/en/guide/using-middleware.html) to attach to Webpack Dev Server to speed up development builds.

For particularly large projects with slow renders and a large number of routes rendering every route on every build can slow down development. The dev server allows you to only render the pages as they are needed during development, whilst ensuring the resulting render works like the full production render.

Using the [Webpack Dev Server Node API](https://github.com/webpack/webpack-dev-server/blob/master/examples/api/simple/server.js#L14) create a dev server and attach the dev HtmlRenderPlugin router to it. When pages are requested they will be rendered just-in-time, using the same method of rendering as production.

```js
const htmlRenderPlugin = new HtmlRenderPlugin({
routes,
skipAssets: true
});

const compiler = webpack(config);

const webpackDevServer = new WebpackDevServer(compiler);

webpackDevServer.use(htmlRenderPlugin.createDevRouter());
```

**Note:** Ensure that you use the same htmlRenderPlugin created for your webpack configuration as you use for your dev server.

# Manual just-in-time rendering

As an alternative to using the default dev server you can access `renderWhenReady` to apply your own just-in-time rendering.

Just call `renderWhenReady` with any route, and the next time the renderer is ready the render will be performed.

**Note:** Be careful to only use routes that are generated in a production. Not doing this can lead to differences between development and production builds.

Example: Using an Express App to render a dynamic path with ids.

```js
app.get('/books/:id', (req, res) => {
res.send(await htmlRenderPlugin.renderWhenReady({route: '/books/_id'}))
})
```

Errors returned during this render will contain a `webpackStats` attribute when available. This can be useful when rendering your own error pages.

# Examples

## Example: Client assets
Expand All @@ -256,10 +256,10 @@ An example of using `mapStatsToParams` to create `<script>` tags.
**src/render.js**

```js
export default ({ clientStats }) => {
export default ({ mainChunk }) => {
return `<html>
<body>
<script src="${clientStats.assetsByChunkName.main}"></script>
<script src="${mainChunk}"></script>
</body>
</html>`;
};
Expand All @@ -270,11 +270,9 @@ export default ({ clientStats }) => {
```js
const path = require("path");

const htmlRenderPlugin = new HtmlRenderPlugin({
const { htmlRenderPlugin, htmlRenderClientPlugin } = createHtmlRenderPlugin({
mapStatsToParams: ({ webpackStats }) => ({
clientStats: webpackStats
.toJson()
.children.find(({ name }) => name === "client")
mainChunk: webpackStats.toJson().assetsByChunkName.main
})
});

Expand All @@ -286,7 +284,7 @@ module.exports = [
filename: "client-[name]-[contenthash].js"
},
entry: path.resolve("src", "client.js"),
plugins: [htmlRenderPlugin]
plugins: [htmlRenderPlugin.statsCollectorPlugin]
},
{
name: "render",
Expand All @@ -297,7 +295,11 @@ module.exports = [
filename: "render-[name]-[contenthash].js"
},
entry: path.resolve("src", "render.js"),
plugins: [htmlRenderPlugin.render()]
plugins: [htmlRenderPlugin.rendererPlugin]
}
];
```

# Migration Guides

Migration from v1 to v2? Checkout the [Migration Guide](./MIGRATION_GUIDE_v1_to_v2.md).
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
testEnvironment: "node",
transform: {},
watchPathIgnorePatterns: ["/dist/"]
watchPathIgnorePatterns: ["/dist/"],
preset: "ts-jest"
};
Loading

0 comments on commit 203a21b

Please sign in to comment.