两只小蚂蚁

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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多文件输入流到多文件输出流。

posted on 2021-12-27 18:33  两只小蚂蚁  阅读(137)  评论(0编辑  收藏  举报