如何为子路径组件配置Rollup打包工具
问题描述
我是Rollup新手,希望打包组件以在多个项目中复用。我的文件结构如下:
src/ ├─ Button/ │ ├─ Button.tsx │ ├─ Button.css │ ├─ GreenButton.tsx │ ├─ GreenButton.css │ ├─ BlueButton.tsx │ └─ BlueButton.css ├─ Dropdown/ │ ├─ Dropdown.tsx │ └─ Dropdown.css └─ ...
我不想将所有组件打包到单个index.js中,目标是创建可通过my-package/Button方式导入的组件包。
我已尝试通过遍历文件生成Rollup入口的方案,但遇到以下问题:
- JSX保留问题:设置
jsx: "preserve"后,外部组件导入被错误修改,出现未使用警告; - 导入处理问题:不清楚如何配置外部库、内部组件的排除规则,是否需要重命名内部组件导入;
- 子模块
package.json生成:不知道如何为每个组件自动生成指定结构的package.json。
附当前rollup.config.js代码:
import typescript from "@rollup/plugin-typescript"; import jsx from 'acorn-jsx'; import postcss from "rollup-plugin-postcss"; import path from 'path'; import * as fs from 'fs'; import { fileURLToPath } from 'url'; const options = { inDir: "./src", outDir: "./dist", include: [/.*\\.*\.[jt]sx?$/], exclude: [/\.test\./, /\.d\.ts$/] } function getFiles(basePath, relPath, include, exclude){ return fs.readdirSync(path.resolve(basePath, relPath), {withFileTypes: true}).reduce((list, element) => { const relElement = path.join(relPath, element.name); if(exclude && exclude.some(exclude => exclude.test(relElement))){ return list; } if(element.isDirectory()){ return list.concat(getFiles(basePath, relElement, include, exclude)); } if(include.some(include => include.test(relElement))){ return list.concat(relElement); }; return list; },[]); } const __dirname = path.dirname(fileURLToPath(import.meta.url)); let modules = getFiles(path.resolve(__dirname, options.inDir), "./", options.include, options.exclude); function convertExt(ext){ switch(ext){ case ".tsx": return ".jsx"; case ".ts": return ".js"; default: return ext; } } function createRollupEntry(module){ let ext = path.extname(module); let file = path.basename(module, ext); let directory = path.dirname(module); let folder = path.basename(directory); let outputExt = convertExt(ext); let input = path.join(options.inDir, module); let output = path.join(options.outDir, directory, file === folder ? "" : "file", file + outputExt); let rollupConfig = { input: input, output: { file: output, format: "esm" }, plugins: [ postcss({ extract: true }) ], external: id => !/.css$/.test(id) } if(outputExt === ".jsx") { rollupConfig.acornInjectPlugins = [jsx()]; } if(/^\.tsx?$/.test(ext)){ rollupConfig.plugins.push( typescript({ tsconfig: "./tsconfig.json", include: input, compilerOptions: { jsx: "preserve", declaration: true } }) ) } return rollupConfig; } export default modules.filter((_, index) => index < 1).map(element => { let entry = createRollupEntry(element); console.log(entry); return entry; });
解决方案
1. 修复JSX保留问题
设置jsx: "preserve"后,TypeScript不会转译JSX,但局部配置覆盖全局tsconfig会导致导入解析异常。调整以下两点:
- 移除
acornInjectPlugins手动配置,@rollup/plugin-typescript已内置JSX支持,无需额外注入; - 取消TypeScript插件的
include: input配置,让插件基于全局tsconfig.json处理所有文件,避免局部规则破坏导入逻辑。
修改createRollupEntry中的TypeScript插件配置:
typescript({ tsconfig: "./tsconfig.json", compilerOptions: { jsx: "preserve", declaration: true, declarationDir: path.join(options.outDir, directory) // 类型文件输出到对应组件目录 } })
同时在项目根目录tsconfig.json中补充配置:
{ "compilerOptions": { "module": "ESNext", "target": "ESNext", "jsx": "preserve", "strict": true, "declaration": true, "moduleResolution": "NodeNext" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }
2. 配置外部库与内部组件导入规则
外部库处理
external配置需要区分外部依赖和内部组件:
- 匹配
react、react-dom等第三方包名,标记为外部; - 内部组件用相对导入,无需标记为外部,Rollup会自动处理。
修改rollupConfig中的external:
external: id => // 匹配React类第三方包 /^react($|\/)/.test(id) || /^react-dom($|\/)/.test(id) || // 排除内部相对/绝对路径导入 !id.startsWith('.') && !path.isAbsolute(id)
内部组件导入处理
内部组件直接使用相对路径导入(如import Button from './Button')即可,不需要重命名。Rollup打包时会保留这些路径,确保子模块间引用正常。
3. 自动生成子模块package.json
要实现my-package/Button的导入方式,需在每个组件目录下生成package.json指定入口。可以通过Rollup自定义插件实现:
在rollup.config.js中添加生成插件:
function createPackageJsonPlugin(directory, fileName) { return { name: 'generate-package-json', writeBundle() { const componentName = path.basename(directory); const packageJsonContent = { name: `my-package/${componentName}`, main: `${fileName}.jsx`, module: `${fileName}.jsx`, types: `${fileName}.d.ts` }; const packageJsonPath = path.join(options.outDir, directory, 'package.json'); fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent, null, 2)); } }; }
然后在rollupConfig.plugins中引入该插件:
rollupConfig.plugins.push(createPackageJsonPlugin(directory, file));
完整修改后的rollup.config.js
import typescript from "@rollup/plugin-typescript"; import postcss from "rollup-plugin-postcss"; import path from 'path'; import * as fs from 'fs'; import { fileURLToPath } from 'url'; const options = { inDir: "./src", outDir: "./dist", include: [/.*\.[jt]sx?$/], exclude: [/\.test\./, /\.d\.ts$/] } function getFiles(basePath, relPath, include, exclude){ return fs.readdirSync(path.resolve(basePath, relPath), {withFileTypes: true}).reduce((list, element) => { const relElement = path.join(relPath, element.name); if(exclude && exclude.some(exclude => exclude.test(relElement))){ return list; } if(element.isDirectory()){ return list.concat(getFiles(basePath, relElement, include, exclude)); } if(include.some(include => include.test(relElement))){ return list.concat(relElement); }; return list; },[]); } const __dirname = path.dirname(fileURLToPath(import.meta.url)); let modules = getFiles(path.resolve(__dirname, options.inDir), "./", options.include, options.exclude); function convertExt(ext){ switch(ext){ case ".tsx": return ".jsx"; case ".ts": return ".js"; default: return ext; } } function createPackageJsonPlugin(directory, fileName) { return { name: 'generate-package-json', writeBundle() { const componentName = path.basename(directory); const packageJsonContent = { name: `my-package/${componentName}`, main: `${fileName}.jsx`, module: `${fileName}.jsx`, types: `${fileName}.d.ts` }; const packageJsonPath = path.join(options.outDir, directory, 'package.json'); fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent, null, 2)); } }; } function createRollupEntry(module){ let ext = path.extname(module); let file = path.basename(module, ext); let directory = path.dirname(module); let outputExt = convertExt(ext); let input = path.join(options.inDir, module); let output = path.join(options.outDir, directory, file + outputExt); let rollupConfig = { input: input, output: { file: output, format: "esm" }, plugins: [ postcss({ extract: true, filename: `${file}.css` }), createPackageJsonPlugin(directory, file) ], external: id => /^react($|\/)/.test(id) || /^react-dom($|\/)/.test(id) || !id.startsWith('.') && !path.isAbsolute(id) } if(/^\.tsx?$/.test(ext)){ rollupConfig.plugins.push( typescript({ tsconfig: "./tsconfig.json", compilerOptions: { jsx: "preserve", declaration: true, declarationDir: path.join(options.outDir, directory) } }) ) } return rollupConfig; } export default modules.map(element => createRollupEntry(element));
内容的提问来源于stack exchange,提问作者Nico Richter
相关产品推荐
相关产品推荐

