Babel plugin for loadable components that enables server-side rendering by transforming dynamic imports
npx @tessl/cli install tessl/npm-loadable--babel-plugin@5.16.0@loadable/babel-plugin is a Babel plugin that transforms loadable component function calls to enable server-side rendering (SSR) support in React applications. The plugin analyzes dynamic import() statements within loadable() calls and injects additional properties required for SSR, including chunk names, synchronous loading methods, and module resolution functions.
npm install --save-dev @loadable/babel-plugin// Babel plugin configuration (in babel.config.js or .babelrc)
module.exports = {
plugins: ['@loadable/babel-plugin']
};For custom import signatures:
module.exports = {
plugins: [
['@loadable/babel-plugin', {
signatures: [
{ name: 'default', from: '@loadable/component' },
{ name: 'loadable', from: 'my-custom-loadable' }
]
}]
]
};The plugin transforms loadable component calls automatically during build time:
// Input code
import loadable from '@loadable/component';
const AsyncComponent = loadable(() => import('./MyComponent'));
// Plugin transforms this to include SSR properties
const AsyncComponent = loadable({
chunkName(props) { return 'MyComponent'; },
isReady(props) { /* ... */ },
importAsync: () => import('./MyComponent'),
requireAsync(props) { /* ... */ },
requireSync(props) { /* ... */ },
resolve(props) { /* ... */ },
resolved: {}
});The plugin operates during Babel's AST transformation phase and consists of:
Configure which import patterns the plugin should transform.
/**
* Main Babel plugin export - configured in Babel configuration
* @param api - Babel API object containing types and other utilities
* @param options - Plugin configuration options
* @returns Babel plugin object with visitor methods
*/
function loadablePlugin(api: object, options: LoadablePluginOptions): BabelPlugin;
interface LoadablePluginOptions {
/** Array of import signature patterns to transform */
signatures?: ImportSignature[];
}
interface ImportSignature {
/** Import specifier name ('default' for default imports, or named import) */
name: string;
/** Package name to match for transformation */
from: string;
}
interface BabelPlugin {
/** Inherits syntax-dynamic-import plugin for import() support */
inherits: object;
/** AST visitor configuration */
visitor: {
Program: {
enter(programPath: object): void;
};
};
}Default Configuration:
// Default signatures - transforms @loadable/component imports
const DEFAULT_SIGNATURE = [{ name: 'default', from: '@loadable/component' }];The plugin identifies and transforms these patterns:
// 1. loadable() function calls (based on import signatures)
loadable(() => import('./Component'))
// 2. lazy() function calls (when imported from @loadable/component)
import { lazy } from '@loadable/component';
lazy(() => import('./Component'))
// 3. loadable.lib() method calls
loadable.lib(() => import('./library'))
// 4. Functions with #__LOADABLE__ comment - supports multiple function types:
/* #__LOADABLE__ */ () => import('./Component') // Arrow function
/* #__LOADABLE__ */ function() { return import('./Component') } // Function expression
const obj = { /* #__LOADABLE__ */ load() { return import('./Component') } } // Object methodWhen the plugin transforms a loadable call, it injects these SSR-support methods:
interface LoadableComponentObject {
/**
* Generates webpack chunk name for the dynamic import
* Handles webpack comments and template literals
*/
chunkName(props: any): string;
/**
* Checks if the module is ready (loaded) for synchronous rendering
* Returns true if module is available in webpack module cache
*/
isReady(props: any): boolean;
/**
* Original async import function - preserves the dynamic import()
*/
importAsync: () => Promise<any>;
/**
* Async module loading with resolution tracking
* Updates resolved state when promise completes
*/
requireAsync(props: any): Promise<any>;
/**
* Synchronous module loading for SSR
* Uses __webpack_require__ or Node.js require based on environment
*/
requireSync(props: any): any;
/**
* Resolves module ID for the dynamic import
* Uses require.resolveWeak or require.resolve based on availability
*/
resolve(props: any): string;
/**
* Object tracking resolved module states
* Maps module IDs to boolean resolution status
*/
resolved: Record<string, boolean>;
}The plugin handles webpack-specific features for chunk naming and module resolution:
// Webpack comment processing for chunk names
import(
/* webpackChunkName: "my-chunk" */
'./Component'
);
// Template literal support for dynamic chunk names
const componentName = 'MyComponent';
import(`./components/${componentName}`);Chunk Name Generation Rules:
The plugin validates usage patterns and throws errors for unsupported configurations:
// Error: Multiple import calls not supported
loadable(() => {
import('./ComponentA');
import('./ComponentB'); // Throws error
});Common Error Messages:
"loadable: multiple import calls inside loadable() function are not supported"The plugin generates environment-aware code that works in different contexts:
// Browser environment (webpack)
if (typeof __webpack_require__ !== 'undefined') {
return __webpack_require__(id);
}
// Node.js environment
return eval('module.require')(id);
// Webpack modules check
if (typeof __webpack_modules__ !== 'undefined') {
return !!(__webpack_modules__[key]);
}// Plugin accepts these configuration options
interface LoadablePluginOptions {
signatures?: ImportSignature[];
}
interface ImportSignature {
name: string; // 'default' | string (named import)
from: string; // Package name like '@loadable/component'
}
// Internal transformation context
interface TransformContext {
path: object; // Babel AST path to the loadable call
callPath: object; // Babel AST path to the import() call
funcPath: object; // Babel AST path to the function expression
}
// Generated object structure
interface GeneratedLoadableObject {
chunkName(props: any): string;
isReady(props: any): boolean;
importAsync: () => Promise<any>;
requireAsync(props: any): Promise<any>;
requireSync(props: any): any;
resolve(props: any): string | number;
resolved: Record<string, boolean>;
}