|
1 | 1 | # Composing Components |
2 | 2 |
|
3 | | -Because the WebAssembly component model packages code in a portable binary format, and provides machine-readable interfaces in [WIT](../design/wit.md) with a standardised ABI (Application Binary Interface), it enables applications and components to work together, no matter what languages they were originally written in. In the same way that, for example, a Rust package (crate) can be compiled together with other Rust code to create a higher-level library or an application, a Wasm component can be linked with other components. |
4 | | - |
5 | | -> Component model interoperation is more convenient and expressive than language-specific foreign function interfaces. A typical C FFI involves language-specific types, so it is not possible to link between arbitrary languages without at least some C-language wrapping or conversion. The component model, by contrast, provides a common way of expressing interfaces, and a standard binary representation of those interfaces. So if an import and an export have the same shape, they fit together directly. |
| 3 | +The WebAssembly component model enables applications and components to work together, |
| 4 | +no matter what languages they were originally written in. |
| 5 | +The component model accomplishes this by packaging code in a portable binary format |
| 6 | +and providing machine-readable interfaces in [WIT](../design/wit.md) |
| 7 | +with a standardised Application Binary Interface (ABI). |
| 8 | +In the same way that, for example, a Rust package (crate) can be compiled together with other Rust code |
| 9 | +to create a higher-level library or an application, a WebAssembly component can be composed with other components. |
| 10 | + |
| 11 | +> Component model interoperation is more convenient and expressive than language-specific foreign function interfaces (FFIs). |
| 12 | +> A typical C FFI involves language-specific types, so it is not possible to link between arbitrary languages |
| 13 | +> without at least some C-language wrapping or conversion. |
| 14 | +> The component model, by contrast, provides a common way of expressing interfaces, |
| 15 | +> and a standard binary representation of those interfaces. |
| 16 | +> So if an import and an export have the same shape, they fit together directly. |
6 | 17 |
|
7 | 18 | ## What is composition? |
8 | 19 |
|
9 | | -When you compose components, you wire up the imports of one "primary" component to the exports of one or more other "dependency" components, creating a new component. The new component, like the original components, is a `.wasm` file, and its interface is defined as: |
| 20 | +When you compose components, you wire up the imports of one _primary_ component |
| 21 | +to the exports of one or more other _dependency_ components, creating a new component. |
| 22 | +The new component, like the original components, is a `.wasm` file, and its interface is defined as follows: |
10 | 23 |
|
11 | | -* The new component _exports_ the same exports as the primary component |
12 | | -* The new component _does not export_ the exports of the dependencies |
13 | | -* The new component _imports_ all the imports of the dependency components |
14 | | -* The new component _imports_ any imports of the primary component imports that the dependencies didn't satisfy |
15 | | -* If several components import the same interface, the new component imports that interface - it doesn't "remember" that the import was declared in several different places |
| 24 | +* The new component _exports_ the same exports as the primary component. |
| 25 | +* The new component _does not export_ the exports of the dependency components. |
| 26 | +* The new component _imports_ all the imports of the dependency components. |
| 27 | +* The new component _imports_ any imports of the primary component |
| 28 | + that the dependencies didn't satisfy. |
| 29 | +* If several components import the same interface, |
| 30 | + the new component imports that interface—it doesn't "remember" |
| 31 | + that the import was declared in several different places. |
16 | 32 |
|
17 | 33 | For example, consider two components with the following worlds: |
18 | 34 |
|
19 | 35 | ```wit |
20 | | -// component `validator` |
21 | | - |
22 | | -
|
23 | | -interface validator { |
24 | | - validate-text: func(text: string) -> string; |
25 | | -} |
26 | | -
|
27 | | -world validator { |
28 | | - export validator; |
29 | | - import docs:regex/[email protected]; |
30 | | -} |
| 36 | +{{#include ../../examples/composing-section-examples/validator.wit}} |
31 | 37 | ``` |
32 | 38 |
|
33 | 39 | ```wit |
34 | | -// component 'regex' |
35 | | - |
36 | | -
|
37 | | -interface match { |
38 | | - first-match: func(regex: string, text: string) -> string; |
39 | | -} |
40 | | -
|
41 | | -world regex { |
42 | | - export match; |
43 | | -} |
| 40 | +{{#include ../../examples/composing-section-examples/regex.wit}} |
44 | 41 | ``` |
45 | 42 |
|
46 | | -If we compose `validator` with `regex`, `validator`'s import of `docs:regex/[email protected]` is wired up to `regex`'s export of `match`. The net result is that the composed component exports `docs:validator/[email protected]` and has no imports. The composed component does _not _ export `docs:regex/[email protected]` - that has become an internal implementation detail of the composed component. |
47 | | - |
48 | | -Component composition tools are in their early stages right now. Here are some tips to avoid or diagnose errors: |
49 | | - |
50 | | -* Composition happens at the level of interfaces. If the initial component directly imports functions, then composition will fail. If composition reports an error such as "component `path/to/component` has a non-instance import named `<name>`" then check that all imports and exports are defined by interfaces. |
51 | | -* Composition is asymmetrical. It is not just "gluing components together" - it takes a primary component which has imports, and satisfies its imports using dependency components. For example, composing an implementation of `validator` with an implementation of `regex` makes sense because `validator` has a dependency that `regex` can satisfy; doing it the other way round doesn't work, because `regex` doesn't have any dependencies, let alone ones that `validator` can satisfy. |
52 | | -* Composition cares about interface versions, and current tools are inconsistent about when they infer or inject versions. For example, if a Rust component exports `test:mypackage`, `cargo component build` will decorate this with the crate version, e.g. `test:[email protected]`. If another Rust component _imports _ an interface from `test:mypackage`, that won't match `test:[email protected]`. You can use [`wasm-tools component wit`](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-component) to view the imports and exports embedded in the `.wasm` files and check whether they match up. |
| 43 | +In this example, `validator` is the primary component |
| 44 | +and `regex` is a dependency component. |
| 45 | +If we compose `validator` with `regex`, `validator`'s import of `docs:regex/[email protected]` |
| 46 | +is wired up to `regex`'s export of `match`. |
| 47 | +The net result is that the composed component exports `docs:validator/[email protected]` and has no imports. |
| 48 | +The composed component does _not _ export `docs:regex/[email protected]`: that has become an internal implementation detail of the composed component. |
| 49 | + |
| 50 | +Component composition tools are in their early stages right now. |
| 51 | +Here are some tips to avoid or diagnose errors: |
| 52 | + |
| 53 | +* Composition happens at the level of interfaces. |
| 54 | + If the initial component directly imports functions, then composition will fail. |
| 55 | + If composition reports an error such as |
| 56 | + "component `path/to/component` has a non-instance import named `<name>`", |
| 57 | + then check that all imports and exports are defined by interfaces. |
| 58 | +* Composition is asymmetrical. It is not just "gluing components together"—it takes a primary component |
| 59 | + that has imports, and satisfies its imports using dependency components. |
| 60 | + For example, composing an implementation of `validator` with an implementation of `regex` makes sense |
| 61 | + because `validator` has a dependency that `regex` can satisfy; doing it the other way around doesn't work, |
| 62 | + because `regex` doesn't have any dependencies, let alone ones that `validator` can satisfy. |
| 63 | +* Composition cares about interface versions, and current tools are inconsistent about |
| 64 | + when they infer or inject versions. |
| 65 | + For example, if a Rust component exports `test:mypackage`, |
| 66 | + `cargo component build` will decorate this with the crate version, e.g. `test:[email protected]`. |
| 67 | + If another Rust component _imports _ an interface from `test:mypackage`, that won't match `test:[email protected]`. |
| 68 | + You can use [`wasm-tools component wit`](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-component) |
| 69 | + to view the imports and exports embedded in the `.wasm` files and check whether they match up. |
53 | 70 |
|
54 | 71 | ## Composing components with WAC |
55 | 72 |
|
56 | | -You can use the [WAC](https://github.com/bytecodealliance/wac) CLI to compose components at the command line. |
| 73 | +You can use the [WebAssembly Compositions (WAC)](https://github.com/bytecodealliance/wac) CLI |
| 74 | +to compose components at the command line. |
57 | 75 |
|
58 | | -To perform quick and simple compositions, use the `wac plug` command. `wac plug` satisfies the import of a "socket" component by plugging a "plug" component's export into the socket. For example, a component that implements the [`validator` world above](#what-is-composition) needs to satisfy it's `match` import. It is a socket. While a component that implements the `regex` world, exports the `match` interface, and can be used as a plug. `wac plug` can plug a regex component's export into the validator component's import, creating a resultant composition: |
| 76 | +To perform quick and simple compositions, use the `wac plug` command. |
| 77 | +`wac plug` satisfies the import of a "socket" component by plugging a "plug" component's export into the socket. |
| 78 | +The socket component is the primary component, while the plug components are dependency components. |
| 79 | +For example, a component that implements the [`validator` world above](#what-is-composition) |
| 80 | +needs to satisfy its `match` import. It is a socket. |
| 81 | +On the other hand, a component that implements the `regex` world exports the `match` interface, |
| 82 | +and can be used as a plug. |
| 83 | +`wac plug` can plug a regex component's export into the validator component's import, |
| 84 | +creating a resultant composition: |
59 | 85 |
|
60 | 86 | ```sh |
61 | 87 | wac plug validator-component.wasm --plug regex-component.wasm -o composed.wasm |
62 | 88 | ``` |
63 | 89 |
|
64 | | -A component can also be composed with two components it depends on. |
| 90 | +A component can also be composed with more than component that it depends on. |
65 | 91 |
|
66 | 92 | ```sh |
67 | 93 | wac plug path/to/component.wasm --plug path/to/dep1.wasm --plug path/to/dep2.wasm -o composed.wasm |
68 | 94 | ``` |
69 | 95 |
|
70 | | -Here `component.wasm` is the component that imports interfaces from `dep1.wasm` and `dep2.wasm`, which export them. The composed component, with those dependencies satisfied and tucked away inside it, is saved to `composed.wasm`. |
| 96 | +Here `component.wasm` is the component that imports interfaces from `dep1.wasm` and `dep2.wasm`, |
| 97 | +which export interfaces. |
| 98 | +The composed component, with those dependencies satisfied and tucked away inside it, is saved to `composed.wasm`. |
71 | 99 |
|
72 | | -The `plug` syntax doesn't cover transitive dependencies. If, for example, `dep1.wasm` has unsatisfied imports that you want to satisfy from `dep3.wasm`, you'd need to be deliberate about the order of your composition. You could compose `dep1.wasm` with `dep3.wasm` first, then refer to that composed component instead of `dep1.wasm`. However, this doesn't scale to lots of transitive dependencies, which is why the WAC language was created. |
| 100 | +The `plug` syntax doesn't cover transitive dependencies. |
| 101 | +If, for example, `dep1.wasm` has unsatisfied imports that you want to satisfy from `dep3.wasm`, |
| 102 | +you'd need to be deliberate about the order of your composition. |
| 103 | +You could compose `dep1.wasm` with `dep3.wasm` first, then refer to that composed component instead of `dep1.wasm`. |
| 104 | +However, this doesn't scale to lots of transitive dependencies, which is why the WAC language was created. |
73 | 105 |
|
74 | 106 | ### Advanced composition with the WAC language |
75 | 107 |
|
76 | | -`wac plug` is a convenience to achieve a common pattern in component compositions like above. However, composition can be arbitrarily complicated. In cases where `wac plug` is not sufficient, the [WAC language](https://github.com/bytecodealliance/wac/blob/main/LANGUAGE.md) can give us the ability to create arbitrarily complex compositions. |
| 108 | +`wac plug` is a convenience to achieve a common pattern in component compositions like the ones above. |
| 109 | +However, composition can be arbitrarily complicated. |
| 110 | +In cases where `wac plug` is not sufficient, the [WAC language](https://github.com/bytecodealliance/wac/blob/main/LANGUAGE.md) |
| 111 | +gives us the ability to create arbitrarily complex compositions. |
77 | 112 |
|
78 | | -In a WAC file, you use the WAC language to describe a composition. For example, the following is a WAC file that could be used to create that validator component from [earlier](#what-is-composition). |
| 113 | +In a WAC file, you use the WAC language to describe a composition. |
| 114 | +For example, the following is a WAC file that could be used to create the validator component from [earlier](#what-is-composition). |
79 | 115 |
|
80 | 116 | ``` |
81 | | -//composition.wac |
82 | | -// Provide a package name for the resulting composition |
83 | | -package docs:composition; |
84 | | -
|
85 | | -// Instantiate the regex-impl component that implements the `regex` world. Bind this instance's exports to the local name `regex`. |
86 | | -let regex = new docs:regex-impl { }; |
87 | | -
|
88 | | -// Instantiate the validator-impl component which implements the `validator` world and imports the match interface from the regex component. |
89 | | -let validator = new docs:validator-impl { match: regex.match, ... }; |
90 | | -
|
91 | | -// Export all remaining exports of the validator instance |
92 | | -export validator...; |
| 117 | +{{#include ../../examples/composing-section-examples/composition.wac}} |
93 | 118 | ``` |
94 | 119 |
|
95 | | -Then, `wac compose` can be used to compose the components, passing in the paths to the components. Alternatively, you can place the components in a `deps` directory with an expected structure, and in the near future, you will be able to pull in components from registries. See the [`wac` documentation](https://github.com/bytecodealliance/wac) for more details. |
| 120 | +Then, `wac compose` can be used to compose the components, using the `--dep` flag to specify |
| 121 | +the relationships between component names and `.wasm` files: |
96 | 122 |
|
97 | 123 | ```sh |
98 | | -wac compose --dep docs:regex-impl=regex-component.wasm --dep docs:validator-impl=validator-component.wasm -o composed.wasm composition.wac |
| 124 | +wac compose --dep docs:regex-impl=regex-component.wasm \ |
| 125 | + --dep docs:validator-impl=validator-component.wasm \ |
| 126 | + -o composed.wasm \ |
| 127 | + composition.wac |
99 | 128 | ``` |
100 | 129 |
|
101 | | -For an in depth description about how to use the wac tool, you can check out the [wac language index](https://github.com/bytecodealliance/wac/blob/main/LANGUAGE.md) and [examples](https://github.com/bytecodealliance/wac/tree/main/examples). |
| 130 | +Alternatively, you can place the components in a `deps` directory with an expected structure, |
| 131 | +and in the near future, you will be able to pull in components from registries. |
| 132 | +See the [`wac` documentation](https://github.com/bytecodealliance/wac) for more details. |
| 133 | + |
| 134 | +For an in-depth description about how to use the `wac` tool, |
| 135 | +you can check out the [WAC language index](https://github.com/bytecodealliance/wac/blob/main/LANGUAGE.md) |
| 136 | +and [examples](https://github.com/bytecodealliance/wac/tree/main/examples). |
102 | 137 |
|
103 | 138 | ## Composing components with a visual interface |
104 | 139 |
|
105 | 140 | You can compose components visually using the builder app at [wasmbuilder.app](https://wasmbuilder.app/). |
106 | 141 |
|
107 | | -1. Use the Add Component Button to upload the `.wasm` component files you want to compose. The components appear in the sidebar. |
| 142 | +1. Use the "Add Component" button to upload the `.wasm` component files you want to compose. |
| 143 | + The components appear in the sidebar. |
108 | 144 |
|
109 | | -2. Drag the components onto the canvas. You'll see imports listed on the left of each component, and exports on the right. |
| 145 | +2. Drag the components onto the canvas. |
| 146 | + You'll see imports listed on the left of each component, and exports on the right. |
110 | 147 |
|
111 | | -3. Click the box in the top left to choose the 'primary' component, that is, the one whose exports will be preserved. (The clickable area is quite small - wait for the cursor to change from a hand to a pointer.) |
| 148 | +3. Click the box in the top left to choose the 'primary' component, that is, |
| 149 | + the one whose exports will be preserved. |
| 150 | + (The clickable area is quite small—wait for the cursor to change from a hand to a pointer.) |
112 | 151 |
|
113 | | -4. To fulfil one of the primary component's imports with a dependency's export, drag from the "I" icon next to the export to the "I" item next to the import. (Again, the clickable area is quite small - wait for the cursor to change from a hand to a cross.) |
| 152 | +4. To fulfil one of the primary component's imports with a dependency's export, |
| 153 | + drag from the "I" icon next to the export to the "I" item next to the import. |
| 154 | + (Again, the clickable area is quite small—wait for the cursor to change from a hand to a cross.) |
114 | 155 |
|
115 | | -5. When you have connected all the imports and exports that you want, click the Download Component button to download the composed component as a `.wasm` file. |
| 156 | +5. When you have connected all the imports and exports that you want, |
| 157 | + click the Download Component button to download the composed component as a `.wasm` file. |
0 commit comments