From 616f5d60731fe1df861c7bc4afb258acd55bba06 Mon Sep 17 00:00:00 2001
From: 0x1eef <0x1eef@protonmail.com>
Date: Sat, 12 Nov 2022 18:55:53 -0300
Subject: [PATCH] add "WebPackage"
The "WebPackage" object allows for scripts, stylesheets, fonts, images
and other webpage assets to be downloaded for the page's primary content
to use afterwards.
---
.eslintrc.js | 5 ++-
Rules | 5 +++
package-lock.json | 13 +++++++
package.json | 1 +
src/html/TheSurahPage.html.erb | 44 ++++++++++++++++++++++--
src/js/hooks/useSurah.ts | 9 +++--
src/js/lib/WebPackage.ts | 49 +++++++++++++++++++++++++++
src/js/lib/WebPackage/CSSLoader.ts | 15 ++++++++
src/js/lib/WebPackage/FontLoader.ts | 12 +++++++
src/js/lib/WebPackage/ImageLoader.ts | 17 ++++++++++
src/js/lib/WebPackage/OtherLoader.ts | 15 ++++++++
src/js/lib/WebPackage/ScriptLoader.ts | 15 ++++++++
src/js/lib/WebPackage/types.ts | 23 +++++++++++++
src/js/pages/TheSurahPage/package.ts | 30 ++++++++++++++++
14 files changed, 244 insertions(+), 9 deletions(-)
create mode 100644 src/js/lib/WebPackage.ts
create mode 100644 src/js/lib/WebPackage/CSSLoader.ts
create mode 100644 src/js/lib/WebPackage/FontLoader.ts
create mode 100644 src/js/lib/WebPackage/ImageLoader.ts
create mode 100644 src/js/lib/WebPackage/OtherLoader.ts
create mode 100644 src/js/lib/WebPackage/ScriptLoader.ts
create mode 100644 src/js/lib/WebPackage/types.ts
create mode 100644 src/js/pages/TheSurahPage/package.ts
diff --git a/.eslintrc.js b/.eslintrc.js
index 48a2963..9fdf58d 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -10,7 +10,10 @@ module.exports = {
"@typescript-eslint/strict-boolean-expressions": 0,
"@typescript-eslint/no-floating-promises": 0,
"@typescript-eslint/prefer-nullish-coalescing": 0,
- "no-useless-return": 0,
"@typescript-eslint/restrict-template-expressions": 0,
+ "@typescript-eslint/promise-function-async": 0,
+ "@typescript-eslint/no-misused-promises": ["error", {"checksConditionals": false}],
+ "no-return-assign": 0,
+ "no-useless-return": 0,
},
};
diff --git a/Rules b/Rules
index eb01f51..71e9e94 100644
--- a/Rules
+++ b/Rules
@@ -59,6 +59,11 @@ compile "/js/pages/TheSurahPage.tsx" do
write "/js/pages/surah.js.gz"
end
+compile "/js/pages/TheSurahPage/package.ts" do
+ filter :webpack, exe: "./node_modules/webpack/bin/webpack.js"
+ write "/js/pages/TheSurahPage/package.js"
+end
+
##
# /js/pages/redirect-to-random-surah.js
compile "/js/pages/redirect-to-random-surah.ts" do
diff --git a/package-lock.json b/package-lock.json
index 625bb39..897dcef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,6 +5,7 @@
"packages": {
"": {
"devDependencies": {
+ "@types/css-font-loading-module": "^0.0.7",
"@types/react": "^18.0.18",
"@types/react-dom": "^18.0.6",
"classnames": "^2.3.2",
@@ -186,6 +187,12 @@
"node": ">= 8"
}
},
+ "node_modules/@types/css-font-loading-module": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz",
+ "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==",
+ "dev": true
+ },
"node_modules/@types/eslint": {
"version": "8.4.6",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz",
@@ -4506,6 +4513,12 @@
"fastq": "^1.6.0"
}
},
+ "@types/css-font-loading-module": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz",
+ "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==",
+ "dev": true
+ },
"@types/eslint": {
"version": "8.4.6",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz",
diff --git a/package.json b/package.json
index 590c75f..0eab348 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"eslint": "node ./node_modules/eslint/bin/eslint.js src/"
},
"devDependencies": {
+ "@types/css-font-loading-module": "^0.0.7",
"@types/react": "^18.0.18",
"@types/react-dom": "^18.0.6",
"classnames": "^2.3.2",
diff --git a/src/html/TheSurahPage.html.erb b/src/html/TheSurahPage.html.erb
index 330a2dc..c38b632 100644
--- a/src/html/TheSurahPage.html.erb
+++ b/src/html/TheSurahPage.html.erb
@@ -2,11 +2,49 @@
Al-Quran: Loading
-
+
-
-
+
+
+
+
diff --git a/src/js/hooks/useSurah.ts b/src/js/hooks/useSurah.ts
index 3dcaa58..36af299 100644
--- a/src/js/hooks/useSurah.ts
+++ b/src/js/hooks/useSurah.ts
@@ -5,11 +5,10 @@ export default function (locale: string, surahId: number) {
const [surah, setSurah] = useState(null);
useEffect(() => {
- (async () => {
- const res = await fetch(`/${locale}/${surahId}/surah.json`);
- const json = await res.json();
- setSurah(Quran.Surah.fromJSON(json.shift(), json));
- })();
+ const path = `/${locale}/${surahId}/surah.json`;
+ const text = document.querySelector(`script[src="${path}"]`).innerText;
+ const json = JSON.parse(text);
+ setSurah(Quran.Surah.fromJSON(json.shift(), json));
}, []);
return {
diff --git a/src/js/lib/WebPackage.ts b/src/js/lib/WebPackage.ts
new file mode 100644
index 0000000..4416250
--- /dev/null
+++ b/src/js/lib/WebPackage.ts
@@ -0,0 +1,49 @@
+import {
+ WebPackage,
+ PackageSpec,
+ Package,
+ ReporterFunction,
+} from "./WebPackage/types";
+import FontLoader from "./WebPackage/FontLoader";
+import ImageLoader from "./WebPackage/ImageLoader";
+import CSSLoader from "./WebPackage/CSSLoader";
+import ScriptLoader from "./WebPackage/ScriptLoader";
+import OtherLoader from "./WebPackage/OtherLoader";
+
+export default function (pkgspec: PackageSpec): WebPackage {
+ const self: WebPackage = Object.create(null);
+ const pkg: Package = {fonts: [], images: [], stylesheets: [], scripts: [], others: []};
+ const { fonts, images, stylesheets, scripts, others, onprogress } = pkgspec;
+ const total = [...fonts, ...images, ...stylesheets, ...scripts].length;
+
+ let index = 0;
+ const reporter: ReporterFunction = (el) => {
+ index++;
+ if (onprogress && index <= total) {
+ onprogress(100 * (index / total));
+ }
+ return el;
+ };
+
+ let fetcher: Promise | null = null;
+ self.fetch = () => {
+ if (fetcher) {
+ return fetcher;
+ } else {
+ fetcher = FontLoader(fonts, reporter)
+ .then((fonts: FontFace[]) => pkg.fonts.push(...fonts))
+ .then(() => ImageLoader(images, reporter))
+ .then((images: HTMLElement[]) => pkg.images.push(...images))
+ .then(() => CSSLoader(stylesheets, reporter))
+ .then((stylesheets: HTMLElement[]) => pkg.stylesheets.push(...stylesheets))
+ .then(() => ScriptLoader(scripts, reporter))
+ .then((scripts: HTMLElement[]) => pkg.scripts.push(...scripts))
+ .then(() => OtherLoader(others, reporter))
+ .then((others: HTMLElement[]) => pkg.others.push(...others))
+ .then(() => pkg);
+ return fetcher;
+ }
+ };
+
+ return self;
+}
diff --git a/src/js/lib/WebPackage/CSSLoader.ts b/src/js/lib/WebPackage/CSSLoader.ts
new file mode 100644
index 0000000..573e52b
--- /dev/null
+++ b/src/js/lib/WebPackage/CSSLoader.ts
@@ -0,0 +1,15 @@
+import { ReporterFunction } from "./types";
+
+export default function(
+ stylesheets: string[] | undefined,
+ reporter: ReporterFunction
+) {
+ return Promise.all(
+ (stylesheets || []).map((href) => {
+ return fetch(href)
+ .then((res) => res.text())
+ .then((innerText) => Object.assign(document.createElement("style"), {innerText}))
+ .then((el) => reporter(el));
+ })
+ );
+}
diff --git a/src/js/lib/WebPackage/FontLoader.ts b/src/js/lib/WebPackage/FontLoader.ts
new file mode 100644
index 0000000..c2311d0
--- /dev/null
+++ b/src/js/lib/WebPackage/FontLoader.ts
@@ -0,0 +1,12 @@
+import { ReporterFunction } from "./types";
+
+export default function(
+ fonts: Array<[string, string]> | undefined,
+ reporter: ReporterFunction
+) {
+ return Promise.all(
+ (fonts || []).map((font) => {
+ return new FontFace(...font).load().then((font) => reporter(font));
+ })
+ );
+}
diff --git a/src/js/lib/WebPackage/ImageLoader.ts b/src/js/lib/WebPackage/ImageLoader.ts
new file mode 100644
index 0000000..f4fb459
--- /dev/null
+++ b/src/js/lib/WebPackage/ImageLoader.ts
@@ -0,0 +1,17 @@
+import { ReporterFunction } from "./types";
+
+export default function(
+ images: string[] | undefined,
+ reporter: ReporterFunction
+) {
+ return Promise.all(
+ (images || []).map((src) => {
+ return new Promise((resolve, reject) => {
+ const el = document.createElement("img");
+ el.onload = () => resolve(el);
+ el.onerror = reject;
+ el.src = src;
+ }).then((el) => reporter(el));
+ })
+ );
+}
diff --git a/src/js/lib/WebPackage/OtherLoader.ts b/src/js/lib/WebPackage/OtherLoader.ts
new file mode 100644
index 0000000..2cb04b0
--- /dev/null
+++ b/src/js/lib/WebPackage/OtherLoader.ts
@@ -0,0 +1,15 @@
+import { ReporterFunction } from "./types";
+
+export default function(
+ others: string[] | undefined,
+ reporter: ReporterFunction
+) {
+ return Promise.all(
+ (others || []).map((src) => {
+ return fetch(src)
+ .then((res) => res.text())
+ .then((text) => Object.assign(document.createElement("script"), {type: "text/plain", src, text}))
+ .then((el) => reporter(el));
+ })
+ );
+}
diff --git a/src/js/lib/WebPackage/ScriptLoader.ts b/src/js/lib/WebPackage/ScriptLoader.ts
new file mode 100644
index 0000000..b7e8d52
--- /dev/null
+++ b/src/js/lib/WebPackage/ScriptLoader.ts
@@ -0,0 +1,15 @@
+import { ReporterFunction } from "./types";
+
+export default function(
+ scripts: string[] | undefined,
+ reporter: ReporterFunction
+) {
+ return Promise.all(
+ (scripts || []).map((src) => {
+ return fetch(src)
+ .then((res) => res.text())
+ .then((text) => Object.assign(document.createElement("script"), {type: "application/javascript", text}))
+ .then((el) => reporter(el));
+ })
+ );
+}
diff --git a/src/js/lib/WebPackage/types.ts b/src/js/lib/WebPackage/types.ts
new file mode 100644
index 0000000..8cd9b49
--- /dev/null
+++ b/src/js/lib/WebPackage/types.ts
@@ -0,0 +1,23 @@
+export interface WebPackage {
+ fetch: () => Promise,
+}
+
+export interface Package {
+ scripts: HTMLElement[],
+ stylesheets: HTMLElement[],
+ images: HTMLElement[],
+ fonts: FontFace[],
+ others: HTMLElement[]
+}
+
+export interface PackageSpec {
+ scripts?: string[],
+ stylesheets?: string[],
+ images?: string[],
+ fonts?: Array<[string, string]>,
+ others?: string[],
+ onprogress?: (percent: number) => any
+}
+
+export type PackageItem = HTMLElement | FontFace;
+export type ReporterFunction = (el: PackageItem) => PackageItem;
diff --git a/src/js/pages/TheSurahPage/package.ts b/src/js/pages/TheSurahPage/package.ts
new file mode 100644
index 0000000..534bec6
--- /dev/null
+++ b/src/js/pages/TheSurahPage/package.ts
@@ -0,0 +1,30 @@
+import WebPackage from "lib/WebPackage";
+
+(function() {
+ const parent: HTMLElement = document.querySelector(".webpackage.loader");
+ const progressBar: HTMLProgressElement = parent.querySelector("progress");
+ const progressNumber: HTMLSpanElement = parent.querySelector(".percentage");
+ const { locale, surahId } = document.querySelector(".surah").dataset;
+
+ WebPackage({
+ scripts: ["/js/pages/surah.js"],
+ stylesheets: ["/css/surah.css"],
+ images: ["/images/moon.svg", "/images/leaf.svg"],
+ others: [`/${locale}/${surahId}/surah.json`],
+ fonts: [
+ ["Kanit Regular", "url(/fonts/kanit-regular.ttf)"],
+ ["Roboto Mono Regular", "url(/fonts/roboto-mono-regular.ttf)"]
+ ],
+ onprogress: (percent: number) => {
+ progressBar.value = percent;
+ progressNumber.innerText = `${percent.toFixed(0)}%`;
+ }
+ }).fetch()
+ .then((pkg) => {
+ parent.remove();
+ pkg.fonts.forEach((f) => document.fonts.add(f));
+ pkg.stylesheets.forEach((s) => document.head.appendChild(s));
+ pkg.others.forEach((o) => document.body.appendChild(o));
+ pkg.scripts.forEach((s) => document.body.appendChild(s));
+ });
+})();