1.0.0 • Published 3 years ago

xiaowei-ui11 v1.0.0

Weekly downloads
-
License
ISC
Repository
-
Last release
3 years ago

TOC

React 组件库开发

准备工作

初始化项目

新建项目文件夹并初始化

mkdir xiaowei-ui
cd xiaowei-ui
npm init -y
git init
mkdir components
touch components/index.ts # 组件入口文件

代码规范

vscode 必要插件

  • eslint
  • Prettier - Code formatter
  • stylelint

在 TypeScript 中使用 ESLint

这部分的参考文档:https://juejin.cn/post/6844903513202409485#heading-9, 大部分我都是直接引用的,写的非常好。

安装 ESLint

项目中使用 eslint 无法识别 TypeScript 的一些语法,故我们需要安装 @typescript-eslint/parser,替代掉默认的解析器, 同时也要安装typescript@typescript-eslint/eslint-plugin 它作为 eslint 默认规则的补充,提供了一些额外的适用于 ts 语法的规则。

yarn add eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin -D

创建配置文件

配置文件的名称一般是 .eslintrc.js.eslintrc.json

in .eslintrc.js

module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  rules: {
    // 这里定义规则,根据项目问题情况,按需补充即可
  }
};

在 VSCode 中集成 ESLint 检查

新增配置文件.vscode/settings.json,内容如下:

{
  "editor.formatOnSave": true,
  "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.fixAll.stylelint": true # stylelint规则自动格式化
  }
}

使用 Prettier 修复格式错误

ESLint 包含了一些代码格式的检查,比如空格、分号等。但前端社区中有一个更先进的工具可以用来格式化代码,那就是 Prettier

Prettier 聚焦于代码的格式化,通过语法分析,重新整理代码的格式,让所有人的代码都保持同样的风格。

首先需要安装 Prettier:

yarn add prettier -D

然后创建一个 prettier.config.js 文件,里面包含 Prettier 的配置项。Prettier 的配置项很少,这里我推荐大家一个配置规则,作为参考:

// prettier.config.js or .prettierrc.js
module.exports = {
  // 一行最多 100 字符
  printWidth: 100,
  // 使用 2 个空格缩进
  tabWidth: 2,
  // 不使用缩进符,而使用空格
  useTabs: false,
  // 行尾需要有分号
  semi: true,
  // 使用单引号
  singleQuote: true,
  // 对象的 key 仅在必要时用引号
  quoteProps: 'as-needed',
  // jsx 不使用单引号,而使用双引号
  jsxSingleQuote: false,
  // 末尾不需要逗号
  trailingComma: 'none',
  // 大括号内的首尾需要空格
  bracketSpacing: true,
  // jsx 标签的反尖括号需要换行
  jsxBracketSameLine: false,
  // 箭头函数,只有一个参数的时候,也需要括号
  arrowParens: 'always',
  // 每个文件格式化的范围是文件的全部内容
  rangeStart: 0,
  rangeEnd: Infinity,
  // 不需要写文件开头的 @prettier
  requirePragma: false,
  // 不需要自动在文件开头插入 @prettier
  insertPragma: false,
  // 使用默认的折行标准
  proseWrap: 'preserve',
  // 根据显示样式决定 html 要不要折行
  htmlWhitespaceSensitivity: 'css',
  // 换行符使用 lf
  endOfLine: 'lf'
};

Prettier 作为 ESLint 规则加入,这样可以在 vscode 中直接看到错误提示

yarn add eslint-plugin-prettier -D

修改 eslint 配置增加

{
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": "error",
  }
}

这样就实现了保存文件时自动格式化并且自动修复 ESLint 错误。

需要注意的是,由于 ESLint 也可以检查一些代码格式的问题,所以在和 Prettier 配合使用时,我们一般会把 ESLint 中的代码格式相关的规则禁用掉,否则就会有冲突了。

我们可以使用 eslint-config-prettier 关闭有冲突的 eslint 配置

yarn add eslint-config-prettier -D

.eslintrc.js中修改

{
...
-  "plugins": ["prettier"],
+  "extends": ["plugin:prettier/recommended"]
...
}

使用 AlloyTeam 的 ESLint 配置

ESLint 原生的规则和 @typescript-eslint/eslint-plugin 的规则太多了,而且原生的规则有一些在 TypeScript 中支持的不好,需要禁用掉。

这里我推荐使用 AlloyTeam ESLint 规则中的 TypeScript 版本,它已经为我们提供了一套完善的配置规则,并且与 Prettier 是完全兼容的(eslint-config-alloy 不包含任何代码格式的规则,代码格式的问题交给更专业的 Prettier 去处理)。

注意:这里我们不需要 eslint-config-prettier ,因为 AlloyTeamPrettier 不冲突。

yarn add eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-alloy -D

这里是最终配置.eslintrc.js

module.exports = {
  extends: ['alloy', 'alloy/react', 'alloy/typescript'],
  plugins: ['prettier'],
  env: {
    browser: true,
    jest: true
  },
  globals: {
    // 您的全局变量(设置为 false 表示它不允许被重新赋值)
    // myGlobal: false
  },
  rules: {
    'prettier/prettier': 'error'
    // 自定义您的规则
  }
};

VSCode 没有显示出 ESLint 的报错

  1. 检查「文件 => 首选项 => 设置」中有没有配置正确
  2. 检查必要的 npm 包有没有安装
  3. 检查 .eslintrc.js 有没有配置
  4. 检查文件是不是在 .eslintignore

如果以上步骤都不奏效,则可以在「文件 => 首选项 => 设置」中配置 "eslint.trace.server": "messages",按 Ctrl+Shift+U 打开输出面板,然后选择 ESLint 输出,查看具体错误。

TS 中有些类型变量定义了没有使用,ESLint 却没有报错?

因为无法支持这种变量定义的检查。建议在 tsconfig.json 中添加以下配置,使 tsc 编译过程能够检查出定义了未使用的变量:

{
    "compilerOptions": {
        "noUnusedLocals": true,
        "noUnusedParameters": true
    }
}

配置 Stylelint

我们使用 stylelint 来格式化 scss 文件

yarn add --dev stylelint stylelint-scss stylelint-config-sass-guidelines

in .stylelintrc.js

module.exports = {
  extends: ['stylelint-scss', 'stylelint-config-sass-guidelines'],
  rules: {
    'declaration-colon-space-after': 'always-single-line',
    'declaration-colon-space-before': 'never',
    'declaration-block-trailing-semicolon': 'always',
    'rule-empty-line-before': [
      'always',
      {
        ignore: ['after-comment', 'first-nested']
      }
    ]
  }
};

git hooks

代码提交前合规性检查和提交信息规范配置

husky

可以让 git hooks 变得更简单,在特定的重要动作触发自定义脚本。

yarn add husky is-ci -D

in package.json

"scripts": {
  "prepare": "is-ci || husky install"
},

lint-staged

lint-staged 在我们提交代码时,只会对修改的文件进行检查、修复处理,以保证提交的代码没有语法错误,不会影响其他伙伴在更新代码无法运行的问题。

yarn add lint-staged -D

In .lintstagedrc

{
  "*.{js,jsx,tx,tsx}": "eslint --fix",
  "*.{html,css,scss,sass}": "stylelint --fix"
}

增加 pre-commit 钩子

yarn husky add .husky/pre-commit 'yarn lint-staged --allow-empty "$1"'

commitlint

git 提交信息规范化,用来帮助我们在多人开发时,遵守 git 提交约定。

yarn add @commitlint/cli @commitlint/config-conventional commitizen cz-conventional-changelog -D

in .commitlintrc.js

module.exports = { extends: ['@commitlint/config-conventional'] };

in package.json

// ...
"scripts": {
  "commit": "git-cz",
},
"config": {
  "commitizen": {
    "path": "cz-conventional-changelog"
  }
}

增加 commit-msg 钩子

yarn husky add .husky/commit-msg 'yarn commitlint --edit "$1"'

此时可以使用 yarn commit 还代替 git commit 生成提交信息

开发与联调

我们使用Storybook进行开发联调。Storybook是用于 UI 开发的工具。 通过隔离组件,可以更快,更轻松地进行开发。 这使您可以一次处理一个组件。 您可以开发整个 UI,而无需启动复杂的开发堆栈。

安装Storybook

# 在当前项目中执行
npx sb init

修改配置文件.storybook/main.js

const path = require('path');
module.exports = {
  stories: ['../stories/**/*.stories.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    {
      name: '@storybook/addon-postcss', // 需要单独安装,转化css(自动添加前缀等),需要提供配置文件
      options: {
        postcssLoaderOptions: {
          implementation: require('postcss') // 需要单独安装
        }
      }
    }
  ],
  babel: async (options) => ({
    ...options
    // 这里重写babel配置
  }),
  webpackFinal: async (config, { configType }) => {
    // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
    // You can change the configuration based on that.
    // 'PRODUCTION' is used when building the static version of storybook.

    // Make whatever fine-grained changes you need
    config.module.rules.push({
      // 支持解析scss, 需要单独安装sass-loader、node-sass
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
      include: [path.resolve(__dirname, '../components'), path.resolve(__dirname, '../stories')]
    });

    // 这里重写webpack配置
    return config;
  }
};

移动端的样式适配

在 scss 里我们使用 vw/vh 方案进行适配。

定义适配函数

$vw-base: 750;

@function vw($px) {
  @return ($px / $vw-base) * 100vw;
}

编写样式文件

.test {
  font-size: vw(36);
  font-weight: bold;
  line-height: vw(50);
  padding-top: vw(30);
}

编写第一个组件

在 component 中新建一个文件夹 mask

类型定义

编写组件参数类型interface.ts, storybook 可以通过这里的定义自动生成属性描述,必须规范编写。

import type React from 'react';
export interface MaskProps {
  /**
   * 样式前缀
   */
  prefixCls?: string;
  /**
   * 类名
   */
  className?: string;
  /**
   * style
   */
  style?: React.CSSProperties;
  /**
   * 控制蒙层显示和隐藏
   */
  visible?: boolean;
  /**
   * 背景
   */
  backgroundColor?: string;
  /**
   * z轴层级
   */
  zIndex?: number;
  /**
   * 锁定背景不可滑动
   */
  closeBody?: boolean;
  /**
   * 神策埋点传递的名称
   */
  name?: string;
  /**
   * 点击回调, 可以在该回调中关闭蒙层
   */
  onClick?: React.MouseEventHandler<HTMLDivElement>;
}

组件逻辑

组件内容,新建mask.tsx

import React, { useEffect, useState } from 'react';
import { CSSTransition } from 'react-transition-group';

import { emptyObj, setBgScrollStatus } from '../utils/tools';
import { MaskProps } from './interface';

export const Mask: React.FC<MaskProps> = ({
  prefixCls = 'xiaowei-ui',
  className = '',
  style = {},
  backgroundColor,
  zIndex,
  visible = false,
  closeBody = true,
  onClick = () => {},
  ...props
}) => {
  const [show, setShow] = useState(false);

  useEffect(() => {
    setShow(visible);
  }, [visible]);

  useEffect(() => {
    if (closeBody) {
      setBgScrollStatus(show);
    }

    return () => {
      if (closeBody) {
        setBgScrollStatus(false);
      }
    };
  }, [show, closeBody]);

  return (
    <CSSTransition in={show} timeout={300} classNames="mask">
      <div
        style={emptyObj({ ...style, backgroundColor, zIndex })}
        onClick={onClick}
        className={`${prefixCls}-mask ${className}`}
        {...props}
      />
    </CSSTransition>
  );
};

export default Mask;

新建index.tsx导出组件

import Mask from './mask';

export default Mask;

组件样式

编写组件样style/index.scss

@import '../../style/index';

.#{$prefix-cls}-mask {
  background-color: rgba(0, 0, 0, 0.5);
  height: 0%;
  left: 0;
  overflow: hidden;
  position: fixed;
  top: 0;
  width: 100%;
  z-index: 999;

  &.mask-enter {
    height: 100%;
    opacity: 0;
  }

  &.mask-exit-done {
    height: 0;
    opacity: 0;
  }

  &.mask-enter-active {
    opacity: 1;
    transition: opacity 300ms;
  }

  &.mask-enter-done,
  &.mask-exit {
    height: 100%;
    opacity: 1;
  }

  &.mask-exit-active {
    opacity: 0;
    transition: opacity 300ms;
  }
}

新建一个 index.ts 文件导入样式, 这个文件是用来独立编译组件样式,进行按需加载时使用的。

import './index.scss';

编写 stories

编写一个 stories,stories/mask/Mask.stories.tsx

import React from 'react';
import type { Story, Meta } from '@storybook/react';

import type { MaskProps } from '../../components/mask/interface';
import { Mask } from '../../components';
import '../../components/mask/style';

const metaData: Meta = {
  title: 'Mask',
  component: Mask,
  parameters: { actions: { argTypesRegex: '^on.*' } },
  argTypes: {
    backgroundColor: { control: 'color' },
    zIndex: { control: 'number' },
    closeBody: {
      defaultValue: false,
      control: 'boolean'
    },
    style: {
      defaultValue: {
        position: 'absolute'
      },
      control: 'object'
    }
  }
};

export default metaData;

const Template: Story<MaskProps> = (args) => (
  <div style={{ width: '100%', height: '200px', position: 'relative' }}>
    <Mask {...args} />
  </div>
);

export const Basic = Template.bind({});

Basic.args = {
  visible: false
};

plop创建组件模板

  1. 安装依赖包
yarn add plop -D
  1. 准备模板文件

在当前目录下新建文件夹 plop-templates,将模板文件放入其中。这里的模板需要根据实际情况去添加。

In plop-templates/component/index.tsx

import ComponentName from './ComponentName';

export default ComponentName;

In plop-templates/component/interface.ts

import type React from 'react';
export interface ComponentNameProps {
  /**
   * 样式前缀
   */
  prefixCls?: string;
  /**
   * 组件className
   */
  className?: string;
}

In plop-templates/component/ComponentName.tsx

import React from 'react';
import { ComponentNameProps } from './interface';

export const ComponentName: React.FC<ComponentNameProps> = ({
  prefixCls = 'xiaowei-ui',
  className = '',
  ...props
}) => {
  return (
    <div className={`${prefixCls}-camel2Dash ${className}`} {...props}>
      ComponentName
    </div>
  );
};

export default ComponentName;

In plop-templates/component/style/index.scss

@import '../../style/index'; // 公共样式、变量等内容

.#{$prefix-cls}-camel2Dash {
  display: flex;
}

In plop-templates/component/style/index.ts

import './index.scss';
  1. 在项目根目录下创建文件 plopfile.js
const camelCase = (text) => {
  return text
    .replace(/([A-Z])/g, '-$1')
    .toLowerCase()
    .replace(/(^[-])/, '');
};

const transform = (fileContents, { ComponentName }) => {
  const CamelCase = camelCase(ComponentName);
  return fileContents.replace(/ComponentName/g, ComponentName).replace(/camel2Dash/g, CamelCase);
};

module.exports = function (plop) {
  plop.setHelper('CamelCase', camelCase);

  plop.setGenerator('controller', {
    description: '新建组件模板',
    prompts: [
      {
        type: 'input',
        name: 'ComponentName',
        message: '请输入组件名称, 例如:DatePicker:',
        validate: (name) => {
          return /^[a-zA-Z]+$/.test(name);
        }
      }
    ],
    actions: [
      {
        type: 'addMany',
        destination: 'components/{{CamelCase ComponentName}}/',
        base: 'plop-templates/component',
        templateFiles: [
          'plop-templates/component/**/*',
          '!plop-templates/component/ComponentName.tsx'
        ],
        force: true,
        transform
      },
      {
        type: 'add',
        path: 'components/{{CamelCase ComponentName}}/{{ComponentName}}.tsx',
        templateFile: 'plop-templates/component/ComponentName.tsx',
        force: true,
        transform
      },
      {
        type: 'add',
        path: 'stories/{{CamelCase ComponentName}}/{{ComponentName}}.stories.tsx',
        templateFile: 'plop-templates/storie/ComponentName.stories.tsx',
        force: true,
        transform
      },
      {
        type: 'modify',
        path: 'components/index.ts',
        transform: (fileContents, { ComponentName }) => {
          const CamelCase = camelCase(ComponentName);
          return fileContents.replace(
            /(export.*) \}/,
            `import ${ComponentName} from './${CamelCase}';
$1, ${ComponentName} }`
          );
        }
      }
    ]
  });
};
  1. package.json
{
    ...,
    "scripts": {
        "new": "plop"
    },
    ...
}
  1. 执行脚本,输入组件名称,将通过模板创建组件
yarn new

组件测试

TODO

组件库打包

库文件入口

  • main 是包入口,Node 环境下使用的入口,可以是 CommonJS 格式或者是 umd(兼容了 AMDCommonJS)格式
  • unpkg 定义浏览器环境使用的入口,命名:name.min.js
  • module 定义 ES6 模块打包的入口,格式是 ES6,命名:name.es.js

如果这三个入口都配置了,相当于我们同时发布了三个模块规范的版本,当打包工具遇到我们的模块的时候,会通过判断是否支持 pkg.module(ES6),而优先使用 ES6 模块。

打包工具分析

  • Webpack 通过库的打包方式,可以将组件库打包成 umd 格式的一个文件,如果要实现按需加载,可以将每一个组件单独打包成 umd 格式,Webpack 不支持 ES6 模块的导出。

    {
      entry: {
        alert:'',
        button:''
      },
      output: {
        path: 'lib',
        filename: '[name]/index.js',
        library: 'xiaowei-ui',
        libraryTarget: 'umd'
      }
    }
  • Rollup 可以通过 format 参数将代码 打包成 任何你想要的格式(AMD, CommonJS, ES6, UMD 等等)。但是 Rollup 无法支持代码拆分和运行时态的动态导入 dynamic imports at runtime

    可以打包的格式参数

    • amd – 异步模块定义,用于像 RequireJS 这样的模块加载器
    • cjs – CommonJS,适用于 Node 和 Browserify/Webpack
    • es – 将软件包保存为 ES 模块文件,在现代浏览器中可以通过 <script type=module> 标签引入
    • iife – 一个自动执行的功能,适合作为<script>标签。(如果要为应用程序创建一个捆绑包,您可能想要使用它,因为它会使文件大小变小。)
    • umd – 通用模块定义,以amdcjsiife 为一体
    • system - SystemJS 加载器格式

    打包配置文件中使用 format 字段指定打包格式

    export default {
      input: 'src/main.js',
      output: {
        file: 'bundle.js',
        format: 'es'
      }
    };

在组件库的构建上一般情况下Rollup是最好的选择,但是在应用程序的构建上Webpack是最好的选择。

Tree Shaking

这是 webpack 打包工具的官方描述:

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 importexport。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。

webpack 2 正式版本内置支持 ES2015 模块(也叫做 harmony modules)和未使用模块检测能力。新的 webpack 4 正式版本扩展了此检测能力,通过 package.json"sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯正 ES2015 模块)",由此可以安全地删除文件中未使用的部分。

其他的配置参考:https://webpack.docschina.org/guides/tree-shaking/#root

明确打包目标

  1. 如果组件的宿主环境是js环境,需要将ts处理成js,并生成声明文件。
  2. 如果组件的宿主环境不想编译scss,需要将scss编译成css
  3. 如果组件的宿主环境不想再对js进行编译,需要将 js 编译并压缩成目标浏览器支持的语法。
  4. 如果宿主环境不支持es6格式,需要将js语法编译成其他格式,比如CommonJS(cjs)
  5. 如果宿主环境想要按需加载组件,又不支持ES模块,需要将组件单独打包。
  6. 如果宿主环境想要按需加载组件,支持ES模块,只需要打包组件为ES模块版本。
  7. 由于Tree Shaking 只对 js生效所以样式文件要按需加载,只能单独编译,独立引入。

由于我开发的是内部组件库,我们本身的项目都是用scss,基于此我选择在项目中编译scss,不会对scss进行额外的编译操作。

我们的使用环境有js有ts所有我需要将ts编译成js,并追加声明文件。由于现代的打包工具都支持esm,所以我只发布了esm的版本,但是对于eslint检测工具就会报找不到模块,在略检测后webpack打包是正常的。

我本来计划是在组件中导入样式,在使用组件的时候直接引入(import { Modal } from 'xiaowei-uiui'),但是打包后发现,组件会通过Tree Shaking实现按需,样式文件会全部提取一遍。所以才需要在styles文件夹下定义一个index.ts, 来索引当前组件需要的所有样式文件,进行独立引入。

我最终的打包目标是,编译ts为js(esm和commonjs两个版本),不做额外压缩和目标浏览器处理,复制样式文件到对应的组件下,不做编译处理, 所有的编译转化都交给宿主环境统一处理。

编译 TS 为 ES6 的模块

修改配置tsconfig.json

{
  "compilerOptions": {
    "baseUrl": "./",
    "target": "esnext",
    "jsx": "react",
    "strict": true,
    "declaration": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  },
  "include": ["components", "stories", "global.d.ts"]
}

添加配置文件tsconfig.build.json排除 stories 目录

{
  "extends": "./tsconfig.json",
  "include": ["components", "global.d.ts"] // 重写排除 stories
}

增加 gulp 任务复制 scss 文件,新建gulpfile.js

const gulp = require('gulp');

const paths = {
  dest: {
    es: 'es',
    lib: 'lib'
  },
  styles: ['components/**/*.scss']
};

/**
 * 拷贝SCSS文件
 */
function copyScss() {
  const { styles, dest } = paths;
  return gulp.src(styles).pipe(gulp.dest(dest.es)).pipe(gulp.dest(dest.lib));
}

exports.default = copyScss;

安装gulp依赖

yarn add gulp -D

修改package.json

...,
  "module": "es/index.js", // 指定es模块的入口文件
  "main": "lib/index.js",
  "types": "es/index.d.ts", // 指定类型声明入口文件
  "files": [
    "es",
    "lib"
  ],
  "scripts": {
    	"clean": "rimraf es", // 编译前清空目录, 需要安装依赖 yarn add rimraf -D
     	"build:es": "tsc -m ES6 --declarationDir es --outDir es -p tsconfig.build.json",
    	"build:cjs": "tsc -m CommonJS --declarationDir lib --outDir lib -p tsconfig.build.json",
    	"build": "yarn clean && yarn build:es && yarn build:cjs && gulp",
  },
  "peerDependencies": { // 指定外部依赖的包, 外部依赖的包不要在 dependencies 中配置
    "react": ">=16.14.0",
    "react-dom": ">=16.14.0",
    "sass-loader": "^10.1.0"
  }
...

组件库发布

  1. 在npm官网(https://www.npmjs.com/)创建自己的帐户
  2. npm login
  3. npm publish // 发布
  4. npm unpublish 包名 --force // 撤包

发布失败,请按照下面步骤检查

  • 用了淘宝镜像源 - 换成 npm 的源。
  • 包名重复 - 删掉之前的包,改个名字。
  • npm 账户没有验证邮箱 - 验证邮箱。
  • vpn 冲突 - 关掉所有 vpn 再次尝试。

快速上手

  1. 安装
yarn add xiaowei-ui -D
  1. 使用
import { Modal } from 'xiaowei-ui';
ReactDOM.render(<Modal />, mountNode);
  1. babel配置

安装 babel-plugin-import ,配置插件

{
  "presets": [["@babel/preset-env", // 只转换新的语法,并不对新的api进行处理
    {
        "useBuiltIns":"usage", // 按需注入新的API
        "corejs": 3,
        "modules": false // 编译后不会转化成CommonJS
      }
  ], "@babel/preset-react" ],
  "plugins": [
      ["import", { 
          "libraryName": "xiaowei-ui", 
          "libraryDirectory": "es", 
          "style": true,
          "camel2DashComponentName": true
        }, 
        "xiaowei-ui"
      ],
  ],
}

这个配置会将

​ import { Modal } from 'xiaowei-ui';

转化为:

​ import "xiaowei-ui/es/modal/style";// 样式文件按需加载

​ import _Modal from "xiaowei-ui/es/modal";

  1. webpack 配置,不能排除 node_modules, 且需要增加 scss-loader
...
{
    test: /\.jsx?$/,
    use: {
      loader: 'babel-loader',
      options: {
        cacheDirectory: true,
      },
    },
  },
  {
    test: /\.scss/,
    use: [
      { loader: env.buildType === 'dev' ? 'style-loader' : MiniCssExtractPlugin.loader },
      { loader: 'css-loader', options: { sourceMap: env.buildType === 'dev' } },
      { loader: 'postcss-loader', options: { sourceMap: env.buildType === 'dev' } },
      { loader: 'sass-loader', options: { sourceMap: env.buildType === 'dev' } },
    ],
  },
...

参考文档

https://www.npmjs.com/package/lint-staged

https://typicode.github.io/husky/#/

https://commitlint.js.org/#/

https://juejin.cn/post/6844904160568016910#heading-0

https://juejin.cn/post/6844903513202409485#heading-9

https://github.com/AlloyTeam/eslint-config-alloy#typescript-react

https://blog.csdn.net/qq_39919114/article/details/110238225

https://storybook.js.org/tutorials/intro-to-storybook/react/zh-CN/get-started/