Dynamic Library Loading
Dynamic library loading functionality for BetterForms - Load external JavaScript libraries, ESM modules, and CSS files from CDN dynamically within function actions.
Version: 3.3.1+
Feature: BF.libraryLoadOnce(), BF.libraryIsLoaded(), BF.libraryGetLoaded()
Overview
Load external JavaScript libraries, ESM modules, and CSS files from CDN dynamically within your function actions. Libraries are loaded once and cached automatically (idempotent), with built-in XSS protection and race condition prevention.
Why Use This?
Reduce initial page load - Only load libraries when needed
Self-contained components - Components can load their own dependencies
Version flexibility - Use different library versions per form
No rebuild required - Add new libraries without webpack rebuild
Quick Start
Load a UMD Library
// Load PapaParse from CDN
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/papaparse.min.js');
// Use it immediately
const result = Papa.parse('Name,Age\nJohn,30', { header: true });
console.log(result.data); // [{Name: "John", Age: "30"}]Load an ESM Module
// Load DayJS as ES module
const dayjs = await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/+esm', {
moduleType: 'module'
});
// Use module exports
console.log('Today:', dayjs.default().format('YYYY-MM-DD'));
console.log('Next week:', dayjs.default().add(7, 'day').format('MMMM D'));Load CSS
// Load stylesheet
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/animate.min.css', {
type: 'stylesheet'
});
// Styles are now availableAPI Reference
BF.libraryLoadOnce(url, options)
BF.libraryLoadOnce(url, options)Load a library from CDN. Returns immediately if already loaded (idempotent).
Parameters:
url
String
required
CDN URL to JavaScript, ESM module, or CSS file
options
Object
{}
Optional configuration
options.type
String
'script'
'script' or 'stylesheet'
options.moduleType
String
'classic'
'classic' for UMD or 'module' for ESM
options.timeout
Number
30000
Timeout in milliseconds
options.attributes
Object
{}
Additional HTML attributes (e.g., integrity, crossorigin)
Returns:
UMD/Script: Promise resolving to
{ url, type, element, cached }ESM Module: Promise resolving to the module object
Throws: Error if network failure, timeout, or invalid parameters
BF.libraryIsLoaded(url)
BF.libraryIsLoaded(url)Check if a library is already loaded.
if (BF.libraryIsLoaded('https://cdn.example.com/lib.js')) {
console.log('Already loaded!');
}Returns: Boolean
BF.libraryGetLoaded()
BF.libraryGetLoaded()Get array of all loaded library URLs.
const loaded = BF.libraryGetLoaded();
console.log('Loaded libraries:', loaded);Returns: Array of URL strings
Usage Examples
Example 1: Chart.js
// Load Chart.js
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.js');
// Create a chart
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['January', 'February', 'March'],
datasets: [{
label: 'Sales',
data: [12, 19, 3],
backgroundColor: 'rgba(54, 162, 235, 0.5)'
}]
}
});Example 2: DayJS (ESM)
// Load DayJS as ESM module
const dayjs = await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/+esm', {
moduleType: 'module'
});
// Format dates
console.log('Today:', dayjs.default().format('YYYY-MM-DD'));
console.log('Next week:', dayjs.default().add(7, 'day').format('MMMM D, YYYY'));Example 3: Axios for API Calls
// Load Axios
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js');
// Make API call
const response = await axios.get('https://api.github.com/users/octocat');
console.log('User:', response.data.name);
console.log('Followers:', response.data.followers);Example 4: Load with Security (SRI)
// Load with Subresource Integrity
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js', {
attributes: {
integrity: 'sha384-example-hash-here',
crossorigin: 'anonymous'
}
});Example 5: Error Handling
try {
await BF.libraryLoadOnce('https://cdn.example.com/library.js', {
timeout: 5000 // 5 second timeout
});
console.log('Library loaded successfully!');
} catch (error) {
console.error('Failed to load library:', error.message);
BF.errorThrow(10500, 'Library load failed', error.message);
}Example 6: Multiple Libraries
// Load multiple libraries in parallel
await Promise.all([
BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/papaparse.min.js'),
BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/qrcode.min.js'),
BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.js')
]);
console.log('All libraries loaded!');Example 7: Dynamic Library Selection
// Load different library based on user preference
const chartLibrary = model.chartType === 'advanced'
? 'https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js'
: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.js';
await BF.libraryLoadOnce(chartLibrary);ESM Module Scope
Important: ESM modules are scoped to the function action where they're loaded. Unlike UMD libraries (which are automatically global), ESM modules return an object that only exists in the current function's scope.
UMD vs ESM Behavior
UMD Libraries (automatically global):
// Action 1
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/papaparse.min.js');
Papa.parse(csv); // ✅ Works - Papa is global
// Action 2 (later)
Papa.parse(csv); // ✅ Still works - Papa is on windowESM Modules (function-scoped):
// Action 1
const dayjs = await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/+esm', {
moduleType: 'module'
});
dayjs.default().format('YYYY-MM-DD'); // ✅ Works
// Action 2 (later)
dayjs.default().format('YYYY-MM-DD'); // ❌ Error: dayjs is not definedSolution 1: Re-load in Each Action (Recommended)
The library is cached, so re-loading is < 1ms:
// Action 1
const dayjs = await BF.libraryLoadOnce('url', { moduleType: 'module' });
dayjs.default().format('YYYY-MM-DD');
// Action 2 (later - different function action)
const dayjs = await BF.libraryLoadOnce('url', { moduleType: 'module' });
dayjs.default().format('YYYY-MM-DD'); // ✅ Works - returns cached module in < 1msWhen to use:
✅ Most common case
✅ Clean, simple code
✅ Nearly instant (cached)
Solution 2: Bind to Window (If Needed Globally)
Manually store the module on window for global access:
// Action 1 (first time setup)
if (!window._myDayjs) {
window._myDayjs = await BF.libraryLoadOnce('url', { moduleType: 'module' });
}
window._myDayjs.default().format('YYYY-MM-DD');
// Action 2 (later)
window._myDayjs.default().format('YYYY-MM-DD'); // ✅ Works - uses cached referenceWhen to use:
Used across many actions
Need consistent reference
Don't mind global namespace
Do You Even Need It Again?
In many cases, you won't need the module after initial setup:
// Load chart library, create chart, done
const Chart = await BF.libraryLoadOnce('chart.js', { moduleType: 'module' });
new Chart.default(ctx, config); // Chart is created
// Module reference no longer needed after this actionOnly need multi-action access if:
Calling library functions repeatedly
Building complex multi-step workflows
Library maintains state you need to access
Best Practices
✅ DO:
Use version pinning: Specify exact versions in CDN URLs
// Good - pinned version 'https://cdn.jsdelivr.net/npm/[email protected]/papaparse.min.js' // Avoid - unpinned, can break 'https://cdn.jsdelivr.net/npm/papaparse/papaparse.min.js' // latest, unstableIdempotent by design: No need to check before loading
// Just load it - automatically idempotent await BF.libraryLoadOnce(url); // If already loaded, returns immediately (< 1ms)Use error handling: Always catch potential failures
try { await BF.libraryLoadOnce(url); } catch (error) { // Handle gracefully }Prefer UMD builds: More compatible, simpler to use
// UMD (preferred) await BF.libraryLoadOnce('lib.umd.js'); window.LibraryName.doSomething(); // ESM (if needed) const lib = await BF.libraryLoadOnce('lib.esm.js', { moduleType: 'module' }); lib.default.doSomething();
❌ DON'T:
Don't load untrusted CDNs: Security risk
Don't load without error handling: Can break your form
Don't use latest/unstable versions: Use pinned versions
Don't load duplicate libraries: Check first with
BF.libraryIsLoaded()
Supported Library Formats
✅ UMD (Universal Module Definition)
Most common format
Exposes global variable
Works everywhere
Examples: Chart.js, PapaParse, QRCode, Axios
await BF.libraryLoadOnce('https://cdn.example.com/lib.umd.js');
window.LibraryName.method();✅ ESM (ES Modules)
Modern format
Uses
import/exportReturns module object
Examples: DayJS, D3.js, Lit (web components)
const lib = await BF.libraryLoadOnce('https://cdn.example.com/lib.esm.js', {
moduleType: 'module'
});
lib.default.method();✅ CSS Stylesheets
Any CSS file
Animations, themes, frameworks
Examples: Animate.css, Font Awesome
await BF.libraryLoadOnce('https://cdn.example.com/styles.css', {
type: 'stylesheet'
});Finding CDN URLs
Finding the correct CDN URL can be tricky. Here's how:
Step 1: Find the Package on npm
Go to
https://www.npmjs.com/package/package-nameCheck for version number and main file info
Step 2: Browse the CDN
jsDelivr (Recommended)
Browse:
https://cdn.jsdelivr.net/npm/package-name@version/Example:
https://cdn.jsdelivr.net/npm/[email protected]/Look in:
dist/,build/, or root folderESM shortcut: Add
/+esmto auto-find ESM entry
Common file patterns:
package.min.js- Minified UMD (most common)package.umd.js- UMD builddist/package.js- Main distribution filebuild/package.min.js- Built file
unpkg
Browse:
https://unpkg.com/package@version/?meta(add?meta)Example:
https://unpkg.com/[email protected]/?meta
cdnjs
Search:
https://cdnjs.com/libraries/package-nameExample:
https://cdnjs.com/libraries/Chart.js
Step 3: Verify the File Works
Test in browser console first:
// Test UMD
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/package@version/file.js');
console.log(window.PackageName); // Should exist
// Test ESM
const pkg = await BF.libraryLoadOnce('url/+esm', { moduleType: 'module' });
console.log(pkg); // Should show module exportsTroubleshooting 404 Errors
Check version exists: Verify on npmjs.com
Try alternate paths:
package.min.jsdist/package.min.jsbuild/package.min.jsindex.js
Check package.json:
https://cdn.jsdelivr.net/npm/package@version/package.jsonLook for
"main","browser", or"module"fields
Real Examples
Chart.js:
Browse:
https://cdn.jsdelivr.net/npm/[email protected]/Find:
dist/chart.umd.jsURL:
https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.js
PapaParse:
Browse:
https://cdn.jsdelivr.net/npm/[email protected]/Find:
papaparse.min.js(at root)URL:
https://cdn.jsdelivr.net/npm/[email protected]/papaparse.min.js
DayJS ESM:
Use shortcut:
https://cdn.jsdelivr.net/npm/[email protected]/+esmDone! (jsDelivr finds ESM automatically)
Common Libraries
Chart.js (Charting)
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.js');PapaParse (CSV Parser)
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/papaparse.min.js');QRCode (QR Generator)
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/qrcode.min.js');Axios (HTTP Client)
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js');DayJS (ESM - Date Library)
const dayjs = await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/+esm', {
moduleType: 'module'
});Sortable.js (Drag & Drop)
await BF.libraryLoadOnce('https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js');Troubleshooting
Library Not Available After Loading
Problem: Library loads but window.LibraryName is undefined
Solution: You might have loaded an ESM module as UMD
// Try as ESM instead
const lib = await BF.libraryLoadOnce(url, { moduleType: 'module' });
console.log(lib); // Check what's in the moduleTimeout Error
Problem: Library takes too long to load
Solution: Increase timeout or check network
await BF.libraryLoadOnce(url, { timeout: 60000 }); // 60 secondsCORS Error
Problem: "blocked by CORS policy"
Solution: Use a reputable CDN that supports CORS (jsDelivr, unpkg, cdnjs)
Version Conflicts
Problem: Library already loaded but different version
Solution: Check what's loaded first
const loaded = BF.libraryGetLoaded();
console.log('Already loaded:', loaded);Need Help?
Check the BetterForms documentation
Visit the BetterForms community forum
Review usage examples above
Test in browser console first before using in function actions
Happy coding! 🚀
Last updated