先把 Rollup 搞明白,再去学 Vite!

概念

  • **Rollup 是一个 ES 模块打包器(bundler)**,擅长把多个 ESModule 打包成一个或多个 bundle,目标常是库(library)或前端应用的生产构建。

核心理念是 ​基于 ES Module 的静态分析​:Rollup 能更精准做 tree-shaking(删除未使用导出),产出的 bundle 更小、更"纯净"。

Rollup 的打包结果更适合做库发布(输出 ESM/CJS/UMD),但在前端应用上(尤其配合 plugin)也非常强大(vite 打包器)。

Rollup 与其它 bundler 的定位对比

  • Webpack:功能齐全、插件生态丰富、面向应用(复杂的 code-splitting、loader )。
  • Rollup:更注重静态 ESM 分析和生成更小的库包,tree-shaking 更精准。用于库开发或作为生产打包(如 Vite 的 rollup 阶段)。
  • esbuild / swc:超快的编译/转译工具,但打包能力、插件生态不如 Rollup 全面(可结合使用:esbuild 做转译,Rollup 做打包/优化 => vite)。

Rollup 工作流程

  1. **输入(entry)**:指定入口文件。
  2. 解析依赖 :基于 ES Module 静态分析 import/export,并通过插件解析 node_modules(@rollup/plugin-node-resolve)、 CommonJS(@rollup/plugin-commonjs)等。
  3. 转换:插件可对模块做转换(TS -> JS、JSX -> JS、替换环境变量等)。
  4. 合并与静态分析:Rollup 构建模块图,决定哪些导出被引用(用于 Tree-shaking)。
  5. 生成 chunks / assets:按配置输出 JS chunk、CSS / images 等静态资产。
  6. 压缩 / 产出 sourcemap:可用 terser 等做最终压缩。

常用核心配置项(rollup.config.js)

JavaScript 复制代码
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser';

export default {
  input: 'src/index.js',        // 入口
  external: ['react'],         // 不打包的外部依赖(库通常会标记 peerDependencies)
  plugins: [
    resolve(),                 // 解析 node_modules 中的包
    commonjs(),                // 转换 CommonJS -> ESM
    // 其他插件:babel/typescript/esbuild/postcss 等
    terser(),                  // 压缩(只在 prod)
  ],
  output: [
    {
      file: 'dist/index.esm.js',
      format: 'es',            // ESM
      sourcemap: true,
    },
    {
      file: 'dist/index.cjs.js',
      format: 'cjs',           // CommonJS
      sourcemap: true,
    }
  ]
};

输出格式(output.format)说明

  • es / esm:ES Module,现代打包器或 Node(>=支持 ESM)使用。推荐用于库的模块化输出。
  • cjs:CommonJS,用于 Node 或老式打包消费者。
  • umd:通用模块定义(UMD),适合直接在浏览器通过 <script> 使用(通常需要 name + globals)。
  • iife:立即调用函数表达式,单文件浏览器脚本(不依赖模块加载器)。
  • amd / system:很少用,分别为 AMD 和 SystemJS 格式。

如果做库,一般输出 es + cjs(并可额外输出 umd 做浏览器兼容)。

external 与 globals

  • external:告诉 Rollup 哪些模块应视为外部依赖,不会被打包进 bundle(通常对 library 很重要)。例:external: ['react', 'react-dom']
  • output.globals:当 output.format 为 umd/iife 时,需要指定外部包在浏览器下的全局变量名,例如 { react: 'React' }

Tree-shaking(摇树优化)

  • Rollup 基于 ESM 的静态结构判断哪些导出可删;相比基于 AST 的后处理工具,Rollup 的精度很高。
  • 重要配置:
    • treeshake: true | { moduleSideEffects: boolean | string[], propertyReadSideEffects: boolean }
    • moduleSideEffects 对应 package.json 的 sideEffects 字段:正确声明能让 Rollup 更激进地移除无用模块。
  • 建议 :在 package.json 中维护 sideEffects,对于不会产生副作用的文件或包标注为 false(或列出那些有副作用的文件)。

Code-Splitting(代码分割)

  • Rollup 支持多入口和动态 import() 以生成多个 chunk。
  • 常用配置:
    • input 可以是数组或对象:input: { main: 'src/main.js', admin: 'src/admin.js' },会生成多个入口 chunk。
    • output.manualChunks:手动控制 chunk 划分(把大型依赖拆出 vendor chunk 等)。

示例 manualChunks

JavaScript 复制代码
output: {
  dir: 'dist',
  format: 'esm',
  manualChunks(id) {
    if (id.includes('node_modules')) {
      return 'vendor';
    }
  }
}

插件生态(常用插件)

基本解析/转换:

  • @rollup/plugin-node-resolve:解析 node_modules,支持 browser, extensions 等选项。
  • @rollup/plugin-commonjs:把 CommonJS 模块转为 ESM(一定要放在 resolve 之后)。
  • @rollup/plugin-json:允许 import data from './data.json'

编译/转译:

  • @rollup/plugin-babel:Babel 转译(配合 preset-env)。
  • @rollup/plugin-typescript:官方的 TS 支持(但常用 rollup-plugin-typescript2,因为更成熟并支持 declaration 输出)。
  • rollup-plugin-esbuild / @rollup/plugin-esbuild:使用 esbuild 做超快转译/TS -> JS(推荐用于提高构建速度)。

其他常用:

  • rollup-plugin-terser:压缩 JS。
  • @rollup/plugin-replace:替换变量(例如 process.env.NODE_ENV)。
  • @rollup/plugin-alias:路径别名。
  • rollup-plugin-postcss:处理 CSS(支持 CSS Modules、extract)。
  • rollup-plugin-url:把小图片/字体转 base64,或复制为 assets。
  • rollup-plugin-visualizer:生成 bundle 可视化报告(helpful)。
  • rollup-plugin-serve / rollup-plugin-livereload:开发时的本地服务(轻量)。

这样排序更好理解 ​:通常为 replacealiasresolvecommonjsesbuild/babel/tspostcssterser

尤其 resolve 要在 commonjs 之前,commonjs 需要转换解析到的包。

Vite 兴起

Vite 把「开发时​不打包 ​、直接利用浏览器原生 ESM + 用 esbuild 做依赖预处理」和「生产构建使用 Rollup 做高质量打包」结合起来,既实现了极快的 dev 启动与 HMR,又保留了 Rollup 的生产打包能力与插件生态。(vitejs)

  1. 开发模式(dev server)是"按模块服务"
    1. Vite 在 dev 模式下**不把应用打包成一个大包(对比 webpack)**,而是把每个源码文件按 ESM 模块原样通过开发服务器直接发给浏览器,浏览器负责模块解析、按需加载。这样冷启动无需预构建整个项目,极大缩短启动时间并保持 HMR 快速。
  2. 依赖预打包(Dependency pre-bundling)用 esbuild
    1. 第一次启动时,Vite 会用 esbuildnode_modules 中的依赖预打包为能被浏览器高效加载的 ESM 格式(放到 .vite/deps 目录),解决了 CommonJS、包内部不能直接以原始形式按原生 ESM 加载的问题,同时提升加载/解析速度。该步骤只是 dev-only 优化。
  3. HMR(Hot Module Replacement)基于原生 ESM + websocket
    1. Vite 利用浏览器的 ESM 单模块热替换能力,只替换实际变更的模块并广播更新,避免全页刷新;框架层(比如 React/Vue)会处理局部状态的保持。Vite 提供 import.meta.hot API 供框架或用户控制 HMR 行为。
  4. 生产构建使用 Rollup
    1. 上面说的都是开发环境,Vite 的 build 阶段使用 Rollup 作为打包器(利用 Rollup 精准的 tree-shaking、code-splitting、和插件能力),并结合 esbuild 做快速的转译/压缩选项(或通过 terser 等 cn.vite.dev/config/buil...
  5. 插件系统基于 Rollup 插件接口扩展
    1. Vite 的插件接口是 Rollup 插件的超集,很多 Rollup 插件可以复用,但不是所有 Hook 在 dev(无 bundling)场景下都适用,所以存在兼容性考虑(有 enforce: 'pre'|'post' 来调整调用顺序,插件作者一般考虑,大多数无需我们开发者考虑)。

Vite 核心配置(vite.config.ts)

JavaScript 复制代码
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import path from 'path';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import viteCompression from 'vite-plugin-compression'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import viteSvgLoader from 'vite-svg-loader';
import legacy from '@vitejs/plugin-legacy';
import { visualizer } from 'rollup-plugin-visualizer';

// 可以传递一个函数(需要额外参数的情况下),也可以直接传递一个对象
export default defineConfig(({ command, mode }) => {
  const isDev = command === 'serve';
  const isProd = command === 'build';

  return {
    root: process.cwd(),
    base: '/', // 发布时根据需求改为 CDN 地址或相对路径
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'src'),
        '~': path.resolve(__dirname, 'src'),
      },
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
    },

    plugins: [ // 控制预打包哪些依赖(有问题的 CJS 包需要手动 include/exclude)
      // Vue3 SFC 支持
      vue(),

      // Vue3 JSX 支持
      vueJsx(),

      // SVG 文件直接当 Vue 组件使用(适合小图标)
      viteSvgLoader(),

      // svg-sprite(按需合并 svg,适合图标库)
      createSvgIconsPlugin({
        // 制定需要缓存的图标文件夹
        iconDirs: [path.resolve(process.cwd(), 'src/icons')],
        symbolId: 'icon-[dir]-[name]',
        svgoOptions: isProd, // 生产时进行 svgo 优化
      }),
      
      // 压缩
      viteCompression({
        algorithm: 'gzip',
        ext: '.gz',
        threshold: 10240 // 10kB 以上才压缩
      }),
      viteCompression({
        algorithm: 'brotliCompress',
        ext: '.br',
        threshold: 10240
      })

      // 自动按需导入 Vue Composition API / Vue Router / Pinia 等
      AutoImport({
        imports: [
          'vue',
          'vue-router',
          'pinia',
          {
            // 你可以在这里自己添加包的自动导入
            axios: [
              ['default', 'axios']
            ]
          }
        ],
        dts: 'src/auto-imports.d.ts',
        eslintrc: {
          enabled: true, // 生成 .eslintrc-auto-import.json(可选)
          filepath: './.eslintrc-auto-import.json',
          globalsPropValue: true
        }
      }),

      // 自动注册组件(按需加载 UI 组件库,例如 Element Plus)
      Components({
        dirs: ['src/components'],
        extensions: ['vue', 'md'],
        dts: 'src/components.d.ts',
        resolvers: [
          // 例:Element Plus 自动按需引入
          ElementPlusResolver()
        ]
      }),

      // legacy 支持(如果需要支持旧浏览器)
      legacy({
        targets: ['defaults', 'not IE 11'] // 根据项目兼容性调整
      }),

      // 在生产构建时生成 bundle 分析报告(可选)
      isProd && visualizer({
        filename: './dist/stats.html',
        open: false,
        gzipSize: true
      })
    ].filter(Boolean),

    // 开发服务器配置
    server: {
      host: true, // 外网可访问(容器/VM 开发时有用)
      port: 5173,
      open: false,
      strictPort: false,
      hmr: {
        overlay: true
      },
      proxy: {
        // 示例:开发代理转发 /api 到后端
        '/api': {
          target: 'http://127.0.0.1:7001',
          changeOrigin: true,
          rewrite: (p) => p.replace(/^\/api/, ''),
          secure: false,
          ws: true
        }
      },
      fs: {
        // 如果是 monorepo,需要允许访问工作区外的文件夹
        allow: [path.resolve(__dirname, '.')],
      }
    },

    // 依赖预构建,控制预打包哪些依赖(有问题的 CJS 包需要手动 include/exclude)
    optimizeDeps: {
      // 生产预构建会使用 esbuild,把 node_modules 中的包转为 ESM,提高 dev 性能
      include: [
        'vue',
        'vue-router',
        'pinia',
        'axios',
        'lodash-es'
      ],
      // 如果某些包有问题,可排除,强制让 Rollup 在 build 时处理
      exclude: [
        // 'some-cjs-only-package'
      ],
      esbuildOptions: {
        // 如果你需要注入 polyfill 或 target
        target: 'es2018',
      }
    },

    // 全局定义、构建选项
    define: {
      __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
      'process.env': {}
    },

    // CSS 相关配置(预处理器、模块化等)
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@import "@/styles/variables.scss";`
        }
      },
      modules: {
        // CSS Modules 命名规则
        generateScopedName: isProd ? '[hash:base64:8]' : '[name]__[local]__[hash:base64:5]'
      },
      postcss: {
        // 可配置 autoprefixer / postcss-preset-env 等
      }
    },

    // 构建(生产)相关,底层使用 Rollup
    build: {
      target: 'es2018',
      outDir: 'dist',
      assetsDir: 'assets',
      sourcemap: false,
      minify: 'esbuild', // esbuild(默认值) 更快;如果想压缩得更小可改为 'terser'
      cssCodeSplit: true,
      rollupOptions: {
        // external 可用于库模式,应用一般不用 external
        // external: ['vue'],
        output: {
          // 手动分包:把 node_modules 全部算作 vendor
          manualChunks(id) {
            if (id.includes('node_modules')) {
              // 可以按 lib 名拆分 vendor(例如 element-plus 单独 chunk)
              if (id.includes('element-plus')) return 'vendor_elementplus';
              if (id.includes('vue')) return 'vendor_vue';
              return 'vendor';
            }
          },
          entryFileNames: 'assets/js/[name]-[hash].js',
          chunkFileNames: 'assets/js/chunks/[name]-[hash].js',
          assetFileNames: (assetInfo) => {
              // 图片文件
              if (/\.(png|jpe?g|svg|gif|tiff|bmp|ico|webp)$/i.test(assetInfo.names[0] || '')) {
                return `assets/images/[name]-[hash][extname]`
              }
    
              // 字体文件
              if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.names[0] || '')) {
                return `assets/fonts/[name]-[hash][extname]`
              }
    
              // CSS 文件
              if (/\.css$/i.test(assetInfo.names[0] || '')) {
                return `assets/css/[name]-[hash][extname]`
              }
    
              // 其他资源文件
              return `assets/[name]-[hash][extname]`
          },
        }
      },
      // 生产构建的并行度控制、chunk 大小限制等
      brotliSize: true,
      // chunkSizeWarningLimit: 1500,
    },

    // SSR / library 等高级模式可以在这里配置
    // ssr: { noExternal: ['some-esm-only-package'] },

    // 测试(如果使用 vitest)
    test: {
      globals: true,
      environment: 'jsdom'
    }
  };
});
```t resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser';

export default {
  input: 'src/index.js',        // 入口
  external: ['react'],         // 不打包的外部依赖(库通常会标记 peerDependencies)
  plugins: [
    resolve(),                 // 解析 node_modules 中的包
    commonjs(),                // 转换 CommonJS -> ESM
    Other plugins: babel/typescript/esbuild/postcss, etc.
terser(),t resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser';

export default {
  input: 'src/index.js',        // 入口
  external: ['react'],         // 不打包的外部依赖(库通常会标记 peerDependencies)
  plugins: [
    resolve(),                 // 解析 node_modules 中的包
    commonjs(),                // 转换 CommonJS -> ESM
    Other plugins: babel/typescript/esbuild/postcss, etc.

import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser';

export default {
  input: 'src/index.js',        // 入口
  external: ['react'],         // 不打包的外部依赖(库通常会标记 peerDependencies)
  plugins: [
    resolve(),                 // 解析 node_modules 中的包
    commonjs(),                // 转换 CommonJS -> ESM
    Other plugins: babel/typescript/esbuild/postcss, etc.
terser(),
相关推荐
鲨叔1 小时前
zustand 从原理到实践 - 原理篇(2)
前端·react.js
之恒君1 小时前
PromiseResolveThenableJobTask 的在Promise中的使用
javascript·promise
勤劳打代码1 小时前
追本溯源 —— SetState 刷新做了什么
flutter·面试·性能优化
骨子里的偏爱1 小时前
uniapp实现数据存储到本地文件,除非卸载app,否则数据一直存在
javascript·chrome·uni-app
狐篱1 小时前
vite 和 webpack 项目使用wasm-pack 生成的 npm 包
前端·webassembly
閞杺哋笨小孩1 小时前
内容平台-SEO 索引提交
前端·seo
苏打水com1 小时前
HTML/CSS 核心考点详解(字节跳动 ToB 中台场景)
java·前端·javascript
jingling5551 小时前
react | 从零开始:使用 Create React App 创建你的第一个 React 项目
前端·javascript·react.js
nnnnna1 小时前
watch监听(一篇文章彻底搞懂watch监听)
前端·vue.js