1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-08-11 17:51:01 +00:00

feat(js): load app.js using JavaScript module

- The JS bundle has its own isolated scope
- There is no need to use IIFEs anymore (Immediately Invoked Function Expressions)
- Modules are executed after the HTML document is fully parsed, similar to `defer` attribute
- There is no need to use `DOMContentLoaded` anymore
- Module scripts inherently run in strict mode (no need to define `use strict` anymore)
This commit is contained in:
Frédéric Guillot 2025-08-02 10:50:00 -07:00
parent 50197c2be3
commit bfbc1c88c3
4 changed files with 172 additions and 187 deletions

View file

@ -48,7 +48,7 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data:; media-src *; frame-src *; require-trusted-types-for 'script'; trusted-types ttpolicy;"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data:; media-src *; frame-src *; require-trusted-types-for 'script'; trusted-types ttpolicy;">
{{ end }} {{ end }}
<script src="{{ route "javascript" "name" "app" "checksum" .app_js_checksum }}" defer></script> <script src="{{ route "javascript" "name" "app" "checksum" .app_js_checksum }}" type="module"></script>
<script src="{{ route "javascript" "name" "service-worker" "checksum" .sw_js_checksum }}" defer id="service-worker-script"></script> <script src="{{ route "javascript" "name" "service-worker" "checksum" .sw_js_checksum }}" defer id="service-worker-script"></script>
</head> </head>
<body <body

View file

@ -489,6 +489,9 @@ function appendIconLabel(element, labelTextContent) {
function toggleEntryStatus(element, toasting) { function toggleEntryStatus(element, toasting) {
const entryID = parseInt(element.dataset.id, 10); const entryID = parseInt(element.dataset.id, 10);
const link = element.querySelector(":is(a, button)[data-toggle-status]"); const link = element.querySelector(":is(a, button)[data-toggle-status]");
if (!link) {
return;
}
const currentStatus = link.dataset.value; const currentStatus = link.dataset.value;
const newStatus = currentStatus === "read" ? "unread" : "read"; const newStatus = currentStatus === "read" ? "unread" : "read";

View file

@ -1,7 +1,6 @@
document.addEventListener("DOMContentLoaded", () => { disableSubmitButtonsOnFormSubmit();
disableSubmitButtonsOnFormSubmit();
if (!document.querySelector("body[data-disable-keyboard-shortcuts=true]")) { if (!document.querySelector("body[data-disable-keyboard-shortcuts=true]")) {
const keyboardHandler = new KeyboardHandler(); const keyboardHandler = new KeyboardHandler();
keyboardHandler.on("g u", () => goToPage("unread")); keyboardHandler.on("g u", () => goToPage("unread"));
keyboardHandler.on("g b", () => goToPage("starred")); keyboardHandler.on("g b", () => goToPage("starred"));
@ -46,12 +45,12 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
keyboardHandler.on("Escape", () => ModalHandler.close()); keyboardHandler.on("Escape", () => ModalHandler.close());
keyboardHandler.listen(); keyboardHandler.listen();
} }
const touchHandler = new TouchHandler(); const touchHandler = new TouchHandler();
touchHandler.listen(); touchHandler.listen();
if (WebAuthnHandler.isWebAuthnSupported()) { if (WebAuthnHandler.isWebAuthnSupported()) {
const webauthnHandler = new WebAuthnHandler(); const webauthnHandler = new WebAuthnHandler();
onClick("#webauthn-delete", () => { webauthnHandler.removeAllCredentials(); }); onClick("#webauthn-delete", () => { webauthnHandler.removeAllCredentials(); });
@ -80,15 +79,15 @@ document.addEventListener("DOMContentLoaded", () => {
webauthnHandler.conditionalLogin(abortController).catch(err => WebAuthnHandler.showErrorMessage(err)); webauthnHandler.conditionalLogin(abortController).catch(err => WebAuthnHandler.showErrorMessage(err));
} }
} }
onClick(":is(a, button)[data-save-entry]", (event) => handleSaveEntry(event.target)); onClick(":is(a, button)[data-save-entry]", (event) => handleSaveEntry(event.target));
onClick(":is(a, button)[data-toggle-bookmark]", (event) => handleBookmark(event.target)); onClick(":is(a, button)[data-toggle-bookmark]", (event) => handleBookmark(event.target));
onClick(":is(a, button)[data-fetch-content-entry]", handleFetchOriginalContent); onClick(":is(a, button)[data-fetch-content-entry]", handleFetchOriginalContent);
onClick(":is(a, button)[data-share-status]", handleShare); onClick(":is(a, button)[data-share-status]", handleShare);
onClick(":is(a, button)[data-action=markPageAsRead]", (event) => handleConfirmationMessage(event.target, markPageAsRead)); onClick(":is(a, button)[data-action=markPageAsRead]", (event) => handleConfirmationMessage(event.target, markPageAsRead));
onClick(":is(a, button)[data-toggle-status]", (event) => handleEntryStatus("next", event.target)); onClick(":is(a, button)[data-toggle-status]", (event) => handleEntryStatus("next", event.target));
onClick(":is(a, button)[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => { onClick(":is(a, button)[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => {
const request = new RequestBuilder(url); const request = new RequestBuilder(url);
request.withCallback((response) => { request.withCallback((response) => {
@ -102,38 +101,38 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
request.execute(); request.execute();
})); }));
onClick("a[data-original-link='true']", (event) => { onClick("a[data-original-link='true']", (event) => {
handleEntryStatus("next", event.target, true); handleEntryStatus("next", event.target, true);
}, true); }, true);
onAuxClick("a[data-original-link='true']", (event) => { onAuxClick("a[data-original-link='true']", (event) => {
if (event.button === 1) { if (event.button === 1) {
handleEntryStatus("next", event.target, true); handleEntryStatus("next", event.target, true);
} }
}, true); }, true);
checkMenuToggleModeByLayout(); checkMenuToggleModeByLayout();
window.addEventListener("resize", checkMenuToggleModeByLayout, { passive: true }); window.addEventListener("resize", checkMenuToggleModeByLayout, { passive: true });
fixVoiceOverDetailsSummaryBug(); fixVoiceOverDetailsSummaryBug();
const logoElement = document.querySelector(".logo"); const logoElement = document.querySelector(".logo");
if (logoElement) { if (logoElement) {
logoElement.addEventListener("click", toggleMainMenu); logoElement.addEventListener("click", toggleMainMenu);
logoElement.addEventListener("keydown", toggleMainMenu); logoElement.addEventListener("keydown", toggleMainMenu);
} }
onClick(".header nav li", (event) => onClickMainMenuListItem(event)); onClick(".header nav li", (event) => onClickMainMenuListItem(event));
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
const scriptElement = document.getElementById("service-worker-script"); const scriptElement = document.getElementById("service-worker-script");
if (scriptElement) { if (scriptElement) {
navigator.serviceWorker.register(ttpolicy.createScriptURL(scriptElement.src)); navigator.serviceWorker.register(ttpolicy.createScriptURL(scriptElement.src));
} }
} }
window.addEventListener('beforeinstallprompt', (e) => { window.addEventListener('beforeinstallprompt', (e) => {
let deferredPrompt = e; let deferredPrompt = e;
const promptHomeScreen = document.getElementById('prompt-home-screen'); const promptHomeScreen = document.getElementById('prompt-home-screen');
if (promptHomeScreen) { if (promptHomeScreen) {
@ -151,20 +150,20 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
} }
} }
}); });
// Save and resume media position // Save and resume media position
const lastPositionElements = document.querySelectorAll("audio[data-last-position],video[data-last-position]"); const lastPositionElements = document.querySelectorAll("audio[data-last-position],video[data-last-position]");
lastPositionElements.forEach((element) => { lastPositionElements.forEach((element) => {
if (element.dataset.lastPosition) { if (element.dataset.lastPosition) {
element.currentTime = element.dataset.lastPosition; element.currentTime = element.dataset.lastPosition;
} }
element.ontimeupdate = () => handlePlayerProgressionSaveAndMarkAsReadOnCompletion(element); element.ontimeupdate = () => handlePlayerProgressionSaveAndMarkAsReadOnCompletion(element);
}); });
// Set media playback rate // Set media playback rate
const playbackRateElements = document.querySelectorAll("audio[data-playback-rate],video[data-playback-rate]"); const playbackRateElements = document.querySelectorAll("audio[data-playback-rate],video[data-playback-rate]");
playbackRateElements.forEach((element) => { playbackRateElements.forEach((element) => {
if (element.dataset.playbackRate) { if (element.dataset.playbackRate) {
element.playbackRate = element.dataset.playbackRate; element.playbackRate = element.dataset.playbackRate;
if (element.dataset.enclosureId){ if (element.dataset.enclosureId){
@ -176,11 +175,10 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
} }
} }
}); });
// Set enclosure media controls handlers // Set enclosure media controls handlers
const mediaControlsElements = document.querySelectorAll("button[data-enclosure-action]"); const mediaControlsElements = document.querySelectorAll("button[data-enclosure-action]");
mediaControlsElements.forEach((element) => { mediaControlsElements.forEach((element) => {
element.addEventListener("click", () => handleMediaControl(element)); element.addEventListener("click", () => handleMediaControl(element));
});
}); });

View file

@ -127,14 +127,6 @@ func GenerateJavascriptBundles() error {
}, },
} }
var prefixes = map[string]string{
"app": "(function(){'use strict';",
}
var suffixes = map[string]string{
"app": "})();",
}
JavascriptBundles = make(map[string][]byte) JavascriptBundles = make(map[string][]byte)
JavascriptBundleChecksums = make(map[string]string) JavascriptBundleChecksums = make(map[string]string)
@ -146,10 +138,6 @@ func GenerateJavascriptBundles() error {
for bundle, srcFiles := range bundles { for bundle, srcFiles := range bundles {
var buffer bytes.Buffer var buffer bytes.Buffer
if prefix, found := prefixes[bundle]; found {
buffer.WriteString(prefix)
}
for _, srcFile := range srcFiles { for _, srcFile := range srcFiles {
fileData, err := javascriptFiles.ReadFile(srcFile) fileData, err := javascriptFiles.ReadFile(srcFile)
if err != nil { if err != nil {
@ -159,10 +147,6 @@ func GenerateJavascriptBundles() error {
buffer.Write(fileData) buffer.Write(fileData)
} }
if suffix, found := suffixes[bundle]; found {
buffer.WriteString(suffix)
}
minifiedData, err := minifier.Bytes("text/javascript", buffer.Bytes()) minifiedData, err := minifier.Bytes("text/javascript", buffer.Bytes())
if err != nil { if err != nil {
return err return err