איך webpack עוזר בשמירת נכסים במטמון
השלב הבא (אחרי אופטימיזציה של גודל האפליקציה) שמשפר את זמן הטעינה של האפליקציה הוא שמירת נתונים במטמון. אפשר להשתמש בה כדי לשמור חלקים מהאפליקציה בצד הלקוח, וכך להימנע מהורדה מחדש שלהם בכל פעם.
שימוש בגרסאות של חבילות ובכותרות של מטמון
הגישה הנפוצה לשימוש במטמון היא:
להורות לדפדפן לשמור קובץ במטמון למשך זמן רב מאוד (למשל, שנה):
# Server header Cache-Control: max-age=31536000
אם אתם לא יודעים מה המשמעות של
Cache-Control
, כדאי לעיין במאמר המעולה של Jake Archibald בנושא שיטות מומלצות לשמירת נתונים במטמון.ומשנים את שם הקובץ כשהוא משתנה כדי לאלץ את ההורדה מחדש:
<!-- Before the change --> <script src="./index-v15.js"></script> <!-- After the change --> <script src="./index-v16.js"></script>
הגישה הזו מורה לדפדפן להוריד את קובץ ה-JS, לשמור אותו במטמון ולהשתמש בעותק שנשמר במטמון. הדפדפן יפנה לרשת רק אם שם הקובץ ישתנה (או אם תעבור שנה).
ב-webpack, מבצעים את אותו תהליך, אבל במקום מספר גרסה, מציינים את גיבוב הקובץ. כדי לכלול את ה-hash בשם הקובץ, משתמשים ב-[chunkhash]
:
// webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
}
};
אם אתם צריכים את שם הקובץ כדי לשלוח אותו ללקוח, תוכלו להשתמש ב-HtmlWebpackPlugin
או ב-WebpackManifestPlugin
.
HtmlWebpackPlugin
היא גישה פשוטה, אבל פחות גמישה. במהלך הידור, הפלאגין יוצר קובץ HTML שכולל את כל המשאבים שעבר תהליך הידור. אם הלוגיקה של השרת לא מורכבת, האפשרויות הבאות אמורות להספיק לכם:
<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>
WebpackManifestPlugin
היא גישה גמישה יותר, שמתאימה אם יש לכם חלק מורכב בשרת.
במהלך ה-build, נוצר קובץ JSON עם מיפוי בין שמות קבצים ללא גיבוב לשמות קבצים עם גיבוב. משתמשים ב-JSON הזה בשרת כדי לברר איזה קובץ לעבוד איתו:
// manifest.json
{
"bundle.js": "bundle.8e0d62a03.js"
}
קריאה נוספת
- Jake Archibald על שיטות מומלצות לשמירה במטמון
חילוץ יחסי התלות וזמן הריצה לקובץ נפרד
יחסי תלות
יחסי התלות של האפליקציה נוטים להשתנות בתדירות נמוכה יותר מקוד האפליקציה בפועל. אם מעבירים אותם לקובץ נפרד, הדפדפן יוכל לשמור אותם בנפרד במטמון – ולא יוריד אותם מחדש בכל פעם שקוד האפליקציה ישתנה.
כדי לחלץ יחסי תלות לקטע נפרד, מבצעים שלושה שלבים:
מחליפים את שם קובץ הפלט ב-
[name].[chunkname].js
:// webpack.config.js module.exports = { output: { // Before filename: 'bundle.[chunkhash].js', // After filename: '[name].[chunkhash].js' } };
כש-webpack יוצר את האפליקציה, הוא מחליף את
[name]
בשם של מקטע. אם לא נוסיף את החלק[name]
, נצטרך להבדיל בין קטעים לפי ה-hash שלהם – וזה די קשה.ממירים את השדה
entry
לאובייקט:// webpack.config.js module.exports = { // Before entry: './index.js', // After entry: { main: './index.js' } };
בקטע הקוד הזה, 'main' הוא שם של מקטע. השם הזה יחליף את השם
[name]
משלב 1.עכשיו, אם תיצרו את האפליקציה, החלק הזה יכלול את כל קוד האפליקציה – בדיוק כמו שלא ביצענו את השלבים האלה. אבל זה ישתנה תוך רגע.
ב-webpack 4, מוסיפים את האפשרות
optimization.splitChunks.chunks: 'all'
לתצורת webpack:// webpack.config.js (for webpack 4) module.exports = { optimization: { splitChunks: { chunks: 'all' } } };
האפשרות הזו מאפשרת פיצול חכם של קוד. בעזרת האפשרות הזו, קוד הספק יופק על ידי webpack אם הוא יהיה גדול מ-30KB (לפני דחיסה מינימלית וגיבוב gzip). הוא גם ישלוף את הקוד המשותף – זה שימושי אם ה-build יוצר כמה חבילות (למשל, אם מפצלים את האפליקציה למסלולים).
ב-webpack 3, מוסיפים את
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'), }) ] };
הפלאגין הזה לוקח את כל המודולים שהנתיבים שלהם כוללים את
node_modules
ומעביר אותם לקובץ נפרד שנקראvendor.[chunkhash].js
.
אחרי השינויים האלה, כל build ייצור שני קבצים במקום אחד: main.[chunkhash].js
ו-vendor.[chunkhash].js
(vendors~main.[chunkhash].js
ב-webpack 4). ב-webpack 4, יכול להיות שהחבילה של הספק לא תיווצר אם יחסי התלות קטנים – וזה בסדר:
$ 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
הדפדפן יאחסן את הקבצים האלה בנפרד – ויוריד מחדש רק קוד שמשתנה.
קוד בסביבת זמן ריצה של Webpack
לצערנו, לא מספיק לחלץ רק את קוד הספק. אם תנסו לשנות משהו בקוד של האפליקציה:
// index.js
…
…
// E.g. add this:
console.log('Wat');
תבחינו שה-hash של vendor
משתנה גם הוא:
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
הסיבה לכך היא שבנוסף לקוד של המודולים, לחבילת ה-Webpack יש סביבת זמן ריצה – קטע קוד קטן שמנהל את ביצוע המודול. כשמחלקים את הקוד לכמה קבצים, קטע הקוד הזה מתחיל לכלול מיפוי בין מזהי קטעים לקבצים התואמים:
// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
"0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";
Webpack כולל את סביבת זמן הריצה הזו בחלק האחרון שנוצר, שהוא vendor
במקרה שלנו. וכל פעם שחלק כלשהו משתנה, גם קטע הקוד הזה משתנה, וכתוצאה מכך כל החלק vendor
משתנה.
כדי לפתור את הבעיה, נעבור את סביבת זמן הריצה לקובץ נפרד. ב-webpack 4,עושים זאת על ידי הפעלת האפשרות optimization.runtimeChunk
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
runtimeChunk: true
}
};
ב-webpack 3, עושים זאת על ידי יצירת מקטע ריק נוסף באמצעות 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
})
]
};
אחרי השינויים האלה, כל גרסה תפיק שלושה קבצים:
$ 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
מוסיפים אותם ל-index.html
בסדר הפוך – וזהו:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>
קריאה נוספת
- מדריך Webpack לגבי אחסון במטמון לטווח ארוך
- מסמכי עזר של Webpack בנושא זמן ריצה של webpack ו-manifest
- "איך להפיק את המקסימום מ-CommonsChunkPlugin"
- איך פועלים השדות
optimization.splitChunks
ו-optimization.runtimeChunk
זמן ריצה של webpack בקוד כדי לחסוך בקשת HTTP נוספת
כדי לשפר את המצב עוד יותר, כדאי לנסות להטמיע את סביבת זמן הריצה של webpack בתגובה של ה-HTML. כלומר, במקום:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
מבצעים את הפעולות הבאות:
<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>
זמן הריצה קצר, והטמעה בקוד תעזור לכם לחסוך בקשת HTTP (חשובה למדי ב-HTTP/1, פחות חשובה ב-HTTP/2, אבל עדיין עשויה להשפיע).
כך עושים את זה.
אם יוצרים HTML באמצעות HtmlWebpackPlugin
אם אתם משתמשים ב-HtmlWebpackPlugin כדי ליצור קובץ HTML, ה-InlineSourcePlugin הוא כל מה שאתם צריכים:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
inlineSource: 'runtime~.+\\.js',
}),
new InlineSourcePlugin()
]
};
אם אתם יוצרים HTML באמצעות לוגיקה של שרת בהתאמה אישית
עם webpack 4:
מוסיפים את הערך
WebpackManifestPlugin
כדי לדעת מה השם שנוצר של מקטע זמן הריצה:// webpack.config.js (for webpack 4) const ManifestPlugin = require('webpack-manifest-plugin'); module.exports = { plugins: [ new ManifestPlugin() ] };
גרסה זמינה (build) עם הפלאגין הזה תיצור קובץ שנראה כך:
// manifest.json { "runtime~main.js": "runtime~main.8e0d62a03.js" }
להוסיף את התוכן של מקטע זמן הריצה בצורה נוחה. לדוגמה, עם Node.js ו-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> … `); });
או באמצעות webpack 3:
כדי להפוך את שם סביבת זמן הריצה לסטטי, מציינים את
filename
:module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', minChunks: Infinity, filename: 'runtime.js' }) ] };
להציג את התוכן של
runtime.js
באופן נוח. לדוגמה, עם Node.js ו-Express:// server.js const fs = require('fs'); const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8'); app.get('/', (req, res) => { res.send(` … <script>${runtimeContent}</script> … `); });
טעינה איטית של קוד שאתם לא צריכים כרגע
לפעמים, בדף יש חלקים חשובים יותר וחלקים חשובים פחות:
- כשאתם טוענים דף של סרטון ב-YouTube, אתם מתעניינים יותר בסרטון מאשר בתגובות. במקרה כזה, הסרטון חשוב יותר מהתגובות.
- כשפותחים מאמר באתר חדשות, עיקר העניין הוא בטקסט של המאמר ולא במודעות. במקרה כזה, הטקסט חשוב יותר מהמודעות.
במקרים כאלה, כדי לשפר את ביצועי הטעינה הראשונית, כדאי להוריד קודם את התוכן החשוב ביותר, ולאחר מכן לבצע טעינה איטית של שאר החלקים. כדי לעשות זאת, משתמשים בפונקציה import()
ובפיצול קוד:
// 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()
מציין שרוצים לטעון מודול ספציפי באופן דינמי. כש-webpack מזהה את import('./module.js')
, הוא מעביר את המודול הזה לקטע נפרד:
$ 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
והיא מורידה אותו רק כשהביצוע מגיע לפונקציה import()
.
כך החבילה main
תהיה קטנה יותר, וזמני הטעינה הראשוניים יהיו קצרים יותר.
בנוסף, כך תוכלו לשפר את האחסון במטמון – אם תשנו את הקוד בחלק הראשי, החלק של התגובות לא יושפע.
קריאה נוספת
- מסמכי העזרה של Webpack לפונקציה
import()
- ההצעה ל-JavaScript להטמעת התחביר של
import()
פיצול הקוד למסלולים ולדפים
אם באפליקציה יש כמה מסלולים או דפים, אבל יש רק קובץ JS אחד עם הקוד (מקטע main
אחד), סביר להניח שאתם מציגים עוד בייטים בכל בקשה. לדוגמה, כשמשתמש נכנס לדף הבית של האתר:
הם לא צריכים לטעון את הקוד לעיבוד מאמר שנמצא בדף אחר, אבל הם יטענו אותו. בנוסף, אם המשתמש תמיד נכנס רק לדף הבית, ואתם מבצעים שינוי בקוד של המאמר, webpack יבטל את התוקף של החבילה כולה – והמשתמש יצטרך להוריד מחדש את האפליקציה כולה.
אם נחלק את האפליקציה לדפים (או למסלולים, אם מדובר באפליקציה של דף יחיד), המשתמש יוריד רק את הקוד הרלוונטי. בנוסף, הדפדפן יאחסן טוב יותר את קוד האפליקציה: אם תשנו את הקוד של דף הבית, webpack יבטל רק את החלק התואם.
לאפליקציות בדף יחיד
כדי לפצל אפליקציות של דף יחיד לפי מסלולים, משתמשים ב-import()
(ראו הקטע קוד של טעינת פריטים בזמן אמת שאין צורך בו כרגע). אם אתם משתמשים ב-framework, יכול להיות שיש לו פתרון קיים לבעיה הזו:
- 'פיצול קוד' במסמכי העזרה של
react-router
(ל-React) - 'Lazy Loading Routes' במסמכי העזרה של
vue-router
(ל-Vue.js)
לאפליקציות מסורתיות עם כמה דפים
כדי לפצל אפליקציות רגילות לפי דפים, משתמשים בנקודות הכניסה של webpack. אם באפליקציה יש שלושה סוגים של דפים: דף הבית, דף המאמר ודף חשבון המשתמש, צריכים להיות לה שלושה רשומות:
// webpack.config.js
module.exports = {
entry: {
home: './src/Home/index.js',
article: './src/Article/index.js',
profile: './src/Profile/index.js'
}
};
לכל קובץ נתונים, מערכת webpack תיצור עץ יחסי תלות נפרד ותיצור חבילה שכוללת רק מודולים שבהם נעשה שימוש בקובץ הנתונים הזה:
$ 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
לכן, אם רק בדף המאמר נעשה שימוש ב-Lodash, החבילות home
ו-profile
לא יכללו אותו – והמשתמש לא יצטרך להוריד את הספרייה הזו כשייכנס לדף הבית.
עם זאת, יש חסרונות לעצים נפרדים של יחסי תלות. אם שתי נקודות כניסה משתמשות ב-Lodash ולא העברתם את יחסי התלות לחבילת צד שלישי, שתי נקודות הכניסה יכללו עותק של Lodash. כדי לפתור את הבעיה, ב-webpack 4,מוסיפים את האפשרות optimization.splitChunks.chunks: 'all'
לתצורת webpack:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
האפשרות הזו מאפשרת פיצול חכם של קוד. אם תבחרו באפשרות הזו, webpack יחפש באופן אוטומטי קוד משותף ויחלץ אותו לקובצי js נפרדים.
לחלופין, ב-webpack 3,משתמשים ב-CommonsChunkPlugin
– הוא יעביר יחסי תלות נפוצים לקובץ חדש שצוין:
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2 // 2 is the default value
})
]
};
אפשר לשחק עם הערך של minChunks
כדי למצוא את הערך הטוב ביותר. באופן כללי, מומלץ לשמור על גודל קטן, אבל להגדיל אותו אם מספר הקטעים יגדל. לדוגמה, אם יש 3 קטעים, הערך של minChunks
יכול להיות 2, אבל אם יש 30 קטעים, הערך יכול להיות 8 – כי אם משאירים את הערך ב-2, יותר מדי מודולים ייכנסו לקובץ המשותף ויגדילו אותו יותר מדי.
קריאה נוספת
- מאמר בנושא קונספט נקודות הכניסה במסמכי התיעוד של Webpack
- מסמכי העזרה של Webpack בנושא ה-CommonsChunkPlugin
- "איך להפיק את המקסימום מ-CommonsChunkPlugin"
- איך פועלים השדות
optimization.splitChunks
ו-optimization.runtimeChunk
שיפור היציבות של מזהי המודולים
כשיוצרים את הקוד, לכל מודול מוקצה מזהה על ידי webpack. בהמשך, המזהים האלה ישמשו ב-require()
s בתוך החבילה. בדרך כלל המזהים מופיעים בפלט ה-build, ממש לפני נתיבי המודולים:
$ 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
↓ כאן
[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
כברירת מחדל, המזהים מחושבים באמצעות מונה (כלומר, למערך הראשון מוקצה המזהה 0, למערך השני מוקצה המזהה 1 וכן הלאה). הבעיה בכך היא שכשמוסיפים מודול חדש, הוא עשוי להופיע באמצע רשימת המודולים, וכתוצאה מכך כל המזהים של המודולים הבאים ישתנו:
$ 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]
↓ הוספנו מודול חדש…
[4] ./webPlayer.js 24 kB {1} [built]
↓ ותראו מה קרה! למשתמש comments.js
יש עכשיו מזהה 5 במקום 4
[5] ./comments.js 58 kB {0} [built]
↓ למשתמש ads.js
יש עכשיו מזהה 6 במקום 5
[6] ./ads.js 74 kB {1} [built]
+ 1 hidden module
הפעולה הזו מבטלת את התוקף של כל הקטעים שכוללים מודולים עם מזהים שהשתנו או תלויים בהם – גם אם הקוד בפועל שלהם לא השתנה. במקרה שלנו, מקטע 0
(המקטע עם comments.js
) והמקטע main
(המקטע עם קוד האפליקציה האחר) יתבטלו, בעוד שרק המקטע main
אמור היה להתבטל.
כדי לפתור את הבעיה, צריך לשנות את אופן החישוב של מזהי המודולים באמצעות HashedModuleIdsPlugin
.
הוא מחליף מזהי ספירה ב-hash של נתיבי המודולים:
$ 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
↓ כאן
[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
בגישה הזו, המזהה של המודול משתנה רק אם משנים את השם שלו או מעבירים אותו. מודולים חדשים לא ישפיעו על המזהים של מודולים אחרים.
כדי להפעיל את הפלאגין, מוסיפים אותו לקטע plugins
בתצורה:
// webpack.config.js
module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin()
]
};
קריאה נוספת
- מידע נוסף על ה-HashedModuleIdsPlugin במסמכי העזרה של Webpack
סיכום
- לשמור את החבילה במטמון ולהבדיל בין גרסאות על ידי שינוי שם החבילה
- חלוקת החבילה לקוד האפליקציה, לקוד הספק ולזמן הריצה
- הטמעת זמן הריצה בקוד כדי לשמור בקשת HTTP
- טעינת קוד לא קריטי באיטרציות עם
import
- חלוקת הקוד לפי מסלולים/דפים כדי להימנע מטעינת דברים מיותרים