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
- life cycle event
- composable
- you don't know npm
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,180