|
1 | | -> **_NOTE_**: This documentation is still being updated to reflect changes for V1, the contents may be outdated while this notice is still present |
2 | | -
|
3 | 1 | # Plugins |
| 2 | + |
| 3 | +Perla supports plugins to extend build and serve functionality. Plugins must be authored manually as F# script files and placed in the `.perla/plugins` directory of your project. |
| 4 | + |
| 5 | +## Using Plugins |
| 6 | + |
| 7 | +Add plugins to your `perla.json` configuration by specifying the plugin name: |
| 8 | + |
| 9 | +```json |
| 10 | +{ |
| 11 | + "plugins": ["markdown-plugin"] |
| 12 | +} |
| 13 | +``` |
| 14 | + |
| 15 | +Perla will automatically look for plugins in the `.perla/plugins` directory of your project. Each plugin is registered with a name specified in the plugin definition. |
| 16 | + |
| 17 | +## Plugin API |
| 18 | + |
| 19 | +The Perla plugin API provides several types and functions to help you create plugins: |
| 20 | + |
| 21 | +### FileTransform |
| 22 | + |
| 23 | +The core data structure that plugins operate on: |
| 24 | + |
| 25 | +```fsharp |
| 26 | +type FileTransform = { |
| 27 | + /// The text of the file, this will change between plugin transformations |
| 28 | + content: string |
| 29 | + /// The extension this file is currently holding |
| 30 | + /// this will change between plugin transformations |
| 31 | + /// It also serves for plugin authors to determine |
| 32 | + /// if their plugin should act on this particular file |
| 33 | + extension: string |
| 34 | + /// Full path to the source file |
| 35 | + fileLocation: string |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +### FilePredicate |
| 40 | + |
| 41 | +A function that determines whether a plugin should process a file: |
| 42 | + |
| 43 | +```fsharp |
| 44 | +/// A function predicate that allows the plugin author |
| 45 | +/// to signal if the file should be processed by the plugin or not |
| 46 | +type FilePredicate = string -> bool |
| 47 | +``` |
| 48 | + |
| 49 | +### Transform Functions |
| 50 | + |
| 51 | +Perla supports multiple types of transform functions: |
| 52 | + |
| 53 | +```fsharp |
| 54 | +/// A Synchronous function that takes the content of the file and its extension |
| 55 | +/// and returns the processed content and the new extension after processing the file |
| 56 | +type Transform = FileTransform -> FileTransform |
| 57 | +
|
| 58 | +/// A Task<'T> based asynchronous function |
| 59 | +type TransformTask = FileTransform -> Task<FileTransform> |
| 60 | +
|
| 61 | +/// An Async<'T> based asynchronous function |
| 62 | +type TransformAsync = FileTransform -> Async<FileTransform> |
| 63 | +
|
| 64 | +/// A ValueTask<'T> based function (used internally by Perla) |
| 65 | +type TransformAction = FileTransform -> ValueTask<FileTransform> |
| 66 | +``` |
| 67 | + |
| 68 | +If a transform function cannot modify the content for any reason, it should return the original `FileTransform` and log the error to the console rather than crashing. |
| 69 | + |
| 70 | +### Plugin Builder |
| 71 | + |
| 72 | +Plugins are defined using a computation expression builder: |
| 73 | + |
| 74 | +```fsharp |
| 75 | +plugin "plugin-name" { |
| 76 | + should_process_file (fun extension -> /* predicate logic */) |
| 77 | + with_transform (fun file -> /* transform logic */) |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +The builder supports these operations: |
| 82 | +- `should_process_file`: Defines a predicate to determine if a file should be processed |
| 83 | +- `with_transform`: Defines the transformation to apply to matching files |
| 84 | + |
| 85 | +Note that if you provide multiple instances of the same operation, only the first one will be used by Perla. |
| 86 | + |
| 87 | +## Authoring Plugins |
| 88 | + |
| 89 | +Plugins are written in F# as script files (`.fsx`). Each plugin exports logic using the Perla plugin API. The plugin receives hooks to process files during build or serve. |
| 90 | + |
| 91 | +Example plugin (`.perla/plugins/markdown.fsx`): |
| 92 | + |
| 93 | +```fsharp |
| 94 | +#r "nuget: Markdig, 0.41.3" |
| 95 | +#r "nuget: Perla.Plugins, 1.0.0-beta-030" |
| 96 | +
|
| 97 | +open Perla.Plugins |
| 98 | +open Markdig |
| 99 | +
|
| 100 | +let pipeline = |
| 101 | + lazy |
| 102 | + (MarkdownPipelineBuilder() |
| 103 | + .UseAdvancedExtensions() |
| 104 | + .UsePreciseSourceLocation() |
| 105 | + .Build()) |
| 106 | +
|
| 107 | +let shouldProcess: FilePredicate = |
| 108 | + fun extension -> [ ".md"; ".markdown" ] |> List.contains extension |
| 109 | +
|
| 110 | +let transform: Transform = |
| 111 | + fun args -> { |
| 112 | + args with |
| 113 | + content = Markdown.ToHtml(args.content, pipeline.Value) |
| 114 | + extension = ".html" |
| 115 | + } |
| 116 | +
|
| 117 | +plugin "markdown-plugin" { |
| 118 | + should_process_file shouldProcess |
| 119 | + with_transform transform |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +## Plugin Lifecycle |
| 124 | + |
| 125 | +1. Perla loads plugins from the `.perla/plugins` directory at startup |
| 126 | +2. Each plugin is registered with its specified name |
| 127 | +3. During build or serve, for each file: |
| 128 | + - Perla checks each plugin's `should_process_file` predicate |
| 129 | + - If the predicate returns true, the file is passed to the plugin's transform function |
| 130 | + - The transform function processes the file and returns the modified content and extension |
| 131 | + - The transformed file is then passed to the next matching plugin or written to disk |
0 commit comments