codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Follow publication

My Best webpack Configuration To Date: Part 2

--

Having written a large number of articles on webpack, I have settled on a final configuration (for now).

This is the second article in a two-part series, starting with My Best webpack Configuration To Date: Part 1.

css-modules-friendly

While CSS modules is excellent in scoping the application’s CSS ids and class names, without some additional configuration the following CSS…

./css-modules-friendly/src/components/App/Cat/index.css

#root {
display: inline-block;
padding: 10px;
background-color: yellow;
}

…shows up as follow in developer tools; ids and class names converted into an unintelligible hash.

The solution is to use different naming patterns for production (the hash) and development (combination of the name and hash).

./css-modules-friendly/webpack.config.js

...
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 2,
sourceMap: true,
localIdentName: env.NODE_ENV === 'production'
? '[hash:base64]'
: '[name]__[local]___[hash:base64:5]',
},
},
...

With this in place, the earlier ids are more readable in developer tools.

Also, it is good practice to only extract the CSS into files in the production builds; leaving it in the bundles during development. The solution here is to pull out the common code into a variable, cssLoaders, and then to use two different configurations for production and development.

../css-modules-friendly/webpack.config.js

...
module.exports = (env) => {
const cssLoaders = [
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 2,
sourceMap: true,
localIdentName: env.NODE_ENV === 'production'
? '[hash:base64]'
: '[name]__[local]___[hash:base64:5]',
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
{
loader: 'postcss-loader',
},
];

return ({
...
{
test: /\.(scss|css)$/,
use: env.NODE_ENV === 'production' ?
ExtractTextPlugin.extract({
use: cssLoaders,
fallback: 'style-loader',
}) :
[
{
loader: 'style-loader',
},
...cssLoaders,
],

},
...

Finally, it turns out that there is an extra required configuration when using ExtractTextPlugin along with CommonsChunkPlugin.

../css-modules-friendly/webpack.config.js

...
new UglifyJSPlugin({ sourceMap: true }),
new ExtractTextPlugin({
allChunks: true,
filename: 'styles.[contenthash].css',
}),
new HtmlWebpackPlugin({
...

react-system-import

While this series is really about webpack, thought it important to demonstration how bundle splitting can work with React.

In an earlier article, we used import to dynamically import bundles with plain JavaScript.

Now that we are linting, we need to make a slight adjustment in our eslint configuration to support dynamic imports. In particular, we will use babel-eslint.

yarn add babel-eslint --dev

and update…

./react-system-import/.eslintrc

{
"parser": "babel-eslint",
"env": {
"browser": true
},
"extends": [
"airbnb"
]
}

With this in place we change up the “routing” code as follows:

./react-system-import/src/components/App/index.jsx

import React, { Component } from 'react';
import asyncComponent from '../asyncComponent';
const Cat = asyncComponent(() => import('./Cat'));
const Dog = asyncComponent(() => import('./Dog'));

class App extends Component {
constructor(props) {
...

They key here is that we wrap our existing, e.g., Cat, component with a higher order component that dynamically imports the child component.

../react-system-import/src/components/asyncComponent.jsx

import React from 'react';export default getComponent => (
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = { DynamicComponent: null };
}
componentWillMount() {
getComponent().then(({ default: Component }) => {
this.setState({ DynamicComponent: Component });
});
}
render() {
const { DynamicComponent } = this.state;
if (DynamicComponent !== null) {
return <DynamicComponent {...this.props} />;
}
return null;
}
}
);

note: After I wrote this, I found that another group of folks took this same approach and handled some edge cases, in particular., network errors.

Using Webpack Bundle Analyzer, we get the following bundles:

The details show the bundles contain the following:

main.07ff49c2d9774f3ab60f.bundle.js

  • src/index.jsx
  • src/components/App
  • src/components/asyncComponent.js

vendor.e3e4e0338ddec206feee.bundle.js

  • node_modules/react-dom
  • node_modules/react
  • etc.

0.bf330b047c42c36ed992.bundle.js

  • node_modules/sillyname

1.2d48107a04a87548c0cc.bundle.js

  • src/components/Dog

2.153fbd59fcae1eafd01e.bundle.js

  • src/components/Cat

manifest.1a87e456708773eac8b4.bundle.js

Does not contain any code; just the manifest.

When you first load the application the following is loaded:

Clicking on the cat button loads the additional files:

Wrapping Up

This is the third time that I thought I had the final webpack configuration. Maybe this time is the charm; I suspect not.

Addendum

Got some feedback that I could reorganize the plugin section a bit to support another build so that you would not have to uncomment the bundle analyzer to run it. It is now setup to trigger off the analyze environment variable. To run, you simply type:

yarn run analyze

Enjoy

--

--

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Written by John Tucker

Broad infrastructure, development, and soft-skill background

Responses (2)

Write a response