A Rollup plugin that transforms dynamic imports containing variables into static imports with runtime switching logic.
npx @tessl/cli install tessl/npm-rollup--plugin-dynamic-import-vars@2.1.0A Rollup plugin that transforms dynamic imports containing variables into static imports with runtime switching logic. It analyzes template literals and string concatenation patterns in dynamic imports and generates glob patterns to include matching files in the bundle.
npm install @rollup/plugin-dynamic-import-vars --save-devimport dynamicImportVars from '@rollup/plugin-dynamic-import-vars';For CommonJS:
const dynamicImportVars = require('@rollup/plugin-dynamic-import-vars');Named imports:
import dynamicImportVars, { dynamicImportToGlob, VariableDynamicImportError } from '@rollup/plugin-dynamic-import-vars';// rollup.config.js
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
export default {
input: 'src/index.js',
output: {
dir: 'dist',
format: 'es'
},
plugins: [
dynamicImportVars({
include: ['src/**/*.js'],
exclude: ['node_modules/**'],
warnOnError: false,
errorWhenNoFilesFound: false
})
]
};The plugin transforms code like this:
// Before transformation
function importLocale(locale) {
return import(`./locales/${locale}.js`);
}Into this:
// After transformation
function __variableDynamicImportRuntime0__(path) {
switch (path) {
case './locales/en-GB.js': return import('./locales/en-GB.js');
case './locales/en-US.js': return import('./locales/en-US.js');
case './locales/nl-NL.js': return import('./locales/nl-NL.js');
default: return new Promise(function(resolve, reject) {
(typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)(
reject.bind(null, new Error("Unknown variable dynamic import: " + path))
);
});
}
}
function importLocale(locale) {
return __variableDynamicImportRuntime0__(`./locales/${locale}.js`);
}The plugin uses a sophisticated multi-stage approach to transform dynamic imports containing variables:
estree-walker to traverse the parsed JavaScript AST and locate ImportExpression nodesdynamicImportToGlob utility converts variable dynamic import expressions into glob patterns by analyzing template literals, string concatenation, and method chaining patternsfast-glob to find all files matching the generated glob pattern relative to the importing modulemagic-string to replace the original dynamic import with a call to the generated runtime function while preserving import assertionsThis approach ensures that all potential imports are statically analyzable and included in the bundle while maintaining runtime behavior that matches the original dynamic import logic.
Creates a Rollup plugin instance that transforms dynamic imports containing variables.
/**
* Creates a Rollup plugin that transforms dynamic imports containing variables
* @param options - Configuration options for the plugin
* @returns Rollup plugin instance
*/
function dynamicImportVars(options?: RollupDynamicImportVariablesOptions): Plugin;
interface RollupDynamicImportVariablesOptions {
/** Files to include in transformation (picomatch pattern or array) */
include?: FilterPattern;
/** Files to exclude from transformation (picomatch pattern or array) */
exclude?: FilterPattern;
/** Whether to throw errors when no files match glob patterns (default: false) */
errorWhenNoFilesFound?: boolean;
/** Whether to warn instead of error on invalid patterns (default: false) */
warnOnError?: boolean;
}
type FilterPattern = string | RegExp | Array<string | RegExp> | null;
interface Plugin {
name: string;
transform(code: string, id: string): TransformResult | null;
}
interface TransformResult {
code: string;
map: SourceMap;
}Usage Examples:
// Basic usage with default options
export default {
plugins: [dynamicImportVars()]
};
// With specific file patterns
export default {
plugins: [
dynamicImportVars({
include: ['src/**/*.js', 'lib/**/*.mjs'],
exclude: ['src/legacy/**']
})
]
};
// With error handling configuration
export default {
plugins: [
dynamicImportVars({
warnOnError: true,
errorWhenNoFilesFound: true
})
]
};Utility function that converts dynamic import AST nodes to glob patterns for file matching.
/**
* Converts a dynamic import AST node to a glob pattern
* @param node - AST node representing the import expression source
* @param sourceString - String representation of the import expression
* @returns Glob pattern string or null if not a variable dynamic import
* @throws VariableDynamicImportError for invalid import patterns
*/
function dynamicImportToGlob(node: BaseNode, sourceString: string): string | null;
interface BaseNode {
type: string;
start?: number;
end?: number;
[key: string]: any;
}Usage Examples:
import { dynamicImportToGlob } from '@rollup/plugin-dynamic-import-vars';
// Example usage in custom AST processing
const glob = dynamicImportToGlob(node, "`./locales/${locale}.js`");
// Returns: './locales/*.js'
// Handles various patterns:
// `./locales/${locale}.js` -> './locales/*.js'
// `./${folder}/${name}.js` -> './*/*.js'
// `./modules-${name}/index.js` -> './modules-*/index.js'
// './locales/' + locale + '.js' -> './locales/*.js'
// './locales/'.concat(locale, '.js') -> './locales/*.js'Custom error class for invalid dynamic import patterns that cannot be statically analyzed.
/**
* Error thrown when dynamic imports cannot be statically analyzed
* @extends Error
*/
class VariableDynamicImportError extends Error {
constructor(message: string);
}Common error scenarios:
// These patterns throw VariableDynamicImportError:
// Must start with ./ or ../
import(bar); // Invalid: bare import
import(`${bar}.js`); // Invalid: starts with variable
import(`/foo/${bar}.js`); // Invalid: absolute path
// Must end with file extension
import(`./foo/${bar}`); // Invalid: no extension
// Own directory imports need specific patterns
import(`./${foo}.js`); // Invalid: too generic
import(`./module-${foo}.js`); // Valid: specific pattern
// Cannot contain wildcards
import(`./foo/*${bar}.js`); // Invalid: contains asteriskThe plugin supports several dynamic import patterns:
import(`./locales/${locale}.js`); // -> './locales/*.js'
import(`./modules/${type}/${name}.js`); // -> './modules/*/*.js'
import(`./components-${variant}.js`); // -> './components-*.js'
import(`./modules-${name}/index.js`); // -> './modules-*/index.js'import('./locales/' + locale + '.js'); // -> './locales/*.js'
import('./src/' + folder + '/' + file); // -> './src/*/*.js'
import('./locales/' + locale + foo + bar + '.js'); // -> './locales/*.js'
import('./locales/' + `${locale}.js`); // -> './locales/*.js'
import('./locales/' + `${foo + bar}.js`); // -> './locales/*.js'import('./locales/'.concat(locale, '.js')); // -> './locales/*.js'
import('./base/'.concat(type, '/').concat(name, '.js')); // -> './base/*/*.js'
import('./'.concat(folder, '/').concat(name, '.js')); // -> './*/*.js'Import assertions are fully preserved in the transformed code, allowing integration with other plugins:
// Input - dynamic import with CSS assertion
import(`./styles/${sheet}.css`, { assert: { type: 'css' } });
// Output - each case preserves the assertion
function __variableDynamicImportRuntime0__(path) {
switch (path) {
case './styles/dark.css': return import('./styles/dark.css', { assert: { type: 'css' } });
case './styles/light.css': return import('./styles/light.css', { assert: { type: 'css' } });
default: return new Promise(function(resolve, reject) {
(typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)(
reject.bind(null, new Error("Unknown variable dynamic import: " + path))
);
});
}
}This preservation enables compatibility with plugins like rollup-plugin-import-css that rely on import assertions to determine how to process different file types.
The plugin enforces several rules to ensure safe static analysis:
./ or ../*, limited to one level deep per directory (e.g., import(\./foo/${x}${y}/${z}.js`)becomes./foo//.js, not ./foo/**/*.js`)* charactersThe plugin provides flexible error handling through configuration options:
warnOnError: false (default): Stops build on invalid patternswarnOnError: true: Issues warnings but continues build, leaving code unchangederrorWhenNoFilesFound: false (default): Continues when no files match globerrorWhenNoFilesFound: true: Throws error when glob matches no filesWhen warnOnError is true and errorWhenNoFilesFound is true, the plugin will warn instead of error for missing files.
interface RollupDynamicImportVariablesOptions {
include?: FilterPattern;
exclude?: FilterPattern;
errorWhenNoFilesFound?: boolean;
warnOnError?: boolean;
}
type FilterPattern = string | RegExp | Array<string | RegExp> | null;
class VariableDynamicImportError extends Error {}
function dynamicImportToGlob(node: BaseNode, sourceString: string): string | null;
interface BaseNode {
type: string;
start?: number;
end?: number;
[key: string]: any;
}
interface Plugin {
name: string;
transform(code: string, id: string): TransformResult | null;
}
interface TransformResult {
code: string;
map: SourceMap;
}
interface SourceMap {
file: string;
includeContent: boolean;
hires: boolean;
}