Rollup 和 React-如何分离组件包?
我目前正在尝试为 React 构建一个 UI 库,但遇到了一些麻烦。目前我正在使用 typescript 和 rollup,我可以捆绑单个
index.js
,并且可以导入这些组件,但它正在导入整个库。
目前 :
文件结构:
src
--components
-----button
-------button.tsx
-------button.types.ts
-----input
-------input.tsx
-------input.types.ts
-----index.ts
rollup.js
我的 rollup 目标是
index.ts
,其中所有内容都导出如下:
export { default as Button} from './button/button'
export { default as Input } from './input/input'
并且我能够像这样在 react 项目中导入:
import { Button, Input } from 'my-library'
我想要做什么 我希望每个组件都单独捆绑,然后像这样导入它们
import { Input } from 'my-library/input'
import { Button } from 'my-library/button'
我尝试过的:
阅读文档后,似乎
preserveModule: true
就是我想要的正在寻找,但随后我尝试按上述方法导入,但它开始抱怨没有找到任何内容。
我当前的 rollup.js 如下所示:
export default {
input: 'src/index.ts',
output: [
{
exports: 'named',
dir: 'build/',
format: 'esm',
sourcemap: true,
preserveModules: true,
},
],
plugins: [
cleaner({ targets: ['./build'] }),
peerDepsExternal(),
resolve(),
commonjs(),
terser(),
typescript({
exclude: ['**/*.stories.tsx', '**/*.test.tsx'],
}),
],
};
编辑:我在 Medium 上发布了一个更全面的教程 这里
我尝试使用preserveModules,但它不会为每个组件生成index.js文件,因此我可以像这样导入:
从'lib/Button'导入按钮
因此,我想出了一个解决方法,让rollup循环遍历我的src文件夹,为我在rootDir的src中的每个组件文件夹生成一个带有入口点的文件夹
- 维护一个严格的文件夹结构,为每个组件文件夹设置入口点。除了src文件夹中没有文件夹的index.ts之外,不要有松散的文件。正确命名文件夹,就像您希望用户导入它一样
src 文件夹结构:
rollup.config.js
src
├── Accordion
│ ├── Accordion.tsx
│ ├── AccordionBody.tsx
│ ├── AccordionButton.tsx
│ ├── AccordionCollapse.tsx
│ ├── AccordionContext.ts
│ ├── AccordionHeader.tsx
│ ├── AccordionItem.tsx
│ ├── AccordionItemContext.ts
│ └── index.ts
├── Alert
│ ├── Alert.tsx
│ └── index.ts
├── Badge
│ ├── Badge.tsx
│ └── index.ts
├── Breadcrumb
│ ├── Breadcrumb.tsx
│ ├── BreadcrumbItem.tsx
│ └── index.ts
├── Button
│ ├── Button.tsx
│ └── index.ts
├── ButtonGroup
│ ├── ButtonGroup.tsx
│ └── index.ts
...
├── Tooltip
│ ├── Tooltip.tsx
│ ├── TooltipBox.tsx
│ └── index.ts
├── index.ts
在这种情况下,维护每个组件文件夹的入口点至关重要。我仍然为 src 文件夹维护一个入口点,以便用户仍然可以用一行从库中导入多个组件
即
import {Button, Accordion, ...} from 'lib'
- Rollup 配置
-
getFolders 返回用于导出的文件夹名称数组
-
循环遍历 getFolders 以生成每个文件夹的汇总对象。
-
对于 typescript 项目,rollup 输出的 typings 文件已经保留了文件夹结构,因此我意识到文件夹 Accordion、Button 等已经只包含 typings 文件。现在我们需要将 index.js 文件添加到其中!
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from 'rollup-plugin-typescript2';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';
const packageJson = require('./package.json');
import { getFolders } from './scripts/buildUtils';
const plugins = [
peerDepsExternal(),
resolve(),
commonjs(),
typescript({
tsconfig: './tsconfig.json',
useTsconfigDeclarationDir: true,
}),
terser()
]
const getFolders = (entry) => {
// get the names of folders and files of the entry directory
const dirs = fs.readdirSync(entry)
// do not include folders not meant for export and do not process index.ts
const dirsWithoutIndex = dirs.filter(name => name !== 'index.ts').filter(name => name !== 'utils')
// ['Accordion', 'Button'...]
return dirsWithoutIndex
}
//loop through your folders and generate a rollup obj per folder
const folderBuilds = getFolders('./src').map(folder=> {
return {
input: `src/${folder}/index.ts`,
output: {
// ensure file destination is same as where the typings are
file: `dist/${folder}/index.js`,
sourcemap: true,
exports: 'named',
},
plugins,
external: ['react', 'react-dom'],
}
})
export default [
{
input: ['src/index.ts'],
output: [
{
file: packageJson.module,
format: 'esm',
sourcemap: true,
exports: 'named',
},
],
plugins,
external: ['react', 'react-dom'],
},
...folderBuilds,
{
input: ['src/index.ts'],
output: [
{
file: packageJson.main,
format: 'cjs',
sourcemap: true,
exports: 'named',
},
],
plugins,
external: ['react', 'react-dom'],
},
];
- CJS 文件
- 最后,我还添加了 rollup 配置来生成 cjs 文件。由于大多数用户都在使用 es6 导入,因此我没有费心对 cjs 文件进行代码拆分
- “frank”构建
构建后,我运行一个脚本将 package.json、Readme 复制粘贴到 ./dist 文件夹
/* eslint-disable no-console */
const { resolve, join, basename } = require('path');
const { readFile, writeFile, copy } = require('fs-extra');
const packagePath = process.cwd();
const distPath = join(packagePath, './dist');
const writeJson = (targetPath, obj) =>
writeFile(targetPath, JSON.stringify(obj, null, 2), 'utf8');
async function createPackageFile() {
const packageData = await readFile(
resolve(packagePath, './package.json'),
'utf8'
);
const { scripts, devDependencies, ...packageOthers } =
JSON.parse(packageData);
const newPackageData = {
...packageOthers,
private: false,
typings: './index.d.ts',
main: './main.js',
module: './index.js',
};
const targetPath = resolve(distPath, './package.json');
await writeJson(targetPath, newPackageData);
console.log(`Created package.json in ${targetPath}`);
}
async function includeFileInBuild(file) {
const sourcePath = resolve(packagePath, file);
const targetPath = resolve(distPath, basename(file));
await copy(sourcePath, targetPath);
console.log(`Copied ${sourcePath} to ${targetPath}`);
}
async function run() {
try {
await createPackageFile();
await includeFileInBuild('./README.md');
// await includeFileInBuild('../../LICENSE');
} catch (err) {
console.error(err);
process.exit(1);
}
}
run();
-
最后从根目录
npm publish ./dist
这是我的 dist 文件夹最终的样子
dist
├── Accordion
│ ├── Accordion.d.ts
│ ├── AccordionBody.d.ts
│ ├── AccordionButton.d.ts
│ ├── AccordionCollapse.d.ts
│ ├── AccordionContext.d.ts
│ ├── AccordionHeader.d.ts
│ ├── AccordionItem.d.ts
│ ├── AccordionItemContext.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── Alert
│ ├── Alert.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── Badge
│ ├── Badge.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── Breadcrumb
│ ├── Breadcrumb.d.ts
│ ├── BreadcrumbItem.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── Button
│ ├── Button.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── ButtonGroup
│ ├── ButtonGroup.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
...
├── Tooltip
│ ├── Tooltip.d.ts
│ ├── TooltipBox.d.ts
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── index.d.ts
├── index.js
├── index.js.map
├── main.js
├── main.js.map
├── package.json
在 gh 上的汇总问题线程中进行大量研究后,我得到了解决方案。 以下是一些参考资料:
- Franking 构建: https://stackoverflow.com/questions/62518396/importing-from-subfolders-for-a-javascript-package#:~:text=Votes-,13,-This%20is%20possible
- 文件夹结构: https://github.com/ezolenko/rollup-plugin-typescript2/issues/136#issuecomment-792383946
- 我编写 getFolders() 的灵感来自此作者的 getFiles() https://www.codefeetime.com/post/rollup-config-for-react-component-library-with-typescript-scss/