WEBPACK TUTORIAL

Me

WHAT IS WEBPACK?

FEATURE

  • cli / api / config
  • bundling
  • async loading
  • packaging various asset(image & css...)
  • HMR
  • loader & plugins

LOADER

  • Transformations on a resource file. Each module is loaded through a loader

PLUGIN

  • Injects themselves into the build process to make all sort of crazy things

PROS

  • It's like browserify
  • alternative for grunt/gulp
  • code splitting
  • multiple entry
  • support amd/commonjs/es2015 module
  • support transpiler(babel, type script, jsx..)
  • performance
  • echo system(loaders, plugins)

CONS

  • Learning Curve
  • Documentation

WHY DID I CHOOSE WEBPACK?

CANDIDATE

  • Browserify
  • SystemJS
  • Webpack

Text

PREREQUISITE

  • node v6.3.1
npm install -g webpack

GLOBAL INSTALL

npm init -y
npm install webpack --save-dev

PROJECT SETUP

module.exports = {
  entry: './app/index.js',
  output: {
    filename: './dist/bundle.js'
  }
}

WEBPACK CONFIG

var moduleA = require('./moduleA')

var mainEl = document.getElementById('main')

mainEl.innerHTML = moduleA

index.js

var name = 'moduleA'

module.exports = name

moduleA.js

webpack

HOW TO RUN

webpack --watch

WATCH MODE

webpack -p

PRODUCTION BUILD

HOW DID I APPLY WEBPACK?

  • package management(npm)
  • apply babel
  • multiple entry
  • common module
  • html plugin
  • webpack dev server
  • production build
  • grunt with webpack
  • other asset(css, image, svg, font)

WHY NPM?

PACKAGE.JSON

"dependencies": {
    "jquery": "~3.1.0",
    "bootstrap-sass": "~3.3.6"
}

INSTALL WITH COMMAND

npm install jquery --save
npm install bootstrap-sass --save

VERSION MANAGEMENT

npm install -g npm-check-updates

VERSION MANAGEMENT

$ ncu
 express           4.12.x  →   4.13.x
 react-bootstrap  ^0.22.6  →  ^0.24.0
 webpack          ~1.9.10  →  ~1.10.5

Run with -u to upgrade your package.json
# update all package
$ ncu -u
  express           4.12.x  →   4.13.x
# update specific package
$ ncu -u react-bootstrap

VERSION MANAGEMENT

BOWER

const path = require("path")
const webpack = require("webpack")

module.exports = {
  resolve: {
    modulesDirectories: ["node_modules", "bower_components"]
  },
  plugins: [
    new webpack.ResolverPlugin(
      new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin(
        ".bower.json", ["main"]
      )
    )
  ]
}

MANAGING DEPENDENCY

USE RESOLVE

resolve: {
  alias: {
    'spin': 'spin.js',
    'bootstrap-collapse': 
      'bootstrap-sass/assets/javascripts/bootstrap/collapse.js',
    'bootstrap-dropdown': 
      'bootstrap-sass/assets/javascripts/bootstrap/dropdown.js',
    'bootstrap-transition': 
      'bootstrap-sass/assets/javascripts/bootstrap/transition.js',
    'bootstrap-modal': 
      'bootstrap-sass/assets/javascripts/bootstrap/modal.js'
  }
},
  • some package doesn't have main/browser field or index.js

PREFER UNMINIFIED COMMONS/AMD OVER DIST

module.exports = {
    ...
    resolve: {
        alias: {
            some: "some-module/src/bla"
        }
    }
};

USE PROVIDEPLUGIN TO INJECT IMPLICIT GLOBALS

plugins: [
    new webpack.ProvidePlugin({
      '$': 'jquery',
      'jQuery': 'jquery',
      'window.jQuery': 'jquery',
      'd3': 'd3'
    })
]

USE THE IMPORTS-LOADER TO CONFIGURE THIS

module: {
  loaders: [
    {
      test: /[\/\\]node_modules[\/\\]some[\/\\]index\.js$/,
      loader: "imports?this=>window"
    }
  ]
}

USE THE IMPORTS-LOADER TO DISABLE AMD

module: {
  loaders: [
    {
      test: /[\/\\]node_modules[\/\\]some-module[\/\\]index\.js$/,
      loader: "imports?define=>false"
    }
  ]
}

USE THE SCRIPT-LOADER TO GLOBALLY IMPORT SCRIPTS

npm install script-loader --save-dev
{
  module: {
    loaders: [
      { test: /\some-module\.js$/, loader: "script" },
    ]
  }
}

USE NOPARSE TO INCLUDE LARGE DISTS

module: {
  noParse: [
    /[\/\\]node_modules[\/\\]some-module[\/\\]some\.js$/
  ]
}

ContextReplacementPlugin

const webpack = require("webpack")

module.exports = {
  // ...
  plugins: [
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, 
      /(ko|ja)/)
  ]
};
//moment.js
...
require('./locale/' + name)
...

INSTALL BABEL

npm install babel-core --save-dev
npm install babel-preset-es2015 --save-dev
npm install babel-loader --save-dev

npm install babel-polyfill --save-dev
(babel-plugin-transform-runtime)

CONFIG FOR BABEL

module: {
  loaders: [
    {
      test: /\.js$/,
      loader: 'babel-loader',
      exclude: /(node_modules|bower_components)/,
      query: {
        presets: ['es2015'],
        cacheDirectory: true
      }
    }
  ]
},
resolve: {
  extensions: ['', '.js', '.json'] 
}

Loose Mode

  • IE doesn't support __proto__
  • loose mode(< babel 6.14.0)
  • babel 6.14.0

RESOURCE

MULTIPLE ENTRY

  • This is for multi page

MULTIPLE ENTRY

{
  entry: {
    'dist/p1': './app/p1/main.js',
    'dist/p2': './app/p2/main.js',
  },
  output: {
    filename: '[name].js'
  }
}

COMMON MODULE

  • explicit vendor
  • common chunks

EXPLICIT VENDOR

const CommonsChunkPlugin = 
  require("webpack/lib/optimize/CommonsChunkPlugin")
module.exports = {
  entry: {
    'vendor': ['jquery', 'moment'],
    'p1': './app/p1/main.js',
    'p2': './app/p2/main.js',
  },
  output: {
    path: './dist/'
    filename: '[name].js'
  },
  plugins: [
    new CommonsChunkPlugin({
      name: "vendor",
      filename: "dist/lib/vendor.js",
      minChunks: Infinity,
    })
  ]
}

COMMON CHUNKS

const CommonsChunkPlugin = 
  require("webpack/lib/optimize/CommonsChunkPlugin")
module.exports = {
  entry: {
    'vendor': ['jquery', 'moment'],
    'p1': './app/p1/main.js',
    'p2': './app/p2/main.js',
  },
  output: {
    path: './dist/',
    filename: '[name].js'
  },
  plugins: [
    new CommonsChunkPlugin('common.js')
  ]
}

HTML-WEBPACK-PLUGIN

  • copy from html to dist
  • append bundling resource automatically

HTML PLUGIN

npm install html-webpack-plugin --save-dev
const HtmlWebpackPlugin = 
  require('html-webpack-plugin')
module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      filename: "app/p1/index.html",
      template: './app/index.html',
      chunks: ['vendor', 'dist/app/p1/app']
    }),
    new HtmlWebpackPlugin({
      filename: "app/p2/index.html",
      template: './app/index.html',
      chunks: ['vendor', 'dist/app/p2/app']
    }),
  ]
}

WEBPACK DEV SERVER

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

WEBPACK DEV SERVER

const path = require('path')
const dir_build = path.resolve(__dirname, 'dist')


module.exports = {
  devServer: {
    contentBase: dir_build,
    inline: true
  }
}

PRODUCTION BUILD

  • optimize
  • uglify(minify + mangle)
  • split config dev and prod 
  • sourcemap
  • hash

PRODUCTION CONFIG

plugins: [
    new CleanWebpackPlugin(['dist/*'], {
      verbose: true,
      dry: false
    }),
    // Avoid publishing files when compilation fails
    new webpack.NoErrorsPlugin(),
    new webpack.optimize.DedupePlugin(),
    new webpack.ExtendedAPIPlugin(),
    new webpack.optimize.UglifyJsPlugin()
]
npm install clean-webpack-plugin --save-dev

SPLIT DEV AND PROD

npm install webpack-merge --save-dev

SPLIT DEV AND PROD

const webpackMerge = require('webpack-merge')

const devConfig = require('./webpack.dev.config.js')
const prodConfig = require('./webpack.prod.config.js')

const target = process.env.npm_lifecycle_event

const common = {
  ...
}

if(target === 'build') {
  config = webpackMerge(common, prodConfig)
} else {
  config = webpackMerge(common, devConfig)
}


SOURCE MAP DEV

devtool: 'eval'  //eval-source-map

SOURCE MAP PROD

devtool: 'source-map'
  • but, your source map file should be restricted to avoid reveal original source code

HASH

output: {
    path: './dist/',
    filename: "[name]-[chunkhash].js",
    pathinfo: true,
    publicPath: '/public'
}

HASH

plugins: [
    new CommonsChunkPlugin({
      name: "vendor",
      filename: "libs/vendor-[hash].js",
      minChunks: Infinity,
    })
]

WEBPACK DEFINE PLUGIN

new webpack.DefinePlugin({
    NODE_ENV: JSON.stringify("production")
})

INTEGRATION WITH GRUNT

module.exports = function(grunt) {
    grunt.loadNpmTasks("grunt-webpack");
    grunt.initConfig({
        webpack: {
            options: {
                // configuration for all builds
            },
            build: {
                // configuration for this build
            }
        },
        "webpack-dev-server": {
            options: {
                webpack: {
                    // configuration for all builds
                },
                // server and middleware options for all builds
            },
            start: {
                webpack: {
                    // configuration for this build
                },
                // server and middleware options for this build
            }
        }
    });
};

OTHER ASSET

npm install node-sass --save-dev
npm install sass-loader --save-dev
npm install style-loader --save-dev
npm install url-loader --save-dev
npm install resolve-url-loader --save-dev

FILE-LOADER

module: {
    loaders: [
      {
        test: /\.(png|gif)$/,
        loaders: ["file?name=images/[name].[ext]"]
      },
      {
        test: /\.(eot|woff|woff2|ttf|eot|svg)$/,
        loaders: ["file?name=[path][name].[ext]"]
      }
    ]
}

SASS DEV CONFIG

module: {
    loaders: [
      {
        test: /\.scss$/,
        loaders: ["style", "css", 
          "resolve-url", "sass?sourceMap"]
      }
    ]
}

SASS PROD CONFIG

module: {
    loaders: [
      {
        test: /\.scss$/,
        loader: ExtractTextPlugin.extract
          ('style', 
           'css!resolve-url!sass?sourceMap')
      }
    ]
}

main.scss

@import "~angular-ui-select/select";
@import "~font-awesome/scss/font-awesome";
@import "~bootstrap-sass/assets/stylesheets/bootstrap";

@import "variables";
@import "mixins";

main.js

import '../stylesheet/main.scss'

import '../moduleA'

PITFALL

@import "~angular-ui-select/select";
@import "~font-awesome/scss/font-awesome";
@import "~bootstrap-sass/assets/stylesheets/bootstrap";

@import "variables";
@import "mixins";

PITFALL

resolve: {
    alias: {
      'angular-ui-select': 'angular-ui-select/select.js',
    }
  }
resolve: {
    alias: {
      'angular-ui-select-js': 'angular-ui-select/select.js',
    }
  }

COPY PLUGIN

plugins: [
  new CopyWebpackPlugin([
      {
        context: 'src',
        from: '**/*.tpl.html'
      }
  ])
]
npm install copy-webpack-plugin --save-dev

HANDLEBARS

npm install handlebars-loader --save-dev

HANDLEBARS

{
  ...
  module: {
    loaders: [
      ...
      { test: /\.handlebars$/, loader: "handlebars-loader" }
    ]
  }
}

HANDLEBARS

const template = require("./some.hbs")
  • It just returns template function
  • You don't need compile step for template

NPM WITH WEBPACK

NPM WITH WEBPACK

"scripts": {
    "start": "npm run dev",
    "dev": "webpack-dev-server",
    "build": 
      "NODE_ENV=production webpack --progress"
}

WEBPACK HINTS

webpack --profile --json >> hint.json

USEFUL LINK

FUTURE

  • webpack v2

WEBPACK2

  • native ES2015 module(tree shaking)
  • code splitting(support system.import)

Demo

webpack

By odyss

webpack

  • 6,204