Quick Start Labels
https://vanillawebdev.blogspot.com/search/label/coding-tips
Coding Tips
3
https://vanillawebdev.blogspot.com/search/label/project-setup
Project Setup
6
https://vanillawebdev.blogspot.com/search/label/starter-code
Starter Code
5
Reading List
https://jackwhiting.co.uk/posts/lazy-loading-vanilla-js-with-webpack-laravel-mix
https://medium.com/free-code-camp/reducing-css-bundle-size-70-by-cutting-the-class-names-and-using-scope-isolation-625440de600b
Libraries
https://github.com/verlok/vanilla-lazyload
Developer Resources
https://webpack.js.org/configuration/output/
https://web.dev/articles/preload-critical-assets
Reading List
https://www.codemzy.com/blog/how-to-name-webpack-chunk
How to name a webpack chunk (including split and common chunks)
Reading List
Featured Post
https://vanillawebdev.blogspot.com/2024/02/installable-web-app.html
Make Your Web App Installable
February 16, 2025How to make your web app installable.
true
https://vanillawebdev.blogspot.com/search/label/homepage
homepage
https://vanillawebdev.blogspot.com/search/label/project-setup
project-setup
Featured Post
Sidebar Labels
https://vanillawebdev.blogspot.com/search/label/app-component
App Component
1
https://vanillawebdev.blogspot.com/search/label/coding-tips
Coding Tips
3
https://vanillawebdev.blogspot.com/search/label/css-tips
CSS Tips
3
https://vanillawebdev.blogspot.com/search/label/data-server
Data Server
2
https://vanillawebdev.blogspot.com/search/label/layout
Layout
1
https://vanillawebdev.blogspot.com/search/label/project-setup
Project Setup
6
https://vanillawebdev.blogspot.com/2025/02/prettier-formatting.html
https://vanillawebdev.blogspot.com/2025/01/vanilla-javascript-project-structure.html
https://vanillawebdev.blogspot.com/
https://vanillawebdev.blogspot.com/2025/02/file-caching.html
Files Caching Component
February 02, 2025
2025
February
03
07:01 AM
File caching component for vanilla JS web app. Mainly used for building PWAs.
HTML
Setup the cache control buttons.
index.html
<section class="_pwaCacheManagerContainer">
[h2 'Offline Access']
<!-- PWA cache manager -->
[ .flex .gap-50
<button data-slot="cacheBtn" onclick="compoCache.Update()">Enable</button>
<button data-slot="clearBtn" onclick="compoCache.Clear()">Disable</button>
]
[
<button data-slot="updateBtn" onclick="compoCache.Update()">Cache latest version</button>
]
[
<label>
<input name="replaceWidgetSettings" type="checkbox" oninput="compoCache.SetAutoCache(this.checked)" data-slot="cacheOpt"/>
Automatically cache latest version.
</label>
]
[ data-slot="txtInfo" .hide-empty]
[pre data-slot="urls" .hide-empty]
</section>
<script src="js/application/components/cache-component.js"></script>
<script>
compoCache.Init();
</script>JSON
List all URLs that you want to cache here. version is a reserved key to trigger auto re-cache version upgrade.
json/manifest-cache.json
{
"version": "0.0.1",
"application": [
"js/application/components/cache-component.js"
],
"root": [
"css/style.css",
"./",
"index.js",
"index.html",
"manifest.json"
]
}JS
js/application/components/cache-component.js
// @ts-check
/* v2.1 */
let compoCache = (() => {
// # vars
let $ = document.querySelector.bind(document);
let buildVersion = '1';
let cacheName = `appcache-example-NjQ4MDQ0MjI-build${buildVersion}`;
let manifestCachePath = './json/manifest-cache.json';
let data = {
isAutoCache: true,
};
let local = {
domslots: {},
componentStorageKey: `${cacheName}-settings`,
defaultDataJSON: JSON.stringify(data),
};
// # function
// #region function
function save() {
localStorage.setItem(local.componentStorageKey, JSON.stringify(data));
}
function extractUrlsFromJson(json) {
const urls = [];
(function recurse(obj) {
for (const key in obj) {
const value = obj[key];
if (Array.isArray(value)) {
urls.push(...value);
} else if (value && typeof value === 'object') {
recurse(value); // Recurse into nested objects
}
}
})(json);
return urls;
}
async function removeCache() {
let cacheNames = await caches.keys();
for (let cname of cacheNames) {
if (cname.includes(cacheName)) {
await caches.delete(cname);
}
}
}
function cacheAssets(opt) {
if (!navigator.onLine) {
console.warn('You are offline.');
return;
}
fetch(manifestCachePath)
.then((res) => res.json())
.then(async (json) => {
let cacheURLs = extractUrlsFromJson(json);
let version = json.version;
let newCacheVersionKey = `${cacheName}-v${version}`;
let cacheNames = await caches.keys();
let hasLatestCache = cacheNames.find((cname) => cname == newCacheVersionKey);
for (let cname of cacheNames) {
if (cname == newCacheVersionKey && opt?.isCheckBySystem) continue;
if (cname.includes(cacheName)) {
await caches.delete(cname);
}
}
if (hasLatestCache && opt?.isCheckBySystem) return;
let errorURLs = [];
caches
.open(newCacheVersionKey)
.then(function (cache) {
return Promise.all(
cacheURLs.map(function (url) {
return cache.add(url).catch(function (error) {
console.error('Failed to cache URL:', url, error);
errorURLs.push(url);
});
})
);
})
.then(function () {
local.domslots.urls.replaceChildren();
if (errorURLs.length > 0) {
setInfo('Failed to cache the following URLs. Offline access may work partially.');
local.domslots.urls.textContent = errorURLs.join('\n');
} else {
setInfo('Done! Reload to take effect.');
}
refreshSettingsState_();
})
.catch(function (error) {
console.log(error);
setInfo('Failed. Check console.');
});
});
}
function loadData() {
data = JSON.parse(localStorage.getItem(local.componentStorageKey) || local.defaultDataJSON);
}
async function hasCache_() {
let cacheNames = await caches.keys();
let hasCache = cacheNames.find((cname) => cname.includes(cacheName));
return hasCache;
}
async function triggerAutoUpdate_() {
let isCached = await hasCache_();
if (!isCached) return;
if (data.isAutoCache) {
cacheAssets({
isCheckBySystem: true,
});
}
}
function GetSlotEl(itemEl) {
let slotData = Array.from(itemEl.querySelectorAll('[data-slot]')).map((x) => {
return {
key: x.dataset.slot,
el: x,
};
});
let slotObj = slotData.reduce((obj, item) => {
obj[item.key] = item.el;
return obj;
}, {});
return slotObj;
}
function setInfo(txt) {
let { txtInfo } = local.domslots;
txtInfo.textContent = txt;
}
async function refreshSettingsState_() {
let { cacheBtn, clearBtn, updateBtn, cacheOpt } = local.domslots;
cacheOpt.checked = data.isAutoCache;
let isCached = await hasCache_();
cacheBtn.disabled = isCached;
clearBtn.disabled = !isCached;
updateBtn.disabled = !isCached;
cacheOpt.disabled = !isCached;
}
// #endregion
// # public
return {
Update(opt) {
cacheAssets(opt);
},
SetAutoCache(isChecked) {
data.isAutoCache = isChecked;
refreshSettingsState_();
save();
},
async Clear() {
let isConfirm = window.confirm('This will disable offline access. Continue?');
if (!isConfirm) return;
localStorage.removeItem(local.componentStorageKey);
data = JSON.parse(local.defaultDataJSON);
await removeCache();
refreshSettingsState_();
},
Init() {
local.domslots = GetSlotEl($('._pwaCacheManagerContainer'));
loadData();
refreshSettingsState_();
triggerAutoUpdate_();
},
};
})();Configure the following settings:
let buildVersion = '1';
let cacheName = `example-NjM2MDQxNTg-build${buildVersion}`;
let manifestCachePath = 'manifest-cache.json';
https://www.blogger.com/comment/fullpage/post/8166404610182826392/1453941876232939061
true
https://vanillawebdev.blogspot.com/search/label/%40lvc
@lvc
https://vanillawebdev.blogspot.com/search/label/Application%20Component
Application Component
https://vanillawebdev.blogspot.com/search/label/pending-review
pending-review
https://vanillawebdev.blogspot.com/2025/02/prettier-formatting.html
https://vanillawebdev.blogspot.com/2025/01/vanilla-javascript-project-structure.html
https://vanillawebdev.blogspot.com/