import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import babel from '@rollup/plugin-babel'; import terser from '@rollup/plugin-terser'; import { readFileSync, existsSync } from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { glob } from 'glob'; // 获取当前文件所在目录的绝对路径 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 读取 package.json,用于获取依赖列表 const pkg = JSON.parse(readFileSync('./package.json', 'utf-8')); /** * 收集所有的入口文件 * * 遍历指定目录,查找所有 .js 文件作为打包入口 * 注意:json 目录被排除,因为 JSON 文件直接复制,不作为入口处理 * * @returns {Object} 入口文件对象,格式:{ 'components/Table/index.js': '/absolute/path/Table/index.js' } */ function getEntryFiles() { const entries = {}; const baseDir = __dirname; const srcDir = path.join(baseDir, 'src'); // 定义需要处理的目录(排除json,json文件直接复制不转换) const dirs = ['components', 'hooks', 'utils', 'regular', 'enum']; dirs.forEach(dir => { const dirPath = path.join(srcDir, dir); if (existsSync(dirPath)) { // 递归查找当前目录下所有 .js 文件 const files = glob.sync('**/*.js', { cwd: dirPath, // 在 src/xxx 目录中查找 absolute: false // 返回相对路径 }); // 为每个文件创建入口映射 files.forEach(file => { // key: 保留源文件的相对路径(包括目录和文件名),如 'components/Table/index.js' // value: src 目录下文件的绝对路径 const key = path.join(dir, file); entries[key] = path.join(srcDir, dir, file); }); } }); return entries; } /** * Babel 配置 * * 只转换 JSX 语法,不转译 ES6+ 语法 * 这样可以保持代码的现代性,减少构建时间 */ const babelConfig = { // 将 Babel helpers 捆绑到每个文件中(避免重复引用) babelHelpers: 'bundled', // 排除 node_modules 目录,不进行处理 exclude: 'node_modules/**', // 支持的文件扩展名 extensions: ['.js', '.jsx', '.ts', '.tsx'], // 使用的预设和插件 presets: [ ['@babel/preset-react', { // 使用新的 JSX 转换方式,自动导入 JSX runtime: 'automatic' }] ], plugins: [] }; /** * 自定义插件:复制类型声明文件和样式文件 * * 功能: * 1. 复制 .d.ts 类型声明文件 * 2. 复制 .less/.css 等样式文件(保持原始格式) * 3. 复制图片文件 (.png, .jpg, .jpeg, .gif, .svg, .webp) * 4. 复制 .json 数据文件(保持原始格式) * 5. 复制 css 文件夹 * * 注意:所有源文件都在 src/ 目录,构建输出到根目录 */ const copyTypesPlugin = () => ({ name: 'copy-types', // 在生成 bundle 时执行 generateBundle() { const srcDir = path.join(__dirname, 'src'); const dirs = ['components', 'hooks', 'utils', 'regular', 'enum', 'json']; const this$1 = this; // 处理每个目录 dirs.forEach(dir => { const dirPath = path.join(srcDir, dir); if (existsSync(dirPath)) { // ===== 1. 复制类型声明文件 (.d.ts) ===== const dtsFiles = glob.sync('**/*.d.ts', { cwd: dirPath, absolute: true }); dtsFiles.forEach(file => { const relativePath = path.relative(dirPath, file); const content = readFileSync(file, 'utf-8'); // 将文件添加到根目录 this$1.emitFile({ type: 'asset', fileName: path.join(dir, relativePath), source: content }); }); // ===== 2. 复制样式文件 (.less, .css, .scss, .sass) ===== const styleFiles = glob.sync('**/*.{less,css,scss,sass}', { cwd: dirPath, absolute: true }); styleFiles.forEach(file => { const relativePath = path.relative(dirPath, file); const content = readFileSync(file, 'utf-8'); // 将样式文件添加到根目录,保持原始格式 this$1.emitFile({ type: 'asset', fileName: path.join(dir, relativePath), source: content }); }); // ===== 3. 复制图片文件 (.png, .jpg, .jpeg, .gif, .svg, .webp) ===== const imageFiles = glob.sync('**/*.{png,jpg,jpeg,gif,svg,webp}', { cwd: dirPath, absolute: true }); imageFiles.forEach(file => { const relativePath = path.relative(dirPath, file); // 读取二进制文件,明确不使用编码 const content = readFileSync(file, null); // 将图片文件添加到根目录,保持二进制格式 this$1.emitFile({ type: 'asset', fileName: path.join(dir, relativePath), source: content }); }); } }); // ===== 4. 复制 json 文件夹(保持原始格式,不转换) ===== const jsonDir = path.join(srcDir, 'json'); if (existsSync(jsonDir)) { const jsonFiles = glob.sync('**/*.json', { cwd: jsonDir, absolute: true }); jsonFiles.forEach(file => { const relativePath = path.relative(jsonDir, file); const content = readFileSync(file, 'utf-8'); // 将 JSON 文件添加到根目录 this$1.emitFile({ type: 'asset', fileName: path.join('json', relativePath), source: content }); }); } // ===== 5. 复制 css 文件夹 ===== const cssDir = path.join(srcDir, 'css'); if (existsSync(cssDir)) { const cssFiles = glob.sync('**/*', { cwd: cssDir, absolute: true, nodir: true // 不包括目录本身 }); cssFiles.forEach(file => { const relativePath = path.relative(cssDir, file); const content = readFileSync(file, 'utf-8'); this$1.emitFile({ type: 'asset', fileName: path.join('css', relativePath), source: content }); }); } } }); /** * Rollup 插件列表 * * 按照执行顺序排列: * 1. resolve - 解析模块路径 * 2. babel - 转换 JSX * 3. commonjs - 转换 CommonJS 模块 * 4. json - 解析 JSON 导入(但被标记为 external,不会实际转换) * 5. copyTypesPlugin - 复制类型、样式和图片文件 * 6. terser - 代码压缩 */ const plugins = [ // 模块解析插件 - 解析 node_modules 中的模块 resolve({ extensions: ['.js', '.jsx', '.ts', '.tsx'], browser: true // 浏览器环境优先 }), // Babel 转换插件 - 只转换 JSX babel(babelConfig), // CommonJS 转换插件 - 允许导入 CommonJS 模块 commonjs({ transformMixedEsModules: true // 转换混合的 ES 模块 }), // JSON 插件 - 处理 JSON 导入(配合 external 使用) json(), // 自定义插件 - 复制类型和样式文件 copyTypesPlugin(), // 代码压缩插件 terser({ compress: { drop_console: false, // 保留 console drop_debugger: true, // 移除 debugger pure_funcs: [], // 不移除任何函数调用 passes: 2 // 压缩次数 }, format: { comments: false, // 移除注释 beautify: false // 不美化代码,保持压缩格式 }, ecma: 2015, // 目标 ECMAScript 版本 module: true, // 保留 ES 模块格式 toplevel: false // 不压缩顶层作用域 }) ]; /** * External 函数 * * 判断模块是否应该标记为"外部依赖" * 外部依赖不会被打包进 bundle,而是保留 import 语句 * * @param {string} id - 模块标识符 * @returns {boolean} - true 表示外部依赖,不打包 */ const external = (id) => { // 1. 样式文件标记为外部 -> 保留 import 语句 // 原因:样式文件需要消费者项目配置自己的加载器来处理 // 保持 Less 变量(如 @{ant-prefix})不被替换 if (id.endsWith('.less') || id.endsWith('.css') || id.endsWith('.scss') || id.endsWith('.sass')) { return true; } // 2. JSON 文件标记为外部 -> 直接使用原始 .json 文件 // 原因:避免将 JSON 转换成 JS 模块,保持原始格式 // 例如:35MB 的 area.json 不会被转换成 56MB 的 area.json.js if (id.endsWith('.json')) { return true; } // 3. 图片文件标记为外部 -> 保留 import 语句,直接使用原始文件 // 原因:图片文件会通过 copyTypesPlugin 复制到输出目录 if (id.endsWith('.png') || id.endsWith('.jpg') || id.endsWith('.jpeg') || id.endsWith('.gif') || id.endsWith('.svg') || id.endsWith('.webp')) { return true; } // 4. npm 依赖包标记为外部 -> 不打包到 bundle 中 // 原因:减少 bundle 体积,避免版本冲突,让消费者项目自己管理依赖 // 从 package.json 的 dependencies 中读取依赖列表 const dependencies = Object.keys(pkg.dependencies || {}); return dependencies.some(dep => id === dep || id.startsWith(dep + '/')); }; /** * 创建 Rollup 配置的辅助函数 * * @param {string} outputDir - 输出目录 * @param {string} format - 输出格式('esm' 或 'cjs') * @param {Array} plugins - 插件列表 * @returns {Object} Rollup 配置对象 */ function createConfig(outputDir, format, plugins) { return { // 入口文件对象 input: getEntryFiles(), // 输出配置 output: { dir: outputDir, // 输出目录 format: format, // 输出格式:'esm' 或 'cjs' preserveModules: true, // 保留模块结构,不打包成单个文件 preserveModulesRoot: './', // 保留模块结构的根目录 entryFileNames: '[name]', // 入口文件名:[name] 保留原始文件名(包括 .js 后缀) exports: 'named' // 导出命名导出 }, // 外部依赖判断函数 external, // 插件列表 plugins }; } /** * 导出配置 * * 只输出 ESM 格式到根目录(components/, hooks/, utils/ 等) */ export default createConfig('.', 'esm', plugins);