Jak pakiet internetowy pomaga buforować zasoby
Następnie (po zoptymalizowaniu rozmiaru aplikacji, skraca czas wczytywania aplikacji to buforowanie. Dzięki niemu niektóre części aplikacji będą dostępne klienta i unikać ich ponownego pobierania za każdym razem.
Użyj obsługi wersji pakietów i nagłówków pamięci podręcznej
Typowym sposobem korzystania z buforowania jest:
poinstruuj przeglądarkę, aby zapisywała plik w pamięci podręcznej przez bardzo długi okres (np. rok):
# Server header Cache-Control: max-age=31536000
Jeśli nie wiesz, co robi
Cache-Control
, zobacz film Jake'a Archibalda świetny post na buforowanie najlepsze metod Google Play.i zmień nazwę pliku, aby wymusić ponowne pobieranie:
<!-- Before the change --> <script src="./index-v15.js"></script> <!-- After the change --> <script src="./index-v16.js"></script>
Dzięki temu przeglądarka może pobrać plik JS, zapisać go w pamięci podręcznej i użyć kopii zapasowej. Przeglądarka łączy się z siecią tylko w przypadku zmiany nazwy pliku (lub jeśli minął rok).
W przypadku Webpacka robisz to samo, ale zamiast numeru wersji podajesz
hasz pliku. Aby umieścić hasz w nazwie pliku, użyj
[chunkhash]
:
// webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
}
};
Jeśli potrzebujesz
nazwę pliku, aby wysłać go do klienta, użyj HtmlWebpackPlugin
lub
WebpackManifestPlugin
HtmlWebpackPlugin
to
proste, ale mniej elastyczne. Podczas kompilacji ta wtyczka generuje
Plik HTML zawierający wszystkie skompilowane zasoby. Jeśli logika Twojego serwera nie jest
to powinno wystarczyć:
<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>
WebpackManifestPlugin
to bardziej elastyczne podejście, które jest przydatne w przypadku złożonych elementów serwera.
Podczas kompilacji generowany jest plik JSON z mapowaniem między nazwami plików
bez haszy i nazw plików z haszem. Użyj tego pliku JSON na serwerze, aby się dowiedzieć
wybrać odpowiedni plik:
// manifest.json
{
"bundle.js": "bundle.8e0d62a03.js"
}
Więcej informacji
- Jake Archibald jak najlepiej buforować pamięć podręczną metody
Wyodrębnij zależności i środowisko wykonawcze do oddzielnego pliku
Zależności
Zależności aplikacji zmieniają się rzadziej niż rzeczywisty kod aplikacji. Jeśli przeprowadzisz się do osobnego pliku, przeglądarka może zapisać je w pamięci podręcznej oddzielnie, i nie będą pobierać ich ponownie po każdej zmianie kodu aplikacji.
Aby wyodrębnić zależności do osobnego fragmentu, wykonaj 3 kroki:
Zastąp nazwę pliku wyjściowego
[name].[chunkname].js
:// webpack.config.js module.exports = { output: { // Before filename: 'bundle.[chunkhash].js', // After filename: '[name].[chunkhash].js' } };
Podczas tworzenia aplikacji pakiet internetowy zastępuje
[name]
i nazwą fragmentu. Jeśli nie dodamy części[name]
, rozróżniania fragmentów według skrótu, co jest dość trudne.Przekonwertuj pole
entry
na obiekt:// webpack.config.js module.exports = { // Before entry: './index.js', // After entry: { main: './index.js' } };
W tym fragmencie kodu „main” to nazwa fragmentu. Ta nazwa zostanie zastąpiona w miejsce
[name]
z kroku 1.Jeśli stworzysz aplikację, ten fragment będzie zawierał cały kod aplikacji – jak nie wykonaliśmy jeszcze tych czynności. Ale to się za chwilę zmieni.
W pakiecie webpack 4 dodaj opcję
optimization.splitChunks.chunks: 'all'
. do konfiguracji pakietu internetowego:// webpack.config.js (for webpack 4) module.exports = { optimization: { splitChunks: { chunks: 'all' } } };
Ta opcja umożliwia inteligentne dzielenie kodu. Dzięki niemu Webpack wyodrębniłby kod dostawcy, jeśli większe niż 30 kB (przed minifikacją i gzip). Wyodrębniłby też wspólny kod – przydaje się to, gdy konstrukcja obejmuje kilka pakietów (np. jeśli aplikacja została podzielona na trasy).
W pakiecie webpack 3 dodaj
CommonsChunkPlugin
:// webpack.config.js (for webpack 3) module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ // A name of the chunk that will include the dependencies. // This name is substituted in place of [name] from step 1 name: 'vendor', // A function that determines which modules to include into this chunk minChunks: module => module.context && module.context.includes('node_modules'), }) ] };
Ta wtyczka bierze pod uwagę wszystkie moduły, które zawierają ścieżki
node_modules
i przeniesie je do osobnego pliku o nazwievendor.[chunkhash].js
.
Po wprowadzeniu tych zmian każda kompilacja wygeneruje 2 pliki zamiast jednego: main.[chunkhash].js
oraz
vendor.[chunkhash].js
(vendors~main.[chunkhash].js
w przypadku pakietu internetowego 4). W przypadku pakietu webpack 4:
pakiet dostawców może nie zostać wygenerowany, jeśli zależności są małe.
$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
Asset Size Chunks Chunk Names
./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main
./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor
Przeglądarka zapisze te pliki oddzielnie w pamięci podręcznej i ponownie pobierze tylko zmieniony kod.
Kod środowiska wykonawczego Webpack
Wyodrębnienie samego kodu dostawcy nie wystarcza. Jeśli spróbujesz zmień coś w kodzie aplikacji:
// index.js
…
…
// E.g. add this:
console.log('Wat');
zauważysz, że wartość hash vendor
również się zmieni:
Asset Size Chunks Chunk Names
./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor
↓
Asset Size Chunks Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js 47 kB 1 [emitted] vendor
Dzieje się tak, ponieważ pakiet Webpack, oprócz kodu modułów, ma aspekt (środowisko wykonawcze) – krótki fragment kodu. który zarządza wykonaniem modułu. Gdy podzielisz kod na kilka plików, Ten fragment kodu zawiera mapowanie między identyfikatorami fragmentów odpowiadające im pliki:
// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
"0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";
Webpack zawiera to środowisko wykonawcze w ostatnim wygenerowanym fragmencie, czyli vendor
w naszym przypadku. Za każdym razem, gdy zmienia się jakiś fragment,
ten fragment kodu też się zmienia,
powodując zmianę całego fragmentu vendor
.
Aby rozwiązać ten problem, przenieśmy środowisko wykonawcze do osobnego pliku. W pakiecie webpack 4 jest to:
co można osiągnąć przez włączenie opcji optimization.runtimeChunk
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
runtimeChunk: true
}
};
W pakiecie webpack 3 utwórz dodatkowy pusty fragment za pomocą pakietu CommonsChunkPlugin
:
// webpack.config.js (for webpack 3)
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: module => module.context && module.context.includes('node_modules')
}),
// This plugin must come after the vendor one (because webpack
// includes runtime into the last chunk)
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
// minChunks: Infinity means that no app modules
// will be included into this chunk
minChunks: Infinity
})
]
};
Po wprowadzeniu tych zmian każda kompilacja będzie generować 3 pliki:
$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
Asset Size Chunks Chunk Names
./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main
./vendor.26886caf15818fa82dfa.js 46 kB 1 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
Dodaj je do elementu index.html
w odwrotnej kolejności – i gotowe:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>
Więcej informacji
- Przewodnik po pakietach Webpack dotyczący długoterminowego buforowania
- Dokumentacja Webpack dotycząca środowiska wykonawczego i pakietu Webpack plik manifestu
- „Aby w pełni korzystać z potencjału CommonsChunkPlugin"
- Jak działają
optimization.splitChunks
ioptimization.runtimeChunk
Wbudowane środowisko wykonawcze pakietu internetowego pozwalające zapisać dodatkowe żądanie HTTP
Aby jeszcze bardziej ulepszyć usługę, umieść środowisko wykonawcze pakietu internetowego w kodzie HTML . To znaczy, że:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
wykonaj następujące czynności:
<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>
Środowisko wykonawcze jest małe, a wbudowanie go pomoże zapisać żądanie HTTP (ładnie jest ważne w przypadku protokołu HTTP/1, mniej ważne w przypadku HTTP/2, ale nadal może odtwarzać efekt).
Oto jak to zrobić.
W przypadku generowania kodu HTML przy użyciu dodatku htmlWebpackPlugin
Jeśli używasz tagu HtmlWebpackPlugin do wygenerowania pliku HTML, InlineSourcePlugin wystarczy Ci:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
inlineSource: 'runtime~.+\\.js',
}),
new InlineSourcePlugin()
]
};
Jeśli generujesz kod HTML przy użyciu niestandardowej logiki serwera
W przypadku pakietu webpack 4:
Dodaj parametr
WebpackManifestPlugin
aby poznać wygenerowaną nazwę fragmentu środowiska wykonawczego:// webpack.config.js (for webpack 4) const ManifestPlugin = require('webpack-manifest-plugin'); module.exports = { plugins: [ new ManifestPlugin() ] };
Kompilacja z tą wtyczką mogłaby spowodować utworzenie pliku podobnego do tego:
// manifest.json { "runtime~main.js": "runtime~main.8e0d62a03.js" }
Możesz w wygodny sposób umieścić treść fragmentu środowiska wykonawczego. Na przykład: Node.js i Express:
// server.js const fs = require('fs'); const manifest = require('./manifest.json'); const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8'); app.get('/', (req, res) => { res.send(` … <script>${runtimeContent}</script> … `); });
Lub w pakiecie webpack 3:
Ustaw statyczną nazwę środowiska wykonawczego, określając
filename
:module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', minChunks: Infinity, filename: 'runtime.js' }) ] };
Umieść w tekście treści
runtime.js
w wygodny sposób. Na przykład: Node.js i Express:// server.js const fs = require('fs'); const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8'); app.get('/', (req, res) => { res.send(` … <script>${runtimeContent}</script> … `); });
leniwe ładowanie kodu, którego teraz nie potrzebujesz;
Czasami strona zawiera więcej lub mniej ważne elementy:
- Jeśli wczytasz w YouTube stronę z filmem, bardziej zależy Ci na nim niż na jego treści komentarzy. W tym przypadku film jest ważniejszy niż komentarze.
- Jeśli otwierasz artykuł w witrynie z wiadomościami, bardziej zależy Ci na tekście niż o reklamach. W tym przypadku tekst jest ważniejszy niż reklamy.
W takich przypadkach możesz poprawić wydajność wczytywania początkowego, pobierając
W pierwszej kolejności to, co najważniejsze, a pozostałe w późniejszym czasie. Za pomocą
funkcji import()
i
code-splitting w tym celu:
// videoPlayer.js
export function renderVideoPlayer() { … }
// comments.js
export function renderComments() { … }
// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();
// …Custom event listener
onShowCommentsClick(() => {
import('./comments').then((comments) => {
comments.renderComments();
});
});
import()
określa, że określony moduł ma być ładowany dynamicznie. Kiedy
Webpack widzi adres import('./module.js')
, przenosi go do osobnego
chunk:
$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
Asset Size Chunks Chunk Names
./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]
./main.f7e53d8e13e9a2745d6d.js 60 kB 1 [emitted] main
./vendor.4f14b6326a80f4752a98.js 46 kB 2 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
i pobiera go dopiero wtedy, gdy wykonanie dotrze do funkcji import()
.
Spowoduje to skrócenie pakietu main
i skrócenie początkowego czasu wczytywania.
Co więcej, usprawni to buforowanie – jeśli zmienisz kod we fragmencie głównym,
nie wpłynie na fragment komentarzy.
Więcej informacji
- Dokumentacja pakietu dla:
import()
- Propozycja JavaScript wdrożenia tagu
import()
Podziel kod na trasy i strony
Jeśli aplikacja ma wiele tras lub stron, ale tylko jeden plik JS z
(pojedynczy fragment main
) prawdopodobnie wykorzystujesz dodatkowe bajty
każdego żądania. Na przykład, gdy użytkownik odwiedza stronę główną w Twojej witrynie:
nie muszą wczytywać kodu, by wyrenderować artykuł znajdujący się na innym ale zostaną wczytane. Co więcej, jeśli użytkownik zawsze odwiedza tylko stronę główną i wprowadzisz zmianę w kodzie artykułu, pakiet webpack unieważni całego pakietu, a użytkownik będzie musiał jeszcze raz pobrać całą aplikację.
Jeśli podzielisz aplikację na strony (lub trasy, jeśli jest to aplikacja jednostronicowa), użytkownik pobierze tylko właściwy kod. Dodatkowo przeglądarka zapisze kod aplikacji w pamięci podręcznej Lepiej: jeśli zmienisz kod strony głównej, pakiet internetowy unieważni tylko odpowiadający mu fragment.
Aplikacje jednostronicowe
Aby podzielić aplikacje jednostronicowe według trasy, użyj funkcji import()
(patrz „Kod leniwego ładowania”
których obecnie nie potrzebujesz”). Jeśli korzystasz z platformy,
może istnieć rozwiązanie tego problemu:
- „Kod
Dzielę”
w dokumentach użytkownika
react-router
(dla React) - „Leniwe ładowanie
Trasy” w
Dokumenty użytkownika
vue-router
(dla Vue.js)
Tradycyjne aplikacje wielostronicowe
Aby podzielić tradycyjne aplikacje na strony, użyj wpisu z pakietu webpack punktów. Jeśli aplikacja ma trzy różne rodzaje stron: strona główna, strona z artykułem i strona konta użytkownika; powinien zawierać 3 pozycje:
// webpack.config.js
module.exports = {
entry: {
home: './src/Home/index.js',
article: './src/Article/index.js',
profile: './src/Profile/index.js'
}
};
Dla każdego pliku wpisu pakiet internetowy utworzy osobne drzewo zależności i wygeneruje pakiet zawierający tylko moduły, które są używane w danym wpisie:
$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
Asset Size Chunks Chunk Names
./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]
./home.91b9ed27366fe7e33d6a.js 18 kB 1 [emitted] home
./article.87a128755b16ac3294fd.js 32 kB 2 [emitted] article
./profile.de945dc02685f6166781.js 24 kB 3 [emitted] profile
./vendor.4f14b6326a80f4752a98.js 46 kB 4 [emitted] vendor
./runtime.318d7b8490a7382bf23b.js 1.45 kB 5 [emitted] runtime
Jeśli więc tylko strona artykułu używa Lodash, pakietów home
i profile
nie zostaną uwzględnione i użytkownik nie będzie musiał pobierać tej biblioteki,
odwiedzający stronę główną.
Osobne drzewa zależności mają jednak wady. Jeśli dwa punkty wejścia używają
Lodash i nie przeniosłeś(-aś) zależności do pakietu dostawców – oba wpisy
punkty będą zawierały kopię hasła Lodash. Aby rozwiązać ten problem, w pakiecie webpack 4 dodaj parametr
Opcja optimization.splitChunks.chunks: 'all'
do konfiguracji pakietu internetowego:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
Ta opcja umożliwia inteligentne dzielenie kodu. W przypadku tej opcji pakiet internetowy automatycznie wyszukać wspólny kod i wyodrębnić go do osobnych plików.
Możesz też w webpack 3 użyć CommonsChunkPlugin
.
– przeniesie wspólne zależności do nowego określonego pliku:
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2 // 2 is the default value
})
]
};
Możesz wypróbować wartość minChunks
, aby znaleźć najlepszą opcję. Ogólnie rzecz biorąc,
niech będzie mały, ale zwiększ, jeśli ich liczba wzrośnie. Dla:
na przykład dla 3 fragmentów minChunks
może wynosić 2, ale dla 30 części może wynosić 8
bo jeśli w niej będzie 2, zbyt wiele modułów będzie miało dostęp do wspólnego pliku,
nie nadużywając go.
Więcej informacji
- Dokumentacja pakietu internetowego na temat koncepcji wprowadzania pkt
- Dokumentacja Webpack dotycząca CommonsChunkPlugin
- „Aby w pełni korzystać z potencjału CommonsChunkPlugin"
- Jak działają
optimization.splitChunks
ioptimization.runtimeChunk
Zwiększ stabilność identyfikatorów modułów
Podczas tworzenia kodu pakiet internetowy przypisuje każdemu modułowi identyfikator. Później te identyfikatory są
używane w elementach require()
w pakiecie. Identyfikatory zwykle znajdują się w danych wyjściowych kompilacji
tuż przed ścieżkami modułów:
$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
Asset Size Chunks Chunk Names
./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]
./main.4e50a16675574df6a9e9.js 60 kB 1 [emitted] main
./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
↓ Tutaj
[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Domyślnie identyfikatory są obliczane za pomocą licznika (tzn. pierwszy moduł ma identyfikator 0, drugi ma identyfikator 1 itd.). Problem polega na tym, że podczas dodawania nowego modułu, może on pojawić się na środku listy modułów i zmieniać wszystkie Identyfikatory:
$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
Asset Size Chunks Chunk Names
./0.5c82c0f337fcb22672b5.js 22 kB 0 [emitted]
./main.0c8b617dfc40c2827ae3.js 82 kB 1 [emitted] main
./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
↓ Dodaliśmy nowe moduł...
[4] ./webPlayer.js 24 kB {1} [built]
↓ I spójrz, co udało Ci się osiągnąć! comments.js
ma teraz identyfikator 5 zamiast 4
[5] ./comments.js 58 kB {0} [built]
↓ ads.js
ma teraz identyfikator 6 zamiast 5
[6] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Spowoduje to unieważnienie wszystkich fragmentów, które zawierają moduły ze zmienionymi identyfikatorami lub są od nich zależne:
nawet jeśli ich faktyczny kod się nie zmienił. W naszym przypadku fragment 0
(fragment)
(comments.js
), a fragment main
(fragment z drugim kodem aplikacji) otrzymuje
unieważnione – podczas gdy powinno było być tylko main
.
Aby rozwiązać ten problem, zmień sposób obliczania identyfikatorów modułów za pomocą metody
HashedModuleIdsPlugin
Zastępuje identyfikatory oparte na licznikach haszami ścieżek modułów:
$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
Asset Size Chunks Chunk Names
./0.6168aaac8461862eab7a.js 22.5 kB 0 [emitted]
./main.a2e49a279552980e3b91.js 60 kB 1 [emitted] main
./vendor.ff9f7ea865884e6a84c8.js 46 kB 2 [emitted] vendor
./runtime.25f5d0204e4f77fa57a1.js 1.45 kB 3 [emitted] runtime
↓ Tutaj
[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Przy tej metodzie identyfikator modułu zmienia się tylko wtedy, gdy zmienisz jego nazwę lub przeniesiesz go . Nowe moduły nie będą miały wpływu na inne moduły Identyfikatory.
Aby włączyć wtyczkę, dodaj ją do sekcji plugins
konfiguracji:
// webpack.config.js
module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin()
]
};
Więcej informacji
- Dokumentacja Webpack dotycząca HashedModuleIdsPlugin
Podsumowanie
- Umieść pakiet w pamięci podręcznej i rozróżnij wersje, zmieniając nazwę pakietu
- Podziel pakiet na kod aplikacji, kod dostawcy i środowisko wykonawcze
- Wbudowane środowisko wykonawcze umożliwiające zapisanie żądania HTTP
- Leniwe ładowanie niekrytycznego kodu za pomocą funkcji
import
- Podziel kod według tras lub stron, aby uniknąć wczytywania niepotrzebnych treści