WebPackage -> Packet

Rename WebPackage to Packet.
Move Packet to `/packages/typescript/packet`.
This commit is contained in:
0x1eef 2023-06-19 12:08:29 -03:00 committed by Robert
parent 42707049a3
commit 6935e67a49
13 changed files with 325 additions and 285 deletions

358
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,24 @@
{
"name": "al-quran.reflectslight.io",
"workspaces": ["./packages/typescript/*"],
"scripts": {
"eslint": "node ./node_modules/eslint/bin/eslint.js src/js/",
"eslint-autofix": "node ./node_modules/eslint/bin/eslint.js --fix src/js/"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ts-loader": "^9.3.1",
"classnames": "^2.3.2",
"es-cookie": "^1.4.0"
},
"devDependencies": {
"@types/css-font-loading-module": "^0.0.7",
"@types/react": "^18.0.18",
"@types/react-dom": "^18.0.6",
"classnames": "^2.3.2",
"es-cookie": "^1.4.0",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ts-loader": "^9.3.1",
"ts-standard": "^12.0.1",
"typescript": "^4.8.2",
"webpack": "^5.74.0",

1
packages/typescript/packet/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
dist/

View file

@ -0,0 +1,21 @@
{
"name": "packet",
"version": "0.1.0",
"description": "Download a web page's dependencies before the page loads.",
"main": "dist/index.js",
"types": ["dist/index.d.ts"],
"scripts": {
"build": "npm exec tsc",
"prepare": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ReflectsLight/al-quran.reflectslight.io.git"
},
"author": "0x1eef",
"license": "0BSDL",
"devDependencies": {
"@types/node": "^16.18",
"typescript": "^4.5"
}
}

View file

@ -0,0 +1,52 @@
import {
Packet,
PacketSpec,
PacketTarget,
} from './packet/types';
import {
image,
stylesheet,
script,
other,
font
} from './packet/loaders';
export type { Packet, PacketSpec, PacketTarget };
export default function (pkgspec: PacketSpec) {
const self: Packet = Object.create(null);
const pkg: PacketTarget = { fonts: [], images: [], stylesheets: [], scripts: [], others: [] };
const { fonts, images, stylesheets, scripts, others, onprogress } = Object.assign({}, pkg, pkgspec);
const total = [...fonts, ...images, ...stylesheets, ...scripts, ...others].length;
let index = 0;
const reporter = <T>(el: T) => {
index++;
if (onprogress && index <= total) {
onprogress(100 * (index / total));
}
return el;
};
let fetcher: Promise<PacketTarget> | null = null;
self.fetch = () => {
if (fetcher) {
return fetcher;
} else {
fetcher = font(fonts, reporter)
.then((fonts: FontFace[]) => pkg.fonts.push(...fonts))
.then(() => image(images, reporter))
.then((images: HTMLElement[]) => pkg.images.push(...images))
.then(() => stylesheet(stylesheets, reporter))
.then((stylesheets: HTMLElement[]) => pkg.stylesheets.push(...stylesheets))
.then(() => script(scripts, reporter))
.then((scripts: HTMLElement[]) => pkg.scripts.push(...scripts))
.then(() => other(others, reporter))
.then((others: HTMLElement[]) => pkg.others.push(...others))
.then(() => pkg);
return fetcher;
}
};
return self;
}

View file

@ -0,0 +1,8 @@
const getNavigationEntries = (): PerformanceNavigationTiming[] => {
return performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
};
export function fetchOptions(): RequestInit {
const pageHasRefreshed = getNavigationEntries().some((e) => e.type === 'reload');
return pageHasRefreshed ? { cache: 'reload' } : {};
}

View file

@ -0,0 +1,80 @@
const fetchOptions = (): RequestInit => {
const getNavigationEntries = (): PerformanceNavigationTiming[] => {
return performance
.getEntriesByType('navigation') as PerformanceNavigationTiming[];
};
const pageHasRefreshed = getNavigationEntries()
.some((e) => e.type === 'reload');
return pageHasRefreshed ? { cache: 'reload' } : {};
}
export function script(
scripts: string[] | undefined,
reporter: <T>(f: T) => T
) {
return Promise.all(
(scripts || []).map((src) => {
return fetch(src, fetchOptions())
.then((res) => res.text())
.then((text) => Object.assign(document.createElement('script'), { type: 'application/javascript', text }))
.then((el) => reporter<HTMLElement>(el));
})
);
}
export function stylesheet(
stylesheets: string[] | undefined,
reporter: <T>(f: T) => T
) {
return Promise.all(
(stylesheets || []).map((href) => {
return fetch(href, fetchOptions())
.then((res) => res.text())
.then((innerText) => Object.assign(document.createElement('style'), { innerText }))
.then((el) => reporter<HTMLElement>(el));
})
);
}
export function image(
images: string[] | undefined,
reporter: <T>(f: T) => T
) {
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<HTMLElement>(el));
})
);
}
export function font(
fonts: Array<[string, string]> | undefined,
reporter: <T>(f: T) => T
) {
return Promise.all(
(fonts || []).map(async (font) => {
return await new FontFace(...font)
.load()
.then((font) => reporter<FontFace>(font));
})
);
}
export function other(
others: string[] | undefined,
reporter: <T>(f: T) => T
) {
return Promise.all(
(others || []).map((src) => {
return fetch(src, fetchOptions())
.then((res) => res.text())
.then((text) => Object.assign(document.createElement('script'), { type: 'text/plain', src, text }))
.then((el) => reporter<HTMLElement>(el));
})
);
}

View file

@ -0,0 +1,22 @@
export interface Packet {
fetch: () => Promise<PacketTarget> | null
}
export type PacketTarget = {
scripts: HTMLElement[]
stylesheets: HTMLElement[]
images: HTMLElement[]
fonts: FontFace[]
others: HTMLElement[]
}
export interface PacketSpec {
scripts: string[]
stylesheets: string[]
images: string[]
fonts: Array<[string, string]>
others: string[]
onprogress?: (percent: number) => any
}
export type PackageItem = HTMLElement | FontFace;

View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"strict": true,
"module": "ESNEXT",
"target": "ES2020",
"esModuleInterop": true,
"moduleResolution": "node",
"baseUrl": "src/",
"paths": { "*": ["*"] },
"outDir": "dist",
"declaration": true,
}
}

View file

@ -1,4 +1,5 @@
import WebPackage from 'lib/WebPackage';
import Packet from 'packet';
import type { PacketTarget } from 'packet';
(function() {
const parent: HTMLElement = document.querySelector('.webpackage.loader')!;
@ -6,7 +7,7 @@ import WebPackage from 'lib/WebPackage';
const progressNumber: HTMLSpanElement = parent.querySelector('.percentage')!;
const inlineStyle: HTMLStyleElement = document.querySelector('.css.webpackage')!;
WebPackage({
Packet({
scripts: ['/js/pages/surah/index.js'],
stylesheets: ['/css/pages/surah/index.css'],
images: ['/images/moon.svg', '/images/leaf.svg'],
@ -21,7 +22,7 @@ import WebPackage from 'lib/WebPackage';
progressNumber.innerText = `${percent.toFixed(0)}%`;
}
}).fetch()
.then((pkg) => {
.then((pkg: PacketTarget) => {
inlineStyle.remove();
parent.remove();
pkg.fonts.forEach((f) => document.fonts.add(f));

View file

@ -1,4 +1,5 @@
import WebPackage from 'lib/WebPackage';
import Packet from 'packet';
import type { PacketTarget } from 'packet';
(function() {
const parent: HTMLElement = document.querySelector('.webpackage.loader')!;
@ -7,7 +8,7 @@ import WebPackage from 'lib/WebPackage';
const inlineStyle: HTMLStyleElement = document.querySelector('.css.webpackage')!;
const { locale, surahId } = document.querySelector<HTMLElement>('.root')!.dataset;
WebPackage({
Packet({
scripts: ['/js/pages/surah/stream.js'],
stylesheets: ['/css/pages/surah/stream.css'],
images: ['/images/moon.svg', '/images/leaf.svg'],
@ -22,7 +23,7 @@ import WebPackage from 'lib/WebPackage';
progressNumber.innerText = `${percent.toFixed(0)}%`;
}
}).fetch()
.then((pkg) => {
.then((pkg: PacketTarget) => {
inlineStyle.remove();
parent.remove();
pkg.fonts.forEach((f) => document.fonts.add(f));

View file

@ -1,19 +1,17 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": ["src/js/*"]
},
"lib": [
"dom"
],
"noImplicitAny": true,
"strict": true,
"module": "es6",
"target": "es6",
"jsx": "react",
"allowJs": true,
"strictNullChecks": false,
"module": "commonjs",
"target": "ES2020",
"noImplicitAny": true,
"moduleResolution": "node",
"esModuleInterop": true,
"jsx": "react",
"allowJs": true,
"lib": [ "ES2020", "DOM" ],
"baseUrl": "src/",
"paths": { "*": ["js/*"] },
}
}

View file

@ -3,9 +3,6 @@ const process = require('process');
module.exports = {
mode: process.env.NODE_ENV || "development",
experiments: {
asyncWebAssembly: true
},
resolve: {
roots: [path.resolve('src/js'), path.resolve('node_modules')],
modules: [path.resolve('src/js'), path.resolve('node_modules')],