#require.js #PoC #topological-sorting #DAG
Last active
October 29, 2020 21:45
-
-
Save tvolodimir/4e38ee81a6494813a4907ba1588f14a6 to your computer and use it in GitHub Desktop.
light require.js PoC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function isContainInArray<T>(array: T[], value: T, comparer: (a: T, b: T) => boolean): boolean { | |
for (let i = 0; i < array.length; i++) { | |
if (comparer(array[i], value)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function topologicalSortingDAG<T>(items: T[], | |
getDependencies: (i: T) => T[], | |
isSkip?: (i: T) => boolean, | |
order?: T[] | undefined, | |
isEqualFn?: (a: T, b: T) => boolean): T[] { | |
/* | |
* Topological sorting (of a DAG) using modified non-recursive Post Order DFS | |
* Graph traversal (Tree traversal) | |
*/ | |
if (order === undefined) { | |
order = []; | |
} | |
if (isEqualFn === undefined) { | |
isEqualFn = (a, b) => a == b; | |
} | |
if (isSkip === undefined) { | |
isSkip = () => false; | |
} | |
for (let ni = 0; ni < items.length; ni++) { | |
const stack = [items[ni]]; | |
while (stack.length > 0) { | |
const item = stack[stack.length - 1]; | |
if (isContainInArray(order, item, isEqualFn)) { | |
stack.pop(); | |
continue; | |
} | |
if (isSkip(item)) { | |
stack.pop(); | |
continue; | |
} | |
const dependencies = getDependencies(item); | |
if (dependencies === null) { | |
throw new Error('dependencies "' + item + '" not found'); | |
} | |
let existsNotResolvedDependencies = false; | |
for (let i = 0; i < dependencies.length; i++) { | |
const r = dependencies[i]; | |
if (isContainInArray(order, r, isEqualFn)) { | |
continue; | |
} | |
if (isContainInArray(stack, r, isEqualFn)) { | |
throw new Error('circular dependency "' + r + '"'); | |
} | |
stack.push(r); | |
existsNotResolvedDependencies = true; | |
break; | |
} | |
if (existsNotResolvedDependencies === false) { | |
order.push(item); | |
stack.pop(); | |
} | |
} | |
} | |
return order; | |
} | |
type Factory = (m: any, r: (n: string) => any, ...args: any[]) => void; | |
interface Module { | |
assembly: Assembly; | |
name: string; | |
dependencyNames: string[]; | |
factory: Factory; | |
exports?: { exports: any }; | |
} | |
class Domain { | |
private assemblies: { [name: string]: Assembly } = {}; | |
public defineAssembly(name: string, dependencies: string[]): Assembly { | |
if (domain.assemblies[name] !== undefined) { | |
throw new Error('[DomainManager] assembly "' + name + '" already defined'); | |
} | |
return domain.assemblies[name] = new Assembly(name, dependencies); | |
}; | |
public getAssembly(name: string): Assembly | undefined { | |
return domain.assemblies[name]; | |
}; | |
} | |
const domain = new Domain(); | |
class Assembly { | |
private modules: { [n: string]: Module } = {}; | |
constructor(public readonly name: string, public readonly dependencies: string[]) { | |
} | |
public getModule(name: string): any { | |
const module = this.modules[name]; | |
if (module === undefined) { | |
throw new Error('[DomainManager] module "' + this.name + '.' + name + '" doesn\'t defined'); | |
} | |
if (module.exports !== undefined) { | |
return module.exports.exports; | |
} | |
const order = topologicalSortingDAG([module], (m) => { | |
if (m === undefined) return []; | |
const dependencies: Module[] = []; | |
for (let i = 0; i < m.dependencyNames.length; i++) { | |
const module = m.assembly.findModule(m.dependencyNames[i]); | |
if (module === undefined) { | |
throw new Error('[DomainManager] module "' + this.name + '.' + name + '" not defined'); | |
} | |
if (module.exports !== undefined) { | |
continue; | |
} | |
dependencies.push(module); | |
} | |
return dependencies; | |
}); | |
for (let i = 0; i < order.length; i++) { | |
order[i].assembly._buildModule(order[i].name); | |
} | |
return module.exports!.exports; | |
} | |
public defineModule(name: string, dependencyNames: string[], factory: Factory): void { | |
if (this.modules[name] !== undefined) { | |
throw new Error('[DomainManager] module "' + this.name + '.' + name + '" already defined'); | |
} | |
this.modules[name] = { assembly: this, name, dependencyNames, factory }; | |
} | |
private _buildModule(name: string): boolean { | |
const module = this.modules[name]; | |
if (module === undefined) { | |
throw new Error('[DomainManager] module "' + this.name + '.' + name + '" not defined'); | |
} | |
if (module.exports !== undefined) { | |
console.log('[DomainManager] module "' + this.name + '.' + name + '" already loaded'); | |
return true; | |
} | |
const requires = module.dependencyNames; | |
const factory = module.factory; | |
const r = []; | |
for (let i = 0; i < requires.length; i++) { | |
r.push(this.findModuleExports(requires[i])); | |
} | |
const mm = { exports: {} }; | |
module.exports = mm; | |
r.unshift(mm, this.findModuleExports); | |
const assemblyName = this.name; | |
console.log('[DomainManager] loading module "' + assemblyName + '.' + name + '"'); | |
try { | |
factory.apply(this, r as any); | |
return true; | |
} catch (error) { | |
throw new Error('[DomainManager] error loading module "' + assemblyName + '.' + name + '"') | |
} | |
} | |
public findModule(name: string): Module | undefined { | |
if (this.modules[name] !== undefined) { | |
return this.modules[name]; | |
} | |
for (let i = 0; i < this.dependencies.length; i++) { | |
const assemblyName = this.dependencies[i]; | |
const assembly = domain.getAssembly(assemblyName); | |
if (assembly) { | |
if (assembly.modules[name] !== undefined) { | |
return assembly.modules[name]; | |
} | |
} | |
} | |
return undefined; | |
} | |
public findModuleExports: (n: string) => any = name => { | |
const module = this.findModule(name); | |
if (module === undefined) { | |
throw new Error('[DomainManager] for assembly "' + this.name + '" not defined "' + name + '"'); | |
} | |
if (module.exports === undefined) { | |
throw new Error('[DomainManager] for assembly "' + this.name + '" not loaded "' + name + '"'); | |
} | |
return module.exports.exports; | |
} | |
} | |
interface AssemblyRef { | |
module(name: string): any; | |
module(name: string, requires: string[], factory: Factory): AssemblyRef; | |
} | |
function assembly(assemblyName: string, dependencies?: string[]): AssemblyRef { | |
let assembly: Assembly; | |
if (dependencies === undefined) { | |
assembly = domain.getAssembly(assemblyName)!; | |
if (!assembly) { | |
throw new Error('[DomainManager] assembly "' + assemblyName + '" not defined'); | |
} | |
} else { | |
assembly = domain.defineAssembly(assemblyName, dependencies); | |
} | |
const obj: AssemblyRef = { | |
module: function (name: string, requires?: string[], factory?: Factory): any { | |
if (requires === undefined || factory === undefined) { | |
return assembly.getModule(name); | |
} else { | |
assembly.defineModule(name, requires, factory); | |
return obj; | |
} | |
} | |
}; | |
return obj; | |
} | |
function demo(): void { | |
assembly('first', []) | |
.module('a1', [], function (module) { | |
module.exports = 'a1'; | |
}) | |
.module('a2', ['a1'], function (module, $r) { | |
module.exports = $r('a1') + ', ' + 'a2'; | |
}); | |
assembly('second', ['first']) | |
.module('b', ['a2'], function (module, _, a2) { | |
module.exports = a2 + ', ' + 'b'; | |
}) | |
console.log(assembly('second').module('b')); // prints "a1, a2, b" | |
} | |
demo(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment