diff --git a/crates/ubrn_cli/src/codegen/templates/index.tsx b/crates/ubrn_cli/src/codegen/templates/index.tsx index d6628067..2b8d87ff 100644 --- a/crates/ubrn_cli/src/codegen/templates/index.tsx +++ b/crates/ubrn_cli/src/codegen/templates/index.tsx @@ -12,11 +12,23 @@ installer.installRustCrate(); export * from './{{ bindings }}/{{ m.ts() }}'; {%- endfor %} +// Now import the bindings so we can: +// - intialize them +// - export them as namespaced objects as the default export. +{%- for m in self.config.modules %} +import * as {{ m.ts() }} from './{{ bindings }}/{{ m.ts() }}'; +{%- endfor %} + // Initialize the generated bindings: mostly checksums, but also callbacks. {%- for m in self.config.modules %} -import {{ m.ts() }}_ from './{{ bindings }}/{{ m.ts() }}'; +{{ m.ts() }}.default.initialize(); {%- endfor %} -{% for m in self.config.modules %} -{{ m.ts() }}_.initialize(); + +// Export the crates as individually namespaced objects. +export default { +{%- for m in self.config.modules %} + {{ m.ts() }}, {%- endfor %} +}; + {# space #} diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 7bf8e675..e3a0dae1 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -7,6 +7,7 @@ - [Before you start](guides/pre-installation.md) - [Step by step: Make your first library project](guides/getting-started.md) - [Publishing your library project](guides/publishing.md) +- [Working with multiple crates in one library](guides/megazords.md) # Mapping Rust on to Typescript diff --git a/docs/src/guides/megazords.md b/docs/src/guides/megazords.md new file mode 100644 index 00000000..6d6a3ace --- /dev/null +++ b/docs/src/guides/megazords.md @@ -0,0 +1,55 @@ +# Working with multiple crates in one library + +Some teams arrange their Rust library in to multiple crates, or multiple teams from one organization combine their efforts into one library. + +This might be for better code organization, or to reduce shipping multiple copies of the same dependencies. + +The combined library from multiple crates, in Mozilla vernacular, is known as a [Megazord](https://robots.fandom.com/wiki/Mighty_Morphin%27_Megazord). + +`uniffi-rs` and `uniffi-bindgen-react-native` both work well with Megazords. + +`uniffi-bindgen-react-native` produces a cluster of files per crate. For example, generating files from the library `libmymegazord.a` might contain two crates, `crate1` and `crate2`. The library directory would look like this: + +``` +cpp +├── generated +│ ├── crate1.cpp +│ ├── crate1.hpp +│ ├── crate2.cpp +│ └── crate2.hpp +├── react-native-my-megazord.cpp +└── react-native-my-megazord.h +src +├── NativeMyMegazord.ts +├── generated +│ ├── crate1.ts +│ ├── crate1-ffi.ts +│ ├── crate2.ts +│ └── crate2-ffi.ts +└── index.tsx +``` + +In `index.tsx`, the types are re-exported from `crate1.ts` and `crate2.ts`. + +In this extended example, `crate1.ts` might declare a `Crate1Type` and `crate2.ts` a `Crate2Type`. + +In this case, your library's client code would import `Crate1Type` and `Crate2Type` like this: + +```ts +import { Crate1Type, Crate2Type } from "react-native-my-megazord"; +``` + +Alternatively, they can use the default export: + +```ts +import megazord from "react-native-my-megazord"; + +const { Crate1Type } = megazord.crate1; +const { Crate2Type } = megazord.crate2; +``` + +```admonish warning title="Duplicated identifiers" +Due to Swift's large granular module sytem, crates in the same megazord cannot have types of the same name. + +This may be solved in Swift at some point— e.g. by adding prefixes— but until then, duplicate identifiers will cause a Typescript compilation error as the types are smooshed together in `index.tsx`. +``` diff --git a/typescript/tests/importing-qualified.test.ts b/typescript/tests/importing-qualified.test.ts new file mode 100644 index 00000000..ba10184e --- /dev/null +++ b/typescript/tests/importing-qualified.test.ts @@ -0,0 +1,38 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/ + */ + +import * as exported from "./playground/exported"; + +import { test } from "../testing/asserts"; + +test("Records imported as type", (t) => { + const record: exported.MyRecord = { + prop1: "string", + prop2: 42, + }; +}); + +test("Enums imported as objects", (t) => { + const enum_: exported.MyEnum = new exported.MyEnum.Variant1(); +}); + +test("Objects interfaces imported as types", (t) => { + // In our generated code, `MyObject` would not be imported into this + // file because all creation happens via the `FfiConverterTypeMyObject`. + const obj: exported.MyObjectInterface = new exported.MyObject(); +}); + +test("Callback interfaces imported as types", (t) => { + class Impl implements exported.MyCallbackInterface { + myMethod(): void {} + } + + const cb = new Impl(); +}); + +test("Custom types imported as types", (t) => { + const s: exported.MyCustomString = "string"; +});