Sfoglia il codice sorgente

* Add dynamic unit loader and demo

michael 6 anni fa
parent
commit
a751522df8

+ 43 - 39
demo/Makefile

@@ -5,7 +5,7 @@ TARGETS=democomponents demobrowseconsole demoajax demoxhr restbridgeclient \
   chartjs_demoline chartjs_demotime chartjs_demoscriptablebubble chartjs_demoradar \
   chartjs_demoline chartjs_demotime chartjs_demoscriptablebubble chartjs_demoradar \
   chartjs_democustompoints chartjs_demopolararea promiseall promisestory1 \
   chartjs_democustompoints chartjs_demopolararea promiseall promisestory1 \
   promisestory2 promisestory demodb demoload demorest fpcunitbrowsertest \
   promisestory2 promisestory demodb demoload demorest fpcunitbrowsertest \
-  sampleda webgl1 pdfbasic hotreload
+  sampleda webgl1 pdfbasic hotreload dynload
 ifneq ($(SKIPWEBCOMPILER),1)
 ifneq ($(SKIPWEBCOMPILER),1)
 TARGETS:=$(TARGETS) demowebcompiler
 TARGETS:=$(TARGETS) demowebcompiler
 BASEDIR=$(CURDIR)/../compiler/packages
 BASEDIR=$(CURDIR)/../compiler/packages
@@ -14,83 +14,87 @@ COMPILERUNITPATH=$(BASEDIR)/compat;$(BASEDIR)/fcl-json/src;$(BASEDIR)/fcl-passrc
 endif
 endif
   
   
 .PHONY: $(TARGETS)
 .PHONY: $(TARGETS)
-P2JS=pas2js
 P2JSOPT=-Fu../packages/\*
 P2JSOPT=-Fu../packages/\*
+P2JS=pas2js 
+BROWSERP2JS=$(P2JS) $(P2JSOPT) -Jirtl.js -Jc -Tbrowser
+
 all: $(TARGETS)
 all: $(TARGETS)
 
 
 info:
 info:
 	@echo Available targets: $(TARGETS)
 	@echo Available targets: $(TARGETS)
 
 
 democomponents: ./rtl/democomponents.lpr
 democomponents: ./rtl/democomponents.lpr
-	$(P2JS) $(P2JSOPT) $<
-	
+	$(BROWSERP2JS) $<
 demobrowseconsole: ./rtl/demobrowserconsole.lpr
 demobrowseconsole: ./rtl/demobrowserconsole.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 demoajax: ./rtl/demoajax.lpr
 demoajax: ./rtl/demoajax.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 demoxhr: ./rtl/demoxhr.lpr
 demoxhr: ./rtl/demoxhr.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 restbridgeclient: ./restbridge/simple/restbridgeclient.lpr
 restbridgeclient: ./restbridge/simple/restbridgeclient.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 demowebcompiler: ./webcompiler/demowebcompiler.lpr
 demowebcompiler: ./webcompiler/demowebcompiler.lpr
-	$(P2JS) -Sc -TBrowser "-Fu$(COMPILERUNITPATH)" "-Fu$(COMPILERDIR)" $(P2JSOPT) $<
+	$(BROWSERP2JS) -Sc -TBrowser "-Fu$(COMPILERUNITPATH)" "-Fu$(COMPILERDIR)" $<
 fpreportdemo: ./fpreport/reportdemo.lpr
 fpreportdemo: ./fpreport/reportdemo.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demoprogressbar: ./chartjs/demoprogressbar.lpr
 chartjs_demoprogressbar: ./chartjs/demoprogressbar.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demodate: ./chartjs/demodate.lpr
 chartjs_demodate: ./chartjs/demodate.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demoscatter: ./chartjs/demoscatter.lpr
 chartjs_demoscatter: ./chartjs/demoscatter.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demomixed: ./chartjs/demomixed.lpr
 chartjs_demomixed: ./chartjs/demomixed.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demobar: ./chartjs/demobar.lpr
 chartjs_demobar: ./chartjs/demobar.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demoarea: ./chartjs/demoarea.lpr
 chartjs_demoarea: ./chartjs/demoarea.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demobubble: ./chartjs/demobubble.lpr
 chartjs_demobubble: ./chartjs/demobubble.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demopie: ./chartjs/demopie.lpr
 chartjs_demopie: ./chartjs/demopie.lpr
-	$(P2JS) -Sc $(P2JSOPT) $<
+	$(BROWSERP2JS) -Sc $<
 chartjs_demodatalabelling: ./chartjs/demodatalabelling.lpr
 chartjs_demodatalabelling: ./chartjs/demodatalabelling.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demodoughnut: ./chartjs/demodoughnut.lpr
 chartjs_demodoughnut: ./chartjs/demodoughnut.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demointeractions: ./chartjs/demointeractions.lpr
 chartjs_demointeractions: ./chartjs/demointeractions.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demoline: ./chartjs/demoline.lpr
 chartjs_demoline: ./chartjs/demoline.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demotime: ./chartjs/demotime.lpr
 chartjs_demotime: ./chartjs/demotime.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demoscriptablebubble: ./chartjs/demoscriptablebubble.lpr
 chartjs_demoscriptablebubble: ./chartjs/demoscriptablebubble.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demoradar: ./chartjs/demoradar.lpr
 chartjs_demoradar: ./chartjs/demoradar.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_democustompoints: ./chartjs/democustompoints.lpr
 chartjs_democustompoints: ./chartjs/democustompoints.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 chartjs_demopolararea: ./chartjs/demopolararea.lpr
 chartjs_demopolararea: ./chartjs/demopolararea.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 promiseall: ./promise/demoall.lpr
 promiseall: ./promise/demoall.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 promisestory1: ./promise/story2.lpr
 promisestory1: ./promise/story2.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 promisestory2: ./promise/story3.lpr
 promisestory2: ./promise/story3.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 promisestory: ./promise/story.lpr
 promisestory: ./promise/story.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 demodb: ./fcldb/demodb.lpr
 demodb: ./fcldb/demodb.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 demoload: ./fcldb/demoload.lpr
 demoload: ./fcldb/demoload.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 demorest: ./fcldb/demorest.lpr
 demorest: ./fcldb/demorest.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 fpcunitbrowsertest: ./fpcunit/browsertest.lpr
 fpcunitbrowsertest: ./fpcunit/browsertest.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 sampleda: ./dataabstract/sampleda.lpr
 sampleda: ./dataabstract/sampleda.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 webgl1: ./webgl/project1.lpr
 webgl1: ./webgl/project1.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 pdfbasic: ./jspdf/basic.lpr
 pdfbasic: ./jspdf/basic.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
 hotreload: ./hotreload/hotreload.lpr
 hotreload: ./hotreload/hotreload.lpr
-	$(P2JS) $(P2JSOPT) $<
+	$(BROWSERP2JS) $<
+dynload: ./dynload/testloader.lpr ./dynload/myform.pp
+	$(BROWSERP2JS) -OoRemoveNotUsedDeclarations- ./dynload/testloader.lpr
+	$(P2JS) ./dynload/myform.pp

+ 61 - 0
demo/dynload/FormFactory.pas

@@ -0,0 +1,61 @@
+unit formfactory;
+
+interface
+
+uses classes;
+
+Type
+  TFormClass = Class of TComponent;
+
+procedure RegisterForm(aClass: TFormClass);
+Function GetFormClassByName(const aName : String) : TFormClass;
+
+implementation
+
+uses sysutils, js;
+
+Type
+  TFormDef = Record
+    aClass : TFormClass;
+    aName : string;
+  end;
+  TFormDefArray = Array of TFormDef;
+  
+Var
+  List : TFormDefArray;
+
+procedure RegisterForm(aClass: TFormClass);
+
+Var
+  Def : TFormDef;
+    
+begin
+  Def.aClass:=aClass;
+  Def.aName:=aClass.ClassName;
+  TJSArray(List).Push(JSValue(Def));
+end;
+
+Function IndexOfFormClassByName(const aName : String) : Integer;
+
+begin
+  Result:=Length(List)-1;
+  While (Result>=0) and Not SameText(aName,List[Result].aName) do 
+    Dec(Result);
+end;
+
+Function GetFormClassByName(const aName : String) : TFormClass;
+
+Var
+  Idx: Integer;
+  
+begin
+  Idx:=IndexOfFormClassByName(aName);
+  if Idx=-1 then 
+    Result:=Nil
+  else
+    Result:=List[Idx].aClass;
+end;
+
+initialization
+  SetLength(List,0);
+end.

+ 19 - 0
demo/dynload/MyFormDep.pas

@@ -0,0 +1,19 @@
+unit MyFormDep;
+
+interface
+
+uses myformdep2;
+
+Procedure DoLoaded;
+
+implementation
+
+Procedure DoLoaded;
+
+begin
+  Writeln('loaded');
+end;
+
+initialization
+  DoLoaded;
+end.  

+ 27 - 0
demo/dynload/myform.pp

@@ -0,0 +1,27 @@
+unit myform;
+
+interface
+ 
+uses classes, FormFactory, MyFormDep;
+
+Type
+  TMyForm = Class(TComponent)
+  Public
+    Constructor Create(aOWner : TComponent); override; 
+  end;
+
+
+implementation
+
+uses web, myformdep2;
+
+Constructor TMyForm.Create(aOWner : TComponent); 
+
+
+begin
+  window.alert('TMyForm created!');
+end;
+
+begin
+  RegisterForm(TMyForm);
+end.

+ 17 - 0
demo/dynload/myformdep2.pp

@@ -0,0 +1,17 @@
+unit myformdep2;
+
+interface
+
+Procedure DoLoaded2;
+
+implementation
+
+Procedure DoLoaded2;
+
+begin
+  Writeln('loaded 2');
+end;
+
+initialization
+  DoLoaded2;
+end.  

+ 15 - 0
demo/dynload/testloader.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Script & Unit loader test</title>
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <script src="testloader.js" type="application/javascript" ></script>
+  </head>
+  <body>
+    <button id="loader">Load JQuery javascript scripts from google</button>
+    <button id="formloader">Load form</button>
+    <script>rtl.run();</script>
+  </body>
+</html>
+

+ 81 - 0
demo/dynload/testloader.lpi

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="11"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <Runnable Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="testloader"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <CustomData Count="2">
+      <Item0 Name="PasJSPort" Value="0"/>
+      <Item1 Name="PasJSWebBrowserProject" Value="1"/>
+    </CustomData>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+      <Modes Count="0"/>
+    </RunParams>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="testloader.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target FileExt=".js">
+      <Filename Value="testloader"/>
+    </Target>
+    <SearchPaths>
+      <UnitOutputDirectory Value="js"/>
+    </SearchPaths>
+    <Parsing>
+      <SyntaxOptions>
+        <AllowLabel Value="False"/>
+        <UseAnsiStrings Value="False"/>
+      </SyntaxOptions>
+    </Parsing>
+    <CodeGeneration>
+      <TargetOS Value="browser"/>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <GenerateDebugInfo Value="False"/>
+        <UseLineInfoUnit Value="False"/>
+      </Debugging>
+    </Linking>
+    <Other>
+      <CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/>
+      <CompilerPath Value="$(pas2js)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 45 - 0
demo/dynload/testloader.lpr

@@ -0,0 +1,45 @@
+program testloader;
+
+uses types, js, web, Rtl.ScriptLoader, Rtl.UnitLoader, FormFactory;
+
+
+Function DoClick (e : TJSMouseEvent) : boolean;
+begin
+  LoadScripts([
+   'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js',
+   'http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js'
+  ],
+  procedure (data : TObject) begin
+     window. alert('All things are loaded');
+  end, Nil);
+  Result:=false;
+end;
+
+Function DoFormClick (e : TJSMouseEvent) : boolean;
+
+  procedure DoLoaded(const aUnitName : array of string; aData : TObject);
+  
+  Var
+    C : TFormClass;
+  
+  begin
+    Writeln('Unit ',aUnitName,' was loaded');
+    C:=GetFormClassByName('TMyForm');
+    if C<>Nil then
+      C.Create(Nil)
+    else
+      begin
+      Writeln('TMyForm not found');  
+      window.alert('TMyForm not found');
+      end;
+  end;
+
+begin
+  TUnitLoader.Instance.LoadUnit('myform',@DoLoaded);
+  Result:=False;
+end;
+
+begin
+  TJSHTMLElement(document.getElementbyID('loader')).onclick:=@DoClick;  
+  TJSHTMLElement(document.getElementbyID('formloader')).onclick:=@DoFormClick;  
+end.

+ 55 - 0
packages/rtl/Rtl.ScriptLoader.pas

@@ -0,0 +1,55 @@
+unit Rtl.ScriptLoader;
+
+interface
+
+uses types;
+
+Type
+  TloadedCallBack = Reference to procedure(Data : TObject);
+  TProc = reference to procedure;
+    
+Procedure loadScripts(scripts : TStringDynArray; callback : TLoadedCallback; Data : TObject);
+
+implementation
+
+uses js, web;
+
+Procedure loadScripts(scripts : TStringDynArray; callback : TLoadedCallback; Data : TObject);
+
+  Procedure loader (src : String; handler : TProc);
+  
+  var 
+    head,script : TJSElement;
+
+    Procedure DoLoaded;
+    
+    begin
+      script.Properties['onload']:=Nil;
+      script.Properties['onreadystatechange']:=Nil;
+      Handler;
+    end;
+    
+  begin
+    script:= document.createElement('script');
+    script['src'] := src;
+    script.Properties['onload'] := @DoLoaded;
+    script.Properties['onreadystatechange']:=@DoLoaded;
+    head:=TJSElement(document.getElementsByTagName('head')[0]);
+    if Head=Nil then
+      Head:=Document.body;
+    head.appendChild( script );
+  end; 
+    
+  Procedure run;
+  begin
+    if Length(Scripts)<>0 then
+      loader(String(TJSArray(scripts).shift()), @run)
+    else if Assigned(callback) then
+      callback(data);
+  end;
+        
+begin
+  Run;
+end;
+
+end. 

+ 296 - 0
packages/rtl/Rtl.UnitLoader.pas

@@ -0,0 +1,296 @@
+unit Rtl.UnitLoader;
+
+interface
+{ $define DEBUGUNITLOADER}
+
+uses SysUtils, JS, Types;
+
+Type
+  EUnitLoader = Class(Exception);
+
+  TLoadedProcedure = Reference to Procedure(const aUnitNames : Array of String; aData : TObject);
+
+  { TLoadTask }
+
+  TLoadTask = Class(TObject)
+  Private
+    FUnitNames : TStringDynArray; // unit names case sensitive!
+    FInitUnitNames : TStringDynArray; // unit names case sensitive!
+    FOnLoaded : TLoadedProcedure;
+    FData : TObject;
+    function GetAllLoaded : Boolean;
+  Protected
+    Procedure CallLoaded;
+  Public
+    Constructor Create(Const aUnitNames : Array of string; aOnLoaded : TLoadedProcedure; aData : TObject);
+    Procedure UnitLoaded(Const aUnitName : String);
+    Property AllLoaded : Boolean Read GetAllLoaded;
+    Property LoadUnitNames : TStringDynArray Read FUnitNames;
+    Property OnLoaded : TLoadedProcedure Read FOnLoaded;
+    Property Data : TObject Read FData;
+  end;
+
+  { TUnitLoader }
+
+  TUnitLoader = Class(TObject)
+  Private
+    Class var FInstance : TUnitLoader;
+    procedure DoDependenciesLoaded(const aUnitName: array of string;
+      aData: TObject);
+  Private
+    FBaseURL : String;
+    FLoadList : TStringDynArray; // unitnames case sensitive!
+    function IndexOfLoadUnit(aUnitName : String): integer;
+  protected
+    Procedure AddToLoadList(aUnitName : String);
+    Procedure RemoveFromLoadList(aUnitName : String);
+    function IsInLoadList(aUnitName: String): Boolean;
+    function GetUnitURL(const aUnitName: string): String; virtual;
+    procedure InitModule(aTask: TLoadTask; const aName: String; aModule : JSValue); virtual;
+    procedure DoLoadUnits(const aUnitNames: array of String;  aOnLoaded: TLoadedProcedure; aData: TObject); virtual;
+    function AreAllDependenciesLoaded(aTask: TLoadTask; const aName: String;   AModule: JSValue): Boolean; virtual;
+    function GetNeededDependencies(const aName: String; AModule: JSValue): TStringDynArray;
+    procedure UnitSourcesLoaded(aData : TObject); virtual;
+  Public
+    Class Function Instance : TUnitLoader;
+    function FindModule(aModuleName: string): JSValue;
+    function HaveModule(aModuleName: string): Boolean;
+    procedure LoadUnit(Const aUnitName : string; aOnLoaded : TLoadedProcedure = Nil; aData : TObject = Nil);
+    procedure LoadUnits(Const aUnitNames : Array of String; aOnLoaded : TLoadedProcedure = Nil; aData : TObject = Nil);
+    Property BaseURL : String Read FBaseUrl Write FBaseURL;
+  end;
+ 
+ 
+Implementation
+
+uses Rtl.ScriptLoader;
+
+function IndexOfI(arr: TStringDynArray; Name: string): integer;
+begin
+  Result:=length(arr)-1;
+  while (Result>=0) and not SameText(arr[Result],Name) do
+    dec(Result);
+end;
+
+function TLoadTask.GetAllLoaded: Boolean;
+begin
+  Result:=Length(FInitunitNames)=0;
+end;
+
+procedure TLoadTask.CallLoaded;
+begin
+  if Assigned(OnLoaded) then
+    OnLoaded(LoadUnitNames,Data);
+end;
+
+constructor TLoadTask.Create(const aUnitNames: array of string;
+  aOnLoaded: TLoadedProcedure; aData: TObject);
+
+Var
+  I : Integer;
+
+begin
+  SetLength(FunitNames,Length(aUnitNames));
+  SetLength(FInitUnitNames,Length(aUnitNames));
+  for I:=Low(aUnitNames) to High(aUnitNames) do
+    begin
+    FUnitNames[i]:=aUnitNames[i];
+    FInitUnitNames[i]:=aUnitNames[i];
+    end;
+  FOnLoaded:=aOnLoaded;
+  FData:=aData;
+end;
+
+procedure TLoadTask.UnitLoaded(const aUnitName: String);
+
+var
+  Idx : integer;
+
+begin
+{$IFDEF DEBUGUNITLOADER}Writeln('Unit ',aUnitName,' loaded, removing from list');{$ENDIF}
+  Idx:=IndexOfI(FInitUnitNames,aUnitName);
+  if Idx>-1 then
+    TJSArray(FInitUnitNames).splice(Idx,1);
+end;
+
+class function TUnitLoader.Instance: TUnitLoader;
+
+begin
+  if (FInstance=Nil) then
+    FInstance:=TUnitLoader.Create;
+  Result:=FInstance;  
+end;
+
+Procedure LoadIntf(aModule : JSValue); external name 'rtl.loadintf';
+Procedure LoadImpl(aModule : JSValue); external name 'rtl.loadimpl';
+var pas : TJSOBject; external name 'pas';
+
+function TUnitLoader.FindModule(aModuleName: string): JSValue;
+var
+  Key: string;
+begin
+  Result:=pas[aModuleName];
+  if isModule(Result) then exit;
+  for Key in pas do
+    begin
+    if not SameText(Key,aModuleName) then continue;
+    Result:=pas[Key];
+    if isModule(Result) then exit;
+    end;
+  Result:=nil;
+end;
+
+function TUnitLoader.HaveModule(aModuleName: string): Boolean;
+begin
+  Result:=FindModule(aModuleName)<>nil;
+end;
+
+procedure TUnitLoader.InitModule(aTask: TLoadTask; const aName: String;
+  aModule: JSValue);
+
+begin
+{$IFDEF DEBUGUNITLOADER}  Writeln('Unit ',aName,' dependencies loaded. Initialising "',TJSObject(aModule)['$name'],'" ...');{$ENDIF}
+  RemoveFromLoadList(aName);
+  LoadIntf(aModule);
+  LoadImpl(aModule);
+  aTask.UnitLoaded(aName);
+end;
+
+function TUnitLoader.GetNeededDependencies(const aName: String; AModule: JSValue
+  ): TStringDynArray;
+
+var
+  l,u : TStringDynArray;
+  m : String;
+begin
+  SetLength(l,0);
+  u:=TStringDynArray(TJSOBject(aModule)['$intfuseslist']);
+  for m in u do
+    if not (HaveModule(m) or IsInLoadList(m)) then
+      TJSArray(l).push(m);
+  u:=TStringDynArray(TJSOBject(aModule)['$impluseslist']);
+  for m in u do
+    if not (HaveModule(m) or IsInLoadList(m)) then
+      TJSArray(l).push(m);
+  Result:=l;
+end;
+
+function TUnitLoader.AreAllDependenciesLoaded(aTask: TLoadTask;
+  const aName: String; AModule: JSValue): Boolean;
+
+begin
+  Result:=Length(GetNeededDependencies(aName,aModule))=0;
+end;
+
+procedure TUnitLoader.DoDependenciesLoaded(const aUnitName : array of string; aData : TObject);
+
+begin
+  UnitSourcesLoaded(aData);
+end;
+
+function TUnitLoader.IndexOfLoadUnit(aUnitName: String): integer;
+begin
+  Result:=IndexOfI(FLoadList,aUnitName);
+end;
+
+procedure TUnitLoader.AddToLoadList(aUnitName: String);
+begin
+  if IndexOfLoadUnit(aUnitName)<0 then
+    TJSArray(FLoadList).Push(aUnitName);
+end;
+
+procedure TUnitLoader.RemoveFromLoadList(aUnitName: String);
+var
+  idx : Integer;
+begin
+  Idx:=IndexOfLoadUnit(aUnitName);
+  if Idx>-1 then
+    TJSArray(FLoadList).splice(Idx,1);
+end;
+
+function TUnitLoader.IsInLoadList(aUnitName: String): Boolean;
+begin
+  Result:=IndexOfLoadUnit(aUnitName)>=0;
+end;
+
+procedure TUnitLoader.UnitSourcesLoaded(aData : TObject);
+
+Var
+  aTask : TLoadTask;
+  aModule : JSValue;
+  aModuleName : String;
+  Deps : TStringDynArray;
+
+begin
+{$IFDEF DEBUGUNITLOADER}  Writeln('Succesfully loaded sources');{$ENDIF}
+  aTask:=TLoadTask(aData);
+  For aModuleName in aTask.LoadUnitNames do
+    begin
+    aModule:=FindModule(aModuleName);
+    if aModule<>nil then
+      begin
+{$IFDEF DEBUGUNITLOADER}      Writeln(aModuleName+' is module. Loading interface');{$ENDIF}
+      Deps:=GetNeededDependencies(aModuleName,aModule);
+      if length(Deps)=0 then
+        InitModule(aTask,aModuleName,aModule)
+      else
+        DoLoadUnits(Deps,@DoDependenciesLoaded,aData);
+      end;
+    end;
+  if (aTask.AllLoaded) then
+    aTask.CallLoaded;
+end;
+
+function TUnitLoader.GetUnitURL(const aUnitName: string): String;
+
+begin
+  Result:=BaseURL;
+  if (Result<>'') then
+    Result:=Result+'/';
+  Result:=Result+aUnitname+'.js';
+end;
+
+procedure TUnitLoader.LoadUnit(const aUnitName : string; aOnLoaded : TLoadedProcedure = Nil; aData : TObject = Nil);
+
+begin
+  LoadUnits([aUnitName],aOnLoaded,aData);
+end;
+
+procedure TUnitLoader.LoadUnits(const aUnitNames: array of String; aOnLoaded: TLoadedProcedure; aData: TObject);
+
+begin
+  if Length(FLoadList)>0 then
+    Raise EUnitLoader.Create('Load operation in progress. Cannot load.');
+  DoLoadUnits(aUnitNames,aOnLoaded,aData);
+end;
+
+procedure TUnitLoader.DoLoadUnits(const aUnitNames: array of String; aOnLoaded: TLoadedProcedure; aData: TObject);
+
+Var
+  Scripts : TStringDynArray;
+  aCount : Integer;
+  S : String;
+
+begin
+  aCount:=0;
+  Setlength(Scripts,Length(aUnitNames));
+  for s in aUnitNames do
+    if Not HaveModule(S) then
+      begin
+{$IFDEF DEBUGUNITLOADER}      Writeln('Need to load unit: ',S);{$ENDIF}
+      Scripts[aCount]:=GetUnitURl(S);
+      AddToLoadList(S);
+      inc(aCount);
+      end;
+  SetLength(S,aCount);
+  if aCount=0 then
+    begin
+    // All is already loaded
+    if Assigned(aOnLoaded) then
+      aOnLoaded(aUnitNames,aData);
+    end
+  else
+    LoadScripts(Scripts,@UnitSourcesLoaded,TLoadTask.Create(aUnitNames,aOnLoaded,aData));
+end;
+
+end.