Rollup
Version: "2.56.2"
从入口开始:
//cli/cli.ts
import run from './run/index';
if (command.help || (process.argv.length <= 2 && process.stdin.isTTY)) {
...
} else if (command.version) {
console.log(`rollup v${version}`);
} else {
...
run(command);
}
//cli/run/index.ts
export default async function runRollup(command: Record<string, any>): Promise<void> {
//忽略环境,命令检查等
...
try {
const { options, warnings } = await getConfigs(command);
try {
for (const inputOptions of options) {
await build(inputOptions, warnings, command.silent);
}
...
} catch (err) {...}
}
}
Build阶段
//cli/run/build.ts
export default async function build(
inputOptions: MergedRollupOptions,
warnings: BatchWarnings,
silent = false
): Promise<unknown> {
...
//构建
const bundle = await rollup.rollup(inputOptions as any);
if (useStdout) {
...
//bundle输出
const { output: outputs } = await bundle.generate(output);
}
...
await Promise.all(outputOptions.map(bundle.write));
await bundle.close();
}
//src/rollup/rollup.ts
export default function rollup(rawInputOptions: GenericConfigObject): Promise<RollupBuild> {
return rollupInternal(rawInputOptions, null);
}
export async function rollupInternal(
rawInputOptions: GenericConfigObject,
watcher: RollupWatcher | null
): Promise<RollupBuild> {
...
const graph = new Graph(inputOptions, watcher);
try {
await graph.pluginDriver.hookParallel('buildStart', [inputOptions]);
//构建Module依赖图
await graph.build();
//忽略hook的调用代码
} catch (err) {...}
}
//src/Graph.ts
async build(): Promise<void> {
await this.generateModuleGraph();
this.sortModules();
this.includeStatements();
}
private async generateModuleGraph(): Promise<void> {
({ entryModules: this.entryModules, implicitEntryModules: this.implicitEntryModules } =
await this.moduleLoader.addEntryModules(normalizeEntryModules(this.options.input), true));
...
}
Module编译
终于到了核心的ModuleLoader代码
//src/ModuleLoader.ts
async addEntryModules(
unresolvedEntryModules: UnresolvedModule[],
isUserDefined: boolean
){
const newEntryModules = await this.extendLoadModulesPromise(
//加载所有入口的Module
Promise.all(
unresolvedEntryModules.map(
({ id, importer }): Promise<Module> =>
//对每一个Module进行处理
this.loadEntryModule(id, true, importer, null)
)
).then(entryModules => {
...
}
)
await this.awaitLoadModulesPromise();
return {
entryModules: this.indexedEntryModules.map(({ module }) => module),
implicitEntryModules: [...this.implicitEntryModules],
newEntryModules
};
}
//加载单个Module
private async loadEntryModule(
unresolvedId: string,
isEntry: boolean,
importer: string | undefined,
implicitlyLoadedBefore: string | null
): Promise<Module> {
...
return this.fetchModule(
this.addDefaultsToResolvedId(
typeof resolveIdResult === 'object'
? (resolveIdResult as NormalizedResolveIdWithoutDefaults)
: { id: resolveIdResult }
)!,
undefined,
isEntry
);
}
private async fetchModule(
{ id, meta, moduleSideEffects, syntheticNamedExports }: ResolvedId,
importer: string | undefined,
isEntry: boolean
): Promise<Module> {
//实例化Module
const module: Module = new Module(
this.graph,
id,
this.options,
isEntry,
moduleSideEffects,
syntheticNamedExports,
meta
);
this.modulesById.set(id, module);
this.graph.watchFiles[id] = true;
//AST处理逻辑
await this.addModuleSource(id, importer, module);
await this.pluginDriver.hookParallel('moduleParsed', [module.info]);
await Promise.all([
this.fetchStaticDependencies(module),
this.fetchDynamicDependencies(module)
]);
module.linkImports();
return module;
}
private async addModuleSource(id: string, importer: string | undefined, module: Module) {
const cachedModule = this.graph.cachedModules.get(id);
if (
cachedModule &&
!cachedModule.customTransformCache &&
cachedModule.originalCode === sourceDescription.code
) {
...
//拿到AST
module.setSource(cachedModule);
} else {
module.updateOptions(sourceDescription);
//使用plugin转换
module.setSource(
await transform(sourceDescription, module, this.pluginDriver, this.options.onwarn)
);
}
}
//src/Module.ts
setSource({...}){
if (!ast) {
ast = this.tryParse();
}
this.magicString = new MagicString(code, {
filename: (this.excludeFromSourcemap ? null : fileName)!,
indentExclusionRanges: []
});
...
}
tryParse(): acorn.Node {
try {
return this.graph.contextParse(this.info.code!);
} catch (err) {...}
}
在AST生成完成后调用pluginDriver中给定的plugin对AST进行转换,比如rollup-plugin-babel。
AST生成
//src/Graph.ts
contextParse(code: string, options: Partial<acorn.Options> = {}): acorn.Node {
...
//这里就是调用acorn库进行AST生成的地方了
const ast = this.acornParser.parse(code, {
...(this.options.acorn as unknown as acorn.Options),
...options
});
...
addAnnotations(comments, ast, code);
return ast;
}
总结
Rollup的核心是Module的依赖图生成到AST转换,并通过使用插件来进行转换达到编译的目的。一般来说是通过单入口文件到依赖树并做TreeSharking,最后输出单文件。
Gulp
Version: "4.0.2"
先看看Gulp的定义
var util = require('util');
var Undertaker = require('undertaker');
var vfs = require('vinyl-fs');
var watch = require('glob-watcher');
function Gulp() {
Undertaker.call(this);
// Bind the functions for destructuring
this.series = this.series.bind(this);
this.parallel = this.parallel.bind(this);
this.registry = this.registry.bind(this);
...
}
//继承了Undertaker
util.inherits(Gulp, Undertaker);
//输入和输出方法
Gulp.prototype.src = vfs.src;
Gulp.prototype.dest = vfs.dest;
Gulp.prototype.symlink = vfs.symlink;
Gulp.prototype.watch = ...
// Let people use this class from our instance
Gulp.prototype.Gulp = Gulp;
var inst = new Gulp();
module.exports = inst;
Undertaker
undertaker是什么?先看看官网描述:
Task registry that allows composition through series/parallel methods.
下面是官网的使用示例:
var fs = require('fs');
var Undertaker = require('undertaker');
var taker = new Undertaker();
taker.task('task1', function(cb){
// do things
cb(); // when everything is done
});
taker.task('task2', function(){
return fs.createReadStream('./myFile.js')
.pipe(fs.createWriteStream('./myFile.copy.js'));
});
taker.task('task3', function(){
return new Promise(function(resolve, reject){
// do things
resolve(); // when everything is done
});
});
taker.task('combined', taker.series('task1', 'task2'));
taker.task('all', taker.parallel('combined', 'task3'));
所以他主要用来进行串行/并行控制器。
vinyl-fs
vinyl-fs是对vinyl库的一层包装,vinyl又是什么?
Vinyl is a very simple metadata object that describes a file.
//vinyl/index.js
...
function File(file) {
...
}
...
File.prototype.isBuffer = function
File.prototype.isStream = function
File.prototype.isNull = function
File.prototype.isDirectory = function
File.prototype.isSymbolic = function
File.prototype.clone = function
File.prototype.inspect = function
...
// Virtual attributes Or stuff with extra logic
Object.defineProperty(File.prototype, 'contents', {
get: function,
set: function,
});
Object.defineProperty(File.prototype, 'path', {
get: function,
set: function,
});
...
module.exports = File;
Vinyl导出一个File对象,提供了对底层stream和Path等文件操作的封装。
总结
Gulp核心是支持Glob的stream文件流和合成任务控制。并通过使用插件来进行流转换达到编译的目的。通常以Glob多文件输入流到多文件输出流。