Webpack 4 Fundamentals
Why Webpack
Problems with Script Loading
There is only two ways that you can actually make JS to work in borwser.
- Adding a script tag for passing a source.
- Write your job description to HTML.
Problems:
- Doesn’t scale
- Too many scripts
每个浏览器都有不同的同时请求数量的限制(针对HTTP1.1,HTTP2等协议除外)。 - Unmaintainable scripts
- Scope
- Size
- Readability
- Fragility
- Monolith files
Solution:
IIFE(Immediately-Invoked-Function-Expression 立即执行函数)
Module(Treat Each File as IIFE)
- We can “safely” combine files without concern of scope collision!*
- Make, Grunt, Gulp, Broccoli, Brunch, StealJS Problem:
Full rebuilds everywhere
Dead code - Concat doesn’t help tie usages across files
Lots of IIFE’s are slow
Can’t dynamic loading
History of Modules
The birth of JavaScript module happened kind of around this early period thanks to Node.js.
- common.js(Modules 1.0)
- Use syntax called
require
, which allows you to inject other pieces of module into the current module. - Static analysis
- NPM + Node + Modules
- Use syntax called
Problems:
- No browser support for common.js
- No live bindings - Problems with circular references
- Synchronous module resolution, loader(Slow)
Solution:
- Bundlers / Linkers
- browserify(static)
- requireJS(loader)
- systemJS(loader)
Problems:
commonJS syntax:
1 | // loading module |
- No static async / lazy loading(all bundles up front)
- commonJS bloat too dynamic
- Not everyone was shipping commonJS
AMD syntax:
1 | define('myAwesomeLib',['ladash', 'someDep'], function(_, someDep) { |
AMD + commonJS:
1 | define(function(require, exports, module) { |
Problems:
- Too dynamic of lazy loading(momentJS)
- Awkward now standard syntax(No real module system)
EcmaScript Modules(ESM)
1 | import {uniq, forOf, bar} from 'lodash-es' |
Benefit:
- Reusable
- Encapsulated
- Organized
- Convenient
Problems:
- ESM for node?
- How does they work in the browser?(Incredibly slow)
Introducing Webpack
Every library is different… Library authors use the module types that they like and choose.
And this is just for JavaScript… Each and every other filetype until now has had to have specific ways to process it.
Wouldn’t it be nice…
Webpack is a module bundler
- Lets you write any module format(mixed!), compiles then for the borwser.
- Supports static async bundling
- Rich, vast, ecosystem
- The most performant way to ship JavaScript today
Configuring Webpack
Webpack - How to use it?
Config - (webpack.config.js) Yes, it’s a module too!
1
2
3
4
5
6
7
8
9
10module.exports = {
entry: {
vendor: './src/vendor.ts',
main: './src/main.browser.ts'
},
output: {
path: 'dist/',
filename: '[name].bundle.js'
}
}Webpack CLI
1
2
3webpack <entry,js> <results.js> --colors --progress
webpack-dev-server --port=9000Node API
1
2
3
4
5
6
7
8
9
10const webpack = require('webpack')
// returns a conpiler instance
webpack({
// configuration object here!
}, function(err, stats) {
//...
// compilerCallback
console.log(err)
})
Webpack from Scratch
Using Webpack for the First Time
Webpack Academy Courses for Frontend Masters
git checkout feature/01-fem-first-script
npm i
npm run webpack
Adding npm Scripts for Environment Builds
Webpack by default, before Webpack 4, you really were only required to specify two properties, your input and your output.
Webpack look for entry property, we default to ./src/index.js
.
直接使用默认的设置npm run webpack
会提示设置模式,
下面在package.json
中scripts
中添加命令
package.json
1 | { |
开发模式npm run dev
生产模式npm run prod
在node中进行debug:
1 | git checkout feature/03-fem-debug-script --force |
Setting Up Debugging
可以在package.json
中的scripts
字段添加以下命令
"debugthis": "node --inspect --inspect-brk ./src/index.js"
Coding Your First Module
touch ./src/nav.js
在nav.js导出模块
1 | export default 'nav' |
在index.js引入模块
1 | import nav from './nav' |
npm run prod
node ./dist/main.js
可以看到控制台输出nav
Adding Watch Mode
在package.json中的scripts中添加
"dev": "npm run webpack -- --mode development --watch",
npm run dev
修改nav.js如下
1 | export default () => 'nav' |
index.js
1 | import nav from './nav' |
可以看到控制台显示了webpack进行了重新编译
ES Module Syntax
touch ./src/footer.js
footer.js:
1 | export const top = 'top' |
index.js:
1 | import nav from './nav' |
npm run prod
node ./dist/main.js
可以看到输出nav top bottom
CommonJS Export
touch ./src/button.js
button.js:
1 | // take a str, the button label and return a element |
index.js:
1 | import nav from './nav' |
CommonJS Named Exports
touch ./src/button-style.js
可以修改footer.js模块导出语法如下:
1 | const top = 'top' |
button-style.js:
1 | const red = 'color: red', |
button.js:
1 | // 添加JSDoc文档 |
index.js:
1 | import nav from './nav' |
Tree Shaking
npm run prod
在导出的main.js中,可以发现并没有red或者blue字符串。
这叫做dead code elimination或者tree shaking.
webpack只会在最终打包使用的模块。
touch ./webpack.config.js
Webpack Bundle Walkthrough
通过观察导出的main.js我们可以看到最终生成了一个IIFEs,其中IIFE的参数为一个数组,参数数组的每一个元素都是一个函数,
Webpack Core Concepts
Webpack Entry
The first JavaScript file to load to “Kick-off” your app.
Webpack uses this as the starting point.
Entry tells webpack what(files) to load for the browser; Compliments the output property.
1 | //webpack.config.js |
Output & Loaders
Output
Tells webpack where and how to discribute bundles(compilations). Work with Entry.
1 | //webpack.config.js |
Loaders + Rules
Loaders tells webpack how to modify files before its added to dependency graph
Loaders are also JavaScript modules(functions) that takes the source file, and returns it in a [modified] state.
1 |
|
1 | module: { |
- test
- A regular expression that instructs the compiler which files to run the loader against.
- use
- An array/string/function that returns loader objects.
- enforce
- Can be “pre” or “post”, tells webpack to run this rule before or after all other rules.
- include
- An array of regular expression that instruct the compiler which folders/files to include. Will only search paths provided with the include.
- exclude
- An array of regular expression that instructs the compiler which folders/files to ignore.
1 | module: { |
Chainning Loaders
1 | rules: [ |
这里参数数组传入的顺序为从右到左,可以理解为style(css(less()))
,既函数从内至外执行。
Loaders
Tells webpack how to interpret and translate files. Transformed on a per-file basis before adding to the dependency graph
Webpack Plugins
- Objects(with an ‘apply’ property)
- Allow you to hook into the entire compilation lifecycle
- webpack has a variety of built in plugins
Basic plugin example:
1 | function BellOnBundlerErrorPlugin () { } |
A plugin is an ES5 ‘class’ which implements an apply function.
The compiler uses it to emit events.
How to use Plugins
1 | // require() from node_modules or webpack or local file |
require() plugin from node_modules into config.
add new instance of plugin to plugins key in config object.
provide additional info for arguments
Plugins
Adds additional functionality to Compilations(optimaized bundled modules).
More powerful w/ more access to CompilerAPI. Does everything else you’d ever want to in webpack.
Webpack Config
git checkout feature/0310-add-first-config-mode-none -f
1 | module.exports = () => ({ |
Passing Varible to Webpack Config
修改package.json中scripts,在mode前加env.
:
1 | "scripts": { |
修改webpack.config.js,添加env参数
1 | module.exports = env => { |
1 | module.exports = env => { |
也可以使用对象解构赋值写为如下:
1 | module.exports = ({ mode }) => { |
Adding Webpack Plugins
npm i html-webpack-plugin -D
mkdir ./build-utils
1 | const webpack = require('webpack') |
npm run prod
可以看到生成了index.html
Setting Up a Local Development Server
git checkout feature/0311-add-first-plugins -f
npm i webpack-dev-server -D
在package.json的scripts中添加
"webpack-dev-server": "webpack-dev-server",
修改
"dev": "npm run webpack-dev-server -- --env.mode development",
修改button.js
1 | // take a str, the button label and return a element |
修改index.js
1 | import nav from './nav' |
可以看到页面上热更新了按钮。
Starting to Code with Webpack
git checkout feature/0312-webpack-dev-server -f
npm run dev
index.js:
1 | import nav from './nav' |
footer.js:
1 | import { red, blue } from './button-styles' |
index.js:
1 | import nav from './nav' |
Splitting Environment Config Files
修改webpack.config.js, 添加modeConfig模块,给module.exports设置默认参数(es6新语法)
1 | const webpack = require('webpack') |
npm i webpack-merge -D
touch ./build-utils/webpack.production.js
touch ./build-utils/webpack.development.js
./build-utils/webpack.production.js:
1 | module.exports = () => ({ |
./build-utils/webpack.development.js:
1 | module.exports = () => ({}) |
webpack.config.js:
1 | const webpack = require('webpack') |
npm run prod
之后可以看到在production模式生成了一个hash值为名的js文件。
Using Plugins
Using CSS with Webpack
git checkout feature/04010-composing-configs-webpack-merge -f
touch ./src/footer.css
footer.css:
1 | footer { |
footer.js:
1 | import './footer.css' |
webpack.development.js:
1 | module.exports = () => ({ |
添加style-loader和css-loader后可以看到导入的css文件已经作用在开发模式下。
Hot Module Replacement with CSS
git checkout feature/040101-add-style-loader -f
修改package.json中scripts:
"dev": "npm run webpack-dev-server -- --env.mode development --hot",
可以看到浏览器无需刷新即可更新最新的状态。
npm i mini-css-extract-plugin -D
更改webpack.production.js:
1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') |
npm run prod
可以看到导出了main.css
可以看到,引入的其他css文件最终也会被打包到main.css中。
File Loader & Url Loader
git checkout feature/04011-adding-styles-css -f
npm i file-loader url-loader -D
webpack.config.js:
1 | const webpack = require('webpack') |
Loading Images with JavaScript
在index.js中
1 | import image from './someImage.jpg' |
可以看到控制台输出了base 64格式的图片代码。
Limit Filesize Option in URL Loader
webpack.config.js
1 | const webpack = require('webpack') |
git checkout feature/04012-adding-images -f
npm run dev
Implementing Presets
touch ./build-utils/presets/loadPresets.js
loadPresets.js:
1 | const webpackMerge = require('webpack-merge') |
webpack.config.js
1 | const webpack = require('webpack') |
git checkout feature/04013-adding-presets -f
touch ./build-utils/webpack.typescript.js
1 | module.exports = () => ({ |
npm i ts-loader typescript@next -D
package.json
"prod:typescript": "npm run prod -- --env.presets typescript",
git checkout feature/04014-typescript-preset -f
npm run prod:typescript
Bundle Analyzer Preset
git checkout feature/04013-adding-presets -f
npm i webpack-bundle-analyzer -D
package.json:
"prod:analyze": "npm run prod -- --env.presets analyze",
touch ./build-utils/presets/webpack.analyze.js
webpack.analyze.js:
1 | const WebpackBundleAnalyzer = require('webpack-bundle-analyzer') |
npm run prod:analyze
可以看到打开了一个新的网页,显示了各个依赖模块及其大小
Compression Plugin
git checkout feature/040131-bundle-analyze -f
npm i compression-webpack-plugin -D
touch ./build-utils/presets/webpack.compress.js
webpack.compress.js:
1 | const CompressionWebpackPlugin = require('compression-webpack-plugin') |
package.json:
"prod:compress": "npm run prod -- --env.presets compress",
npm run prod:compress
npm run prod:compress -- --env.presets analyze
Source Map
webpack.production.js:
1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') |
npm run prod -- --env.presets compress
webpack.production.js:
1 | module.exports = () => ({ |
npm run dev -- --env.presets compress
Lazy Loading
index.js:
1 | // import footer from './footer' |