Skip to content

Commit

Permalink
docs(book): 📝 merged next docs with 0.3.x docs for plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Oct 16, 2021
1 parent ca0aaa2 commit c1e8033
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 1 deletion.
7 changes: 7 additions & 0 deletions docs/0.3.x/en-US/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
- [Communicating with a Server](/docs/server-communication)
- [Stores](/docs/stores)
- [Static Exporting](/docs/exporting)
- [Plugins](/docs/plugins/intro)
- [Functional Actions](/docs/plugins/functional)
- [Control Actions](/docs/plugins/control)
- [Using Plugins](/docs/plugins/using)
- [The `tinker` Action](/docs/plugins/tinker)
- [Writing Plugins](/docs/plugins/writing)
- [Security Considerations](/docs/plugins/security)
- [Deploying](/docs/deploying/intro)
- [Server Deployment](/docs/deploying/serverful)
- [Serverless Deployment](/docs/deploying/serverless)
Expand Down
2 changes: 2 additions & 0 deletions docs/0.3.x/en-US/ejecting.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ The Perseus CLI is fantastic at enabling rapid and efficient development, but so

However, there are some things that are too advanced for the CLI to support, and, in those cases, you'll need to eject. Don't worry, you'll still be able to use the CLI itself for running your app, but you'll be given access to the engine that underlies it, and you'll be able to tweak basically anything you want.

Before you proceed though, you should know that Perseus supports modularizing the functionality of ejected code through [plugins](:plugins/intro), which let you modify the `.perseus/` directory in all sorts of ways (including arbitrary file modification), without needing to eject in the first place. In nearly all cases (even for smaller apps), plugins are a better way to go than ejecting. In future, you'll even be able to replace the entire `.perseus/` directory with a custom engine (planned for v0.4.0)!

*Note: ejecting from Perseus exposes the bones of the system, and you should be quite familiar with Rust before doing this. That said, if you're just doing it for fun, go right ahead!*

## Ejecting
Expand Down
22 changes: 22 additions & 0 deletions docs/0.3.x/en-US/plugins/control.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Control Actions

Control actions in Perseus can only be taken by one plugin, unlike [functional actions](:plugins/functional), because, if multiple plugins took them, Perseus wouldn't know what to do. For example, if more than one plugin tried to replace the [immutable store](:stores), Perseus wouldn't know which alternative to use.

Control actions can be considered more powerful than functional actions because they allow a plugin to not only extend, but to replace engine functionality.

## List of Control Actions

Here's a list of all the control actions currently supported by Perseus, which will likely grow over time. You can see these in [this file](https://github.com/arctic-hen7/perseus/blob/main/packages/perseus/src/plugins/control.rs) in the Perseus repository.

If you'd like to request that a new action, functional or control, be added, please [open an issue](https://github.com/arctic-hen7/perseus/issues/new/choose).

_Note: there are currently very few control actions, and this list will be expanded over time._

- `settings_actions` -- actions that can alter the settings provided by the user with [`define_app!`](:define-app)
- `set_immutable_store` -- sets an alternative [immutable store](:stores) (e.g. to store data somewhere other than the filesystem for some reason)
- `set_locales` -- sets the app's locales (e.g. to fetch locales from a database in a more convenient way)
- `set_app_root` -- sets the HTML `id` of the `div` in which to render Perseus (e.g. to fetch the app root from some other service)
- `build_actions` -- actions that'll be run when the user runs `perseus build` or `perseus serve` as part of the build process (these will not be run in [static exporting](:exporting))
- `export_actions` -- actions that'll be run when the user runs `perseus export`
- `server_actions` -- actions that'll be run as part of the Perseus server when the user runs `perseus serve` (or when a [serverful production deployment](:deploying/serverful) runs)
- `client_actions` -- actions that'll run in the browser when the user's app is accessed
32 changes: 32 additions & 0 deletions docs/0.3.x/en-US/plugins/functional.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Functional Actions

The first type of action that a Perseus plugin can take is a functional action, and a single functional action can be taken by many plugins. These are the more common type of Perseus action, and are extremely versatile in extending the capabilities of the Perseus engine. However, they don't have the ability to replace critical functionality on their own.

## List of Functional Actions

Here's a list of all the functional actions currently supported by Perseus, which will likely grow over time. You can see these in [this file](https://github.com/arctic-hen7/perseus/blob/main/packages/perseus/src/plugins/functional.rs) in the Perseus repository.

If you'd like to request that a new action, functional or control, be added, please [open an issue](https://github.com/arctic-hen7/perseus/issues/new/choose).

- `tinker` -- see [this section](:plugins/tinker)
- `settings_actions` -- actions that can alter the settings provided by the user with [`define_app!`](:define-app)
- `add_static_aliases` -- adds extra static aliases to the user's app (e.g. a [TailwindCSS](https://tailwindcss.com) stylesheet)
- `add_templates` -- adds extra templates to the user's app (e.g. a prebuilt documentation system)
- `add_error_pages` -- adds extra [error pages](:error-pages) to the user's app (e.g. a prebuilt 404 page)
- `build_actions` -- actions that'll be run when the user runs `perseus build` or `perseus serve` as part of the build process (these will not be run in [static exporting](:exporting))
- `before_build` -- runs arbitrary code just before the build process starts (e.g. to run a CSS preprocessor)
- `after_successful_build` -- runs arbitrary code after the build process has completed, if it was successful (e.g. copying custom files into `.perseus/dist/`)
- `after_failed_build` -- runs arbitrary code after the build process has completed, if it failed (e.g. to report the failed build to a server crash management system)
- `export_actions` -- actions that'll be run when the user runs `perseus export`
- `before_export` -- runs arbitrary code just before the export process starts (e.g. to run a CSS preprocessor)
- `after_successful_build` -- runs arbitrary code after the build process has completed (inside the export process), if it was successful (e.g. copying custom files into `.perseus/dist/`)
- `after_failed_build` -- runs arbitrary code after the build process has completed (inside the export process), if it failed (e.g. to report the failed export to a server crash management system)
- `after_failed_export` -- runs arbitrary code after the export process has completed, if it failed (e.g. to report the failed export to a server crash management system)
- `after_failed_static_copy` -- runs arbitrary code if the export process fails to copy the `static` directory (e.g. to report the failed export to a server crash management system)
- `after_failed_static_alias_dir_copy` -- runs arbitrary code if the export process fails to copy a static alias that was a directory (e.g. to report the failed export to a server crash management system)
- `after_failed_static_alias_file_copy` -- runs arbitrary code if the export process fails to copy a static alias that was a file (e.g. to report the failed export to a server crash management system)
- `after_successful_export` -- runs arbitrary code after the export process has completed, if it was successful (e.g. copying custom files into `.perseus/dist/`)
- `server_actions` -- actions that'll be run as part of the Perseus server when the user runs `perseus serve` (or when a [serverful production deployment](:deploying/serverful) runs)
- `before_serve` -- runs arbitrary code before the server starts (e.g. to spawn an API server)
- `client_actions` -- actions that'll run in the browser when the user's app is accessed
- `start` -- runs arbitrary code when the Wasm delivered to the browser is executed (e.g. to ping an analytics service)
9 changes: 9 additions & 0 deletions docs/0.3.x/en-US/plugins/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Plugins

Perseus is extremely versatile, but there are some cases where is needs to be modified a little under the hood to do something very advanced. For example, as you'll learn [here](:deploying/size), the common need for applying size optimizations requires modifying a file in the `.perseus/` directory, which requires [ejecting](:ejecting). This is a laborious process, and makes updating difficult, so Perseus support a system of _plugins_ to automatically apply common modifications under the hood!

First, a little bit of background. The `.perseus/` directory contains what's called the Perseus engine, which is basically the core of your app. The code you write is actually imported by this and used to invoke various methods from the `perseus` crate. If you had to build all this yourself, it would take a very long time! Because this directory can be automatically generated though, there's no need to check it into version control (like Git). However, this becomes problematic if you then want to change even a single file inside, because you'll then need to commit the whole directory, which can be unwieldy. More importantly, when updates come along that involve changes to that directory, you'll either have to delete it and re-apply your modifications to the updated directory, or apply the updates manually, either of which is overly tedious for simple cases.

Perseus has plugins to help with this. At various points in the engine, plugins have what are called _actions_ that they can take. Those actions are then executed by the engine at the appropriate time. For example, if a plugin needed to run some code before a Perseus app initialized, it could do that by taking a particular action, and then the engine would execute that action just before the app initialized.

There are two types of actions a plugin can take: _functional actions_, and _control actions_. A single functional action can be taken by many plugins, and they (usually) won't interfere with each other. For example, many plugins can add additional [static aliases](:static-content) to an app. A single control action can only be taken by one plugin, because otherwise Perseus would have conflicting data. For example, if multiple plugins all set their own custom [immutable stores](:stores), Perseus wouldn't know which one to use. Both types of actions are explained in detail in the following sections.
14 changes: 14 additions & 0 deletions docs/0.3.x/en-US/plugins/security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Security Considerations of Plugins

Perseus' plugins system makes it phenomenally versatile, and allows you to reshape default behavior in ways that are possible in very few other frameworks (especially frameworks built in compiled languages like Rust). However, this comes with a major security risk to your system, because plugins have the power to execute arbitrary code.

## The Risks

If you enable a plugin in your app, it will have the opportunity to run arbitrary code. The actions that plugins take are just functions that they provide, so a plugin could easily be saying that it's adding an extra [static alias](:static-content) while simultaneously installing malware on your computer.

## Precautions

1. **Only ever use plugins that you trust!** Anyone can create a Perseus plugin, and some people may create plugins designed to install malware on your system. Optimally, you should review the code of every plugin that you install.
2. **Never run Perseus as root!** If you run Perseus and any plugins as the root user, a plugin can do literally anything on your computer, which could include installing privileged malware (by which point your computer would be owned by an attacker).

**TL;DR:** don't use shady code, and don't run things with unnecessary privileges in general.
11 changes: 11 additions & 0 deletions docs/0.3.x/en-US/plugins/tinker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# The `tinker` Action

There's one [functional action](:plugins/functional) that's quite special in Perseus: the `tinker` action. This action doesn't run as part of any of the usual processes, and it actually has its own command in the CLI: `perseus tinker`. That's because this action allows plugins to modify the code of the Perseus engine. For example, applying [size optimizations](:deploying/size) is a common requirement in Perseus apps, which means modifying `.perseus/Cargo.toml`. This is the perfect job for a plugin, but if it were done by any other action, you'd be modifying the `Cargo.toml` *after* the code had been compiled, which means the modifications would have no effect until the next run.

The `tinker` action solves this problem by creating its own process that's specifically designed for engine modification and tweaking. Until [#59](https://github.com/arctic-hen7/perseus/issues/59) is resolved, this is how you'd make major modifications to the `.perseus/` engine efficiently.

## `perseus tinker`

The `tinker` subcommand in the CLI has one simple function: to execute the tinkers of all the plugins an app uses. By default, it will delete and re-create the `.perseus/` directory to remove any corruptions (which are common with plugins that arbitrarily modify Perseus' code, as you can probably imagine). You can disable that behavior with the `--no-clean` flag.

If you've ejected, running this command will lead to an error, because running tinkers after you've ejected may delete some of your modifications. Most plugins expect to start with the default engines, and your modifications may cause all sorts of problems. If you're certain your modifications won't interfere with things, you can add the `--force` flag to push on. Note that if you don't provide `--no-clean` as well, the entire `.perseus/` directory will be deleted irrecoverably!
11 changes: 11 additions & 0 deletions docs/0.3.x/en-US/plugins/using.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Using Plugins

The plugins system is designed to be as easy as possible to use, and you can import plugins into your app like so (taken from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/plugins/src/lib.rs)):

```rust
{{#include ../../../../examples/plugins/src/lib.rs}}
```

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`).
41 changes: 41 additions & 0 deletions docs/0.3.x/en-US/plugins/writing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Writing Plugins

Writing Perseus plugins is a relatively seamless process once you get the hang of the structure, and this section will guide you through the process. If you just want to use plugins, you can skip this section.

## Structure

A plugin will usually occupy its own crate, but it may also be part of a larger app that just uses plugins for convenience and to avoid [ejection](:ejecting). The only thing you'll need in a plugin is the `perseus` crate, though you'll probably want to bring other libraries in (like `sycamore` if you're adding templates or error pages).

## Defining a Plugin

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

- 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

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)):

```rust
{{#include ../../../../examples/plugins/src/plugin.rs}}
```

One particularly important thing to note here is the absence of any control actions in this plugin. Because you still have to provide a registrar, this function is using the `empty_control_actions_registrar` convenience function, which does exactly what its name implies.

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.

## 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).

## 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
Loading

0 comments on commit c1e8033

Please sign in to comment.