From cac1f7d83b270f9c1afeffc2526c1664e4bba474 Mon Sep 17 00:00:00 2001 From: Petr Pucil Date: Wed, 14 Feb 2024 21:01:07 +0100 Subject: [PATCH] JS: enable circular imports by exporting object (not function) Resolves https://github.com/kaitai-io/kaitai_struct/issues/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 https://github.com/kaitai-io/kaitai_struct/issues/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. --- .../struct/languages/JavaScriptCompiler.scala | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala index 17d6db711..e124f1fc4 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala @@ -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 = { @@ -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 = { @@ -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) @@ -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)" } }