Custom writers | typescript-transformer | Spatie

 SPATIE

  TypeScript Transformer
=========================

spatie.be/open-source

  [Docs](https://spatie.be/docs)  [Typescript-transformer](https://spatie.be/docs/typescript-transformer/v3)  Advanced  Custom writers

 Version   v3   v2   v1

 Other versions for crawler [v3](https://spatie.be/docs/typescript-transformer/v3) [v2](https://spatie.be/docs/typescript-transformer/v2) [v1](https://spatie.be/docs/typescript-transformer/v1)

  Custom writers
- [ Introduction ](https://spatie.be/docs/typescript-transformer/v3/introduction)
- [ Postcardware ](https://spatie.be/docs/typescript-transformer/v3/postcardware)
- [ Installation ](https://spatie.be/docs/typescript-transformer/v3/installation)
- [ Questions &amp; issues ](https://spatie.be/docs/typescript-transformer/v3/questions-and-issues)
- [ Changelog ](https://spatie.be/docs/typescript-transformer/v3/changelog)
- [ About us ](https://spatie.be/docs/typescript-transformer/v3/about-us)

Getting started
---------------

- [ Setting up ](https://spatie.be/docs/typescript-transformer/v3/getting-started/setting-up)
- [ Running TypeScript Transformer for the first time ](https://spatie.be/docs/typescript-transformer/v3/getting-started/first-run)
- [ Special attributes ](https://spatie.be/docs/typescript-transformer/v3/getting-started/attributes)
- [ Typing properties ](https://spatie.be/docs/typescript-transformer/v3/getting-started/typing-properties)
- [ Replacing common types ](https://spatie.be/docs/typescript-transformer/v3/getting-started/replacing-types)
- [ Formatters ](https://spatie.be/docs/typescript-transformer/v3/getting-started/formatters)

Laravel
-------

- [ Installation and setup ](https://spatie.be/docs/typescript-transformer/v3/laravel/installation-and-setup)
- [ Laravel Data ](https://spatie.be/docs/typescript-transformer/v3/laravel/laravel-data)
- [ Controllers ](https://spatie.be/docs/typescript-transformer/v3/laravel/controllers)
- [ Routes ](https://spatie.be/docs/typescript-transformer/v3/laravel/routes)
- [ Route filters ](https://spatie.be/docs/typescript-transformer/v3/laravel/route-filters)
- [ Watch mode ](https://spatie.be/docs/typescript-transformer/v3/laravel/watch-mode)

Custom transformers
-------------------

- [ Getting started ](https://spatie.be/docs/typescript-transformer/v3/transformers/getting-started)
- [ Class transformer ](https://spatie.be/docs/typescript-transformer/v3/transformers/class-transformer)
- [ Enum transformer ](https://spatie.be/docs/typescript-transformer/v3/transformers/enum-transformer)

Transformed providers
---------------------

- [ Getting started ](https://spatie.be/docs/typescript-transformer/v3/providers/getting-started)
- [ Using different writers in providers ](https://spatie.be/docs/typescript-transformer/v3/providers/writers-in-providers)
- [ Logging in providers ](https://spatie.be/docs/typescript-transformer/v3/providers/logging)
- [ Referencing types ](https://spatie.be/docs/typescript-transformer/v3/providers/references)
- [ Helpers ](https://spatie.be/docs/typescript-transformer/v3/providers/helpers)

TypeScript nodes
----------------

- [ Introduction ](https://spatie.be/docs/typescript-transformer/v3/typescript-nodes/introduction)
- [ Building your own TypeScript node ](https://spatie.be/docs/typescript-transformer/v3/typescript-nodes/custom-nodes)
- [ Visiting TypeScript nodes ](https://spatie.be/docs/typescript-transformer/v3/typescript-nodes/visitor)
- [ Node reference ](https://spatie.be/docs/typescript-transformer/v3/typescript-nodes/reference)

Watch mode
----------

- [ How does it work? ](https://spatie.be/docs/typescript-transformer/v3/watch-mode/how-it-works)
- [ Setting up the runner ](https://spatie.be/docs/typescript-transformer/v3/watch-mode/setting-up-the-runner)
- [ Watch events ](https://spatie.be/docs/typescript-transformer/v3/watch-mode/watch-events)
- [ PHP Nodes ](https://spatie.be/docs/typescript-transformer/v3/watch-mode/php-nodes)

Advanced
--------

- [ Extensions ](https://spatie.be/docs/typescript-transformer/v3/advanced/extensions)
- [ Managing transformers ](https://spatie.be/docs/typescript-transformer/v3/advanced/managing-transformers)
- [ Loggers ](https://spatie.be/docs/typescript-transformer/v3/advanced/loggers)
- [ Custom writers ](https://spatie.be/docs/typescript-transformer/v3/advanced/custom-writers)

 Custom writers
==============

###  On this page

1. [ Configuring TypeScript identifier name resolution ](#content-configuring-typescript-identifier-name-resolution)

It is possible to create your own writer by implementing the `Writer` interface:

```
use Spatie\TypeScriptTransformer\Data\WriteableFile;

interface Writer
{
/**
     * @param array $transformed
     *
     * @return array
     */
    public function output(
        array $transformed,
        TransformedCollection $transformedCollection,
    ): array;

    public function resolveReference(Transformed $transformed): ModuleImportResolvedReference|GlobalNamespaceResolvedReference;
}
```

The `output` method should return an array of `WriteableFile` objects, these objects contain the TypeScript code and the path relative to the configured output directory where the file should be stored.

The parameters for output are an array of `Transformed` objects and the full `TransformedCollection` while intuitively these structures seem similar, the array of `Transformed` objects only contains the objects that need to be written by this writer, while the`TransformedCollection` contains all transformed objects being handled by the package. This comes in handy when needing to resolve references to other transformed objects not being written by this writer.

In order to reference to other transformed objects you'll need an identifier to be used within the TypeScript code. For the GlobalNamespaceWriter this will be a fully qualified name like 'App.Models.User' while for the ModuleWriter this will be an import statement like `import { User } from '../models/User'` and then an identifier like `User`.

In order to resolve these references we strongly recommend you to use the `ResolveImportsAndResolvedReferenceMapAction`which takes the current path the writer is currently writing to, the array of transformed objects to be written by this writer in the current file and the full transformed collection:

```
use Spatie\TypeScriptTransformer\Actions\ResolveImportsAndResolvedReferenceMapAction;

[$imports, $resolvedReferenceMap] = (new ResolveImportsAndResolvedReferenceMapAction())->execute(
    currentPath: $currentPath, // e.g. 'app/models/index.ts'
    transformed: $transformed,
    transformedCollection: $transformedCollection,
);
```

The action returns a tuple containing, the collection of imports needed for the current file which can be transformed into TypeScript import nodes like this:

```
$imports->getTypeScriptNodes()
```

The action also returns a resolved reference map which is a mapping of the transformed reference key to their TypeScript identifier, this map should be provided to the `WritingContext` a structure required for writing TypeScript nodes.

We now can write out the imports and transformed objects like this:

```
$output = '';

foreach ($imports->getTypeScriptNodes() as $import) {
    $output .= $import->write($writingContext).PHP_EOL;
}

foreach ($location->transformed as $transformedItem) {
    $output .= $transformedItem->write($writingContext).PHP_EOL;
}

$writeableFile = new WriteableFile($filePath, $output);
```

In the end the writer then should return an array of all the `WriteableFile` objects it created.

Configuring TypeScript identifier name resolution
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

The `ResolveImportsAndResolvedReferenceMapAction` uses a default strategy to resolve TypeScript identifiers from all the transformed references. It works like this:

1. If the identifier was written by a GlobalNamespaceWriter, the fully qualified name is used as the identifier.
2. If no identifier in the module exists with the same name, the transformed name is used as the identifier.
3. If an identifier in the module already exists with the same name, a Import suffix is added to the transformed name.
4. If the Import suffix name also exists, a numeric suffix is added until a unique name is found.

It is possible to customize this strategy by providing a Closure returning a unique identifier for the current identifier to be used:

```
new ResolveImportsAndResolvedReferenceMapAction(
    moduleImportNameResolver: fn(string $identifier, array $usedIdentifiers): string => $identifier . uniqid(),
);
```

The first parameter is the identifier that would be used by default and the second parameter is an array of already used identifiers within the current module. Every time the closure is called the array of used identifiers is updated with the previously returned identifiers.

 A good
match?
-------------

### What we do best

- All things Laravel
- Custom frontend components
- Building APIs
- AI-powered features
- Simplifying things
- Clean solutions
- Integrating services

### Not our cup of tea

- WordPress themes
- Cutting corners
- Free mockups to win a job
- "Just execute the briefing"

 In short: we'd like to be a **substantial part** of your project.

 [ Get in touch via email ](mailto:info@spatie.be?subject=A%20good%20match%21&body=Tell%20us%20as%20much%20as%20you%20can%20about%0A-%20your%20online%20project%0A-%20your%20planning%0A-%20your%20budget%0A-%20%E2%80%A6%0A%0AAnything%20that%20helps%20us%20to%20start%20straightforward%21)
