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.
This commit is contained in:
0x1eef 2022-11-12 18:55:53 -03:00 committed by Robert
parent 9971ea3616
commit 616f5d6073
14 changed files with 244 additions and 9 deletions

View file

@ -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,
},
};

5
Rules
View file

@ -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

13
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -2,11 +2,49 @@
<html lang="<%= locale %>">
<head>
<title>Al-Quran: Loading</title>
<link rel="stylesheet" href="/css/surah.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
html {
height: 100%;
}
body {
height: 100%;
margin: 0;
padding: 0;
}
.webpackage.loader {
height: 100%;
}
.webpackage.loader div:first-child {
width: 200px;
margin: 0 auto;
display: flex;
flex-direction: column;
position: relative;
top: 250px;
}
.webpackage.loader progress {
width: 100%;
}
.webpackage.loader .percentage {
font-family: monospace;
text-align: center;
width: 100%;
}
</style>
</head>
<body>
<div class="surah" data-locale="<%= locale %>" data-surah-id="<%= surah_id %>"></div>
<script src="/js/pages/surah.js"></script>
<div class="webpackage loader">
<div>
<progress value="0" max="100"></progress>
<span class="percentage">Loading...</span>
</div>
</div>
<div class="surah" data-locale="<%= locale %>" data-surah-id="<%= surah_id %>">
</div>
<script src="/js/pages/TheSurahPage/package.js"></script>
</body>
</html>

View file

@ -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<HTMLElement>(`script[src="${path}"]`).innerText;
const json = JSON.parse(text);
setSurah(Quran.Surah.fromJSON(json.shift(), json));
}, []);
return {

49
src/js/lib/WebPackage.ts Normal file
View file

@ -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<Package> | 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;
}

View file

@ -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));
})
);
}

View file

@ -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));
})
);
}

View file

@ -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<HTMLElement>((resolve, reject) => {
const el = document.createElement("img");
el.onload = () => resolve(el);
el.onerror = reject;
el.src = src;
}).then((el) => reporter(el));
})
);
}

View file

@ -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));
})
);
}

View file

@ -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));
})
);
}

View file

@ -0,0 +1,23 @@
export interface WebPackage {
fetch: () => Promise<Package>,
}
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;

View file

@ -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<HTMLElement>(".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));
});
})();