@sourceacademy/web-module-loader 0.0.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/README.md +67 -0
- package/dist/__tests__/web.test.d.ts +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.mjs +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<h1 align="center"> Module Loader </h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">Host-side plugin to load Source Academy modules</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@sourceacademy/web-module-loader"><img src="https://img.shields.io/npm/v/@sourceacademy/web-module-loader?color=1a2530&label=npm"></a>
|
|
7
|
+
<a href="https://github.com/source-academy/plugins/tree/main/src/web/module-loader"><img src="https://img.shields.io/badge/github-repo-blue?logo=github"></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
- Provides an interface to load modules with a module ID, as well as the location of tabs on the host-side
|
|
12
|
+
- Accepts a module directory URL from the host app
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
To install the package, use:
|
|
16
|
+
```bash
|
|
17
|
+
yarn add @sourceacademy/web-module-loader
|
|
18
|
+
# OR
|
|
19
|
+
npm i @sourceacademy/web-module-loader
|
|
20
|
+
# OR
|
|
21
|
+
pnpm add @sourceacademy/web-module-loader
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Structure
|
|
25
|
+
This package ([`@sourceacademy/web-module-loader`](https://github.com/source-academy/plugins/tree/main/src/web/module-loader)) contains the `ModuleLoaderWebPlugin` class — a Conductor host plugin that subscribes to a Conductor channel and delivers module information to the host app.
|
|
26
|
+
|
|
27
|
+
### API Reference
|
|
28
|
+
| Name | Description |
|
|
29
|
+
|------|-------------|
|
|
30
|
+
| `instance` | The singleton instance of the plugin. |
|
|
31
|
+
| `onModuleDirectoryURLChange(newUrl: string): void` | To be called during module directory URL changes. |
|
|
32
|
+
| `getModuleTabLocation(tabName: string): string | null` | Returns the location of the specified module tab, or null if not found. |
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
During the host-side Conductor initialisation, register the class with the conduit.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
...
|
|
39
|
+
hostPlugin.registerPlugin(ModuleLoaderWebPlugin);
|
|
40
|
+
ModuleLoaderWebPlugin.instance.onModuleDirectoryURLChange(moduleDirectoryURL);
|
|
41
|
+
...
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then, on module directory changes, call `onModuleDirectoryURLChange` to update the module directory URL.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export const flagDirectoryModulesUrl = createFeatureFlag(
|
|
48
|
+
'directory.modules.url',
|
|
49
|
+
'https://source-academy.github.io/modules-conductor/modules.json',
|
|
50
|
+
'The URL where the module directory may be found.',
|
|
51
|
+
// eslint-disable-next-line require-yield
|
|
52
|
+
function* (url: string) {
|
|
53
|
+
ModuleLoaderWebPlugin.instance?.onModuleDirectoryURLChange(url);
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To receive the location of a module tab, call `getModuleTabLocation` with the tab name.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const tabLocation = ModuleLoaderWebPlugin.instance.getModuleTabLocation(tabName);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Further reading
|
|
65
|
+
- For the shared protocol types and IDs, see [`@sourceacademy/common-module-loader`](https://github.com/source-academy/plugins/tree/main/src/common/module-loader)
|
|
66
|
+
- For the runner-side plugin that sends snapshots, see [`@sourceacademy/runner-module-loader`](https://github.com/source-academy/plugins/tree/main/src/runner/module-loader)
|
|
67
|
+
- The [plugins wiki](https://github.com/source-academy/plugins/wiki) covers how Conductor plugins communicate
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";function e(e,t,o,r){return new(o||(o=Promise))(function(u,n){function l(e){try{d(r.next(e))}catch(e){n(e)}}function i(e){try{d(r.throw(e))}catch(e){n(e)}}function d(e){var t;e.done?u(e.value):(t=e.value,t instanceof o?t:new o(function(e){e(t)})).then(l,i)}d((r=r.apply(e,t||[])).next())})}"function"==typeof SuppressedError&&SuppressedError;var t,o,r,u;!function(e){e.REQUEST_MODULE="request_module",e.MODULE_RESPONSE="module_response",e.MODULE_ERROR="module_error"}(t||(t={})),function(e){e[e.CALL=0]="CALL",e[e.RETURN=1]="RETURN",e[e.RETURN_ERR=2]="RETURN_ERR"}(o||(o={})),function(e){e.UNKNOWN="__unknown",e.INTERNAL="__internal",e.EVALUATOR="__evaluator",e.EVALUATOR_SYNTAX="__evaluator_syntax",e.EVALUATOR_TYPE="__evaluator_type",e.EVALUATOR_RUNTIME="__evaluator_runtime"}(r||(r={})),function(e){e[e.PROTOCOL_VERSION=0]="PROTOCOL_VERSION",e[e.PROTOCOL_MIN_VERSION=0]="PROTOCOL_MIN_VERSION",e[e.SETUP_MESSAGES_BUFFER_SIZE=10]="SETUP_MESSAGES_BUFFER_SIZE"}(u||(u={}));class n{constructor(e,[o]){this.id="__web_module_loader",this.moduleDirectoryURL=null,this.moduleDirectory=null,this.__moduleRequestChannel=o,n.instance=this,this.__moduleRequestChannel.subscribe(e=>{if(e.type!==t.REQUEST_MODULE)return;if(null===this.moduleDirectory||null===this.moduleDirectoryURL)return this.__moduleRequestChannel.send({type:t.MODULE_ERROR,moduleName:e.moduleName,error:"Module directory not loaded yet"});if(!Object.hasOwn(this.moduleDirectory,e.moduleName))return this.__moduleRequestChannel.send({type:t.MODULE_ERROR,moduleName:e.moduleName,error:`Module not found: ${e.moduleName}`});if(!/^[a-zA-Z0-9_-]+$/.test(e.moduleName))return this.__moduleRequestChannel.send({type:t.MODULE_ERROR,moduleName:e.moduleName,error:`Invalid module name: ${e.moduleName}`});const o=this.moduleDirectoryURL.slice(0,this.moduleDirectoryURL.lastIndexOf("/")+1);return this.__moduleRequestChannel.send({type:t.MODULE_RESPONSE,moduleName:e.moduleName,moduleURL:o+"bundles/"+e.moduleName+".js",tabs:this.moduleDirectory[e.moduleName].tabs})})}onModuleDirectoryURLChange(t){return e(this,void 0,void 0,function*(){t===this.moduleDirectoryURL&&this.moduleDirectory||(this.moduleDirectoryURL=t,this.moduleDirectory=null,yield fetch(t).then(e=>e.json()).then(e=>{this.moduleDirectory=e}).catch(e=>{console.error("Failed to load module directory:",e)}))})}getModuleTabLocation(e){if(!this.moduleDirectory||!this.moduleDirectoryURL)return null;for(const t in this.moduleDirectory)if(this.moduleDirectory[t].tabs.includes(e)){return this.moduleDirectoryURL.slice(0,this.moduleDirectoryURL.lastIndexOf("/")+1)+"tabs/"+e+".js"}return null}}n.channelAttach=["module_config"],n.instance=null,exports.ModuleLoaderWebPlugin=n;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type IPlugin, type IChannel, type IConduit } from "@sourceacademy/conductor/conduit";
|
|
2
|
+
export declare class ModuleLoaderWebPlugin implements IPlugin {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
static readonly channelAttach: string[];
|
|
5
|
+
private readonly __moduleRequestChannel;
|
|
6
|
+
static instance: ModuleLoaderWebPlugin | null;
|
|
7
|
+
private moduleDirectoryURL;
|
|
8
|
+
private moduleDirectory;
|
|
9
|
+
constructor(_conduit: IConduit, [moduleRequestChannel]: IChannel<any>[]);
|
|
10
|
+
onModuleDirectoryURLChange(newURL: string): Promise<void>;
|
|
11
|
+
getModuleTabLocation(tabName: string): string | null;
|
|
12
|
+
}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,t,o,r){return new(o||(o=Promise))(function(u,n){function l(e){try{d(r.next(e))}catch(e){n(e)}}function i(e){try{d(r.throw(e))}catch(e){n(e)}}function d(e){var t;e.done?u(e.value):(t=e.value,t instanceof o?t:new o(function(e){e(t)})).then(l,i)}d((r=r.apply(e,t||[])).next())})}"function"==typeof SuppressedError&&SuppressedError;var t,o,r,u;!function(e){e.REQUEST_MODULE="request_module",e.MODULE_RESPONSE="module_response",e.MODULE_ERROR="module_error"}(t||(t={})),function(e){e[e.CALL=0]="CALL",e[e.RETURN=1]="RETURN",e[e.RETURN_ERR=2]="RETURN_ERR"}(o||(o={})),function(e){e.UNKNOWN="__unknown",e.INTERNAL="__internal",e.EVALUATOR="__evaluator",e.EVALUATOR_SYNTAX="__evaluator_syntax",e.EVALUATOR_TYPE="__evaluator_type",e.EVALUATOR_RUNTIME="__evaluator_runtime"}(r||(r={})),function(e){e[e.PROTOCOL_VERSION=0]="PROTOCOL_VERSION",e[e.PROTOCOL_MIN_VERSION=0]="PROTOCOL_MIN_VERSION",e[e.SETUP_MESSAGES_BUFFER_SIZE=10]="SETUP_MESSAGES_BUFFER_SIZE"}(u||(u={}));class n{constructor(e,[o]){this.id="__web_module_loader",this.moduleDirectoryURL=null,this.moduleDirectory=null,this.__moduleRequestChannel=o,n.instance=this,this.__moduleRequestChannel.subscribe(e=>{if(e.type!==t.REQUEST_MODULE)return;if(null===this.moduleDirectory||null===this.moduleDirectoryURL)return this.__moduleRequestChannel.send({type:t.MODULE_ERROR,moduleName:e.moduleName,error:"Module directory not loaded yet"});if(!Object.hasOwn(this.moduleDirectory,e.moduleName))return this.__moduleRequestChannel.send({type:t.MODULE_ERROR,moduleName:e.moduleName,error:`Module not found: ${e.moduleName}`});if(!/^[a-zA-Z0-9_-]+$/.test(e.moduleName))return this.__moduleRequestChannel.send({type:t.MODULE_ERROR,moduleName:e.moduleName,error:`Invalid module name: ${e.moduleName}`});const o=this.moduleDirectoryURL.slice(0,this.moduleDirectoryURL.lastIndexOf("/")+1);return this.__moduleRequestChannel.send({type:t.MODULE_RESPONSE,moduleName:e.moduleName,moduleURL:o+"bundles/"+e.moduleName+".js",tabs:this.moduleDirectory[e.moduleName].tabs})})}onModuleDirectoryURLChange(t){return e(this,void 0,void 0,function*(){t===this.moduleDirectoryURL&&this.moduleDirectory||(this.moduleDirectoryURL=t,this.moduleDirectory=null,yield fetch(t).then(e=>e.json()).then(e=>{this.moduleDirectory=e}).catch(e=>{console.error("Failed to load module directory:",e)}))})}getModuleTabLocation(e){if(!this.moduleDirectory||!this.moduleDirectoryURL)return null;for(const t in this.moduleDirectory)if(this.moduleDirectory[t].tabs.includes(e)){return this.moduleDirectoryURL.slice(0,this.moduleDirectoryURL.lastIndexOf("/")+1)+"tabs/"+e+".js"}return null}}n.channelAttach=["module_config"],n.instance=null;export{n as ModuleLoaderWebPlugin};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sourceacademy/web-module-loader",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"packageManager": "yarn@4.6.0",
|
|
5
|
+
"description": "The web plugin for the module loader in Source Academy",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "rollup -c",
|
|
8
|
+
"prepack": "yarn build"
|
|
9
|
+
},
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"main": "dist/index.cjs",
|
|
18
|
+
"module": "dist/index.mjs",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": "./dist/index.mjs",
|
|
23
|
+
"require": "./dist/index.cjs",
|
|
24
|
+
"types": "./dist/index.d.ts"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@sourceacademy/conductor": ">=0.3.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@rollup/plugin-commonjs": "^29.0.3",
|
|
32
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
33
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
34
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
35
|
+
"@sourceacademy/common-module-loader": "workspace:*",
|
|
36
|
+
"@sourceacademy/common-tabs": "workspace:^",
|
|
37
|
+
"@sourceacademy/conductor": ">=0.3.0",
|
|
38
|
+
"rollup": "^4.60.2",
|
|
39
|
+
"tslib": "^2.8.1",
|
|
40
|
+
"typescript": "^6.0.3",
|
|
41
|
+
"vitest": "^4.1.9"
|
|
42
|
+
}
|
|
43
|
+
}
|