Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 组件库搭建指南(三):编译打包 #5

Open
worldzhao opened this issue May 9, 2021 · 35 comments
Open

React 组件库搭建指南(三):编译打包 #5

worldzhao opened this issue May 9, 2021 · 35 comments

Comments

@worldzhao
Copy link
Owner

worldzhao commented May 9, 2021

前言

宿主环境各不相同,需要将源码进行相关处理后发布至 npm。

明确以下目标:

  1. 导出类型声明文件;
  2. 导出 UMD/Commonjs module/ES module 等 3 种形式产物供使用者引入;
  3. 支持样式文件 css 引入,而非只有less,减少业务方接入成本;
  4. 支持按需加载。

然后,向目标前进!

导出类型声明文件

既然是使用typescript编写的组件库,那么使用者应当享受到类型系统的好处。

我们可以生成类型声明文件,并在package.json中定义入口,如下:

package.json

{
  "typings": "lib/index.d.ts", // 定义类型入口文件
  "scripts": {
    "build:types": "tsc -p tsconfig.build.json && cpr lib esm" // 执行tsc命令生成类型声明文件
  }
}

值得注意的是:此处使用cpr(需要手动安装)将lib的声明文件拷贝了一份,并将文件夹重命名为esm,用于后面存放 ES module 形式的组件。这样做的原因是保证用户手动按需引入组件时依旧可以获取自动提示。

最开始的方式是将声明文件单独存放在types文件夹,但这样只有通过'happy-ui'引入才可以获取提示,而'happy-ui/esm/xxx'和'happy-ui/lib/xxx'就无法获取提示。

tsconfig.build.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": { "emitDeclarationOnly": true }, // 只生成声明文件
  "exclude": ["**/__tests__/**", "**/demo/**", "node_modules", "lib", "esm"] // 排除示例、测试以及打包好的文件夹
}

执行yarn build:types,可以发现根目录下已经生成了lib文件夹(tsconfig.json中定义的declarationDir字段)以及esm文件夹(拷贝而来),目录结构与src文件夹保持一致,如下:

lib

├── alert
│   ├── index.d.ts
│   └── style
│       └── index.d.ts
└── index.d.ts

这样使用者引入npm 包时,便能得到自动提示,也能够复用相关组件的类型定义。

接下来将ts(x)等文件处理成js文件。

需要注意的是,我们需要输出Commonjs module以及ES module两种模块类型的文件(暂不考虑UMD),以下使用cjs指代Commonjs moduleesm指代ES module。对此有疑问的同学推荐阅读:import、require、export、module.exports 混合详解

导出 Commonjs 模块

其实完全可以使用babeltsc命令行工具进行代码编译处理(实际上很多工具库就是这样做的),此处借助 gulp 来串起这个流程。

为什么是 gulp 而不是 webpackrollup ?因为我们要做的是代码编译而非代码打包,同时需要考虑到样式处理及其按需加载

babel 配置

首先安装babel及其相关依赖

yarn add @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-proposal-class-properties  @babel/plugin-transform-runtime --dev
yarn add @babel/runtime-corejs3

新建.babelrc.js文件,写入以下内容:

.babelrc.js

module.exports = {
  presets: ['@babel/env', '@babel/typescript', '@babel/react'],
  plugins: [
    '@babel/proposal-class-properties',
    [
      '@babel/plugin-transform-runtime',
      {
        corejs: 3,
        helpers: true,
      },
    ],
  ],
};

关于@babel/plugin-transform-runtime@babel/runtime-corejs3

  • helpers选项设置为true,可抽离代码编译过程重复生成的 helper 函数(classCallCheck,extends等),减小生成的代码体积;
  • corejs设置为3,可引入不污染全局的按需polyfill,常用于类库编写(我更推荐:不引入polyfill,转而告知使用者需要引入何种polyfill,避免重复引入或产生冲突,后面会详细提到)。

更多参见官方文档-@babel/plugin-transform-runtime

配置目标环境

为了避免转译浏览器原生支持的语法,新建.browserslistrc文件,根据适配需求,写入支持浏览器范围,作用于@babel/preset-env

.browserslistrc

>0.2%
not dead
not op_mini all

很遗憾的是,@babel/runtime-corejs3无法在按需引入的基础上根据目标浏览器支持程度再次减少polyfill的引入,参见@babel/runtime for target environment

这意味着@babel/runtime-corejs3 甚至会在针对现代引擎的情况下注入所有可能的 polyfill:不必要地增加了最终捆绑包的大小。

对于组件库(代码量可能很大),个人建议将polyfill的选择权交还给使用者,在宿主环境进行polyfill。若使用者具有兼容性要求,自然会使用@babel/preset-env + core-js + .browserslistrc进行全局polyfill,这套组合拳引入了最低目标浏览器不支持API的全部 polyfill

顺带一提,业务开发中,若将@babel/preset-envuseBuiltIns选项值设置为 usage,同时把node_modulesbabel-loaderexclude,会导致babel 无法检测到nodes_modules中所需要的polyfill"useBuiltIns: usage" for node_modules without transpiling #9419,在未支持该issue提到的内容之前,请将useBuiltIns设置为entry,或者不要把node_modulesbabel-loaderexclude

所以组件库不用画蛇添足,引入多余的polyfill,写好文档说明,比什么都重要(就像zentantd这样)。

现在@babel/runtime-corejs3更换为@babel/runtime,只进行helper函数抽离。

yarn remove @babel/runtime-corejs3

yarn add @babel/runtime

.babelrc.js

module.exports = {
  presets: ['@babel/env', '@babel/typescript', '@babel/react'],
  plugins: ['@babel/plugin-transform-runtime', '@babel/proposal-class-properties'],
};

@babel/transform-runtimehelper选项默认为true

gulp 配置

再来安装gulp相关依赖

yarn add gulp gulp-babel --dev

新建gulpfile.js,写入以下内容:

gulpfile.js

const gulp = require('gulp');
const babel = require('gulp-babel');

const paths = {
  dest: {
    lib: 'lib', // commonjs 文件存放的目录名 - 本块关注
    esm: 'esm', // ES module 文件存放的目录名 - 暂时不关心
    dist: 'dist', // umd文件存放的目录名 - 暂时不关心
  },
  styles: 'src/**/*.less', // 样式文件路径 - 暂时不关心
  scripts: ['src/**/*.{ts,tsx}', '!src/**/demo/*.{ts,tsx}'], // 脚本文件路径
};

function compileCJS() {
  const { dest, scripts } = paths;
  return gulp
    .src(scripts)
    .pipe(babel()) // 使用gulp-babel处理
    .pipe(gulp.dest(dest.lib));
}

// 并行任务 后续加入样式处理 可以并行处理
const build = gulp.parallel(compileCJS);

exports.build = build;

exports.default = build;

修改package.json

package.json

{
- "main": "index.js",
+ "main": "lib/index.js",
  "scripts": {
    ...
+   "clean": "rimraf lib esm dist",
+   "build": "npm run clean && npm run build:types && gulp",
    ...
  },
}

执行yarn build,得到如下内容:

lib

├── alert
│   ├── index.js
│   └── style
│       └── index.js
└── index.js

观察编译后的源码,可以发现:诸多helper方法已被抽离至@babel/runtime中,模块导入导出形式也是commonjs规范。

lib/alert/alert.js

rc-lib-6

导出 ES module

生成ES module可以更好地进行tree shaking,基于上一步的babel配置,更新以下内容:

  1. 配置@babel/preset-envmodules选项为false,关闭模块转换;
  2. 配置@babel/plugin-transform-runtimeuseESModules选项为true,使用ES module形式引入helper函数。

.babelrc.js

module.exports = {
  presets: [
    [
      '@babel/env',
      {
        modules: false, // 关闭模块转换
      },
    ],
    '@babel/typescript',
    '@babel/react',
  ],
  plugins: [
    '@babel/proposal-class-properties',
    [
      '@babel/plugin-transform-runtime',
      {
        useESModules: true, // 使用esm形式的helper
      },
    ],
  ],
};

目标达成,我们再使用环境变量区分esmcjs(执行任务时设置对应的环境变量即可),最终babel配置如下:

.babelrc.js

module.exports = {
  presets: ['@babel/env', '@babel/typescript', '@babel/react'],
  plugins: ['@babel/plugin-transform-runtime', '@babel/proposal-class-properties'],
  env: {
    esm: {
      presets: [
        [
          '@babel/env',
          {
            modules: false,
          },
        ],
      ],
      plugins: [
        [
          '@babel/plugin-transform-runtime',
          {
            useESModules: true,
          },
        ],
      ],
    },
  },
};

接下来修改gulp相关配置,抽离compileScripts任务,增加compileESM任务。

gulpfile.js

// ...

/**
 * 编译脚本文件
 * @param {string} babelEnv babel环境变量
 * @param {string} destDir 目标目录
 */
function compileScripts(babelEnv, destDir) {
  const { scripts } = paths;
  // 设置环境变量
  process.env.BABEL_ENV = babelEnv;
  return gulp
    .src(scripts)
    .pipe(babel()) // 使用gulp-babel处理
    .pipe(gulp.dest(destDir));
}

/**
 * 编译cjs
 */
function compileCJS() {
  const { dest } = paths;
  return compileScripts('cjs', dest.lib);
}

/**
 * 编译esm
 */
function compileESM() {
  const { dest } = paths;
  return compileScripts('esm', dest.esm);
}

// 串行执行编译脚本任务(cjs,esm) 避免环境变量影响
const buildScripts = gulp.series(compileCJS, compileESM);

// 整体并行执行任务
const build = gulp.parallel(buildScripts);

// ...

执行yarn build,可以发现生成了lib/esm两个文件夹,观察esm目录,结构同lib一致,js 文件都是以ES module模块形式导入导出。

esm/alert/alert.js

rc-lib-7

别忘了给package.json增加相关入口。

package.json

{
+ "module": "esm/index.js"
}

处理样式文件

拷贝 less 文件

我们会将less文件包含在npm包中,用户可以通过happy-ui/lib/alert/style/index.js的形式按需引入less文件,此处可以直接将 less 文件拷贝至目标文件夹。

gulpfile.js中新建copyLess任务。

gulpfile.js

// ...

/**
 * 拷贝less文件
 */
function copyLess() {
  return gulp.src(paths.styles).pipe(gulp.dest(paths.dest.lib)).pipe(gulp.dest(paths.dest.esm));
}

const build = gulp.parallel(buildScripts, copyLess);

// ...

观察lib目录,可以发现 less 文件已被拷贝至alert/style目录下。

lib

├── alert
│   ├── alert.js
│   ├── index.js
│   ├── interface.js
│   └── style
│       ├── index.js
│       └── index.less # less文件
└── index.js

可能有些同学已经发现问题:若使用者没有使用less预处理器,使用的是sass方案甚至原生css方案,那现有方案就搞不定了。经分析,有以下 4 种预选方案:

  1. 告知业务方增加less-loader。会导致业务方使用成本增加;
  2. 打包出一份完整的 css 文件,进行全量引入。无法进行按需引入;
  3. css in js方案;
  4. 提供一份style/css.js文件,引入组件 css样式依赖,而非 less 依赖,组件库底层抹平差异。

重点看一看方案 3 以及方案 4。

css in js除了赋予样式编写更多的可能性之外,在编写第三方组件库时更是利器。

如果我们写一个react-use这种hooks工具库,不涉及到样式,只需要在package.json中设置sideEffectsfalse,业务方使用 webpack 进行打包时,只会打包被使用到的 hooks(优先使用 ES module)。

入口文件index.js中导出的但未被使用的其他 hooks 会被tree shaking,第一次使用这个库的时候我很好奇,为什么没有按需引入的使用方式,后来进行打包分析,发现人家天生支持按需引入。

回到正题。如果将样式使用javascript来编写,在某种维度上讲,组件库和工具库一致了,配好sideEffects,自动按需引入。

而且每个组件都与自己的样式绑定,不需要业务方或组件开发者去维护样式依赖,什么是样式依赖,后面会讲到。

缺点:

  1. 样式无法单独缓存;
  2. styled-components 自身体积较大;
  3. 复写组件样式需要使用属性选择器或者使用styled-components自带方法。

需要看取舍了,偷偷说一句styled-components做主题定制也极其方便。

方案 4 是antd使用的这种方案。

在搭建组件库的过程中,有一个问题困扰了我很久:为什么需要alert/style/index.js引入less文件或alert/style/css.js引入css文件?

答案是管理样式依赖

因为我们的组件是没有引入样式文件的,需要使用者去手动引入。

假设存在以下场景:使用者引入<Button /><Button />依赖了<Icon />,则需要手动去引入调用组件的样式(<Button />)及其依赖的组件样式(<Icon />),遇到复杂组件极其麻烦,所以组件库开发者可以提供一份这样的js文件,使用者手动引入这个js文件,就能引入对应组件及其依赖组件的样式。

那么问题又来了,为什么组件不能自己去import './index.less'呢?

当然可以,但业务方需要配置less-loader

业务方不想配置 less-loader?那我们import './index.css'开发体验岂不是直线下降?

所以需要一个两全其美的方案:

  1. 保障组件库开发者的开发体验 DX;
  2. 减轻业务方的使用成本。

答案就是css in js单独提供一份style/css.js文件,引入的是组件 css样式文件依赖,而非 less 依赖,组件库底层抹平差异。

之前了解到 father 可以在打包的时候将index.less转成index.css,这倒是个好法子,但是一些重复引入的样式模块(比如动画样式),会被重复打包,不知道有没有好的解决方案。

生成 css 文件

安装相关依赖。

yarn add gulp-less gulp-autoprefixer gulp-cssnano --dev

less文件生成对应的css文件,在gulpfile.js中增加less2css任务。

// ...

/**
 * 生成css文件
 */
function less2css() {
  return gulp
    .src(paths.styles)
    .pipe(less()) // 处理less文件
    .pipe(autoprefixer()) // 根据browserslistrc增加前缀
    .pipe(cssnano({ zindex: false, reduceIdents: false })) // 压缩
    .pipe(gulp.dest(paths.dest.lib))
    .pipe(gulp.dest(paths.dest.esm));
}

const build = gulp.parallel(buildScripts, copyLess, less2css);

// ...

执行yarn build,组件style目录下已经存在css文件了。

接下来我们需要一个alert/style/css.js来帮用户引入css文件。

生成 css.js

此处参考antd-tools的实现方式:在处理scripts任务中,截住style/index.js,生成style/css.js,并通过正则将引入的less文件后缀改成css

安装相关依赖。

yarn add through2 --dev

gulpfile.js

// ...

/**
 * 编译脚本文件
 * @param {*} babelEnv babel环境变量
 * @param {*} destDir 目标目录
 */
function compileScripts(babelEnv, destDir) {
  const { scripts } = paths;
  process.env.BABEL_ENV = babelEnv;
  return gulp
    .src(scripts)
    .pipe(babel()) // 使用gulp-babel处理
    .pipe(
      through2.obj(function z(file, encoding, next) {
        this.push(file.clone());
        // 找到目标
        if (file.path.match(/(\/|\\)style(\/|\\)index\.js/)) {
          const content = file.contents.toString(encoding);
          file.contents = Buffer.from(cssInjection(content)); // 文件内容处理
          file.path = file.path.replace(/index\.js/, 'css.js'); // 文件重命名
          this.push(file); // 新增该文件
          next();
        } else {
          next();
        }
      }),
    )
    .pipe(gulp.dest(destDir));
}

// ...

cssInjection的实现:

gulpfile.js

/**
 * 当前组件样式 import './index.less' => import './index.css'
 * 依赖的其他组件样式 import '../test-comp/style' => import '../test-comp/style/css.js'
 * 依赖的其他组件样式 import '../test-comp/style/index.js' => import '../test-comp/style/css.js'
 * @param {string} content
 */
function cssInjection(content) {
  return content
    .replace(/\/style\/?'/g, "/style/css'")
    .replace(/\/style\/?"/g, '/style/css"')
    .replace(/\.less/g, '.css');
}

再进行打包,可以看见组件style目录下生成了css.js文件,引入的也是上一步less转换而来的css文件。

lib/alert

├── alert.js
├── index.js
├── interface.js
└── style
    ├── css.js # 引入index.css
    ├── index.css
    ├── index.js
    └── index.less

按需加载

在 package.json 中增加sideEffects属性,配合ES module达到tree shaking效果(将样式依赖文件标注为side effects,避免被误删除)。

// ...
"sideEffects": [
  "dist/*",
  "esm/**/style/*",
  "lib/**/style/*",
  "*.less"
],
// ...

使用以下方式引入,可以做到js部分的按需加载,但需要手动引入样式:

import { Alert } from 'happy-ui';
import 'happy-ui/esm/alert/style';

也可以使用以下方式引入:

import Alert from 'happy-ui/esm/alert'; // or import Alert from 'happy-ui/lib/alert';
import 'happy-ui/esm/alert/style'; // or import Alert from 'happy-ui/lib/alert';

以上引入样式文件的方式不太优雅,直接入口处引入全量样式文件又和按需加载的本意相去甚远。

使用者可以借助 babel-plugin-import 来进行辅助,减少代码编写量(还是增加了使用成本)。

import { Alert } from 'happy-ui';

⬇️

import Alert from 'happy-ui/lib/alert';
import 'happy-ui/lib/alert/style';

最重要的构建流程到此结束,可以发现 sideEffects 字段对于非 CSS in JS 组件库用处并不大,还是依赖 babel 插件达到完整的按需引入效果。

@tiyunchen
Copy link

大佬呀

@worldzhao
Copy link
Owner Author

大佬呀

嘿嘿,喜欢可以给博客来个 star 哈

@tiyunchen
Copy link

我有个问题,如果我用纯js写的使用react 的propTypes类型检查,并且使用webpack打包,这样在组件库引用的时候会有类型提示吗?

@tiyunchen
Copy link

js工程化好难理解

@worldzhao
Copy link
Owner Author

我有个问题,如果我用纯js写的使用react 的propTypes类型检查,并且使用webpack打包,这样在组件库引用的时候会有类型提示吗?

这样的话应该没有类型提示了,可以试一下

@Gllidan
Copy link

Gllidan commented Aug 4, 2021

膜拜大佬,不过有些点还没能理解,整理下请教大佬

@worldzhao
Copy link
Owner Author

膜拜大佬,不过有些点还没能理解,整理下请教大佬

好呀好呀,如果有帮助可以给个 star 哈

@ZhangTaibin
Copy link

貌似不用安装@babel/runtime只使用@babel/plugin-transform-runtime页可以进行helpers抽离,babel/babel#10271

@wowohuai
Copy link

wowohuai commented Dec 24, 2021

有个疑问, 组件库提供commonjs版本的作用是什么?

默认不是加载我们提供的 es 版本的吗

@worldzhao
Copy link
Owner Author

有个疑问, 组件库提供commonjs版本的作用是什么?

默认不是加载我们提供的 es 版本的吗

从现在的发展来看 cjs 版本的组件的确意义不大,但是考虑历史因素,比如对 es 模块兼容性较差的环境就有意义了,如服务端渲染

@knockkk
Copy link

knockkk commented Jan 17, 2022

大佬你好!这篇文章真的给了我很多启发!个人有两个问题想请教一下。

  1. 一个是”为什么组件不能自己去import './index.less'呢?“,这个是我一直有的一个疑问,就是为什么组件JS里不直接引入样式文件,这样外部就不用考虑CSS的按需加载了。这里是不是有这样一个原因呢,就是引入less还是CSS是由项目开发者来定的,如果需要定制主题,那么就需要less,否则用CSS(主题都是写死的)就够了。但是这个在组件库的组件代码里无法确定,所以将样式的引入逻辑单独抽离了。

  2. 第二个问题是,我想基于 Antd 组件库来封装一套符合自己公司设计规范的组件库(需要修改 Antd 的主题和一些组件的默认样式)。我的想法是将 Antd 代码作为依赖引入,但是在打包的时候将 Antd 里的 less 文件中的主题变量替换然后输出为 CSS,同时各个组件的JS代码里直接引入CSS文件(外部就不需要做CSS的按需引入了),最后一起打包进来。在进行实现的时候我发现使用 rollup 这类模块打包工具并不适合,我更需要的是对代码进行定制化的编译和输出,所以也打算用 glup 来尝试一下。然后想问下大佬,我的这个想法的方向对不对,因为我没有看到社区里基于 Antd 来开发组件库是这样做的😭,我这个需要将node_modules的 Antd 源码编译一下然后打包进来,看到有些方案是直接基于 Antd 的项目源码来做,或者只将 Antd 作为依赖。

@worldzhao
Copy link
Owner Author

大佬你好!这篇文章真的给了我很多启发!个人有两个问题想请教一下。

  1. 一个是”为什么组件不能自己去import './index.less'呢?“,这个是我一直有的一个疑问,就是为什么组件JS里不直接引入样式文件,这样外部就不用考虑CSS的按需加载了。这里是不是有这样一个原因呢,就是引入less还是CSS是由项目开发者来定的,如果需要定制主题,那么就需要less,否则用CSS(主题都是写死的)就够了。但是这个在组件库的组件代码里无法确定,所以将样式的引入逻辑单独抽离了。
  2. 第二个问题是,我想基于 Antd 组件库来封装一套符合自己公司设计规范的组件库(需要修改 Antd 的主题和一些组件的默认样式)。我的想法是将 Antd 代码作为依赖引入,但是在打包的时候将 Antd 里的 less 文件中的主题变量替换然后输出为 CSS,同时各个组件的JS代码里直接引入CSS文件(外部就不需要做CSS的按需引入了),最后一起打包进来。在进行实现的时候我发现使用 rollup 这类模块打包工具并不适合,我更需要的是对代码进行定制化的编译和输出,所以也打算用 glup 来尝试一下。然后想问下大佬,我的这个想法的方向对不对,因为我没有看到社区里基于 Antd 来开发组件库是这样做的😭,我这个需要将node_modules的 Antd 源码编译一下然后打包进来,看到有些方案是直接基于 Antd 的项目源码来做,或者只将 Antd 作为依赖。

第一点是正确的,变量覆盖更换主题是有必要直接使用 less 的,直接引入 less 也可以,只要接入方接受就可以了。其实 css vars 也能做到,不一定要 less。

第二点不需要打包,做编译就可以,你的思路是正确的,npm 包基本上都不是很需要打包,要做的只是编译构建。

所以把 antd 作为 deps 或者 peerDeps 都可以,取决于你想整体做 antd 的上层还是做 antd 的扩展层,至于 antd 的主题定制我没有研究过,反正最后一般都是输出变量 或者 css vars,像 arco 和 semi 都是如此,主题因素不应该对组件库的构建产生影响,需要具体问题具体分析了,你的组件库只是封装了一层,不应该影响 antd 自身的主题应用。

@knockkk
Copy link

knockkk commented Jan 17, 2022

大佬你好!这篇文章真的给了我很多启发!个人有两个问题想请教一下。

  1. 一个是”为什么组件不能自己去import './index.less'呢?“,这个是我一直有的一个疑问,就是为什么组件JS里不直接引入样式文件,这样外部就不用考虑CSS的按需加载了。这里是不是有这样一个原因呢,就是引入less还是CSS是由项目开发者来定的,如果需要定制主题,那么就需要less,否则用CSS(主题都是写死的)就够了。但是这个在组件库的组件代码里无法确定,所以将样式的引入逻辑单独抽离了。
  2. 第二个问题是,我想基于 Antd 组件库来封装一套符合自己公司设计规范的组件库(需要修改 Antd 的主题和一些组件的默认样式)。我的想法是将 Antd 代码作为依赖引入,但是在打包的时候将 Antd 里的 less 文件中的主题变量替换然后输出为 CSS,同时各个组件的JS代码里直接引入CSS文件(外部就不需要做CSS的按需引入了),最后一起打包进来。在进行实现的时候我发现使用 rollup 这类模块打包工具并不适合,我更需要的是对代码进行定制化的编译和输出,所以也打算用 glup 来尝试一下。然后想问下大佬,我的这个想法的方向对不对,因为我没有看到社区里基于 Antd 来开发组件库是这样做的😭,我这个需要将node_modules的 Antd 源码编译一下然后打包进来,看到有些方案是直接基于 Antd 的项目源码来做,或者只将 Antd 作为依赖。

第一点是正确的,变量覆盖更换主题是有必要直接使用 less 的,直接引入 less 也可以,只要接入方接受就可以了。其实 css vars 也能做到,不一定要 less。

第二点不需要打包,做编译就可以,你的思路是正确的,npm 包基本上都不是很需要打包,要做的只是编译构建。

所以把 antd 作为 deps 或者 peerDeps 都可以,取决于你想整体做 antd 的上层还是做 antd 的扩展层,至于 antd 的主题定制我没有研究过,反正最后一般都是输出变量 或者 css vars,像 arco 和 semi 都是如此,主题因素不应该对组件库的构建产生影响,需要具体问题具体分析了,你的组件库只是封装了一层,不应该影响 antd 自身的主题应用。

好的,感谢😁

@fzh199410
Copy link

你好,请教下如何在demo代码中,比如
import { Alert } from 'tommy';
实现按需加载呢,尝试了一些方法都没成功,
我看你的代码中目前是手动相对路径引入的样式

@worldzhao
Copy link
Owner Author

你好,请教下如何在demo代码中,比如 import { Alert } from 'tommy'; 实现按需加载呢,尝试了一些方法都没成功, 我看你的代码中目前是手动相对路径引入的样式

主要思路就是配置 webpack 的 alias 和 tsconfig 的 path 保证能够正确编译和 ts 提示就可以了,换成 dumi 后没折腾出来,这是 dumi 的对应配置文档,可以尝试一下:https://d.umijs.org/zh-CN/guide/faq#%E5%BC%80%E5%8F%91%E9%98%B6%E6%AE%B5%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE-md-%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84%E6%A0%B7%E5%BC%8F%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5

@fzh199410
Copy link

非常感谢,成功了,下来再研究下它的实现。贴一下配置(目录结构和官网稍微有点区别)

export default defineConfig({
  title: 'Tommy UI',
  mode: 'site',
  outputPath: 'doc-site',
  exportStatic: {},
  dynamicImport: {},
  base,
  publicPath,
  extraBabelPlugins: [
    [
      'import',
      {
        libraryName: 'tommy-ui',
        camel2DashComponentName: false,
        customStyleName: () => {
          return `../style/index.less`;
        },
      },
      'tommy-ui',
    ],
  ]
});

@DerekRayna
Copy link

Found 2 errors in 2 files.

Errors Files
1 node_modules/@types/react-router-config/index.d.ts:12 1 node_modules/@types/react-router-dom/index.d.ts:14
error Command failed with exit code 2.
大佬请问一下,为啥我生成类型文件时报错

@worldzhao
Copy link
Owner Author

Found 2 errors in 2 files.

Errors Files 1 node_modules/@types/react-router-config/index.d.ts:12 1 node_modules/@types/react-router-dom/index.d.ts:14 error Command failed with exit code 2. 大佬请问一下,为啥我生成类型文件时报错

错误不具体,但看起来类型定义有问题,确认一下 @types/react-router-dom 版本是否存在冲突,并全仓库保持统一,如果是yarn就是yarn resolution,如果是 pnpm 就在.pnpmfile.cjs定义一下

@zhourm3659
Copy link

zhourm3659 commented Jun 29, 2022

node_modules/@types/react-router-config/index.d.ts:12:26 - error TS7016: Could not find a declaration file for module 'history'. '/Users/xxx/Desktop/hp-ui/xxxx/personal-code/hp-ui/node_modules/history/index.js' implicitly has an 'any' type.
If the 'history' package actually exposes this module, consider sending a pull request to amend 'https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/history'

12 import { Location } from 'history';
~~~~~~~~~

node_modules/@types/react-router-dom/index.d.ts:14:20 - error TS7016: Could not find a declaration file for module 'history'. '/Users/xxx/Desktop/hp-ui/xxx/personal-code/hp-ui/node_modules/history/index.js' implicitly has an 'any' type.
If the 'history' package actually exposes this module, consider sending a pull request to amend 'https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/history'

14 import * as H from 'history';
~~~~~~~~~

Found 2 errors in 2 files.

Errors Files
1 node_modules/@types/react-router-config/index.d.ts:12

大佬请问yarn build:types时,报这种错就是版本冲突的问题吗?

@worldzhao
Copy link
Owner Author

node_modules/@types/react-router-config/index.d.ts:12:26 - error TS7016: Could not find a declaration file for module 'history'. '/Users/xxx/Desktop/hp-ui/xxxx/personal-code/hp-ui/node_modules/history/index.js' implicitly has an 'any' type. If the 'history' package actually exposes this module, consider sending a pull request to amend 'https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/history'

12 import { Location } from 'history'; ~~~~~~~~~

node_modules/@types/react-router-dom/index.d.ts:14:20 - error TS7016: Could not find a declaration file for module 'history'. '/Users/xxx/Desktop/hp-ui/xxx/personal-code/hp-ui/node_modules/history/index.js' implicitly has an 'any' type. If the 'history' package actually exposes this module, consider sending a pull request to amend 'https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/history'

14 import * as H from 'history'; ~~~~~~~~~

Found 2 errors in 2 files.

Errors Files 1 node_modules/@types/react-router-config/index.d.ts:12

大佬请问yarn build:types时,报这种错就是版本冲突的问题吗?

对的,统一一下 @types/react-router-dom 的版本哈,tsconfig 配置 skipLibCheck 应该也可以

@xiaochena
Copy link

我有一个问题、如果仅用gulp的话如何过滤掉一些仅仅申明却没有使用的代码

@worldzhao
Copy link
Owner Author

我有一个问题、如果仅用gulp的话如何过滤掉一些仅仅申明却没有使用的代码

这里的【过滤】我当成打包时的tree shaking 理解不知是否正确,取决于宿主项目的配置,比如 webpack。作为第三方库如果不做打包(rollup)只做编译(babel/tsc)一般不会做也不需要做额外处理,gulp 在这里只是一个 task runner

@xiaochena
Copy link

我有一个问题、如果仅用gulp的话如何过滤掉一些仅仅申明却没有使用的代码

这里的【过滤】我当成打包时的tree shaking 理解不知是否正确,取决于宿主项目的配置,比如 webpack。作为第三方库如果不做打包(rollup)只做编译(babel/tsc)一般不会做也不需要做额外处理,gulp 在这里只是一个 task runner

是的、就是 打包时的tree shaking ,gulp只做编译不做 tree shaking 库的体积就会比较大(多余的代码也会被打包)

@worldzhao
Copy link
Owner Author

我有一个问题、如果仅用gulp的话如何过滤掉一些仅仅申明却没有使用的代码

这里的【过滤】我当成打包时的tree shaking 理解不知是否正确,取决于宿主项目的配置,比如 webpack。作为第三方库如果不做打包(rollup)只做编译(babel/tsc)一般不会做也不需要做额外处理,gulp 在这里只是一个 task runner

是的、就是 打包时的tree shaking ,gulp只做编译不做 tree shaking 库的体积就会比较大(多余的代码也会被打包)

应当关注的是使用者打包他们应用时的我们组件库的体积,webpack 默认会做 tree shaking 的(package.json配置了sideEffects),再配个组件的按需引入,无需担心这一点哈,可以自己试验一下

@xiaochena
Copy link

我有一个问题、如果仅用gulp的话如何过滤掉一些仅仅申明却没有使用的代码

这里的【过滤】我当成打包时的tree shaking 理解不知是否正确,取决于宿主项目的配置,比如 webpack。作为第三方库如果不做打包(rollup)只做编译(babel/tsc)一般不会做也不需要做额外处理,gulp 在这里只是一个 task runner

是的、就是 打包时的tree shaking ,gulp只做编译不做 tree shaking 库的体积就会比较大(多余的代码也会被打包)

应当关注的是使用者打包他们应用时的我们组件库的体积,webpack 默认会做 tree shaking 的(package.json配置了sideEffects),再配个组件的按需引入,无需担心这一点哈,可以自己试验一下

感谢大佬百忙之中的秒回、感动。我想我应该明白了

@worldzhao
Copy link
Owner Author

我有一个问题、如果仅用gulp的话如何过滤掉一些仅仅申明却没有使用的代码

这里的【过滤】我当成打包时的tree shaking 理解不知是否正确,取决于宿主项目的配置,比如 webpack。作为第三方库如果不做打包(rollup)只做编译(babel/tsc)一般不会做也不需要做额外处理,gulp 在这里只是一个 task runner

是的、就是 打包时的tree shaking ,gulp只做编译不做 tree shaking 库的体积就会比较大(多余的代码也会被打包)

应当关注的是使用者打包他们应用时的我们组件库的体积,webpack 默认会做 tree shaking 的(package.json配置了sideEffects),再配个组件的按需引入,无需担心这一点哈,可以自己试验一下

感谢大佬百忙之中的秒回、感动。我想我应该明白了

能有帮助就好,遇到问题多交流哈

@hec9527
Copy link

hec9527 commented Aug 12, 2022

这个选项已经弃用了, 在 7.13.0 之后的版本可以根据 package.json 中的exports字段自动选择导入方式
配置@babel/plugin-transform-runtime的useESModules选项为true

@worldzhao
Copy link
Owner Author

这个选项已经弃用了, 在 7.13.0 之后的版本可以根据 package.json 中的exports字段自动选择导入方式 配置@babel/plugin-transform-runtime的useESModules选项为true

具体是哪个配置选项呀

@hec9527
Copy link

hec9527 commented Aug 15, 2022

这个选项已经弃用了, 在 7.13.0 之后的版本可以根据 package.json 中的exports字段自动选择导入方式 配置@babel/plugin-transform-runtime的useESModules选项为true

具体是哪个配置选项呀

@babel/plugin-transform-runtime 插件的 useESModules 属性

@ivank-s
Copy link

ivank-s commented Sep 8, 2022

有个疑问,最后style里面虽然生成了css.js
但是后面按需加载使用里面
import { Alert } from 'happy-ui'; import 'happy-ui/esm/alert/style';
css默认还是加载的style/index.js 文件啊,是不是要精确到css.js才好

@worldzhao
Copy link
Owner Author

有个疑问,最后style里面虽然生成了css.js
但是后面按需加载使用里面
import { Alert } from 'happy-ui'; import 'happy-ui/esm/alert/style';
css默认还是加载的style/index.js 文件啊,是不是要精确到css.js才好

可以通过配置 babel-plugin-import 参数决定引入 style/index.js(less) 还是 style/css.js(css),具体看使用者需求的。

@ivank-s
Copy link

ivank-s commented Sep 17, 2022

有个疑问,最后style里面虽然生成了css.js
但是后面按需加载使用里面
import { Alert } from 'happy-ui'; import 'happy-ui/esm/alert/style';
css默认还是加载的style/index.js 文件啊,是不是要精确到css.js才好

可以通过配置 babel-plugin-import 参数决定引入 style/index.js(less) 还是 style/css.js(css),具体看使用者需求的。

感谢 还有一个问题 看你上面说的主题定制可以通过css vars而不需要less,所以输出less的动机是啥,你也说了‘其实 css vars 也能做到,不一定要 less’实在想不到输出less的动机了,如果组件直接引入样式文件,应该也能完成组件吧

@worldzhao
Copy link
Owner Author

有个疑问,最后style里面虽然生成了css.js
但是后面按需加载使用里面
import { Alert } from 'happy-ui'; import 'happy-ui/esm/alert/style';
css默认还是加载的style/index.js 文件啊,是不是要精确到css.js才好

可以通过配置 babel-plugin-import 参数决定引入 style/index.js(less) 还是 style/css.js(css),具体看使用者需求的。

感谢 还有一个问题 看你上面说的主题定制可以通过css vars而不需要less,所以输出less的动机是啥,你也说了‘其实 css vars 也能做到,不一定要 less’实在想不到输出less的动机了,如果组件直接引入样式文件,应该也能完成组件吧

用 less 开发也能享受预处理器

@qiugu
Copy link

qiugu commented Dec 20, 2022

大佬,组件库编译出来的产物,需要使用babel转译吗,我看有的组件库是没有使用babel,产物就是兼容现代浏览器,如果需要兼容老版本,再从使用方引入babel

@worldzhao
Copy link
Owner Author

worldzhao commented Dec 21, 2022

大佬,组件库编译出来的产物,需要使用babel转译吗,我看有的组件库是没有使用babel,产物就是兼容现代浏览器,如果需要兼容老版本,再从使用方引入babel

我文章里的产物是不需要的,这个看库维护者的考量了,可以提供多种 target 的产物,供用户选择,但我认为兼容老版本浏览器应该是默认行为,因为很多脚手架会在编译时把 node_modules exclude 掉,到时候 modern 产物在需要兼容低版本的场景线上白屏就是徒增烦恼,默认提供现代浏览器产物当然可以,但是使用者也必须知道这一点,这个信息的传递无疑是比较困难的。

Repository owner deleted a comment Dec 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests