Graduate Program KB

Evolution

  • JavaScript

    • Adding script tag, passing src attribute and a reference to the JavaScript file
    • Write JavaScript in HTML
    • Problems:
      • Doesn't scale
      • Too many scripts
      • Unmaintainable scripts
      • Scope, size, readability, fragility, monolith files
  • Immediately Invoked Function Expression (IIFE)

    • Revealing module pattern, provides encapsulation. Treat each file as an IIFE
    • Concatenate, "safely" combine files without concern of scope collision
    • Problems:
      • Full rebuilds everytime
      • Dead code, concatenate doesn't help tie usages across files
      • Lots of IIFE are slows
      • Can't lazy load (dynamic loading)
  • JavaScript Modules

    • CommonJS, require and export keyword to import/export functions from other files
    • Provides static analysis, can see what dependencies are used in the code
    • NPM, Node + Modules, easy to distribute CommonJS node modules
    • Problems:
      • No browser support
      • No live bindings, issues with circular references
      • Sync module resolution loader is slow
  • Bundlers/Linkers

    • Browserify (static)
    • RequireJS (loader)
    • SystemJS (loader)
    • Problems:
      • No static async/lazy loading (all bundles up-front)
      • CommonJS bloat too dynamic
      • Not everyone was shipping CommonJS
  • Asynchronous Module Definitions (AMD)

    • Pattern of creating and sharing modules of JavaScript code
    • Problems:
      • Too dynamic of lazy loading
      • Awkward non-standard syntax, no real module system
  • ESM

    • Don't need require from CommonJS, use from keyword to loading from a file
    • Reusable, encapsulated, organised, convenient
    • Problems:
      • ESM for browser is very slow

Introduction to Webpack

  • Write any module format (mixed), compiles them for the browser
  • Supports static async bundling
  • Large ecosystem for Webpack, most performant way to ship JavaScript currently

Ways to use Webpack

  • Configuration file: webpack.config.js

    module.exports = {
        entry: {
            vendor: './src/vendors.ts',
            main: './src/main.browser.ts'
        },
        output: {
            path: 'dist/',
            filename: '[name].bundle.js'
        }
    }
    
  • Webpack CLI

    webpack <entry.js> <result.js> --colors --progress
    webpack-dev-server --port=9000
    
  • Node API

    var webpack = require("webpack");
    
    webpack({
        // Configuration objection here
    }, function(error, stats) {
        // Error
    });
    

Webpack from scratch

  • Adding npm scripts

    "scripts": {
        "webpack": "webpack",
        "dev": "npm run webpack -- --mode development",
        "prod": "npm run webpack -- --mode production"
    }
    
  • Setting up debugging

    "scripts": {
        "webpack": "webpack",
        "debug": "node --webpack --debug-brk ./node_modules/webpack/bin/webpack.js",
        "dev": "npm run webpack -- --mode development",
        "prod": "npm run webpack -- --mode production",
        "prod:debug": "npm run debg -- --mode production",
        "dev:debug": "npm run debug -- --mode development"
    }
    
  • Writing/using a module

    • Export functionality: export default a;
    • Import functionality: import a from "./file";
  • Adding watch mode

    "scripts": {
        ...,
        "dev": "npm run webpack -- --mode development --watch",
        ...
    }
    
  • ES Module syntax

    • Export functionalities: export {a, b, c};
    • Import functionalities: import {a, b, c} from "./file";
  • CommonJS module syntax

    • Can't use commonJS and ES syntax in the same file, will throw an error
    • Webpack, supports require as well
    • Export functionality: module.exports = someUtility;
    • Import functionality: import a from "./file";
  • CommonJS named exports

    const a, b, c;
    
    exports.a = a;
    exports.b = b;
    exports.c = c;
    
  • Tree shaking

    • Identify dead code that's imported but not used
    • Add a webpack.config.js file
    module.exports = {
        mode: "none"
    };
    

Webpack core concepts

  • Webpack uses a single JavaScript file as an entry point to "kick-off" the application

    • The entry file tells Webpack what files to load for the browser
  • Needs an output, specify in the configration file

    • The output tells Webpack where and how to distribute bundles, it works with the entry file
    module.exports = {
        entry: './browser.main.ts',
        output: {
            path: './dist',
            filename: './bundle.js'
        }
    }
    
  • Loaders tells Webpack how to interpret and translate files

    • Files are transformed one at a time before adding to the dependency graph
    module: {
        rules: [
            {test: /\.ts$/, use: ‘ts-loader’},
            {test: /\.js$/, use: ‘babel-loader’},           
            {test: /\.css$/, use: ‘css-loader’}
        ]
    }
    
    • test is a regex that instructs the compiler which files to run the loader against
    • use is an array/string/function that returns loader objects
    • enforce can be "pre" or "post", it tells Webpack to run this rule before or after all other rules
    • include is an array of regex that instructs the compiler which files/folders to include
    • exclude is an array of regex that instructs the compiler which files/folders to ignore
    module: {
        rules: [
            { 
                test: regex,
                use: (Array|String|Function)
                include: RegExp[],
                exclude: RegExp[],
                issuer: (RegExp|String)[],
                enforce: “pre”|”post”
            },
        ]
    }
    
    • Chaining loaders, applies right-to-left (applicative-order evaluation): less-loader -> css-loader -> style-loader
    rules: [
        { 
            test: /\.less$\,
            use: ['style', 'css', 'less']
        }
    ]
    
  • Webpack plugins adds functionality to compilations (optimised bundle modules)

    • Objects with an apply property
    • Enables you to hook into the entire compilation lifecycle
    • There are a variety of built-in plugins
    function BellOnBundlerErrorPlugin () { }
    
    BellOnBundlerErrorPlugin.prototype.apply = function(compiler) {
        if (typeof(process) !== 'undefined') {
            
            // Compiler events that are emitted and handled
            compiler.plugin('done', function(stats) { 
                if (stats.hasErrors()) { 
                    process.stderr.write('\x07'); 
                }
            });
            
            compiler.plugin('failed', function(err) { 
                process.stderr.write('\x07'); 
            });
        
        }
    }
    
    module.exports = BellOnBundlerErrorPlugin;
    
    • Use require to add plugin into config, add a new instance to plugins key in the config object
    var BellOnBundlerErrorPlugin = require(‘bell-on-error’);
    var webpack = require(‘webpack’);
    
    module.exports = {
        ...
        plugins: [
            new BellOnBundlerErrorPlugin(),
            
            // Couple of the built-in plugins
            new webpack.optimize.CommonsChunkPlugin(‘vendors’),
            new webpack.optimize.UglifyJsPlugin()
        ]
        ...
    }
    

Setting up Webpack config

  • Add output

    module.exports = () => ({
        output: {
            filename: "bundle.js"
        }
    });
    
  • Add scripts to environment

    "scripts": {
        ...,
        "dev": "npm run webpack -- --env.mode development",
        "prod": "npm run webpack -- --env.mode production",
        "prod:debug": "npm run debg -- --env.mode production",
        "dev:debug": "npm run debug -- --env.mode development"
    }
    
    module.exports = (env) => ({
        return {
            mode: env.mode,
            output: {
                filename: "bundle.js"
            }
        };
    });
    
  • Adding plugins

    • Create new directory /build-utils
    const webpack = require("webpack");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = (env) => ({
        return {
            mode: env.mode,
            output: {
                filename: "bundle.js"
            },
            plugins: [
                new HtmlWebpackPlugin(),
                new webpack.ProgressPlugin()
            ]
        };
    });
    
  • Setting up local development server

    "scripts": {
        "webpack-dev-server": "webpack-dev-server",
        ...
    }
    
  • Splitting environment configuration files

    const webpack = require("webpack");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const modeConfig = env => require(`./build-utils/webpack.${env}`)(env);
    
    module.exports = ({ mode, presets } = { mode: "production", presets: [] }) => {
        return {
            mode,
            output: {
                filename: "bundle.js"
            },
            plugins: [
                new HtmlWebpackPlugin(),
                new webpack.ProgressPlugin()
            ]
        };
    };
    
    • yarn add webpack-merge --dev
    const webpack = require("webpack");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const modeConfig = env => require(`./build-utils/webpack.${env}`)(env);
    const webpackMerge = require("webpack-merge");
    
    module.exports = ({ mode, presets } = { mode: "production", presets: [] }) => {
        return webpackMerge(
            {
                mode,
                output: {
                    filename: "bundle.js"
                },
                plugins: [
                    new HtmlWebpackPlugin(),
                    new webpack.ProgressPlugin()
                ]
            },
            modeConfig(mode),
            loadPresets({ mode, presets })
        );
    };
    

Using plugins

  • Using css with Webpack

    • In build-utils/webpack.development.js:
    module.exports = () => {(
        module: {
            rules: [
                {
                    test:, /\.css$/,
                    use: ["style-loader", "css-loader"]
                }
            ]
        };
    )};
    
  • Hot module replacement with css, patch changes incrementally without reloading the browser

    "scripts": {
        ...,
        "dev": "npm run webpack -- --env.mode development --hot",
        ...
    }
    
    • In build-utils/webpack.production.js:
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = () => {(
        output: {
            filename: "bundle.js"
        },
        module: {
            rules: [
                {
                    test:, /\.css$/,
                    use: [MiniCssExtractPlugin.loader, "css-loader"]
                }
            ]
        },
        plugins: [new MiniCssExtractPlugin()]
    )};
    
  • File and URL loaders (can add images, etc.)

    • yarn add file-loader url-loader --dev
    • Can limit file size option in URL loader
    • In webpack.config.js:
    const webpack = require("webpack");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const modeConfig = env => require(`./build-utils/webpack.${env}`)(env);
    const webpackMerge = require("webpack-merge");
    
    module.exports = ({ mode, presets } = { mode: "production", presets: [] }) => {
        return webpackMerge(
            {
                mode,
                module: {
                    rules: [
                        {
                            test: /\.jpe?g$/,
                            use: [
                                {
                                    loader: "url-loader",
                                    options: {
                                        limit: 5000
                                    }
                                }
                            ]
                        }
                    ]
                },
                output: {
                    filename: "bundle.js"
                },
                plugins: [
                    new HtmlWebpackPlugin(),
                    new webpack.ProgressPlugin()
                ]
            },
            modeConfig(mode),
            loadPresets({ mode, presets })
        );
    };
    
  • Implementing presets allow configuration settings to be specified

    • In build-utils/loadPresets.js:
    const webpackMerge = require("webpackge-merge");
    
    const applyPresets = env => {
        const { presets } = env;
        const mergedPresets = [].concat(...[presets]);
        const mergedConfigs = mergedPresets.map(
            presetName => require(`./presets/webpack.${presetName}`)(env)
        );
    
        return webpackMerge({}, ...mergedConfigs);
    };
    
    module.exports = applyPresets;
    
    • In webpack.config.js, add module:
    const presetConfig = require("./build-utils/");
    
    module.exports = ({ mode, presets } = { mode: "production", presets: [] }) => {
        return webpackMerge(
            {
                ...
            },
            modeConfig(mode),
            presetConfig({ mode, presets })
        );
    };
    
    • Adding TypeScript, yarn add ts-loader typescript@next --dev
    • In build-utils/webpack.typescript.js:
    module.exports = () => ({
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    use: "ts-loader"
                }
            ]
        };
    });
    
    "scripts": {
        ...,
        "prod:typescript": "npm run prod -- --env.presets typescript",
        ...
    }
    
  • Bundle Analyser Preset

    • yarn add webpack-bundle-analyzer --dev
    "scripts": {
        ...,
        "prod:analyse": "npm run prod -- --env.presets analyze",
        ...
    }
    
    • Create file build-utils/webpack.analyse.js
    const WebpackBundleAnalyzer = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
    
    module.exports = () => ({
        plugins: [new WebpackBundleAnalyzer()]
    });
    
  • Compression plugin

    • yarn add compression-webpack-plugin --dev
    "scripts": {
       ...,
       "prod:compress": "npm run prod -- --env.presets compress",
       ...
    }
    
    • In webpack.compress.js:
    const CompressionWebpackPlugin = require("compression-webpack-plugin");
    
    module.exports = () => ({
       plugins: [new CompressionWebpackPlugin()]
    });
    
  • Source maps provide information during debugging such as visualising project structure and adding break points

    • In build-utils/webpack.development.js:
    module.exports = () => {(
        devtool: "source-map",
        module: {
            rules: [
                {
                    test:, /\.css$/,
                    use: ["style-loader", "css-loader"]
                }
            ]
        };
    )};