Compare commits

..

10 commits

Author SHA1 Message Date
4f222ffda6 Add source 2024-07-12 02:33:34 -03:00
d552685ff2 Update README 2024-07-12 02:30:23 -03:00
9edf257a1d Update README 2024-07-12 02:28:40 -03:00
9d831d6d04 Add share/postman/examples/ 2024-07-12 02:26:46 -03:00
3b9f683b5b Add "See also" 2024-07-12 02:10:05 -03:00
c7112a7640 Reorder args 2024-07-12 01:54:50 -03:00
466c10ba08 Add improvements 2024-07-12 01:51:20 -03:00
2efc9eace2 Apply eslint 2024-07-12 01:00:31 -03:00
23a2b2f9e8 Add eslint, prettier, ts-standard 2024-07-12 01:00:04 -03:00
bb1d672339 Update example 2024-07-12 00:35:17 -03:00
8 changed files with 195 additions and 112 deletions

18
.eslintrc.js Normal file
View file

@ -0,0 +1,18 @@
module.exports = {
extends: ["standard-with-typescript", "prettier"],
plugins: ["prettier"],
parserOptions: { project: "./tsconfig.json", },
rules: {
"prettier/prettier": [
"error",
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"printWidth": 85,
"arrowParens": "avoid"
}
]
},
};

View file

@ -19,7 +19,7 @@ progress bar is removed once the delivery is complete:
<html> <html>
<head> <head>
<title>Postman</title> <title>Postman</title>
<script type="module" src="/postman.js"></script> <script type="module" src="delivery.js"></script>
</head> </head>
<body> <body>
<div class="postman loader"> <div class="postman loader">
@ -31,39 +31,46 @@ progress bar is removed once the delivery is complete:
</html> </html>
``` ```
**postman.js** **delivery.js**
```typescript ```typescript
import postman, { item } from "postman"; import postman, { item } from "postman";
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const progressBar = document.querySelector("progress"); const bar = document.querySelector("progress");
const span = document.querySelector(".percentage"); const span = document.querySelector(".percentage");
postman( const delivery = postman(
item.font("Kanit Regular", "url(/fonts/kanit-regular.ttf)"), item.font("Kanit Regular", "url(/fonts/kanit-regular.ttf)"),
item.script("/js/app.js"), item.script("/js/app.js"),
item.image("/images/app.png"), item.image("/images/app.png"),
item.css("/css/app.css"), item.css("/css/app.css"),
item.progress((percent) => { item.progress((percent) => {
progressBar.value = percent; bar.value = percent;
span.innerText = `${percent}%`; span.innerText = `${percent}%`;
}) })
).deliver() ).deliver();
.then((package) => {
/* Add page assets */ delivery.then((package) => {
package.fonts.forEach((font) => documents.fonts.add(font)); /* Add page assets */
package.scripts.forEach((script) => document.body.appendChild(script)); package.fonts.forEach((font) => documents.fonts.add(font));
package.css.forEach((css) => document.head.appendChild(css)); package.scripts.forEach((script) => document.body.appendChild(script));
/* Replace progress bar */ package.css.forEach((css) => document.head.appendChild(css));
progressBar.remove(); /* Replace progress bar */
span.remove(); bar.remove();
}); span.remove();
});
}); });
``` ```
## See also
* [https://al-quran.reflectslight.io/](https://al-quran.reflectslight.io) <br>
Delivers all of its assets with Postman
## Sources ## Sources
* [GitHub](https://github.com/0x1eef/postman) * [GitHub](https://github.com/0x1eef/postman)
* [GitLab](https://gitlab.com/0x1eef/postman) * [GitLab](https://gitlab.com/0x1eef/postman)
* [brew.bsd.cafe/@0x1eef](https://brew.bsd.cafe/@0x1eef)
## License ## License

View file

@ -3,10 +3,14 @@
"version": "0.1.0", "version": "0.1.0",
"description": "Delivers the assets of a web page", "description": "Delivers the assets of a web page",
"main": "dist/index.js", "main": "dist/index.js",
"types": ["dist/index.d.ts"], "types": [
"dist/index.d.ts"
],
"scripts": { "scripts": {
"build": "npm exec tsc", "build": "npm exec tsc",
"prepare": "npm run build" "prepare": "npm run build",
"tsc": "npm exec tsc -- --noEmit",
"eslint": "npm exec eslint -- src/*.ts src/**/*.ts"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -16,6 +20,10 @@
"license": "0BSDL", "license": "0BSDL",
"devDependencies": { "devDependencies": {
"@types/node": "^16.18", "@types/node": "^16.18",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"ts-standard": "^12.0.2",
"typescript": "^4.5" "typescript": "^4.5"
} }
} }

View file

@ -0,0 +1,25 @@
import postman, { item } from "postman";
document.addEventListener("DOMContentLoaded", () => {
const bar = document.querySelector("progress");
const span = document.querySelector(".percentage");
const delivery = postman(
item.font("Kanit Regular", "url(/fonts/kanit-regular.ttf)"),
item.script("/js/app.js"),
item.image("/images/app.png"),
item.css("/css/app.css"),
item.progress((percent) => {
bar.value = percent;
span.innerText = `${percent}%`;
})
).deliver();
delivery.then((package) => {
/* Add page assets */
package.fonts.forEach((font) => documents.fonts.add(font));
package.scripts.forEach((script) => document.body.appendChild(script));
package.css.forEach((css) => document.head.appendChild(css));
/* Replace progress bar */
bar.remove();
span.remove();
});
});

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>Postman</title>
<script type="module" src="postman.js"></script>
</head>
<body>
<div class="postman loader">
<progress value="0" max="100"></progress>
<span class="percentage"></span>
</div>
</div>
</body>
</html>

View file

@ -1,73 +1,79 @@
import type { Item, FontItem } from './postman/item'; import type { Item, FontItem } from "./postman/item";
import item from './postman/item'; import item from "./postman/item";
import request from './postman/request'; import request from "./postman/request";
type Postman = { deliver: () => Promise<Package> }; interface Postman {
type Args = Array<Item | FontItem | Function> deliver: () => Promise<Package>;
}
type Args = Array<Item | FontItem | ((n: number) => number)>;
type Items = Array<Item | FontItem>; type Items = Array<Item | FontItem>;
type Package = { interface Package {
fonts: FontFace[] fonts: FontFace[];
images: HTMLElement[] images: HTMLElement[];
css: HTMLElement[] css: HTMLElement[];
scripts: HTMLElement[] scripts: HTMLElement[];
json: HTMLElement[] json: HTMLElement[];
}; }
function parseArgs(args: Args): [Items, Function] { function parseArgs(args: Args): [Items, (n: number) => number] {
const items: Items = []; const items: Items = [];
let callback: Function = (n: number) => n let progresscb = (n: number): number => n;
args.forEach((item) => { args.forEach(item => {
if (typeof item === 'function') { if (typeof item === "function") {
callback = item; progresscb = item;
} else { } else {
items.push(item); items.push(item);
} }
}); });
return [items, callback]; return [items.sort((i1, i2) => (i1.priority >= i2.priority ? 1 : -1)), progresscb];
} }
export { item }; export { item };
export default function (...args: Args) { export default function (...args: Args): Postman {
const self: Postman = Object.create(null); const self = Object.create(null);
const result: Package = { fonts: [], images: [], css: [], scripts: [], json: [] }; const [items, progresscb] = parseArgs(args);
const [items, callback] = parseArgs(args); const _package: Package = {
items.sort((i1, i2) => i1.priority >= i2.priority ? 1 : -1); fonts: [],
images: [],
css: [],
scripts: [],
json: [],
};
let index = 0; let index = 0;
const onProgress = <T>(el: T) => { function onProgress<T>(el: T): T {
index++; index++;
if (index <= items.length) { if (index <= items.length) {
callback(100 * (index / items.length)); progresscb(100 * (index / items.length));
} }
return el; return el;
}; }
const spawnRequests = () => { function fetch(): Array<Promise<Package | null>> {
const reqs = items.map((item: Item | FontItem) => { return items.map(async (item: Item | FontItem) => {
if ('fontFamily' in item) { if ("fontFamily" in item) {
const req = request.font; const req = request.font;
return req(item) return await req(item)
.then((el) => onProgress<FontFace>(el)) .then(el => onProgress<FontFace>(el))
.then((font) => result.fonts.push(font)) .then(font => _package.fonts.push(font))
.then(() => result); .then(() => _package);
} else if(item.requestId !== 'font' && item.group !== 'fonts') { } else if (item.requestId !== "font" && item.group !== "fonts") {
const req = request[item.requestId]; const req = request[item.requestId];
const ary = result[item.group]; const ary = _package[item.group];
return req(item) return await req(item)
.then((el) => onProgress<HTMLElement>(el)) .then(el => onProgress<HTMLElement>(el))
.then((el) => ary.push(el)) .then(el => ary.push(el))
.then(() => result); .then(() => _package);
} else {
return null;
} }
/* unreachable */
return null;
}); });
return reqs as Array<Promise<Package>>; }
};
self.deliver = async () => { self.deliver = async () => {
await Promise.all<Package>(spawnRequests()); await Promise.all(fetch());
return result; return _package;
}; };
return self; return self;

View file

@ -1,65 +1,70 @@
type Group = 'fonts' | 'images' | 'css' | 'scripts' | 'json'; type Group = "fonts" | "images" | "css" | "scripts" | "json";
type RequestID = 'font' | 'image' | 'css' | 'script' | 'json'; type RequestID = "font" | "image" | "css" | "script" | "json";
export type Item = { export interface Item {
priority: number priority: number;
group: Group group: Group;
requestId: RequestID requestId: RequestID;
href: string href: string;
props?: Partial<HTMLElement> props?: Partial<HTMLElement>;
}; }
export type FontItem = { export type FontItem = {
fontFamily: string fontFamily: string;
} & Item; } & Item;
export default { export default {
font(fontFamily: string, href: string): FontItem { font(fontFamily: string, href: string): FontItem {
return { return {
priority: 1, priority: 1,
group: 'fonts', group: "fonts",
requestId: 'font', requestId: "font",
href, fontFamily, href,
fontFamily,
}; };
}, },
image(href: string, props?: Partial<HTMLElement>): Item { image(href: string, props?: Partial<HTMLElement>): Item {
return { return {
priority: 2, priority: 2,
group: 'images', group: "images",
requestId: 'image', requestId: "image",
href, props href,
props,
}; };
}, },
css(href: string, props?: Partial<HTMLElement>): Item { css(href: string, props?: Partial<HTMLElement>): Item {
return { return {
priority: 3, priority: 3,
group: 'css', group: "css",
requestId: 'css', requestId: "css",
href, props href,
props,
}; };
}, },
script(href: string, props?: Partial<HTMLElement>): Item { script(href: string, props?: Partial<HTMLElement>): Item {
return { return {
priority: 4, priority: 4,
group: 'scripts', group: "scripts",
requestId: 'script', requestId: "script",
href, props href,
props,
}; };
}, },
json(href: string, props?: Partial<HTMLElement>): Item { json(href: string, props?: Partial<HTMLElement>): Item {
return { return {
priority: 5, priority: 5,
group: 'json', group: "json",
requestId: 'json', requestId: "json",
href, props href,
} props,
};
}, },
progress(fn: (percent: number) => void): (percent: number) => void { progress(fn: (percent: number) => void): (percent: number) => void {
return fn; return fn;
} },
}; };

View file

@ -1,43 +1,43 @@
import type { Item, FontItem } from './item'; import type { Item, FontItem } from "./item";
export default { export default {
font(item: FontItem): Promise<FontFace> { async font(item: FontItem): Promise<FontFace> {
const { fontFamily, href } = item; const { fontFamily, href } = item;
return new FontFace(fontFamily, href).load(); return await new FontFace(fontFamily, href).load();
}, },
script(item: Item, options: RequestInit = {}): Promise<HTMLElement> { async script(item: Item, options: RequestInit = {}): Promise<HTMLElement> {
const { href } = item; const { href } = item;
return fetch(href, options) return await fetch(href, options)
.then((res) => res.text()) .then(async res => await res.text())
.then((text) => ({ type: 'application/javascript', text })) .then(text => ({ type: "application/javascript", text }))
.then((props) => Object.assign(document.createElement('script'), props)); .then(props => Object.assign(document.createElement("script"), props));
}, },
css(item: Item, options: RequestInit = {}): Promise<HTMLElement> { async css(item: Item, options: RequestInit = {}): Promise<HTMLElement> {
const { href } = item; const { href } = item;
return fetch(href, options) return await fetch(href, options)
.then((res) => res.text()) .then(async res => await res.text())
.then((text) => ({ innerText: text })) .then(text => ({ innerText: text }))
.then((props) => Object.assign(document.createElement('style'), props)); .then(props => Object.assign(document.createElement("style"), props));
}, },
image(item: Item): Promise<HTMLElement> { async image(item: Item): Promise<HTMLElement> {
const { href } = item; const { href } = item;
return new Promise<HTMLElement>((resolve, reject) => { return await new Promise<HTMLElement>((resolve, reject) => {
const el = document.createElement('img'); const el = document.createElement("img");
el.onload = () => resolve(el); el.onload = () => resolve(el);
el.onerror = reject; el.onerror = reject;
el.src = href; el.src = href;
}); });
}, },
json(item: Item, options: RequestInit = {}): Promise<HTMLElement> { async json(item: Item, options: RequestInit = {}): Promise<HTMLElement> {
const { href } = item; const { href } = item;
return fetch(href, options) return await fetch(href, options)
.then((res) => res.text()) .then(async res => await res.text())
.then((text) => ({type: 'application/json', text})) .then(text => ({ type: "application/json", text }))
.then((props) => Object.assign(props, item.props || {})) .then(props => Object.assign(props, item.props != null || {}))
.then((props) => Object.assign(document.createElement('script'), props)); .then(props => Object.assign(document.createElement("script"), props));
} },
}; };