Webpack
Problems with script loading
- Two ways to load a script in browser
- script tag with src
- script tag with js
- Problems
- Doesn't scale
- Too many scripts
- Browser can only make a certain number of concurrent requests
- Unmaintainable scripts
- If you use a single file
- Doesn't scale
- Solution
- IIFE
- Immediately invoked function expression
const whatever = (function(...)){return ...})()
- Encapsulate modules in their own scope
- Immediately invoked function expression
- Now we can concatenate modules without concern that we will overwrite another module's scope
- Need to rebuild
- Have dead code
- Lots of IIFEs are slow
- Overhead in parsing and executing
- Common JS
- No browser support
- No live binding, circular references
- Slow module loading - synchronous
- Can't statically analyses bundles
- People wanted to start shipping web modules through npm
- bundlers / linkers
- Not everyone used CommonJS, other module formats like AMD are even more dynamic
- bundlers / linkers
- ESM
- Slow in the browser
- Webpack
- People ship any module format
- Need something that
- Can read every module format
- Can handle resources and assets
- Lets you write any module format and compiles for browser
- Static async bundling
- Lazy loading
- Most performant way to ship js
- Configuring webpack
-
webpack.config.js
- Common JS module
- CLI arguments
- Node API
-
- IIFE
Webpack from scratch
- npm scripts
-
node_modules/bin
- scripts key in
package.json
"scripts" : { "webpack": "webpack" "dev": "npm run webpack -- --mode development" "prod": "npm run webpack -- --mode production" }
--
tells npm to add flags to the command- default
- mode is production
- entry is
src/index.js
- output is
./dist
-
- Debugging
-
"debugthis": "node --inspect-brk ./src/index.js
-
"debug": "node --inspect-brk ./node_modules/webpack/bin/webpack.js
- open
chrome://inspect
- Can connect to a remote target
- Button for opening dev tools for node
-
- First module
-
nav.js
export default "nav"
for a single item
-
index.js
-
import nav from "./nav"
-
-
- Watch mode
-
"dev": "npm run webpack -- --mode development --watch"
- Will rebuild module
-
- ES Module syntax
-
multiple exports
export const top = "top"; export const bottom = "bottom";
-
multiple exports, destructuring
export {top, bottom};
-
multiple imports, with destructuring
import {top, bottom} from "./footer.js"
-
Common JS syntax
- Default export
module.exports = (buttonName) => {return `Button: ${buttonName}`};
- Named export
const red = "color: red;" const blue = "color: blue;" exports.red = red; exports.blue = blue;
- He recommends leaving exports at the bottom
- Import, webpack interop use import with ESM instead of require
import makeButton from "./button"
- Default export
-
Webpack
- Can't use CJS and ESM in the same file
- Does support using require
-
- Tree shaking
- Webpack will remove code that can't be reached from the entry point
- Not methods though
-
webpack.config.js
- Common JS
- Overrides defaults
- Bundle walkthrough
- Setting mode to none puts comments in the output
- Module cache
- require function
- If module in cache return exports from cache
- Create a new module and put in cache
- Return exports
- Execute entry module with scope, state, and require function
- Import statements replaced with
__webpack_require__
Core concepts
- Entry
- Root of dependency graph
- Simplest is a string path to file
- Tells webpack what you want to include
- Output
- Where and how we name the fie
output: {
path: './dist',
filename: './bindle.js'
}
- Loaders and rules
- How to treat files that aren't js
- If webpack finds a filename matching regex, use a loader to transform the file
- Before added to dependency graph
- Per file process
rules: [
{test: /\.ts$/, use: 'ts-loader'},
...
]
- Chaining loaders
- Run from right to left, like
style(css(less()))
rules: [ { test: /\.less$/ use: ['style', 'css', 'less'] } ]
- Run from right to left, like
- Can do anything on a file, not just transform
- e.g. output code coverage
- Plugins
- Objects with an apply property
- Hook into the entire compilation lifecycle
- 80% of webpack is made up of it's own plugin system
function BellOnBundlerErrorPlugin() {}
BellOnBundlerErrorPlugin.prototype.apply = function(compiler) {
if (typeof(process) !== undefined) {
compiler.plugin('done', function(stats){
if (stats.hasErrors()) {
process.stderr.write('\x07');
}
});
compiler.plugin('failed', function(err) {
process.stderr.write('\x07');
});
}
}
module.exports = BellOnBundlerErrorPlugin;
var BellOnBundlerErrorPlugin = require('bell-on-error')
module.exports = {
...
plugins: [
new BellOnBundlerErrorPlugin()
]
}
Config
module.exports
can also be a function that returns an object-
"prod": "npm run webpack -- --env.mode production"
- webpack takes the value of env and provides it to the config function
module.exports = ({mode}) => {
return {
mode,
output: {
filename "bundle.js"
}
}
}
- html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin') ... plugins: [ new HtmlWebpackPlugin, new webpack.ProgressPlugin() ]
- Injects output into html index
- Entry points are pre-page
- Nee to create a plugin for each entry
- Local dev server
-
webpack-dev-server
- script:
"webpack-dev-server": "webpack-dev-server"
- web server based on express
- Uses
webpack-dev-middleware
- Can use that directly with express for backend
- Uses
- generates bundle in memory and gives to express
-
- Splitting configs
build-utils
directory-
npm install --dev webpack-merge
- more or less just does Object.assign internally
const webpackMerge = require('webpack-merge') const loadPresets = require("./build-utils/loadPresets") const modeConfig = env => require(`./build-utils/webpack.${env}`)(env); ... module.exports = ({mode, presets} = {mode: "production", presets: []}) => webpackMerge( { { mode, plugins: [new HtmlWebpackPlugin(), new webpack.ProgressPlugin()] }, modeConfig(mode), loadPresets({mode, presets}) })
- Use "[chunkhash].js" as prod output file?
- Seems to make the browser realize it needs to refetch, since it's a different url?
Using plugins
- CSS with webpack
-
import "./footer.css"
-
webpack.development.js
{test: /\.css$/, use: ["style-loader", "css-loader"]}
-
- Hot module replacement with CSS
--hot
argument to webpack- Patch changes made incrementally and apply without reloading browser
webpack.production.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin) {test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"]} ... plugins: [new MiniCssExtractPlugin()]
- Output to css file and add a style tag with url
- Will put everything in one file, not scoped
- Support for lazy loading CSS
- Output to css file and add a style tag with url
- File loader & URL loader
{test: /\.jpe?g$/, use: "url-loader"}
import imageUrl from "./webpack-logo.jpg" // base64 string const makeImage = (url) => { const image = document.createElement("img"); image.src = url; return image; } const image = makeImage(imageUrl)
- Limit file size
- Bloating bundle with large file is not good for performance
{test: /\.jpe?g$/, use: [{loader: "url-loader", options: { limit: 5000 }}]
- If below limit includes in bundle as base64, otherwise outputs it to dist and returns the url
- Limit file size
- Presets
- Dev, prod, but also trying out features
-
loadPresets.js
module.exports = env => { {presets} = env; const mergedPresets = [].concat(...[presets]); const mergedConfigs = mergedPresets.map( presetName => require(`./presets/webpack.${presetName}`)(env); ) return webpackMerge({}, ...mergedConfigs); }
-
presets/webpack.typescript.js
module.exports = () => ({ module: { rules:[ test: /\.ts$/, use: "ts-loader" ] } })
-
package.json
-
"prod:typescript": "npm run prod -- --env.presets typescript"
-
- Bundle analyzer
-
npm install webpack-bundle-analyzer --save-dev
-
"prod:analyze": "npm run prod -- --env.presets analyze"
-
presets/webpack.analyze.js
const WebpackBundleAnalyzer = require("webpack-bundle-analyzer").BundleAnalyzerPlugin module.exports = () => ({ plugins: [new WebpackBundleAnalyzer()] });
- Creates a dev server with a tree map visualization of the bundle
-
- Compression
-
npm install compression-webpack-plugin --save-dev
-
- Source maps
- devtool, property for creating source maps
- tradeoffs on how long builds take with source map quality
devtool: 'source-map'