Build the column series directory entry

Zuo Lin, Front end development engineer of Wedoctor Front End Technology Department. Super ~ can eat, like swimming, fitness and dancing, love life and technology.

Rollup Plugin rollup Plugin rollup Plugin rollup Plugin

Although the introduction of rollup plug-in on the official website is almost in English, which is not very friendly to learn and there are relatively few examples, there are quite a few articles about the analysis and development guide of rollup plug-in at present, mainly about the translation of official English documents and the analysis of functional hooks.

To be fair, looking at source code analysis in confusion will only dissuade me from writing a rollup plugin in a minute

Why does rollup need a Plugin

Rollup -c Packaging process

In rollup’s packaging process, an entry file and a module are created into a simple bundle through a relative path. As you build more complex bundles, you often need more flexibility — importing modules installed by NPM, compiling code through Babel, working with JSON files, and so on. The implementation process packaged by rollup -C can be understood by referring to the flowchart below.

To do this, plugins can change the behavior of Rollup during the critical packaging process.

This is similar to webPack plugins, except that WebPack distinguishes between loader and plugin, while the rollup plugin can play the role of both loader and traditional plugin.

Understand the rollup plugin

To quote the official website:

The Rollup plug-in is an object with one or more properties described below, build hooks, and output generated hooks that follow our convention. A plug-in should be distributed as a package that exports a function that can be called with plugin-specific options and returns such an object. Plug-ins allow you to customize Rollup’s behavior, for example, by compiling code before bundling, or by finding third-party modules in your node_modules folder.

Simply put, the rollup plug-in is a normal function that returns an object containing properties (such as name) and hook functions for different phases (build and output phases). The flow chart above should be reviewed here.

About the contract

  • The plug-in should have an explicit name prefixed with rollup-plugin–.
  • Include the rollup-plugin keyword in package.json.
  • Mocha or AVA libraries that support Promises out of the box are recommended.
  • Use asynchronous methods whenever possible.
  • Record your plugin in English.
  • Make sure your plugin outputs the correct sourcemap.
  • If your plugin uses ‘virtual modules’ (such as helper functions), add the module name\ 0Prefix. This prevents other plug-ins from executing it.

Write a rollup plugin in a minute

In order to maintain the enthusiasm and power of learning, first give an example of pressure, if you see the plug-in to achieve a variety of source function hook part of the brain is not sober, welcome to come back at any time to see this section, regain courage and confidence!

Plug-ins are simple

Go to the Rollup plugin list, find any plugin you’re interested in, and look at the source code.

There are many plug-ins with dozens of lines, no more than 100 lines. For example, the @rollup/plugin-image plugin for image files has no more than 50 lines of code, and the @rollup/plugin-json plugin for converting JSON files to ES6 modules has even less source code.

A case in point

// An example from the official website
export default function myExample () {
  return {
    name: 'my-example'.// The name is displayed in warnings and errors
    resolveId ( source ) {
      if (source === 'virtual-module') {
        return source; Rollup should not query other plug-ins or file systems
      }
      return null; // other IDS Normal processing
    },
    load ( id ) {
      if (id === 'virtual-module') {
        return 'export default "This is virtual!" '; // source code for "virtual-module"
      }
      return null; // other ids}}; }// rollup.config.js
import myExample from './rollup-plugin-my-example.js';
export default ({
  input: 'virtual-module'.// Configure virtual-Module as an entry file to be processed by the plug-in
  plugins: [myExample()],
  output: [{
    file: 'bundle.js'.format: 'es'}}]);Copy the code

Just see not practice false handle, imitate write a:

// Make up an example of QAQ
export default function bundleReplace () {
  return {
    name: 'bundle-replace'.// The name is displayed in warnings and errors
    transformBundle(bundle) {
      return bundle
        .replace('key_word'.'replace_word')
        .replace(/ regular /.'Replace content'); }}; }// rollup.config.js
import bundleReplace from './rollup-plugin-bundle-replace.js';
export default ({
  input: 'src/main.js'.// Generic entry file
  plugins: [bundleReplace()],
  output: [{
    file: 'bundle.js'.format: 'es'}}]);Copy the code

Hey! It’s not hard

Implementation of the rollup Plugin function

The rollup Plugin can’t be that simple

Then, of course, the implementation principle is analyzed with examples

Rollup provides configuration options and infuses properties and methods related to the current build results for developers to add, delete, change, and check.

So once the plug-in is written, how does Rollup invoke it and implement its functionality during the packaging process?

Relevant concepts

First of all, we need to take a look at the necessary prerequisites for handling plugins in rollup. Ts (context dependent), plugindriver. ts (driver dependent), PluginCache. Ts (cache dependent), and pluginutils.ts (warning error exception handling) files can be located basically. One of the most critical is in plugindriver.ts.

It is important to understand the concept of plug-in driver, which is the core of implementing the functions provided by plug-ins — PluginDriver, plug-in driver, call plug-in, provide plug-in environment context, etc.

When the hook function is called

The most important part of the rollup plugin is the hook function. There are three types of hook functions:

  • Const chunks = rollup. Build hook function during rollup execution – Build Hooks
  • Chunk. generator(write) Output hook function during execution – Output Generation Hooks
  • The watchChange hook function listens for file changes and re-executes during the execution of the built rollup.watch

Classification of hook function handling methods

In addition to the call timing, we can also divide hook functions by the way they are handled, so there are four main versions of hook functions:

  • Async: An asynchronous hook that handles promises, i.e. such hooks can return a promise that resolves to a value of the same type. The synchronous version of the hook will be marked assync.
  • First: If multiple plug-ins implement the same hook function, it will be executed in string, beginning to end, but if one of them returns a value other than null or undefined, the subsequent plug-in will be terminated.
  • Sequential: If multiple plug-ins implement the same hook function, it is executed in sequence, beginning to end in the order in which the plug-in was used, or asynchronously, waiting for processing to complete before executing the next plug-in.
  • Parallel: same as above, but if a plug-in is asynchronous, subsequent plug-ins do not wait, but execute in parallel, which is what we saw in the rollup.rollup() phase.

Building hook functions

To interact with the build process, your plug-in object needs to contain some build hook functions. Build hooks are functions that are called during each phase of a build. Build hook functions can influence how a build executes, provide information about a build, or modify a build after it is complete. There are different build hook functions in rollup, and when executed during build, They are [rollup.rollup(inputOptions)](https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/cli/run/bui Ld. Ts# L34) trigger.

Building hook functions focuses on locating, supplying, and converting input files before they are processed by Rollup. The first hook in the build phase is Options, and the last hook is always buildEnd unless there is a build error, in which case closeBundle will be called after that.

Incidentally, in watch mode, the watchChange hook can be fired at any time to tell a new run that it will fire after the current run produces its output. The closeWatcher hook function is triggered when Watcher is closed.

Output hook function

The output generated hook function provides information about the generated package and is executed immediately after the build is complete. They work the same way and have the same type as build hook functions, But the difference is that they are separated by , [bundle. The generate (output)] (HTTP: / / https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/cli/run/build. Ts# L44) or [bundle.write(outputOptions)](https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/cli/run/bui Ld. Ts# L64) call. Plug-ins that generate hooks using only output can also be passed in with the output option, since only some outputs are run.

The first hook function in the output generation phase is outputOptions, if the output passes bundle.generate(…). The first hook function is generateBundle if the output is generated successfully through [bundle.write(…)]. (https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/src/watch/watch.ts#L200) to generate the final function is a hook [writeBundle] (https://github.com/rollup/rollup/blob/master/src/rollup/rollup.ts#L176), and if the error output generated phase, The last hook function is renderError.

Also, closeBundle can be called as the last hook, but it is the user’s responsibility to trigger it manually by calling bundle.close(). The CLI will always ensure that this happens.

These are the concepts that you need to know, and it’s still hard to understand what these hook functions do! So let’s get down to business!

Hook function load implementation

[PluginDriver](https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/src/utils/PluginDriver.ts# L124) has 9 hook loading functions. This is mainly because there are synchronous and asynchronous versions of each type of hook.

Next kangkang first 9 hook loading functions and their application scenarios (see the first time I do not know why, but others see the za also have to see, first see again, do not understand how to see several times)

In no particular order, only refer to the order in which they appear in plugindriver.ts 🌠.

1. hookFirst

Load hook functions of type FIRST, resolveId, resolveAssetUrl, etc., initialize promises and this.plugins when instantiating Graph, and override previous promises. Implement serially executed hook functions. When multiple plug-ins implement the same hook function, execute it from beginning to end. If one of them returns a value other than null or undefined, the subsequent plug-in is terminated.

function hookFirst<H extends keyof PluginHooks.R = ReturnType<PluginHooks[H] > > (hookName: H, args: Args<PluginHooks[H]>, replaceContext? : ReplaceContext |null, skip? : number |null
) :EnsurePromise<R> {
  // Initialize promise
  let promise: Promise<any> = Promise.resolve();
  // When instantiating Graph, initialize this.plugins
  for (let i = 0; i < this.plugins.length; i++) {
    if (skip === i) continue;
    // Override the previous promise, which executes the hook function sequentially
    promise = promise.then((result: any) = > {
      // If not null or undefined, stop running and return the result
      if(result ! =null) return result;
      // Execute the hook function
      return this.runHook(hookName, args as any[], i, false, replaceContext);
    });
  }
  // return the hook promise
  return promise;
}
Copy the code

2. hookFirstSync

A synchronous version of hookFirst, used in resolveFileUrl, resolveImportMeta, etc.

function hookFirstSync<H extends keyof PluginHooks.R = ReturnType<PluginHooks[H] > > (hookName: H, args: Args
       
        , replaceContext? : ReplaceContext
       [h]>) :R {
  for (let i = 0; i < this.plugins.length; i++) {
    // Synchronous version of runHook
    const result = this.runHookSync(hookName, args, i, replaceContext);
    // If not null or undefined, stop running and return the result
    if(result ! =null) return result as any;
  }
  // Otherwise null is returned
  return null as any;
}
Copy the code

3. hookParallel

Execute the hook in parallel without waiting for the current hook to complete. That is, if a plug-in is asynchronous, subsequent plug-ins do not wait, but execute in parallel. Use scenarios buildEnd, buildStart, moduleParsed, and so on.

hookParallel<H extends AsyncPluginHooks & ParallelPluginHooks>(
  hookName: H,
  args: Parameters<PluginHooks[H]>, replaceContext? : ReplaceContext ):Promise<void> {
  const promises: Promise<void= > [] [];for (const plugin of this.plugins) {
    const hookPromise = this.runHook(hookName, args, plugin, false, replaceContext);
    if(! hookPromise)continue;
    promises.push(hookPromise);
  }
  return Promise.all(promises).then(() = > {});
}
Copy the code

4.hookReduceArg0

Perform the reduce operation on the first arG item. Scenarios: Options, renderChunk, etc.

function hookReduceArg0<H extends keyof PluginHooks.V.R = ReturnType<PluginHooks[H] > > (
    hookName: H,
    [arg0, ...args]: any[], // Take the first argument of the array passed in and place the rest in an arrayreduce: Reduce<V, R>, replaceContext? : ReplaceContext// Replace the context of the current plugin call
) {
  let promise = Promise.resolve(arg0); // Return source.code by default
  for (let i = 0; i < this.plugins.length; i++) {
    // The first promise will receive only arg0 passed above
    // Each time after that, the promise accepts the source.code value from the previous plug-in
    promise = promise.then(arg0= > {
      const hookPromise = this.runHook(hookName, [arg0, ...args], i, false, replaceContext);
      // If no promise is returned, arg0 is returned
      if(! hookPromise)return arg0;
      // result represents the return value of plug-in execution completion
      return hookPromise.then((result: any) = >
        reduce.call(this.pluginContexts[i], arg0, result, this.plugins[i])
      );
    });
  }
  return promise;
}
Copy the code

5.hookReduceArg0Sync

HookReduceArg0 synchronizes the version using scenario Transform, generateBundle, etc.

6. hookReduceValue

Reduce the return value to type T and process the reduced value separately. Allow hooks as values.

hookReduceValue<H extends PluginValueHooks, T>(
		hookName: H,
		initialValue: T | Promise<T>,
		args: Parameters<AddonHookFunction>,
		reduce: (reduction: T, result: ResolveValue
       
        >, plugin: Plugin
       ) = >T, replaceContext? : ReplaceContext ):Promise<T> {
		let promise = Promise.resolve(initialValue);
		for (const plugin of this.plugins) {
			promise = promise.then(value= > {
				const hookPromise = this.runHook(hookName, args, plugin, true, replaceContext);
				if(! hookPromise)return value;
				return hookPromise.then(result= >
					reduce.call(this.pluginContexts.get(plugin), value, result, plugin)
				);
			});
		}
		return promise;
	}
Copy the code

7. hookReduceValueSync

A synchronous version of hookReduceValue.

8. hookSeq

Sequential hook functions that are different from hookFirst in that they cannot be broken. Use scenarios include onwrite, generateBundle, and so on.

async function hookSeq<H extends keyof PluginHooks> (hookName: H, args: Args<PluginHooks[H]>, replaceContext? : ReplaceContext,// hookFirst determines whether to skip a hook function using the skip argument
) :Promise<void> {
  let promise: Promise<void> = Promise.resolve();
  for (let i = 0; i < this.plugins.length; i++)
    promise = promise.then(() = >
      this.runHook<void>(hookName, args as any[], i, false, replaceContext),
    );
  return promise;
}
Copy the code

9.hookSeqSync

The synchronous version of hookSeq does not need to construct promises and instead executes hook functions directly using runHookSync. The application scenarios include closeWatcher and watchChange.

hookSeqSync<H extends SyncPluginHooks & SequentialPluginHooks>(
  hookName: H,
  args: Parameters<PluginHooks[H]>, replaceContext? : ReplaceContext ):void {
  for (const plugin of this.plugins) {
    this.runHookSync(hookName, args, plugin, replaceContext); }}Copy the code

By looking at how the above hook functions are called, we can see that there is an internal method to call the hook function: runHook(Sync)(synchronous and asynchronous versions, of course), which actually executes the hook function provided in the plug-in.

In other words, all the hook functions mentioned above only determine when and how our plug-in is called (e.g. synchronous/asynchronous), whereas the hook that actually calls and executes the plug-in function (the plug-in itself is a “function”) is actually a runHook.

runHook(Sync)

Actually executing the plug-in’s hook functions, the difference between the synchronous and asynchronous versions is the permitValues license that allows the return of values rather than just functions.

function runHook<T> (hookName: string, args: any[], pluginIndex: number, permitValues: boolean, hookContext? : ReplaceContext |null.) :Promise<T> {
  this.previousHooks.add(hookName);
  // Find the current plugin
  const plugin = this.plugins[pluginIndex];
  // Find the currently executing hooks function defined in plugin
  const hook = (plugin as any)[hookName];
  if(! hook)return undefined as any;

  PluginContexts are defined at the time plugin driver class is initialized. PluginContexts are an array that holds the context of each plug-in
  let context = this.pluginContexts[pluginIndex];
  // Used to distinguish between plug-in contexts that treat different hook functions
  if (hookContext) {
    context = hookContext(context, plugin);
  }
  return Promise.resolve()
    .then(() = > {
      // Allows return values, instead of a function hook, to be loaded using hookReduceValue or hookReduceValueSync.
      // In the sync version of the hook function, there is no permitValues license flag to allow the return value
      if (typeofhook ! = ='function') {
        if (permitValues) return hook;
        return error({
          code: 'INVALID_PLUGIN_HOOK'.message: `Error running plugin hook ${hookName} for ${plugin.name}, expected a function hook.`}); }// Pass in the plug-in context and parameters and return the plug-in execution result
      return hook.apply(context, args);
    })
    .catch(err= > throwPluginError(err, plugin.name, { hook: hookName }));
}
Copy the code

After reading these hook functions, we know when to call the plug-in, how to call it, and execute the output hook function. But you think this is the end? Of course, we’re not done. We’re going to take these hooks back to the rollup package process for an example of when and how to call

rollup.rollup()

Back to the original starting point ~~~

As mentioned earlier, build hook functions locate, supply, and transform input files before they are processed by Rollup. So of course you should start from the input ~

The build phase

Processing inputOptions

// From processing the inputOptions, your plug-in hook function has arrived!
const { options: inputOptions, unsetOptions: unsetInputOptions } = awaitgetInputOptions( rawInputOptions, watcher ! = =null
);
Copy the code

Guys, bring async, first, sequential and Parallel along with nine hook functions!

// inputOptions uses the options hook
function applyOptionHook(watchMode: boolean) {
	return async ( // asynchronous serial execution
		inputOptions: Promise<GenericConfigObject>,
		plugin: Plugin
	): Promise<GenericConfigObject> => {
		if (plugin.options) { // The plugin configuration exists
			return(((await plugin.options.call(
					{ meta: { rollupVersion, watchMode } }, / / context
					await inputOptions
				)) as GenericConfigObject) || inputOptions
			);
		}

		return inputOptions;
	};
}
Copy the code

And then standardize the plug-in

// Standard plugin
function normalizePlugins(plugins: Plugin[], anonymousPrefix: string) :void {
	for (let pluginIndex = 0; pluginIndex < plugins.length; pluginIndex++) {
		const plugin = plugins[pluginIndex];
		if(! plugin.name) { plugin.name =`${anonymousPrefix}${pluginIndex + 1}`; }}}Copy the code

Generate graph object processing

Here we go! const graph = new Graph(inputOptions, watcher); This calls some of the key hook functions described above

// Handle more than caching
this.pluginCache = options.cache? .plugins ||Object.create(null);

// And WatchChangeHook
if (watcher) {
  this.watchMode = true;
  const handleChange: WatchChangeHook = (. args) = > this.pluginDriver.hookSeqSync('watchChange', args); // hookSeq synchronization version,watchChange scenario
  const handleClose = () = > this.pluginDriver.hookSeqSync('closeWatcher'[]);// hookSeq synchronized version, used in closeWatcher scenarios
  watcher.on('change', handleChange);
  watcher.on('close', handleClose);
  watcher.once('restart'.() = > {
    watcher.removeListener('change', handleChange);
    watcher.removeListener('close', handleClose);
  });
}

this.pluginDriver = new PluginDriver(this, options, options.plugins, this.pluginCache); // Generate a plug-in driver object.this.moduleLoader = new ModuleLoader(this.this.modulesById, this.options, this.pluginDriver); // Initialize the module load object
Copy the code

So far, processing inputOptions generated graph objects, remember? We’ve seen that the _ graph contains entries and the interrelationships of dependencies, operations, caches, etc., and implements AST transformations inside instances that are at the heart of rollup.

We talked about that! In the _resolve_path phase, to find the module definition from the absolute path of the entry file and obtain all the dependent statements of the entry module, we need to resolve the file address by resolveId() method and get the absolute path of the file. This is done by calling resolveId from the ModuleLoader. ResolveId (), which we talked about in tree-shaking the basic build process, calls the hook function

export function resolveIdViaPlugins(
	source: string,
	importer: string | undefined,
	pluginDriver: PluginDriver,
	moduleLoaderResolveId: (
		source: string,
		importer: string | undefined,
		customOptions: CustomPluginOptions | undefined,
		skip: { importer: string | undefined; plugin: Plugin; source: string }[] | null) = >Promise<ResolvedId | null>,
	skip: { importer: string | undefined; plugin: Plugin; source: string }[] | null,
	customOptions: CustomPluginOptions | undefined
) {
	let skipped: Set<Plugin> | null = null;
	let replaceContext: ReplaceContext | null = null;
	if (skip) {
		skipped = new Set(a);for (const skippedCall of skip) {
			if (source === skippedCall.source && importer === skippedCall.importer) {
				skipped.add(skippedCall.plugin);
			}
		}
		replaceContext = (pluginContext, plugin): PluginContext= > ({
			...pluginContext,
			resolve: (source, importer, { custom, skipSelf } = BLANK) = > {
				returnmoduleLoaderResolveId( source, importer, custom, skipSelf ? [...skip, { importer, plugin, source }] : skip ); }}); }return pluginDriver.hookFirst( // hookFirst is called, which is an absolute path, of type FIRST. If any plug-in returns a value, then all subsequent plug-in resolveids will not be executed.
		'resolveId',
		[source, importer, { custom: customOptions }],
		replaceContext,
		skipped
	);
}
Copy the code

After getting the absolute path returned by the resolveId hook, we need to find its module definition from the absolute path of the entry file, obtain all the dependent statements of the entry module, and return all the contents. Here we collect configurations and standardize, analyze files and compile source code to generate AST, generate modules and parse dependencies, finally generate chunks, and all in all, read and modify files! Note that each file is handled by only one plug-in load Hook because it is executed using hookFirst. Also, if you don’t return a value, rollup will automatically read the file. Next, enter the fetchModule phase ~

const module: Module = newModule(...) .await this.pluginDriver.hookParallel('moduleParsed'[module.info]); // Execute hook,moduleParsed scenarios in parallel.await this.addModuleSource(id, importer, module); .// addModuleSource
source = (await this.pluginDriver.hookFirst('load', [id])) ?? (await readFile(id)); // The load phase transforms and builds the code.// resolveDynamicImport
const resolution = await this.pluginDriver.hookFirst('resolveDynamicImport', [
  specifier,
  importer
]);
Copy the code

Bundle handling code

The generated graph object is ready to enter the build phase ~~ Plugin function hooks in build start and end

await graph.pluginDriver.hookParallel('buildStart', [inputOptions]); // Execute hook in parallel,buildStart scenario.awaitgraph.build(); .await graph.pluginDriver.hookParallel('buildEnd'[]);// Execute hook in parallel,buildEnd scenario
Copy the code

If an exception occurs during the buildStart and Build phases, the hookParallel hook function that handles closeBundle is triggered early:

await graph.pluginDriver.hookParallel('closeBundle'[]);Copy the code

The generate phase

outputOptions

In the handleGenerateWrite() phase, get the processed outputOptions.

outputPluginDriver.hookReduceArg0Sync(
  'outputOptions',
  [rawOutputOptions.output || rawOutputOptions] as [OutputOptions],
  (outputOptions, result) = > result || outputOptions,
    pluginContext= > {
    const emitError = () = > pluginContext.error(errCannotEmitFromOptionsHook());
    return {
      ...pluginContext,
      emitFile: emitError,
      setAssetSource: emitError }; })Copy the code

Generate the bundle object with the processed outputOptions as the passed arguments:

const bundle = new Bundle(outputOptions, unsetOptions, inputOptions, outputPluginDriver, graph);
Copy the code

The generated code

Generate (isWrite) in const generated = await bundle.generate(isWrite); Bundle generation code phase,

./ / start render
await this.pluginDriver.hookParallel('renderStart'[this.outputOptions, this.inputOptions]); .// This hook function cannot be interrupted
await this.pluginDriver.hookSeq('generateBundle'[this.outputOptions,
  outputBundle as OutputBundle,
  isWrite
]);
Copy the code

Finally, the generated code ~ is executed in parallel

await outputPluginDriver.hookParallel('writeBundle', [outputOptions, generated]);
Copy the code

summary

As you can see, plug-in hooks play different roles throughout the rollup packaging process and support the implementation. What we’ve done so far is tease out and understand the process, and then look back at the picture and see if it’s a little bit clearer.

Finally, we’ll talk about the rollup plugin’s two peripherals

Plug-in context

Rollup injects the hook function with context, which is used to add, delete, change chunks and other build information. In other words, in the plug-in, the above method can be called directly from each hook through this. XXX.

const context: PluginContext = {
    addWatchFile(id) {},
    cache: cacheInstance,
    emitAsset: getDeprecatedContextHandler(...) .emitChunk: getDeprecatedContextHandler(...) .emitFile: fileEmitter.emitFile,
    error(err)
    getAssetFileName: getDeprecatedContextHandler(...) .getChunkFileName: getDeprecatedContextHandler(),
    getFileName: fileEmitter.getFileName,
    getModuleIds: () = > graph.modulesById.keys(),
    getModuleInfo: graph.getModuleInfo,
    getWatchFiles: () = > Object.keys(graph.watchFiles),
    isExternal: getDeprecatedContextHandler(...) .meta: { / / bind graph. WatchMode
        rollupVersion,
        watchMode: graph.watchMode
    },
    get moduleIds() { / / bind graph. ModulesById. Keys ();
        const moduleIds = graph.modulesById.keys();
        return wrappedModuleIds();
    },
    parse: graph.contextParse, / / bind graph. ContextParse
    resolve(source, importer, { custom, skipSelf } = BLANK) { // Bind methods on graph.moduleloader
        return graph.moduleLoader.resolveId(source, importer, custom, skipSelf ? pidx : null);
    },
    resolveId: getDeprecatedContextHandler(...) .setAssetSource: fileEmitter.setAssetSource,
    warn(warning){}};Copy the code

Caching of plug-ins

The plug-in also provides caching capabilities that take advantage of closures in a very clever way.

export function createPluginCache(cache: SerializablePluginCache) :PluginCache {
	// Use closures to cache the cache
	return {
		has(id: string) {
			const item = cache[id];
			if(! item)return false;
			item[0] = 0; // If yes, then reset the access expiration times, so that the user intends to use actively
			return true;
		},
		get(id: string) {
			const item = cache[id];
			if(! item)return undefined;
			item[0] = 0; // If yes, reset the access expiration times
			return item[1];
		},
		set(id: string, value: any) {
            // The storage unit is an array, the first item is used to count the number of accesses
			cache[id] = [0, value];
		},
		delete(id: string) {
			return deletecache[id]; }}; }Copy the code

The cache is then created and added to the plug-in context:

import createPluginCache from 'createPluginCache';

const cacheInstance = createPluginCache(pluginCache[cacheKey] || (pluginCache[cacheKey] = Object.create(null)));

const context = {
	// ...
    cache: cacheInstance,
    // ...
}
Copy the code

After that, we can use cache to cache in the plug-in environment to further improve the packaging efficiency:

function testPlugin() {
  return {
    name: "test-plugin".buildStart() {
      if (!this.cache.has("prev")) {
        this.cache.set("prev"."Results of last plug-in execution");
      } else {
        // it will be executed when rollup is executed the second time
        console.log(this.cache.get("prev")); }}}; }let cache;
async function build() {
  const chunks = await rollup.rollup({
    input: "src/main.js".plugins: [testPlugin()],
    // We need to pass the last packing result
    cache,
  });
  cache = chunks.cache;
}

build().then(() = > {
  build();
});
Copy the code

conclusion

Congratulations, the rollup so several hooks endure to see come over, and combed the rollup. Again a rollup () packaging process. To sum up a few outputs, kangkang what we learned:

  1. Rollup’s plug-in is essentially a handler function that returns an object. The returned object contains properties (such as name) and hook functions at different stages (build and output stages) to implement functions inside the plug-in.
  2. Most of the hook functions in the plug-in return object define when and how the plug-in is called. Only the runHook(Sync) hook actually executes the plug-in.
  3. Rollup.rollup () packaging process is also combed through the flow chart in Figure 1.
  4. So easy, a function who will not use it? And for the simple plug-in function of the development page is not just simple imitation, can also do know!

In the actual plug-in development, we will further use these knowledge and master one by one, at least when writing bugs, comb through the principle of the plug-in, and then further internalize absorption, can quickly locate the problem. If you have an idea during development, start writing your own rollup plug-in!

Go to the online diagnosis and treatment platform of Wedoctor Internet hospital, make a quick consultation, and find a top three doctor for you in 3 minutes.