@@ -62,6 +62,8 @@ struct BridgeJSLink {
6262 var classLines : [ String ] = [ ]
6363 var dtsExportLines : [ String ] = [ ]
6464 var dtsClassLines : [ String ] = [ ]
65+ var namespacedFunctions : [ ExportedFunction ] = [ ]
66+ var namespacedClasses : [ ExportedClass ] = [ ]
6567
6668 if exportedSkeletons. contains ( where: { $0. classes. count > 0 } ) {
6769 classLines. append (
@@ -83,10 +85,19 @@ struct BridgeJSLink {
8385 exportsLines. append ( " \( klass. name) , " )
8486 dtsExportLines. append ( contentsOf: dtsExportEntry)
8587 dtsClassLines. append ( contentsOf: dtsType)
88+
89+ if klass. namespace != nil {
90+ namespacedClasses. append ( klass)
91+ }
8692 }
8793
8894 for function in skeleton. functions {
8995 var ( js, dts) = renderExportedFunction ( function: function)
96+
97+ if function. namespace != nil {
98+ namespacedFunctions. append ( function)
99+ }
100+
90101 js [ 0 ] = " \( function. name) : " + js[ 0 ]
91102 js [ js. count - 1 ] += " , "
92103 exportsLines. append ( contentsOf: js)
@@ -108,6 +119,36 @@ struct BridgeJSLink {
108119 importObjectBuilders. append ( importObjectBuilder)
109120 }
110121
122+ let hasNamespacedItems = !namespacedFunctions. isEmpty || !namespacedClasses. isEmpty
123+
124+ let exportsSection : String
125+ if hasNamespacedItems {
126+ let namespaceSetupCode = renderGlobalNamespace (
127+ namespacedFunctions: namespacedFunctions,
128+ namespacedClasses: namespacedClasses
129+ )
130+ . map { $0. indent ( count: 12 ) } . joined ( separator: " \n " )
131+ exportsSection = """
132+ \( classLines. map { $0. indent ( count: 12 ) } . joined ( separator: " \n " ) )
133+ const exports = {
134+ \( exportsLines. map { $0. indent ( count: 16 ) } . joined ( separator: " \n " ) )
135+ };
136+
137+ \( namespaceSetupCode)
138+
139+ return exports;
140+ },
141+ """
142+ } else {
143+ exportsSection = """
144+ \( classLines. map { $0. indent ( count: 12 ) } . joined ( separator: " \n " ) )
145+ return {
146+ \( exportsLines. map { $0. indent ( count: 16 ) } . joined ( separator: " \n " ) )
147+ };
148+ },
149+ """
150+ }
151+
111152 let outputJs = """
112153 // NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
113154 // DO NOT EDIT.
@@ -169,15 +210,13 @@ struct BridgeJSLink {
169210 /** @param {WebAssembly.Instance} instance */
170211 createExports: (instance) => {
171212 const js = swift.memory.heap;
172- \( classLines. map { $0. indent ( count: 12 ) } . joined ( separator: " \n " ) )
173- return {
174- \( exportsLines. map { $0. indent ( count: 16 ) } . joined ( separator: " \n " ) )
175- };
176- },
213+ \( exportsSection)
177214 }
178215 }
179216 """
217+
180218 var dtsLines : [ String ] = [ ]
219+ dtsLines. append ( contentsOf: namespaceDeclarations ( ) )
181220 dtsLines. append ( contentsOf: dtsClassLines)
182221 dtsLines. append ( " export type Exports = { " )
183222 dtsLines. append ( contentsOf: dtsExportLines. map { $0. indent ( count: 4 ) } )
@@ -204,6 +243,102 @@ struct BridgeJSLink {
204243 return ( outputJs, outputDts)
205244 }
206245
246+ private func namespaceDeclarations( ) -> [ String ] {
247+ var dtsLines : [ String ] = [ ]
248+ var namespaceFunctions : [ String : [ ExportedFunction ] ] = [ : ]
249+ var namespaceClasses : [ String : [ ExportedClass ] ] = [ : ]
250+
251+ for skeleton in exportedSkeletons {
252+ for function in skeleton. functions {
253+ if let namespace = function. namespace {
254+ let namespaceKey = namespace. joined ( separator: " . " )
255+ if namespaceFunctions [ namespaceKey] == nil {
256+ namespaceFunctions [ namespaceKey] = [ ]
257+ }
258+ namespaceFunctions [ namespaceKey] ? . append ( function)
259+ }
260+ }
261+
262+ for klass in skeleton. classes {
263+ if let classNamespace = klass. namespace {
264+ let namespaceKey = classNamespace. joined ( separator: " . " )
265+ if namespaceClasses [ namespaceKey] == nil {
266+ namespaceClasses [ namespaceKey] = [ ]
267+ }
268+ namespaceClasses [ namespaceKey] ? . append ( klass)
269+ }
270+ }
271+ }
272+
273+ guard !namespaceFunctions. isEmpty || !namespaceClasses. isEmpty else { return dtsLines }
274+
275+ dtsLines. append ( " export {}; " )
276+ dtsLines. append ( " " )
277+ dtsLines. append ( " declare global { " )
278+
279+ let identBaseSize = 4
280+
281+ for (namespacePath, classes) in namespaceClasses. sorted ( by: { $0. key < $1. key } ) {
282+ let parts = namespacePath. split ( separator: " . " ) . map ( String . init)
283+
284+ for i in 0 ..< parts. count {
285+ dtsLines. append ( " namespace \( parts [ i] ) { " . indent ( count: identBaseSize * ( i + 1 ) ) )
286+ }
287+
288+ for klass in classes {
289+ dtsLines. append ( " class \( klass. name) { " . indent ( count: identBaseSize * ( parts. count + 1 ) ) )
290+
291+ if let constructor = klass. constructor {
292+ let constructorSignature =
293+ " constructor( \( constructor. parameters. map { " \( $0. name) : \( $0. type. tsType) " } . joined ( separator: " , " ) ) ); "
294+ dtsLines. append ( " \( constructorSignature) " . indent ( count: identBaseSize * ( parts. count + 2 ) ) )
295+ }
296+
297+ for method in klass. methods {
298+ let methodSignature =
299+ " \( method. name) \( renderTSSignature ( parameters: method. parameters, returnType: method. returnType) ) ; "
300+ dtsLines. append ( " \( methodSignature) " . indent ( count: identBaseSize * ( parts. count + 2 ) ) )
301+ }
302+
303+ dtsLines. append ( " } " . indent ( count: identBaseSize * ( parts. count + 1 ) ) )
304+ }
305+
306+ for i in ( 0 ..< parts. count) . reversed ( ) {
307+ dtsLines. append ( " } " . indent ( count: identBaseSize * ( i + 1 ) ) )
308+ }
309+ }
310+
311+ for (namespacePath, functions) in namespaceFunctions. sorted ( by: { $0. key < $1. key } ) {
312+ let parts = namespacePath. split ( separator: " . " ) . map ( String . init)
313+
314+ var namespaceExists = false
315+ if namespaceClasses [ namespacePath] != nil {
316+ namespaceExists = true
317+ } else {
318+ for i in 0 ..< parts. count {
319+ dtsLines. append ( " namespace \( parts [ i] ) { " . indent ( count: identBaseSize * ( i + 1 ) ) )
320+ }
321+ }
322+
323+ for function in functions {
324+ let signature =
325+ " function \( function. name) \( renderTSSignature ( parameters: function. parameters, returnType: function. returnType) ) ; "
326+ dtsLines. append ( " \( signature) " . indent ( count: identBaseSize * ( parts. count + 1 ) ) )
327+ }
328+
329+ if !namespaceExists {
330+ for i in ( 0 ..< parts. count) . reversed ( ) {
331+ dtsLines. append ( " } " . indent ( count: identBaseSize * ( i + 1 ) ) )
332+ }
333+ }
334+ }
335+
336+ dtsLines. append ( " } " )
337+ dtsLines. append ( " " )
338+
339+ return dtsLines
340+ }
341+
207342 class ExportedThunkBuilder {
208343 var bodyLines : [ String ] = [ ]
209344 var cleanupLines : [ String ] = [ ]
@@ -396,6 +531,53 @@ struct BridgeJSLink {
396531 return ( jsLines, dtsTypeLines, dtsExportEntryLines)
397532 }
398533
534+ func renderGlobalNamespace( namespacedFunctions: [ ExportedFunction ] , namespacedClasses: [ ExportedClass ] ) -> [ String ]
535+ {
536+ var lines : [ String ] = [ ]
537+ var uniqueNamespaces : [ String ] = [ ]
538+ var seen = Set < String > ( )
539+
540+ let functionNamespacePaths : Set < [ String ] > = Set (
541+ namespacedFunctions
542+ . compactMap { $0. namespace }
543+ )
544+ let classNamespacePaths : Set < [ String ] > = Set (
545+ namespacedClasses
546+ . compactMap { $0. namespace }
547+ )
548+
549+ let allNamespacePaths =
550+ functionNamespacePaths
551+ . union ( classNamespacePaths)
552+
553+ allNamespacePaths. forEach { namespacePath in
554+ namespacePath. makeIterator ( ) . enumerated ( ) . forEach { ( index, _) in
555+ let path = namespacePath [ 0 ... index] . joined ( separator: " . " )
556+ if seen. insert ( path) . inserted {
557+ uniqueNamespaces. append ( path)
558+ }
559+ }
560+ }
561+
562+ uniqueNamespaces. sorted ( ) . forEach { namespace in
563+ lines. append ( " if (typeof globalThis. \( namespace) === 'undefined') { " )
564+ lines. append ( " globalThis. \( namespace) = {}; " )
565+ lines. append ( " } " )
566+ }
567+
568+ namespacedClasses. forEach { klass in
569+ let namespacePath : String = klass. namespace? . joined ( separator: " . " ) ?? " "
570+ lines. append ( " globalThis. \( namespacePath) . \( klass. name) = exports. \( klass. name) ; " )
571+ }
572+
573+ namespacedFunctions. forEach { function in
574+ let namespacePath : String = function. namespace? . joined ( separator: " . " ) ?? " "
575+ lines. append ( " globalThis. \( namespacePath) . \( function. name) = exports. \( function. name) ; " )
576+ }
577+
578+ return lines
579+ }
580+
399581 class ImportedThunkBuilder {
400582 var bodyLines : [ String ] = [ ]
401583 var parameterNames : [ String ] = [ ]
0 commit comments