Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated output-management.md #1344

Merged
merged 15 commits into from
Jul 9, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions content/concepts/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,3 @@ As the compiler enters, resolves, and maps out your application, it keeps detail
So now you have a little bit of insight about how webpack works behind the scenes. "But, how does this affect me?", you might ask. The simple answer is that most of the time it doesn't. The runtime will do its thing, utilizing the manifest, and everything will appear to just magically work once your application hits the browser. However, if you decide to improve your projects performance by utilizing browser caching, this process will all of a sudden become an important thing to understand.

By using content hashes within your bundle file names, you can indicate to the browser when the contents of a file has changed thus invalidating the cache. Once you start doing this though, you'll immediately notice some funny behavior. Certain hashes change even when their contents apparently does not. This is caused by the injection of the runtime and manifest which changes every build.

See [the manifest section](/guides/output-management#the-manifest) of our _Managing Built Files_ guide to learn how to extract the manifest, and read the guides below to learn more about the intricacies of long term caching.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment below re the manifest -- if we do leave that section in then we should leave this in as well.

277 changes: 227 additions & 50 deletions content/guides/output-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,85 +3,262 @@ title: Output Management
sort: 4
contributors:
- skipjack
- TheDutchCoder
---

Managing webpack's [output](/configuration/output) and including it in your HTML files may not seem tough at first, but once you start [using hashes in filenames](/guides/caching) and outputting [multiple bundles](/guides/code-splitting), things can start to get a bit hairy. However, there's no need to fear as a few plugins exist that will make this process much easier to manage.
>T This guide extends on code examples found in the [`Asset Management`](/guides/asset-management) guide.

First let's take a look at where you might stand without these plugins:
So far we've manually included all our assets in our `index.html` file, but as your application grows and once you start [using hashes in filenames](/guides/caching) and outputting [multiple bundles](/guides/code-splitting-libraries), it will be difficult to keep managing your `index.html` file manually. However, there's no need to fear as a few plugins exist that will make this process much easier to manage.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The multiple bundles link should just point to /guides/code-splitting.


__index.html__
## Preparation ##
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove the trailing ## on this title and the others for consistency with the rest of the markdown files. I didn't realize that was valid markdown though, interesting...


``` html
<!doctype html>
First, let's adjust our project a little bit:

__dist/index.html__

``` diff
<html>
<head>
- <title>Asset Management</title>
+ <title>Output Management</title>
+ <script src="./vendor.bundle.js"></script>
</head>
<body>
- <script src="./bundle.js"></script>
+ <script src="./app.bundle.js"></script>
</body>
</html>
```

Now adjust the config. We'll be adding a vendor entry point as an example and we'll change the output as well, so that it will dynamically generate bundle names, based on the entry point names:

__webpack.config.js__

``` diff
const path = require('path');

module.exports = {
entry: {
- index: './src/index.js',
+ app: './src/index.js',
+ vendor: ['lodash']
},
output: {
- filename: 'bundle.js',
+ filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
```

Let's run `npm run build` and see what this generates:

``` bash
Hash: 5f3b0265b87c603b4a0f
Version: webpack 2.6.1
Time: 539ms
Asset Size Chunks Chunk Names
vendor.bundle.js 544 kB 0 [emitted] [big] vendor
app.bundle.js 2.81 kB 1 [emitted] app
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll need to include the CommonsChunkPlugin for this to work -- meaning I think your app.bundle.js will actually have lodash included in it as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss this one, maybe there's a simpler example that we can use as we should probably hold off on introducing the CommonsChunkPlugin until Code Splitting.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You seem to be right, maybe we can just remove the lodash import from the main script for this example?

Copy link
Collaborator

@skipjack skipjack Jul 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could work, however then the vendor bundle wouldn't really be used anywhere. How about we leave lodash but don't extract it and instead of vendor we add separate entry script that does something else to the page? This way that second bundle will be automatically included by the HTMLWebpackPlugin (i.e. it should serve as a good way to illustrate the ideas of this article) but doesn't require any extra complexity in terms of plugins or configuration.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I didn't think about that.

Adding something else in might be a good idea, what would you propose?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm I'll give this some thought. Nothing jumping to mind at the moment. Maybe inserting another button that does something?

Copy link
Collaborator Author

@TheDutchCoder TheDutchCoder Jul 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scratch this.

How about something like a print.js script that outputs something on the console.
We import it in index.js and attach it to a button.

With the HtmlWebpackPlugin it then gets automatically added.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I've set up a repo to host the code examples for the guides.

It's under my account for now, but we could move it over to the webpack organization, that would be fine by me.

I'll go through the output-management code examples, based off the end state of the asset-management one and I'll see what I can add as a vendor entry that makes sense.

[0] ./~/lodash/lodash.js 540 kB {0} [built]
[1] (webpack)/buildin/global.js 509 bytes {0} [built]
[2] (webpack)/buildin/module.js 517 bytes {0} [built]
[3] ./src/index.js 172 bytes {1} [built]
[4] multi lodash 28 bytes {0} [built]
```

We can see that webpack generates our `vendor.bundle.js` and `app.bundle.js` files, which we also specified in our `index.html` file. But what would happen if we changed the name of one of our entry points? The generated bundles would be renamed on a build, but our `index.html` file would still reference the old names. Let's fix that with the [`HtmlWebpackPlugin`](/plugins/html-webpack-plugin).


## Setting up HtmlWebpackPlugin ##

First install the plugin and adjust the `webpack.config.js` file:

``` bash
npm install --save-dev html-webpack-plugin
```

__webpack.config.js__

``` diff
const path = require('path');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
app: './src/index.js',
vendor: ['lodash']
},
+ plugins: [
+ new HtmlWebpackPlugin()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things... I think you need to first show that the old index.html file is removed, maybe via a project diff or at least make it clear that it's getting overwritten. The current setup will just overwrite the old one. The second, I think you should pass a title here:

HtmlWebpackPlugin({
  title: 'Output Management'
})

+ ],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
```

Now run `npm run build` and you should see something similar to this:

``` bash
Hash: 81f82697c19b5f49aebd
Version: webpack 2.6.1
Time: 854ms
Asset Size Chunks Chunk Names
vendor.bundle.js 544 kB 0 [emitted] [big] vendor
app.bundle.js 2.81 kB 1 [emitted] app
index.html 249 bytes [emitted]
[0] ./~/lodash/lodash.js 540 kB {0} [built]
[1] (webpack)/buildin/global.js 509 bytes {0} [built]
[2] (webpack)/buildin/module.js 517 bytes {0} [built]
[3] ./src/index.js 172 bytes {1} [built]
[4] multi lodash 28 bytes {0} [built]
Child html-webpack-plugin for "index.html":
[0] ./~/lodash/lodash.js 540 kB {0} [built]
[1] ./~/html-webpack-plugin/lib/loader.js!./~/html-webpack-plugin/default_index.ejs 538 bytes {0} [built]
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
```

If you open `index.html` in your code editor, you'll see that the `HtmlWebpackPlugin` has created an entirely new file for you and that all the bundles are automatically added. This is great, but it also has overwritten everything else we had in our file.

In most cases you probably want to provide a certain template to the `HtmlWebpackPlugin`, so that you can still have your own `index.html` file, but have webpack automatically add generated files.


## Adding a template to HtmlWebpackPlugin ##
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would drop this as an entire section and, if anything, just mention that you can pass a custom template if necessary, maybe linking to that . It's also probably worth linking to the popular html-webpack-template package as you can get pretty far with that.

I have multiple projects that use this plugin and haven't needed to write a custom template -- plus the things you added with the custom template are available from the base plugin (and almost anything else you'd need is available from html-webpack-template). I know this might sound nitpicky but I think we should really try to avoid replicating documentation and instead focus on just introducing a variety of approaches and then let the user take their own path from there.


Let's add a template that `HtmlWebpackPlugin` can use, but we keep it in our `src` directory instead.

>T `HtmlWebpackPlugin` uses the `.ejs` extension by default for templates.

__project__

``` diff
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- bundle.js
- |- index.html
|- /src
+ |- index.ejs
|- index.js
|- /node_modules
```

__src/index.ejs__

``` html
<html>
<head>
<title>Output Management</title>
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="stylesheet" href="/styles.min.css" />
<script src="/vendor.bundle.js"></script>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<script src="/app.bundle.js"></script>
<h1>Output Management</h1>
</body>
</html>
```

Here we've loaded a favicon, our stylesheet (extracted with the [`ExtractTextWebpackPlugin`](/plugins/extract-text-webpack-plugin)), any libraries (split into [a separate bundle](/guides/code-splitting)), and finally our main bundle (`app.bundle.js`). This is ok, but what happens if we change our entry point names? What if we decide to take advantage of [better caching practices](/guides/caching)?
Now adjust your `webpack.config.js` and tell `HtmlWebpackPlugin` to use our new template:

__webpack.config.js__

## Auto-Generated HTML
``` diff
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

In comes the [`HtmlWebpackPlugin`](/plugins/html-webpack-plugin) to save the day. Using this plugin, you can stop hard-coding the output filenames into a manually-managed file and, instead, sit back as webpack auto-generates an `index.html` file for you. Let's take a look at what you'll need to add to your configuration:
module.exports = {
entry: {
app: './src/index.js',
vendor: ['lodash']
},
plugins: [
- new HtmlWebpackPlugin()
+ new HtmlWebpackPlugin({
+       title: 'Output Management',
+       filename: 'index.html',
+ template: 'src/index.html'
+ })
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
```

__webpack.config.js__
Now run `npm run build` again, which should yield a similar result as in the last step:

``` js
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
app: './src/index.js',
vendor: [ 'react', 'react-dom' ]
},

output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},

plugins: [
new HtmlWebpackPlugin({
title: 'Output Management',
favicon: './favicon.ico'
})
]
};
``` bash
Hash: 3adf5eed79147a35559e
Version: webpack 2.6.1
Time: 844ms
Asset Size Chunks Chunk Names
vendor.bundle.js 544 kB 0 [emitted] [big] vendor
app.bundle.js 2.81 kB 1 [emitted] app
index.html 244 bytes [emitted]
[0] ./~/lodash/lodash.js 540 kB {0} [built]
[1] (webpack)/buildin/global.js 509 bytes {0} [built]
[2] (webpack)/buildin/module.js 517 bytes {0} [built]
[3] ./src/index.js 172 bytes {1} [built]
[4] multi lodash 28 bytes {0} [built]
Child html-webpack-plugin for "index.html":
[0] ./~/lodash/lodash.js 540 kB {0} [built]
[1] ./~/html-webpack-plugin/lib/loader.js!./src/index.ejs 540 bytes {0} [built]
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
```

This setup will generate the same HTML shown in the example above, excluding the extracted CSS. The plugin allows for many settings including extending your own template, minifying the output, and changing the script injection site. See [its documentation](/plugins/html-webpack-plugin) for more details.
Open `index.html` in your code editor again. This time you will see that the `HtmlWebpackPlugin` has dynamically added a title (which we specified in our `webpack.config.js` at the `HtmlWebpackPlugin` configuration options), as well as the generated bundles.

T> Check out the [`HTMLWebpackTemplate`](https://github.com/jaketrent/html-webpack-template) extension for even more options including easy insertion of an `appMountId`, `meta` tags, and a `baseHref`.
This is great, because now we don't have to manually adjust our `index.html` file anymore, webpack takes care of that. As an added bonus, we now keep the `index.ejs` template in our `src` directory, so our `dist` directory only contains generated files.

T> Check out the [`HtmlWebpackTemplate`](https://github.com/ampedandwired/html-webpack-plugin#configuration) page for more advanced options, including outputting multiple HTML files.

## Multiple HTML Files

To generate multiple HTML files, say for a multi-page web application, you can utilize the [`MultipageWebpackPlugin`](https://github.com/mutualofomaha/multipage-webpack-plugin). This plugin is similar to the `html-webpack-plugin`, in fact it uses that plugin under the hood, however it can be used to generate an HTML file per entry point.
## Cleaning up the `/dist`folder ##
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need a space between /dist and "folder". I do like the mention and use of the clean-webpack-plugin, and I think with these two included we should basically stop diffing or showing the files within /dist in our project diffs.


As you might have noticed over the past guides and code example, our `/dist` folder has become quite cluttered. Webpack will generate the files and put them in the `/dist` folder for you, but it doesn't keep track of which files are actually in use by your project.

## The Manifest
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why scrap this section? I know it's not easy to do a fully fleshed out example but we could leave it in for now and just say something like:

We're not going to go through a full example of how to use this plugin within our projects but here's the basics and you can read through the plugin's documentation to learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly thought it was way too advanced to be useful, but I'll add it back in with some adjustments and link off to the right resources in case people are interested in it.

In general it's good practice to clean the `/dist` folder before each build, so that only used files will be generated. Let's take care of that.

Let's step back for a second now and ask a more high-level question -- how do these plugins know what files are being spit out? The answer is in the manifest webpack keeps to track how all your modules map to the output bundles. If you're interested in managing webpack's [output](/configuration/output) in other ways, the manifest would be a good place to start.
A popular plugin to manage this is the [`clean-webpack-plugin`](https://www.npmjs.com/package/clean-webpack-plugin) so let's install and configure it.

This data can be extracted into a json file for easy consumption using the [`WebpackManifestPlugin`](https://github.com/danethurber/webpack-manifest-plugin) or [`ChunkManifestPlugin`](https://github.com/soundcloud/chunk-manifest-webpack-plugin). Using the `ChunkManifestPlugin`, you would specify what to name it, what variable to expose it under, and whether or not to inline it via the `html-webpack-plugin`:
``` bash
npm install clean-webpack-plugin --save-dev
```

``` js
new ChunkManifestPlugin({
filename: 'manifest.json',
manifestVariable: 'webpackManifest',
inlineManifest: false
})
__webpack.config.js__
``` diff
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
entry: {
app: './src/index.js',
vendor: ['lodash']
},
plugins: [
new HtmlWebpackPlugin({
      title: 'Output Management',
      filename: 'index.html',
template: 'src/index.html'
- })
+ }),
new CleanWebpackPlugin(['dist'])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe put this before the HtmlWebpackPlugin as logically it comes first -- e.g. /dist is first stripped of old contents and then new contents is added.

],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
```

See [the concepts page](/concepts/manifest) for more background information and the [caching guide](/guides/caching) guide to find out how this ties into long term caching.
Now run an `npm run build` and inspect the `/dist` folder. If everything went well you should now only see the files generated from the build and no more old files!


## Conclusion ##

Now that you've learned about dynamically adding bundles to your HTML, let's dive into the next guide where this will be very useful: [`Code Splitting`](/guides/code-splitting).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though Code Splitting points to this as a pre-requisite, I think we should just point to the next guide down which I believe is Development so people can go through in order. You could also point to both and mention that Code Splitting is more advanced.