From c288216a6041abed7e0585435ca95ee0a7645359 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Mon, 25 Dec 2017 11:00:36 +0800 Subject: [PATCH 1/4] Remove mentions of [PrimaryGlobal] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4c24973..c7da3a45 100644 --- a/README.md +++ b/README.md @@ -360,7 +360,7 @@ Notable missing features include: - `maplike<>` and `setlike<>` - `[AllowShared]` - `[Default]` (for `toJSON()` operations) -- `[Global]` and `[PrimaryGlobal]`'s various consequences, including the named properties object and `[[SetPrototypeOf]]` +- `[Global]`'s various consequences, including the named properties object and `[[SetPrototypeOf]]` - `[LegacyWindowAlias]` - `[LenientSetter]` - `[LenientThis]` From a7a9d460054f2ceff7e9c1bd4a2b14b4a6661432 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Mon, 25 Dec 2017 11:33:43 +0800 Subject: [PATCH 2/4] Make "expose" export a function --- README.md | 20 +- lib/constructs/interface.js | 62 +++--- lib/utils.js | 12 ++ test/__snapshots__/test.js.snap | 346 ++++++++++++++++++++++++++------ 4 files changed, 335 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index c7da3a45..e7f9917e 100644 --- a/README.md +++ b/README.md @@ -173,23 +173,11 @@ jsdom does this for `Window`, which is written in custom, non-webidl2js-generate This export is the wrapper class interface, suitable for example for putting on a global scope or exporting to module consumers who don't know anything about webidl2js. -#### `expose` +#### `expose(globalName, obj)` -This export contains information about where an interface is supposed to be exposed as a property. It takes into account the Web IDL extended attributes `[Expose]` and `[NoInterfaceObject]` to generate a data structure of the form: +This function allows the interface object to be automatically exposed on a global object `obj`, taking into account the Web IDL extended attributes `[Expose]` and `[NoInterfaceObject]`. The `globalName` parameter specifies the [global name](https://heycam.github.io/webidl/#dfn-global-name) of the interface the provided global object implements, such as `Window`, `Worker`, and `Worklet`. -```js -{ - nameOfGlobal1: { - nameOfInterface: InterfaceClass - }, - nameOfGlobal2: { - nameOfInterface: InterfaceClass - }, - // etc. -} -``` - -This format may seem a bit verbose, but eventually when we support `[NamedConstructor]`, there will be potentially more than one key/value pair per global, and it will show its worth. +A limitation of the current implementation of `expose()` is that it does not yet support the `[SecureContext]` extended attribute, and all members of the interface are exposed regardless of the origin of that global object. This is expected to be remedied in the future. ### For dictionaries @@ -342,7 +330,7 @@ webidl2js is implementing an ever-growing subset of the Web IDL specification. S - `[Clamp]` - `[Constructor]` - `[EnforceRange]` -- `[Exposed]` and `[NoInterfaceObject]` (by exporting metadata on where/whether it is exposed) +- `[Exposed]` and `[NoInterfaceObject]` (through the exported `expose()` function) - `[LegacyArrayClass]` - `[LegacyUnenumerableNamedProperties]` - `[OverrideBuiltins]` diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index 3401ece3..e9c9d983 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -1086,32 +1086,6 @@ class Interface { } generateIface() { - const shouldExposeRoot = !utils.getExtAttr(this.idl.extAttrs, "NoInterfaceObject"); - - const exposedMap = {}; - if (shouldExposeRoot) { - let exposedOn = ["Window"]; - const exposedAttrs = this.idl.extAttrs - .filter(attr => attr.name === "Exposed"); - if (exposedAttrs.length !== 0) { - if (typeof exposedAttrs[0].rhs.value === "string") { - exposedAttrs[0].rhs.value = [exposedAttrs[0].rhs.value]; - } - exposedOn = exposedAttrs[0].rhs.value; - } - for (let i = 0; i < exposedOn.length; ++i) { - if (!exposedMap[exposedOn[i]]) { - exposedMap[exposedOn[i]] = []; - } - exposedMap[exposedOn[i]].push(this.name); - } - } - - const exposers = []; - for (let keys = Object.keys(exposedMap), i = 0; i < keys.length; ++i) { - exposers.push(keys[i] + ": { " + exposedMap[keys[i]].join(", ") + " }"); - } - this.str += ` create(constructorArgs, privateData) { let obj = Object.create(${this.name}.prototype); @@ -1172,8 +1146,40 @@ class Interface { return obj; }, interface: ${this.name}, - expose: { - ${exposers.join(",\n ")} + expose(globalName, obj) { + `; + + let exposedOn = []; + if (!utils.getExtAttr(this.idl.extAttrs, "NoInterfaceObject")) { + const exposedAttr = utils.getExtAttr(this.idl.extAttrs, "Exposed"); + if (exposedAttr) { + exposedOn = utils.getIdentifiers(exposedAttr); + } else { + exposedOn = ["Window"]; + } + } + + if (exposedOn.length > 0) { + this.str += ` + switch (globalName) { + `; + + for (const globalName of exposedOn) { + this.str += `case ${JSON.stringify(globalName)}:`; + } + + this.str += ` + Object.defineProperty(obj, ${JSON.stringify(this.name)}, { + value: ${this.name}, + writable: true, + enumerable: false, + configurable: true + }); + } + `; + } + + this.str += ` } `; } diff --git a/lib/utils.js b/lib/utils.js index ba0fd425..3de797ef 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -28,6 +28,17 @@ function getExtAttr(attrs, name) { return null; } +// https://heycam.github.io/webidl/#legacywindowalias-identifier +function getIdentifiers(extAttr) { + let identifiers; + if (typeof extAttr.rhs.value === "string") { + identifiers = [extAttr.rhs.value]; + } else { + identifiers = extAttr.rhs.value; + } + return identifiers; +} + function isGlobal(idl) { return Boolean(getExtAttr(idl.extAttrs, "Global")); } @@ -92,6 +103,7 @@ class RequiresMap extends Map { module.exports = { getDefault, getExtAttr, + getIdentifiers, isGlobal, isOnInstance, stringifyPropertyName, diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 2d956941..b154c341 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -189,8 +189,16 @@ const iface = { return obj; }, interface: DictionaryConvert, - expose: { - Window: { DictionaryConvert } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"DictionaryConvert\\", { + value: DictionaryConvert, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -328,8 +336,16 @@ const iface = { return obj; }, interface: Enum, - expose: { - Window: { Enum } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Enum\\", { + value: Enum, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -424,8 +440,16 @@ module.exports = { return obj; }, interface: Factory, - expose: { - Window: { Factory } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Factory\\", { + value: Factory, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface return iface; @@ -643,8 +667,16 @@ const iface = { return obj; }, interface: Global, - expose: { - Window: { Global } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Global\\", { + value: Global, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -748,8 +780,16 @@ const iface = { return obj; }, interface: LegacyArrayClass, - expose: { - Window: { LegacyArrayClass } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"LegacyArrayClass\\", { + value: LegacyArrayClass, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -985,8 +1025,16 @@ const iface = { return obj; }, interface: MixedIn, - expose: { - Window: { MixedIn } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"MixedIn\\", { + value: MixedIn, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -1156,7 +1204,7 @@ const iface = { return obj; }, interface: Mixin, - expose: {} + expose(globalName, obj) {} }; // iface module.exports = iface; @@ -1292,7 +1340,7 @@ const iface = { return obj; }, interface: MixinInherited, - expose: {} + expose(globalName, obj) {} }; // iface module.exports = iface; @@ -1417,7 +1465,7 @@ const iface = { return obj; }, interface: MixinMixin, - expose: {} + expose(globalName, obj) {} }; // iface module.exports = iface; @@ -1819,8 +1867,16 @@ const iface = { return obj; }, interface: Overloads, - expose: { - Window: { Overloads } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Overloads\\", { + value: Overloads, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -1964,8 +2020,16 @@ const iface = { return obj; }, interface: PromiseTypes, - expose: { - Window: { PromiseTypes } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"PromiseTypes\\", { + value: PromiseTypes, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -2194,8 +2258,16 @@ const iface = { return obj; }, interface: Reflect, - expose: { - Window: { Reflect } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Reflect\\", { + value: Reflect, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -2522,8 +2594,16 @@ const iface = { return obj; }, interface: SeqAndRec, - expose: { - Window: { SeqAndRec } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"SeqAndRec\\", { + value: SeqAndRec, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -2658,8 +2738,16 @@ const iface = { return obj; }, interface: Static, - expose: { - Window: { Static } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Static\\", { + value: Static, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -3022,8 +3110,16 @@ const iface = { return obj; }, interface: Storage, - expose: { - Window: { Storage } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Storage\\", { + value: Storage, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -3134,8 +3230,16 @@ const iface = { return obj; }, interface: StringifierAttribute, - expose: { - Window: { StringifierAttribute } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"StringifierAttribute\\", { + value: StringifierAttribute, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -3238,8 +3342,16 @@ const iface = { return obj; }, interface: StringifierDefaultOperation, - expose: { - Window: { StringifierDefaultOperation } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"StringifierDefaultOperation\\", { + value: StringifierDefaultOperation, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -3351,8 +3463,16 @@ const iface = { return obj; }, interface: StringifierNamedOperation, - expose: { - Window: { StringifierNamedOperation } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"StringifierNamedOperation\\", { + value: StringifierNamedOperation, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -3455,8 +3575,16 @@ const iface = { return obj; }, interface: StringifierOperation, - expose: { - Window: { StringifierOperation } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"StringifierOperation\\", { + value: StringifierOperation, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -3960,8 +4088,16 @@ const iface = { return obj; }, interface: TypedefsAndUnions, - expose: { - Window: { TypedefsAndUnions } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"TypedefsAndUnions\\", { + value: TypedefsAndUnions, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -4298,9 +4434,17 @@ const iface = { return obj; }, interface: URL, - expose: { - Window: { URL }, - Worker: { URL } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + case \\"Worker\\": + Object.defineProperty(obj, \\"URL\\", { + value: URL, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -4588,8 +4732,16 @@ const iface = { return obj; }, interface: URLList, - expose: { - Window: { URLList } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"URLList\\", { + value: URLList, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -5023,9 +5175,17 @@ const iface = { return obj; }, interface: URLSearchParams, - expose: { - Window: { URLSearchParams }, - Worker: { URLSearchParams } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + case \\"Worker\\": + Object.defineProperty(obj, \\"URLSearchParams\\", { + value: URLSearchParams, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -5363,8 +5523,16 @@ const iface = { return obj; }, interface: URLSearchParamsCollection, - expose: { - Window: { URLSearchParamsCollection } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"URLSearchParamsCollection\\", { + value: URLSearchParamsCollection, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -5678,8 +5846,16 @@ const iface = { return obj; }, interface: URLSearchParamsCollection2, - expose: { - Window: { URLSearchParamsCollection2 } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"URLSearchParamsCollection2\\", { + value: URLSearchParamsCollection2, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -5888,8 +6064,16 @@ const iface = { return obj; }, interface: UnderscoredProperties, - expose: { - Window: { UnderscoredProperties } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"UnderscoredProperties\\", { + value: UnderscoredProperties, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -6068,8 +6252,16 @@ const iface = { return obj; }, interface: Unforgeable, - expose: { - Window: { Unforgeable } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Unforgeable\\", { + value: Unforgeable, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -6353,8 +6545,16 @@ const iface = { return obj; }, interface: UnforgeableMap, - expose: { - Window: { UnforgeableMap } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"UnforgeableMap\\", { + value: UnforgeableMap, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -6470,8 +6670,16 @@ const iface = { return obj; }, interface: Unscopable, - expose: { - Window: { Unscopable } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Unscopable\\", { + value: Unscopable, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -6713,8 +6921,16 @@ const iface = { return obj; }, interface: Variadic, - expose: { - Window: { Variadic } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"Variadic\\", { + value: Variadic, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; @@ -6808,8 +7024,16 @@ const iface = { return obj; }, interface: ZeroArgConstructor, - expose: { - Window: { ZeroArgConstructor } + expose(globalName, obj) { + switch (globalName) { + case \\"Window\\": + Object.defineProperty(obj, \\"ZeroArgConstructor\\", { + value: ZeroArgConstructor, + writable: true, + enumerable: false, + configurable: true + }); + } } }; // iface module.exports = iface; From 0e3ee10578d557ba772b5478530bb171414b1cf4 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Mon, 25 Dec 2017 11:49:42 +0800 Subject: [PATCH 3/4] Implement [LegacyWindowAlias] --- README.md | 4 ++-- lib/constructs/interface.js | 26 +++++++++++++++++++++++++- test/__snapshots__/test.js.snap | 8 ++++++++ test/cases/URL.webidl | 3 ++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e7f9917e..a30c3059 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ This export is the wrapper class interface, suitable for example for putting on #### `expose(globalName, obj)` -This function allows the interface object to be automatically exposed on a global object `obj`, taking into account the Web IDL extended attributes `[Expose]` and `[NoInterfaceObject]`. The `globalName` parameter specifies the [global name](https://heycam.github.io/webidl/#dfn-global-name) of the interface the provided global object implements, such as `Window`, `Worker`, and `Worklet`. +This function allows the interface object to be automatically exposed on a global object `obj`, taking into account the Web IDL extended attributes `[Expose]`, `[NoInterfaceObject]`, and `[LegacyWindowAlias]`. The `globalName` parameter specifies the [global name](https://heycam.github.io/webidl/#dfn-global-name) of the interface the provided global object implements, such as `Window`, `Worker`, and `Worklet`. A limitation of the current implementation of `expose()` is that it does not yet support the `[SecureContext]` extended attribute, and all members of the interface are exposed regardless of the origin of that global object. This is expected to be remedied in the future. @@ -333,6 +333,7 @@ webidl2js is implementing an ever-growing subset of the Web IDL specification. S - `[Exposed]` and `[NoInterfaceObject]` (through the exported `expose()` function) - `[LegacyArrayClass]` - `[LegacyUnenumerableNamedProperties]` +- `[LegacyWindowAlias]` - `[OverrideBuiltins]` - `[PutForwards]` - `[Replaceable]` @@ -349,7 +350,6 @@ Notable missing features include: - `[AllowShared]` - `[Default]` (for `toJSON()` operations) - `[Global]`'s various consequences, including the named properties object and `[[SetPrototypeOf]]` -- `[LegacyWindowAlias]` - `[LenientSetter]` - `[LenientThis]` - `[NamedConstructor]` diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index e9c9d983..cd4b0ba7 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -1164,8 +1164,32 @@ class Interface { switch (globalName) { `; + if (exposedOn.includes("Window")) { + this.str += `case "Window":`; + + const legacyWindowAlias = utils.getExtAttr(this.idl.extAttrs, "LegacyWindowAlias"); + if (legacyWindowAlias) { + const identifiers = utils.getIdentifiers(legacyWindowAlias); + for (const id of identifiers) { + this.str += ` + Object.defineProperty(obj, ${JSON.stringify(id)}, { + value: ${this.name}, + writable: true, + enumerable: false, + configurable: true + }); + `; + } + this.str += ` + // fall-through + `; + } + } + for (const globalName of exposedOn) { - this.str += `case ${JSON.stringify(globalName)}:`; + if (globalName !== "Window") { + this.str += `case ${JSON.stringify(globalName)}:`; + } } this.str += ` diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index b154c341..393271a6 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -4437,6 +4437,14 @@ const iface = { expose(globalName, obj) { switch (globalName) { case \\"Window\\": + Object.defineProperty(obj, \\"webkitURL\\", { + value: URL, + writable: true, + enumerable: false, + configurable: true + }); + + // fall-through case \\"Worker\\": Object.defineProperty(obj, \\"URL\\", { value: URL, diff --git a/test/cases/URL.webidl b/test/cases/URL.webidl index eb1efa84..c88daf84 100644 --- a/test/cases/URL.webidl +++ b/test/cases/URL.webidl @@ -1,5 +1,6 @@ [Constructor(USVString url, optional USVString base), - Exposed=(Window,Worker)] + Exposed=(Window,Worker), + LegacyWindowAlias=webkitURL] interface URL { stringifier attribute USVString href; readonly attribute USVString origin; From 11ee521176d8cd0814b410696b02a99af8bab456 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Mon, 25 Dec 2017 19:44:47 +0800 Subject: [PATCH 4/4] Add a way to expose interfaces automatically --- README.md | 6 ++++ lib/output/bundle-entry.js | 21 ++++++++++++ lib/transformer.js | 9 +++++ test/__snapshots__/test.js.snap | 58 +++++++++++++++++++++++++++++++++ test/test.js | 7 ++++ 5 files changed, 101 insertions(+) create mode 100644 lib/output/bundle-entry.js diff --git a/README.md b/README.md index a30c3059..7e34bd93 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,12 @@ This can be useful when you are given a wrapper, but need to modify its inaccess Returns the corresponding impl class instance for a given wrapper class instance, or returns the argument back if it is not an implementation class instance. +## The generated `bundle-entry.js` file + +webidl2js also generates a `bundle-entry.js` file in the same directory as all interface wrapper files and the utilities file. This file contains only one exported function, `bootstrap(globalName, globalObj, defaultPrivateData)`, which when called will expose all interfaces generated onto the provided `globalObj`. For interfaces annotated with `[WebIDL2JSFactory]` (documented below), it will automatically create the interface with the given `defaultPrivateData` and expose them the same way as ordinary interfaces. The return value is an object containing all interface wrappers, including the newly created ones for `[WebIDL2JSFactory]`. + +As the name implies, this file is also fit for bundling all interface files through a code bundler such as Browserify and webpack, for faster loading of the initial interface wrappers. Care must be taken however to avoid bundling the implementation files as well. + ## Web IDL features webidl2js is implementing an ever-growing subset of the Web IDL specification. So far we have implemented: diff --git a/lib/output/bundle-entry.js b/lib/output/bundle-entry.js new file mode 100644 index 00000000..6de8af76 --- /dev/null +++ b/lib/output/bundle-entry.js @@ -0,0 +1,21 @@ +"use strict"; + +const allExports = Object.create(null); +exports.bootstrap = function (globalName, globalObj, defaultPrivateData) { + const exportsWithFactories = Object.assign(Object.create(null), allExports); + for (const name of Object.keys(exportsWithFactories)) { + const obj = exportsWithFactories[name]; + if (typeof obj.expose === "function") { + obj.expose(globalName, globalObj); + } else if (typeof obj.createInterface === "function") { + const iface = obj.createInterface(defaultPrivateData); + // Mix in is()/isImpl(). + Object.assign(iface, obj); + exportsWithFactories[name] = iface; + iface.expose(globalName, globalObj); + } + } + return exportsWithFactories; +}; + +// Below this line, exports will be added. diff --git a/lib/transformer.js b/lib/transformer.js index 592f96aa..af89dfce 100644 --- a/lib/transformer.js +++ b/lib/transformer.js @@ -170,6 +170,7 @@ class Transformer { yield fs.writeFile(this.utilPath, utilsText); const { interfaces, dictionaries, enumerations } = this.ctx; + const allExports = []; for (const obj of interfaces.values()) { let source = obj.toString(); @@ -197,6 +198,7 @@ class Transformer { source = this._prettify(source); yield fs.writeFile(path.join(outputDir, obj.name + ".js"), source); + allExports.push(obj.name); } for (const obj of dictionaries.values()) { @@ -218,6 +220,7 @@ class Transformer { source = this._prettify(source); yield fs.writeFile(path.join(outputDir, obj.name + ".js"), source); + allExports.push(obj.name); } for (const obj of enumerations.values()) { @@ -227,7 +230,13 @@ class Transformer { ${obj.toString()} `); yield fs.writeFile(path.join(outputDir, obj.name + ".js"), source); + allExports.push(obj.name); } + + const bundleEntryText = yield fs.readFile(path.resolve(__dirname, "output/bundle-entry.js")); + const out = path.join(outputDir, "bundle-entry.js"); + yield fs.writeFile(out, bundleEntryText); + yield fs.appendFile(out, allExports.map(e => `allExports[${JSON.stringify(e)}] = require("./${e}");\n`).join("")); } _prettify(source) { diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 393271a6..cb4ae450 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -7049,3 +7049,61 @@ module.exports = iface; const Impl = require(\\"../implementations/ZeroArgConstructor.js\\"); " `; + +exports[`bundle-entry.js 1`] = ` +"\\"use strict\\"; + +const allExports = Object.create(null); +exports.bootstrap = function (globalName, globalObj, defaultPrivateData) { + const exportsWithFactories = Object.assign(Object.create(null), allExports); + for (const name of Object.keys(exportsWithFactories)) { + const obj = exportsWithFactories[name]; + if (typeof obj.expose === \\"function\\") { + obj.expose(globalName, globalObj); + } else if (typeof obj.createInterface === \\"function\\") { + const iface = obj.createInterface(defaultPrivateData); + // Mix in is()/isImpl(). + Object.assign(iface, obj); + exportsWithFactories[name] = iface; + iface.expose(globalName, globalObj); + } + } + return exportsWithFactories; +}; + +// Below this line, exports will be added. +allExports[\\"DictionaryConvert\\"] = require(\\"./DictionaryConvert\\"); +allExports[\\"Enum\\"] = require(\\"./Enum\\"); +allExports[\\"Factory\\"] = require(\\"./Factory\\"); +allExports[\\"Global\\"] = require(\\"./Global\\"); +allExports[\\"LegacyArrayClass\\"] = require(\\"./LegacyArrayClass\\"); +allExports[\\"MixedIn\\"] = require(\\"./MixedIn\\"); +allExports[\\"Mixin\\"] = require(\\"./Mixin\\"); +allExports[\\"MixinInherited\\"] = require(\\"./MixinInherited\\"); +allExports[\\"MixinMixin\\"] = require(\\"./MixinMixin\\"); +allExports[\\"Overloads\\"] = require(\\"./Overloads\\"); +allExports[\\"PromiseTypes\\"] = require(\\"./PromiseTypes\\"); +allExports[\\"Reflect\\"] = require(\\"./Reflect\\"); +allExports[\\"SeqAndRec\\"] = require(\\"./SeqAndRec\\"); +allExports[\\"Static\\"] = require(\\"./Static\\"); +allExports[\\"Storage\\"] = require(\\"./Storage\\"); +allExports[\\"StringifierAttribute\\"] = require(\\"./StringifierAttribute\\"); +allExports[\\"StringifierDefaultOperation\\"] = require(\\"./StringifierDefaultOperation\\"); +allExports[\\"StringifierNamedOperation\\"] = require(\\"./StringifierNamedOperation\\"); +allExports[\\"StringifierOperation\\"] = require(\\"./StringifierOperation\\"); +allExports[\\"TypedefsAndUnions\\"] = require(\\"./TypedefsAndUnions\\"); +allExports[\\"URL\\"] = require(\\"./URL\\"); +allExports[\\"URLList\\"] = require(\\"./URLList\\"); +allExports[\\"URLSearchParams\\"] = require(\\"./URLSearchParams\\"); +allExports[\\"URLSearchParamsCollection\\"] = require(\\"./URLSearchParamsCollection\\"); +allExports[\\"URLSearchParamsCollection2\\"] = require(\\"./URLSearchParamsCollection2\\"); +allExports[\\"UnderscoredProperties\\"] = require(\\"./UnderscoredProperties\\"); +allExports[\\"Unforgeable\\"] = require(\\"./Unforgeable\\"); +allExports[\\"UnforgeableMap\\"] = require(\\"./UnforgeableMap\\"); +allExports[\\"Unscopable\\"] = require(\\"./Unscopable\\"); +allExports[\\"Variadic\\"] = require(\\"./Variadic\\"); +allExports[\\"ZeroArgConstructor\\"] = require(\\"./ZeroArgConstructor\\"); +allExports[\\"Dictionary\\"] = require(\\"./Dictionary\\"); +allExports[\\"RequestDestination\\"] = require(\\"./RequestDestination\\"); +" +`; diff --git a/test/test.js b/test/test.js index 35adcdca..5bea9f61 100644 --- a/test/test.js +++ b/test/test.js @@ -29,6 +29,13 @@ for (const idlFile of idlFiles) { }); } +test("bundle-entry.js", () => { + const outputFile = path.resolve(outputDir, "bundle-entry.js"); + const output = fs.readFileSync(outputFile, { encoding: "utf-8" }); + + expect(output).toMatchSnapshot(); +}); + test("utils.js", () => { const input = fs.readFileSync(path.resolve(rootDir, "lib/output/utils.js"), { encoding: "utf-8" }); const output = fs.readFileSync(path.resolve(outputDir, "utils.js"), { encoding: "utf-8" });