@php-wasm/progress 0.1.2 → 0.1.3
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/index.cjs +1 -0
- package/index.js +164 -0
- package/lib/emscripten-download-monitor.d.ts +51 -0
- package/lib/progress-observer.d.ts +27 -0
- package/package.json +32 -27
- package/.eslintrc.json +0 -18
- package/README.md +0 -11
- package/project.json +0 -56
- package/src/lib/emscripten-download-monitor.ts +0 -242
- package/src/lib/progress-observer.ts +0 -65
- package/tsconfig.json +0 -22
- package/tsconfig.lib.json +0 -10
- package/tsconfig.spec.json +0 -19
- package/vite.config.ts +0 -58
- /package/{src/index.ts → index.d.ts} +0 -0
package/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=5*1024*1024;class g extends EventTarget{#e={};#t={};constructor(t=[]){super(),this.setModules(t),this.#s()}getEmscriptenArgs(){return{dataFileDownloads:this.#r()}}setModules(t){this.#e=t.reduce((e,n)=>{if(n.dependenciesTotalSize>0){const s="http://example.com/",o=new URL(n.dependencyFilename,s).pathname.split("/").pop();e[o]=Math.max(o in e?e[o]:0,n.dependenciesTotalSize)}return e},{}),this.#t=Object.fromEntries(Object.entries(this.#e).map(([e])=>[e,0]))}#s(){const t=WebAssembly.instantiateStreaming;WebAssembly.instantiateStreaming=async(e,...n)=>{const s=await e,r=s.url.substring(new URL(s.url).origin.length+1),o=u(s,({detail:{loaded:i,total:l}})=>this.#n(r,i,l));return t(o,...n)}}#r(){const t=this,e={};return new Proxy(e,{set(n,s,r){return t.#n(s,r.loaded,r.total),n[s]=new Proxy(JSON.parse(JSON.stringify(r)),{set(o,i,l){return o[i]=l,t.#n(s,o.loaded,o.total),!0}}),!0}})}#n(t,e,n){const s=new URL(t,"http://example.com").pathname.split("/").pop();n||(n=this.#e[s]),s in this.#t||console.warn(`Registered a download #progress of an unregistered file "${s}". This may cause a sudden **decrease** in the #progress percentage as the total number of bytes increases during the download.`),this.#t[t]=e,this.dispatchEvent(new CustomEvent("progress",{detail:{loaded:c(this.#t),total:c(this.#e)}}))}}function c(a){return Object.values(a).reduce((t,e)=>t+e,0)}function u(a,t){const e=a.headers.get("content-length")||"",n=parseInt(e,10)||h;function s(r,o){t(new CustomEvent("progress",{detail:{loaded:r,total:o}}))}return new Response(new ReadableStream({async start(r){if(!a.body){r.close();return}const o=a.body.getReader();let i=0;for(;;)try{const{done:l,value:d}=await o.read();if(d&&(i+=d.byteLength),l){s(i,i),r.close();break}else s(i,n),r.enqueue(d)}catch(l){console.error({e:l}),r.error(l);break}}}),{status:a.status,statusText:a.statusText,headers:a.headers})}class p extends EventTarget{constructor(){super(...arguments),this.#e={},this.#t=0,this.progress=0,this.mode="REAL_TIME",this.caption=""}#e;#t;partialObserver(t,e=""){const n=++this.#t;return this.#e[n]=0,s=>{const{loaded:r,total:o}=s?.detail||{};this.#e[n]=r/o*t,this.#s(this.totalProgress,"REAL_TIME",e)}}slowlyIncrementBy(t){const e=++this.#t;this.#e[e]=t,this.#s(this.totalProgress,"SLOWLY_INCREMENT")}get totalProgress(){return Object.values(this.#e).reduce((t,e)=>t+e,0)}#s(t,e,n){this.dispatchEvent(new CustomEvent("progress",{detail:{progress:t,mode:e,caption:n}}))}}exports.EmscriptenDownloadMonitor=g;exports.ProgressObserver=p;exports.cloneResponseMonitorProgress=u;
|
package/index.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
class u extends EventTarget {
|
|
2
|
+
#e = {};
|
|
3
|
+
#t = {};
|
|
4
|
+
constructor(t = []) {
|
|
5
|
+
super(), this.setModules(t), this.#s();
|
|
6
|
+
}
|
|
7
|
+
getEmscriptenArgs() {
|
|
8
|
+
return {
|
|
9
|
+
dataFileDownloads: this.#r()
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
setModules(t) {
|
|
13
|
+
this.#e = t.reduce((e, n) => {
|
|
14
|
+
if (n.dependenciesTotalSize > 0) {
|
|
15
|
+
const s = "http://example.com/", a = new URL(n.dependencyFilename, s).pathname.split("/").pop();
|
|
16
|
+
e[a] = Math.max(
|
|
17
|
+
a in e ? e[a] : 0,
|
|
18
|
+
n.dependenciesTotalSize
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return e;
|
|
22
|
+
}, {}), this.#t = Object.fromEntries(
|
|
23
|
+
Object.entries(this.#e).map(([e]) => [e, 0])
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Replaces the default WebAssembly.instantiateStreaming with a version
|
|
28
|
+
* that monitors the download #progress.
|
|
29
|
+
*/
|
|
30
|
+
#s() {
|
|
31
|
+
const t = WebAssembly.instantiateStreaming;
|
|
32
|
+
WebAssembly.instantiateStreaming = async (e, ...n) => {
|
|
33
|
+
const s = await e, r = s.url.substring(
|
|
34
|
+
new URL(s.url).origin.length + 1
|
|
35
|
+
), a = h(
|
|
36
|
+
s,
|
|
37
|
+
({ detail: { loaded: i, total: l } }) => this.#n(r, i, l)
|
|
38
|
+
);
|
|
39
|
+
return t(a, ...n);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a `dataFileDownloads` Proxy object that can be passed
|
|
44
|
+
* to `startPHP` to monitor the download #progress of the data
|
|
45
|
+
* dependencies.
|
|
46
|
+
*/
|
|
47
|
+
#r() {
|
|
48
|
+
const t = this, e = {};
|
|
49
|
+
return new Proxy(e, {
|
|
50
|
+
set(n, s, r) {
|
|
51
|
+
return t.#n(s, r.loaded, r.total), n[s] = new Proxy(JSON.parse(JSON.stringify(r)), {
|
|
52
|
+
set(a, i, l) {
|
|
53
|
+
return a[i] = l, t.#n(s, a.loaded, a.total), !0;
|
|
54
|
+
}
|
|
55
|
+
}), !0;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Notifies about the download #progress of a file.
|
|
61
|
+
*
|
|
62
|
+
* @param file The file name.
|
|
63
|
+
* @param loaded The number of bytes of that file loaded so far.
|
|
64
|
+
* @param fileSize The total number of bytes in the loaded file.
|
|
65
|
+
*/
|
|
66
|
+
#n(t, e, n) {
|
|
67
|
+
const s = new URL(t, "http://example.com").pathname.split("/").pop();
|
|
68
|
+
n || (n = this.#e[s]), s in this.#t || console.warn(
|
|
69
|
+
`Registered a download #progress of an unregistered file "${s}". This may cause a sudden **decrease** in the #progress percentage as the total number of bytes increases during the download.`
|
|
70
|
+
), this.#t[t] = e, this.dispatchEvent(
|
|
71
|
+
new CustomEvent("progress", {
|
|
72
|
+
detail: {
|
|
73
|
+
loaded: c(this.#t),
|
|
74
|
+
total: c(this.#e)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function c(o) {
|
|
81
|
+
return Object.values(o).reduce((t, e) => t + e, 0);
|
|
82
|
+
}
|
|
83
|
+
function h(o, t) {
|
|
84
|
+
const e = o.headers.get("content-length") || "", n = parseInt(e, 10) || 5242880;
|
|
85
|
+
function s(r, a) {
|
|
86
|
+
t(
|
|
87
|
+
new CustomEvent("progress", {
|
|
88
|
+
detail: {
|
|
89
|
+
loaded: r,
|
|
90
|
+
total: a
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return new Response(
|
|
96
|
+
new ReadableStream({
|
|
97
|
+
async start(r) {
|
|
98
|
+
if (!o.body) {
|
|
99
|
+
r.close();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const a = o.body.getReader();
|
|
103
|
+
let i = 0;
|
|
104
|
+
for (; ; )
|
|
105
|
+
try {
|
|
106
|
+
const { done: l, value: d } = await a.read();
|
|
107
|
+
if (d && (i += d.byteLength), l) {
|
|
108
|
+
s(i, i), r.close();
|
|
109
|
+
break;
|
|
110
|
+
} else
|
|
111
|
+
s(i, n), r.enqueue(d);
|
|
112
|
+
} catch (l) {
|
|
113
|
+
console.error({ e: l }), r.error(l);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
|
+
{
|
|
119
|
+
status: o.status,
|
|
120
|
+
statusText: o.statusText,
|
|
121
|
+
headers: o.headers
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
class g extends EventTarget {
|
|
126
|
+
constructor() {
|
|
127
|
+
super(...arguments), this.#e = {}, this.#t = 0, this.progress = 0, this.mode = "REAL_TIME", this.caption = "";
|
|
128
|
+
}
|
|
129
|
+
#e;
|
|
130
|
+
#t;
|
|
131
|
+
partialObserver(t, e = "") {
|
|
132
|
+
const n = ++this.#t;
|
|
133
|
+
return this.#e[n] = 0, (s) => {
|
|
134
|
+
const { loaded: r, total: a } = s?.detail || {};
|
|
135
|
+
this.#e[n] = r / a * t, this.#s(this.totalProgress, "REAL_TIME", e);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
slowlyIncrementBy(t) {
|
|
139
|
+
const e = ++this.#t;
|
|
140
|
+
this.#e[e] = t, this.#s(this.totalProgress, "SLOWLY_INCREMENT");
|
|
141
|
+
}
|
|
142
|
+
get totalProgress() {
|
|
143
|
+
return Object.values(this.#e).reduce(
|
|
144
|
+
(t, e) => t + e,
|
|
145
|
+
0
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
#s(t, e, n) {
|
|
149
|
+
this.dispatchEvent(
|
|
150
|
+
new CustomEvent("progress", {
|
|
151
|
+
detail: {
|
|
152
|
+
progress: t,
|
|
153
|
+
mode: e,
|
|
154
|
+
caption: n
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
u as EmscriptenDownloadMonitor,
|
|
162
|
+
g as ProgressObserver,
|
|
163
|
+
h as cloneResponseMonitorProgress
|
|
164
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface MonitoredModule {
|
|
2
|
+
dependencyFilename: string;
|
|
3
|
+
dependenciesTotalSize: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Monitors the download #progress of Emscripten modules
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
*
|
|
10
|
+
* ```js
|
|
11
|
+
* const downloadMonitor = new EmscriptenDownloadMonitor();
|
|
12
|
+
* const php = await startPHP(
|
|
13
|
+
* phpLoaderModule,
|
|
14
|
+
* 'web',
|
|
15
|
+
* downloadMonitor.phpArgs
|
|
16
|
+
* );
|
|
17
|
+
* downloadMonitor.addEventListener('#progress', (e) => {
|
|
18
|
+
* console.log( e.detail.#progress);
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare class EmscriptenDownloadMonitor extends EventTarget {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(modules?: MonitoredModule[]);
|
|
25
|
+
getEmscriptenArgs(): {
|
|
26
|
+
dataFileDownloads: Record<string, any>;
|
|
27
|
+
};
|
|
28
|
+
setModules(modules: MonitoredModule[]): void;
|
|
29
|
+
}
|
|
30
|
+
export default EmscriptenDownloadMonitor;
|
|
31
|
+
export interface DownloadProgress {
|
|
32
|
+
/**
|
|
33
|
+
* The number of bytes loaded so far.
|
|
34
|
+
*/
|
|
35
|
+
loaded: number;
|
|
36
|
+
/**
|
|
37
|
+
* The total number of bytes to load.
|
|
38
|
+
*/
|
|
39
|
+
total: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Clones a fetch Response object and returns a version
|
|
43
|
+
* that calls the `onProgress` callback as the #progress
|
|
44
|
+
* changes.
|
|
45
|
+
*
|
|
46
|
+
* @param response The fetch Response object to clone.
|
|
47
|
+
* @param onProgress The callback to call when the download #progress changes.
|
|
48
|
+
* @returns The cloned response
|
|
49
|
+
*/
|
|
50
|
+
export declare function cloneResponseMonitorProgress(response: Response, onProgress: (event: CustomEvent<DownloadProgress>) => void): Response;
|
|
51
|
+
export type DownloadProgressCallback = (progress: DownloadProgress) => void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DownloadProgress } from './emscripten-download-monitor';
|
|
2
|
+
export type ProgressMode =
|
|
3
|
+
/**
|
|
4
|
+
* Real-time progress is used when we get real-time reports
|
|
5
|
+
* about the progress.
|
|
6
|
+
*/
|
|
7
|
+
'REAL_TIME'
|
|
8
|
+
/**
|
|
9
|
+
* Slowly increment progress is used when we don't know how long
|
|
10
|
+
* an operation will take and just want to keep slowly incrementing
|
|
11
|
+
* the progress bar.
|
|
12
|
+
*/
|
|
13
|
+
| 'SLOWLY_INCREMENT';
|
|
14
|
+
export type ProgressObserverEvent = {
|
|
15
|
+
progress: number;
|
|
16
|
+
mode: ProgressMode;
|
|
17
|
+
caption: string;
|
|
18
|
+
};
|
|
19
|
+
export declare class ProgressObserver extends EventTarget {
|
|
20
|
+
#private;
|
|
21
|
+
progress: number;
|
|
22
|
+
mode: ProgressMode;
|
|
23
|
+
caption: string;
|
|
24
|
+
partialObserver(progressBudget: number, caption?: string): (progress: CustomEvent<DownloadProgress>) => void;
|
|
25
|
+
slowlyIncrementBy(progress: number): void;
|
|
26
|
+
get totalProgress(): number;
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
2
|
+
"name": "@php-wasm/progress",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "PHP.wasm – loading progress monitoring",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/WordPress/wordpress-playground"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://developer.wordpress.org/playground",
|
|
10
|
+
"author": "The WordPress contributors",
|
|
11
|
+
"contributors": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Adam Zielinski",
|
|
14
|
+
"email": "adam@adamziel.com",
|
|
15
|
+
"url": "https://github.com/adamziel"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"typedoc": {
|
|
19
|
+
"entryPoint": "./src/index.ts",
|
|
20
|
+
"readmeFile": "./README.md",
|
|
21
|
+
"displayName": "@php-wasm/progress",
|
|
22
|
+
"tsconfig": "./tsconfig.lib.json"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"directory": "../../../dist/packages/php-wasm/progress"
|
|
27
|
+
},
|
|
28
|
+
"license": "GPL-2.0-or-later",
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "index.js",
|
|
31
|
+
"types": "index.d.ts",
|
|
32
|
+
"gitHead": "ac5bf6b09cb425d650bc57eff03eac6417cccd49",
|
|
33
|
+
"dependencies": {}
|
|
29
34
|
}
|
package/.eslintrc.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": ["../../../.eslintrc.json"],
|
|
3
|
-
"ignorePatterns": ["!**/*"],
|
|
4
|
-
"overrides": [
|
|
5
|
-
{
|
|
6
|
-
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
7
|
-
"rules": {}
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"files": ["*.ts", "*.tsx"],
|
|
11
|
-
"rules": {}
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"files": ["*.js", "*.jsx"],
|
|
15
|
-
"rules": {}
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
package/README.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# php-wasm-progress
|
|
2
|
-
|
|
3
|
-
PHP.wasm – loading progress monitoring utilities.
|
|
4
|
-
|
|
5
|
-
## Building
|
|
6
|
-
|
|
7
|
-
Run `nx build php-wasm-progress` to build the library.
|
|
8
|
-
|
|
9
|
-
## Running unit tests
|
|
10
|
-
|
|
11
|
-
Run `nx test php-wasm-progress` to execute the unit tests via [Jest](https://jestjs.io).
|
package/project.json
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "php-wasm-progress",
|
|
3
|
-
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"sourceRoot": "packages/php-wasm/progress/src",
|
|
5
|
-
"projectType": "library",
|
|
6
|
-
"targets": {
|
|
7
|
-
"build": {
|
|
8
|
-
"executor": "@wp-playground/nx-extensions:package-json",
|
|
9
|
-
"options": {
|
|
10
|
-
"tsConfig": "packages/php-wasm/progress/tsconfig.lib.json",
|
|
11
|
-
"outputPath": "dist/packages/php-wasm/progress",
|
|
12
|
-
"buildTarget": "php-wasm-progress:build:bundle:production"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"build:bundle": {
|
|
16
|
-
"executor": "@nrwl/vite:build",
|
|
17
|
-
"outputs": ["{options.outputPath}"],
|
|
18
|
-
"options": {
|
|
19
|
-
"outputPath": "dist/packages/php-wasm/progress"
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"publish": {
|
|
23
|
-
"executor": "nx:run-commands",
|
|
24
|
-
"options": {
|
|
25
|
-
"command": "node tools/scripts/publish.mjs php-wasm-progress {args.ver} {args.tag}",
|
|
26
|
-
"parallel": false
|
|
27
|
-
},
|
|
28
|
-
"dependsOn": ["build"]
|
|
29
|
-
},
|
|
30
|
-
"test": {
|
|
31
|
-
"executor": "@nrwl/vite:test",
|
|
32
|
-
"outputs": ["coverage/packages/php-wasm/progress"],
|
|
33
|
-
"options": {
|
|
34
|
-
"passWithNoTests": true,
|
|
35
|
-
"reportsDirectory": "../../../coverage/packages/php-wasm/progress"
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
"lint": {
|
|
39
|
-
"executor": "@nrwl/linter:eslint",
|
|
40
|
-
"outputs": ["{options.outputFile}"],
|
|
41
|
-
"options": {
|
|
42
|
-
"lintFilePatterns": ["packages/php-wasm/progress/**/*.ts"]
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
"typecheck": {
|
|
46
|
-
"executor": "@nrwl/workspace:run-commands",
|
|
47
|
-
"options": {
|
|
48
|
-
"commands": [
|
|
49
|
-
"yarn tsc -p packages/php-wasm/progress/tsconfig.lib.json --noEmit",
|
|
50
|
-
"yarn tsc -p packages/php-wasm/progress/tsconfig.spec.json --noEmit"
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"tags": []
|
|
56
|
-
}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* An approximate total file size to use when the actual
|
|
3
|
-
* total number of bytes is missing.
|
|
4
|
-
*
|
|
5
|
-
* This may happen when the files are compressed before transmission
|
|
6
|
-
* and no content-length header is being sent.
|
|
7
|
-
*
|
|
8
|
-
* The approximation isn't accurate, but it's better than nothing.
|
|
9
|
-
* It's not about being exact but about giving the user a rough sense
|
|
10
|
-
* of #progress.
|
|
11
|
-
*/
|
|
12
|
-
const FALLBACK_FILE_SIZE = 5 * 1024 * 1024;
|
|
13
|
-
|
|
14
|
-
export interface MonitoredModule {
|
|
15
|
-
dependencyFilename: string;
|
|
16
|
-
dependenciesTotalSize: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Monitors the download #progress of Emscripten modules
|
|
21
|
-
*
|
|
22
|
-
* Usage:
|
|
23
|
-
*
|
|
24
|
-
* ```js
|
|
25
|
-
* const downloadMonitor = new EmscriptenDownloadMonitor();
|
|
26
|
-
* const php = await startPHP(
|
|
27
|
-
* phpLoaderModule,
|
|
28
|
-
* 'web',
|
|
29
|
-
* downloadMonitor.phpArgs
|
|
30
|
-
* );
|
|
31
|
-
* downloadMonitor.addEventListener('#progress', (e) => {
|
|
32
|
-
* console.log( e.detail.#progress);
|
|
33
|
-
* })
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export class EmscriptenDownloadMonitor extends EventTarget {
|
|
37
|
-
#assetsSizes: Record<string, number> = {};
|
|
38
|
-
#progress: Record<string, number> = {};
|
|
39
|
-
|
|
40
|
-
constructor(modules: MonitoredModule[] = []) {
|
|
41
|
-
super();
|
|
42
|
-
|
|
43
|
-
this.setModules(modules);
|
|
44
|
-
this.#monitorWebAssemblyStreaming();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
getEmscriptenArgs() {
|
|
48
|
-
return {
|
|
49
|
-
dataFileDownloads: this.#createDataFileDownloadsProxy(),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
setModules(modules: MonitoredModule[]) {
|
|
54
|
-
this.#assetsSizes = modules.reduce((acc, module) => {
|
|
55
|
-
if (module.dependenciesTotalSize > 0) {
|
|
56
|
-
// Required to create a valid URL object
|
|
57
|
-
const dummyBaseUrl = 'http://example.com/';
|
|
58
|
-
const url = new URL(module.dependencyFilename, dummyBaseUrl)
|
|
59
|
-
.pathname;
|
|
60
|
-
const filename = url.split('/').pop()!;
|
|
61
|
-
acc[filename] = Math.max(
|
|
62
|
-
filename in acc ? acc[filename] : 0,
|
|
63
|
-
module.dependenciesTotalSize
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
return acc;
|
|
67
|
-
}, {} as Record<string, number>);
|
|
68
|
-
this.#progress = Object.fromEntries(
|
|
69
|
-
Object.entries(this.#assetsSizes).map(([name]) => [name, 0])
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Replaces the default WebAssembly.instantiateStreaming with a version
|
|
75
|
-
* that monitors the download #progress.
|
|
76
|
-
*/
|
|
77
|
-
#monitorWebAssemblyStreaming() {
|
|
78
|
-
const instantiateStreaming = WebAssembly.instantiateStreaming;
|
|
79
|
-
WebAssembly.instantiateStreaming = async (
|
|
80
|
-
responseOrPromise,
|
|
81
|
-
...args
|
|
82
|
-
) => {
|
|
83
|
-
const response = await responseOrPromise;
|
|
84
|
-
const file = response.url.substring(
|
|
85
|
-
new URL(response.url).origin.length + 1
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const reportingResponse = cloneResponseMonitorProgress(
|
|
89
|
-
response,
|
|
90
|
-
({ detail: { loaded, total } }) =>
|
|
91
|
-
this.#notify(file, loaded, total)
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
return instantiateStreaming(reportingResponse, ...args);
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Creates a `dataFileDownloads` Proxy object that can be passed
|
|
100
|
-
* to `startPHP` to monitor the download #progress of the data
|
|
101
|
-
* dependencies.
|
|
102
|
-
*/
|
|
103
|
-
#createDataFileDownloadsProxy() {
|
|
104
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
105
|
-
const self = this;
|
|
106
|
-
const dataFileDownloads: Record<string, any> = {};
|
|
107
|
-
// Monitor assignments like dataFileDownloads[file] = #progress
|
|
108
|
-
return new Proxy(dataFileDownloads, {
|
|
109
|
-
set(obj, file: string, progress) {
|
|
110
|
-
self.#notify(file, progress.loaded, progress.total);
|
|
111
|
-
|
|
112
|
-
// Monitor assignments like dataFileDownloads[file].total += delta
|
|
113
|
-
obj[file] = new Proxy(JSON.parse(JSON.stringify(progress)), {
|
|
114
|
-
set(nestedObj, prop, value) {
|
|
115
|
-
nestedObj[prop] = value;
|
|
116
|
-
self.#notify(file, nestedObj.loaded, nestedObj.total);
|
|
117
|
-
return true;
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
return true;
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Notifies about the download #progress of a file.
|
|
127
|
-
*
|
|
128
|
-
* @param file The file name.
|
|
129
|
-
* @param loaded The number of bytes of that file loaded so far.
|
|
130
|
-
* @param fileSize The total number of bytes in the loaded file.
|
|
131
|
-
*/
|
|
132
|
-
#notify(file: string, loaded: number, fileSize: number) {
|
|
133
|
-
const fileName = new URL(file, 'http://example.com').pathname
|
|
134
|
-
.split('/')
|
|
135
|
-
.pop()!;
|
|
136
|
-
if (!fileSize) {
|
|
137
|
-
fileSize = this.#assetsSizes[fileName];
|
|
138
|
-
}
|
|
139
|
-
if (!(fileName in this.#progress)) {
|
|
140
|
-
console.warn(
|
|
141
|
-
`Registered a download #progress of an unregistered file "${fileName}". ` +
|
|
142
|
-
`This may cause a sudden **decrease** in the #progress percentage as the ` +
|
|
143
|
-
`total number of bytes increases during the download.`
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
this.#progress[file] = loaded;
|
|
148
|
-
this.dispatchEvent(
|
|
149
|
-
new CustomEvent('progress', {
|
|
150
|
-
detail: {
|
|
151
|
-
loaded: sumValues(this.#progress),
|
|
152
|
-
total: sumValues(this.#assetsSizes),
|
|
153
|
-
},
|
|
154
|
-
})
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function sumValues(obj: Record<string, number>) {
|
|
160
|
-
return Object.values(obj).reduce((total, value) => total + value, 0);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export default EmscriptenDownloadMonitor;
|
|
164
|
-
|
|
165
|
-
export interface DownloadProgress {
|
|
166
|
-
/**
|
|
167
|
-
* The number of bytes loaded so far.
|
|
168
|
-
*/
|
|
169
|
-
loaded: number;
|
|
170
|
-
/**
|
|
171
|
-
* The total number of bytes to load.
|
|
172
|
-
*/
|
|
173
|
-
total: number;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Clones a fetch Response object and returns a version
|
|
178
|
-
* that calls the `onProgress` callback as the #progress
|
|
179
|
-
* changes.
|
|
180
|
-
*
|
|
181
|
-
* @param response The fetch Response object to clone.
|
|
182
|
-
* @param onProgress The callback to call when the download #progress changes.
|
|
183
|
-
* @returns The cloned response
|
|
184
|
-
*/
|
|
185
|
-
export function cloneResponseMonitorProgress(
|
|
186
|
-
response: Response,
|
|
187
|
-
onProgress: (event: CustomEvent<DownloadProgress>) => void
|
|
188
|
-
): Response {
|
|
189
|
-
const contentLength = response.headers.get('content-length') || '';
|
|
190
|
-
const total = parseInt(contentLength, 10) || FALLBACK_FILE_SIZE;
|
|
191
|
-
|
|
192
|
-
function notify(loaded: number, total: number) {
|
|
193
|
-
onProgress(
|
|
194
|
-
new CustomEvent('progress', {
|
|
195
|
-
detail: {
|
|
196
|
-
loaded,
|
|
197
|
-
total,
|
|
198
|
-
},
|
|
199
|
-
})
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return new Response(
|
|
204
|
-
new ReadableStream({
|
|
205
|
-
async start(controller) {
|
|
206
|
-
if (!response.body) {
|
|
207
|
-
controller.close();
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
const reader = response.body.getReader();
|
|
211
|
-
let loaded = 0;
|
|
212
|
-
for (;;) {
|
|
213
|
-
try {
|
|
214
|
-
const { done, value } = await reader.read();
|
|
215
|
-
if (value) {
|
|
216
|
-
loaded += value.byteLength;
|
|
217
|
-
}
|
|
218
|
-
if (done) {
|
|
219
|
-
notify(loaded, loaded);
|
|
220
|
-
controller.close();
|
|
221
|
-
break;
|
|
222
|
-
} else {
|
|
223
|
-
notify(loaded, total);
|
|
224
|
-
controller.enqueue(value);
|
|
225
|
-
}
|
|
226
|
-
} catch (e) {
|
|
227
|
-
console.error({ e });
|
|
228
|
-
controller.error(e);
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
}),
|
|
234
|
-
{
|
|
235
|
-
status: response.status,
|
|
236
|
-
statusText: response.statusText,
|
|
237
|
-
headers: response.headers,
|
|
238
|
-
}
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export type DownloadProgressCallback = (progress: DownloadProgress) => void;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { DownloadProgress } from './emscripten-download-monitor';
|
|
2
|
-
|
|
3
|
-
export type ProgressMode =
|
|
4
|
-
/**
|
|
5
|
-
* Real-time progress is used when we get real-time reports
|
|
6
|
-
* about the progress.
|
|
7
|
-
*/
|
|
8
|
-
| 'REAL_TIME'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Slowly increment progress is used when we don't know how long
|
|
12
|
-
* an operation will take and just want to keep slowly incrementing
|
|
13
|
-
* the progress bar.
|
|
14
|
-
*/
|
|
15
|
-
| 'SLOWLY_INCREMENT';
|
|
16
|
-
|
|
17
|
-
export type ProgressObserverEvent = {
|
|
18
|
-
progress: number;
|
|
19
|
-
mode: ProgressMode;
|
|
20
|
-
caption: string;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export class ProgressObserver extends EventTarget {
|
|
24
|
-
#observedProgresses: Record<number, number> = {};
|
|
25
|
-
#lastObserverId = 0;
|
|
26
|
-
|
|
27
|
-
progress = 0;
|
|
28
|
-
mode: ProgressMode = 'REAL_TIME';
|
|
29
|
-
caption = '';
|
|
30
|
-
|
|
31
|
-
partialObserver(progressBudget: number, caption = '') {
|
|
32
|
-
const id = ++this.#lastObserverId;
|
|
33
|
-
this.#observedProgresses[id] = 0;
|
|
34
|
-
return (progress: CustomEvent<DownloadProgress>) => {
|
|
35
|
-
const { loaded, total } = progress?.detail || {};
|
|
36
|
-
this.#observedProgresses[id] = (loaded / total) * progressBudget;
|
|
37
|
-
this.#onProgress(this.totalProgress, 'REAL_TIME', caption);
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
slowlyIncrementBy(progress: number) {
|
|
42
|
-
const id = ++this.#lastObserverId;
|
|
43
|
-
this.#observedProgresses[id] = progress;
|
|
44
|
-
this.#onProgress(this.totalProgress, 'SLOWLY_INCREMENT');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
get totalProgress() {
|
|
48
|
-
return Object.values(this.#observedProgresses).reduce(
|
|
49
|
-
(total, progress) => total + progress,
|
|
50
|
-
0
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
#onProgress(progress: number, mode: ProgressMode, caption?: string) {
|
|
55
|
-
this.dispatchEvent(
|
|
56
|
-
new CustomEvent('progress', {
|
|
57
|
-
detail: {
|
|
58
|
-
progress,
|
|
59
|
-
mode,
|
|
60
|
-
caption,
|
|
61
|
-
},
|
|
62
|
-
})
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"forceConsistentCasingInFileNames": true,
|
|
5
|
-
"strict": true,
|
|
6
|
-
"noImplicitOverride": true,
|
|
7
|
-
"noPropertyAccessFromIndexSignature": true,
|
|
8
|
-
"noImplicitReturns": true,
|
|
9
|
-
"noFallthroughCasesInSwitch": true,
|
|
10
|
-
"types": ["vitest"]
|
|
11
|
-
},
|
|
12
|
-
"files": [],
|
|
13
|
-
"include": [],
|
|
14
|
-
"references": [
|
|
15
|
-
{
|
|
16
|
-
"path": "./tsconfig.lib.json"
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"path": "./tsconfig.spec.json"
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
package/tsconfig.lib.json
DELETED
package/tsconfig.spec.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "../../dist/out-tsc",
|
|
5
|
-
"types": ["vitest/globals", "vitest/importMeta", "vite/client", "node"]
|
|
6
|
-
},
|
|
7
|
-
"include": [
|
|
8
|
-
"vite.config.ts",
|
|
9
|
-
"src/**/*.test.ts",
|
|
10
|
-
"src/**/*.spec.ts",
|
|
11
|
-
"src/**/*.test.tsx",
|
|
12
|
-
"src/**/*.spec.tsx",
|
|
13
|
-
"src/**/*.test.js",
|
|
14
|
-
"src/**/*.spec.js",
|
|
15
|
-
"src/**/*.test.jsx",
|
|
16
|
-
"src/**/*.spec.jsx",
|
|
17
|
-
"src/**/*.d.ts"
|
|
18
|
-
]
|
|
19
|
-
}
|
package/vite.config.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/// <reference types="vitest" />
|
|
2
|
-
import { defineConfig } from 'vite';
|
|
3
|
-
|
|
4
|
-
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
|
5
|
-
import dts from 'vite-plugin-dts';
|
|
6
|
-
import { join } from 'path';
|
|
7
|
-
|
|
8
|
-
export default defineConfig({
|
|
9
|
-
cacheDir: '../../../node_modules/.vite/php-wasm-progress',
|
|
10
|
-
|
|
11
|
-
plugins: [
|
|
12
|
-
dts({
|
|
13
|
-
entryRoot: 'src',
|
|
14
|
-
tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'),
|
|
15
|
-
skipDiagnostics: true,
|
|
16
|
-
}),
|
|
17
|
-
|
|
18
|
-
viteTsConfigPaths({
|
|
19
|
-
root: '../../../',
|
|
20
|
-
}),
|
|
21
|
-
],
|
|
22
|
-
|
|
23
|
-
// Uncomment this if you are using workers.
|
|
24
|
-
// worker: {
|
|
25
|
-
// plugins: [
|
|
26
|
-
// viteTsConfigPaths({
|
|
27
|
-
// root: '../../../',
|
|
28
|
-
// }),
|
|
29
|
-
// ],
|
|
30
|
-
// },
|
|
31
|
-
|
|
32
|
-
// Configuration for building your library.
|
|
33
|
-
// See: https://vitejs.dev/guide/build.html#library-mode
|
|
34
|
-
build: {
|
|
35
|
-
lib: {
|
|
36
|
-
// Could also be a dictionary or array of multiple entry points.
|
|
37
|
-
entry: 'src/index.ts',
|
|
38
|
-
name: 'php-wasm-progress',
|
|
39
|
-
fileName: 'index',
|
|
40
|
-
// Change this to the formats you want to support.
|
|
41
|
-
// Don't forgot to update your package.json as well.
|
|
42
|
-
formats: ['es', 'cjs'],
|
|
43
|
-
},
|
|
44
|
-
rollupOptions: {
|
|
45
|
-
// External packages that should not be bundled into your library.
|
|
46
|
-
external: [],
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
test: {
|
|
51
|
-
globals: true,
|
|
52
|
-
cache: {
|
|
53
|
-
dir: '../../../node_modules/.vitest',
|
|
54
|
-
},
|
|
55
|
-
environment: 'jsdom',
|
|
56
|
-
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
57
|
-
},
|
|
58
|
-
});
|
|
File without changes
|