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:
parent
9971ea3616
commit
616f5d6073
14 changed files with 244 additions and 9 deletions
|
@ -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
5
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
|
||||
|
|
13
package-lock.json
generated
13
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
49
src/js/lib/WebPackage.ts
Normal 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;
|
||||
}
|
15
src/js/lib/WebPackage/CSSLoader.ts
Normal file
15
src/js/lib/WebPackage/CSSLoader.ts
Normal 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));
|
||||
})
|
||||
);
|
||||
}
|
12
src/js/lib/WebPackage/FontLoader.ts
Normal file
12
src/js/lib/WebPackage/FontLoader.ts
Normal 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));
|
||||
})
|
||||
);
|
||||
}
|
17
src/js/lib/WebPackage/ImageLoader.ts
Normal file
17
src/js/lib/WebPackage/ImageLoader.ts
Normal 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));
|
||||
})
|
||||
);
|
||||
}
|
15
src/js/lib/WebPackage/OtherLoader.ts
Normal file
15
src/js/lib/WebPackage/OtherLoader.ts
Normal 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));
|
||||
})
|
||||
);
|
||||
}
|
15
src/js/lib/WebPackage/ScriptLoader.ts
Normal file
15
src/js/lib/WebPackage/ScriptLoader.ts
Normal 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));
|
||||
})
|
||||
);
|
||||
}
|
23
src/js/lib/WebPackage/types.ts
Normal file
23
src/js/lib/WebPackage/types.ts
Normal 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;
|
30
src/js/pages/TheSurahPage/package.ts
Normal file
30
src/js/pages/TheSurahPage/package.ts
Normal 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));
|
||||
});
|
||||
})();
|
Loading…
Reference in a new issue