feat: reduce firebase blocking time

- use esm format
- defer loading with type="module"
- upgrade from v8.10.0 to v9.23.0
- allow homepage/taxonomy/term log
This commit is contained in:
ZhenShuo Leo
2026-02-06 00:46:47 +08:00
parent 0e9a151ce5
commit 99d329c7b0
4 changed files with 170 additions and 246 deletions
-4
View File
@@ -1,4 +0,0 @@
document.getElementById("button_likes") &&
document.getElementById("button_likes").addEventListener("click", () => {
process_article();
});
+148 -202
View File
@@ -1,208 +1,154 @@
const pageScriptElement = document.currentScript; import { initializeApp } from "https://www.gstatic.com/firebasejs/9.23.0/firebase-app.js";
const configEl = document.getElementById("firebase-config"); import {
getFirestore,
doc,
getDoc,
setDoc,
updateDoc,
increment,
onSnapshot,
} from "https://www.gstatic.com/firebasejs/9.23.0/firebase-firestore.js";
import { getAuth, signInAnonymously } from "https://www.gstatic.com/firebasejs/9.23.0/firebase-auth.js";
let app, db, auth, oids;
try {
const configEl = document.getElementById("firebase-config");
if (!configEl?.textContent) {
throw new Error("Firebase config element not found");
}
if (!configEl?.textContent) {
console.error("Firebase config element not found");
document
.querySelectorAll(
'[id^="views_"], [id^="likes_"], #button_likes_heart, #button_likes_emtpty_heart, #button_likes_text',
)
.forEach((el) => el.remove());
} else {
const data = JSON.parse(configEl.textContent); const data = JSON.parse(configEl.textContent);
const oid = data.oids?.views; app = initializeApp(data.config);
const oid_likes = data.oids?.likes;
let liked_page = false; oids = {
const id = oid ? oid.replaceAll("/", "-") : oid; views: configEl.getAttribute("data-views"),
const id_likes = oid_likes ? oid_likes.replaceAll("/", "-") : oid_likes; likes: configEl.getAttribute("data-likes"),
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function toggleLoaders(node) {
var classesString = node.className;
if (classesString == "") return;
var classes = classesString.split(" ");
for (var i in classes) {
node.classList.toggle(classes[i]);
}
}
var update_views = function (node, id) {
db.collection("views")
.doc(id)
.onSnapshot((doc) => {
var data = doc.data();
if (data) {
node.innerText = numberWithCommas(data.views);
} else {
node.innerText = 0;
}
toggleLoaders(node);
});
}; };
var update_likes = function (node, id) { db = getFirestore(app);
db.collection("likes") auth = getAuth(app);
.doc(id) } catch (e) {
.onSnapshot((doc) => { console.error("Firebase initialization failed:", e.message);
var data = doc.data(); throw e;
if (data) {
node.innerText = numberWithCommas(data.likes);
} else {
node.innerText = 0;
}
toggleLoaders(node);
});
};
if (typeof auth !== "undefined") {
const viewed = localStorage.getItem(id);
if (!viewed && id) {
auth
.signInAnonymously()
.then(() => {
const docRef = db.collection("views").doc(id);
localStorage.setItem(id, true);
docRef
.get()
.then((doc) => {
if (doc.exists) {
db.collection("views")
.doc(id)
.update({
views: firebase.firestore.FieldValue.increment(1),
});
} else {
db.collection("views").doc(id).set({ views: 1 });
}
})
.catch((error) => {
console.log("Error getting document:", error);
});
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.error(errorCode, errorMessage);
});
}
const liked = localStorage.getItem(id_likes);
if (liked) {
liked_page = true;
document.querySelectorAll("span[id='button_likes_heart']")[0].style.display = "";
document.querySelectorAll("span[id='button_likes_emtpty_heart']")[0].style.display = "none";
document.querySelectorAll("span[id='button_likes_text']")[0].innerText = "";
}
auth
.signInAnonymously()
.then(() => {
var views_nodes = document.querySelectorAll("span[id^='views_']");
for (var i in views_nodes) {
var node = views_nodes[i];
var id = node.id ? node.id.replaceAll("/", "-") : node.id;
if (id) {
update_views(node, id);
}
}
var likes_nodes = document.querySelectorAll("span[id^='likes_']");
for (var i in likes_nodes) {
var node = likes_nodes[i];
var id = node.id ? node.id.replaceAll("/", "-") : node.id;
if (id) {
update_likes(node, id);
}
}
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
console.error(errorCode, errorMessage);
});
}
function like_article(id_likes) {
auth
.signInAnonymously()
.then(() => {
const docRef = db.collection("likes").doc(id_likes);
docRef
.get()
.then((doc) => {
liked_page = true;
localStorage.setItem(id_likes, true);
document.querySelectorAll("span[id='button_likes_heart']")[0].style.display = "";
document.querySelectorAll("span[id='button_likes_emtpty_heart']")[0].style.display = "none";
document.querySelectorAll("span[id='button_likes_text']")[0].innerText = "";
if (doc.exists) {
db.collection("likes")
.doc(id_likes)
.update({
likes: firebase.firestore.FieldValue.increment(1),
});
} else {
db.collection("likes").doc(id_likes).set({ likes: 1 });
}
})
.catch((error) => {
console.log("Error getting document:", error);
});
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.error(errorCode, errorMessage);
});
}
function remove_like_article(id_likes) {
auth
.signInAnonymously()
.then(() => {
const docRef = db.collection("likes").doc(id_likes);
docRef
.get()
.then((doc) => {
liked_page = false;
localStorage.removeItem(id_likes);
document.querySelectorAll("span[id='button_likes_heart']")[0].style.display = "none";
document.querySelectorAll("span[id='button_likes_emtpty_heart']")[0].style.display = "";
document.querySelectorAll("span[id='button_likes_text']")[0].innerText = "\xa0Like";
if (doc.exists) {
db.collection("likes")
.doc(id_likes)
.update({
likes: firebase.firestore.FieldValue.increment(-1),
});
} else {
db.collection("likes").doc(id_likes).set({ likes: 0 });
}
})
.catch((error) => {
console.log("Error getting document:", error);
});
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.error(errorCode, errorMessage);
});
}
window.process_article = function () {
if (!liked_page) {
like_article(id_likes);
} else {
remove_like_article(id_likes);
}
};
} }
const id = oids?.views?.replaceAll("/", "-");
const id_likes = oids?.likes?.replaceAll("/", "-");
let liked = false;
let authReady = false;
function formatNumber(n) {
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function toggleLoaders(node) {
var classesString = node.className;
if (classesString == "") return;
var classes = classesString.split(" ");
for (var i in classes) {
node.classList.toggle(classes[i]);
}
}
function updateDisplay(collection, nodeId) {
const node = document.getElementById(nodeId);
if (!node) return;
const docId = nodeId.replaceAll("/", "-");
onSnapshot(
doc(db, collection, docId),
(snapshot) => {
node.innerText = snapshot.exists() ? formatNumber(snapshot.data()[collection]) : 0;
toggleLoaders(node);
},
(error) => {
console.error("Firebase snapshot update failed:", error);
},
);
}
async function recordView(id) {
if (!id || localStorage.getItem(id)) return;
try {
const ref = doc(db, "views", id);
const snap = await getDoc(ref);
snap.exists() ? await updateDoc(ref, { views: increment(1) }) : await setDoc(ref, { views: 1 });
localStorage.setItem(id, true);
} catch (e) {
console.error("Record view operation failed:", e.message);
}
}
function updateButton(isLiked) {
const hearts = document.querySelectorAll("span[id='button_likes_heart']");
const empties = document.querySelectorAll("span[id='button_likes_emtpty_heart']");
const texts = document.querySelectorAll("span[id='button_likes_text']");
hearts.forEach((el) => {
el.style.display = isLiked ? "" : "none";
});
empties.forEach((el) => {
el.style.display = isLiked ? "none" : "";
});
texts.forEach((el) => {
el.innerText = isLiked ? "" : "\xa0Like";
});
}
async function toggleLike(add) {
if (!id_likes || !authReady) return;
try {
const ref = doc(db, "likes", id_likes);
const snap = await getDoc(ref);
liked = add;
add ? localStorage.setItem(id_likes, true) : localStorage.removeItem(id_likes);
updateButton(add);
snap.exists()
? await updateDoc(ref, { likes: increment(add ? 1 : -1) })
: await setDoc(ref, { likes: add ? 1 : 0 });
} catch (e) {
console.error("Like operation failed:", e.message);
liked = !add;
add ? localStorage.removeItem(id_likes) : localStorage.setItem(id_likes, true);
updateButton(!add);
}
}
signInAnonymously(auth)
.then(() => {
authReady = true;
document.querySelectorAll("span[id^='views_']").forEach((node) => {
if (node.id) updateDisplay("views", node.id);
});
document.querySelectorAll("span[id^='likes_']").forEach((node) => {
if (node.id) updateDisplay("likes", node.id);
});
recordView(id);
if (id_likes && localStorage.getItem(id_likes)) {
liked = true;
updateButton(true);
}
const likeButton = document.getElementById("button_likes");
if (likeButton) {
likeButton.addEventListener("click", () => {
toggleLike(!liked);
});
}
})
.catch((error) => {
console.error("Firebase anonymous sign-in failed:", error.message);
authReady = false;
});
window.process_article = () => toggleLike(!liked);
-1
View File
@@ -154,7 +154,6 @@
{{ if .Site.Params.rtl | default false }} {{ if .Site.Params.rtl | default false }}
{{ $jsResources = $jsResources | append (resources.Get "js/rtl.js") }} {{ $jsResources = $jsResources | append (resources.Get "js/rtl.js") }}
{{ end }} {{ end }}
{{ $jsResources = $jsResources | append (resources.Get "js/button-likes.js") }}
{{ $jsResources = $jsResources | append (resources.Get "js/katex-render.js") }} {{ $jsResources = $jsResources | append (resources.Get "js/katex-render.js") }}
{{ $jsResources = $jsResources | append (resources.Get "js/print-support.js") }} {{ $jsResources = $jsResources | append (resources.Get "js/print-support.js") }}
{{ if $jsResources }} {{ if $jsResources }}
+22 -39
View File
@@ -154,7 +154,12 @@
{{ end }} {{ end }}
{{ end }} {{ end }}
{{/* Firebase */}}
{{ if site.Params.firebase.apiKey }} {{ if site.Params.firebase.apiKey }}
{{ $firebase := resources.Get "js/firebase.js" }}
{{ $firebase = $firebase | resources.Minify | resources.Fingerprint (site.Params.fingerprintAlgorithm | default "sha512") }}
<script type="module" src="{{ $firebase.RelPermalink }}" integrity="{{ $firebase.Data.Integrity }}"></script>
{{ if in (slice "page" "section") .Kind }} {{ if in (slice "page" "section") .Kind }}
{{ $translations := .AllTranslations }} {{ $translations := .AllTranslations }}
{{ with .File }} {{ with .File }}
@@ -169,48 +174,26 @@
{{ else if eq .Kind "taxonomy" }} {{ else if eq .Kind "taxonomy" }}
{{ partial "inline/firebase-config.html" (dict "views" (printf "views_taxonomy_%s" .Data.Plural) "likes" (printf "likes_taxonomy_%s" .Data.Plural)) }} {{ partial "inline/firebase-config.html" (dict "views" (printf "views_taxonomy_%s" .Data.Plural) "likes" (printf "likes_taxonomy_%s" .Data.Plural)) }}
{{ else if eq .Kind "home" }} {{ else if eq .Kind "home" }}
{{ partial "inline/firebase-config.html" (dict "views" (printf "views_home") "likes" (printf "likes_home")) }} {{ partial "inline/firebase-config.html" (dict "views" "views_home" "likes" "likes_home") }}
{{ end }} {{ end }}
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-firestore.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js"></script>
<script>
const firebaseConfig = {
apiKey: {{ site.Params.firebase.apiKey }},
authDomain: {{ site.Params.firebase.authDomain }},
projectId: {{ site.Params.firebase.projectId }},
storageBucket: {{ site.Params.firebase.storageBucket }},
messagingSenderId: {{ site.Params.firebase.messagingSenderId }},
appId: {{ site.Params.firebase.appId }},
measurementId: {{ site.Params.firebase.measurementId }}
};
var app = firebase.initializeApp(firebaseConfig);
var db = firebase.firestore();
var auth = firebase.auth();
</script> {{/* */}}
{{ $firebase := resources.Get "js/firebase.js" }}
{{ $firebase = $firebase | resources.Minify | resources.Fingerprint (site.Params.fingerprintAlgorithm | default "sha512") }}
<script
type="text/javascript"
src="{{ $firebase.RelPermalink }}"
integrity="{{ $firebase.Data.Integrity }}"></script>
{{ end }} {{ end }}
{{ define "partials/inline/firebase-config.html" }} {{ define "_partials/inline/firebase-config.html" }}
<script id="firebase-config" type="application/json"> <script id="firebase-config"
{ type="application/json"
"config": { data-views="{{ .views }}"
"apiKey": "{{ site.Params.firebase.apiKey }}", data-likes="{{ .likes }}">
"authDomain": "{{ site.Params.firebase.authDomain }}", {
"projectId": "{{ site.Params.firebase.projectId }}", "config": {
"storageBucket": "{{ site.Params.firebase.storageBucket }}", "apiKey": "{{ site.Params.firebase.apiKey }}",
"messagingSenderId": "{{ site.Params.firebase.messagingSenderId }}", "authDomain": "{{ site.Params.firebase.authDomain }}",
"appId": "{{ site.Params.firebase.appId }}", "projectId": "{{ site.Params.firebase.projectId }}",
"measurementId": "{{ site.Params.firebase.measurementId }}" "storageBucket": "{{ site.Params.firebase.storageBucket }}",
}, "messagingSenderId": "{{ site.Params.firebase.messagingSenderId }}",
"oids": {{ . | jsonify | safeJS }} "appId": "{{ site.Params.firebase.appId }}",
"measurementId": "{{ site.Params.firebase.measurementId }}"
} }
</script> }
</script>
{{ end }} {{ end }}