CTO @ Kaleidoscope Srl
Teacher @ ITS Logistica
Per ottimizzare sempre si consideri il modello RAIL (Response, Animation, Idle, Load)
https://developers.google.com/web/progressive-web-apps/checklist
# SUL TERMINALE
npm install -g lighthouse
# LANCIARE UN AUDIT
lighthouse <url>
Installiamo prima :
- Google Chrome
- Node.JS LTS
# SUL TERMINALE
yarn add webpack webpack-cli --dev
// package.json
"scripts": {
"dev": "webpack --mode development --watch",
"build": "webpack --mode production --watch"
}
# SUL TERMINALE
yarn dev
// webpack.config.js
module.exports = {
entry: './src/index.js'
};
Suggerisce a webpack da quale modulo iniziare il bundling.
Suggerisce a webpack dove salvare il bundle creato.
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
}
};
Suggerisce a webpack da quale modulo iniziare il bundling.
// webpack.config.js
const path = require('path');
module.exports = {
entry: {
first: './src/one.js',
second: './src/two.js',
},
output: {
filename: '[name].bundle.js', // GENERA first.bundle.js, second.bundle.js, ecc.
path: path.resolve(__dirname, 'dist')
}
}
Oltre che a creare bundle, con webpack è possibile effettuare altre operazioni in fase di bundling attraverso i loaders.
# TERMINALE
yarn add css-loader --dev
// webpack.config.js
module.exports = {
[...],
module: {
rules: [{
test: /\.css$/,
use: 'css-loader'
}]
}
}
rules è un array di tutti le regole e dei loader da utilizzare a tutti i file che matchano la proprietà test.
use indica quale loader usare nel caso il file matchasse la regola
# TERMINALE
yarn add node-sass sass-loader style-loader --dev
// webpack.config.js
module.exports = {
[...],
module: {
rules: [{
test: /\.(sass|scss)$/,
use: [{
loader: 'style-loader' // creates style nodes from JS strings
}, {
loader: 'css-loader' // translates CSS into CommonJS
}, {
loader: 'sass-loader', // compiles Sass to CSS
options: {
includePaths: [
__dirname + '/resources/assets/sass/frontend/'
]
}
}]
}]
}
}
// style.scss
body {
background-color: $red;
}
// index.js
import './style.scss';
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
<style type="text/css">
body {
background-color:red;
}
</style>
</head>
<body>
<script type="text/javascript" src="bundle.js"></script>
</body>
</html>
# TERMINALE
yarn add babel-core babel-loader babel-preset-env ts-loader --dev
// webpack.config.js
module.exports = {
[...],
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: [
'/node_modules/',
'/vendor/'
]
}]
}
}
# TERMINALE
yarn add webpack-manifest-plugin mini-css-extract-plugin --dev
// webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ManifestPlugin = require('webpack-manifest-plugin');
module.exports = {
[...],
module: {
rules: [{
test: /\.(sass|scss)$/,
use: [
"style-loader",
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}]
},
plugins: [
new ManifestPlugin(),
new MiniCssExtractPlugin({
filename: 'style.css',
})
]
}
// webpack.config.js
module.exports = {
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
}
// webpack.config.js
const config = {
[...]
};
module.exports = (env, argv) => {
config.mode = argv.mode;
config.optimization = {
minimize: true,
minimizer: [
new TerserPlugin()
]
};
return config;
};
Minimizer disponibili:
// webpack.config.js
module.exports = {
[...],
plugins: [
new webpack.NoEmitOnErrorsPlugin();
];
};
Se webpack, durante la compilazione, genera un errore, questo viene incluso del bundle.
Con il plugin NoEmitOnErrors, questo viene evitato.
// webpack.config.js
module.exports = {
[...],
plugins: [
new webpack.optimization.ModuleConcatenationPlugin()
];
};
Webpack, in modalità dev, wrappa ogni modulo in una closure, rendendo l'esecuzione più lenta.
Con questo plugin (ormai abilitato di default) è possibile evitare le generazione di ogni singola closure, diminuendo i tempi di esecuzione.
//./utilities/users.js
export default [
{ firstName: "Adam", age: 28 },
{ firstName: "Jane", age: 24 },
{ firstName: "Ben", age: 31 },
{ firstName: "Lucy", age: 40 }
];
// first.js
import _ from 'lodash';
import users from './users';
const adam = _.find(users, { firstName: 'Adam' });
// second.js
import _ from 'lodash';
import users from './users';
const lucy = _.find(users, { firstName: 'Lucy' });
// webpack.config.js
module.exports = {
entry: {
first: "./src/first.js",
second: "./src/second.js"
},
output: {
filename: "[name].[chunkhash].bundle.js",
path: __dirname + "/dist"
}
};
WebPack genererà : first.[chunkhash].bundle.js e second.[chunkhash].bundle.js; entrambi contengono lodash e users.js
// webpack.config.js
module.exports = {
[...],
optimization: {
splitChunks: {
chunks: "all"
}
},
};
WebPack genererà :
Entrambi first.[chunkhash].bundle.js e second.[chunkhash].bundle.js contengono users.js
// webpack.config.js
module.exports = {
[...]
splitChunks: {
chunks: "all",
minSize: 30,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
// webpack.config.js
module.exports = {
[...],
optimization: {
splitChunks: {
chunks: "all",
minSize: 0 // Webpack genererà first~second.[chunkhash].bundle.js
}
},
};
WebPack genererà :
# SUL TERMINALE
yarn add --save-dev webpack-lighthouse-plugin
// webpack.config.js
const WebpackLighthousePlugin = require('webpack-lighthouse-plugin');
module.exports = {
[...]
plugins: [
new WebpackLighthousePlugin({
url: 'http://localhost:9001'
})
]
}
// webpack.config.js
const WebpackLighthousePlugin = require('webpack-lighthouse-plugin');
module.exports = {
[...]
plugins: [
new WebpackLighthousePlugin({
url: 'http://localhost:9001',
perf: true, // EFFETTUA L'AUDIT SOLO DELLE PERFORMANCE
disableCPUThrottling: false, // DISABILITA LO STRESS TEST SULLA CPU
disableNetworkThrottling: true, // DISABILITA LO STRESS TEST SULLA CONNESSIONE
saveAssets: true, // SALVA SCREENSHOT, TRACE O REPORT
})
]
}
Bundle JS: 4.7 MB
Immagini: 445KB
Altro (html, css, fonts, ecc.): 30KB
Totale: 5.16 MB
Bundle JS: 4.7 MB
Immagini: 3.7MB
Altro (html, css, fonts, ecc.): 30KB
Totale: 8.42 MB
<head>
<link rel="stylesheet" href="/style.css">
...
</head>
<body>
<p>Non visibile fino a che non ho caricato style.css!</p>
</body>
# TERMINALE
yarn add html-critical-webpack-plugin -d
// webpack.config.js
module.exports = {
[...],
plugins: [
new HtmlWebpackPlugin({ ... }),
new MiniCssExtractPlugin({ ... }),
new HtmlCriticalWebpackPlugin({
base: path.resolve(__dirname, 'dist'),
src: 'index.html',
dest: 'index.html',
inline: true,
minify: true,
extract: true,
width: 375,
height: 565,
penthouse: {
blockJSRequests: false,
}
})
],
[...]
};
<!-- index.html -->
<head>
<style type="text/css">
/* Il CSS definito critico viene messo in linea nell'head. */
body {
font-family: Helvetica Neue,Helvetica, Arial,sans-serif;
font-size: 14px;
line-height: 1.42857;
color: #333;
background-color: #fff;
}
</style>
<link href="/style.96106fab.css" rel="preload" as="style" onload="this.rel='stylesheet'">
<noscript>
<link href="/style.96106fab.css" rel="stylesheet">
</noscript>
<script>
/* Uno script si occupa di caricare il CSS "non critico" */
</script>
</head>
<body>
[...]
<script type="text/javascript" src="/build_main.js"></script>
</body>
</html>
<link rel="preload" as="script" href="super-important.js">
<link rel="preload" as="style" href="critical.css">
<link rel="preload" as="font" crossorigin="crossorigin" type="font/woff2" href="myfont.woff2">
rel="preload" : recupera questa risorsa il prima possibile
as="script" : suggerisco al browser già che tipo di risorsa è questa
crossorigin="crossorigin" : effettua questa richiesta come una CORS anonima, altrimenti il font viene caricato 2 volte
<link rel="preconnect" href="https://example.com">
rel="preconnect" : suggeriamo al browser che probabilmente servirà una risorsa reperibile all'URL specificato, il browser eviterà roundtrip inutili
1 roundtrip = lookup del DNS + negoziazione TLS + handshake TCP
rel="preconnect" torna utile nell'uso delle CDN
<link rel="dns-prefetch" href="https://example.com">
rel="dns-prefetch" : suggeriamo al browser di fare già il DNS Lookup dell'URL
A differenza del preconnect, dns-prefetch effettua SOLO il lookup del DNS.
<link rel="prefetch" href="page-2.html">
rel="prefetch" : suggeriamo al browser che probabilmente useremo la risorsa oppure no (a seconda di dinamiche dovute alle scelte dell'utente).
E' un modo per aiutare il browser a gestire le risorse in modo intelligente.
NON va usato, per specificare delle risorse come non prioritarie.
<head>
<link rel="prefetch" href="optional.css"><!-- CARICATO CON PRIORITA BASSA, PROBABILE CACHE HIT -->
<link rel="stylesheet" href="optional.css"><!-- CARICATO CON PRIORITA MASSIMA -->
</head>
Bundle JS: 831 KB
Immagini: 450KB
Altro (html, css, fonts, ecc.): 30KB
Totale: 1.28 MB
Bundle JS: 831 KB
Immagini: 3.7MB
Altro (html, css, fonts, ecc.): 30KB
Totale: 4.54 MB
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.(jpe?g|png|gif)$/,
loader: 'url-loader',
options: {
// Inserisce in linea immagini fino a 10 KB di peso
limit: 10 * 1024
}
}]
}
};
Il loader converte le immagini in Base 64
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.svg$/,
loader: 'svg-url-loader',
options: {
// Inserisce in linea immagini fino a 10 KB di peso
limit: 10 * 1024,
// Rimuove gli apici dall'URL codificato in base64
noquotes: true,
}
}]
}
};
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.(jpg|png|gif|svg)$/,
loader: 'image-webpack-loader',
enforce: 'pre'
}]
}
};
enforce applica il loader prima di url-loader/svg-url-loader
Modello RAIL:
https://developers.google.com/web/fundamentals/performance/rail
Resource Hinting:
https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf
https://developers.google.com/web/fundamentals/performance/resource-prioritization
Bundle Splitting:
https://medium.com/hackernoon/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
Addy Osmani - Guida alle immagini:
Webpack Bundle Analyzer:
https://www.npmjs.com/package/webpack-bundle-analyzer
Critial CSS:
Full Stack Developer @KaleidoscopeSrl
Junior UX Designer @Fondazione Edulife