Webpack Notes
Why Webpack
Problems with placing scripts directly into html or importing them:
- Doesn't scale
- Too many scripts to load (creates bottlenecks)
- Unmaintainable scripts
- Struggle with scope, size, readability, fragility and monolith files
IIFE (Immediately invoked function expression).
var outerScope = 1
// IIFE
const whatever = (function (dataNowUsedInside) {
var outerScope = 4
return {
someAttribute: 'youwant',
}
})(1)
console.log(outerScope)
People begun to treat each file as an IIFE (revealing module). This allows us to then concatenate (safely combine) files without concern of scope collision. However this would require a full rebuild every time just one module/portion is changed. This may also result in dead code without us realising. It also turns out that IIFE's are kind of slow, and obviously we don't want it to impact performance.
CommonJS
Using 'require' it allows us to import sections of one module, and use them in another module. Additionally we can export function's or modules that we want to be able to use in another file. NPM was then created to be able to share common modules not only in the same project, but amongst any project that users wish. There is no browser support for CommonJS.
ECMAScript Modules (ESM)
import { uniq, forOf, bar } from 'lodash-es'
import * as utils from 'utils'
export const uniqConst = uniq([1, 2, 2, 4])
Allows for
- Re-usability
- Encapsulation
- Organisation
- Convenience
Introducing Webpack
Webpack is a module bundler. It allow's you to write any module format and compiles them for the browser. It supports static async bundling. It has a rich, vast ecosystem. It is the most performant way to ship Javascript.
How to Use Webpack
module.exports = {
entry: {
vendor: './src/venders.ts',
main: './src/main.browser.ts',
},
output: {
path: 'dist/',
filename: '[name].bundle.js',
},
}
This is simply a module which has some properties, which describes to webpack what I should be doing with this code.
NodeAPI:
var webpack = require('webpack')
// returns a Compiler instance
webpack(
{
//configuration object here
},
function (err, stats) {
// ...
// compilerCallback
// console.error(err);
},
)
Webpack from Scratch
- npm scripts
"scripts": {
"webpack": "webpack",
"dev": "npm run webpack -- --mode development",
"prod": "npm run webpack -- --mode production"
}
- For 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 debug -- --mode production",
"dev:debug": "npm run debug -- --mode development"
}
- To use a module, we can simply use export and import in their respective files like normal.
- We can also add a '--watch' flag to our dev script, this will update on any change. This is similar to test:watch for jest.
Tree shaking:
- Identifies dead code which is imported but not used
- We can add a webpack.config.js file with the following structure:
module.exports = {
mode: 'none',
}
Webpack Core Concepts
Entry
- The entry file is the first js file to load to 'kick-off' your app
- In the course, index.js is the entry point
- This is defined in the webpack.config.js file
Output and Loaders
Output
- Also defined in the webpack.config.js file
- The path to the output and the filename of the bundled files (singular)
Loaders and Rules
- Tells webpack how to modify files before its added to the dependency graph
- Loaders are also js modules (functions) that takes the source file, and returns it in a [modified] state
// webpack.config.js
moduleL {
rules: [
{test: /\.ts$/, use: 'ts-loader'}
// ...
]
}
- Test is a regex which instructs the compiler which files to run the loader against
- use is an array/string/function that returns loader objects
Chaining Loaders
- Runs right-to-left: less-loader -> css-loader -> style-loader
rules: [
{
test: /\.less$\,
use: ['style', 'css', 'less']
}
]
Plugins
- Objects with an 'apply' property
- Allow you to hook into the entire compilation lifecycle
- Webpack has a variety of built-in plugins
// Plugin Example
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
We then use require to add plugin into our configuration, 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()
]
...
}
Local Development Server
"scripts": {
"webpack-dev-server": "webpack-dev-server",
// ...
}
- To split 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()
]
}
}
- Webpack Merge
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 }),
)
}
Advanced webpack.config.js
// ... imports
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 different presets allow for different configuration settings to be specified
// build-utils/loadPresets.js
const webpackMerge = require('webpack-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
Then in webpack.config.js we change
loadPresets({ mode, presets })
// to
presetConfig({ mode, presets })
- We can also add other types of plugins for further functionality
Examples:
- CSS
- Compression
- Build Analyser