Writing to Output

The primary purpose of the exporter is to write code or really any text output to the destination - so the blueprints must be able to do that easily. You can define both what output you want to produce as well as how to structure it.

Declaring output

You can declare a new output file (or set of files) inside output.json configuration file. For example, this is how Flutter exporter declares it:

Each unique exporter output is defined as a set of two attributes

  • invoke that tells the exporter which blueprints to run to obtain the code information

  • write_to that tells the exporter where to put the file, relative to export directory

For example, take the first item:

  • invoke: colors.pr tells exporter to execute blueprint named colors.pr

  • write_to: colors.dart tells exporter to write the result to {export_root}/colors.dart

The export root is provided by whoever executes the exporter - if you are using VSCode extension, you can select the folder to which to write. CI writes to our servers and sends you a notification so you can consume the data in a form of .zip package. Default exporters have the path defined relative to your product roots and so on. But the resulting structure will always be the same, no matter the target.

Only blueprints that are defined in output.json will be executed, other blueprints are considered helpers and you can use them through injection or with emitters.

Writing to output

Now that you are executing correct blueprints, you can start filling them with useful pieces of information, like formatted code. To write anything to the output inside the blueprint, simply write it to the blueprint!

For example, if you want to create an exporter that will create hello-world.txt file inside the export root and will be filled with appropriate text, you just declare that in output.json:

output.json
{
    "blueprints": [
        {
            "invoke": "hello.pr",
            "write_to": "hello-world.txt"
        }
    ]
}

And then you declare blueprint called hello.pr (or whatever different name you prefer - as long as it has .pr - meaning Pulsar - extension:

hello.pr
Hello World

// Will result in:

Hello World

Of course, static text is rarely very useful on its own, so you can enhance the blueprint with dynamic content as well. To do that, you use substitution, similarly to how you would do string interpolation within javascript using ${variable} syntax. In pulsar, substitutions are declared with {{ and }} enclosing tags, like this:

{[ const dynamicVariable = "Hello World" /]}
{{ dynamicVariable }}

// Will also result in:

Hello World

We are using substitution and flow to first declare the variable, and then to write it to output. Of course, you can combine both plain text and dynamic parts together:

{[ const dynamicVariable = "World" /]}
Hello {{ dynamicVariable }}

// Will also result in:

Hello World

This barely scratches the surface of what you can do with blueprints and how you can manipulate the output. Visit Pulsar Language reference guide to learn more.

Output Structure

You can structure content in any way just from the configuration file. For example, this is how you create a new sub-directory when content is exported, and write the appropriate file there:

output.json
{
    "blueprints": [
        {
            "invoke": "hello.pr",
            "write_to": "my-folder/hello-world.txt"
        }
    ]
}

You can of course combine the paths however you want and structure the output appropriately:

output.json
{
    "blueprints": [
        {
            "invoke": "configuration.pr",
            "write_to": "root.yaml"
        },
        {
            "invoke": "readme.pr",
            "write_to": "readme.md"
        },
        {
            "invoke": "tokens.pr",
            "write_to": "styles/tokens.css"
        }
    ]
}

// Will produce the following output:

{export_root}/root.yaml
{export_root}/readme.md
{export_root}/styles/tokens.css

Conditional Output

Lastly, you can use configuration to define the behavior of the file output. In this case, you can use your configuration boolean keys to decide whether a file should be generated, or not. Simply add when to your output.json entry that defines one file:

{
    "blueprints": [
        {
            "invoke": "colors.pr",
            "write_to": "colors.html",
            "when": "generateColors"
        },
    ]
}

In this case, when generateColors is true, colors.html will be generated, but otherwise, it will be ignored and won't show at the output.

You can also use negation to take the opposite value:

{
    "blueprints": [
        {
            "invoke": "colors.pr",
            "write_to": "colors.html",
            "when": "!ignoreColors"
        },
    ]
}

This will generate colors.html only when ignoreColors is false. Note that in all cases, this key must be defined inside exporter.json in contributes.configuration, otherwise, an error will be thrown.

Dynamic paths using blueprints

In some cases, the static paths are not enough and you need to write the output to a path that is named depending on the exported content, for example, by component name. For this special case, you can use the third optional key in output.json definition:

output.json
{
    "blueprints": [
        {
            "invoke": "configuration.pr",
            "write_using": "configuration-path.pr"
        }
    ]
}

If you use write_using key instead of write_to (they are mutually exclusive and only one can be defined for one specific export entry), then instead of providing a static path, you provide the name of the blueprint that should be used to generate that path. The blueprint follows all the same rules as writing the normal blueprint:

configuration-path.pr
{[ const filename = "config.yaml" /]}
configuration/{{ filename }}

// Will result in path:

configuration/config.yaml

Your output will now be exported to the dynamic path provided by the blueprint, rather than static path you would declare in the configuration itself.

Blueprint output must result in a valid relative URL, otherwise, an error is thrown. The output must be exactly one line of text.

File Emitters

In some rare cases, you will not know in advance what files you will generate because the output will differ heavily based on the content. For this reason, we introduced file emitters, blueprint-level declarations that allow you to define new files to output in processing time.

pageEmit

Last updated