raiutils 9.1.0 → 9.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/build.ts ADDED
@@ -0,0 +1,280 @@
1
+ //Auto Build v2.3, Pecacheu 2026. GNU GPL v3
2
+
3
+ import { spawn } from 'child_process';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import mHTML from '@minify-html/node';
7
+ import type { HtmlFileConfiguration } from '@pecacheu/esbuild-plugin-html';
8
+ import C from 'chalk';
9
+ import type { BuildContext, BuildOptions, Plugin } from 'esbuild';
10
+ import { type MinifyOptions, minify as mJS } from 'terser';
11
+
12
+ let esbuild: typeof import('esbuild') | undefined,
13
+ pHTML: typeof import('@pecacheu/esbuild-plugin-html') | undefined;
14
+
15
+ try {esbuild = await import('esbuild')} catch(e) {}
16
+ if(esbuild) pHTML = await import('@pecacheu/esbuild-plugin-html');
17
+
18
+ const Mode = process.argv[2],
19
+ Watch = Mode === 'watch',
20
+ Dev = Watch || Mode === 'dev',
21
+ Meta = Mode === 'meta',
22
+ log = console.log,
23
+ UTF = {encoding: 'utf8' as BufferEncoding},
24
+ R_SC = /;\n?(\/\/#.+)?$/,
25
+ _hFD: HtmlFileConfiguration[] = [];
26
+
27
+ /** Default build options */
28
+ const defaults = {
29
+ //Paths
30
+ /** Entrypoint of app for esbuild, relative to `srcCli`.
31
+ You can also set `opts.esOpts.entryPoints` manually
32
+ */
33
+ app: 'app.ts',
34
+ /** Source directory */
35
+ src: 'src',
36
+ /** Build output directory */
37
+ dist: 'dist',
38
+ /** Client source dir, relative to `src` */
39
+ srcCli: 'web',
40
+ /** Client build output, relative to `dist` */
41
+ distCli: 'web',
42
+ /** Server source dir, relative to `src`.
43
+ Set to `''` to disable server build */
44
+ srcSrv: 'srv',
45
+
46
+ //Hooks
47
+ //TODO Make them work in watch mode
48
+ /** Runs after build but before minify */
49
+ onPreMin: null as (() => Promise<void>) | null,
50
+ /** Runs after build completes
51
+ @param ctx Only defined if esbuild is installed */
52
+ onPostBuild: null as ((ctx?: BuildContext) => Promise<void>) | null,
53
+
54
+ //Build
55
+ /** JS extensions to minify */
56
+ jsMinExt: ['.js'],
57
+ /** HTML extensions to minify */
58
+ htmlMinExt: ['.html'],
59
+ /** Extensions to trim whitespace */
60
+ trimExt: ['.css', '.svg'],
61
+ /** Set to false to disable esbuild even if it is installed */
62
+ esbuild: true,
63
+ /** Esbuild HTML plugin options */
64
+ htmlLoadOpts: {
65
+ scriptLoading: 'module'
66
+ } as Omit<HtmlFileConfiguration, 'filename' | 'htmlFile' | 'htmlTemplate'>,
67
+ /** Esbuild options */
68
+ esOpts: {
69
+ bundle: true,
70
+ minify: !Dev,
71
+ sourcemap: Dev,
72
+ format: 'esm',
73
+ splitting: true,
74
+ metafile: Meta ? true : undefined,
75
+ loader: {
76
+ '.svg': 'file',
77
+ '.jpg': 'file',
78
+ '.woff': 'copy',
79
+ '.woff2': 'copy'
80
+ },
81
+ plugins: []
82
+ } as BuildOptions,
83
+
84
+ //Minify
85
+ /** Terser minify options */
86
+ jsMin: {
87
+ ecma: 2020,
88
+ module: true,
89
+ format: {inline_script: false, comments: false},
90
+ mangle: {properties: {regex: /^[_#]/}},
91
+ compress: {passes: 2, arguments: true, keep_fargs: false, keep_infinity: true, unsafe: true}
92
+ } as MinifyOptions,
93
+ /** HTML minify options */
94
+ htmlMin: {
95
+ allow_optimal_entities: true,
96
+ allow_removing_spaces_between_attributes: true,
97
+ minify_css: true,
98
+ minify_js: true
99
+ } as Parameters<typeof mHTML.minify>[1]
100
+ };
101
+
102
+ export type Options = typeof defaults;
103
+ let opts!: Options, _hPl: Plugin;
104
+
105
+ //==== Minify ====
106
+
107
+ async function minify(pin: string, _: any, fn: string) {
108
+ let fin = `${pin}/${fn}`, ext = path.extname(fn), f;
109
+ if(fin.indexOf('.min') !== -1) return;
110
+ const fls = fin.slice(opts.dist.length + 1);
111
+ try {
112
+ if(opts.jsMinExt.includes(ext)) { //Minify JS
113
+ const map = `${fin}.map`;
114
+ f = await fs.readFile(fin, UTF);
115
+ try {
116
+ await getSrc(map);
117
+ const out = await mJS(f, opts.jsMin);
118
+ f = out.code!.replace(R_SC, '$1');
119
+ if(out.map) {
120
+ await fs.writeFile(map, out.map as string);
121
+ log(C.cyan(`- ${fls}.map`));
122
+ }
123
+ } finally {delete opts.jsMin.sourceMap}
124
+ await fs.writeFile(fin, f);
125
+ log(C.cyan(`- ${fls}`));
126
+ } else if(opts.htmlMinExt.includes(ext)) { //Minify HTML
127
+ f = mHTML.minify(await fs.readFile(fin), opts.htmlMin);
128
+ await fs.writeFile(fin, f);
129
+ log(C.cyan(`- ${fls}`));
130
+ } else if(opts.trimExt.includes(ext)) { //Trim
131
+ f = (await fs.readFile(fin, UTF)).trim();
132
+ await fs.writeFile(fin, f);
133
+ log(C.magenta(`- ${fls}`));
134
+ } else { //Log
135
+ log(C.dim(`- ${fls}`));
136
+ }
137
+ } catch(e) {
138
+ log(C.red(`- ${fls}`));
139
+ throw e;
140
+ }
141
+ }
142
+
143
+ async function getSrc(map: string) {
144
+ try {
145
+ opts.jsMin.sourceMap = {
146
+ content: await fs.readFile(map, UTF),
147
+ url: path.basename(map)
148
+ };
149
+ } catch(e) {}
150
+ }
151
+
152
+ function addHTML(pin: string, _: any, fn: string) {
153
+ if(fn.endsWith('.html') && fn[0] !== '+') _hFD.push({
154
+ filename: fn, htmlFile: `${pin}/${fn}`, ...opts.htmlLoadOpts
155
+ });
156
+ }
157
+
158
+ /** Set overrides for build options */
159
+ function setOpts(o?: Partial<Options>) {
160
+ const oo = opts ? [opts.srcCli, opts.distCli, opts.srcSrv, opts.app] : [];
161
+ opts = o ? {...defaults, ...o} : defaults;
162
+
163
+ if(opts.srcCli !== oo[0]) opts.srcCli = path.join(opts.src, opts.srcCli);
164
+ if(opts.distCli !== oo[1]) opts.distCli = path.join(opts.dist, opts.distCli);
165
+ if(opts.srcSrv !== oo[2]) opts.srcSrv = opts.srcSrv ? path.join(opts.src, opts.srcSrv) : '';
166
+ if(opts.app !== oo[3]) opts.app = opts.app ? path.join(opts.srcCli, opts.app) : '';
167
+
168
+ if(opts.app) opts.esOpts.entryPoints = [opts.app];
169
+ opts.esOpts.target = `es${opts.jsMin.ecma}`;
170
+ opts.esOpts.outdir = opts.distCli;
171
+ };
172
+
173
+ /** Default build pipeline */
174
+ async function run() {
175
+ if(!opts) setOpts();
176
+
177
+ if(!Dev) {
178
+ log(C.bgYellow('Clean'));
179
+ await rm(opts.dist);
180
+
181
+ if(opts.srcSrv) {
182
+ log(C.bgYellow('Build Server'));
183
+ await exec(`npx tsc -p ${opts.srcSrv}`);
184
+ }
185
+ }
186
+
187
+ await mkdir(opts.srcCli);
188
+
189
+ log(C.bgYellow('Build'));
190
+ if(esbuild && opts.esbuild) {
191
+ await recurse(addHTML, opts.srcCli);
192
+ if(opts.esOpts.plugins) opts.esOpts.plugins = [];
193
+ else if(_hPl) opts.esOpts.plugins!.remove(_hPl);
194
+ _hPl = pHTML!.htmlPlugin({files: _hFD});
195
+ opts.esOpts.plugins!.push(_hPl);
196
+ const ctx = await esbuild.context(opts.esOpts);
197
+
198
+ if(Watch) {
199
+ await ctx.watch();
200
+ log('Watching for changes...');
201
+ } else {
202
+ const build = await ctx.rebuild();
203
+ if(Meta) await fs.writeFile('meta.json', JSON.stringify(build.metafile));
204
+ _minify();
205
+ await ctx.dispose();
206
+
207
+ await opts.onPostBuild?.(ctx);
208
+ log(C.green('Done!'));
209
+ }
210
+ } else {
211
+ await exec('npx tsc');
212
+ _minify();
213
+
214
+ await opts.onPostBuild?.();
215
+ log(C.green('Done!'));
216
+ }
217
+ };
218
+
219
+ //==== Support ====
220
+
221
+ const _minify = async () => {
222
+ await opts.onPreMin?.();
223
+
224
+ if(!Dev) {
225
+ log(C.bgYellow('Minify'));
226
+ await recurse(minify, opts.dist);
227
+ }
228
+ };
229
+
230
+ /** Recursively create the directory, if it doesn't exist */
231
+ async function mkdir(path: string) {
232
+ try {await fs.mkdir(path, {recursive: true})} catch(e) {
233
+ if((e as any).code !== 'EEXIST') throw e;
234
+ }
235
+ }
236
+
237
+ /** Recursively remove the file or directory, if it exists */
238
+ async function rm(path: string) {
239
+ try {await fs.rm(path, {recursive: true})} catch(e) {
240
+ if((e as any).code !== 'ENOENT') throw e;
241
+ }
242
+ }
243
+
244
+ /** Run the shell command, mirroring output to console */
245
+ function exec(cmd: string) {
246
+ return new Promise((res, rej) => {
247
+ const p = spawn(cmd, {shell: true, stdio: 'inherit'});
248
+ p.on('exit', c => c ? rej(`Exit Code ${c}`) : res());
249
+ p.on('error', rej);
250
+ }) as Promise<void>;
251
+ }
252
+
253
+ /** Recurse over all files in a directory */
254
+ async function recurse(func: (pin: string, pout: string | null, filename: string)
255
+ => void, pin: string, pout?: string | null) {
256
+ const pl = [], d = await fs.readdir(pin, {withFileTypes: true});
257
+ if(pout) await mkdir(pout);
258
+ for(const f of d) {
259
+ if(f.isFile()) pl.push(func(pin, pout!, f.name));
260
+ else if(f.isDirectory()) pl.push(recurse(func,
261
+ path.join(pin, f.name), pout && path.join(pout, f.name)));
262
+ }
263
+ await Promise.all(pl);
264
+ }
265
+
266
+ export default {
267
+ /** Default build options */
268
+ defaults,
269
+ /** Current options (read-only) */
270
+ get opts() {return opts},
271
+ setOpts,
272
+ run,
273
+ mkdir,
274
+ rm,
275
+ exec,
276
+ recurse,
277
+ Watch,
278
+ Dev,
279
+ Meta
280
+ };
package/src/utils-dom.ts CHANGED
@@ -390,7 +390,13 @@ export async function download(data: string | URL | Blob | ArrayBuffer, name?: s
390
390
  URL.revokeObjectURL(u);
391
391
  }
392
392
  }
393
+
394
+ /** Import modules only in Node.js, otherwise return empty list */
395
+ export const importNode = async (..._: string[]) => [] as any[];
393
396
  }
394
397
 
398
+ //Export types
399
+ export type UtilRect = InstanceType<typeof ext.UtilRect>;
400
+
395
401
  export const utils = <typeof U & typeof ext>U;
396
402
  export default utils;
package/src/utils-node.ts CHANGED
@@ -5,6 +5,9 @@ import { utils as U } from './utils.js';
5
5
  export type * from './utils.js';
6
6
 
7
7
  namespace ext {
8
+ /** Import modules only in Node.js, otherwise return empty list */
9
+ export const importNode = async (...mods: string[]) => Promise.all(mods.map(i => import(i)));
10
+
8
11
  /** Get list of system IPs */
9
12
  export function getIPs() {
10
13
  const ip: string[]=[], fl=os.networkInterfaces();
package/src/utils.ts CHANGED
@@ -68,6 +68,36 @@ export interface Uint8Array {
68
68
  export interface AnyMap {[k: string]: any};
69
69
  export interface StringMap {[k: string]: string};
70
70
  export interface QueryMap {[k: string]: true | string | string[]};
71
+ export type Ease = (t: number) => number;
72
+
73
+ export interface UserAgentInfo {
74
+ os?: string;
75
+ rawOS?: string;
76
+ type?: string;
77
+ version?: string;
78
+ browser?: string;
79
+ engine?: string;
80
+ mobile?: boolean;
81
+ }
82
+
83
+ export interface DateFormatOpts {
84
+ /** Include seconds */
85
+ sec?: boolean;
86
+ /** True or `3` to include milliseconds (requires sec), `2` or `1` to limit precision */
87
+ ms?: boolean | number;
88
+ /** Use 24-hour time */
89
+ h24?: boolean;
90
+ /** Show time only, false to show date only, null to show both */
91
+ time?: boolean;
92
+ /** Add date suffix (1st, 2nd, etc.), default true */
93
+ suf?: boolean;
94
+ /** Show year (default true), or a number to show year only if it differs from given year */
95
+ year?: boolean | number;
96
+ /** Put date first instead of time */
97
+ df?: boolean;
98
+ /** Date & time separator; defaults to `' '` */
99
+ sep?: string;
100
+ }
71
101
 
72
102
  //-------------------------------------------- Extensions --------------------------------------------
73
103
 
@@ -75,14 +105,11 @@ export namespace utils {
75
105
  const [document, HTMLCollection] = P;
76
106
 
77
107
  /** Current library version */
78
- export const VER = "v9.1.0";
108
+ export const VER = "v9.1.1";
79
109
 
80
110
  /** Whether the environment is Node.js or Browser */
81
111
  export const isNode = IsNode;
82
112
 
83
- /** Import modules only in Node.js, otherwise return empty list */
84
- export const importNode = async (...mods: string[]) => (IsNode ? Promise.all(mods.map(i => import(i))) : []) as any[];
85
-
86
113
  //==== Objects ====
87
114
 
88
115
  /** Add getter and/or setter for `name` to `obj` */
@@ -300,35 +327,35 @@ export function formatCost(num: number, sym='$') {
300
327
 
301
328
  //JavaScript Easing Library by: https://github.com/gre & https://gizma.com/easing
302
329
  //t should be between 0 and 1
303
- export type Ease = (t: number) => number;
304
- export const Easing: {[k: string]: Ease} = {
305
- //no easing, no acceleration
306
- linear:t => t,
307
- //accelerating from zero velocity
308
- easeInQuad:t => t*t,
309
- //decelerating to zero velocity
310
- easeOutQuad:t => t*(2-t),
311
- //acceleration until halfway, then deceleration
312
- easeInOutQuad:t => t<.5 ? 2*t*t : -1+(4-2*t)*t,
313
- //accelerating from zero velocity
314
- easeInCubic:t => t*t*t,
315
- //decelerating to zero velocity
316
- easeOutCubic:t => (--t)*t*t+1,
317
- //acceleration until halfway, then deceleration
318
- easeInOutCubic:t => t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1,
319
- //accelerating from zero velocity
320
- easeInQuart:t => t*t*t*t,
321
- //decelerating to zero velocity
322
- easeOutQuart:t => 1-(--t)*t*t*t,
323
- //acceleration until halfway, then deceleration
324
- easeInOutQuart:t => t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t,
325
- //accelerating from zero velocity
326
- easeInQuint:t => t*t*t*t*t,
327
- //decelerating to zero velocity
328
- easeOutQuint:t => 1+(--t)*t*t*t*t,
329
- //acceleration until halfway, then deceleration
330
- easeInOutQuint:t => t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t
331
- }
330
+ const _ease = {
331
+ /** No easing, no acceleration */
332
+ linear:(t: number) => t,
333
+ /** Accelerating from zero velocity */
334
+ easeInQuad:(t: number) => t*t,
335
+ /** Decelerating to zero velocity */
336
+ easeOutQuad:(t: number) => t*(2-t),
337
+ /** Acceleration until halfway, then deceleration */
338
+ easeInOutQuad:(t: number) => t<.5 ? 2*t*t : -1+(4-2*t)*t,
339
+ /** Accelerating from zero velocity */
340
+ easeInCubic:(t: number) => t*t*t,
341
+ /** Decelerating to zero velocity */
342
+ easeOutCubic:(t: number) => (--t)*t*t+1,
343
+ /** Acceleration until halfway, then deceleration */
344
+ easeInOutCubic:(t: number) => t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1,
345
+ /** Accelerating from zero velocity */
346
+ easeInQuart:(t: number) => t*t*t*t,
347
+ /** Decelerating to zero velocity */
348
+ easeOutQuart:(t: number) => 1-(--t)*t*t*t,
349
+ /** Acceleration until halfway, then deceleration */
350
+ easeInOutQuart:(t: number) => t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t,
351
+ /** Accelerating from zero velocity */
352
+ easeInQuint:(t: number) => t*t*t*t*t,
353
+ /** Decelerating to zero velocity */
354
+ easeOutQuint:(t: number) => 1+(--t)*t*t*t*t,
355
+ /** Acceleration until halfway, then deceleration */
356
+ easeInOutQuint:(t: number) => t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t
357
+ };
358
+ export const Easing = _ease as {[k in keyof typeof _ease]: Ease};
332
359
 
333
360
  //==== Polyfills ====
334
361
 
@@ -419,16 +446,6 @@ export const delay = (ms: number): Promise<void> => new Promise(r => setTimeout(
419
446
 
420
447
  //-------------------------------------------- DOM Model --------------------------------------------
421
448
 
422
- export interface UserAgentInfo {
423
- os?: string;
424
- rawOS?: string;
425
- type?: string;
426
- version?: string;
427
- browser?: string;
428
- engine?: string;
429
- mobile?: boolean;
430
- }
431
-
432
449
  /** UserAgent-based Mobile device detection
433
450
  @param ua User Agent string; defaults to navigator.userAgent */
434
451
  export function deviceInfo(ua?: string) {
@@ -559,25 +576,6 @@ export function toQuery(data: QueryMap, sep='&') {
559
576
  export const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
560
577
  function fixed2(n: number) {return n<=9?'0'+n:n}
561
578
 
562
- export interface DateFormatOpts {
563
- /** Include seconds */
564
- sec?: boolean;
565
- /** True or `3` to include milliseconds (requires sec), `2` or `1` to limit precision */
566
- ms?: boolean | number;
567
- /** Use 24-hour time */
568
- h24?: boolean;
569
- /** Show time only, false to show date only, null to show both */
570
- time?: boolean;
571
- /** Add date suffix (1st, 2nd, etc.), default true */
572
- suf?: boolean;
573
- /** Show year (default true), or a number to show year only if it differs from given year */
574
- year?: boolean | number;
575
- /** Put date first instead of time */
576
- df?: boolean;
577
- /** Date & time separator; defaults to `' '` */
578
- sep?: string;
579
- }
580
-
581
579
  /** Format Date object into human-readable string */
582
580
  export function formatDate(d?: Date, opt: DateFormatOpts={}) {
583
581
  let t='', yy: number, dd: any;