Skip to content

Commit

Permalink
docs(book): 📝 updated docs for plugins system changes
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Oct 30, 2021
1 parent 686f369 commit a85f150
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 32 deletions.
4 changes: 3 additions & 1 deletion docs/0.3.x/en-US/plugins/using.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ The plugins system is designed to be as easy as possible to use, and you can imp

In addition to the usual `define_app!` calls, this also uses the `plugins` parameter, passing to it an instance of `perseus::plugins::Plugins`, which manages all the intricacies of the plugins system. If this parameter isn't provided, it'll default to `Plugins::new()`, which creates the configuration for plugins without registering any.

To register a plugin, we use the `.plugin()` function on `Plugins`, which takes two parameters: the plugin's definition (a `perseus::plugins::Plugin`) and any data that should be provided to the plugin. The former should be exported from the plugin's crate, and the latter you'll need to provide based on the plugin's documentation. Note that plugins can accept almost anything as data (specifically, anything that can be expressed as `dyn Any`).
To register a plugin, we use the `.plugin()`/`.plugin_with_client_privilege()` function on `Plugins`, which takes two parameters: the plugin's definition (a `perseus::plugins::Plugin`) and any data that should be provided to the plugin. The former should be exported from the plugin's crate, and the latter you'll need to provide based on the plugin's documentation. Note that plugins can accept almost anything as data (specifically, anything that can be expressed as `dyn Any`).

Plugins in Perseus are fantastic, but they're also a great way to increase your Wasm bundle size, which will make your website slower to laod when users first visit it. To mitigate this, Perseus lets plugin authors define where their plugins should run: in the browser (`PluginEnv::Client`), on the server-side (`PluginEnv::Server`), or on both (`PluginEnv::Both`). Plugins that only run on the server-side should be registered with `.plugin()`, and they will not be included in your final Wasm binary, which keeps your website nimble. If a plugin does need to run on the client though, it can be registered with `.plugin_with_client_privilege()` instead, which is named separately for conditional compilation reasons as well as to create a clear separation. But don't worry, if you accidentally register a plugin with the wrong function, your app won'y build, and Perseus will tell you that you've used the wrong function.
15 changes: 4 additions & 11 deletions docs/0.3.x/en-US/plugins/writing.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To define a plugin, you'll call `perseus::plugins::Plugin::new()`, which takes f
- The name of the plugin as a `&str`, which should be the name of the crate the plugin is in (or the name of a larger app with some extension) (**all plugins MUST have unique names**)
- A [functional actions](:plugins/functional) registrar function, which is given some functional actions and then extends them
- A [control actions](:plugins/control) registrar, which is given some control actions and then extends them
- Whether or not the plugin should only run at `tinker`-time (see below)
- The environment for the plugin to run in (see below)

Here's an example of a very simple plugin that adds a static alias for the project's `Cargo.toml`, creates an about page, and prints the working directory at [tinker](:plugins/tinker)-time (taken from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/plugins/src/plugin.rs)):

Expand Down Expand Up @@ -41,15 +41,8 @@ Right now, there are few things that you can't do with Perseus plugins, which ca
- You can't set the [mutable store](:stores) from a plugin due to a traits issue, so you'll need to provide something for the user to provide to the `mutable_store` parameter of the `define_app!` macro
- Similarly, you can't set the translations manager from a plugin

## Tinker-Only Plugins
## Plugin Environments

There are some cases of plugin development in which a plugin only uses [the `tinker` action](:plugins/tinker), and therefore it should only be included when the user is running `perseus tinker`. The main reason you'd want to do this is to prevent your plugin from becoming part of the client-side Wasm bundle, which will be served to browsers. For example, a size optimizations plugin only needs to run at tinker-time, and, if it were allowed to leak into the client-side bundle, it would actually increase the bundle size because it draws in all its dependencies!
As explained [here](:plugins/using), plugins can either run on the client (`PluginEnv::Client`), the server-side (`PluginEnv::Server`), or on both (`PluginEnv::Both`). Note that the server-side includes `tinker`-time and during the build process. If your plugin does not absolutely need to run on the client, use `PluginEnv::Server`! Your users will thank you for their much smaller bundles! If you don't do this, every single dependency of your plugin will end up in the user's final Wasm bundle, which has to be sent to browsers, and bundle sizes can end up doubling or more as a result! If this is the case though, make sure to tell your users to register your plugin using `.plugin_with_client_privilege()` rather than just `.plugin()` (but don't stress, they'll get an explanatory error if they use the wrong one accidentally).

You can make your plugin tinker-only by setting the fourth argument to `Plugin::new()` to `true`.

<details>
<summary>I want my plugin to run on the server, but not the client.</summary>

You should make it a tinker-only plugin. As a technicality, tinker-only plugins will actually run on the server and in the build process in addition to the `tinker` process. They just won't run on the client. Be warned though: a future release may well change this.

</details>
You can set the environment your plugin runs on by changing the fourth argument to a variant of `PluginEnv`.
4 changes: 3 additions & 1 deletion docs/next/en-US/plugins/using.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ The plugins system is designed to be as easy as possible to use, and you can imp

In addition to the usual `define_app!` calls, this also uses the `plugins` parameter, passing to it an instance of `perseus::plugins::Plugins`, which manages all the intricacies of the plugins system. If this parameter isn't provided, it'll default to `Plugins::new()`, which creates the configuration for plugins without registering any.

To register a plugin, we use the `.plugin()` function on `Plugins`, which takes two parameters: the plugin's definition (a `perseus::plugins::Plugin`) and any data that should be provided to the plugin. The former should be exported from the plugin's crate, and the latter you'll need to provide based on the plugin's documentation. Note that plugins can accept almost anything as data (specifically, anything that can be expressed as `dyn Any`).
To register a plugin, we use the `.plugin()`/`.plugin_with_client_privilege()` function on `Plugins`, which takes two parameters: the plugin's definition (a `perseus::plugins::Plugin`) and any data that should be provided to the plugin. The former should be exported from the plugin's crate, and the latter you'll need to provide based on the plugin's documentation. Note that plugins can accept almost anything as data (specifically, anything that can be expressed as `dyn Any`).

Plugins in Perseus are fantastic, but they're also a great way to increase your Wasm bundle size, which will make your website slower to laod when users first visit it. To mitigate this, Perseus lets plugin authors define where their plugins should run: in the browser (`PluginEnv::Client`), on the server-side (`PluginEnv::Server`), or on both (`PluginEnv::Both`). Plugins that only run on the server-side should be registered with `.plugin()`, and they will not be included in your final Wasm binary, which keeps your website nimble. If a plugin does need to run on the client though, it can be registered with `.plugin_with_client_privilege()` instead, which is named separately for conditional compilation reasons as well as to create a clear separation. But don't worry, if you accidentally register a plugin with the wrong function, your app won'y build, and Perseus will tell you that you've used the wrong function.
31 changes: 12 additions & 19 deletions docs/next/en-US/plugins/writing.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ A plugin will usually occupy its own crate, but it may also be part of a larger

To define a plugin, you'll call `perseus::plugins::Plugin::new()`, which takes four parameters:

- The name of the plugin as a `&str`, which should be the name of the crate the plugin is in (or the name of a larger app with some extension) (**all plugins MUST have unique names**)
- A [functional actions](:plugins/functional) registrar function, which is given some functional actions and then extends them
- A [control actions](:plugins/control) registrar, which is given some control actions and then extends them
- Whether or not the plugin should only run at `tinker`-time (see below)
- The name of the plugin as a `&str`, which should be the name of the crate the plugin is in (or the name of a larger app with some extension) (**all plugins MUST have unique names**)
- A [functional actions](:plugins/functional) registrar function, which is given some functional actions and then extends them
- A [control actions](:plugins/control) registrar, which is given some control actions and then extends them
- The environment for the plugin to run in (see below)

Here's an example of a very simple plugin that adds a static alias for the project's `Cargo.toml`, creates an about page, and prints the working directory at [tinker](:plugins/tinker)-time (taken from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/plugins/src/plugin.rs)):

Expand All @@ -25,31 +25,24 @@ One particularly important thing to note here is the absence of any control acti

Another notable thing is the presence of `GenericNode` as a type parameter, because some plugin actions take this, so you'll need to pass it through. We also tell Perseus what type of data out plugin will take in the second type parameter, which enables type checking in the `.plugin()` call when the user imports the plugin.

The rest of the code is the functional actions registrar, which just registers the plugin on the `functional_actions.settings_actions.add_static_aliases`, `functional_actions.settings_actions.add_templates`, and `functional_actions.tinker` actions. The functions provided to the `.register_plugin()` function are *runners*, which will be executed at the appropriate time by the Perseus engine. Runners take two parameters, *action data*, and *plugin data*. Action data are data provided to every runner for the given action (e.g. an action that runs after a failed build will be told what the error was). You should refer to [the API docs](https://docs.rs/perseus) to learn more about these for different actions. The second parameter is plugin data, covered below.
The rest of the code is the functional actions registrar, which just registers the plugin on the `functional_actions.settings_actions.add_static_aliases`, `functional_actions.settings_actions.add_templates`, and `functional_actions.tinker` actions. The functions provided to the `.register_plugin()` function are _runners_, which will be executed at the appropriate time by the Perseus engine. Runners take two parameters, _action data_, and _plugin data_. Action data are data provided to every runner for the given action (e.g. an action that runs after a failed build will be told what the error was). You should refer to [the API docs](https://docs.rs/perseus) to learn more about these for different actions. The second parameter is plugin data, covered below.

## Plugin Data

Quite often, plugins should accept user configuration, and this is supported through the second runner parameter, which will be given any data that the user defined for your plugin. You can define the type of this with the second type parameter to `Plugin`.

However, because Perseus is juggling all the data for all the plugins the user has installed, across all their different runners, it can't store the type of the data that the user gives (but don't worry, whatever they provide will be type-checked). This means that your runner ends up being given what Rust considers to be *something*. Basically, **we know that it's your plugin data, but Rust doesn't**. Specifically, you'll be given `&dyn Any`, which means you'll need to *downcast* this to a concrete type (the type of your plugin data). As in the above example, we can do this with `plugin_data.downcast_ref::<YourPluginDataTypeHere>()`, which will return an `Option<T>`. **This will always be `Some`**, which is why it's perfectly safe to label the `None` branch as `unreachable!()`. If this ever does result in `None`, then either you've tried to downcast to something that's not your plugin's data type, or there's a critical bug in Perseus' plugins system, which you should [report to us](https://github.com/arctic-hen7/perseus/issues/new/choose).
However, because Perseus is juggling all the data for all the plugins the user has installed, across all their different runners, it can't store the type of the data that the user gives (but don't worry, whatever they provide will be type-checked). This means that your runner ends up being given what Rust considers to be _something_. Basically, **we know that it's your plugin data, but Rust doesn't**. Specifically, you'll be given `&dyn Any`, which means you'll need to _downcast_ this to a concrete type (the type of your plugin data). As in the above example, we can do this with `plugin_data.downcast_ref::<YourPluginDataTypeHere>()`, which will return an `Option<T>`. **This will always be `Some`**, which is why it's perfectly safe to label the `None` branch as `unreachable!()`. If this ever does result in `None`, then either you've tried to downcast to something that's not your plugin's data type, or there's a critical bug in Perseus' plugins system, which you should [report to us](https://github.com/arctic-hen7/perseus/issues/new/choose).

## Caveats

Right now, there are few things that you can't do with Perseus plugins, which can be quite weird.

- You can't extend the engine's server (due to a limitation of Actix Web types), you'll need to manually run a `tinker` on it (add your code into the file by writing it in using [the `tinker` action](:plugins/tinker))
- You can't set the [mutable store](:stores) from a plugin due to a traits issue, so you'll need to provide something for the user to provide to the `mutable_store` parameter of the `define_app!` macro
- Similarly, you can't set the translations manager from a plugin
- You can't extend the engine's server (due to a limitation of Actix Web types), you'll need to manually run a `tinker` on it (add your code into the file by writing it in using [the `tinker` action](:plugins/tinker))
- You can't set the [mutable store](:stores) from a plugin due to a traits issue, so you'll need to provide something for the user to provide to the `mutable_store` parameter of the `define_app!` macro
- Similarly, you can't set the translations manager from a plugin

## Tinker-Only Plugins
## Plugin Environments

There are some cases of plugin development in which a plugin only uses [the `tinker` action](:plugins/tinker), and therefore it should only be included when the user is running `perseus tinker`. The main reason you'd want to do this is to prevent your plugin from becoming part of the client-side Wasm bundle, which will be served to browsers. For example, a size optimizations plugin only needs to run at tinker-time, and, if it were allowed to leak into the client-side bundle, it would actually increase the bundle size because it draws in all its dependencies!
As explained [here](:plugins/using), plugins can either run on the client (`PluginEnv::Client`), the server-side (`PluginEnv::Server`), or on both (`PluginEnv::Both`). Note that the server-side includes `tinker`-time and during the build process. If your plugin does not absolutely need to run on the client, use `PluginEnv::Server`! Your users will thank you for their much smaller bundles! If you don't do this, every single dependency of your plugin will end up in the user's final Wasm bundle, which has to be sent to browsers, and bundle sizes can end up doubling or more as a result! If this is the case though, make sure to tell your users to register your plugin using `.plugin_with_client_privilege()` rather than just `.plugin()` (but don't stress, they'll get an explanatory error if they use the wrong one accidentally).

You can make your plugin tinker-only by setting the fourth argument to `Plugin::new()` to `true`.

<details>
<summary>I want my plugin to run on the server, but not the client.</summary>

You should make it a tinker-only plugin. As a technicality, tinker-only plugins will actually run on the server and in the build process in addition to the `tinker` process. They just won't run on the client. Be warned though: a future release may well change this.

</details>
You can set the environment your plugin runs on by changing the fourth argument to a variant of `PluginEnv`.

0 comments on commit a85f150

Please sign in to comment.