摘要
"旨在通过从0到1式,通过一个个依赖,搭建起来一个正常的react + rollup项目,帮大家理解项目运行机制,以及基于前端工程化的基础搭建流程"
在上一篇文章中,我们已经完成了一个最基础的react项目,基于rollup打包构建,以及开发模式的服务监听,但离组件库的成品效果还差一些
分析
组件库最少所需具备的特性:
- 组件仓库
- 开发模式:全量打包(支持提供热更新、实时预览的开发体验)
- 生产模式:多入口打包,进行组件批次的打包构建,优化构建产物,生成多种模块格式的发布包,支持业务项目按需引入组件
- 需要支持处理ts、css、tsx编译特性
一、🚦 组件仓库规范定义
|-- src
|-- components
|-- index.ts // 组件声明,暴露
|-- Button // 组件模块定义
|-- Button.tsx // 组件核心逻辑封装
|-- index.ts // 组件注册/暴露
|-- style
|-- index.css // 组件样式模块
index.ts
export * from './Button'
export * from './Input'
Button.tsx
import React from 'react'
import './style/index.css'
export interface ViButtonProps {
children: React.ReactNode
onClick: () => void
}
const ViButton: React.FC<ViButtonProps> = ({ children, onClick }) => {
return (
<button className={'violet-btn'} onClick={onClick}>
{children}
</button>
)
}
export default ViButton
Button/index.ts
export { default as ViButton } from './Button'
export type { ViButtonProps } from './Button'
二、📦 开发模式打包规范
开发的时候我们需要对每个组件进行打包,同样的,需要输出esm/cjs规范的打包产物(esm支持import模块语法进行使用)
// 基础插件配置
const basePlugins = [
replace({ /* 环境变量注入 */ }),
babel({ /* Babel 转换 */ }),
resolve({ /* 模块解析 */ }),
commonjs() /* CommonJS 转换 */
]
// 开发环境插件 - fn避免重复调用(如果是对象,isProduction ? 1 : devConfig这个判断的时候就会执行serve,需要用方法返回来避免调用)
const getDevPlugins = () => {
return [
// 开发模式的时候,本地需要解析css,所以需要这个插件,生产环境不需要(使用gulp进行打包)
postcss({
modules: false, // 禁用 CSS Modules
extract: false, // 提取为单独文件
inject: true, // 注入到 JavaScript 文件中,会强制使用css.js的形式进行css依赖注入
minimize: false, // 生产环境压缩
plugins: [
autoprefixer()
]
}),
// 我们可以搭建一个本地服务进行辅助开发,并热更新dist打包产物
serve({
open: true,
contentBase: [
path.resolve(__dirname),
path.resolve(__dirname, 'dist'),
path.resolve(__dirname, 'public')
],
port: 3000,
host: 'localhost'
}),
livereload({
watch: 'dist'
})
]
}
// 生产环境特有配置
const productionPlugins = [
typescript({ /* TypeScript 编译 */ })
]
关于esm与cjs
在现代 JavaScript 开发中,esm(ES Modules)和 cjs(CommonJS)是两种常见的模块化系统,它们在构建和发布 JavaScript 组件库时扮演着非常重要的角色。理解这两种格式的作用和区别,有助于你决定如何发布你的组件库,使其适配不同的使用场景。
ESM(ECMAScript Modules)
ESM 是基于 ES6(ECMAScript 2015)引入的模块化系统,也是现代 JavaScript 标准的模块化方式。它通过 import 和 export 关键字来定义模块的导入和导出。
优点:
-
静态分析:ESM 是静态的,这意味着导入和导出在编译时就能确定,这使得现代构建工具(如 Webpack、Rollup)能够进行更好的优化(比如 tree-shaking)。
-
更好的支持未来的 JavaScript 特性:ESM 是 JavaScript 官方的模块系统,未来的 JavaScript 特性(如异步加载模块)将基于 ESM。
-
浏览器原生支持:现代浏览器原生支持 ESM,可以直接通过 <script type="module"> 引入模块,而不需要打包。
使用场景:
- 适用于现代 JavaScript 环境,如前端开发(React、Vue、Angular 等现代框架)。
- 适用于使用 tree-shaking(消除未使用代码)进行优化的项目。
- 在使用 import 和 export 时,能够减少额外的构建步骤,浏览器能够直接理解。
eg:
// 导出
export const MyComponent = () => {
return <div>Hello World</div>;
};
// 导入
import { MyComponent } from 'my-library';
CJS(CommonJS)
CJS 是最早的模块化规范,它在 Node.js 环境中非常流行。它使用 require 和 module.exports 来导入和导出模块。
优点:
-
兼容性:CJS 是 Node.js 的标准模块化格式,因此在大多数 Node.js 项目中都可以直接使用。
-
动态导入:CJS 支持在代码中动态加载模块(如 require() 语句可以在任何地方调用),这对于一些复杂的动态逻辑来说非常有用。
使用场景:
- 适用于 Node.js 后端应用程序 和一些老旧的 CommonJS 环境,或者不支持 ESM 的环境。
- 某些老旧的构建工具和库仍然只支持 CJS 格式。
eg:
// 导出
module.exports = {
MyComponent: () => {
return 'Hello World';
}
};
// 导入
const { MyComponent } = require('my-library');
运行
pnpm dev
三、📦 生产模式打包规范
生产模式下,我们需要对多组件进行打包,但是不同的是,css我们不使用rolluo的插件postcss进行打包,转而使用gulp进行打包(源自antd项目打包流程借鉴),为什么使用Gulp呢?
一、Gulp特性:
Gulp 是一个任务自动化工具,提供了 强大的插件系统 和 灵活的任务配置,适用于许多复杂的 CSS 处理任务。而 Rollup 本身是一个模块打包器,尽管也能处理 CSS,但它并不是专门为 CSS 处理设计的,它的 CSS 处理能力相较于 Gulp 更加基础。
它的优点:
- 任务自动化和流水线处理:Gulp 可以用来处理多种任务,像是压缩 CSS、CSS 分离、自动添加前缀、编译 Sass/LESS 等,非常灵活。
- 插件扩展性:Gulp 拥有丰富的插件生态系统,比如 gulp-postcss,gulp-sass,gulp-cssnano 等,能够灵活组合多个插件来实现不同的 CSS 处理需求。
- 并行任务执行:Gulp 允许并行执行多个任务,这对于大项目的构建非常有帮助。
二、Rollup对css的支持
Rollup 是一个专注于 模块打包 的工具,虽然它支持通过 rollup-plugin-postcss 来处理 CSS,但 Rollup 的设计初衷并不是为了处理复杂的 CSS 任务,而是主要处理模块化 JavaScript。Rollup 对 CSS 的处理更多是为了将其作为模块导入、按需提取,或者是打包成一个最终文件,而不是执行复杂的样式任务。
Rollup 的 CSS 处理能力:
- 简单的 CSS 打包:通过 rollup-plugin-postcss,Rollup 可以处理 CSS、将其提取为独立文件、自动添加前缀等。
- 按需提取:Rollup 支持通过 extract 配置将 CSS 提取到独立文件,但它的处理逻辑相对较简单,不如 Gulp 灵活。
- 不支持复杂的任务流水线:比如,自动将 Sass 或 LESS 编译成 CSS,或者需要多个 CSS 处理步骤的组合,Rollup 处理起来并不如 Gulp 那么容易。
三、结合使用 Gulp 和 Rollup
通常的做法是,使用 Gulp 来进行 资源处理,如 CSS、Sass/LESS 编译、图像压缩、文件合并等,然后将处理后的资源交给 Rollup 进行 模块打包。这种做法的优点是能够利用 Gulp 强大的任务处理和 Rollup 强大的模块打包功能。
Gulp 和 Rollup 的结合:
-
Gulp 负责:
- 处理 CSS(例如编译 Sass、压缩、自动添加前缀等)。
- 其他资源(如图片、字体)的优化和压缩。
-
Rollup 负责:
- 打包模块化的 JavaScript 代码。
- 将打包后的 CSS 提取到单独文件中,或者将其内联到 JavaScript 中。
这种组合能够让你充分发挥两者的优势,既能享受 Gulp 灵活的任务自动化,又能利用 Rollup 高效的 JavaScript 模块打包。
rollup.config.js
const productionPlugins = [
typescript({
tsconfig: './tsconfig.json', // 主配置,用于 JS 构建
declaration: false, // 禁止 Rollup 插件生成类型
})
]
const resultConfig = {
input: [
'src/main.tsx',
'src/components/index.ts'
// ...getComponentOutput() // 直接使用函数返回的数组,不需要额外的解构赋值
],
output: [
{
dir: 'dist/esm',
format: 'esm',
preserveModules: true, // 保留模块结构
preserveModulesRoot: 'src' // 保留模块的根目录
},
{
dir: 'dist/cjs',
format: 'cjs',
preserveModules: true, // 保留模块结构
preserveModulesRoot: 'src' // 保留模块的根目录
}
],
plugins: [
...basePlugins,
...(isProduction ? productionPlugins : getDevPlugins())
],
// 不这样设置:打包结果会把 .pnpm 的软链接结构直接保留到了输出目录里,会产生virtual目录和node_modules目录
external: isProduction ? ((id) =>
/^react/.test(id) ||
/^react-dom/.test(id) ||
/\.css$/.test(id) // 新增:排除所有CSS文件
) : []
}
export default resultConfig
构建脚本如下:
"scripts": {
"clean": "rm -rf dist",
"dev": "cross-env NODE_ENV=development rollup -c -w",
"build:types": "tsc -p tsconfig.build.json",
"build": "pnpm clean && pnpm build:css && cross-env NODE_ENV=production rollup -c --no-watch && pnpm build:types",
"build:css": "gulp"
},
gulpfile.js
需要安装依赖:
pnpm add gulp gulp-postcss -D
多任务进行结合打包,es和cjs产物的css样式输出,这边的路径需要提前约定规范
import gulp from 'gulp';
import postcss from 'gulp-postcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
// import path from 'path';
function processESMCSS() {
return gulp.src('src/components/**/style/*.css')
.pipe(postcss([autoprefixer(), cssnano()]))
.pipe(gulp.dest((file) => 'dist/esm/components'));
}
function processCJSCSS() {
return gulp.src('src/components/**/style/*.css')
.pipe(postcss([autoprefixer(), cssnano()]))
.pipe(gulp.dest((file) => 'dist/cjs/components'));
}
export default gulp.series(
gulp.parallel(processESMCSS, processCJSCSS)
);
运行 pnpm build,生成打包产物,并生成ts的类型声明文件(方便ide进行编译提示检查)
四、测试构建
运行link进行全局软链注册
pnpm link --global
在业务项目中进行使用
pnpm link violet-ui --global
打包产物只会有这个组件,不包含全量资源(后续其他组件模块可依赖babel-plugin-import辅助css模块的注入,这里由于组件内部显式引入css所以自带了,并不需要)
四、🚀 演进方向
- 文档系统 :集成 Storybook 或 Dumi
- 主题系统 :实现类似 Ant Design 的动态主题
- 测试体系 :添加 Jest + React Testing Library
- Monorepo :迁移到 pnpm workspace
github地址参考:github.com/chenhebin/v…