We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
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
本文旨在介绍一个打包工具的目标,该工具的主要功能包括:
主要是利用tsc(typescript自带命令)去单独导出ts文件定义
首先,我们需要在package.json中定义入口,也就是别人安装了你的npm包之后,如果想有类型提示,就要去你在package.json中定义入口定义的位置去找,例如说:
// package.json { "typings": "types/index.d.ts", // 定义类型入口文件 "scripts": { "build:types": "rimraf types && tsc --outDir types -d --emitDeclarationOnly", } }
为什么是 gulp 而不是 webpack 或 rollup ?因为我们要做的是代码编译而非代码打包,同时需要考虑到样式处理及其按需加载。
首先安装babel及其相关依赖
.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,避免重复引入或产生冲突,后面会详细提到)。
为了避免转译浏览器原生支持的语法,新建.browserslistrc文件,根据适配需求,写入支持浏览器范围,作用于@babel/preset-env。
.browserslistrc
>0.2% not dead not op_mini all
很遗憾的是,@babel/runtime-corejs3无法在按需引入的基础上根据目标浏览器支持程度再次减少polyfill的引入
这意味着@babel/runtime-corejs3 甚至会在针对现代引擎的情况下注入所有可能的 polyfill:不必要地增加了最终捆绑包的大小。
对于组件库(代码量可能很大),个人建议将polyfill的选择权交还给使用者,在宿主环境进行polyfill。若使用者具有兼容性要求,自然会使用@babel/preset-env + core-js + .browserslistrc进行全局polyfill,这套组合拳引入了最低目标浏览器不支持API的全部 polyfill。
顺带一提,业务开发中,若将@babel/preset-env的useBuiltIns选项值设置为 usage,同时把node_modules从babel-loader中exclude,会导致babel 无法检测到nodes_modules中所需要的polyfill。
我是怎么做的呢,在mx-design中,我只是在mx-deisgn项目根目录添加了.browserslistrc文件,配置为:
chrome 80 edge 79 firefox 78 safari 14
所以配置修改为:
module.exports = { presets: ['@babel/env', '@babel/typescript', '@babel/react'], plugins: ['@babel/plugin-transform-runtime', '@babel/proposal-class-properties'], };
@babel/transform-runtime的helper选项默认为true。
再来安装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
观察编译后的源码,可以发现:诸多helper方法已被抽离至@babel/runtime中,模块导入导出形式也是commonjs规范。
生成ES module可以更好地进行tree shaking,基于上一步的babel配置,更新以下内容:
module.exports = { presets: [ [ '@babel/env', { modules: false, // 关闭模块转换 }, ], '@babel/typescript', '@babel/react', ], plugins: [ '@babel/proposal-class-properties', [ '@babel/plugin-transform-runtime', { useESModules: true, // 使用esm形式的helper }, ], ], };
目标达成,我们再使用环境变量区分esm和cjs(执行任务时设置对应的环境变量即可),最终babel配置如下:
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任务。
// ... /** * 编译脚本文件 * @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,可以发现生成了types/lib/esm三个文件夹,观察esm目录,结构同lib/types一致,js 文件都是以ES module模块形式导入导出
我们会将less文件包含在npm包中,用户可以通过happy-ui/lib/alert/style/index.js的形式按需引入less文件,此处可以直接将 less 文件拷贝至目标文件夹。
在gulpfile.js中新建copyLess任务。
// ... /** * 拷贝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 文件已被拷贝至组件的style目录下
可能有些同学已经发现问题:若使用者没有使用less预处理器,使用的是sass方案甚至原生css方案,那现有方案就搞不定了。经分析,有以下 3 种预选方案:
方案 1 会导致使用成本增加。
方案 2 无法对样式文件进行按需引入(后续在 umd 打包时我们也会提供该样式文件)。
以上两种方案实为下策。
方案 3 比较符合此时的的场景,ant design4版本使用的也是这种方案。
在搭建组件库的过程中,有一个问题困扰了我很久:为什么需要alert/style/index.js引入less文件或alert/style/css.js引入css文件?
答案是管理样式依赖。
假设存在以下场景:引入<Button />,<Button />依赖了<Icon />,使用者需要手动去引入调用的组件的样式(<Button />)及其依赖的组件样式(<Icon />),遇到复杂组件极其麻烦,所以组件库开发者可以提供一份这样的js文件,使用者手动引入这个js文件,就能引入对应组件及其依赖组件的样式。
<Button />
<Icon />
继续我们的旅程。
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文件。
安装相关依赖。
yarn add through2 --dev
// ... /** * 编译脚本文件 * @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的实现:
/** * 当前组件样式 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文件。
在 package.json 中增加sideEffects属性,配合ES module达到tree shaking效果(将样式依赖文件标注为side effects,避免被误删除)。
// ... "sideEffects": [ "dist/*", "esm/**/style/*", "lib/**/style/*", "*.less" ], // ...
使用以下方式引入,可以做到js部分的按需加载(会自动引入less样式,所以你的开发环境例如webpack需要配置less-loader):
import { Alert } from '@mx-design/web';
当然,如果你也可以引入没有css的js文件
import { NcAlert } from '@mx-design/web';
@mx-design/web设置了全局和局部的css变量,可以让你去改变,全局css配置。
import React from 'react'; import { MxConfigProvider, darkTheme, lightTheme } from '@mx-design/web'; function App() { return ( <MxConfigProvide globalCssVariables={lightTheme}> <RouterProvider router={router} /> </MxConfigProvider> </ConfigProvider> ); }
其中lightTheme是亮色主题,darkTheme是暗色主题,所以,你可以自己写一份css变量名跟lightTheme一致的文件导入到MxConfigProvide中,实现自己的主题色切换。
lightTheme
darkTheme
通过themeStyle来配置,具体每个组件和全局有哪些css变量支持,首先你可以在官网打开chrome浏览器的控制台查看,也可以查阅mx-design官网(待补充)
import { Button } from '@mx-design/web'; function App() { return ( <Button type="brand" themeStyle={{ '--btn-brand-color-bg': 'red' }}>Brand</Button> ) }`;
当然,你也可以在打包文件里单独引入css
import { NcAlert } from '@mx-design/web'; import '@mx-design/web/esm/Alert/style/index.css';
引用(获得作者允许): React 组件库搭建指南-打包输出
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
本文旨在介绍一个打包工具的目标,该工具的主要功能包括:
导出类型声明文件
主要是利用tsc(typescript自带命令)去单独导出ts文件定义
首先,我们需要在package.json中定义入口,也就是别人安装了你的npm包之后,如果想有类型提示,就要去你在package.json中定义入口定义的位置去找,例如说:
导出 Commonjs 模块
为什么是 gulp 而不是 webpack 或 rollup ?因为我们要做的是代码编译而非代码打包,同时需要考虑到样式处理及其按需加载。
babel 配置
首先安装babel及其相关依赖
.babelrc.js
关于@babel/plugin-transform-runtime与@babel/runtime-corejs3:
若helpers选项设置为true,可抽离代码编译过程重复生成的 helper 函数(classCallCheck,extends等),减小生成的代码体积;
若corejs设置为3,可引入不污染全局的按需polyfill,常用于类库编写(我更推荐:不引入polyfill,转而告知使用者需要引入何种polyfill,避免重复引入或产生冲突,后面会详细提到)。
配置目标环境
为了避免转译浏览器原生支持的语法,新建.browserslistrc文件,根据适配需求,写入支持浏览器范围,作用于@babel/preset-env。
.browserslistrc
很遗憾的是,@babel/runtime-corejs3无法在按需引入的基础上根据目标浏览器支持程度再次减少polyfill的引入
这意味着@babel/runtime-corejs3 甚至会在针对现代引擎的情况下注入所有可能的 polyfill:不必要地增加了最终捆绑包的大小。
对于组件库(代码量可能很大),个人建议将polyfill的选择权交还给使用者,在宿主环境进行polyfill。若使用者具有兼容性要求,自然会使用@babel/preset-env + core-js + .browserslistrc进行全局polyfill,这套组合拳引入了最低目标浏览器不支持API的全部 polyfill。
顺带一提,业务开发中,若将@babel/preset-env的useBuiltIns选项值设置为 usage,同时把node_modules从babel-loader中exclude,会导致babel 无法检测到nodes_modules中所需要的polyfill。
我是怎么做的呢,在mx-design中,我只是在mx-deisgn项目根目录添加了.browserslistrc文件,配置为:
所以配置修改为:
.babelrc.js
@babel/transform-runtime的helper选项默认为true。
gulp 配置
再来安装gulp相关依赖
新建gulpfile.js,写入以下内容:
gulpfile.js
修改package.json
package.json
执行yarn build
观察编译后的源码,可以发现:诸多helper方法已被抽离至@babel/runtime中,模块导入导出形式也是commonjs规范。
导出 ES module
生成ES module可以更好地进行tree shaking,基于上一步的babel配置,更新以下内容:
.babelrc.js
目标达成,我们再使用环境变量区分esm和cjs(执行任务时设置对应的环境变量即可),最终babel配置如下:
.babelrc.js
接下来修改gulp相关配置,抽离compileScripts任务,增加compileESM任务。
gulpfile.js
执行yarn build,可以发现生成了types/lib/esm三个文件夹,观察esm目录,结构同lib/types一致,js 文件都是以ES module模块形式导入导出
处理样式文件
拷贝 less 文件
我们会将less文件包含在npm包中,用户可以通过happy-ui/lib/alert/style/index.js的形式按需引入less文件,此处可以直接将 less 文件拷贝至目标文件夹。
在gulpfile.js中新建copyLess任务。
gulpfile.js
观察lib目录,可以发现 less 文件已被拷贝至组件的style目录下
可能有些同学已经发现问题:若使用者没有使用less预处理器,使用的是sass方案甚至原生css方案,那现有方案就搞不定了。经分析,有以下 3 种预选方案:
方案 1 会导致使用成本增加。
方案 2 无法对样式文件进行按需引入(后续在 umd 打包时我们也会提供该样式文件)。
以上两种方案实为下策。
方案 3 比较符合此时的的场景,ant design4版本使用的也是这种方案。
在搭建组件库的过程中,有一个问题困扰了我很久:为什么需要alert/style/index.js引入less文件或alert/style/css.js引入css文件?
答案是管理样式依赖。
假设存在以下场景:引入
<Button />
,<Button />
依赖了<Icon />
,使用者需要手动去引入调用的组件的样式(<Button />
)及其依赖的组件样式(<Icon />
),遇到复杂组件极其麻烦,所以组件库开发者可以提供一份这样的js文件,使用者手动引入这个js文件,就能引入对应组件及其依赖组件的样式。继续我们的旅程。
生成 css 文件
将less文件生成对应的css文件,在gulpfile.js中增加less2css任务
行yarn build,组件style目录下已经存在css文件了。
接下来我们需要一个alert/style/css.js来帮用户引入css文件。
生成 css.js
安装相关依赖。
gulpfile.js
cssInjection的实现:
再进行打包,可以看见组件style目录下生成了css.js文件,引入的也是上一步less转换而来的css文件。
按需加载
在 package.json 中增加sideEffects属性,配合ES module达到tree shaking效果(将样式依赖文件标注为side effects,避免被误删除)。
使用以下方式引入,可以做到js部分的按需加载(会自动引入less样式,所以你的开发环境例如webpack需要配置less-loader):
当然,如果你也可以引入没有css的js文件
全局css配置
@mx-design/web设置了全局和局部的css变量,可以让你去改变,全局css配置。
其中
lightTheme
是亮色主题,darkTheme
是暗色主题,所以,你可以自己写一份css变量名跟lightTheme一致的文件导入到MxConfigProvide中,实现自己的主题色切换。局部css配置
通过themeStyle来配置,具体每个组件和全局有哪些css变量支持,首先你可以在官网打开chrome浏览器的控制台查看,也可以查阅mx-design官网(待补充)
当然,你也可以在打包文件里单独引入css
引用(获得作者允许):
React 组件库搭建指南-打包输出
The text was updated successfully, but these errors were encountered: