Centric connect.engage.succeed

Webpacking your stuff away

Geschreven door Martijn Kloosterman - 03 november 2017

Martijn Kloosterman
Downloading separate JavaScript files, stylesheets, images and other assorted resources is so prehistoric; if you want your page loads to be fast, get with the programme and optimise your resource handling. Providing your client application with just the things it needs and trimming excess data should be an integral part of your development cycle. This has become even easier now that you can include webpack 2 in your development environment!

What is webpack?

Webpack is a tool that lets you create optimised bundles out of the static content of your application. It takes the modules that make up your application, figures out the interdependencies, and packs together just the bits needed in an optimised file. This file, also called a bundle, is built with JavaScript and provides your website with content – stylesheets, images or whatever else you can process through JavaScript. Anything that can’t be put into the bundle (exotic media types or large, incompressible files for example) will be available as direct downloads.

This is far from the only application that can help you optimise your resources; if you’ve been around the block you might have worked with requires, Browserify or similar methods. Webpack is a different kind of creature though: because transformations are triggered by file types or file names, it doesn’t need to rely on a task runner like Grunt or GulpJS. This is completely configurable to the webpack user.

Built on the principle of ‘You want to move files from point A to point B and do something with them along the way’, webpack relieves you of some of the humdrum plumbing work. It doesn’t really matter what that file is: anything that can be handled by JavaScript can be processed. Multi-step processing is also possible, like having a TypeScript file transpiled to JavaScript, which is then packed into bundle.js for example.

The only thing you need to ‘teach’ webpack is how to handle the different types of files it comes across and what to do with them. You do this by adding loaders and configuring them. Webpack’s configuration file is a JavaScript in its own right, meaning you can literally program the configuration to behave as desired for any situation – debug, production environments, treat the first 10 image files like this and the rest like that, etc. It’s very flexible. Unfortunately, this also means that if you do want to add non-default behaviour, you will need to do some additional building.

Please note that, at the time of writing, webpack v2.3.2[1] has just been released and this will be used in the examples in this article. As with any modern, rapidly evolving JavaScript tool, you must keep in mind that any sample code you find online might no longer function as intended. And webpack has the added fun of some of the examples you’ll come across while doing research not bothering to mention which version they were made for. Caveat emptor.

Setting up a sample implementation

You’ll need to have both NodeJS and NPM(2) installed. Luckily, the NodeJS installer includes both of these. Download the NodeJS installer from their site (get the LTS version) and ‘next-next-next’ your way through the dialogue screens. You can work with NPM directly on the command line (DOS, Bash or PowerShell), but I prefer using a tool like Visual Studio Code(3) since it comes with a built-in text editor and other handy functions.

If you want to create the example files by hand, you can do it like this. This is recommended, because you’re going to mess up and learn along the way. Anyway, create a new directory ‘webpack-example’ somewhere, and then add the following files:

{
  "name": "webpack-example",
  "main": "index",
  "dependencies": {
    "jquery": "3.1.1",
    "webpack": "2.2.1"
  },
  "devDependencies": {
    "@types/jquery": "2.0.40",
    "@types/node": "7.0.5",
    "css-loader": "0.26.2",
    "file-loader": "0.10.1",
    "http-server": "0.9.0",
    "less": "2.7.2",
    "less-loader": "2.2.3",
    "style-loader": "0.13.2",
    "ts-loader": "2.0.1",
    "ts-node": "2.1.0",
    "typescript": "2.2.1",
    "url-loader": "0.5.8",
    "webpack": "2.2.1"
  },
  "scripts": {
    "start": "npm run server",
    "server": "http-server -c-1",
    "webpack": "webpack",
    "webpack.watch": "webpack --watch",
    "webpack.optimize": "webpack --optimize-minimize"
  }
}

package.json

{
    "compilerOptions": {
        "target": "es5"
    }
}

tsconfig.json

<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <script type="text/javascript" src="dist/bundle.js"></script>
        <div class="logo"></div>
    </body>
</html>

index.html

const webpack = require('webpack');
const path = require('path');

module.exports = {
    entry: './src/bundle.entry.ts',
    output: {
        path: path.resolve(__dirname, 'dist'),
        publicPath: 'http://localhost:8080/dist/',
        filename: 'bundle.js'
    },
    module: {
        loaders: [
            {
                test: /\.ts$/,
                use: 'ts-loader'
            },
            {
                test: /\.less$/,
                use: [ 'style-loader', { 
                    loader: 'css-loader', 
                    options: { 
                        importLoaders: 1 
                    } 
                }, 'less-loader' ]
            },
            { 
               test: /\.(png|jpg|gif)$/, 
               loader: 'url-loader',
                query: {
                    limit: 4096
                } 
            }
        ],
    },
    resolve: {
        extensions: ['.ts', '.less']
    },
    plugins: [
        new webpack.LoaderOptionsPlugin({
            minimize: true,
            debug: false
        }),
        new webpack.ProvidePlugin({
            $: 'jquery'
        })
    ]
};

Next, create three new directories in the root of your webpack-example directory: content, dist and src. Add the following files to the src directory.

import './bundle.style';
import { giveMeSomeContent } from './bundle.content';
document.write(giveMeSomeContent());
$("span").css("border-style", "dashed"); 

src/bundle.entry.ts


export function giveMeSomeContent() {
    return '<span>This is content from the bundle.content module</span>';
}
export function giveMeSomeMoreContent() {
    return '<span>This is more content from the bundle.content module</span>';
}

src/bundle.content.ts

@import "bundle.style.variables";
body {
    background-color: @baseBackground;
    color: @baseColor;
    font-size: 20px;
}
.logo {
    width: 100px;
    height: 100px;
    border: solid 1px @baseColor;
    background-image: @baseImage;
}

src/bundle.style.less

src/bundle.style.variables.less

Finally, we need a smallish PNG image to demonstrate image file conversion. You can grab it directly from Github[4]. Drop it in the content directory. Almost done.

If you have GIT available, you can get the prepared example here: git clone https://github.com/rarz/webpack-example.git

Let’s get packing

Go to your ‘webpack-example’ directory, open a command line and enter the following commands:
npm install
npm run webpack

This should result in the following:

The thing to note here is the fact that the bundle.js comes in at 284KB. You can see webpack chew its way through your files in order, using the TypeScript and LESS loaders.

Webpack has done more than just put everything into bundle.js:

  • It has combined and transpiled both TypeScript files to JavaScript.
  • It has combined and converted the LESS files to CSS.
  • It has converted a PNG to a data:image url and included it in the CSS.
  • Last but not least, it has packed all the resulting files into a single file dist/bundle.js.

Yes, that JavaScript file includes the CSS. And the image. Everything is JavaScript! Funky, right? This does come at a price though: it’s larger than it would have been if it hadn’t bundled everything, but it probably loads faster.

The PNG image has been turned into a data:image string and included in the CSS. This technique lets you import small images into your CSS so that the client does not have to retrieve tiny files that take relatively long to fetch. For larger images this conversion is not beneficial. You can define the limit that governs this behaviour in the url-loader settings. Image files larger than the limit will not be changed, only renamed. Webpack renames all resource files to a random string, or adds a random string to prevent older content remaining cached. This technique is known as cache-busting.

Let’s see if we can get the bundle smaller:

npm run webpack.optimize

This does the same as in the previous run, but compacts the resulting code through minimisation, simply removing any excess comments and spaces.

It’s down to 95.3KB now. That includes everything needed to run the site in a single download. Can we go lower? Yes, we can!

Shaking that tree

Right now we’re downloading the entire JavaScript module that makes up the app. If you take a look at bundle.content.ts you’ll notice it has two functions, one of which is never called. There are no dependencies on it, so it’s basically wasted space. We need to get rid of it!

We could go to the file and delete the function, since it’s our own library. That would work, but would set a bad example. Also, deleting it by hand is too much work. What we want is for webpack to figure it out for us.

This technique is called tree shaking. It optimises the JavaScript by cutting modules into smaller bits and only including those that are actually dependent on each other. If a function isn’t needed, don’t bother packing it. Other optimisers and packers are not able to do it at such a granular level.

Edit the tsconfig.json file and change the value of target from ‘es5’ to ‘es6’. Webpack can’t shake any trees unless it can compile to EcmaScript 6[5]. Took me a while before I realised that. When done, we’ll process all the files again with:

npm run webpack.optimize

It’s smaller. Not by much, but 0.2KB evaporating shows that the tree shaking was successful. If you open the newly generated bundle.js file, you’ll see a mass of compacted JavaScript. Search for the string ‘this is content’. The packer has not excluded the function containing this text, as it is a dependency. Now search for ‘this is more content’. You won’t be able to find it, as it was removed from the module through the tree-shaking action.

What else can webpack do?

Here’s just a sample of what webpack can do.

It can cut the bundle.js file into chunks, each chunk covering just the functionality that your application needs at that moment. This is especially useful for Single Page Applications that only need specific scripts at specific moments.

Webpack provides you with a webpack-dev-server, a lightweight webserver that facilitates development.

Transformations are available for any type of file, including HTML. It can run your files through a template for example. Handy for including default stuff, such as footers and the like.

The webpack.config.js file is just as modular as the application itself. It can be put together to contain various configurations for different situations. Since it’s a JavaScript file in its own right, you can use functions in the configuration file. The sky’s the limit!

What will the future bring?

Webpack is under very active development, with new functionality being added on a regular basis. In the time it took me to put this text together, webpack went from version 2.1.1 up to 2.4.2. The packer and optimiser landscape is rolling past us at a fair speed. Grunt has been dethroned by GulpJS, which is in turn being overthrown by webpack. Browserify is fighting a valiant but losing battle against webpack. None of these alternatives are going to immediately disappear, but new projects should take a good look before committing to any solution, as is always the case.

There are concerns for webpack, as it has a steep learning curve when it comes to configuration settings when you are starting from scratch. Currently, it’s hitching a ride with Angular CLI’s and the Angular seed project start, largely pre-configured for immediate use. This gives a new wave of developers a chance to test the waters without having to immediately jump into the shark pool.

Want to go down this path? Don’t forget to pack everything you need along the way.

Links used in this post:

  1. https://webpack.github.io/
  2. https://nodejs.org/
  3. https://code.visualstudio.com/
  4. https://github.com/Rarz/webpack-example/blob/master/content/image.png
  5. https://www.typescriptlang.org/docs/handbook/compiler-options.html

Want to read more on the subject?

About Martijn
Craft Expert Martijn Kloosterman is part of the .NET team within Craft, the development programme for IT professionals (powered by Centric). If you would like to follow his blog, sign up for the monthly Craft update.

Want to know more about Craft, the development programme for IT professionals? Check out the website.

Tags:.NET

     
Schrijf een reactie
  • Captcha image
  • Verzenden