Rollup 插件系统 #

插件概述 #

Rollup 插件是一个对象,包含一个或多个属性、构建钩子和输出生成钩子。

text
┌─────────────────────────────────────────────────────────────┐
│                    Rollup 插件系统                           │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  解析类插件   │  │  转换类插件   │  │  优化类插件   │      │
│  │  resolve     │  │  babel      │  │  terser     │      │
│  │  commonjs    │  │  typescript │  │  visualizer │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  资源类插件   │  │  工具类插件   │  │  开发类插件   │      │
│  │  image       │  │  json       │  │  serve      │      │
│  │  postcss     │  │  alias      │  │  livereload │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                              │
└─────────────────────────────────────────────────────────────┘

官方插件 #

@rollup/plugin-node-resolve #

解析 node_modules 中的模块:

bash
npm install @rollup/plugin-node-resolve --save-dev
javascript
import resolve from '@rollup/plugin-node-resolve';

export default {
  plugins: [
    resolve({
      // 配置选项
      browser: true,              // 浏览器环境
      dedupe: ['vue'],           // 去重
      extensions: ['.js', '.jsx', '.ts', '.tsx'],
      mainFields: ['browser', 'module', 'main'],
      preferBuiltins: false,      // 不优先使用内置模块
      moduleDirectories: ['node_modules', 'src'],
      rootDir: process.cwd()
    })
  ]
};

@rollup/plugin-commonjs #

转换 CommonJS 模块为 ES 模块:

bash
npm install @rollup/plugin-commonjs --save-dev
javascript
import commonjs from '@rollup/plugin-commonjs';

export default {
  plugins: [
    commonjs({
      include: /node_modules/,    // 包含的文件
      exclude: [],                // 排除的文件
      extensions: ['.js', '.cjs'],
      ignore: ['some-dependent'], // 忽略的模块
      requireReturnsDefault: 'auto',
      sourceMap: false,
      transformMixedEsModules: false
    })
  ]
};

@rollup/plugin-json #

导入 JSON 文件:

bash
npm install @rollup/plugin-json --save-dev
javascript
import json from '@rollup/plugin-json';

export default {
  plugins: [
    json({
      include: 'node_modules/**',
      exclude: ['node_modules/foo/**'],
      preferConst: true,
      indent: '  ',
      compact: false,
      namedExports: true
    })
  ]
};
javascript
// 使用示例
import pkg from './package.json';
console.log(pkg.version);

@rollup/plugin-babel #

使用 Babel 转换代码:

bash
npm install @rollup/plugin-babel @babel/core --save-dev
javascript
import babel from '@rollup/plugin-babel';

export default {
  plugins: [
    babel({
      babelHelpers: 'bundled',
      exclude: 'node_modules/**',
      extensions: ['.js', '.jsx', '.ts', '.tsx'],
      presets: ['@babel/preset-env'],
      plugins: ['@babel/plugin-transform-runtime']
    })
  ]
};

@rollup/plugin-typescript #

编译 TypeScript:

bash
npm install @rollup/plugin-typescript typescript --save-dev
javascript
import typescript from '@rollup/plugin-typescript';

export default {
  plugins: [
    typescript({
      tsconfig: './tsconfig.json',
      include: ['src/**/*.ts', 'src/**/*.tsx'],
      exclude: ['node_modules/**'],
      declaration: true,
      declarationDir: 'dist/types',
      outDir: 'dist',
      rootDir: 'src'
    })
  ]
};

@rollup/plugin-terser #

压缩代码:

bash
npm install @rollup/plugin-terser --save-dev
javascript
import terser from '@rollup/plugin-terser';

export default {
  plugins: [
    terser({
      compress: {
        drop_console: true,
        drop_debugger: true
      },
      mangle: true,
      format: {
        comments: false
      },
      toplevel: true,
      ecma: 2020
    })
  ]
};

@rollup/plugin-alias #

设置模块别名:

bash
npm install @rollup/plugin-alias --save-dev
javascript
import alias from '@rollup/plugin-alias';

export default {
  plugins: [
    alias({
      entries: [
        { find: '@', replacement: 'src' },
        { find: '@utils', replacement: 'src/utils' },
        { find: '@components', replacement: 'src/components' },
        { find: /^@\/(.+)/, replacement: 'src/$1' }
      ],
      resolve: ['.js', '.ts', '.jsx', '.tsx']
    })
  ]
};

@rollup/plugin-replace #

替换代码中的变量:

bash
npm install @rollup/plugin-replace --save-dev
javascript
import replace from '@rollup/plugin-replace';

export default {
  plugins: [
    replace({
      preventAssignment: true,
      values: {
        'process.env.NODE_ENV': JSON.stringify('production'),
        'process.env.VERSION': JSON.stringify('1.0.0'),
        __buildDate__: () => JSON.stringify(new Date()),
        __buildVersion: 15
      }
    })
  ]
};

@rollup/plugin-virtual #

创建虚拟模块:

javascript
import virtual from '@rollup/plugin-virtual';

export default {
  plugins: [
    virtual({
      entry: `
        import { foo } from 'foo';
        console.log(foo);
      `,
      foo: `
        export const foo = 'Hello from virtual module';
      `
    })
  ],
  input: 'entry'
};

@rollup/plugin-url #

导入文件为 URL 或 Base64:

bash
npm install @rollup/plugin-url --save-dev
javascript
import url from '@rollup/plugin-url';

export default {
  plugins: [
    url({
      include: ['**/*.svg', '**/*.png', '**/*.jpg', '**/*.gif'],
      exclude: ['node_modules/**'],
      limit: 10 * 1024,  // 小于 10KB 转 Base64
      emitFiles: true,
      fileName: 'assets/[name]-[hash][extname]',
      publicPath: '/assets/'
    })
  ]
};

常用第三方插件 #

rollup-plugin-postcss #

处理 CSS:

bash
npm install rollup-plugin-postcss postcss --save-dev
javascript
import postcss from 'rollup-plugin-postcss';

export default {
  plugins: [
    postcss({
      extensions: ['.css', '.scss', '.sass', '.less'],
      extract: true,              // 提取到单独文件
      minimize: true,             // 压缩
      sourceMap: true,
      inject: false,              // 不注入到 JS
      plugins: [
        require('autoprefixer'),
        require('cssnano')
      ]
    })
  ]
};

rollup-plugin-vue #

处理 Vue 组件:

bash
npm install rollup-plugin-vue @vue/compiler-sfc --save-dev
javascript
import vue from 'rollup-plugin-vue';

export default {
  plugins: [
    vue({
      target: 'browser',
      exposeFilename: false,
      preprocessStyles: true,
      css: false
    })
  ]
};

rollup-plugin-svelte #

处理 Svelte 组件:

bash
npm install rollup-plugin-svelte svelte --save-dev
javascript
import svelte from 'rollup-plugin-svelte';

export default {
  plugins: [
    svelte({
      compilerOptions: {
        dev: !production
      },
      extensions: ['.svelte']
    })
  ]
};

rollup-plugin-serve #

开发服务器:

bash
npm install rollup-plugin-serve --save-dev
javascript
import serve from 'rollup-plugin-serve';

export default {
  plugins: [
    serve({
      contentBase: 'dist',
      port: 3000,
      host: 'localhost',
      open: true,
      headers: {
        'Access-Control-Allow-Origin': '*'
      }
    })
  ]
};

rollup-plugin-livereload #

热重载:

bash
npm install rollup-plugin-livereload --save-dev
javascript
import livereload from 'rollup-plugin-livereload';

export default {
  plugins: [
    livereload({
      watch: 'dist',
      delay: 300,
      clientUrl: 'localhost:35729'
    })
  ]
};

rollup-plugin-copy #

复制文件:

bash
npm install rollup-plugin-copy --save-dev
javascript
import copy from 'rollup-plugin-copy';

export default {
  plugins: [
    copy({
      targets: [
        { src: 'src/assets/*', dest: 'dist/assets' },
        { src: 'src/index.html', dest: 'dist' },
        { src: 'static/**/*', dest: 'dist' }
      ],
      hook: 'writeBundle',
      verbose: true
    })
  ]
};

rollup-plugin-delete #

删除文件:

bash
npm install rollup-plugin-delete --save-dev
javascript
import del from 'rollup-plugin-delete';

export default {
  plugins: [
    del({
      targets: 'dist/*',
      runOnce: true
    })
  ]
};

rollup-plugin-visualizer #

打包分析:

bash
npm install rollup-plugin-visualizer --save-dev
javascript
import { visualizer } from 'rollup-plugin-visualizer';

export default {
  plugins: [
    visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true
    })
  ]
};

rollup-plugin-dts #

生成类型声明文件:

bash
npm install rollup-plugin-dts --save-dev
javascript
import dts from 'rollup-plugin-dts';

export default {
  input: 'dist/types/index.d.ts',
  output: {
    file: 'dist/my-lib.d.ts',
    format: 'es'
  },
  plugins: [dts()]
};

插件开发 #

基本结构 #

javascript
export default function myPlugin(options = {}) {
  return {
    name: 'my-plugin',
    
    // 构建钩子
    buildStart(options) {
      console.log('Build started');
    },
    
    resolveId(source, importer) {
      if (source === 'virtual-module') {
        return source;
      }
      return null;
    },
    
    load(id) {
      if (id === 'virtual-module') {
        return 'export default "This is virtual"';
      }
      return null;
    },
    
    transform(code, id) {
      if (id.endsWith('.custom')) {
        return {
          code: 'export default "Transformed"',
          map: null
        };
      }
      return null;
    },
    
    buildEnd() {
      console.log('Build ended');
    }
  };
}

构建钩子 #

javascript
export default function myPlugin() {
  return {
    name: 'my-plugin',
    
    // 选项设置
    options(options) {
      console.log('Options:', options);
      return options;
    },
    
    // 构建开始
    buildStart(options) {
      console.log('Build started');
    },
    
    // 解析模块 ID
    resolveId(source, importer, options) {
      console.log('Resolving:', source);
      return null; // 返回 null 使用默认解析
    },
    
    // 加载模块
    load(id) {
      console.log('Loading:', id);
      return null; // 返回 null 使用默认加载
    },
    
    // 转换代码
    transform(code, id) {
      console.log('Transforming:', id);
      return null; // 返回 null 不转换
    },
    
    // 模块解析完成
    moduleParsed(moduleInfo) {
      console.log('Module parsed:', moduleInfo.id);
    },
    
    // 构建结束
    buildEnd(error) {
      if (error) {
        console.error('Build error:', error);
      }
    }
  };
}

输出生成钩子 #

javascript
export default function myPlugin() {
  return {
    name: 'my-plugin',
    
    // 输出生成开始
    renderStart(outputOptions, inputOptions) {
      console.log('Render started');
    },
    
    // Banner/Footer
    banner() {
      return '// My custom banner';
    },
    
    footer() {
      return '// My custom footer';
    },
    
    // Intro/Outro
    intro() {
      return 'var ENV = "production";';
    },
    
    outro() {
      return 'console.log("Done");';
    },
    
    // 渲染代码块
    renderChunk(code, chunk, options) {
      console.log('Rendering chunk:', chunk.fileName);
      return null; // 返回 null 不修改
    },
    
    // 生成代码块
    generateBundle(options, bundle, isWrite) {
      console.log('Generating bundle');
      for (const fileName in bundle) {
        console.log('File:', fileName);
      }
    },
    
    // 写入完成
    writeBundle(options, bundle) {
      console.log('Bundle written');
    },
    
    // 输出生成结束
    renderDone() {
      console.log('Render done');
    }
  };
}

实战示例:自定义插件 #

示例一:添加时间戳 #

javascript
function timestampPlugin() {
  return {
    name: 'timestamp',
    banner() {
      return `// Built at: ${new Date().toISOString()}`;
    }
  };
}

export default {
  plugins: [timestampPlugin()]
};

示例二:环境变量注入 #

javascript
function envPlugin(env) {
  return {
    name: 'env-inject',
    transform(code, id) {
      if (id.endsWith('.js')) {
        const envCode = Object.entries(env)
          .map(([key, value]) => `const ${key} = ${JSON.stringify(value)};`)
          .join('\n');
        
        return {
          code: envCode + '\n' + code,
          map: null
        };
      }
      return null;
    }
  };
}

export default {
  plugins: [
    envPlugin({
      API_URL: 'https://api.example.com',
      VERSION: '1.0.0'
    })
  ]
};

示例三:文件复制 #

javascript
function copyPlugin(targets) {
  const fs = require('fs');
  const path = require('path');
  
  return {
    name: 'copy-files',
    writeBundle() {
      targets.forEach(({ src, dest }) => {
        const srcPath = path.resolve(src);
        const destPath = path.resolve(dest);
        
        if (!fs.existsSync(destPath)) {
          fs.mkdirSync(destPath, { recursive: true });
        }
        
        fs.copyFileSync(srcPath, path.join(destPath, path.basename(srcPath)));
        console.log(`Copied: ${src} -> ${dest}`);
      });
    }
  };
}

export default {
  plugins: [
    copyPlugin([
      { src: 'src/index.html', dest: 'dist' }
    ])
  ]
};

示例四:代码压缩统计 #

javascript
function sizePlugin() {
  const sizes = new Map();
  
  return {
    name: 'size-report',
    renderChunk(code, chunk) {
      sizes.set(chunk.fileName, {
        original: code.length,
        compressed: require('zlib').gzipSync(code).length
      });
      return null;
    },
    writeBundle() {
      console.log('\nBundle Size Report:');
      console.log('File\t\tOriginal\tGzipped');
      console.log('─'.repeat(50));
      
      for (const [file, size] of sizes) {
        console.log(
          `${file}\t\t${(size.original / 1024).toFixed(2)}KB\t\t${(size.compressed / 1024).toFixed(2)}KB`
        );
      }
    }
  };
}

export default {
  plugins: [sizePlugin()]
};

插件顺序 #

正确的插件顺序 #

javascript
export default {
  plugins: [
    // 1. 文件操作
    del({ targets: 'dist/*' }),
    copy({ targets: [...] }),
    
    // 2. 模块解析
    alias({ entries: [...] }),
    resolve(),
    commonjs(),
    
    // 3. 代码转换
    vue(),
    typescript(),
    babel({ babelHelpers: 'bundled' }),
    postcss(),
    
    // 4. 代码替换
    replace({ ... }),
    
    // 5. 代码优化
    terser(),
    
    // 6. 输出处理
    visualizer(),
    
    // 7. 开发工具
    serve(),
    livereload()
  ]
};

插件顺序原则 #

  1. 解析类插件:resolve、commonjs、alias
  2. 转换类插件:babel、typescript、vue、postcss
  3. 优化类插件:terser、replace
  4. 输出类插件:copy、visualizer
  5. 开发类插件:serve、livereload

插件调试 #

启用调试日志 #

javascript
function debugPlugin() {
  return {
    name: 'debug',
    resolveId(source, importer) {
      console.log(`[resolveId] ${source} from ${importer}`);
      return null;
    },
    load(id) {
      console.log(`[load] ${id}`);
      return null;
    },
    transform(code, id) {
      console.log(`[transform] ${id} (${code.length} bytes)`);
      return null;
    }
  };
}

使用 this.error 和 this.warn #

javascript
function myPlugin() {
  return {
    name: 'my-plugin',
    transform(code, id) {
      if (code.includes('eval(')) {
        this.warn('eval() is not recommended', { line: 1, column: 0 });
      }
      
      if (code.includes('dangerousFunction')) {
        this.error('dangerousFunction is not allowed', { line: 1, column: 0 });
      }
      
      return null;
    }
  };
}

下一步 #

现在你已经掌握了 Rollup 插件系统,接下来学习 高级特性 了解代码分割、动态导入等高级功能!

最后更新:2026-03-28