Our example app:
Create webpack.config.ts
...
...wait... what? π§
That's right: you can use TypeScript in your webpack config.
.ts
file extension.
typescript
@types/webpack
and
ts-node
.
Add this to your tsconfig.json
:
{
"compilerOptions": {
"esModuleInterop": true
}
}
webpack.Configuration
type.
import webpack from "webpack";
const config: webpack.Configuration = {
// ...
};
export default config;
const config: webpack.Configuration = {
// ...
};
export default config;
const createConfig = () => {
const config: webpack.Configuration = {
// ...
};
return config;
};
export default createConfig;
const createConfig = () => {
const config: webpack.Configuration = {
// ...
};
return config;
};
const createConfig = (env) => {
const config: webpack.Configuration = {
// ...
};
return config;
};
const createConfig = ({mode}) => {
const config: webpack.Configuration = {
mode,
};
return config;
};
const createConfig = ({mode = "production"} = {}) => {
const config: webpack.Configuration = {
mode,
};
return config;
};
Webpack has three different modes to enable the following default configurations:
development |
No bundle optimizations, such as
minification and tree shaking, but with
developer tool support.
process.env.NODE_ENV is set
to "development" which
switches some modules (such as React)
into development mode.
|
production |
All bundle optimizations enabled, but no
developer tool support by default.
process.env.NODE_ENV is set
to "production" .
|
none | Disable any default configuration. |
The mode
applies defaults. You can
always override them...
...but the default configuration is often good enough for a lot of applications. Only override them if you know what you're doing.
"scripts": {
"build": "webpack",
"dev": "webpack --env.mode=development"
}
const config: webpack.Configuration = {
mode,
};
const config: webpack.Configuration = {
mode,
entry: require.resolve("./src/start.ts"),
};
// ...
const config: webpack.Configuration = {
// ...
output: {
}
};
// ...
const config: webpack.Configuration = {
// ...
output: {
path: path.resolve(__dirname, "dist"),
}
};
// ...
const config: webpack.Configuration = {
// ...
output: {
}
};
// ...
const isDev = mode === "development";
const config: webpack.Configuration = {
// ...
output: {
}
};
// ...
const isDev = mode === "development";
const outputFilenamePattern = isDev ? "[name].js" : "[name].[contenthash].js";
const config: webpack.Configuration = {
// ...
output: {
}
};
// ...
const isDev = mode === "development";
const outputFilenamePattern = isDev ? "[name].js" : "[name].[contenthash].js";
const config: webpack.Configuration = {
// ...
output: {
filename: outputFilenamePattern,
chunkFilename: outputFilenamePattern,
}
};
π In development
main.js
posts.js
about.js
π¬ In production
main.7f78047c8cc5f42081dd.js
posts.6d780j728dc5f42l87gd.js
about.8lss01xc87f3ga28ks6d.js
babel-loader
Wait... isn't this a TypeScript project? π§
Aren't we supposed to use the ts-loader
then?
You can ...
...but @babel/preset-typescript
works also pretty good.
@babel/preset-typescript
...
const enum
s and there are other minor differences (see caveats)
π
NO TYPE CHECKS???Isn't that the point of using TypeScript?
It is, but...
...do we need to check the types during a webpack build?
...do we need to lint our code during a webpack build?
Do less.
Thanks for coming to my TED talk.
~40s
~12s
Just by dropping eslint-loader
posttest
oneOf: [
{
loader: "babel-loader",
},
],
oneOf: [
{
test: /\.tsx?$/,
loader: "babel-loader",
},
},
],
oneOf: [
{
test: /\.tsx?$/,
loader: "babel-loader",
options: {
},
},
],
oneOf: [
{
test: /\.tsx?$/,
loader: "babel-loader",
options: {
presets: [
"@babel/typescript",
"@babel/react",
],
},
},
],
oneOf: [
{
test: /\.tsx?$/,
loader: "babel-loader",
options: {
// ...
},
},
],
oneOf: [
{
test: /\.tsx?$/,
loader: "babel-loader",
options: {
// ...
cacheDirectory: true,
},
},
],
oneOf: [
{
include: [path.resolve(__dirname, "src")],
test: /\.tsx?$/,
loader: "babel-loader",
options: {
// ...
cacheDirectory: true,
},
},
],
test
for file extensionsinclude
for directories and absolute pathsrules: [{
include: [
path.resolve(__dirname, "src"),
],
test: /\.js$/,
use: ["babel-loader"],
}],
rules: [{
include: [
path.resolve(__dirname, "src"),
path.resolve(__dirname, "node_modules", "modern-module-b"),
],
test: /\.js$/,
use: ["babel-loader"],
}],
@babel/preset-env
and use a .browserslistrc
// babel-loader options
presets: [
"@babel/typescript",
"@babel/react",
[
"@babel/env",
],
],
// babel-loader options
presets: [
"@babel/typescript",
"@babel/react",
[
"@babel/env",
{
modules: false,
},
],
],
// babel-loader options
presets: [
"@babel/typescript",
"@babel/react",
[
"@babel/env",
{
modules: false,
useBuiltIns: "usage",
},
],
],
Now let's create a .browserslistrc
file in our project root.
.browserslistrc
last 2 versions, not dead, IE > 8
That's nice for production builds...
...but do we want to do that in development builds as well? π€
No.
last 2 versions, not dead, IE > 8
[production]
last 2 versions, not dead, IE > 8
[production]
last 2 versions, not dead, IE > 8
[development]
last 1 Chrome version, last 1 Firefox version
const config: webpack.Configuration = {
// ...
};
return config;
const config: webpack.Configuration = {
// ...
};
process.env.BROWSERSLIST_ENV =
process.env.BABEL_ENV = isDev ? "development" : "production";
return config;
This may speed up the build a little bit...
...but more importantly it improves debuggability!
Features like async/await
can be annoying to debug in transpiled code.
There are a lot of different solutions, but my general advice is:
Embrace
JavaScript
as your
CSS preprocessor!
(aka CSS-in-JS)
CSS-in-JS in combination with the mini-css-extract-plugin theoretically allows us
No giant main.css
file! πͺ
No unused styles! πͺ
There are many great CSS-in-JS solutions, most notably:
but most of them come with a runtime cost π°
Which is why I prefer:
We won't go much into detail because every app is different.
But these are my general tips:
mode: "production"
is good enough for most apps
Use dynamic import()
π
...should be lazy-loaded via import()
.
You can tell Webpack to prefetch chunks via
<link rel="prefetch">
:
import(/* webpackPrefetch: true */ "./popup.js")
Check what you're importing:
https://bundlephobia.com
Don't forget to measure!
optimization.splitChunks
without measuring makes no sense
Don't overestimate long-term caching.
Long-term caching for fonts, images and CSS is often good enough π€·ββοΈ
Minify your CSS
development
and production
test
for file extensions
include
for paths
cacheDirectory: true
in your babel-loader options
.browserslistrc
import()
If you don't like to configure that for yourself: