# HG changeset patch # User Paul Fisher # Date 1570841450 14400 # Node ID df3e0534c9949ceb7c2e6889d388e94559b22ca9 # Parent dd77a7ee02c18ca93eea13ab05be3a473fd131f4 Tighten up MADRegistry: - Use only one map for registry. - Make the user construct it, to avoid modifying global state. diff -r dd77a7ee02c1 -r df3e0534c994 weather_server/typescript/amd/globals.d.ts --- 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): {}; -} diff -r dd77a7ee02c1 -r df3e0534c994 weather_server/typescript/amd/mad-amd.ts --- 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(); - private readonly reified = new Map(); +class MADRegistry { + /** The registry itself, mapping from name to module. */ + private readonly mods = new Map(); + /** + * 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())();