# rollup 打包组件库和 react+ts+less 项目
# 一. 初始化项目
mkdir y-design
cd y-design
npm init -y
# 二. 实现 react+ts 打包组件库或者工具库
# 1. 安装 rollup 基础依赖
npm i -D rollup @rollup/plugin-commonjs @rollup/plugin-node-resolve
@rollup/plugin-commonjs
:rollup
收集依赖打包,是依赖esModule
规范,而引入第三方commonjs
规范的代码需要使用该插件进行转化为esModule
才能使用rollup-plugin-commonjs
rollup-plugin-commonjs
已被弃用,所以使用@rollup/plugin-commonjs
@rollup/plugin-node-resolve
: 如果没有该插件import
引入的 第三包,会被转为require
形式,不会将包打进入bundle.js
rollup-plugin-node-resolve
rollup-plugin-node-resolve
已被弃用,所以使用@rollup/plugin-node-resolve
# 2.安装 react 相关依赖
npm i react react-dom && npm i -D @types/react @types/react-dom
# 3.安装 typescript 相关依赖
npm i -D typescript rollup-plugin-typescript2
# 4.安装 css 相关依赖
npm i -D less postcss postcss-less rollup-plugin-postcss classnames
rollup-plugin-postcss
: 默认支持 less、scss,但是需要安装对应前置插件classnames
: 可以使用函数来组合 jsx 语法的 class 名
# 4.编辑以下文件
tsconfig.json
{
"compilerOptions": {
"jsx": "react", // 可以使用jsx或tsx
"baseUrl": ".",
"outDir": "./", // 输出目录
"sourceMap": false, // 是否生成sourceMap
"target": "esnext", // 编译目标
"module": "esnext", // 模块类型
"moduleResolution": "node",
"allowJs": false, // 是否编辑js文件
"strict": true, // 严格模式
"noUnusedLocals": true, // 未使用变量报错
"experimentalDecorators": true, // 启动装饰器
"resolveJsonModule": true, // 加载json
"esModuleInterop": true,
"removeComments": false, // 删除注释
"declaration": true, // 生成定义文件
"declarationMap": false, // 生成定义sourceMap
// "declarationDir": "./lib/types", // 定义文件输出目录
"lib": ["esnext", "dom"], // 导入库类型定义
"types": ["node"] // 导入指定类型包
},
"include": ["packages", "typings.d.ts"],
"exclude": ["node_modules"]
}
rollup.config.js
import typescript from "rollup-plugin-typescript2";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import path from "path";
// import babel from "@rollup/plugin-babel";
//babel: 转换为es5语法,需要安装@babel/core,@babel/preset-env
// import { terser } from "rollup-plugin-terser";
//terser 压缩代码
import pkg from "./package.json";
// import cssnanoPlugin from "cssnano";
import postcss from "rollup-plugin-postcss";
// import autoprefixer from "autoprefixer";
// import clear from "rollup-plugin-clear";
const resolve = (p) => path.resolve(__dirname, p);
export default {
input: resolve("packages/index.ts"),
output: [
{
file: resolve("lib/cjs/index.js"),
format: "cjs",
name: pkg.name,
},
{
file: resolve("lib/es/index.js"),
format: "es",
name: pkg.name,
},
{
file: resolve("lib/umd/index.js"),
format: "umd",
name: pkg.name, //umd 模块 必须给name
},
],
plugins: [
// clear({
// targets: ["lib"],
// }),
postcss(
// {
// plugins: [autoprefixer(), cssnanoPlugin()],
// }
),
commonjs(),
nodeResolve({
extensions: [".ts", ".tsx"]
}),
typescript(),
// babel({
// exclude: "node_modules/**",
// extensions: [".ts", ".tsx"], //需要手动配置后缀,不然文件ts文件不会被转成es5
// }),
// terser(),
],
// 指出应将哪些模块视为外部模块 就不会被打包
external: ["react", "react-dom"],
};
packages/index.ts
import Button from "./Button/Button";
const count = (x: number,y: number) => {
return x + y;
}
export {
count,
Button
}
packages/Button/Button.tsx
import React from "react";
import classnames from "classnames";
import './index.less';
const prefix = 'y';
export default (props: {
children: React.ReactNode;
type?: "default" | "primary";
}) => {
const cName = `${prefix}-button`;
return (
<button
className={classnames(cName, cName + '-' + (props.type || "default"))}
>
{props.children}
</button>
);
};
packages/Button/index.less
.y-button{
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px #00000004;
cursor: pointer;
transition: all .3s cubic-bezier(.645,.045,.355,1);
user-select: none;
touch-action: manipulation;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: 2px;
color: #000000d9;
border-color: #d9d9d9;
background: #fff;
&-primary{
color: #fff;
border-color: #1890ff;
background: #1890ff;
text-shadow: 0 -1px 0 rgb(0 0 0 / 12%);
box-shadow: 0 2px #0000000b;
}
}
# package.json
{
...,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:pro": "rollup -c"
},
...
}
# 5.运行命令即可打包
npm run build:pro
# 6.优化代码,安装插件,编辑.babelrc,根据安装的插件将上面文件中的注释恢复
npm i -D @rollup/plugin-babel @babel/core @babel/preset-env rollup-plugin-terser cssnano autoprefixer rollup-plugin-clear
@rollup/plugin-babel
: 将 es6 以上的代码装换为 es5,前置依赖@babel/core @babel/preset-env
rollup-plugin-babel
rollup-plugin-babel
已被弃用,所以使用@rollup/plugin-babel
rollup-plugin-terser
: js 压缩cssnano
: css 压缩rollup-plugin-clear
: 重新编译的时候清除上一次的文件
.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"ie": 10
//如果代码运行的浏览器支持 ES Modules,应当指定如下配置.不过,此时 browsers 选项将被忽略
// "esmodules": true
}
}
]
]
}
# 三. 实现 react+ts 引用组件库或者工具库
# 1. 在以上基础下,再创建rollup.config.dev.js
,examples/App.tsx
,examples/index.tsx
,
# examples/style.less
,examples/typeings.d.ts
,public/index.html
rollup.config.dev.js
import typescript from "rollup-plugin-typescript2";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import path from "path";
import babel from "@rollup/plugin-babel";
//babel: 转换为es5语法,需要安装@babel/core,@babel/preset-env
import { terser } from "rollup-plugin-terser";
//terser 压缩代码
// import pkg from "./package.json";
import cssnanoPlugin from "cssnano";
import postcss from "rollup-plugin-postcss";
import replace from "@rollup/plugin-replace";
import autoprefixer from "autoprefixer";
import clear from "rollup-plugin-clear";
const resolve = (p) => path.resolve(__dirname, p);
//预览
import htmlTemplate from "rollup-plugin-generate-html-template";//
import livereload from "rollup-plugin-livereload";//
import serve from "rollup-plugin-serve";//
export default {
input: resolve("examples/index.tsx"),//
output: {//
file: "dist/main.js",//
format: "cjs",//
},//
plugins: [
clear({
targets: ["dist"],//
}),
//
htmlTemplate({
template: "public/index.html",
target: "dist/index.html",
}),
serve("dist"),
livereload("examples"),
replace({
preventAssignment: true,
"process.env.NODE_ENV": JSON.stringify("production"), // 否则会报:process is not defined的错
}),
//
postcss({
plugins: [autoprefixer(), cssnanoPlugin()],
modules: true// 可以使用 import style from './style.less';
}),
commonjs(),
nodeResolve({
extensions: [".ts", ".tsx"]
}),
typescript(),
babel({
exclude: "node_modules/**",
extensions: [".ts", ".tsx"], //需要手动配置后缀,不然文件ts文件不会被转成es5
}),
terser(),
],
// 指出应将哪些模块视为外部模块 就不会被打包
// external: ["react", "react-dom"], 生产或开发的时候需要打包进去
};
examples/App.tsx
import React from "react";
import { Button } from "../lib/es";
import style from './style.less';
function App() {
return (
<div className={style.App}>
<header className="App-header">header</header>
<Button type="primary">primary</Button>
<Button>default</Button>
</div>
);
}
export default App;
examples/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
examples/style.less
.App{
width: 800px;
margin: 10px auto;
}
examples/typeings.d.ts
declare module '*.less' {
const classes : { readonly [key: string]: string }
export default classes
}
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
注意一下 tsconfig.json
tsconfig.json
这个文件需要注意一下,npm run build:pro
的时候 "include": ["packages", "typings.d.ts"]
,
npm run dev
的时候要加上"examples"
,即:"include": ["packages", "typings.d.ts","examples"]
# 2. 添加 dev 命令
# package.json
{
...,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:pro": "rollup -c",
"dev": "rollup -wc rollup.config.dev.js"
},
...
}
# 3. 安装依赖
npm i -D rollup-plugin-generate-html-template rollup-plugin-livereload rollup-plugin-serve
rollup-plugin-generate-html-template
: 使用 html 模板rollup-plugin-livereload
: 实时刷新页面rollup-plugin-serve
: 启动一个服务器
# 4.运行命令实现在线预览
npm run dev
# 四. 添加代码规范 eslint+prettier+stylelint+husky+lint-staged
# 1. eslint
npm i eslint -D
npx eslint --init
npx eslint --init
根据选择自定义安装依赖,完成以后生成.eslintrc.js.eslintrc.js
module.exports = { env: { browser: true, es2021: true, }, extends: [ 'eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', ], overrides: [], parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true, },//这几行自己加的 ecmaVersion: 'latest', sourceType: 'module', }, plugins: ['react', '@typescript-eslint'], rules: { 'no-undef': 2,//这几行自己加的 '@typescript-eslint/no-unused-vars': 2,//这几行自己加的 }, };
# 2. prettier
npm i prettier eslint-config-prettier eslint-plugin-prettier -D
eslint-config-prettier
解决eslint
和prettier
的冲突配置,保证 prettier 格式不出错eslint-plugin-prettier
配置eslint --fix
采用prettier
的配置规则格式化
创建.prettierrc.js
'use strict';
/** @format */
module.exports = {
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
proseWrap: 'never',
endOfLine: 'lf',
overrides: [
{
files: '.prettierrc',
options: {
parser: 'json',
},
},
{
files: 'document.ejs',
options: {
parser: 'html',
},
},
],
};
# 3.stylelint
npm i stylelint stylelint-config-css-modules stylelint-config-prettier stylelint-config-standard stylelint-declaration-block-no-ignored-properties -D
stylelint-config-prettier
解决stylelint
和prettier
的冲突配置,保证 prettier 格式不出错
创建.stylelintrc.js
'use strict';
/** @format */
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-css-modules',
'stylelint-config-prettier',
],
customSyntax: 'postcss-less',
plugins: ['stylelint-declaration-block-no-ignored-properties'],
rules: {
'no-descending-specificity': null,
//https://github.com/stylelint/stylelint/issues/4114
// 'function-calc-no-invalid': null,
'function-url-quotes': 'always',
'selector-attribute-quotes': 'always',
'font-family-no-missing-generic-family-keyword': null,
'plugin/declaration-block-no-ignored-properties': true,
'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
// webcomponent
'selector-type-no-unknown': null,
'value-keyword-case': ['lower', { ignoreProperties: ['composes'] }],
'declaration-block-no-shorthand-property-overrides': null,
},
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
};
# 4.husky + lint-staged
npm i husky lint-staged -D
husky
: husky 是 Git hooks 工具,提供很多 git 操作的钩子
编辑package.json
文件,并运行 prepare 命令,进行初始化 husky
# package.json
{
...,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:pro": "rollup -c",
"dev": "rollup -wc rollup.config.dev.js",
"prepare": "husky install"
},
"lint-staged": {
"**/**.{js,jsx,json,ts,tsx}": [
"prettier --write",
"eslint --fix",
"git add"
],
"**/*.less": [
"prettier --write",
"stylelint --fix",
"git add"
]
},
...
}
npm run prepare
npx husky add .husky/pre-commit "npx lint-staged"
配置完成,进行测试
git init
git add .
git commit -m "test"
# 五. babel 的优化配置策略
- 安装依赖
npm install @babel/runtime @babel/runtime-corejs3
- 编辑
.babelrc
,rollup.config.js
,rollup.config.dev.js
# .babelrc
{
"presets": [
[
"@babel/preset-env",
]
],
"plugins": [
"@babel/plugin-external-helpers",// 把 helpers 收集到一个共享模块或共享文件
[
"@babel/plugin-transform-runtime",//依赖@babel/runtime 生产依赖
{
"corejs": 3,//配置安装 @babel/runtime-corejs3 生产依赖
"useESModules": true,//不转换esm的代码,让其支持tree-shaking。交给rollup去转换esm。
}
]
]
}
# rollup.config.js rollup.config.dev.js
...,
babel({
exclude: 'node_modules/**',
extensions: [ '.ts', '.tsx'], //需要手动配置后缀,不然文件ts文件不会被转成es5
babelHelpers: 'runtime',
}),
...
← 前端打包工具