The JS plugin for Nx contains executors and generators that provide the best experience for developing JavaScript and TypeScript projects.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
@nx/js provides comprehensive static asset handling capabilities for copying, processing, and managing non-code files during the build process with support for glob patterns, watch mode, and flexible output configuration.
High-performance asset copying with glob pattern support, filtering, and flexible input/output mapping.
/**
* Copies static assets from source to destination with glob pattern support
* @param options - Asset copying configuration options
* @param context - Nx executor context for project information
* @returns Promise resolving to copy result with success status and optional cleanup
*/
function copyAssets(
options: CopyAssetsOptions,
context: ExecutorContext
): Promise<CopyAssetsResult>;
interface CopyAssetsOptions {
assets: Array<AssetGlob | string>; // Asset patterns or simple paths
outputPath: string; // Base output directory
watchMode?: WatchMode; // Optional watch mode configuration
}
interface CopyAssetsResult {
success: boolean; // Whether copy operation succeeded
stop?(): void; // Optional cleanup function for watch mode
}
interface WatchMode {
onCopy?: (files: string[]) => void; // Callback for when files are copied
}Usage Examples:
import { copyAssets } from "@nx/js";
// Basic asset copying
const result = await copyAssets({
assets: [
'src/assets/**/*', // Simple glob pattern
'README.md', // Single file
{
input: 'src/docs', // Source directory
output: './documentation', // Destination directory
glob: '**/*.md' // File pattern
}
],
outputPath: 'dist/my-app'
}, context);
// With watch mode
const watchResult = await copyAssets({
assets: ['src/assets/**/*'],
outputPath: 'dist/my-app',
watchMode: {
onCopy: (files) => {
console.log(`Copied ${files.length} files`);
}
}
}, context);
// Cleanup watcher when done
if (watchResult.stop) {
watchResult.stop();
}Converts asset glob patterns and configurations into concrete file input/output mappings.
/**
* Converts asset globs to concrete file input/output mappings
* @param assets - Array of asset patterns or configurations
* @param rootDir - Root directory for resolving relative paths
* @param outDir - Base output directory for assets
* @returns Array of file input/output mappings
*/
function assetGlobsToFiles(
assets: Array<AssetGlob | string>,
rootDir: string,
outDir: string
): FileInputOutput[];
interface FileInputOutput {
input: string; // Source file path
output: string; // Destination file path
}
interface AssetGlob extends FileInputOutput {
glob: string; // Glob pattern for matching files
ignore?: string[]; // Patterns to ignore
dot?: boolean; // Include dotfiles (default: false)
includeIgnoredFiles?: boolean; // Include files ignored by .gitignore
}Usage Examples:
import { assetGlobsToFiles } from "@nx/js";
// Convert asset configurations to file mappings
const fileMap = assetGlobsToFiles([
'src/assets/**/*', // Simple pattern
{
input: 'src/images', // Source directory
output: './assets/images', // Output directory
glob: '**/*.{png,jpg,gif}', // Image files only
ignore: ['**/*.tmp'], // Ignore temporary files
dot: false // Exclude dotfiles
},
{
input: 'docs',
output: './documentation',
glob: '**/*.md',
includeIgnoredFiles: true // Include gitignored files
}
], 'libs/my-lib', 'dist/libs/my-lib');
// Results in array like:
// [
// { input: 'libs/my-lib/src/assets/logo.png', output: 'dist/libs/my-lib/logo.png' },
// { input: 'libs/my-lib/src/images/hero.jpg', output: 'dist/libs/my-lib/assets/images/hero.jpg' },
// { input: 'libs/my-lib/docs/README.md', output: 'dist/libs/my-lib/documentation/README.md' }
// ]Use simple glob strings for basic asset copying:
const assets = [
'src/assets/**/*', // All files in assets directory
'*.md', // All markdown files in root
'public/**/*.{html,css,js}' // Web assets
];Use AssetGlob objects for precise control over asset handling:
const assets: AssetGlob[] = [
{
input: 'src/assets', // Source directory
output: './static', // Output subdirectory
glob: '**/*', // All files recursively
ignore: ['**/*.tmp', '**/.DS_Store'], // Ignore patterns
dot: false, // Exclude dotfiles
includeIgnoredFiles: false // Respect .gitignore
},
{
input: 'src/translations',
output: './i18n',
glob: '*.json', // JSON files only
dot: true, // Include dotfiles
includeIgnoredFiles: true // Include ignored files
}
];Combine simple strings and detailed configurations:
const assets = [
'README.md', // Simple file copy
'LICENSE', // Another simple file
{
input: 'src/assets', // Detailed configuration
output: './assets',
glob: '**/*.{png,jpg,svg}',
ignore: ['**/*-temp.*']
},
'src/styles/**/*.css' // Simple glob pattern
];Copy different assets based on build configuration:
// Development assets (includes source maps, unminified files)
const devAssets = [
'src/assets/**/*',
'src/styles/**/*.css',
'src/scripts/**/*.js'
];
// Production assets (optimized, compressed)
const prodAssets = [
{
input: 'src/assets',
output: './assets',
glob: '**/*.{png,jpg,svg,webp}', // Image assets only
ignore: ['**/*-dev.*'] // Exclude development assets
},
{
input: 'dist/styles', // Pre-processed styles
output: './styles',
glob: '*.min.css' // Minified CSS only
}
];Include different assets based on environment or configuration:
const baseAssets = ['README.md', 'LICENSE'];
const environmentAssets = process.env.NODE_ENV === 'production'
? [
{
input: 'src/assets/prod',
output: './assets',
glob: '**/*'
}
]
: [
{
input: 'src/assets/dev',
output: './assets',
glob: '**/*',
includeIgnoredFiles: true
}
];
const allAssets = [...baseAssets, ...environmentAssets];Use asset copying with file watching for development:
import { copyAssets } from "@nx/js";
const setupAssetWatcher = async () => {
const result = await copyAssets({
assets: ['src/assets/**/*'],
outputPath: 'dist/my-app',
watchMode: {
onCopy: (files) => {
console.log(`Asset update: ${files.length} files copied`);
// Trigger other processes like live reload
notifyLiveReload();
}
}
}, context);
// Return cleanup function
return () => {
if (result.stop) {
result.stop();
}
};
};
// Use in development server
const stopWatching = await setupAssetWatcher();
// Cleanup on exit
process.on('SIGINT', () => {
stopWatching();
process.exit(0);
});Organize assets by type, environment, or feature:
// By asset type
const assetsByType = [
{
input: 'src/images',
output: './assets/images',
glob: '**/*.{png,jpg,jpeg,gif,svg,webp}',
ignore: ['**/*-temp.*']
},
{
input: 'src/fonts',
output: './assets/fonts',
glob: '**/*.{woff,woff2,ttf,eot}',
dot: false
},
{
input: 'src/data',
output: './assets/data',
glob: '**/*.{json,csv,xml}',
includeIgnoredFiles: false
}
];
// By feature or module
const assetsByFeature = [
{
input: 'src/modules/dashboard/assets',
output: './assets/dashboard',
glob: '**/*'
},
{
input: 'src/modules/profile/assets',
output: './assets/profile',
glob: '**/*'
}
];Combine asset copying with other build processes:
// In an executor or build script
const buildWithAssets = async (options, context) => {
// 1. Clean output directory
await fs.remove(options.outputPath);
// 2. Compile TypeScript/JavaScript
await compileCode(options, context);
// 3. Copy assets
const assetResult = await copyAssets({
assets: options.assets,
outputPath: options.outputPath
}, context);
// 4. Generate package.json
if (options.generatePackageJson) {
await generatePackageJson(options, context);
}
return {
success: assetResult.success
};
};Asset operations include comprehensive error handling:
try {
const result = await copyAssets({
assets: ['src/assets/**/*'],
outputPath: 'dist/my-app'
}, context);
if (!result.success) {
console.error('Asset copying failed');
process.exit(1);
}
} catch (error) {
console.error('Asset copying error:', error.message);
// Handle specific error types
if (error.code === 'ENOENT') {
console.error('Source asset directory not found');
}
process.exit(1);
}Install with Tessl CLI
npx tessl i tessl/npm-nx--js