Skip to content

Webpack 4 overview: Webpack allows you to break up and modulate Javascript.

Notifications You must be signed in to change notification settings

Raigyo/webpack-overview

Repository files navigation

Webpack 4

🔨 Webpack overview. From Grafikart.fr 'Comprendre Webpack'. The training was for version 3 but has been updated for version 4 using documentation.

Webpack allows you to break up and modulate Javascript.

For the React version with classes see my webpack-react-hot-reload repository.

For the React version with hooks see my webpack-react-hot-reload-hooks repository.

Webpack logo

Webpack is a module builder. This is important to understand, as Webpack does not run during your page, it runs during your development.

Webpack is a tool wherein you use a configuration to explain to the builder how to load specific things. You describe to Webpack how to load *.js files, or how it should look at .scss files, etc. Then, when you run it, it goes into your entry point and walks up and down your program and figures out exactly what it needs, in what order it needs it, and what each piece depends on. It will then create bundles — as few as possible, as optimized as possible, that you include as the scripts in your application.

Concepts covered

  • Configuration
  • Dev mode / prod mode (new in Webpack 4)
  • webpack.config.js (several files: common, dev and prod)
  • Lazy Loading / Code splitting
  • Minification
  • Babel
  • CSS, Sass, CSS extraction
  • Caching
  • Url Loader
  • Resolve / Aliases
  • Eslint
  • Dev Server

Install Webpack

Init project

npm init -y

npm i -D webpack webpack-cli

Structure:

 webpack-demo
  |- package.json
+ |- index.html
+ |- /src
+   |- index.js

Compile

  • Use:

./node_modules/.bin/webpack

  • Or in package.json:
"scripts": {

  "test": "echo \"Error: no test specified\" && exit 1",

  "build": "webpack --mode=production",

  "dev": "webpack --mode=development"

 },

Then:

npm run dev / npm run build

  • Or if installed globally use:

webpack

NB: it's not a good practise to install it globally.

Will create a 'dist' folder:

 webpack-demo
  |- package.json
+ |- index.html
+ |- /dist
+   |- main.js
+ |- /src
+   |- index.js

Techniques

Providing the mode configuration option tells webpack to use its built-in optimizations accordingly.

It's new in Webpack 4. So webpack provides a preconfiguration for testing and building.

string = 'production': 'none' | 'development' | 'production'

package.json

  "scripts": {
    "dev": "webpack --mode=development",
    "prod": "webpack --mode=production",
    "serve:dev": "webpack-dev-server --open --mode=development",
    "serve:prod": "webpack-dev-server --open --mode=production"
  },

We can still use a webpack.config.js file.

Here is an exemple with several configs according to the mode dev or prod, with a common part.

 webpack-demo
  |- package.json
  |- webpack.config.js
+ |- index.html
+ |- /config
+   |- webpack.common.js
+   |- webpack.development.js
+   |- webpack.production.js
+ |- /build (prod)
+   |- main.js
+ |- /dist (dev)
+   |- main.js
+ |- /src
+   |- index.js

webpack.config.js

const { merge } = require('webpack-merge');
const commonConfig = require('./config/webpack.common');

module.exports = (env, options) => {
  const config = require(`./config/webpack.${options.mode}`);
  return merge(commonConfig, config);
};

config/webpack.common.js

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

module.exports = {
    entry: {
        main: ['./src/css/app.scss', './src/index.js']
        //the entry is the name given to the files in build, here 'main'
    },
    plugins: [
      new HtmlWebpackPlugin({
        hash: true,
        title: 'Webpack overview',
        myPageHeader: 'Webpack overview',
        myEnv: 'Webpack environment: ',
        template: './src/template.html',
        filename: './index.html' //relative to root of the application
      })
    ],
};

config/webpack.development.js

const path = require("path");

//Configuration
module.exports = {
  watch: true,
  mode: 'development',
	output: {
    path: path.resolve(__dirname, '..', "dist"),
    filename: '[name].js'
  },
  devtool: "eval-cheap-module-source-map",
  plugins: [

  ],//\plugins*/
	module: {
        rules: [
    	//...
        ]//\rules
  }//\modules
}//\module export

config/webpack.production.js

const path = require("path");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
//...

//Configuration
module.exports = {
  watch: true,
  mode: 'production',
	output: {
    path: path.resolve(__dirname, '..', "build"),
    filename: '[name].[chunkhash:8].js'
    //Cache invalidation can be achieved by including a hash to the filenames
  },
  devtool: "source-map",
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css',
      chunkFilename: '[id].css',
    }),
    new TerserPlugin({
      sourceMap: true
    }),
	//...
  ],//\plugins
	module: {
        rules: [
          //..
        ]//\rules
  }//\modules
}//\module export

It's also possible to use only one config file using conditionnals to filter what is applied on dev or prod.

package.json

  "scripts": {
    "dev": "NODE_ENV=dev webpack",
    "start": "webpack-dev-server --open",
    "prod": "webpack"
  },

webpack.config.js

const path = require("path");
const TerserPlugin = require('terser-webpack-plugin');
const dev = process.env.NODE_ENV === "dev";

let cssLoaders = [
  'style-loader',
  'css-loader'
]
//Only use postcss-loader in production
if (!dev) {
  cssLoaders.push({
    loader: 'postcss-loader'
  })
}

//Configuration
let config = {
  entry: './src/index.js',
  watch: dev,
  mode: 'development',
	output: {
    path: path.resolve(__dirname, "build"),
    filename: 'bundle.js'
  },
  //Ternary operator to filter the way to use of TerserPlugin
  devtool: dev ? "eval-cheap-module-source-map" : "source-map",
  plugins: [
   //**
  ],//\plugins
	module: {
		rules: [
          {//CSS used in dev mode
            test: /\.css$/i,
            sideEffects: true,
            use: cssLoaders
          },
          {//SASS used in prod mode
            test: /\.scss$/i,
            sideEffects: true,
            use: [
              ...cssLoaders,
              'sass-loader'
            ]
          }
    	]//\rules
  	}//\modules
}//\config

//Only use TerserPlugin (source map plugin) in production
if (!dev) {
  config.plugins.push(new TerserPlugin({
    sourceMap: true
  }))
}

module.exports = config;

Allows deferred loading for some file to improve performances or if we don't always need a component. This practice essentially involves splitting your code at logical breakpoints, and then loading it once the user has done something that requires, or will require, a new block of code. This speeds up the initial load of the application and lightens its overall weight as some blocks may never even be loaded.

Use this plugin for dynamic imports:

npm i -D babel-plugin-syntax-dynamic-import

In .babelrc.json:

{
	"presets": [
		["@babel/preset-env", {
			"useBuiltIns": "entry",
			"modules": false,
			"debug":true
		}]
	],
	"plugins": ["syntax-dynamic-import"]
}

Exemple with an external component displaying a console log using a promise:

index.js

document.getElementById('button').addEventListener('click', function () {
  //Async promise
  import('./print').then(module => {
      const print = module.default;
      print();
  })
})

print.js

console.log('The print.js module has loaded! See the network tab in dev tools...');

export default () => {
  console.log('Button Clicked: Here\'s "some text"!');
};

Another exemple that display Web Assembly in client console, in index.js:

import('./test.wasm').then(function (module) {
  //wasm script to add a number to 42
 log(module._Z5add42i(20))// Output: 62
}).catch(console.log)

A resolver is a library which helps in locating a module by its absolute path. A module can be required as a dependency from another module as:

import foo from 'path/to/module';
// or
require('path/to/module');

The dependency module can be from the application code or a third-party library. The resolver helps webpack find the module code that needs to be included in the bundle for every such require/import statement. webpack uses enhanced-resolve to resolve file paths while bundling modules.

npm install enhanced-resolve

By default webpack uses Resolve.

Here an exemple to create aliases:

webpack.config.js

    resolve: {
      alias: {
        '@Css': path.resolve(__dirname, '../src/css/'),
        '@Img': path.resolve(__dirname, '../src/img/')
      }
    },

index.js

import webpackLogo from '@Css/app.scss';

It's important not to forget to use this alias everywhere:

app.scss

$background: #DDD;

body {
    background: $background url(@Img/webpack-logo-horizontal.png);
    background-size: 100% 100%;
}

Use webpack with a development server that provides live reloading. This should be used for development only.

npm install webpack-dev-server --save-dev

package.json

  "scripts": {
    "serve:dev": "webpack-dev-server --open --mode=development --hot",
    "serve:prod": "webpack-dev-server --open --mode=production"
  },

The flag '--hot' enable hot reload.

The flag '--open' opens the served page in the browser.

webpack.config.js

module.exports = {
    entry: {
        app: ['./assets/css/app.scss', './assets/js/app.js']
    },
    output: {
    	path: path.resolve(./public/assets),
    filename: '[name].js',
        publicPath: '/assets/'
  },
  devServer: {
      contentBase: path.resolve('./public'), // path to the directory where pages are served
      sockjsPrefix: '/assets', // use it if DevServ try to access websockets in root
      overlay: true, //add an overlay with errors in the browser
      // If problems with CORS (exemple if we use several domains during dev)
      /*headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
          "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
      },*/
      // Or we can use a proxy 
       proxy: {
          '/web': {// to distribute assets correctly
            target: ''http://localhost:8000',', // launch PHP server on: 127.0.0.1:8000
            pathRewrite: {'^/web' : ''}
          }
       }
  }
  //...
}

Hot reload for specific javascript libraries/frameworks are usually provided with the CLI.

The can be modified using Hot Module Replacement.

Dev server can be used with the html using html-webpack-plugin (cfr. infra in plugins section).

Dev server allows custom setup using webpack-dev-middleware.

Packages

Loaders

Webpack enables use of loaders to preprocess files. This allows you to bundle any static resource way beyond JavaScript.

Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code (ex: JSX) into a backwards compatible version of JavaScript (vanilla) in current and older browsers or environments.

npm install --save-dev @babel/core @babel/register @babel/preset-env babel-loader
npm install --save  @babel/polyfill

webpack.config.js

module.exports = {
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
      }
    }]
  }
}

.babelrc.json

{
	"presets": [
		["@babel/preset-env", {
			"useBuiltIns": "entry",
			"debug":true
		}]
	]
}

Eslint: EslintLoader

ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs.

npm install eslint-loader --save-dev

Note: You also need to install eslint from npm, if you haven't already:

npm install eslint --save-dev

Following plugins/dependencies are also needed:

eslint-plugin-import: npm i eslint-plugin-import -D

eslint-plugin-node: npm i eslint-plugin-node -D

eslint-plugin-promise: npm i eslint-plugin-promise -D

eslint-plugin-standard: npm i eslint-plugin-standard -D

We have to load Eslint before Babel so for that we use the enforce property:

webpack.config.js

rules: [
      {
        enforce: 'pre',
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'eslint-loader',
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        }
      },
]

It's possible to add autofix in the config file, buts it's not advised. It's better to correct the errors by hand and learn to avoid them when coding.

options: {
          fix: true,
        },

A config file is mandatory to make Eslint working, .eslintrc in the root.

Let's use JavaScript Standard Style:

npm install eslint-config-standard-D

.eslintrc

{
    "extends": "standard"
}

Preprocessor CSS (SASS): CssLoader, StyleLoader, SassLoader

css-loader: convert css to strings

npm install --save-dev css-loader

style-loader: inject strings into style tags

npm install --save-dev style-loader

sass-loader and node-sass: to use css preprocessor, here sass to css

npm install --save-dev sass-loader node-sass

webpack.config.js

	module: {
        rules: [
          {
            test: /\.css$/i,
            use: ['style-loader', 'css-loader'],
            //loader on the right loaded first, respect the logical order
          },
          {
            test: /\.scss$/i,
            use: ['style-loader', 'css-loader', 'sass-loader'],
          }
        ]
  }

app.scss

$background: #DDD;

body {
    background: $background;
}

index.js, import scss

import css from './app.scss'

SCSS will be automaticaly converted into CSS.

Postprocessor CSS (PostCSS): postcss-loader

PostCSS loader : a CSS post-processing tool that can transform your CSS in a lot of ways, like autoprefixing, linting and more!

npm i -D postcss-loader

If you use plugins, you have to install them.

Exemple:

postcss-preset-env: npm i postcss-preset-env

css nano: npm i cssnano

webpack.config.js

      {
        test: /\.scss$/i,
        //use: ['style-loader', 'css-loader', 'sass-loader'],
        use: [
          'style-loader',
          { loader: 'css-loader', options: { importLoaders: 1 } },
          { loader: 'postcss-loader',
            options: {
              plugins: (loader) => [
                require('postcss-preset-env')({
                  browsers: "last 2 versions",
                  stage: 3,
                  features: {
                    "nesting-rules": true
                  }
                }),
                require('cssnano')(),
              ],
            }
          },
          'sass-loader'
        ],
      }

or with an external config file:

webpack.config.js

      {
        test: /\.scss$/i,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
      }

postcss.config.js

module.exports = {
  plugins: {
    'postcss-preset-env': {
      browsers: 'last 2 versions',
      stage: 3,
      features: {
        "nesting-rules": true
      }
    },
    cssnano: {},
  },
};

url-loader: A loader for webpack which transforms files into base64 URIs.

npm install url-loader --save-dev

webpack.config.js

 {
     test: /\.(jpe?g|png|gif|svg)$/i,
         use: [
             {
                 loader: 'url-loader',//base 64 conversion
                 options: {
                     limit: 8192,//beyond the limit it will use 'file-loader' by default
                     name: '[name].[hash:7].[ext]'
                 },
             },
         ],
 },

file-loader: The file-loader resolves import/require() on a file into a url and emits the file into the output directory.

npm install file-loader --save-dev

webpack.config.js

{
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
    loader: 'file-loader'
},

img-loader: Image minimizing loader for webpack 4, meant to be used with url-loader, file-loader, or raw-loader.

Meant to be used with url-loader, file-loader, or raw-loader

npm install img-loader --save-dev

img-loader can be used with a combination of other plugins, for instance imagemin:

imagemin: npm install imagemin

imagemin-mozjpeg: npm install imagemin-mozjpeg

imagemin-gifsicle: npm install imagemin-gifsicle (if problem during install use: npm install imagemin-gifsicle@6.0.1)

imagemin-pngquant: npm install imagemin-pngquant

imagemin-svgo: npm install imagemin-svgo

imagemin-webp: npm install imagemin-webp

webpack.config.js

 {
     test: /\.(jpe?g|png|gif|svg)$/i,
         use: [
			'url-loader?limit=10000',
             {
                 loader: 'img-loader',
                 options: {
                     plugins: [
                         require('imagemin-pngquant')({
                             floyd: 0.5,
                             speed: 2
                         }),
                     ]
                 }
             }
         ],
 },

raw-loader: A loader for webpack that allows importing files as a String.

npm install raw-loader --save-dev

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/i,
        use: 'raw-loader',
      },
    ],
  },
};

Plugins

Webpack has a rich plugin interface. Most of the features within webpack itself use this plugin interface. This makes webpack flexible.

Minify code

npm i terser-webpack-plugin --save-dev

It's generally a good practice to minify and combine your assets (Javascript & CSS) when deploying to production. This process reduces the size of your assets and dramatically improves your website's load time. Source maps create a map from these compressed asset files back to the source files.

Whe will use Devtool to control if and how source maps are generated.

For instance in webpack.config.js for production:

module.exports = {
  watch: true,
  mode: 'production',
	//...
  },
  devtool: "source-map",
 }

In the browser, disable 'Enable JS source maps'.

Then we can use console.log and debugger even with the minified build files.

Warning: UglifyjsWebpackPlugin is deprecated

The HtmlWebpackPlugin simplifies creation of HTML files to serve your webpack bundles.

npm i -D webpack-dev-server html-webpack-plugin

webpack.config.js, exemple with a template call:

const HTMLPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    new HTMLPlugin({
      hash: true,
      title: 'My Awesome application',
      myPageHeader: 'Hello World',
      template: './src/template.html',
      filename: './index.html' //relative to root of the application
    })
  ]
}

In package.json, for instance with 'start':

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "Webpack --watch --mode none",
    "start": "webpack-dev-server --mode development --open"
  },

Exemple of template.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>

  <body>
    <h1> <%= htmlWebpackPlugin.options.myPageHeader %> </h1>
    <h3>Welcome to the Awesome application</h3>

    <my-app></my-app>

  </body>
</html>

then: npm run start / http://localhost:8080/

This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.

To use in production.

npm install --save-dev mini-css-extract-plugin

webpack.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash:8].css',
            chunkFilename: '[id].css',
        }),
    ],//\plugins
    module: {
      {
        test: /\.scss$/i,
        sideEffects: true,
        use: [
          'style-loader',
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: (resourcePath, context) => {
                return path.relative(path.dirname(resourcePath), context) + '/';
              },
            },
          },
          'css-loader',
          'postcss-loader',
          'sass-loader'
        ],
      }
    ]//\rules
};

It will search for CSS assets during the Webpack build and will optimize \ minimize the CSS (by default it uses cssnano but a custom CSS processor can be specified).

npm install --save-dev optimize-css-assets-webpack-plugin

webpack.config.js

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
//...
plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css',
      chunkFilename: '[id].css',
    }),
    new TerserPlugin({
      sourceMap: true
    }),
    new OptimizeCSSAssetsPlugin({})
 ],//\plugins

Webpack plugin for generating an asset manifest.

npm install --save-dev webpack-manifest-plugin

If we invalidate cache using hashes (chunckhash, contenthash), name of the files are generated with keys.

This plugin will create a 'manifest.json' that can be useful to manage the names, for instance in backend side.

const ManifestPlugin = require('webpack-manifest-plugin');

plugins: [
    new ManifestPlugin()
 ],//\plugins

manifest.json

{
  "main.css": "/build/main.96a6baa9.css",
  "main.js": "/build/main.d6f62816.js",
  "main.js.map": "/build/main.d6f62816.js.map",
  "./index.html": "/build/./index.html"
}

A webpack plugin to remove/clean your build folder(s) before building again.

npm install --save-dev clean-webpack-plugin

const { CleanWebpackPlugin } = require('clean-webpack-plugin');

plugins: [
     new CleanWebpackPlugin({
      verbose: true,//log
      dry: false// true = test without delete, false = delete
    })
 ],//\plugins

Useful links

About

Webpack 4 overview: Webpack allows you to break up and modulate Javascript.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published