Me

WHAT IS WEBPACK?

FEATURE

  • module
  • cli / api / config
  • bundling
  • async loading
  • code splitting
  • 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

LOADER vs PLUGIN

  • Loaders work at the individual file level during or before the bundle is generated
  • Plugins work at bundle or chunk level and usually work at the end of the bundle generation proces

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
  • rollup

Let's get started!

PREREQUISITE

  • node v6.9.1
  • yarn v0.17.8
yarn global add webpack

GLOBAL INSTALL

yarn init
yarn add webpack -D

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

//package.json
scripts: {
  "dev": "webpack --watch",
  "build": "webpack -p"
}

NPM(YARN) WITH WEBPACK

yarn run dev //for dev
yarn run build //for prod

HOW DID I APPLY WEBPACK?

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

WHY YARN?

INSTALL WITH COMMAND

yarn add jquery
yarn add bootstrap-sass

PACKAGE.JSON

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

yarn.lock

jquery@^3.1.0:
  version "3.1.1"
  resolved "https://registry.yarnpkg.com/jq...
bootstrap-sass@^3.3.6:
  version "3.3.7"
  resolved "https://registry.yarnpkg.com/boot...
  • We don't need npm shrinkwrap

VERSION MANAGEMENT

yarn global add 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

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

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

yarn add babel-core -D
yarn add babel-preset-es2015 -D
yarn add babel-loader -D

yarn add babel-polyfill -D
(babel-plugin-transform-runtime)

CONFIG FOR BABEL

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

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

yarn add html-webpack-plugin -D
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

yarn add webpack-dev-server -D

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()
]
yarn add clean-webpack-plugin -D

SPLIT DEV AND PROD

yarn add webpack-merge -D

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")
})

OTHER ASSET

yarn add node-sass -D
yarn add sass-loader -D
yarn add style-loader -D
yarn add url-loader -D
yarn add resolve-url-loader -D

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

yarn add handlebars-loader -D

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(YARN) WITH WEBPACK

YARN WITH WEBPACK

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

WEBPACK HINTS

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

WEBPACK DASHBOARD

yarn add webpack-dashboard -D

WEBPACK DASHBOARD

const DashboardPlugin = 
  require('webpack-dashboard/plugin')

module.exports = {
  ...
  plugins: [
    new DashboardPlugin()
  ]
}

WEBPACK ANALYZE

yarn add webpack-bundle-analyzer -D

WEBPACK ANALYZE

const wba = require('webpack-bundle-analyzer')
const BundleAnalyzerPlugin = wba.BundleAnalyzerPlugin

module.exports = {
  plugins: [
    ...
    new BundleAnalyzerPlugin({
      analyzerMode: 'server',
      analyzerPort: 8888
    })
  ]
}

babel-plugin-lodash

yarn add babel-plugin-lodash -D

babel-plugin-lodash

import _ from 'lodash'
import { add } from 'lodash/fp'

const addOne = add(1)
_.map([1, 2, 3], addOne)
import _add from 'lodash/fp/add'
import _map from 'lodash/map'

const addOne = _add(1)
_map([1, 2, 3], addOne)

lodash-webpack-plugin

yarn add lodash-webpack-plugin -D

lodash-webpack-plugin

yarn add lodash-webpack-plugin -D
  • Create smaller Lodash builds by replacing feature sets of modules with noop, identity, or simpler alternatives.

HOW TO DEBUG WEBPACK

node --inspect --debug-brk $(which webpack)

USEFUL LINK

WEBPACK2

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

WEBPACK2

yarn add webpack@v2.1.0-beta.25 -D
yarn add webpack-dev-server@v2.1.0-beta.10 -D

TREE SHAKING

"presets": [
  ["es2015", { "modules": false }]
]

TREE SHAKING

//module A

export const add = (a, b) => a + b
export const minus = (a, b) => a - b
import {add} from './moduleA'

const result = add(1,4)

CODE SPLITTING

System.import('./components/Home')
      .then(loadRoute(cb))
      .catch(errorLoading);

WEBPACK MIGRATION

Sample

webpack guide

By odyss

webpack guide

  • 1,900