开发者问题收集

Rollup 和 React-如何分离组件包?

2022-03-02
9836

我目前正在尝试为 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'],
        }),
    ],
};
1个回答

编辑:我在 Medium 上发布了一个更全面的教程 这里

我尝试使用preserveModules,但它不会为每个组件生成index.js文件,因此我可以像这样导入:

从'lib/Button'导入按钮

因此,我想出了一个解决方法,让rollup循环遍历我的src文件夹,为我在rootDir的src中的每个组件文件夹生成一个带有入口点的文件夹

  1. 维护一个严格的文件夹结构,为每个组件文件夹设置入口点。除了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'

  1. 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'],
  },
];

  1. CJS 文件
  • 最后,我还添加了 rollup 配置来生成 cjs 文件。由于大多数用户都在使用 es6 导入,因此我没有费心对 cjs 文件进行代码拆分
  1. “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();

  1. 最后从根目录 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 上的汇总问题线程中进行大量研究后,我得到了解决方案。 以下是一些参考资料:

  1. Franking 构建: https://stackoverflow.com/questions/62518396/importing-from-subfolders-for-a-javascript-package#:~:text=Votes-,13,-This%20is%20possible
  2. 文件夹结构: https://github.com/ezolenko/rollup-plugin-typescript2/issues/136#issuecomment-792383946
  3. 我编写 getFolders() 的灵感来自此作者的 getFiles() https://www.codefeetime.com/post/rollup-config-for-react-component-library-with-typescript-scss/
hairy25
2022-03-07