Browse Source

Package-level imports. Fix #1068.

Herbert Vojčík 9 years ago
parent
commit
9528caa695
4 changed files with 728 additions and 24 deletions
  1. 112 9
      src/Kernel-ImportExport.js
  2. 26 3
      src/Kernel-ImportExport.st
  3. 484 9
      src/Kernel-Infrastructure.js
  4. 106 3
      src/Kernel-Infrastructure.st

+ 112 - 9
src/Kernel-ImportExport.js

@@ -1380,6 +1380,7 @@ var $1,$2;
 self._exportPackagePrologueOf_on_(aPackage,aStream);
 self._exportPackageDefinitionOf_on_(aPackage,aStream);
 self._exportPackageContextOf_on_(aPackage,aStream);
+self._exportPackageImportsOf_on_(aPackage,aStream);
 $1=self._exportPackageTransportOf_on_(aPackage,aStream);
 $recv($recv(aPackage)._sortedClasses())._do_((function(each){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -1445,10 +1446,10 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aPackage", "aStream"],
-source: "exportPackage: aPackage on: aStream\x0a\x09\x0a\x09self \x0a\x09\x09exportPackagePrologueOf: aPackage on: aStream;\x0a\x09\x09exportPackageDefinitionOf: aPackage on: aStream;\x0a\x09\x09exportPackageContextOf: aPackage on: aStream;\x0a\x09\x09exportPackageTransportOf: aPackage on: aStream.\x0a\x09\x0a\x09aPackage sortedClasses do: [ :each |\x0a\x09\x09self exportDefinitionOf: each on: aStream.\x0a\x09\x09each ownMethods do: [ :method |\x0a\x09\x09\x09self exportMethod: method on: aStream ].\x0a\x09\x09\x09\x0a\x09\x09self exportMetaDefinitionOf: each on: aStream.\x0a\x09\x09each class ownMethods do: [ :method |\x0a\x09\x09\x09self exportMethod: method on: aStream ] ].\x0a\x09\x09\x09\x0a\x09(self extensionMethodsOfPackage: aPackage) do: [ :each |\x0a\x09\x09self exportMethod: each on: aStream ].\x0a\x09\x09\x0a\x09self exportPackageEpilogueOf: aPackage on: aStream",
+source: "exportPackage: aPackage on: aStream\x0a\x09\x0a\x09self \x0a\x09\x09exportPackagePrologueOf: aPackage on: aStream;\x0a\x09\x09exportPackageDefinitionOf: aPackage on: aStream;\x0a\x09\x09exportPackageContextOf: aPackage on: aStream;\x0a\x09\x09exportPackageImportsOf: aPackage on: aStream;\x0a\x09\x09exportPackageTransportOf: aPackage on: aStream.\x0a\x09\x0a\x09aPackage sortedClasses do: [ :each |\x0a\x09\x09self exportDefinitionOf: each on: aStream.\x0a\x09\x09each ownMethods do: [ :method |\x0a\x09\x09\x09self exportMethod: method on: aStream ].\x0a\x09\x09\x09\x0a\x09\x09self exportMetaDefinitionOf: each on: aStream.\x0a\x09\x09each class ownMethods do: [ :method |\x0a\x09\x09\x09self exportMethod: method on: aStream ] ].\x0a\x09\x09\x09\x0a\x09(self extensionMethodsOfPackage: aPackage) do: [ :each |\x0a\x09\x09self exportMethod: each on: aStream ].\x0a\x09\x09\x0a\x09self exportPackageEpilogueOf: aPackage on: aStream",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: ["exportPackagePrologueOf:on:", "exportPackageDefinitionOf:on:", "exportPackageContextOf:on:", "exportPackageTransportOf:on:", "do:", "sortedClasses", "exportDefinitionOf:on:", "ownMethods", "exportMethod:on:", "exportMetaDefinitionOf:on:", "class", "extensionMethodsOfPackage:", "exportPackageEpilogueOf:on:"]
+messageSends: ["exportPackagePrologueOf:on:", "exportPackageDefinitionOf:on:", "exportPackageContextOf:on:", "exportPackageImportsOf:on:", "exportPackageTransportOf:on:", "do:", "sortedClasses", "exportDefinitionOf:on:", "ownMethods", "exportMethod:on:", "exportMetaDefinitionOf:on:", "class", "extensionMethodsOfPackage:", "exportPackageEpilogueOf:on:"]
 }),
 $globals.Exporter);
 
@@ -1552,6 +1553,61 @@ messageSends: ["subclassResponsibility"]
 }),
 $globals.Exporter);
 
+$core.addMethod(
+$core.method({
+selector: "exportPackageImportsOf:on:",
+protocol: 'output',
+fn: function (aPackage,aStream){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$2;
+$recv($recv(aPackage)._importsAsJson())._ifNotEmpty_((function(imports){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$recv(aStream)._nextPutAll_("$core.packages[");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["nextPutAll:"]=1;
+//>>excludeEnd("ctx");
+$1=$recv($recv(aPackage)._name())._asJavascript();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["asJavascript"]=1;
+//>>excludeEnd("ctx");
+$recv(aStream)._nextPutAll_($1);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["nextPutAll:"]=2;
+//>>excludeEnd("ctx");
+$recv(aStream)._nextPutAll_("].imports = ");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["nextPutAll:"]=3;
+//>>excludeEnd("ctx");
+$recv(aStream)._nextPutAll_($recv(imports)._asJavascript());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["nextPutAll:"]=4;
+//>>excludeEnd("ctx");
+$recv(aStream)._nextPutAll_(";");
+$2=$recv(aStream)._lf();
+return $2;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({imports:imports},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"exportPackageImportsOf:on:",{aPackage:aPackage,aStream:aStream},$globals.Exporter)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aPackage", "aStream"],
+source: "exportPackageImportsOf: aPackage on: aStream\x0a\x09aPackage importsAsJson ifNotEmpty: [ :imports |\x0a\x09\x09aStream\x0a\x09\x09\x09nextPutAll: '$core.packages[';\x0a\x09\x09\x09nextPutAll: aPackage name asJavascript;\x0a\x09\x09\x09nextPutAll: '].imports = ';\x0a\x09\x09\x09nextPutAll: imports asJavascript;\x0a\x09\x09\x09nextPutAll: ';';\x0a\x09\x09\x09lf ]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["ifNotEmpty:", "importsAsJson", "nextPutAll:", "asJavascript", "name", "lf"]
+}),
+$globals.Exporter);
+
 $core.addMethod(
 $core.method({
 selector: "exportPackagePrologueOf:on:",
@@ -1846,10 +1902,36 @@ selector: "exportPackagePrologueOf:on:",
 protocol: 'output',
 fn: function (aPackage,aStream){
 var self=this;
+var namedImports,anonImports,importVarNames,loadDependencies;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1;
+var $1,$5,$4,$3,$2,$6;
+namedImports=[];
+anonImports=[];
+importVarNames=[];
+$recv($recv(aPackage)._imports())._do_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$1=$recv(each)._isString();
+if($core.assert($1)){
+return $recv(anonImports)._add_(each);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["add:"]=1;
+//>>excludeEnd("ctx");
+} else {
+$recv(namedImports)._add_($recv(each)._value());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["add:"]=2;
+//>>excludeEnd("ctx");
+return $recv(importVarNames)._add_($recv(each)._key());
+};
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+loadDependencies=self._amdNamesOfPackages_($recv(aPackage)._loadDependencies());
 $recv(aStream)._nextPutAll_("define(\x22");
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["nextPutAll:"]=1;
@@ -1870,31 +1952,52 @@ $recv(aStream)._nextPutAll_("\x22, ");
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["nextPutAll:"]=5;
 //>>excludeEnd("ctx");
-$recv(aStream)._nextPutAll_($recv(["amber/boot"].__comma(self._amdNamesOfPackages_($recv(aPackage)._loadDependencies())))._asJavascript());
+$5=$recv(namedImports).__comma(["amber/boot"]);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx[","]=3;
+//>>excludeEnd("ctx");
+$4=$recv($5).__comma(anonImports);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx[","]=2;
+//>>excludeEnd("ctx");
+$3=$recv($4).__comma(loadDependencies);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx[","]=1;
+//>>excludeEnd("ctx");
+$2=$recv($3)._asJavascript();
+$recv(aStream)._nextPutAll_($2);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["nextPutAll:"]=6;
 //>>excludeEnd("ctx");
-$recv(aStream)._nextPutAll_(", function($boot){");
+$recv(aStream)._nextPutAll_(", function(");
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["nextPutAll:"]=7;
 //>>excludeEnd("ctx");
+$recv(aStream)._nextPutAll_($recv($recv(importVarNames).__comma(["$boot"]))._join_(","));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["nextPutAll:"]=8;
+//>>excludeEnd("ctx");
+$recv(aStream)._nextPutAll_("){");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["nextPutAll:"]=9;
+//>>excludeEnd("ctx");
 $recv(aStream)._lf();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["lf"]=1;
 //>>excludeEnd("ctx");
 $recv(aStream)._nextPutAll_("var $core=$boot.api,nil=$boot.nil,$recv=$boot.asReceiver,$globals=$boot.globals;");
-$1=$recv(aStream)._lf();
+$6=$recv(aStream)._lf();
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"exportPackagePrologueOf:on:",{aPackage:aPackage,aStream:aStream},$globals.AmdExporter)});
+}, function($ctx1) {$ctx1.fill(self,"exportPackagePrologueOf:on:",{aPackage:aPackage,aStream:aStream,namedImports:namedImports,anonImports:anonImports,importVarNames:importVarNames,loadDependencies:loadDependencies},$globals.AmdExporter)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aPackage", "aStream"],
-source: "exportPackagePrologueOf: aPackage on: aStream\x0a\x09aStream\x0a\x09\x09nextPutAll: 'define(\x22';\x0a\x09\x09nextPutAll: (self amdNamespaceOfPackage: aPackage);\x0a\x09\x09nextPutAll: '/';\x0a\x09\x09nextPutAll: aPackage name;\x0a\x09\x09nextPutAll: '\x22, ';\x0a\x09\x09nextPutAll: (#('amber/boot'), (self amdNamesOfPackages: aPackage loadDependencies)) asJavascript;\x0a\x09\x09nextPutAll: ', function($boot){';\x0a\x09\x09lf;\x0a\x09\x09nextPutAll: 'var $core=$boot.api,nil=$boot.nil,$recv=$boot.asReceiver,$globals=$boot.globals;';\x0a\x09\x09lf",
+source: "exportPackagePrologueOf: aPackage on: aStream\x0a\x09| namedImports anonImports importVarNames loadDependencies |\x0a\x09namedImports := #().\x0a\x09anonImports := #().\x0a\x09importVarNames := #().\x0a\x09aPackage imports do: [ :each | each isString\x0a\x09\x09ifTrue: [ anonImports add: each ]\x0a\x09\x09ifFalse: [ namedImports add: each value.\x0a\x09\x09\x09importVarNames add: each key ]].\x0a\x09loadDependencies := self amdNamesOfPackages: aPackage loadDependencies.\x0a\x09aStream\x0a\x09\x09nextPutAll: 'define(\x22';\x0a\x09\x09nextPutAll: (self amdNamespaceOfPackage: aPackage);\x0a\x09\x09nextPutAll: '/'; \x0a\x09\x09nextPutAll: aPackage name;\x0a\x09\x09nextPutAll: '\x22, ';\x0a\x09\x09nextPutAll: (namedImports, #('amber/boot'), anonImports, loadDependencies) asJavascript;\x0a\x09\x09nextPutAll: ', function(';\x0a\x09\x09nextPutAll: (importVarNames, #('$boot') join: ',');\x0a\x09\x09nextPutAll: '){';\x0a\x09\x09lf;\x0a\x09\x09nextPutAll: 'var $core=$boot.api,nil=$boot.nil,$recv=$boot.asReceiver,$globals=$boot.globals;';\x0a\x09\x09lf",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: ["nextPutAll:", "amdNamespaceOfPackage:", "name", "asJavascript", ",", "amdNamesOfPackages:", "loadDependencies", "lf"]
+messageSends: ["do:", "imports", "ifTrue:ifFalse:", "isString", "add:", "value", "key", "amdNamesOfPackages:", "loadDependencies", "nextPutAll:", "amdNamespaceOfPackage:", "name", "asJavascript", ",", "join:", "lf"]
 }),
 $globals.AmdExporter);
 

+ 26 - 3
src/Kernel-ImportExport.st

@@ -322,6 +322,7 @@ exportPackage: aPackage on: aStream
 		exportPackagePrologueOf: aPackage on: aStream;
 		exportPackageDefinitionOf: aPackage on: aStream;
 		exportPackageContextOf: aPackage on: aStream;
+		exportPackageImportsOf: aPackage on: aStream;
 		exportPackageTransportOf: aPackage on: aStream.
 	
 	aPackage sortedClasses do: [ :each |
@@ -360,6 +361,17 @@ exportPackageEpilogueOf: aPackage on: aStream
 	self subclassResponsibility
 !
 
+exportPackageImportsOf: aPackage on: aStream
+	aPackage importsAsJson ifNotEmpty: [ :imports |
+		aStream
+			nextPutAll: '$core.packages[';
+			nextPutAll: aPackage name asJavascript;
+			nextPutAll: '].imports = ';
+			nextPutAll: imports asJavascript;
+			nextPutAll: ';';
+			lf ]
+!
+
 exportPackagePrologueOf: aPackage on: aStream
 	self subclassResponsibility
 !
@@ -389,14 +401,25 @@ exportPackageEpilogueOf: aPackage on: aStream
 !
 
 exportPackagePrologueOf: aPackage on: aStream
+	| namedImports anonImports importVarNames loadDependencies |
+	namedImports := #().
+	anonImports := #().
+	importVarNames := #().
+	aPackage imports do: [ :each | each isString
+		ifTrue: [ anonImports add: each ]
+		ifFalse: [ namedImports add: each value.
+			importVarNames add: each key ]].
+	loadDependencies := self amdNamesOfPackages: aPackage loadDependencies.
 	aStream
 		nextPutAll: 'define("';
 		nextPutAll: (self amdNamespaceOfPackage: aPackage);
-		nextPutAll: '/';
+		nextPutAll: '/'; 
 		nextPutAll: aPackage name;
 		nextPutAll: '", ';
-		nextPutAll: (#('amber/boot'), (self amdNamesOfPackages: aPackage loadDependencies)) asJavascript;
-		nextPutAll: ', function($boot){';
+		nextPutAll: (namedImports, #('amber/boot'), anonImports, loadDependencies) asJavascript;
+		nextPutAll: ', function(';
+		nextPutAll: (importVarNames, #('$boot') join: ',');
+		nextPutAll: '){';
 		lf;
 		nextPutAll: 'var $core=$boot.api,nil=$boot.nil,$recv=$boot.asReceiver,$globals=$boot.globals;';
 		lf

+ 484 - 9
src/Kernel-Infrastructure.js

@@ -2061,10 +2061,34 @@ $globals.PackageOrganizer.comment="I am an organizer specific to packages. I hol
 //>>excludeEnd("ide");
 
 
-$core.addClass('Package', $globals.Object, ['transport', 'dirty'], 'Kernel-Infrastructure');
+$core.addClass('Package', $globals.Object, ['transport', 'imports', 'dirty'], 'Kernel-Infrastructure');
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.Package.comment="I am similar to a \x22class category\x22 typically found in other Smalltalks like Pharo or Squeak. Amber does not have class categories anymore, it had in the beginning but now each class in the system knows which package it belongs to.\x0a\x0aEach package has a name and can be queried for its classes, but it will then resort to a reverse scan of all classes to find them.\x0a\x0a## API\x0a\x0aPackages are manipulated through \x22Smalltalk current\x22, like for example finding one based on a name or with `Package class >> #name` directly:\x0a\x0a    Smalltalk current packageAt: 'Kernel'\x0a    Package named: 'Kernel'\x0a\x0aA package differs slightly from a Monticello package which can span multiple class categories using a naming convention based on hyphenation. But just as in Monticello a package supports \x22class extensions\x22 so a package can define behaviors in foreign classes using a naming convention for method categories where the category starts with an asterisk and then the name of the owning package follows.\x0a\x0aYou can fetch a package from the server:\x0a\x0a\x09Package load: 'Additional-Examples'";
 //>>excludeEnd("ide");
+$core.addMethod(
+$core.method({
+selector: "basicImports",
+protocol: 'private',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return self.imports || [];
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"basicImports",{},$globals.Package)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "basicImports\x0a\x09\x22Answer the imports literal JavaScript object as setup in the JavaScript file, if any\x22\x0a\x09\x0a\x09<return self.imports || []>",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.Package);
+
 $core.addMethod(
 $core.method({
 selector: "basicName:",
@@ -2290,7 +2314,7 @@ function $String(){return $globals.String||(typeof String=="undefined"?nil:Strin
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $2,$4,$5,$3,$7,$6,$8,$9,$1;
+var $2,$4,$5,$3,$7,$6,$9,$10,$8,$11,$12,$1;
 $1=$recv($String())._streamContents_((function(stream){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
@@ -2319,7 +2343,7 @@ $recv(stream)._nextPutAll_($3);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx2.sendIdx["nextPutAll:"]=2;
 //>>excludeEnd("ctx");
-$recv(stream)._nextPutAll_(" named: ");
+$recv(stream)._nextPutAll_("named: ");
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx2.sendIdx["nextPutAll:"]=3;
 //>>excludeEnd("ctx");
@@ -2335,7 +2359,15 @@ $recv(stream)._nextPutAll_($6);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx2.sendIdx["nextPutAll:"]=4;
 //>>excludeEnd("ctx");
-$8=$recv($recv($String())._lf()).__comma($recv($String())._tab());
+$9=$recv($String())._lf();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["lf"]=2;
+//>>excludeEnd("ctx");
+$10=$recv($String())._tab();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["tab"]=2;
+//>>excludeEnd("ctx");
+$8=$recv($9).__comma($10);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx2.sendIdx[","]=4;
 //>>excludeEnd("ctx");
@@ -2343,12 +2375,28 @@ $recv(stream)._nextPutAll_($8);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx2.sendIdx["nextPutAll:"]=5;
 //>>excludeEnd("ctx");
-$recv(stream)._nextPutAll_(" transport: (");
+$recv(stream)._nextPutAll_("imports: ");
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx2.sendIdx["nextPutAll:"]=6;
 //>>excludeEnd("ctx");
-$9=$recv(stream)._nextPutAll_($recv($recv(self._transport())._definition()).__comma(")"));
-return $9;
+$recv(stream)._nextPutAll_(self._importsDefinition());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["nextPutAll:"]=7;
+//>>excludeEnd("ctx");
+$11=$recv($recv($String())._lf()).__comma($recv($String())._tab());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx[","]=5;
+//>>excludeEnd("ctx");
+$recv(stream)._nextPutAll_($11);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["nextPutAll:"]=8;
+//>>excludeEnd("ctx");
+$recv(stream)._nextPutAll_("transport: (");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["nextPutAll:"]=9;
+//>>excludeEnd("ctx");
+$12=$recv(stream)._nextPutAll_($recv($recv(self._transport())._definition()).__comma(")"));
+return $12;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx2) {$ctx2.fillBlock({stream:stream},$ctx1,1)});
 //>>excludeEnd("ctx");
@@ -2360,10 +2408,215 @@ return $1;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "definition\x0a\x09^ String streamContents: [ :stream |\x0a\x09\x09stream \x0a\x09\x09\x09nextPutAll: self class name;\x0a\x09\x09\x09nextPutAll: String lf, String tab;\x0a\x09\x09\x09nextPutAll: ' named: ';\x0a\x09\x09\x09nextPutAll: '''', self name, '''';\x0a\x09\x09\x09nextPutAll: String lf, String tab;\x0a\x09\x09\x09nextPutAll:  ' transport: (';\x0a\x09\x09\x09nextPutAll: self transport definition, ')' ]",
+source: "definition\x0a\x09^ String streamContents: [ :stream |\x0a\x09\x09stream \x0a\x09\x09\x09nextPutAll: self class name;\x0a\x09\x09\x09nextPutAll: String lf, String tab;\x0a\x09\x09\x09nextPutAll: 'named: ';\x0a\x09\x09\x09nextPutAll: '''', self name, '''';\x0a\x09\x09\x09nextPutAll: String lf, String tab;\x0a\x09\x09\x09nextPutAll: 'imports: ';\x0a\x09\x09\x09nextPutAll: self importsDefinition;\x0a\x09\x09\x09nextPutAll: String lf, String tab;\x0a\x09\x09\x09nextPutAll: 'transport: (';\x0a\x09\x09\x09nextPutAll: self transport definition, ')' ]",
 referencedClasses: ["String"],
 //>>excludeEnd("ide");
-messageSends: ["streamContents:", "nextPutAll:", "name", "class", ",", "lf", "tab", "definition", "transport"]
+messageSends: ["streamContents:", "nextPutAll:", "name", "class", ",", "lf", "tab", "importsDefinition", "definition", "transport"]
+}),
+$globals.Package);
+
+$core.addMethod(
+$core.method({
+selector: "imports",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $2,$1,$receiver;
+$2=self["@imports"];
+if(($receiver = $2) == null || $receiver.isNil){
+var parsed;
+parsed=self._importsFromJson_(self._basicImports());
+parsed;
+self._imports_(parsed);
+$1=self["@imports"];
+} else {
+$1=$2;
+};
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"imports",{},$globals.Package)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "imports\x0a\x09^ imports ifNil: [\x0a\x09\x09| parsed |\x0a\x09\x09parsed := self importsFromJson: self basicImports.\x0a\x09\x09self imports: parsed.\x0a\x09\x09imports ]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["ifNil:", "importsFromJson:", "basicImports", "imports:"]
+}),
+$globals.Package);
+
+$core.addMethod(
+$core.method({
+selector: "imports:",
+protocol: 'accessing',
+fn: function (anArray){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+self._validateImports_(anArray);
+self["@imports"]=$recv(anArray)._asSet();
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"imports:",{anArray:anArray},$globals.Package)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anArray"],
+source: "imports: anArray\x0a\x09self validateImports: anArray.\x0a\x09imports := anArray asSet",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["validateImports:", "asSet"]
+}),
+$globals.Package);
+
+$core.addMethod(
+$core.method({
+selector: "importsAsJson",
+protocol: 'converting',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $2,$1;
+$1=$recv(self._sortedImportsAsArray())._collect_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$2=$recv(each)._isString();
+if($core.assert($2)){
+return each;
+} else {
+return $recv($recv($recv(each)._key()).__comma("=")).__comma($recv(each)._value());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx[","]=1;
+//>>excludeEnd("ctx");
+};
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"importsAsJson",{},$globals.Package)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "importsAsJson\x0a\x0a\x09^ self sortedImportsAsArray collect: [ :each |\x0a\x09\x09each isString\x0a\x09\x09\x09ifTrue: [ each ]\x0a\x09\x09\x09ifFalse: [ each key, '=', each value ]]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["collect:", "sortedImportsAsArray", "ifTrue:ifFalse:", "isString", ",", "key", "value"]
+}),
+$globals.Package);
+
+$core.addMethod(
+$core.method({
+selector: "importsDefinition",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+function $String(){return $globals.String||(typeof String=="undefined"?nil:String)}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+$1=$recv($String())._streamContents_((function(stream){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$recv(stream)._nextPutAll_("{");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["nextPutAll:"]=1;
+//>>excludeEnd("ctx");
+$recv(self._imports())._do_separatedBy_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx3) {
+//>>excludeEnd("ctx");
+return $recv(stream)._nextPutAll_($recv(each)._importsString());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx3.sendIdx["nextPutAll:"]=2;
+//>>excludeEnd("ctx");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx3) {$ctx3.fillBlock({each:each},$ctx2,2)});
+//>>excludeEnd("ctx");
+}),(function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx3) {
+//>>excludeEnd("ctx");
+return $recv(stream)._nextPutAll_(". ");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx3.sendIdx["nextPutAll:"]=3;
+//>>excludeEnd("ctx");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2,3)});
+//>>excludeEnd("ctx");
+}));
+return $recv(stream)._nextPutAll_("}");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({stream:stream},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"importsDefinition",{},$globals.Package)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "importsDefinition\x0a\x09^ String streamContents: [ :stream |\x0a\x09\x09stream nextPutAll: '{'.\x0a\x09\x09self imports\x0a\x09\x09\x09do: [ :each | stream nextPutAll: each importsString ]\x0a\x09\x09\x09separatedBy: [ stream nextPutAll: '. ' ].\x0a\x09\x09stream nextPutAll: '}' ]",
+referencedClasses: ["String"],
+//>>excludeEnd("ide");
+messageSends: ["streamContents:", "nextPutAll:", "do:separatedBy:", "imports", "importsString"]
+}),
+$globals.Package);
+
+$core.addMethod(
+$core.method({
+selector: "importsFromJson:",
+protocol: 'converting',
+fn: function (anArray){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $2,$1;
+$1=$recv(anArray)._collect_((function(each){
+var split;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+split=$recv(each)._tokenize_("=");
+split;
+$2=$recv($recv(split)._size()).__eq((1));
+if($core.assert($2)){
+return $recv(split)._first();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["first"]=1;
+//>>excludeEnd("ctx");
+} else {
+return $recv($recv(split)._first()).__minus_gt($recv(split)._second());
+};
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({each:each,split:split},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"importsFromJson:",{anArray:anArray},$globals.Package)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anArray"],
+source: "importsFromJson: anArray\x0a\x09\x22Parses array of string, eg. #('asdf' 'qwer=tyuo')\x0a\x09into array of Strings and Associations,\x0a\x09eg. {'asdf'. 'qwer'->'tyuo'}\x22\x0a\x0a\x09^ anArray collect: [ :each |\x0a\x09\x09| split |\x0a\x09\x09split := each tokenize: '='.\x0a\x09\x09split size = 1\x0a\x09\x09\x09ifTrue: [ split first ]\x0a\x09\x09\x09ifFalse: [ split first -> split second ]]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["collect:", "tokenize:", "ifTrue:ifFalse:", "=", "size", "first", "->", "second"]
 }),
 $globals.Package);
 
@@ -2705,6 +2958,74 @@ messageSends: ["sortedClasses:", "class", "classes"]
 }),
 $globals.Package);
 
+$core.addMethod(
+$core.method({
+selector: "sortedImportsAsArray",
+protocol: 'private',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $4,$3,$5,$2,$7,$6,$8,$1;
+$1=$recv($recv(self._imports())._asArray())._sorted_((function(a,b){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$4=$recv(a)._isString();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["isString"]=1;
+//>>excludeEnd("ctx");
+$3=$recv($4)._not();
+$5=$recv(b)._isString();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["isString"]=2;
+//>>excludeEnd("ctx");
+$2=$recv($3).__and($5);
+return $recv($2)._or_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx3) {
+//>>excludeEnd("ctx");
+$7=$recv(a)._isString();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx3.sendIdx["isString"]=3;
+//>>excludeEnd("ctx");
+$6=$recv($7).__eq($recv(b)._isString());
+return $recv($6)._and_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx4) {
+//>>excludeEnd("ctx");
+$8=$recv(a)._value();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx4.sendIdx["value"]=1;
+//>>excludeEnd("ctx");
+return $recv($8).__lt_eq($recv(b)._value());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx4) {$ctx4.fillBlock({},$ctx3,3)});
+//>>excludeEnd("ctx");
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2,2)});
+//>>excludeEnd("ctx");
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({a:a,b:b},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"sortedImportsAsArray",{},$globals.Package)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "sortedImportsAsArray\x0a\x09\x22Answer imports sorted first by type (associations first),\x0a\x09then by value\x22\x0a\x0a\x09^ self imports asArray\x0a\x09\x09sorted: [ :a :b |\x0a\x09\x09\x09a isString not & b isString or: [\x0a\x09\x09\x09\x09a isString = b isString and: [\x0a\x09\x09\x09\x09\x09a value <= b value ]]]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["sorted:", "asArray", "imports", "or:", "&", "not", "isString", "and:", "=", "<=", "value"]
+}),
+$globals.Package);
+
 $core.addMethod(
 $core.method({
 selector: "transport",
@@ -2765,6 +3086,70 @@ messageSends: ["package:"]
 }),
 $globals.Package);
 
+$core.addMethod(
+$core.method({
+selector: "validateImports:",
+protocol: 'validation',
+fn: function (aCollection){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$2,$5,$4,$3,$6;
+$recv(aCollection)._do_((function(import_){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$1=$recv(import_)._isString();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["isString"]=1;
+//>>excludeEnd("ctx");
+if(!$core.assert($1)){
+$2=$recv(import_)._respondsTo_("key");
+if(!$core.assert($2)){
+self._error_("Imports must be Strings or Associations");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["error:"]=1;
+//>>excludeEnd("ctx");
+};
+$5=$recv(import_)._key();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["key"]=1;
+//>>excludeEnd("ctx");
+$4=$recv($5)._isString();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["isString"]=2;
+//>>excludeEnd("ctx");
+$3=$recv($4).__and($recv($recv(import_)._value())._isString());
+if(!$core.assert($3)){
+self._error_("Key and value must be Strings");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["error:"]=2;
+//>>excludeEnd("ctx");
+};
+$6=$recv($recv(import_)._key())._match_("^[a-zA-Z][a-zA-Z0-9]*$");
+if(!$core.assert($6)){
+return self._error_("Keys must be identifiers");
+};
+};
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({import_:import_},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"validateImports:",{aCollection:aCollection},$globals.Package)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aCollection"],
+source: "validateImports: aCollection\x0a\x0a\x09aCollection do: [ :import |\x0a\x09\x09import isString ifFalse: [\x0a\x09\x09\x09(import respondsTo: #key) ifFalse: [\x0a\x09\x09\x09\x09self error: 'Imports must be Strings or Associations' ].\x0a\x09\x09\x09import key isString & import value isString ifFalse: [\x0a\x09\x09\x09\x09self error: 'Key and value must be Strings' ].\x0a\x09\x09\x09(import key match: '^[a-zA-Z][a-zA-Z0-9]*$') ifFalse: [\x0a\x09\x09\x09\x09self error: 'Keys must be identifiers' ]]]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["do:", "ifFalse:", "isString", "respondsTo:", "error:", "&", "key", "value", "match:"]
+}),
+$globals.Package);
+
 
 $globals.Package.klass.iVarNames = ['defaultCommitPathJs','defaultCommitPathSt'];
 $core.addMethod(
@@ -2827,6 +3212,35 @@ messageSends: ["packageAt:ifAbsent:"]
 }),
 $globals.Package.klass);
 
+$core.addMethod(
+$core.method({
+selector: "named:imports:transport:",
+protocol: 'accessing',
+fn: function (aPackageName,anArray,aTransport){
+var self=this;
+var package_;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+package_=self._named_(aPackageName);
+$recv(package_)._imports_(anArray);
+$recv(package_)._transport_(aTransport);
+$1=package_;
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"named:imports:transport:",{aPackageName:aPackageName,anArray:anArray,aTransport:aTransport,package_:package_},$globals.Package.klass)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aPackageName", "anArray", "aTransport"],
+source: "named: aPackageName imports: anArray transport: aTransport\x0a\x09| package |\x0a\x09\x0a\x09package := self named: aPackageName.\x0a\x09package imports: anArray.\x0a\x09package transport: aTransport.\x0a\x09\x0a\x09^ package",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["named:", "imports:", "transport:"]
+}),
+$globals.Package.klass);
+
 $core.addMethod(
 $core.method({
 selector: "named:transport:",
@@ -5118,6 +5532,39 @@ messageSends: ["shouldNotImplement"]
 }),
 $globals.SmalltalkImage.klass);
 
+$core.addMethod(
+$core.method({
+selector: "importsString",
+protocol: '*Kernel-Infrastructure',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $3,$2,$1;
+$3=$recv(self._key())._importsString();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["importsString"]=1;
+//>>excludeEnd("ctx");
+$2=$recv($3).__comma(" -> ");
+$1=$recv($2).__comma($recv(self._value())._importsString());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx[","]=1;
+//>>excludeEnd("ctx");
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"importsString",{},$globals.Association)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "importsString\x0a\x09\x22This is for use by package exporter.\x0a\x09It can fail for non-string keys and values.\x22\x0a\x0a\x09^ self key importsString, ' -> ', self value importsString",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: [",", "importsString", "key", "value"]
+}),
+$globals.Association);
+
 $core.addMethod(
 $core.method({
 selector: "do:displayingProgress:",
@@ -5219,6 +5666,34 @@ messageSends: ["at:ifAbsent:"]
 }),
 $globals.String);
 
+$core.addMethod(
+$core.method({
+selector: "importsString",
+protocol: '*Kernel-Infrastructure',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+$1=$recv("'".__comma(self._replace_with_("'","''"))).__comma("'");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx[","]=1;
+//>>excludeEnd("ctx");
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"importsString",{},$globals.String)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "importsString\x0a\x09\x22Answer receiver as Smalltalk expression\x22\x0a\x09^ '''', (self replace: '''' with: ''''''), ''''",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: [",", "replace:with:"]
+}),
+$globals.String);
+
 $core.addMethod(
 $core.method({
 selector: "settingValue",

+ 106 - 3
src/Kernel-Infrastructure.st

@@ -526,7 +526,7 @@ Organizer subclass: #PackageOrganizer
 I am an organizer specific to packages. I hold classes categorization information.!
 
 Object subclass: #Package
-	instanceVariableNames: 'transport dirty'
+	instanceVariableNames: 'transport imports dirty'
 	package: 'Kernel-Infrastructure'!
 !Package commentStamp!
 I am similar to a "class category" typically found in other Smalltalks like Pharo or Squeak. Amber does not have class categories anymore, it had in the beginning but now each class in the system knows which package it belongs to.
@@ -583,13 +583,38 @@ definition
 		stream 
 			nextPutAll: self class name;
 			nextPutAll: String lf, String tab;
-			nextPutAll: ' named: ';
+			nextPutAll: 'named: ';
 			nextPutAll: '''', self name, '''';
 			nextPutAll: String lf, String tab;
-			nextPutAll:  ' transport: (';
+			nextPutAll: 'imports: ';
+			nextPutAll: self importsDefinition;
+			nextPutAll: String lf, String tab;
+			nextPutAll: 'transport: (';
 			nextPutAll: self transport definition, ')' ]
 !
 
+imports
+	^ imports ifNil: [
+		| parsed |
+		parsed := self importsFromJson: self basicImports.
+		self imports: parsed.
+		imports ]
+!
+
+imports: anArray
+	self validateImports: anArray.
+	imports := anArray asSet
+!
+
+importsDefinition
+	^ String streamContents: [ :stream |
+		stream nextPutAll: '{'.
+		self imports
+			do: [ :each | stream nextPutAll: each importsString ]
+			separatedBy: [ stream nextPutAll: '. ' ].
+		stream nextPutAll: '}' ]
+!
+
 name
 	<return self.pkgName>
 !
@@ -633,6 +658,29 @@ sortedClasses
 	^ self class sortedClasses: self classes
 ! !
 
+!Package methodsFor: 'converting'!
+
+importsAsJson
+
+	^ self sortedImportsAsArray collect: [ :each |
+		each isString
+			ifTrue: [ each ]
+			ifFalse: [ each key, '=', each value ]]
+!
+
+importsFromJson: anArray
+	"Parses array of string, eg. #('asdf' 'qwer=tyuo')
+	into array of Strings and Associations,
+	eg. {'asdf'. 'qwer'->'tyuo'}"
+
+	^ anArray collect: [ :each |
+		| split |
+		split := each tokenize: '='.
+		split size = 1
+			ifTrue: [ split first ]
+			ifFalse: [ split first -> split second ]]
+! !
+
 !Package methodsFor: 'dependencies'!
 
 loadDependencies
@@ -671,6 +719,12 @@ printOn: aStream
 
 !Package methodsFor: 'private'!
 
+basicImports
+	"Answer the imports literal JavaScript object as setup in the JavaScript file, if any"
+	
+	<return self.imports || []>
+!
+
 basicName: aString
 	<self.pkgName = aString>
 !
@@ -679,6 +733,17 @@ basicTransport
 	"Answer the transport literal JavaScript object as setup in the JavaScript file, if any"
 	
 	<return self.transport>
+!
+
+sortedImportsAsArray
+	"Answer imports sorted first by type (associations first),
+	then by value"
+
+	^ self imports asArray
+		sorted: [ :a :b |
+			a isString not & b isString or: [
+				a isString = b isString and: [
+					a value <= b value ]]]
 ! !
 
 !Package methodsFor: 'testing'!
@@ -691,6 +756,20 @@ isPackage
 	^ true
 ! !
 
+!Package methodsFor: 'validation'!
+
+validateImports: aCollection
+
+	aCollection do: [ :import |
+		import isString ifFalse: [
+			(import respondsTo: #key) ifFalse: [
+				self error: 'Imports must be Strings or Associations' ].
+			import key isString & import value isString ifFalse: [
+				self error: 'Key and value must be Strings' ].
+			(import key match: '^[a-zA-Z][a-zA-Z0-9]*$') ifFalse: [
+				self error: 'Keys must be identifiers' ]]]
+! !
+
 Package class instanceVariableNames: 'defaultCommitPathJs defaultCommitPathSt'!
 
 !Package class methodsFor: 'accessing'!
@@ -706,6 +785,16 @@ named: aPackageName ifAbsent: aBlock
 	^ Smalltalk packageAt: aPackageName ifAbsent: aBlock
 !
 
+named: aPackageName imports: anArray transport: aTransport
+	| package |
+	
+	package := self named: aPackageName.
+	package imports: anArray.
+	package transport: aTransport.
+	
+	^ package
+!
+
 named: aPackageName transport: aTransport
 	| package |
 	
@@ -1364,6 +1453,15 @@ new
 	self shouldNotImplement
 ! !
 
+!Association methodsFor: '*Kernel-Infrastructure'!
+
+importsString
+	"This is for use by package exporter.
+	It can fail for non-string keys and values."
+
+	^ self key importsString, ' -> ', self value importsString
+! !
+
 !SequenceableCollection methodsFor: '*Kernel-Infrastructure'!
 
 do: aBlock displayingProgress: aString
@@ -1391,6 +1489,11 @@ asSettingIfAbsent: aDefaultValue
 	^ Setting at: self ifAbsent: aDefaultValue
 !
 
+importsString
+	"Answer receiver as Smalltalk expression"
+	^ '''', (self replace: '''' with: ''''''), ''''
+!
+
 settingValue
 	^ self asSetting value
 !