changeset 15:df3e0534c994

Tighten up MADRegistry: - Use only one map for registry. - Make the user construct it, to avoid modifying global state.
author Paul Fisher <paul@pfish.zone>
date Fri, 11 Oct 2019 20:50:50 -0400
parents dd77a7ee02c1
children 9a609bcf0809
files weather_server/typescript/amd/globals.d.ts weather_server/typescript/amd/mad-amd.ts
diffstat 2 files changed, 71 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/weather_server/typescript/amd/globals.d.ts	Wed Oct 09 23:14:16 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-type FactoryFunction = (...deps: unknown[]) => void;
-
-interface Module {
-    deps: string[];
-    factory: FactoryFunction;
-    exports: {};
-}
-
-interface Window {
-    define(name: string, deps: string[], factory: FactoryFunction): void;
-    require(dep: string): {};
-}
--- a/weather_server/typescript/amd/mad-amd.ts	Wed Oct 09 23:14:16 2019 -0400
+++ b/weather_server/typescript/amd/mad-amd.ts	Fri Oct 11 20:50:50 2019 -0400
@@ -1,41 +1,94 @@
-// TODO: add more comments.
+/** Type of the AMD factory function. */
+type FactoryFunction = (...deps: unknown[]) => void;
+
+/** An individual AMD module. */
+interface Module {
+    /** The names of the module's dependencies. */
+    deps: string[];
+    /**
+     * The function that, when called with the module's list of dependencies,
+     * creates the module.
+     */
+    factory: FactoryFunction;
+    /**
+     * When null, an indication that the module has not yet been reified.
+     * When non-null, the members that the module has exported.
+     */
+    exports: {}|null;
+}
 
 /**
- * The dumbest possible implementation of AMD to handle only the code that
- * `tsc -m amd` produces.
+ * Minimal AMD Dumb Registry: the dumbest possible implementation of AMD,
+ * to handle only the code that `tsc -m amd` produces.
+ *
+ * Supports `require` of absolute paths and `define`s.
  */
-class Registry {
-    private readonly modules = new Map<string, Module>();
-    private readonly reified = new Map<string, {}>();
+class MADRegistry {
+    /** The registry itself, mapping from name to module. */
+    private readonly mods = new Map<string, Module>();
 
+    /**
+     * The (subset of) the AMD `define` function we implement.
+     *
+     * This supports
+     *
+     * - Absolute paths
+     * - `exports`-based module construction
+     *
+     * @param name The name of the module to define.
+     * @param deps The dependencies of the module. Must be explicit.
+     * @param factory The module's factory function.
+     */
     define(name: string, deps: string[], factory: FactoryFunction) {
-        this.modules.set(name, {deps, factory, exports: {}});
+        this.mods.set(name, {deps, factory, exports: null});
     }
 
+    /**
+     * The (subset of) the AMD `require` function we implement.
+     * Only `require(dep)` is exposed to users; `require(dep, srcMod)`
+     * is internal-only.
+     *
+     * - Does not support relative paths.
+     * - Does not define `require.amd`, because we do not fully support AMD
+     *   and don't want to give the impression that we do.
+     *
+     * @param dep The name of the dependency.
+     * @param srcMod The module whence the dependency was requested.
+     *     Used for when the name `exports` is required.
+     */
     require(dep: string, srcMod?: Module): {} {
         if (dep === 'require') {
             return (child: string) => this.require(child, srcMod);
         }
         if (dep === 'exports') {
-            if (!srcMod) throw new Error('Invalid package name.');
-            return srcMod.exports;
+            if (!srcMod) throw new Error('Internal consistency error.');
+            // We know this is safe because a module can only ever require
+            // its own exports after it is itself required.
+            return srcMod.exports!;
         }
-        const exp = this.reified.get(dep);
-        if (exp) return exp;
-        const mod = this.modules.get(dep);
+        const mod = this.mods.get(dep);
         if (!mod) throw new Error('Undefined module.');
-        this.reified.set(dep, mod.exports);
+        // If we've required the module before, return its exports.
+        if (mod.exports) return mod.exports;
+        // Otherwise, we need to prepare the module and require its parents.
+        mod.exports = {};
         const deps = mod.deps.map(child => this.require(child, mod));
         mod.factory(...deps);
         return mod.exports;
     }
 
-    install() {
-        self.define =
+    /**
+     * Installs this registry into the given object, usually `window` or `self`.
+     * For usage with a separately-compiled JS file, do:
+     *
+     * ```typescript
+     * new MADRegistry().install();
+     * ```
+     */
+    install(to: any) {
+        to['define'] =
             (name: string, deps: string[], factory: FactoryFunction) =>
                 this.define(name, deps, factory);
-        self.require = (dep: string) => this.require(dep);
+        to['require'] = (dep: string) => this.require(dep);
     }
 }
-
-(() => new Registry().install())();