Skip to content

Commit

Permalink
JS: enable circular imports by exporting object (not function)
Browse files Browse the repository at this point in the history
Resolves kaitai-io/kaitai_struct#1074

This change breaks backward compatibility with 0.10 and older, but
allows for circular imports and out-of-order module loading in a
"browser globals" context (the latter is relevant in the Web IDE, as
explained at kaitai-io/kaitai_struct#1074). In
short, it does this by switching from the UMD envelope
[returnExports.js](https://github.com/umdjs/umd/blob/36fd113/templates/returnExports.js#L17-L37)
to modified
[commonjsStrict.js](https://github.com/umdjs/umd/blob/36fd113/templates/commonjsStrict.js#L19-L36).

The BC break is that until now the generated modules exported the
constructor function directly, whereas now they export the object
containing the constructor function under the only object key that
matches the format module name. The same behavior is expected from
imported opaque types and custom processors as well.
  • Loading branch information
generalmimon committed Feb 14, 2024
1 parent 3af7a08 commit cac1f7d
Showing 1 changed file with 19 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,21 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def outImports(topClass: ClassSpec) = {
val impList = importList.toList
val quotedImpList = impList.map((x) => s"'$x'")
val defineArgs = quotedImpList.mkString(", ")
val moduleArgs = quotedImpList.map((x) => s"require($x)").mkString(", ")
val argClasses = impList.map((x) => x.split('/').last)
val rootArgs = argClasses.map((x) => s"root.$x").mkString(", ")
val defineArgs = ("'exports'" +: quotedImpList).mkString(", ")
val exportsArgs = ("exports" +: quotedImpList.map((x) => s"require($x)")).mkString(", ")
val argClasses = types2class(topClass.name) +: impList.map((x) => x.split('/').last)
val rootArgs = argClasses.map((x) => if (x == "KaitaiStream") s"root.$x" else s"root.$x || (root.$x = {})").mkString(", ")
val factoryParams = argClasses.map((x) => if (x == "KaitaiStream") x else s"${x}_").mkString(", ")

"(function (root, factory) {\n" +
indent + "if (typeof define === 'function' && define.amd) {\n" +
indent * 2 + s"define([$defineArgs], factory);\n" +
indent + "} else if (typeof module === 'object' && module.exports) {\n" +
indent * 2 + s"module.exports = factory($moduleArgs);\n" +
indent + "} else if (typeof exports === 'object' && exports !== null && typeof exports.nodeType !== 'number') {\n" +
indent * 2 + s"factory($exportsArgs);\n" +
indent + "} else {\n" +
indent * 2 + s"root.${types2class(topClass.name)} = factory($rootArgs);\n" +
indent * 2 + s"factory($rootArgs);\n" +
indent + "}\n" +
s"}(typeof self !== 'undefined' ? self : this, function (${argClasses.mkString(", ")}) {"
s"})(typeof self !== 'undefined' ? self : this, function ($factoryParams) {"
}

override def fileHeader(topClassName: String): Unit = {
Expand All @@ -53,8 +54,8 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}

override def fileFooter(name: String): Unit = {
out.puts(s"return ${type2class(name)};")
out.puts("}));")
out.puts(s"${type2class(name)}_.${type2class(name)} = ${type2class(name)};")
out.puts("});")
}

override def opaqueClassDeclaration(classSpec: ClassSpec): Unit = {
Expand Down Expand Up @@ -215,7 +216,7 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)

importList.add(s"$pkgName$procClass")

out.puts(s"var _process = new $procClass(${args.map(expression).mkString(", ")});")
out.puts(s"var _process = new ${procClass}_.${procClass}(${args.map(expression).mkString(", ")});")
s"_process.decode($srcExpr)"
}
handleAssignment(varDest, expr, rep, false)
Expand Down Expand Up @@ -379,7 +380,13 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case _ => ""
}
val addParams = Utils.join(t.args.map((a) => translator.translate(a)), ", ", ", ", "")
s"new ${types2class(t.name)}($io, $parent, $root$addEndian$addParams)"
val topLevelModulePrefix =
if (t.classSpec.map((classSpec) => t.name == classSpec.name).getOrElse(false)) {
s"${type2class(t.name(0))}_."
} else {
""
}
s"new ${topLevelModulePrefix}${types2class(t.name)}($io, $parent, $root$addEndian$addParams)"
}
}

Expand Down

0 comments on commit cac1f7d

Please sign in to comment.