Plugin API for Parcel bundler - provides base classes for creating Parcel plugins including transformers, resolvers, bundlers, namers, runtimes, packagers, optimizers, compressors, reporters, and validators
67
The Namer plugin generates output filenames for bundles based on content, configuration, and optimization strategies. Namers control how bundles are named in the final build output.
Base class for creating bundle naming plugins.
/**
* Base class for bundle naming plugins
* @template T - Configuration type for this namer
*/
export declare class Namer<T> {
constructor(opts: NamerOpts<T>);
}
/**
* Namer plugin configuration interface
* @template ConfigType - Type of configuration returned by loadConfig
*/
interface NamerOpts<ConfigType> {
/** Load configuration for this namer */
loadConfig?: (args: {
config: Config;
options: PluginOptions;
logger: PluginLogger;
tracer: PluginTracer;
}) => Promise<ConfigType> | ConfigType;
/** Generate a filename for a bundle (required) */
name(args: {
bundle: Bundle;
bundleGraph: BundleGraph<Bundle>;
config: ConfigType;
options: PluginOptions;
logger: PluginLogger;
tracer: PluginTracer;
}): Promise<FilePath | null>;
}Usage Example:
import { Namer } from "@parcel/plugin";
import path from "path";
import crypto from "crypto";
export default new Namer({
// Load namer configuration
loadConfig({config}) {
return {
pattern: config.pattern || '[name].[hash].[ext]',
hashLength: config.hashLength || 8,
preserveEntryNames: config.preserveEntryNames !== false
};
},
// Generate bundle filename (required)
async name({bundle, bundleGraph, config, options}) {
const bundleAssets = bundleGraph.getBundleAssets(bundle);
// Use entry name for entry bundles
if (bundle.entryAsset && config.preserveEntryNames) {
const entryName = path.basename(
bundle.entryAsset.filePath,
path.extname(bundle.entryAsset.filePath)
);
return this.formatName(config.pattern, {
name: entryName,
hash: this.generateHash(bundleAssets),
ext: this.getExtension(bundle.type)
});
}
// Generate name based on content
const contentHash = this.generateHash(bundleAssets);
const baseName = this.generateBaseName(bundle, bundleAssets);
return this.formatName(config.pattern, {
name: baseName,
hash: contentHash.slice(0, config.hashLength),
ext: this.getExtension(bundle.type)
});
},
// Helper methods
generateHash(assets) {
const hasher = crypto.createHash('md5');
for (const asset of assets) {
hasher.update(asset.getCode());
}
return hasher.digest('hex');
},
generateBaseName(bundle, assets) {
if (assets.length === 1) {
return path.basename(assets[0].filePath, path.extname(assets[0].filePath));
}
return 'chunk';
},
formatName(pattern, vars) {
return pattern.replace(/\[(\w+)\]/g, (match, key) => vars[key] || match);
},
getExtension(bundleType) {
const extensions = {
'js': 'js',
'css': 'css',
'html': 'html',
'json': 'json'
};
return extensions[bundleType] || bundleType;
}
});/**
* Bundle information available for naming
*/
interface Bundle {
/** Bundle ID */
id: string;
/** Bundle type (js, css, html, etc.) */
type: string;
/** Entry asset for entry bundles */
entryAsset?: Asset;
/** Main entry asset */
mainEntryAsset?: Asset;
/** Target environment */
target: Target;
/** Whether this bundle needs a stable name */
needsStableName: boolean;
/** Bundle behavior */
bundleBehavior?: BundleBehavior;
/** Bundle display name */
displayName?: string;
/** Bundle metadata */
meta: Record<string, any>;
}
/**
* Bundle graph for accessing related bundles and assets
*/
interface BundleGraph<TBundle> {
/** Get assets in a bundle */
getBundleAssets(bundle: TBundle): Array<Asset>;
/** Get bundle dependencies */
getBundleDependencies(bundle: TBundle): Array<TBundle>;
/** Get bundles that depend on this bundle */
getBundleDependents(bundle: TBundle): Array<TBundle>;
/** Get all bundles */
getBundles(): Array<TBundle>;
/** Check if bundle has dependency on another bundle */
bundleHasDependency(bundle: TBundle, dependency: TBundle): boolean;
}Hash-based Naming:
// Content-based hashing for cache busting
const contentHash = crypto.createHash('md5');
for (const asset of bundleAssets) {
contentHash.update(asset.getCode());
}
const hash = contentHash.digest('hex').slice(0, 8);
return `${baseName}.${hash}.${extension}`;Entry Name Preservation:
// Preserve original entry file names
if (bundle.entryAsset && bundle.needsStableName) {
const originalName = path.basename(
bundle.entryAsset.filePath,
path.extname(bundle.entryAsset.filePath)
);
return `${originalName}.${extension}`;
}Hierarchical Naming:
// Create directory structure based on bundle type
const typeDir = bundle.type;
const targetDir = bundle.target.name;
return path.join(targetDir, typeDir, `${baseName}.${extension}`);Chunk Naming:
// Name shared chunks based on their dependencies
const dependencies = bundleGraph.getBundleDependencies(bundle);
if (dependencies.length > 1) {
const depNames = dependencies
.map(dep => dep.displayName || 'chunk')
.sort()
.join('-');
return `shared-${depNames}.${hash}.${extension}`;
}/**
* Common file extensions for different bundle types
*/
interface BundleTypeExtensions {
'js': 'js';
'css': 'css';
'html': 'html';
'json': 'json';
'xml': 'xml';
'txt': 'txt';
'wasm': 'wasm';
'webmanifest': 'json';
}/**
* Common naming configuration options
*/
interface NamingConfig {
/** Filename pattern with placeholders */
pattern?: string;
/** Length of content hash */
hashLength?: number;
/** Whether to preserve entry file names */
preserveEntryNames?: boolean;
/** Output subdirectories by type */
outputDir?: Record<string, string>;
/** Custom name mapping */
customNames?: Record<string, string>;
}Pattern Placeholders:
[name] - Base name of the file or bundle[hash] - Content hash for cache busting[ext] - File extension based on bundle type[id] - Bundle ID[type] - Bundle type[target] - Target environment nameInstall with Tessl CLI
npx tessl i tessl/npm-parcel--plugindocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10