1. rollup #

1.1 debugger.js #

import { rollup, watch } from 'rollup';
import inputOptions from './rollup.config.js'
  ; (async function () {
    //打包阶段 
    const bundle = await rollup(inputOptions);
    //生成阶段
    await bundle.generate(inputOptions.output);
    //写入阶段
    await bundle.write(inputOptions.output);
    /* 
    const watcher = watch(inputOptions);
    watcher.on('event', event => {
      console.log(event);
    });
    setTimeout(() => {
      watcher.close();
    }, 1000); */
    //关闭阶段
    await bundle.close();
  })();

1.2 rollup.config.js #

rollup.config.js

export default {
  input: "./src/index.js",
  output: {
    dir: 'dist',
  }
}

1.3 package.json #

package.json

{
  "scripts": {
    "build": "rollup -c"
  },
}

2. rollup插件 #

2.1 插件规范 #

2.2.插件属性 #

2.2.1 name #

2.2 Build Hooks #

2.2.1 rollup-plugin-build.js #

plugins\rollup-plugin-build.js

import fs from 'fs';

function build() {
  return {
    name: 'build',
    async watchChange(id, change) {
      console.log('watchChange', id, change);
    },
    async closeWatcher() {
      console.log('closeWatcher');
    },
    async options(inputOptions) {
      console.log('options');
      //inputOptions.input = './src/main.js';
    },
    async buildStart(inputOptions) {
      console.log('buildStart');
    },
    async resolveId(source, importer) {
      if (source === 'virtual') {
        console.log('resolveId', source);
        //如果resolveId钩子有返回值了,那么就会跳过后面的查找逻辑,以此返回值作为最终的模块ID
        return source;
      }
    },
    //加载此模块ID对应的内容
    async load(id) {
      if (id === 'virtual') {
        console.log('load', id);
        return `export default "virtual"`;
      }
    },
    async shouldTransformCachedModule({ id, code, ast }) {
      console.log('shouldTransformCachedModule');
      //不使用缓存,再次进行转换
      return true;
    },
    async transform(code, id) {
      console.log('transform');
    },
    async moduleParsed(moduleInfo) {
      console.log('moduleParsed');
    },
    async resolveDynamicImport(specifier, importer) {
      console.log('resolveDynamicImport', specifier, importer);
    },
    async buildEnd() {
      console.log('buildEnd');
    }
  }
}
export default build;

2.2.2 rollup.config.js #

rollup.config.js

+import build from './plugins/rollup-plugin-build.js';
export default {
  input: "./src/index.js",
  output: [{
    dir: 'dist',
  }],
  plugins: [
+   build()
  ]
}

2.2.3 options #

字段
Type (options: InputOptions) => InputOptions
Kind async, sequential
Previous Hook 这是构建阶段的第一个钩子
Next Hook buildStart

2.2.4 buildStart #

字段
Type (options: InputOptions) => void
Kind async, parallel
Previous Hook options
Next Hook resolveId并行解析每个入口点

2.2.5 resolveId #

字段
Type (source, importer) => string false null
Kind async, first
Previous Hook buildStart(如果我们正在解析入口点),moduleParsed(如果我们正在解析导入),或者作为resolveDynamicImport的后备方案。此外,这个钩子可以在构建阶段通过调用插件钩子触发。emitFile发出一个入口点,或在任何时候通过调用此。resolve可手动解析id
Next Hook 如果解析的id尚未加载,则load,否则buildEnd

index.js

import virtual from 'virtual-module'
console.log(virtual);

plugins\rollup-plugin-build.js

function build() {
  return {
    name: 'build',
    async resolveId(source, importer) {
      if (source === 'virtual') {
        console.log('resolveId', source);
        //如果resolveId钩子有返回值了,那么就会跳过后面的查找逻辑,以此返回值作为最终的模块ID
        return source;
      }
    },
    //加载此模块ID对应的内容
    async load(id) {
      if (id === 'virtual') {
        console.log('load', id);
        return `export default "virtual"`;
      }
    }
  }
}
export default build;

2.2.6 load #

字段
Type (id) => string
Kind async, first
Previous Hook 解析加载id的resolveIdresolveDynamicImport。此外,这个钩子可以在任何时候从插件钩子中通过调用this.load来触发预加载与id对应的模块
Next Hook transform可在未使用缓存或没有使用相同代码的缓存副本时转换加载的文件,否则应使用TransformCachedModule

2.2.7 transform #

字段
Type (code, id) => string
Kind async, sequential
Previous Hook load 当前处理的文件的位置。如果使用了缓存,并且有该模块的缓存副本,那么如果插件为该钩子返回true,则应shouldTransformCachedModule
Next Hook moduleParsed 一旦文件被处理和解析,模块就会被解析

plugins\rollup-plugin-babel.js

import { createFilter } from 'rollup-pluginutils'
import babel from '@babel/core'
function plugin(pluginOptions = {}) {
  const defaultExtensions = ['.js', '.jsx']
  const { exclude, include, extensions = defaultExtensions } = pluginOptions;
  const extensionRegExp = new RegExp(`(${extensions.join('|')})$`)
  const userDefinedFilter = createFilter(include, exclude);
  const filter = id => extensionRegExp.test(id) && userDefinedFilter(id);
  return {
    name: 'babel',
    async transform(code, filename) {
      if (!filter(filename)) return null;
      let result = await babel.transformAsync(code);
      return result
    }
  }
}
export default plugin

2.2.8 shouldTransformCachedModule #

字段
Type ({id, code, ast, resoledSources, moduleSideEffects, syntheticNamedExports) => boolean
Kind async, first
Previous Hook load 加载缓存文件以将其代码与缓存版本进行比较的位置
Next Hook moduleParsed if no plugin returns true, otherwise transform.
npx rollup -c -w
shouldTransformCachedModule
transform
moduleParsed

shouldTransformCachedModule
moduleParsed

2.2.9 moduleParsed #

字段
Type (moduleInfo: ModuleInfo) => void
Kind async, parallel
Previous Hook transform 转换当前处理的文件的位置
Next Hook resolveIdresolveDynamicImport 并行解析所有发现的静态和动态导入(如果存在),否则buildEnd

2.2.10 resolveDynamicImport #

字段
Type (specifier, importer) => string
Kind async, first
Previous Hook moduleParsed 已为导入文件分配模块
Next Hook load 如果钩子使用尚未加载的id ,如果动态导入包含字符串且钩子未解析,请加载resolveId,否则为buildEnd

index.js

import('./msg.js').then(res => console.log(res))

2.2.11 buildEnd #

字段
Type (error) => void
Kind async, parallel
Previous Hook moduleParsed, resolveId or resolveDynamicImport.
Next Hook outputOptions 输出生成阶段的输出,因为这是构建阶段的最后一个挂钩

2.3 Output Generation Hooks #

2.3.1 rollup-plugin-generation.js #

plugins\rollup-plugin-generation.js

function generation() {
  return {
    name: 'rollup-plugin-generation',
    //这个钩子是同步的,不能加async
    outputOptions(outputOptions) {
      console.log('outputOptions');
    },
    renderStart() {
      console.log('renderStart');
    },
    banner() {
      console.log('banner');
    },
    footer() {
      console.log('footer');
    },
    intro() {
      console.log('intro');
    },
    outro() {
      console.log('outro');
    },
    renderDynamicImport() {
      console.log('renderDynamicImport');
    },
    augmentChunkHash() {
      console.log('augmentChunkHash');
    },
    resolveFileUrl() {
      console.log('resolveFileUrl');
    },
    resolveImportMeta() {
      console.log('resolveImportMeta');
    },
    renderChunk() {
      console.log('renderChunk');
    },
    generateBundle() {
      console.log('generateBundle');
    },
    writeBundle() {
      console.log('writeBundle');
    },
    renderError() {
      console.log('renderError');
    },
    closeBundle() {
      console.log('closeBundle');
    }
  }
}
export default generation;

2.3.2 rollup.config.js #

rollup.config.js

import build from './plugins/rollup-plugin-build.js';
+import generation from './plugins/rollup-plugin-generation.js';
export default {
  input: "./src/index.js",
  output: [{
    dir: 'dist',
  }],
  plugins: [
    build(),
+   generation()
  ]
}

2.3.3 outputOptions #

字段
Type (outputOptions) => null
Kind async, parallel
Previous Hook buildEnd如果这是第一次生成输出,否则为generateBundlewriteBundlerenderError取决于先前生成的输出。这是输出生成阶段的第一个钩子
Next Hook outputOptions 输出生成阶段的输出,因为这是构建阶段的最后一个挂钩

2.3.4 renderStart #

字段
Type (outputOptions, inputOptions) => void
种类 async, parallel
上一个钩子 outputOptions
下一个钩子 banner, footer, intro and outro 并行运行
字段
Type () => string)
Kind async, parallel
Previous Hook renderStart
Next Hook 针对每个动态导入表达式 renderDynamicImport
字段
Type () => string)
Kind async, parallel
Previous Hook renderStart
Next Hook 针对每个动态导入表达式 renderDynamicImport

2.3.7 intro #

字段
Type () => string)
Kind async, parallel
Previous Hook renderStart
Next Hook 针对每个动态导入表达式 renderDynamicImport

2.3.8 outro #

字段
Type () => string)
Kind async, parallel
Previous Hook renderStart
Next Hook 针对每个动态导入表达式 renderDynamicImport

2.3.9 renderDynamicImport #

字段
Type ({format, moduleId, targetModuleId, customResolution}) => {left: string, right: string}
Kind async, parallel
Previous Hook banner , footer, intro, outro
Next Hook augmentChunkHash对于每个在文件名中包含哈希的块

plugins\rollup-plugin-import-polyfill.js

function importPolyfill() {
  return {
    name: 'importPolyfill',
    async moduleParsed(moduleInfo) {
      if (moduleInfo.isEntry) {
        moduleInfo.moduleSideEffects = 'no-treeshake';
      }
    },
    renderDynamicImport({ format, moduleId, targetModuleId }) {
      return {
        left: 'dynamicImportPolyfill(',
        right: ', import.meta.url)'
      };
    }
  }
}
export default importPolyfill;
import('./msg.js').then(res => console.log(res.default))
function dynamicImportPolyfill(filename, url) {
  return new Promise(resolve => {
    window.dynamicImportPolyfillResolve = resolve;
    const resourceUrl = new URL(filename, url).href;
    let script = document.createElement('script');
    script.type = 'module';
    script.innerHTML = `
    import * as resource from ${JSON.stringify(resourceUrl)};
    dynamicImportPolyfillResolve(resource);
    `;
    document.head.appendChild(script);
  });
}
console.log(dynamicImportPolyfill);

2.3.9 augmentChunkHash #

字段
Type (chunkInfo: ChunkInfo) => string
Kind sync, sequential
Previous Hook renderDynamicImport针对每个动态导入表达式
Next Hook resolveFileUrl对于每次使用import.meta.ROLLUP_FILE_URL_referenceIdresolveImportMeta所有其他访问import.meta
function generation() {
  return {
    name: 'generation',
    augmentChunkHash(chunkInfo) {
      console.log('augmentChunkHash', chunkInfo.name);
      if (chunkInfo.name === 'msg') {
        //返回不是hash,而是计算hash的内容
        return Date.now().toString();
      }
    }
  }
}
export default generation

2.3.10 resolveFileUrl #

字段
Type ({chunkId, fileName, format, moduleId, referenceId, relativePath}) => string
Kind sync, first
Previous Hook augmentChunkHash对于在文件名中包含哈希的每个块
Next Hook renderChunk对于每个块

src\index.js

import logger from 'logger'
console.log(logger);

plugins\rollup-plugin-resolveFileUrl.js

export default function resolveFileUrl() {
  return {
    name: 'resolveFileUrl',
    resolveId(source) {
      if (source === 'logger') {
        return source;
      }
    },
    load(importee) {
      if (importee === 'logger') {
        let referenceId = this.emitFile({ type: 'asset', source: 'console.log("logger")', fileName: "logger.js" });
        return `export default import.meta.ROLLUP_FILE_URL_${referenceId}`;
      }
    },
    resolveFileUrl({ chunkId, fileName, format, moduleId, referenceId, relativePath }) {//import.meta.url
      return `new URL('${fileName}', document.baseURI).href`;
    }
  };
}

2.3.11 resolveImportMeta #

字段
Type (property, {chunkId, moduleId, format}) => string
Kind sync, first
Previous Hook augmentChunkHash对于在文件名中包含哈希的每个块
Next Hook renderChunk对于每个块

2.3.12 renderChunk #

字段
Type (code, chunk, options) => string
Kind async, sequential
Previous Hook resolveFileUrl对于 . 的每次使用import.meta.ROLLUP_FILE_URL_referenceIdresolveImportMeta所有其他访问import.meta
Next Hook generateBundle

2.3.13 generateBundle #

字段
Type (options, bundle, isWrite) => void
Kind async, sequential
Previous Hook renderChunk对于每个块
Next Hook writeBundle如果输出是通过生成的,否则这是输出生成阶段的最后一个钩子,如果生成另一个输出bundle.write(),可能会再次跟随outputOptions

plugins\rollup-plugin-html.js

import dedent from 'dedent';
export default function html() {
  return {
    name: 'html',
    generateBundle(options, bundle) {
      let entryName;
      for (let fileName in bundle) {
        let assetOrChunkInfo = bundle[fileName];
        //console.log(fileName, assetOrChunkInfo);
        if (assetOrChunkInfo.isEntry) {
          entryName = fileName;
        }
      }
      this.emitFile({
        type: 'asset',
        fileName: 'index.html',
        source: dedent`
        <!DOCTYPE html>
        <html>
        <head>
          <meta charset="UTF-8">
          <title>rollup</title>
         </head>
        <body>
          <script src="${entryName}" type="module"></script>
        </body>
        </html>`
      });
    }
  };
}

2.3.14 writeBundle #

字段
Type (options,bundle) => void
Kind async, parallel
Previous Hook generateBundle
Next Hook 如果被调用,这是输出生成阶段的最后一个钩子,如果生成另一个输出,可能会再次跟随outputOptions

2.3.15 renderError #

字段
Type (error: Error) => void
Kind async, parallel
Previous Hook renderStart从到 的任何钩子renderChunk
Next Hook outputOptions如果它被调用,这是输出生成阶段的最后一个钩子,如果生成另一个输出,可能会再次跟随

2.3.16 closeBundle #

字段
Type closeBundle: () => Promise void
Kind async, parallel
Previous Hook buildEnd 如果有构建错误.否则何时bundle.close()被调用,在这种情况下,这将是最后一个被触发的钩子。

3.Plugin Context #

3.1 this.emitFile #

type EmittedChunk = {
  type: 'chunk';
  id: string;
  name?: string;
  fileName?: string;
};

type EmittedAsset = {
  type: 'asset';
  name?: string;
  fileName?: string;
  source?: string | Uint8Array;
};

3.2 this.load #

3.3 this.load #

3.4 this.resolve #