webpack para alta performance front-end

Otimizando suas configurações para melhorar seu bundle final

Quer ficar por dentro das novidades do webpack? Se inscreva já https://webpack.academy/

Créditos

Do que iremos falar (mais 🚀 === mais velocidade):

💻 Códigos para nosso exemplo

1. Scope Hoisting

const webpack = require('webpack');module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
],
};

2. Minificação e Uglificação

          Asset     Size  Chunks                    Chunk Names
index.bundle.js 2.46 MB 0 [emitted] [big] index
webpack -p
          Asset     Size  Chunks                    Chunk Names
index.bundle.js 1.02 MB 0 [emitted] [big] index

📝 Nota

NODE_ENV=production PLATFORM=web webpack -p

💁 Dica

"scripts": {
"build": "webpack -p"
},

Configurando UglifyJS

plugins:[
new webpack.optimize.UglifyJsPlugin({/* opções aqui */}),
],

⚠️🐌 Alerta sobre seu build

3. Importações Dinâmicas e Módulos sob Demanda

          Asset     Size  Chunks                    Chunk Names
index.bundle.js 1.02 MB 0 [emitted] [big] index

Importação Dinâmica, Parte 1: Configuração do Babel

yarn add babel-loader babel-core babel-preset-env
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
},
],
},
yarn add babel-plugin-syntax-dynamic-import
{
"presets": ["env"],
"plugins": ["syntax-dynamic-import", "transform-react-jsx"]
}

💁 Dica

Importação Dinâmica, Parte 2: import()

import Home from './components/Home';
const Home = import('./components/Home');
import React from 'react';
import Async from 'react-code-splitting';
const Nav = () => (<Async load={import('./components/Nav')} />);
const Home = () => (<Async load={import('./views/home')} />);
const Countdown = () => (<Async load={import('./views/countdown')} />);
    0.bundle.js     222 kB       0  [emitted]         
1.bundle.js 533 kB 1 [emitted] [big]
2.bundle.js 1.41 kB 2 [emitted]
index.bundle.js 229 kB 3 [emitted] index

4. Hashes Determinísticos

output: {
filename: '[name].[hash].js',
},

⚠️🐌 Alerta sobre seu build

yarn add chunk-manifest-webpack-plugin webpack-chunk-hash
const webpack = require('webpack');
const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
const WebpackChunkHash = require('webpack-chunk-hash');
const HtmlWebpackPlugin = require('html-webpack-plugin');
/* Configuração compartilhada Dev & Prod */const config = {
/* … nossa configuração até esse momento */
plugins: [
// /* outros plugins aqui */
//
// /* Descomente para gerar HTML automaticamente */
// new HtmlWebpackPlugin({
// inlineManifestWebpackName: 'webpackManifest',
// template: require('html-webpack-template'),
// }),
],
};
/* Produção */if (process.env.NODE_ENV === 'production') {
config.output.filename = '[name].[chunkhash].js';
config.plugins = [
// ES6 Array Destructuring, disponível em Node 5+
...config.plugins,
new webpack.HashedModuleIdsPlugin(),
new WebpackChunkHash(),
new ChunkManifestPlugin({
filename: 'chunk-manifest.json',
manifestVariable: 'webpackManifest',
inlineManifest: true,
}),
];
}
module.exports = config;

💁 Dica

<head>
<script>
//<![CDATA[
window.webpackManifest = { /* contents of chunk-manifest.json */ };
//]]>
</script>
</head>

5. CommonsChunkPlugin, desduplicação e cache de vendors

module.exports = {
entry: {
app: './app.js',
vendor: ['react', 'react-dom', 'react-router'],
},
};
           Asset    Size  Chunks                    Chunk Names
index.bundle.js 230 kB 3 [emitted] index
vendor.bundle.js 173 kB 4 [emitted] vendor
const webpack = require('webpack');plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
}),
],

💁 Dica

           Asset     Size  Chunks               Chunk Names
index.bundle.js 55.7 kB 3 [emitted] index
vendor.bundle.js 174 kB 4 [emitted] vendor
<!-- vendor vem primeiro! -->
<script src="vendor.bundle.js"></script>
<script src="index.bundle.js"></script>

💁 Dica

6. Offline Plugin para webpack

yarn add offline-plugin
const OfflinePlugin = require('offline-plugin');module.exports = {
entry: {
// Adicionando ao ponto de entrada de vendor, porém é opcional
vendor: ['offline-plugin/runtime', /* … */],
},
plugins: [
new OfflinePlugin({
AppCache: false,
ServiceWorker: { events: true },
}),
],
};
/* index.js */if (process.env.NODE_ENV === 'production') {
const runtime = require('offline-plugin/runtime');
runtime.install({
onUpdateReady() {
runtime.applyUpdate();
},
onUpdated() {
window.location.reload();
},
});
}

7. webpack Bundle Analyzer

yarn add --dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;config = { /* configurações comuns */ };if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
config.plugins = [
...config.plugins,
new BundleAnalyzerPlugin(),
];
}
node_module/.bin/webpack --profile --json > stats.json
Bundle antes da otimização
                    Asset    Size Chunks             Chunk Names
0.cc206a4187c30a32c54e.js 224 kB 0 [emitted]
Bundle depois da otimização
                    Asset     Size Chunks            Chunk Names
0.4108c847bef03ae9e840.js 62.7 kB 0 [emitted]

8. Múltiplos pontos de entrada automaticamente com CommonsChunkPlugin

module.exports = {
entry: {
main: './main.js',
account: './account.js',
shop: './shop.js',
},
};
/* Dev & Prod */
new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
minChunks: 2,
}),
/* Prod */
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity,
}),

Analisando nossas otimizações e conclusão

Resumo das etapas de otimizações

📚 Leitura adicional e notas

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store