Emit

Emit is a special type of flow that allows you to output more than just one file from an executed blueprint.

Emitters are an advanced topic that requires you to understand the basics of blueprints and output mapping first and how those two relate to each other. If you haven't read it yet, do so before you continue!

In some situations, what output to generate can't be defined ahead of time. A good example is components - some components are complex, require more files than just one, the files can be different based on the type of component, and so on. In complex cases like this, you want to use an emitter via emit flow.

Syntax

Basic syntax of emit flow is as follows:

{[ emit file [filepath] ]}
    [content]
{[/]}

The emitter itself has only one attribute - filepath. Filepath tells the emitter where to write the content you provide. The emitter writes content - which can be any content generated using any flow, substitution, or even plain text - into the file defined using filepath.

Generating static files

For example, let's say we want to generate a readme file based on some definition flag. The blueprint would look like this:

{* Write the file Readme.md if requested *}
{[ if shouldWriteReadme ]}
    {[ emit file "Readme.md" ]}
        # Readme Title! 
        This is content of readme file
    {[/]}
{[/]}

In the example above, if shouldWriteReadme evaluates to true, a file will be emitted. The file will be written into /[export directory]/Readme.md and the content will be everything defined inside emit body:

/[export directory/Readme.md
# Readme Title!
This is content of readme file

Generating files using dynamic paths

The most powerful feature of emitters lies inside the filepath argument. Because the filepath can contain any path, it can not only be hardcoded as in the previous example, but also constructed dynamically. Let's look at the next example:

{* Write an extra file for each component, if enabled *}
{[ if shouldGenerateSupportFile ]}

    {* Fetch data about component *}
    {[ let componentData = @ds.componentById(componentId) /]}
    
    {* Construct filepath, for example /components/TextField.swift *}
    {[ let writeToFilepath = componentData.name.extended("/components/", ".swift") /]}
    
    {* Emit new file into defined filepath *}
    {[ emit file writeToFilepath ]}
        // Support file for {{ componentData.name }} component
    {[/]}
{[/]}

As you can see, first, we requested all component data using function and stored it into a variable writeToFilePath. This file path will differ based on the component name. Then, we emitted the content defined within emit body into that file. The resulting file will look like this:

/[export directory]/components/TextField.swift
// Support file for TextField Component

Using one definition file to define the exporter mapping

Now that you know that you can basically export any file directly from the blueprints, there is an obvious question - should I use exporter mapping at all, because I can just write one definition file declaring the entire output (especially when combined with inject?

The answer is yes, you should always use exporter mapping instead of an emitter unless what gets generated depends on either the project data (such as animations, component support files, and so on) or on the exporter configuration itself.

As a general rule, if you can define it through mapping of the output, you should always do that, without exceptions. There are several reasons to avoid over-using emitters:

  • The exporter is parallelized and greatly optimized when using mapping. However, when using emitters, the exporter still thinks only one blueprint is executed and will execute in serial fashion, which can really start showing on larger projects. You should always strive for the performance of the exporter and this can lead to very poor performance.

  • One monolithic file is very hard to maintain for large projects and is especially hard to debug when something unexpected happens. In this case, the debugger will only tell you that the issue happened in the main configuration file, but not inside the emitted blueprint (because it was defined dynamically and can't be referenced).

  • Mapping tells users what to expect, and makes it easy to change the behavior if they need to, such as mapping to a different directory. However, searching for configuration inside source code is always much harder and usually requires an explanation in documentation or similar.

That being said, emitters are a really powerful part of blueprints and when used well, can produce amazing results. Emitters are best used with inject flow, which allows you to inject the content of a blueprint into another one.

Last updated