demo
import { name, age } from "./msg.js";
let msg = 123;
console.log(name);
export const name = "name";
export const age = "age";
import babel from "@rollup/plugin-babel";
export default {
input: "./main.js",
output: {
file: "dist/bundle.js",
format: "es",
},
plugins: [
babel({
babelHelpers: "bundled",
exclude: "node_modules/**",
presets: ["@babel/preset-env"],
}),
],
};
"build": "rollup -c"

1. 从入口出发
- rollup -c 命令
"bin": {
"rollup": "dist/bin/rollup"
}
"main": "dist/rollup.js",
- bin/rollup
const rollup = require('../shared/rollup.js')
const command = yargsParser(process.argv.slice(2))
runRollup(command);
async function runRollup(command) {
const { options } = await getConfigs(command);
for (const inputOptions of options) {
await build(inputOptions, warnings, command.silent);
}
}
async function build(inputOptions) {
const bundle = await rollup.rollup(inputOptions);
await Promise.all(outputOptions.map(bundle.write));
}
2.rollup函数
function rollup(rawInputOptions) {
return rollupInternal(rawInputOptions, null);
}
function rollupInternal(rawInputOptions, watcher) {
const { options: inputOptions, unsetOptions: unsetInputOptions } = await getInputOptions(rawInputOptions, watcher !== null);
const graph = new Graph(inputOptions, watcher);
await graph.pluginDriver.hookParallel('buildStart', [inputOptions]);
await graph.build();
await graph.pluginDriver.hookParallel('buildEnd', []);
const result = {
async close() {},
async generate() {},
watchFiles: Object.keys(graph.watchFiles),
async write(rawOutputOptions) {
return handleGenerateWrite(true, inputOptions, unsetInputOptions, rawOutputOptions, graph);
}
}
}
2.1.getInputOptions
function getInputOptions(rawInputOptions, watchMode) {
const rawPlugins = ensureArray(rawInputOptions.plugins);
const { options, unsetOptions } = normalizeInputOptions(
await rawPlugins.reduce(
applyOptionHook(watchMode),
Promise.resolve(rawInputOptions)
)
);
normalizePlugins(options.plugins);
return { options, unsetOptions };
}
2.2 Graph
class Graph {
constructor(options, watcher) {
this.modulesById = {}
this.modules =[]
this.options = options
this.deoptimizationTracker = new PathTracker();
this.cachedModules = new Map();
if(options.cache !== false) {}
if(watcher) {}
this.pluginDriver = new PluginDriver()
this.scope = new GlobalScope();
this.acornParser = acorn.Parser.extend(...options.acornInjectPlugins);
this.moduleLoader = new ModuleLoader();
}
}
2.3 PluginDriver
class PluginDriver {
constructor(graph, options, userPlugins, pluginCache, basePluginDriver) {
this.basePluginDriver = basePluginDriver;
this.pluginContexts.set()
}
hookFirst(){}
hookParallel(){}
runHook(){}
}
2.4 GlobalScope
class GlobalScope extends Scope {}
class Scope{
addDeclaration() {}
}
2.5 ModuleLoader
class ModuleLoader {
constructor(graph, modulesById, options, pluginDriver) {
this.modulesById = modulesById;
}
async addEntryModules(unresolvedEntryModules, isUserDefined) {
}
}
3. build
function build() {
await this.generateModuleGraph();
this.includeStatements();
}
3.1 generateModuleGraph
async generateModuleGraph() {
await this.moduleLoader.addEntryModules(normalizeEntryModules(this.options.input))
}
function normalizeEntryModules(entryModules) {
return Object.entries(entryModules).map(([name, id]) => ({
fileName: null,
id,
implicitlyLoadedAfter: [],
importer: undefined,
name,
}));
}
3.2 addEntryModules
function addEntryModules(unresolvedEntryModules) {
unresolvedEntryModules.map(({ id, importer }) =>
this.loadEntryModule(id, true, importer, null)
)
}
3.3 loadEntryModule
function loadEntryModule() {
const resolveIdResult = await resolveId();
return this.fetchModule(this.addDefaultsToResolvedId(resolveIdResult), undefined);
}
function addDefaultsToResolvedId(resolvedId) {
return {
id: resolvedId.id
}
}
3.4 fetchModule
function fetchModule({ id }, importer, isEntry) {
const module = new Module()
this.modulesById.set(id, module);
this.graph.watchFiles[id] = true;
await this.addModuleSource(id, importer, module);
await this.pluginDriver.hookParallel("moduleParsed", [module.info]);
await this.fetchStaticDependencies(module),
await this.fetchDynamicDependencies(module),
module.linkImports();
return module
}
3.4.1 Module
class Module {
constructor(graph, id, options, isEntry) {
this.context = options.moduleContext(id);
this.info = {}
}
}
3.4.2 addModuleSource
function addModuleSource(id, importer, module) {
let source =
(await this.pluginDriver.hookFirst("load", [id])) ?? (await readFile(id))
const sourceDescription = { code: source };
module.setSource(
await transform(
sourceDescription,
module,
this.pluginDriver,
this.options.onwarn
)
)
}
3.4.3 transform
function transform() {
return pluginDriver.hookReduceArg0('transform').then(code => {
return {ast, code}
})
}
3.4.4 setSource
import { nodeConstructors } from "./ast/nodes/index";
function setSource(ast, code) {
if (!ast) ast = this.graph.contextParse(this.info.code);
this.magicString = new MagicString(code, {});
this.astContext = {
addExport: this.addExport.bind(this),
addImport: this.addImport.bind(this),
nodeConstructors,
};
this.scope = new ModuleScope(this.graph.scope, this.astContext);
this.ast = new Program(ast, { context: this.astContext, type: 'Module' }, this.scope)
this.info.ast = ast
}
function addImport() {
const source = node.source.value;
this.sources.add(source);
}
3.4.5 nodeConstructors
import ImportDeclaration from "./ImportDeclaration";
import Program from "./Program";
import Identifier from "./Identifier";
export const nodeConstructors = {
ImportDeclaration,
Program,
Identifier,
};
3.4.6 Program
class Program extends NodeBase {
render(code, options) {
if (this.boy.length) {
renderStatementList(this.body, code, this.start, this.end, options);
} else {
super.render(code, options);
}
}
}
3.4.7 NodeBase
class NodeBase extends ExpressionEntity {
constructor(esTreeNode, parent, parentScope) {
this.createScope(parentScope);
this.parseNode(esTreeNode);
this.initialise();
}
parseNode(esTreeNode) {
for (const [key, value] of Object.entries(esTreeNode)) {
if (Array.isArray(value)) {
for (const child of value) {
this[key].push(
child === null
? null
:
new this.context.nodeConstructors[child.type]()
);
}
}
}
}
}
class ImportDeclaration extends NodeBase {
initialise() {
this.context.addImport(this);
}
}
3.4.8 fetchStaticDependencies
function fetchStaticDependencies() {
Array.from(module.sources, async (source) =>
this.fetchResolvedDependency(source, module.id, module.resolvedIds)
)
}
3.5 result
{
modules: [mainModule, msgModule],
modulesById: {main: mainModule, msg: msgModule},
moduleLoader:ModuleLoader,
pluginDriver:PluginDriver,
scope:GlobalScope: {children: [moduleScope, moduleScope]}
}
{
graph,
id,
name,
filename,
imports: [],
exports: [],
scope,
ast,
info
}
4. bundle.write
async write(rawOutputOptions) {
return handleGenerateWrite(true, inputOptions, unsetInputOptions, rawOutputOptions, graph);
}
async function handleGenerateWrite() {
const { options: outputOptions, outputPluginDriver } =
getOutputOptionsAndPluginDriver();
const bundle = new Bundle(outputOptions, outputPluginDriver, graph);
const generated = await bundle.generate();
await Promise.all(
Object.values(generated).map((chunk) =>
writeOutputFile(chunk, outputOptions)
)
);
return createOutput(generated);
}
4.1 getOutputOptionsAndPluginDriver
function getOutputOptionsAndPluginDriver() {
const outputPluginDriver = inputPluginDriver.createOutputPluginDriver(rawPlugins);
return {
...getOutputOptions(),
outputPluginDriver
}
}
4.2
class Bundle {
constructor(outputOptions, unsetOptions, inputOptions, pluginDriver, graph) {
this.outputOptions = outputOptions;
this.unsetOptions = unsetOptions;
this.inputOptions = inputOptions;
this.pluginDriver = pluginDriver;
this.graph = graph;
}
}
4.3 bundle.generate
function generate() {
const outputBundle = Object.create(null);
await this.pluginDriver.hookParallel('renderStart', [this.outputOptions, this.inputOptions]);
const chunks = await this.generateChunks()
const inputBase = commondir(getAbsoluteEntryModulePaths(chunks));
const addons = await createAddons(this.outputOptions, this.pluginDriver);
this.prerenderChunks(chunks, inputBase);
await this.addFinalizedChunksToBundle(chunks, inputBase, addons, outputBundle);
this.finaliseAssets(outputBundle);
return outputBundle
}
4.3.1 generateChunks
function generateChunks() {
const chunks = []
const chunkByModule = new Map()
const chunkDefinitions = getChunkAssignments(this.graph.entryModules)
for(const { alias, modules } of chunkDefinitions) {
const chunk = new Chunk(modules, this.inputOptions)
chunks.push(chunk)
}
return [...chunks]
}
function addManualChunks(manualChunks) {
addModuleToManualChunk(alias, entry, manualChunkAliasByEntry);
}
function getChunkAssignments() {
for(const entry of entryModules) {
assignEntryToStaticDependencies(entry, null);
}
chunkDefinitions.push(...createChunks([...entryModules, ...dynamicEntryModules]));
return chunkDefinitions;
}
4.3.2 prerenderChunks
function prerenderChunks() {
for (const chunk of chunks) {
chunk.generateExports();
}
for (const chunk of chunks) {
chunk.preRender(this.outputOptions, inputBase);
}
}
4.3.3 addFinalizedChunksToBundle
function addFinalizedChunksToBundle() {
for(const chunk of chunks) {
outputBundle[chunk.id] = chunk.getChunkInfoWithFileNames();
}
await Promise.all(chunks.map(async (chunk) => {
const outputChunk = outputBundle[chunk.id];
Object.assign(outputChunk, await chunk.render(this.outputOptions, addons, outputChunk));
}))
}
4.3.4 finaliseAssets
function finaliseAssets(outputBundle) {
for(const file of Object.values(outputBundle)) {
if (this.outputOptions.validate && typeof file.code == 'string') {
this.graph.contextParse(file.code)
}
}
this.pluginDriver.finaliseAssets();
}
5. chunk
class Chunk {
constructor(orderedModules, modulesById) {
this.orderedModules = orderedModules
this.modulesById = modulesById;
this.imports = new Set();
const chunkModules = new Set(orderedModules);
for(const module of orderedModules) {
}
}
}
5.1 preRender
function preRender(options, inputBase) {
const magicString = new MagicStringBundle()
const renderOptions = {}
for(const module of this.orderedModules) {
const source = module.render(renderOptions)
this.renderedModuleSources.set(module, source);
magicString.addSource(source);
const { renderedExports, removedExports } = module.getRenderedExports();
const { renderedModuleSources } = this;
renderedModules[module.id] = {
get code() {
var _a, _b;
return (_b =
(_a = renderedModuleSources.get(module)) === null || _a === void 0
? void 0
: _a.toString()) !== null && _b !== void 0
? _b
: null;
},
originalLength: module.originalCode.length,
removedExports,
renderedExports,
};
}
}
5.2 render
function render(options, addons, outputChunk) {
const format = options.format;
const finalise = finalisers[format];
const magicString = finalize(this.renderedSource, {})
const prevCode = magicString.toString();
let code = await renderChunk({code: prevCode})
if (options.sourcemap) {}
return {code, map}
}
5.3 renderChunk
function renderChunk({code, options, renderChunk}) {
return outputPluginDriver.hookReduceArg0('renderChunk', [code, renderChunk, options], renderChunkReducer);
}
5.4 es
function es(magicString) {
const importBlock = getImportBlock(dependencies, _)
const exportBlock = getExportBlock(exports, _, varOrConst)
return magicString.trim();
}
6. module
for (const module of this.orderedModules) {
const source = module.render(renderOptions).trim();
}
function render() {
const magicString = this.magicString.clone();
this.ast.render(magicString, options)
return magicString;
}
function render(code, options) {
renderStatementsList(this.body,code)
}
6.1 Program
function render(code, options) {
renderStatementList(this.body, code, this.start, this.end, options);
}
6.2 renderStatementList
function renderStatementList() {
for (let nextIndex = 1; nextIndex <= statements.length; nextIndex++) {
if(currentNode.included) {
currentNode.render(code, options, {})
} else {
treeshakeNode(currentNode, code)
}
}
}
6.2.1 ExportNamedDeclaration
function render(code, options) {
code.remove(this.start, this.declaration.start);
this.declaration.render(code, options, { end, start });
}
6.2.2 VariableDeclaration
function render() {
for (const declarator of this.declarations) {
declarator.render(code, options);
}
}
6.2.3 VariableDeclarator
function render(code, options) {
this.id.render(code, options);
this.init.render(code, options)
}
6.2.4 ExpressionStatement
function render(code, options) {
super.render(code, options)
this.insertSemicolon(code);
}
6.2.5 NodeBase
function render() {
for (const key of this.keys) {
value.render(code, options)
}
}
6.2.6 CallExpression
function render() {
this.callee.render(code, options, {})
for (const arg of this.arguments) {
arg.render(code, options);
}
}
6.2.7 MemberExpression
function render() {
this.object.render();
this.property.render(code, options);
}
6.2.8 Identifier
function render() {
const name = this.variable.getName();
}
6.3 treeshakeNode
function treeshakeNode(node, code, start, end) {
code.remove(start, end)
}
6.4 include
function setSource({ast, code}) {
this.astContext = {}
this.scope = new ModuleScope()
this.ast = new Program(ast, { context: this.astContext, type: 'Module' }, this.scope)
}
await this.fetchStaticDependencies()
this.includeStatements()
6.4.1 includeStatements
function includeStatements() {
module.includeAllExports(false);
for (const module of this.modules) {
module.include()
}
}
function include() {
const context = createInclusionContext();
if(this.ast.shouldBeIncluded(context)) {
this.ast.include(context, false)
}
}
function include(context, includeChildrenRecursively) {
this.included = true;
for (const node of this.body) {
if(includeChildrenRecursively || node.shouldBeIncluded(context)) {
node.include(context)
}
}
}
function shouldBeIncluded(context) {
return this.included || (!context.brokenFlow && this.hasEffects(createHasEffectsContext()));
}
function include() {
this.included = true;
this.callee.include(context, false);
this.callee.includeCallArguments(context, this.arguments);
}
function include() {
this.included = true;
this.object.include(context, includeChildrenRecursively);
this.property.include(context, includeChildrenRecursively);
}
function include() {
this.included = true;
this.context.includeVariableInModule(this.variable);
}
function includeVariableInModule() {
this.includeVariable(variable);
this.imports.add(variable);
}
function includeVariable() {
variable.include();
}
function include() {
this.include = true
for (const declarator of this.declarations) {
if (includeChildrenRecursively || declarator.shouldBeIncluded(context))
declarator.include(context, includeChildrenRecursively);
}
}
6.4.2 shouldBeIncluded
function shouldBeIncluded(context) {
return this.included || (!context.brokenFlow && this.hasEffects())
}
function include() {
const context = createInclusionContext();
}
function createInclusionContext() {
return {
brokenFlow: BROKEN_FLOW_NONE,
}
}
function hasEffects() {
for (const node of this.body) {
if (node.hasEffects(context)) {
return (this.hasCachedEffect = true);
}
}
return false;
}
function hasEffects(context) {
return this.declaration !== null && this.declaration.hasEffects(context);
}
function hasEffects(context) {
child.hasEffects(context)
}
function hasEffects(context) {
return this.id.hasEffects(context) || (this.init !== null && this.init.hasEffects(context));
}
function hasEffects() {
return this.variable instanceof GlobalVariable && this.variable.hasEffectsWhenAccessedAtPath(EMPTY_PATH)
}
function hasEffects() return false
function hasEffects() {
child.hasEffects()
}
function hasEffects() {
argument.hasEffects(context)
this.callee.hasEffects(context)
}
function include() {
this.included = true;
for(const declaration of this.declarations) {
declaration.include(createInclusionContext(), false);
}
}