Transformer

Transformer plugins transform a single asset to compile it, discover dependencies, or convert it to a different format. Many transformers are wrappers around other tools such as compilers and preprocessors, and are responsible for integrating them with Parcel.

Transforming assets

#

There is one required function that must be passed to the Transformer constructor: transform. This function receives a MutableAsset object, which represents a file. The source code or content of the asset can be retrieved, along with any associated source map (see below). The transformer can then transform this content in some way, and set the compiled result back on the asset.

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async transform({asset}) {
// Retrieve the asset's source code and source map.
let source = await asset.getCode();
let sourceMap = await asset.getMap();

// Run it through some compiler, and set the results
// on the asset.
let {code, map} = compile(source, sourceMap);
asset.setCode(code);
asset.setMap(map);

// Return the asset
return [asset];
}
});

Loading configuration

#

Loading configuration from the user’s project should be done in the loadConfig method of a Transformer plugin. See Loading configuration for details on how to do this.

Note: It's important to use Parcel's config loading mechanism so that the cache can be properly invalidated. Avoid loading files directly from the file system.

Changing the asset type

#

Transformers may transform an asset from one format to another, for example from TypeScript to JavaScript. To do this, set the asset's type property to the new file type (e.g. js). The asset will then be processed by the pipeline matching the new type. See Transformers in the Parcel configuration docs for details.

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async transform({asset}) {
let code = await asset.getCode();

let result = compile(code);
asset.type = 'js';
asset.setCode(result);

return [asset];
}
});

The environment

#

Assets are associated with an Environment, which describes the how the asset should be compiled. The same asset may be processed multiple times with different environments, for example, when building for modern and legacy targets. If possible, Transformer plugins should take the environment into account when compiling code to ensure that the result works in and is optimized for the target. See Targets for details.

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async transform({asset}) {
let code = await asset.getCode();

let result = asset.env.isBrowser()
? compileForBrowser(code, asset.engines.browser)
: compileForNode(code, asset.engines.node);

asset.setCode(result);
return [asset];
}
});

See the Environment API docs for details on the available properties.

Adding dependencies

#

In addition to transforming the contents of an asset, Transformer plugins are also responsible for discovering dependencies in the code so that they may also be processed by Parcel. Some transformers don’t need to worry about this, because another transformer will run afterward and do it (e.g. the default JavaScript transformer). If you’re adding support for a new language that doesn’t compile to one of the existing languages supported by Parcel, or otherwise introduces dependencies outside the compiled code, you’ll need to add them to the asset.

Dependencies can be added to an asset using the addDependency method, passing a DependencyOptions object. There are two required parameters: specifier, which is a string describing the location of the dependency, and specifierType, which describes how the specifier should be interpreted. See Dependency resolution for details.

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async transform({asset}) {
let code = await asset.getCode();
let deps = code.matchAll(/import "(.*?)"/g);

for (let dep of deps) {
asset.addDependency({
specifier: dep,
specifierType: 'esm'
});
}

return [asset];
}
});

Influencing bundling

#

The way a dependency is specified can influence how it is bundled. By default, dependencies are bundled together into the same output file. The priority property of a dependency can specify that it should be loaded lazily or in parallel with the asset that depends on it. For example, dynamic import() in JavaScript loads dependencies with the lazy priority. See Code splitting.

The bundleBehavior property further controls how a dependency is bundled. For example, dependencies may be separated into a new bundle but inlined into the parent by setting bundleBehavior to inline. See Bundle inlining.

See DependencyOptions for more details on each of the available options.

URL dependencies

#

In some languages, dependencies reference other files by URL. In the compiled code, these URL references will need to be updated to point to the final bundle names. However, when an asset is being transformed, bundle names will not yet be known.

addDependency returns a unique dependency ID. This can be placed in the transformed asset content as a placeholder for the final bundle URL, and it will be replaced later by a Packager once the URL is known.

As a shortcut, the addURLDependency method creates a dependency with specifierType set to url, and priority set to lazy (to create a separate bundle).

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async transform({asset}) {
let code = await asset.getCode();
let result = code.replace(/import "(.*?)"/g, (m, dep) => {
// Replace the original specifier with a dependency id
// as a placeholder. This will be replaced later with
// the final bundle URL.
let depId = asset.addURLDependency(dep);
return `import "${depId}"`;
});

asset.setCode(result);
return [asset];
}
});

Reusing ASTs

#

If multiple Transformer plugins run in series over an asset, it would be wasteful to parse, transform, and code generate for each one if they could reuse the same parsed AST. Parcel facilitates AST sharing by splitting the transform function into several parts:

import {Transformer} from '@parcel/plugin';
import semver from 'semver';

export default new Transformer({
async canReuseAST({ast}) {
return ast.type === 'my-compiler'
&& semver.satisfies(ast.version, '^1.0.0');
},
async parse({asset}) {
return {
type: 'my-compiler',
version: '1.0.0',
program: parse(await asset.getCode())
};
},
async transform({asset}) {
let ast = await asset.getAST();

let compiledAST = compile(ast.program);
asset.setAST({
type: 'my-compiler',
version: '1.0.0',
program: compiledAST
});

return [asset];
},
async generate({ast}) {
let {content, map} = generate(ast.program);
return {
content,
map
};
}
});

Source maps

#

Source maps help developers when debugging compiled and bundled code in the browser by mapping locations in the compiled code back to the original source code. Transformer plugins should add a source map to assets when transforming their content if possible. Since assets may be processed by multiple Transformers, you should also transform the existing source map included with the asset in addition to its content.

Parcel uses the @parcel/source-map library for source map manipulation. See Source Maps for more details on how to use it. You may need to convert the source map you pass to and from other tools.

import {Transformer} from '@parcel/plugin';
import SourceMap from '@parcel/source-map';

export default new Transformer({
async transform({asset, options}) {
let source = await asset.getCode();
let sourceMap = await asset.getMap();

// Convert the input source map to JSON.
let result = compile(source, sourceMap.toVLQ());
asset.setCode(result.code);

// Convert returned JSON source map to a Parcel SourceMap.
let map = new SourceMap(options.projectRoot);
map.addVLQMap(result.map);
asset.setMap(map);

return [asset];
}
});

Binary data

#

In addition to textual source code, Transformers can handle binary content. This can be done either by using a Buffer, or using streams.

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async transform({asset}) {
let buffer = await asset.getBuffer();

let result = transform(buffer);
asset.setBuffer(result);

return [asset];
}
});

Query parameters

#

An asset may be referenced by a dependency with query parameters. These specify options for the transformer to use when compiling or transforming the asset. For example, the Parcel image transformer uses query parameters to allow users to specify the width, height, and format to convert images to. The same asset may be compiled multiple times with different query parameters.

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async transform({asset}) {
let buffer = await asset.getBuffer();

let result = resize(
buffer,
asset.query.width,
asset.query.height
);

asset.setBuffer(result);
return [asset];
}
});

Returning multiple assets

#

All of the examples so far have shown how to transform a single asset. However, sometimes a file may contain multiple different assets. For example, in HTML there are inline <script> and <style> elements that should be processed through their own separate Parcel pipelines. For this, Parcel allows returning multiple assets from a transformer.

To create new assets, return an array of TransformerResult objects. These must have a type and content, but may also have dependencies of their own, as well as many of the same options that Asset objects have.

Usually, the original asset should be returned in addition to any child assets it may have. The parent can create dependencies on the children by assigning them a uniqueKey property and referencing that as the specifier of a dependency. This allows creating "virtual" assets that don't really exist on the file system, but may be referenced like they are.

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async transform({asset}) {
let code = await asset.getCode();

// Extract inline assets to return in addition to this asset.
let assets = [asset];

let uniqueKey = `${asset.id}-style`;
assets.push({
type: 'css',
content: '...',
uniqueKey,
bundleBehavior: 'inline'
});

// Add a dependency, using the uniqueKey as a specifier.
asset.addDependency({
specifier: uniqueKey,
specifierType: 'esm'
});

return assets;
}
});

Relevant API

#

DependencyOptions parcel/packages/core/types/index.js:464

Usen when creating a Dependency, see that.

type DependencyOptions = {|
  +specifier: DependencySpecifier,

The specifier used to resolve the dependency.

  +specifierType: SpecifierType,

How the specifier should be interpreted.

  • esm: An ES module specifier. It is parsed as a URL, but bare specifiers are treated as node_modules.
  • commonjs: A CommonJS specifier. It is not parsed as a URL.
  • url: A URL that works as in a browser. Bare specifiers are treated as relative URLs.
  • custom: A custom specifier. Must be handled by a custom resolver plugin.
  +priority?: DependencyPriority,

When the dependency should be loaded.

  • sync: The dependency should be resolvable synchronously. The resolved asset will be placed in the same bundle as the parent, or another bundle that's already on the page.
  • parallel: The dependency should be placed in a separate bundle that's loaded in parallel with the current bundle.
  • lazy: The dependency should be placed in a separate bundle that's loaded later.

Default value: 'sync'

  +bundleBehavior?: BundleBehavior,

Controls the behavior of the bundle the resolved asset is placed into. Use in combination with priority to determine when the bundle is loaded.

  • inline: The resolved asset will be placed into a new inline bundle. Inline bundles are not written to a separate file, but embedded into the parent bundle.
  • isolated: The resolved asset will be isolated from its parents in a separate bundle. Shared assets will be duplicated.
  +needsStableName?: boolean,

When the dependency is a bundle entry (priority is "parallel" or "lazy"), this controls the naming of that bundle. needsStableName indicates that the name should be stable over time, even when the content of the bundle changes. This is useful for entries that a user would manually enter the URL for, as well as for things like service workers or RSS feeds, where the URL must remain consistent over time.

  +isOptional?: boolean,

Whether the dependency is optional. If the dependency cannot be resolved, this will not fail the build.

  +loc?: SourceLocation,

The location within the source file where the dependency was found.

  +env?: EnvironmentOptions,

The environment of the dependency.

  +meta?: Meta,

Plugin-specific metadata for the dependency.

  +pipeline?: string,

The pipeline defined in .parcelrc that the dependency should be processed with.

  +resolveFrom?: FilePath,

The file path where the dependency should be resolved from. By default, this is the path of the source file where the dependency was specified.

  +symbols?: $ReadOnlyMap<Symbol, {|
    local: Symbol,
    loc: ?SourceLocation,
    isWeak: boolean,
    meta?: Meta,
  |}>,

The symbols within the resolved module that the source file depends on.

|}
Referenced by:
MutableAsset, TransformerResult

Dependency parcel/packages/core/types/index.js:530

A Dependency denotes a connection between two assets (likely some effect from the importee is expected - be it a side effect or a value is being imported).

interface Dependency {
  +id: string,

The id of the dependency.

  +specifier: DependencySpecifier,

The specifier used to resolve the dependency.

  +specifierType: SpecifierType,

How the specifier should be interpreted.

  • esm: An ES module specifier. It is parsed as a URL, but bare specifiers are treated as node_modules.
  • commonjs: A CommonJS specifier. It is not parsed as a URL.
  • url: A URL that works as in a browser. Bare specifiers are treated as relative URLs.
  • custom: A custom specifier. Must be handled by a custom resolver plugin.
  +priority: DependencyPriority,

When the dependency should be loaded.

  • sync: The dependency should be resolvable synchronously. The resolved asset will be placed in the same bundle as the parent, or another bundle that's already on the page.
  • parallel: The dependency should be placed in a separate bundle that's loaded in parallel with the current bundle.
  • lazy: The dependency should be placed in a separate bundle that's loaded later.

Default value: 'sync'

  +bundleBehavior: ?BundleBehavior,

Controls the behavior of the bundle the resolved asset is placed into. Use in combination with priority to determine when the bundle is loaded.

  • inline: The resolved asset will be placed into a new inline bundle. Inline bundles are not written to a separate file, but embedded into the parent bundle.
  • isolated: The resolved asset will be isolated from its parents in a separate bundle. Shared assets will be duplicated.
  +needsStableName: boolean,

When the dependency is a bundle entry (priority is "parallel" or "lazy"), this controls the naming of that bundle. needsStableName indicates that the name should be stable over time, even when the content of the bundle changes. This is useful for entries that a user would manually enter the URL for, as well as for things like service workers or RSS feeds, where the URL must remain consistent over time.

  +isOptional: boolean,

Whether the dependency is optional. If the dependency cannot be resolved, this will not fail the build.

  +isEntry: boolean,

Whether the dependency is an entry.

  +loc: ?SourceLocation,

The location within the source file where the dependency was found.

  +env: Environment,

The environment of the dependency.

  +meta: Meta,

Plugin-specific metadata for the dependency.

  +target: ?Target,

If this is an entry, this is the target that is associated with that entry.

  +sourceAssetId: ?string,

The id of the asset with this dependency.

  +sourcePath: ?FilePath,

The file path of the asset with this dependency.

  +sourceAssetType: ?string,

The type of the asset that referenced this dependency.

  +resolveFrom: ?FilePath,

The file path where the dependency should be resolved from. By default, this is the path of the source file where the dependency was specified.

  +pipeline: ?string,

The pipeline defined in .parcelrc that the dependency should be processed with.

  +symbols: MutableDependencySymbols,
}
Referenced by:
BaseAsset, Bundle, BundleGraph, BundleGraphTraversable, BundleTraversable, DependencyOptions, DependencySpecifier, MutableBundleGraph, Resolver, ResolvingProgressEvent, RuntimeAsset

ASTGenerator parcel/packages/core/types/index.js:609

type ASTGenerator = {|
  type: string,
  version: Semver,
|}
Referenced by:
BaseAsset

BaseAsset parcel/packages/core/types/index.js:622

An asset represents a file or part of a file. It may represent any data type, including source code, binary data, etc. Assets may exist in the file system or may be virtual.

interface BaseAsset {
  +id: string,

The id of the asset.

  +fs: FileSystem,

The file system where the source is located.

  +filePath: FilePath,

The file path of the asset.

  +type: string,

The asset's type. This initially corresponds to the source file extension, but it may be changed during transformation.

  +query: URLSearchParams,

The transformer options for the asset from the dependency query string.

  +env: Environment,

The environment of the asset.

  +isSource: boolean,

Whether this asset is part of the project, and not an external dependency (e.g. in node_modules). This indicates that transformation using the project's configuration should be applied.

  +meta: Meta,

Plugin-specific metadata for the asset.

  +bundleBehavior: ?BundleBehavior,

Controls which bundle the asset is placed into.

  • inline: The asset will be placed into a new inline bundle. Inline bundles are not written to a separate file, but embedded into the parent bundle.
  • isolated: The asset will be isolated from its parents in a separate bundle. Shared assets will be duplicated.
  +isBundleSplittable: boolean,

If the asset is used as a bundle entry, this controls whether that bundle can be split into multiple, or whether all of the dependencies must be placed in a single bundle.

  +sideEffects: boolean,

Whether this asset can be omitted if none of its exports are being used. This is initially set by the resolver, but can be overridden by transformers.

  +uniqueKey: ?string,

When a transformer returns multiple assets, it can give them unique keys to identify them. This can be used to find assets during packaging, or to create dependencies between multiple assets returned by a transformer by using the unique key as the dependency specifier.

  +astGenerator: ?ASTGenerator,

The type of the AST.

  +pipeline: ?string,

The pipeline defined in .parcelrc that the asset should be processed with.

  +symbols: AssetSymbols,

The symbols that the asset exports.

  getAST(): Promise<?AST>,

Returns the current AST.

  getCode(): Promise<string>,

Returns the asset contents as a string.

  getBuffer(): Promise<Buffer>,

Returns the asset contents as a buffer.

  getStream(): Readable,

Returns the asset contents as a stream.

  getMap(): Promise<?SourceMap>,

Returns the source map for the asset, if available.

  getMapBuffer(): Promise<?Buffer>,

Returns a buffer representation of the source map, if available.

  getDependencies(): $ReadOnlyArray<Dependency>,

Returns a list of dependencies for the asset.

}
Referenced by:
Asset, MutableAsset, ResolveResult

MutableAsset parcel/packages/core/types/index.js:695

A mutable Asset, available during transformation.

interface MutableAsset extends BaseAsset {
  type: string,

The asset's type. This initially corresponds to the source file extension, but it may be changed during transformation.

  bundleBehavior: ?BundleBehavior,

Controls which bundle the asset is placed into.

  • inline: The asset will be placed into a new inline bundle. Inline bundles are not written to a separate file, but embedded into the parent bundle.
  • isolated: The asset will be isolated from its parents in a separate bundle. Shared assets will be duplicated.
  isBundleSplittable: boolean,

If the asset is used as a bundle entry, this controls whether that bundle can be split into multiple, or whether all of the dependencies must be placed in a single bundle.

Default value:

  sideEffects: boolean,

Whether this asset can be omitted if none of its exports are being used. This is initially set by the resolver, but can be overridden by transformers.

  +symbols: MutableAssetSymbols,

The symbols that the asset exports.

  addDependency(DependencyOptions): string,

Adds a dependency to the asset.

  addURLDependency(url: string, opts: $Shape<DependencyOptions>): string,

Adds a url dependency to the asset. This is a shortcut for addDependency that sets the specifierType to 'url' and priority to 'lazy'.

  invalidateOnFileChange(FilePath): void,

Invalidates the transformation when the given file is modified or deleted.

  invalidateOnFileCreate(FileCreateInvalidation): void,

Invalidates the transformation when matched files are created.

  invalidateOnEnvChange(string): void,

Invalidates the transformation when the given environment variable changes.

  setCode(string): void,

Sets the asset contents as a string.

  setBuffer(Buffer): void,

Sets the asset contents as a buffer.

  setStream(Readable): void,

Sets the asset contents as a stream.

  setAST(AST): void,

Returns whether the AST has been modified.

  isASTDirty(): boolean,

Sets the asset's AST.

  setMap(?SourceMap): void,

Sets the asset's source map.

  setEnvironment(opts: EnvironmentOptions): void,
}
Referenced by:
Transformer

Config parcel/packages/core/types/index.js:779

interface Config {
  +isSource: boolean,

Whether this config is part of the project, and not an external dependency (e.g. in node_modules). This indicates that transformation using the project's configuration should be applied.

  +searchPath: FilePath,

The path of the file to start searching for config from.

  +env: Environment,

The environment

  invalidateOnFileChange(FilePath): void,

Invalidates the config when the given file is modified or deleted.

  invalidateOnFileCreate(FileCreateInvalidation): void,

Invalidates the config when matched files are created.

  invalidateOnEnvChange(string): void,

Invalidates the config when the given environment variable changes.

  invalidateOnStartup(): void,

Invalidates the config when Parcel restarts.

  addDevDependency(DevDepOptions): void,

Adds a dev dependency to the config. If the dev dependency or any of its dependencies change, the config will be invalidated.

  setCacheKey(string): void,

Sets the cache key for the config. By default, this is computed as a hash of the files passed to invalidateOnFileChange or loaded by getConfig. If none, then a hash of the result returned from loadConfig is used. This method can be used to override this behavior and explicitly control the cache key. This can be useful in cases where only part of a file is used to avoid unnecessary invalidations, or when the result is not hashable (i.e. contains non-serializable properties like functions).

  getConfig<T>(filePaths: Array<FilePath>, options: ?{|
    packageKey?: string,
    parse?: boolean,
    exclude?: boolean,
  |}): Promise<?ConfigResultWithFilePath<T>>,

Searches for config files with the given names in all parent directories of the config's searchPath.

  getConfigFrom<T>(searchPath: FilePath, filePaths: Array<FilePath>, options: ?{|
    packageKey?: string,
    parse?: boolean,
    exclude?: boolean,
  |}): Promise<?ConfigResultWithFilePath<T>>,

Searches for config files with the given names in all parent directories of the passed searchPath.

  getPackage(): Promise<?PackageJSON>,

Finds the nearest package.json from the config's searchPath.

}
Referenced by:
Bundler, Namer, Optimizer, Packager, Runtime, Transformer

GenerateOutput parcel/packages/core/types/index.js:850

type GenerateOutput = {|
  +content: Blob,
  +map?: ?SourceMap,
|}
Referenced by:
Transformer

TransformerResult parcel/packages/core/types/index.js:864

Transformers can return multiple result objects to create new assets. For example, a file may contain multiple parts of different types, which should be processed by their respective transformation pipelines.

type TransformerResult = {|
  +type: string,

The asset's type.

  +content?: ?Blob,

The content of the asset. Either content or an AST is required.

  +ast?: ?AST,

The asset's AST. Either content or an AST is required.

  +map?: ?SourceMap,

The source map for the asset.

  +dependencies?: $ReadOnlyArray<DependencyOptions>,

The dependencies of the asset.

  +env?: EnvironmentOptions,

The environment of the asset. The options are merged with the input asset's environment.

  +bundleBehavior?: ?BundleBehavior,

Controls which bundle the asset is placed into.

  • inline: The asset will be placed into a new inline bundle. Inline bundles are not written to a separate file, but embedded into the parent bundle.
  • isolated: The asset will be isolated from its parents in a separate bundle. Shared assets will be duplicated.
  +isBundleSplittable?: boolean,

If the asset is used as a bundle entry, this controls whether that bundle can be split into multiple, or whether all of the dependencies must be placed in a single bundle.

  +meta?: Meta,

Plugin-specific metadata for the asset.

  +pipeline?: ?string,

The pipeline defined in .parcelrc that the asset should be processed with.

  +sideEffects?: boolean,

Whether this asset can be omitted if none of its exports are being used. This is initially set by the resolver, but can be overridden by transformers.

  +symbols?: $ReadOnlyMap<Symbol, {|
    local: Symbol,
    loc: ?SourceLocation,
  |}>,

The symbols that the asset exports.

  +uniqueKey?: ?string,

When a transformer returns multiple assets, it can give them unique keys to identify them. This can be used to find assets during packaging, or to create dependencies between multiple assets returned by a transformer by using the unique key as the dependency specifier.

|}
Referenced by:
Transformer

ResolveFn parcel/packages/core/types/index.js:944

Type
type ResolveFn = (from: FilePath, to: string) => Promise<FilePath>;
Referenced by:
Transformer

Transformer parcel/packages/core/types/index.js:1011

The methods for a transformer plugin.

type Transformer<ConfigType> = {|
  loadConfig?: ({|
    config: Config,
    options: PluginOptions,
    logger: PluginLogger,
  |}) => Promise<ConfigType> | ConfigType,
  canReuseAST?: ({|
    ast: AST,
    options: PluginOptions,
    logger: PluginLogger,
  |}) => boolean,

Whether an AST from a previous transformer can be reused (to prevent double-parsing)

  parse?: ({|
    asset: Asset,
    config: ConfigType,
    resolve: ResolveFn,
    options: PluginOptions,
    logger: PluginLogger,
  |}) => Async<?AST>,

Parse the contents into an ast

  transform({|
    asset: MutableAsset,
    config: ConfigType,
    resolve: ResolveFn,
    options: PluginOptions,
    logger: PluginLogger,
  |}): Async<Array<TransformerResult | MutableAsset>>,

Transform the asset and/or add new assets

  generate?: ({|
    asset: Asset,
    ast: AST,
    options: PluginOptions,
    logger: PluginLogger,
  |}) => Async<GenerateOutput>,

Stringify the AST

|}