Rollup plugin to mimic Webpack's script-loader inline behavior. Inlines raw scripts into the bundle in import order, or emits them as a separate asset file for non-module, non-strict mode execution.
Using npm:
npm install --save-dev @rollup-extras/plugin-script-loader
In legacy codebases using libraries like d3 or AngularJS, you need to:
- Load scripts in a stable, deterministic order (e.g., d3 before d3 plugins, AngularJS before Angular modules)
- Concatenate everything into a single bundle
- Pass all code through the optimization layer (terser) — not hidden inside
eval()strings - Support legacy libraries that aren't proper ES modules
- Optionally, run legacy code in sloppy mode (non-strict) outside the main ES module bundle
In Webpack, this is solved with script-loader:
import "script-loader!d3";
import "script-loader!d3-plugin1";
import "script-loader!angular";
import "script-loader!angular-route";This plugin brings the same pattern to Rollup.
The plugin intercepts imports that start with a configurable prefix (script! by default), resolves the underlying module, and processes it based on the emit option:
emit: 'inline'(default) — Inlines the raw source code directly into the bundle at the import siteemit: 'asset'— Collects all scripts and emits them as a separate concatenated asset file
Because the code is handled as real JavaScript (not wrapped in eval() or new Function()), it:
- Preserves import order — scripts appear exactly as imported
- Is fully visible to terser and other optimization plugins
- Gets concatenated into a single output
- Is CSP-safe — no dynamic code evaluation
import scriptLoader from "@rollup-extras/plugin-script-loader";
export default {
input: "src/index.js",
output: {
format: "iife",
file: "dist/bundle.js",
},
plugins: [scriptLoader()],
};In your entry point:
// Legacy libraries — inlined in this exact order
import "script!d3";
import "script!d3-tip";
import "script!angular";
import "script!angular-route";
import "script!angular-sanitize";
// Your application code (regular ES module imports)
import { app } from "./app.js";
app.init();For legacy code that must run in true sloppy (non-strict) mode, use emit: 'asset' to emit a separate classic script file:
import scriptLoader from "@rollup-extras/plugin-script-loader";
export default {
input: "src/index.js",
output: {
format: "es",
dir: "dist",
sourcemap: true,
},
plugins: [
scriptLoader({
emit: "asset",
name: "vendor.js",
sourcemap: true,
}),
],
};This emits vendor.js as a separate asset file containing all concatenated scripts. Include it in your HTML before the main bundle:
<script src="vendor.js"></script>
<script src="main.js" type="module"></script>To let Rollup apply its assetFileNames pattern for content hashing:
scriptLoader({
emit: "asset",
name: "vendor.js",
exactFileName: false, // Use Rollup's assetFileNames pattern
});With output.assetFileNames: 'assets/[name].[hash].[ext]', this outputs assets/vendor.abc123.js.
Provide a custom minify function to minify the concatenated output:
import { minify } from "terser";
scriptLoader({
emit: "asset",
name: "vendor.js",
minify: async (code, map) => {
const result = await minify(code, {
sourceMap: map ? { content: map } : false,
});
return { code: result.code, map: result.map };
},
});Or with oxc-minify:
import { minify } from "oxc-minify";
scriptLoader({
emit: "asset",
minify: async (code) => {
const result = await minify("vendor.js", code);
return { code: result.code, map: result.map };
},
});Use with @rollup-extras/plugin-html to automatically inject the vendor bundle:
import scriptLoader from "@rollup-extras/plugin-script-loader";
import html from "@rollup-extras/plugin-html";
import {
simpleES5Script,
combineAssetFactories,
} from "@rollup-extras/plugin-html/asset-factories";
export default {
input: "src/main.js",
output: {
format: "es",
dir: "dist",
sourcemap: true,
assetFileNames: "assets/[name].[hash].[ext]",
},
plugins: [
scriptLoader({
emit: "asset",
name: "vendor.js",
exactFileName: false,
sourcemap: true,
}),
html({
template: "index.html",
assetsFactory: combineAssetFactories(
simpleES5Script(/vendor\..*\.js$/) // Inject vendor as classic script
),
}),
],
};type ScriptLoaderPluginOptions = {
// Existing options
prefix?: string; // 'script!' by default
useStrict?: boolean; // true by default (for inline mode)
pluginName?: string; // '@rollup-extras/plugin-script-loader' by default
verbose?: boolean; // false by default
// Emit mode options
emit?: "inline" | "asset"; // 'inline' by default
name?: string; // 'vendor.js' by default
exactFileName?: boolean; // true by default
sourcemap?: boolean; // true by default when emit: 'asset'
minify?: (
code: string,
sourcemap?: SourceMap
) => Promise<{ code: string; map?: SourceMap }>;
};'inline'(default) — Scripts are inlined into the main bundle (current behavior)'asset'— Scripts are concatenated and emitted as a separate asset file
Base name for the emitted asset file. Only used when emit: 'asset'.
Default: 'vendor.js'
Controls how the asset filename is determined. Only used when emit: 'asset'.
true(default) — Uses Rollup'sfileNameproperty; asset emitted with exact name (e.g.,vendor.js)false— Uses Rollup'snameproperty; Rollup appliesoutput.assetFileNamespattern (e.g.,assets/vendor.abc123.js)
Whether to generate sourcemaps for the emitted asset. Only used when emit: 'asset'.
Default: true when emit: 'asset'
When enabled, generates a concatenated sourcemap (vendor.js.map) pointing back to original source files.
Optional async function to minify the concatenated code. Only used when emit: 'asset'.
Receives the code and optionally the sourcemap, returns minified code and optionally a new sourcemap.
Controls whether the inlined script is parsed in strict mode or sloppy (non-strict) mode. Only applies to emit: 'inline' mode.
true(default) — appendsexport {}to the loaded code, which makes Rollup's parser treat the file as an ES module (strict mode)false— does not appendexport {}, so the parser uses script (sloppy) mode
// Sloppy mode for legacy libraries that break under strict parsing
scriptLoader({ useStrict: false });Note:
useStrictonly controls parse-time mode. For the output to actually run in non-strict mode at runtime, you also needoutput.strict: falsein your Rollup config (applies toiife,cjs, andumdformats). Theesformat is always strict per spec. For true sloppy mode at runtime, useemit: 'asset'instead.
The import prefix that triggers script loading.
Default: 'script!'
For Webpack migration compatibility:
scriptLoader({ prefix: "script-loader!" });Enables detailed logging of processed scripts.
Default: false
With format: 'iife', all inlined code shares the same function scope. Libraries that explicitly assign to window (e.g., window.d3 = ..., window.angular = ...) work correctly.
With emit: 'asset', the emitted file runs as a classic script in true global scope — ideal for legacy libraries.
The plugin delegates module resolution to other plugins via Rollup's this.resolve(). This means script!d3 will be resolved by @rollup/plugin-node-resolve (if present) just like a regular import — picking up the file from node_modules.
Since the code is handled as real JavaScript, @rollup/plugin-terser can process inline mode output. For asset mode, use the minify option to apply minification to the concatenated output.
The plugin properly clears its state between builds, fully supporting Rollup's watch mode.
- script-loader (Webpack, deprecated)