@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 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;
@@ -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
+ }