天津特产[:{原来}rollup《这么简朴》之 tree shaking【篇】

admin 6个月前 (04-07) 科技 51 0

(人人好),“我「‘是’」小雨小”雨,致力于分享有趣「的」、适(用)「的」技术文章。
内容分{为}翻译‘“和”’原创,{若}「‘是’」有 问题[,迎接随时谈论或私信,<希望‘“和”’>人人一起提高。
(分享不)易,〖希望能够获得人人〗「的」支‘持‘“和”’’关注。

〖设〗『计』

rollup系列『计』划一章一章「的」放【出】,内容更精简更专一更易 于明白[

《现在『计』划分》{为}以下几章:

  • rollup.rollup
  • rollup.generate + rollup.write
  • rollup.watch
  • tree shaking <==== 「当前」文章
  • plugins

TL;DR

es node: 种种语法 块[「的」(类),《‘好’比》if 块[,「箭头函」数 块[,『函』数挪(用) 块[【等】【等】

rollup()(阶段),{剖析源「码」},〖‘ 天生[’〗ast tree,对ast tree〖上「的」〗每个节点举行遍历,判断【出】「‘是’」否include,「‘是’」「的」话符号, 然后[〖‘ 天生[’〗chunks,最后导【出】。
generate()(『或者』)write()(阶段)(凭据)rollup()(阶段)做「的」符号,(举行)代「码」网络,最后〖‘ 天生[’〗真正(用)到「的」代「码」,“这就「‘是’」”tree shaking<「的」基本原理>。

《一句话》就「‘是’」,(凭据)side effects(「的」界)说,设定es node「的」include (与) 差别「的」es node〖‘ 天生[’〗差别「的」渲染(引入到magic string实例)【“函”数】,有magic string〖举〗行网络,『最后写入』。

本文[没有详细剖析各es node渲染< 『方』式[>‘“和”’include【设定「的」详细实现】,不外有 问题[迎接讨论,拍砖~

‘注重点’

!!!版本 => 笔者阅读「的」rollup<版本{为}>: 1.32.0

!!!【提醒】 => 标有TODO({为}详细)实现细节, 会视情形剖[析。

!!!注重 => 每一个子题[《目》都「‘是’」父题《目》(【“函”数】)(内部实现)

!!!〖强调〗 => rollup“「中」”模 块[((文件))「的」id就「‘是’」(文件)地址,‘以「‘是’」(类)似’resolveID这种就「‘是’」剖析(文件)地址(「的」意思),‘我们’可以返回我们想返回「的」(文件)id(也就「‘是’」地址,『相对路径』、决议路径)来让rollup<加载>

rollup“「‘是’」一”个焦点,只做最基础「的」事情,《‘好’比》提供默认模 块[((文件))<加载>机制, 《‘好’比》打包成差别气概「的」内容,我们「的」插件“「中」”提供‘<“了”>’<加载>(文件)路径,剖析(文件)内容(「处置」ts,sass【等】)【【等】操作】,「‘是’」一种插拔式「的」〖设〗『计』,‘“和”’webpack(类)似
〖插拔式「‘是’」〗一种异常天真且可历久迭代更新「的」〖设〗『计』,《这也「‘是’」一个“「中」”大》型框架(「的」焦点),人多力量大嘛~

〖主要通(用)模 块[以及寄〗义

  1. Graph: ‘全局唯’一「的」图,【包《罗》入口以及种种依赖】「「的」相互关系」,操作< 『方』式[>,缓存【等】。「‘是’」rollup(「的」焦点)
  2. PathTracker: (引(用))(挪(用))(追)踪器
  3. PluginDriver: <插件驱>动器,挪(用)插件‘“和”’提供插件环《境上下文【等】》
  4. FileEmitter: {资源操作器}
  5. GlobalScope: 「全局作(用)局」,相对「的」另有局部「的」
  6. ModuleLoader: 模 块[<加载>器
  7. NodeBase: ast各语法(ArrayExpression、AwaitExpression【等】)「的」组织基(类)

《流程》剖析

这次就不全《流程》剖析‘<“了”>’, 咱们举个最简朴「的」[例子剖析一下,更有助 于明白[。

《‘好’比》我们有这么一段简朴「的」代「码」:

function test() {
    var name = 'test';
    console.log(123);
}
const name = '《 「测试测试」[》';
function fn() {
    console.log(name);
}

fn();

〖如你所见〗,打包「的」效果应该「‘是’」不包《罗》test【“函”数】「的」,像下面这样:

'use strict';

const name = '《 「测试测试」[》';
function fn() {
    console.log(name);
}

fn();

{那}rollup「‘是’」怎么「处置」这段代「码」「的」《呢》?

  • (模 块[剖)析

还得回到‘<“了”>’rollup()《流程》,(凭据)例子,《我们》可以把对importexportre-export【等】相关「的」都干掉,〖暂时不需要关〗注,领会最基本「的」《流程》后会(水到渠成)「的」。
关于插件,《我也不会(「使(用)」)任何》插件,『只(「使(用)」)』rollup内置「的」默认插件。

〖对于〗这个例子来说,首先会(凭据)剖析(文件)地址,获取(文件)真正「的」路径:

function createResolveId(preserveSymlinks: boolean) {
	return function(source: string, importer: string) {
		if (importer !== undefined && !isAbsolute(source) && source[0] !== '.') return null;

		// 最终挪(用)path.resolve,({将}正当路)径片断转{为}绝对路径
		return addJsExtensionIfNecessary(
			resolve(importer ? dirname(importer) : resolve(), source),
			preserveSymlinks
		);
	};
}

然后[建立rollup模 块[,设置缓存【等】:

const module: Module = new Module(
    this.graph,
    id,
    moduleSideEffects,
    syntheticNamedExports,
    isEntry
);

‘之后’【【通过】】内置「的」load钩子获取(文件)内容,〖固然咱也可以自界说〗该‘行{为}’:

// 第二个参数「‘是’」 『传给』load钩子【“函”数】「的」 参数,内部(「使(用)」)「的」apply
return Promise.resolve(this.pluginDriver.hookFirst('load', [id]))

(之后)经由transform(transform{这里}可以明白{为}webpack【「的」种种】loader,「处置」差别《(类)型》(文件)「的」)「处置」〖‘ 天生[’〗一段标准化「的」结构:

const source = { ast: undefined,
    code:
     'function test() {\n    var name = \'test\';\n    console.log(123);\n}\nconst name = \'《 「测试测试」[》\';\nfunction fn() {\n    console.log(name);\n}\n\nfn();\n',
    customTransformCache: false,
    moduleSideEffects: null,
    originalCode:
     'function test() {\n    var name = \'test\';\n    console.log(123);\n}\nconst name = \'《 「测试测试」[》\';\nfunction fn() {\n    console.log(name);\n}\n\nfn();\n',
    originalSourcemap: null,
    sourcemapChain: [],
    syntheticNamedExports: null,
    transformDependencies: [] 
}

“ 然后[就”到‘<“了”>’对照要害「的」一步,{将}source剖析并设置到「当前」module上:

// 〖‘ 天生[’〗 es tree ast
this.esTreeAst = ast || tryParse(this, this.graph.acornParser, this.graph.acornOptions);

// 挪(用) magic string, 超利便操作〖字符串〗「的」工[具库
this.magicString = new MagicString(code, options).

// 《搞一个》ast<上下文环境>,“包装一”些< 『方』式[>,《‘好’比》动态<导入>、导【出】【等】【等】吧,之后会{将}剖析到「的」模 块[<或内容填充到「当前」>module“「中」”,bind「的」this(指向「当前」)module
this.astContext = {
    addDynamicImport: this.addDynamicImport.bind(this), // 动态<导入>
    addExport: this.addExport.bind(this), // 导【出】
    addImport: this.addImport.bind(this), // <导入>
    addImportMeta: this.addImportMeta.bind(this), // importmeta
    annotations: (this.graph.treeshakingOptions && this.graph.treeshakingOptions.annotations)!,
    code, // Only needed for debugging
    deoptimizationTracker: this.graph.deoptimizationTracker,
    error: this.error.bind(this),
    fileName, // Needed for warnings
    getExports: this.getExports.bind(this),
    getModuleExecIndex: () => this.execIndex,
    getModuleName: this.basename.bind(this),
    getReexports: this.getReexports.bind(this),
    importDescriptions: this.importDescriptions,
    includeDynamicImport: this.includeDynamicImport.bind(this),
    includeVariable: this.includeVariable.bind(this),
    isCrossChunkImport: importDescription =>
        (importDescription.module as Module).chunk !== this.chunk,
    magicString: this.magicString,
    module: this,
    moduleContext: this.context,
    nodeConstructors,
    preserveModules: this.graph.preserveModules,
    propertyReadSideEffects: (!this.graph.treeshakingOptions ||
        this.graph.treeshakingOptions.propertyReadSideEffects)!,
    traceExport: this.getVariableForExportName.bind(this),
    traceVariable: this.traceVariable.bind(this),
    treeshake: !!this.graph.treeshakingOptions,
    tryCatchDeoptimization: (!this.graph.treeshakingOptions ||
        this.graph.treeshakingOptions.tryCatchDeoptimization)!,
    unknownGlobalSideEffects: (!this.graph.treeshakingOptions ||
        this.graph.treeshakingOptions.unknownGlobalSideEffects)!,
    usesTopLevelAwait: false,
    warn: this.warn.bind(this),
    warnDeprecation: this.graph.warnDeprecation.bind(this.graph)
};

// 《实例化》Program,{将}效果赋给「当前」模 块[「的」ast{『属性』上以供后续使}(用)
// !!! 注重实例“「中」”有一个included『属性』,(用)于「‘是’」否打包到最终输【出】(文件)“「中」”,也就「‘是’」tree shaking。 默以{为}[false。!!!
this.ast = new Program(
    this.esTreeAst,
    { type: 'Module', context: this.astContext }, // astContext里包《罗》‘<“了”>’「当前」module‘“和”’「当前」module<「的」相关信息>,(「使(用)」)bind「绑定「当前」上」下文
    this.scope
);
// Program内部会{将}种种差别《(类)型》「的」 estree node type 「的」实例添加到实例上,以供后续遍历(「使(用)」)
// 差别「的」node type{继续同}一个NodeBase“父(类)”,《‘好’比》箭头【“函”数】表达式(ArrayExpression(类)),详见[nodes《目》录](https://github.com/FoxDaxian/rollup-analysis/tree/master/src/ast/nodes)
function parseNode(esTreeNode: GenericEsTreeNode) {
		// 就「‘是’」遍历, 然后[new nodeType, 然后[挂载到实例上
    for (const key of Object.keys(esTreeNode)) {
        // That way, we can override this function to add custom initialisation and then call super.parseNode
        // this 指向 Program组织(类),【【通过】】new建立「的」
        // 若「‘是’」program上有「的」话,{那}么跳过
        if (this.hasOwnProperty(key)) continue;
        // ast tree上「的」每一个『属性』
        const value = esTreeNode[key];
        // 不【等】于工具(『或者』)null(『或者』)key「‘是’」annotations
        // annotations「‘是’」type
        if (typeof value !== 'object' || value === null || key === 'annotations') {
            (this as GenericEsTreeNode)[key] = value;
        } else if (Array.isArray(value)) {
            // 若「‘是’」「‘是’」数组,{那}么建立数组并遍历上‘去’
            (this as GenericEsTreeNode)[key] = [];
            // this.context.nodeConstructors 针对差别「的」语法书《(类)型》,举行差别「的」操作,《‘好’比》挂载依赖【等】【等】
            for (const child of value) {
                // 循环 然后[种种new 种种《(类)型》「的」node,都「‘是’」继成「的」NodeBase
                (this as GenericEsTreeNode)[key].push(
                    child === null
                        ? null
                        : new (this.context.nodeConstructors[child.type] ||
                                this.context.nodeConstructors.UnknownNode)(child, this, this.scope) // 「处置」种种ast《(类)型》
                );
            }
        } else {
            // 以上都不「‘是’」「的」情形下,『直接』new
            (this as GenericEsTreeNode)[key] = new (this.context.nodeConstructors[value.type] ||
                this.context.nodeConstructors.UnknownNode)(value, this, this.scope);
        }
    }
}

后面「处置」相关依赖模 块[,『直接』跳过咯~

“实现一个简易版”Webpack

return this.fetchAllDependencies(module).then();

到现在{为}止,我们{将}(文件)转换成‘<“了”>’模 块[,并剖析【出】 es tree node 以及其内部包《罗》「的」各种型「的」语法树

  • (「使(用)」)PathTracker追踪上下文关系
for (const module of this.modules) {
    // 每个一个节点自己「的」实现,不「‘是’」全都有
    module.bindReferences();
}

《‘好’比》我们有箭头【“函”数】,由于没有this指向以「‘是’」默认设置UNKONW

// ArrayExpression(类),继续(与)NodeBase
bind() {
    super.bind();
    for (const element of this.elements) {
        if (element !== null) element.deoptimizePath(UNKNOWN_PATH);
    }
}

{若}「‘是’」有外包裹【“函”数】,「就会加深一层」path,最后会(凭据)层级关系,举行代「码」「的」wrap

  • 符号模 块[「‘是’」否可shaking

其“「中」”焦点{为}(凭据)isExecuted「的」状态举行模 块[以及es tree node「的」引入,“再次”之前我们要知道includeMarked< 『方』式[>「‘是’」获取入口之后挪(用)「的」。
也就「‘是’」所有「的」入口模 块[((用)户界说「的」、(动态引入)、入口(文件)依赖、入口(文件)依赖「的」依赖..)〖都市〗module.isExecuted{为}true
之后才会挪(用)下面「的」includeMarked< 『方』式[>,“这时候”module.isExecuted已经{为}true,{即}可挪(用)include< 『方』式[>

function includeMarked(modules: Module[]) {
    // {若}「‘是’」有treeshaking不{为}空
    if (this.treeshakingOptions) {
        // 《第一个》tree shaking
        let treeshakingPass = 1;
        do {
            timeStart(`treeshaking pass ${treeshakingPass}`, 3);
            this.needsTreeshakingPass = false;
            for (const module of modules) {
                // 给ast node“符号上”include
                if (module.isExecuted) module.include();
            }
            timeEnd(`treeshaking pass ${treeshakingPass++}`, 3);
        } while (this.needsTreeshakingPass);
    } else {
        // Necessary to properly replace namespace imports
        for (const module of modules) module.includeAllInBundle();
    }
}

// 《上面》module.include()「的」实现。
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) {
    // {将}固然程序 块[「的」included设{为}true,再‘去’遍历「当前」程序 块[“「中」”「的」所有es node,(凭据)差别条件举行include「的」设定
    this.included = true;
    for (const node of this.body) {
        if (includeChildrenRecursively || node.shouldBeIncluded(context)) {
            node.include(context, includeChildrenRecursively);
        }
    }
}

module.include内部就涉及到es tree node‘<“了”>’,由于NodeBase{初始}include{为}false,以「‘是’」另有第二个判断条件:「当前」node「‘是’」否有副作(用)side effects。
这个「‘是’」否有副作(用)「‘是’」继续(与)NodeBase「的」各种node子(类)自身「的」实现。<现在就我看来>,副作(用)也「‘是’」有自身「的」协议划定「的」,《‘好’比》修改‘<“了”>’全局变量这(类)就算「‘是’」副作(用),固然也有些「‘是’」一定无副作(用)「的」,《‘好’比》export语句,rollup“「中」”就写死{为}false‘<“了”>’。
rollup内部差别《(类)型》「的」es node 实现‘<“了”>’差别「的」hasEffects实现,可自身观摩学习。‘可以【【通过】】该篇文章’,「简朴」领会一些 side effects。

  • chunks「的」〖‘ 天生[’〗

后面就「‘是’」【【通过】】模 块[,〖‘ 天生[’〗chunks,固然其“「中」”还包《罗》多chunk,【少】chunks【等】设置选项「的」区别,<这里不>再赘述,有兴趣「的」同伙可以参考本系列第一篇文章(『或者』)『直接』查看带注释「的」源「码」

  • 【【通过】】chunks〖‘ 天生[’〗代「码」(〖字符串〗)

挪(用)rollup< 『方』式[>后,『会返回』一个工具,其“「中」”包《罗》‘<“了”>’代「码」〖‘ 天生[’〗‘“和”’《写》入操作「的」write< 『方』式[>(已‘去’掉一些warn【等】):

return {
    write: ((rawOutputOptions: GenericConfigObject) => {
        const { outputOptions, outputPluginDriver } = getOutputOptionsAndPluginDriver(
            rawOutputOptions
        );
		// 这里「‘是’」要害
        return generate(outputOptions, true, outputPluginDriver).then(async bundle => {
            await Promise.all(
                Object.keys(bundle).map(chunkId =>
                    writeOutputFile(result, bundle[chunkId], outputOptions, outputPluginDriver) // => 《写》入操作
                )
            );
            // 修改〖‘ 天生[’〗后「的」代「码」
            await outputPluginDriver.hookParallel('writeBundle', [bundle]);
            // 现在看来「‘是’」供之后缓存(用),《提高构建速率》
            return createOutput(bundle);
        });
    }) as any
}

generate「‘是’」就相对简朴些‘<“了”>’,就「‘是’」一些钩子‘“和”’< 『方』式[>「的」挪(用),《‘好’比》:

preRender< 『方』式[>{将}es node渲染{为}〖字符串〗,挪(用)「的」「‘是’」各es node自身实现「的」render< 『方』式[>,{详细参考代「码」哈}。「规则许多」,这里不赘述,我也没细看~~

{那}哪些需要渲染,哪些不需要渲染《呢》?

没错,就(用)到‘<“了”>’之前界说「的」include『字段』,做‘<“了”>’一个简朴「的」判断,《‘好’比》:

// label node(类)“「中」”
function render(code: MagicString, options: RenderOptions) {
    // 诺~
    if (this.label.included) {
        this.label.render(code, options);
    } else {
        code.remove(
            this.start,
            findFirstOccurrenceOutsideComment(code.original, ':', this.label.end) + 1
        );
    }
    this.body.render(code, options);
}

<之后添加到>chunks“「中」”,这样chunks“「中」”不仅有ast,另有〖‘ 天生[’〗后「的」可执行代「码」。

之后(凭据)format『字段』获取差别「的」wrapper,对代「码」〖字符串〗举行「处置」, 然后[传递给renderChunk< 『方』式[>,该< 『方』式[>主要{为}‘<“了”>’挪(用)renderChunktransformChunktransformBundle三个钩子【“函”数】,对效果举行进一步「处置」。不外由于我剖析「的」版本不「‘是’」最新「的」,以「‘是’」会(与)「当前」2.x有收支, 改动详见[changlog

对‘<“了”>’,另有sourceMap,这个能力「‘是’」magic string提供「的」,可自行查阅

这样我们就获得‘<“了”>’最终想要「的」效果:

chunks.map(chunk => {
    // 【【通过】】id获取之前设置到outputBundleWithPlaceholders上「的」一些『属性』
    const outputChunk = outputBundleWithPlaceholders[chunk.id!] as OutputChunk;
    return chunk
        .render(outputOptions, addons, outputChunk, outputPluginDriver)
        .then(rendered => {
            // (引(用))《(类)型》,outputBundleWithPlaceholders上「的」也变化‘<“了”>’,以「‘是’」outputBundle也变化‘<“了”>’,『最后返回』outputBundle
            // 在这里给outputBundle挂载上‘<“了”>’code‘“和”’map,后面『直接』返回 outputBundle ‘<“了”>’
            outputChunk.code = rendered.code;
            outputChunk.map = rendered.map;

            // 挪(用)〖‘ 天生[’〗「的」钩子【“函”数】
            return outputPluginDriver.hookParallel('ongenerate', [
                { bundle: outputChunk, ...outputOptions },
                outputChunk
            ]);
        });
})

《上面》【“函”数】「处置」「的」「‘是’」(引(用))《(类)型》,以「‘是’」最后可以『直接』返回效果。 不在赘[述。

  • (文件)写入

这部门没啥好说「的」,人人自己看下下面「的」代「码」吧。其“「中」”writeFile< 『方』式[>挪(用)「的」node fs模 块[提供「的」能力。

function writeOutputFile(
	build: RollupBuild,
	outputFile: OutputAsset | OutputChunk,
	outputOptions: OutputOptions,
	outputPluginDriver: PluginDriver
): Promise<void> {
	const fileName = resolve(outputOptions.dir || dirname(outputOptions.file!), outputFile.fileName);
	let writeSourceMapPromise: Promise<void>;
	let source: string | Buffer;
	if (outputFile.type === 'asset') {
		source = outputFile.source;
	} else {
		source = outputFile.code;
		if (outputOptions.sourcemap && outputFile.map) {
			let url: string;
			if (outputOptions.sourcemap === 'inline') {
				url = outputFile.map.toUrl();
			} else {
				url = `${basename(outputFile.fileName)}.map`;
				writeSourceMapPromise = writeFile(`${fileName}.map`, outputFile.map.toString());
			}
			if (outputOptions.sourcemap !== 'hidden') {
				source += `//# ${SOURCEMAPPING_URL}=${url}\n`;
			}
		}
	}

	return writeFile(fileName, source)
		.then(() => writeSourceMapPromise)
		.then(
			(): any =>
				outputFile.type === 'chunk' &&
				outputPluginDriver.hookSeq('onwrite', [
					{
						bundle: build,
						...outputOptions
					},
					outputFile
				])
		)
		.then(() => {});
}
  • 其他
    〖对于〗importexportre-export这(类)ast node,rollup会剖析〖‘ 天生[’〗「的」ast tree,获取其“「中」”「的」value,也就「‘是’」模 块[名,组合有(用)「的」信息备(用)。 然后[就‘“和”’上述《流程》(类)似‘<“了”>’。

推荐(「使(用)」)ast explorer剖析一段code, 然后[看看内里「的」结构,领会后会更容易明白。

function addImport(node: ImportDeclaration) {
    // 《‘好’比》引入‘<“了”>’path模 块[
    // source: {
    // 	type: 'Literal',
    // 	start: 'xx',
    // 	end: 'xx',
    // 	value: 'path',
    // 	raw: '"path"'
    // }
    const source = node.source.value;
    this.sources.add(source);
    for (const specifier of node.specifiers) {
        const localName = specifier.local.name;

        // 重复引入‘<“了”>’
        if (this.importDescriptions[localName]) {
            return this.error(
                {
                    code: 'DUPLICATE_IMPORT',
                    message: `Duplicated import '${localName}'`
                },
                specifier.start
            );
        }

        const isDefault = specifier.type === NodeType.ImportDefaultSpecifier;
        const isNamespace = specifier.type === NodeType.ImportNamespaceSpecifier;

        const name = isDefault
            ? 'default'
            : isNamespace
            ? '*'
            : (specifier as ImportSpecifier).imported.name;

        // <导入>「的」模 块[「的」相关形貌
        this.importDescriptions[localName] = {
            module: null as any, // filled in later
            name,
            source,
            start: specifier.start
        };
    }
}

【总结】

感受这次写「的」欠好,看下来可能会以{为}只「‘是’」符号(与)网络「的」这么一个历程,然则其内部细节「‘是’」异常复杂「的」。【以至】于你需要深入领会side effects(「的」界)说(与)影响。『日后也许会专门整』理一下。

rollup系列也快靠近尾声‘<“了”>’,“虽然”一直在自嗨,然则也蛮爽「的」。

<学>习使我快乐,《哈哈》~~

,

Sunbet 『<【申博】>』

Sunbet 『<【申博】>』www.baodingxsls.com Sunbet「‘是’」菲律宾娱乐「的」官方网『站』。Sunbt官网有你喜欢「的」Sunbet、『<【申博】>』APP(下载)、《菲律宾娱乐》最新网址、‘菲律宾’娱乐管理网最新网址【等】。

Allbet声明:该文看法仅代表作者自己,与本平台无关。转载请注明: 天津特产[:{原来}rollup《这么简朴》之 tree shaking【篇】

网友评论

  • (*)

最新评论

文章归档

站点信息

  • 文章总数:439
  • 页面总数:0
  • 分类总数:8
  • 标签总数:900
  • 评论总数:123
  • 浏览总数:3178