(建议收藏)使用React+Typescript开发组件并发布到npm仓库
前言
这篇文章将来介绍如何使用React+Typescript开发一个组件并发布到npm仓库,此文中开发的组件是在实际项目中已开发使用的,将不再这赘述,下篇文章将具体讲解它的代码实现逻辑,本文主要介绍使用npm
发布一个React
组件供其他开发者下载使用的过程。在此实践流程中,踩了许多坑才成功将其发布,因而记录下整个流程,希望能帮助到想发布包到npm的各位小伙伴。
开发
这次要发布的组件是一个文本宽度自适应标签组件。当文本的容器固定之后,文字长度超过容器宽度时不想让文字省略显示而是需要文字全部显示出来,实现方案就是使用canvas
来绘制文字可避免去做动态改变字号的问题。
一、创建项目
首先新建项目文件夹,并使用npm init
命令初始化package.json
配置文件。如下:
mkdir textWidthAutoLabel
cd textWidthAutoLabel
npm init
在使用npm init
命令时,会提示输入项目的名称、版本号、关键词、作者等,可以一路回车结束,后续也可对package.json
文件进行修改。
这一步完成后,项目文件夹中就会新增一个package.json
文件。接下来添加一些目录和文件,以下为我的目录结构:
├── config # webpack配置
├── webpack.base.js # 公共配置
├── webpack.dev.config.js # 开发环境配置
└── webpack.prod.config.js # 打包发布环境配置
├── demo # 开发时预览代码
├── src # 示例代码目录
├── index.jsx # 入口 jsx 文件
├── index.html # 入口 html 文件
└── index.scss # 样式文件
├── lib # 组件打包结果目录
├── node_modules # 安装依赖时自动生成
├── src # 组件源代码目录
└── index.tsx # 组件源代码
├── .babelrc # babel 配置
├── .gitignore # git 忽略
├── .npmignore # 指定发布 npm 的时候须要忽略的文件和文件夹
├── README.md
├── package-lock.json
└── package.json
二、安装依赖与配置
接下来就是安装项目所需要的的依赖和对一些文件进行配置,请往下看→
2.1 安装React依赖
首先安装react
相关的依赖:
npm i react react-dom -D
2.2 安装webpack依赖
本项目采用webpack
进行构建,使用web-pack-dev-server
作为本地开发服务器,使用webpack-merge
合并webpack
配置,所以需要安装以下依赖:
npm i webpack webpack-cli webpack-dev-server webpack-merge -D
2.3 安装css相关依赖
此项目的组件代码是不含css
样式代码的,但是为了后续需要用到样式写组件的小伙伴,这里也安装处理样式的依赖,需要的自取就行,如下:
npm i postcss postcss-loader postcss-preset-env style-loader css-loader sass-loader node-sass mini-css-extract-plugin -D
2.4 安装babel相关依赖
接着安装babel
编译相关的依赖:
npm i @babel/cli @babel/core @babel/preset-env @babel/preset-react -D
2.5 typescript依赖
本项目的组件使用了typescript
进行类型限制,所以也需要安装支持TS
的依赖包:
npm i @types/react @types/react-dom ts-loader @babel/preset-typescript -D
2.6 配置文件修改
当执行完以上命令,会在package.json
中看到包含的依赖信息如下:
"devDependencies": {
// babel用于将es6+的代码转换成es5
"@babel/cli": "^7.17.10",
"@babel/core": "^7.18.5",
"@babel/preset-env": "^7.18.2", // 根据目标环境实现按需转码
"@babel/preset-react": "^7.17.12", // 让babel支持react语法
"@babel/preset-typescript": "^7.17.12",
"@types/react": "^18.0.14",
"@types/react-dom": "^18.0.5",
"babel-loader": "^8.2.5", // 编译jsx
"mini-css-extract-plugin": "^2.6.1", // 提取css
"react": "^18.2.0",
"react-dom": "^18.2.0",
"style-loader": "^3.3.1", // 将css编译成js
"postcss": "^8.4.14",
"postcss-loader": "^7.0.0",
"postcss-preset-env": "^7.7.2",
"css-loader": "^6.7.1",
"sass-loader": "^13.0.0",
"node-sass": "^7.0.1",
"ts-loader": "^9.3.1", // 支持ts
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0", // webpack4以后须要额外安装webpack-cli
"webpack-dev-server": "^4.9.2", // 开发是预览组件所用的服务,在文件变化时会自动刷新页面
"webpack-merge": "^5.8.0" // 用于合并webpack配置
}
接下来就是根据前面给出的目录结构,在config
目录下配置3
个webpack
文件:
- 因为开发和发布打包时
webpack
配置有一部分是公共并且重复的,因此把这部分的配置单独拿出来放到webpack.base.js
中,该文件内容以下:
module.exports = {
resolve: {
// 定义 import 引用时可省略的文件后缀名
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
module: {
rules: [
{
// 编译处理 js 和 jsx 文件
test: /(\.js(x?))|(\.ts(x?))$/,
use: [
{
loader: 'babel-loader'
}
],
exclude: /node_modules/, // 只解析 src 目录下的文件
}
]
},
};
- 开发时采用的
webpack
配置写在webpack.dev.config.js
中,内容以下:
const path = require('path');
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base.js'); // 公共配置
const devConfig = {
mode: 'development', // 开发模式
entry: path.join(__dirname, "../demo/src/index.jsx"), // 入口,处理资源文件的依赖关系
output: {
path: path.join(__dirname, "../demo/src/"),
filename: "dev.js",
},
module: {
rules: [
{
test: /.s[ac]ss$/,
exclude: /.min.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: {
mode: "global"
}
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'postcss-preset-env',
{
// 其他选项
},
],
],
},
},
},
{ loader: 'sass-loader' }
]
},
{
test: /.min.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' }
]
}
]
},
devServer: {
static: path.join(__dirname, '../demo/src/'),
compress: true,
host: '127.0.0.1',
port: 8686, // 启动端口
open: true // 打开浏览器
},
};
module.exports = merge(devConfig, baseConfig); // 合并配置
- 打包组件时采用的
webpack
配置写在webpack.prod.config.js
中,内容以下:
const path = require('path');
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base.js'); // 引用公共的配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 用于将组件的css打包成单独的文件输出到`lib`目录中
const prodConfig = {
mode: 'production', // 生产模式
entry: path.join(__dirname, "../src/index.tsx"),
output: {
path: path.join(__dirname, "../lib/"),
filename: "index.js",
libraryTarget: 'umd', // 采用通用模块定义
libraryExport: 'default', // 兼容 ES6 Module、CommonJS 和 AMD 模块规范
},
module: {
rules: [
{
test: /.s[ac]ss$/,
exclude: /.min.css$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
modules: {
mode: "global"
}
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'postcss-preset-env',
{
// 其他选项
},
],
],
},
},
},
{ loader: 'sass-loader' }
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "index.min.css" // 提取后的css的文件名
})
],
externals: { // 定义外部依赖,避免把react和react-dom打包进去
react: {
root: "React",
commonjs2: "react",
commonjs: "react",
amd: "react"
},
"react-dom": {
root: "ReactDOM",
commonjs2: "react-dom",
commonjs: "react-dom",
amd: "react-dom"
}
},
};
module.exports = merge(prodConfig, baseConfig); // 合并配置
- 使用
babel
把代码编译成es5
版本,在项目根目录下建好的.babelrc
文件内补充如下内容:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
]
}
- 在
git
提交代码进行版本管理时需要在.gitignore
中添加如下配置:
# 指定发布 npm 的时候需要忽略的文件和文件夹
# npm 默认不会把 node_modules 发上去
# dependencies
/node_modules
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
- 完成
webpack
配置文件的编写以后,须要在package.json
中的scripts
字段中配置相关的启动、打包和发布命令,在package.json
中添加的脚本内容以下:
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"dev": "webpack-dev-server --config config/webpack.dev.config.js",
"build": "webpack --config config/webpack.prod.config.js"
}
至此,依赖和配置就已完成,下面就开始开发组件的逻辑。
三、开发组件
这一部分不讲解具体代码的实现逻辑,只是贴出来让小伙伴们能正常往下进行,请往下看→
3.1 编写组件代码
在src/index.tsx
中添加组件逻辑代码:
import React, { useEffect, useRef } from 'react';
interface WidthAutoLabelProps {
children: string; // 要绘制的文本
}
/**
* 文本宽度自适应标签组件
* @param props
* @constructor
*/
const WidthAutoLabel = (props: WidthAutoLabelProps) => {
const { children = '' } = props;
const ref = useRef<any>(null);
useEffect(() => {
drawText();
}, [children]);
const drawText = () => {
let canvas = ref.current;
if (!canvas) {
return;
}
let parentNode = canvas.parentNode;
if (!parentNode) {
return;
}
const { fontSize, fontStyle, fontWeight, fontFamily } = getComputedStyle(parentNode, null);
const { clientWidth, clientHeight } = parentNode;
canvas.width = clientWidth;
canvas.height = clientHeight;
let ctx = canvas.getContext("2d");
// 设置文本样式
ctx.font = `${fontStyle} ${fontWeight} ${fontSize} ${fontFamily}`;
ctx.textBaseline = 'top';
let y = Math.ceil((clientHeight - Number(fontSize.replace('px', ''))) / 2);
let x = 0;
// 居中显示
const { width: textWidth } = ctx.measureText(children);
if (textWidth < clientWidth) {
x = (clientWidth - textWidth) / 2;
}
// 绘制文本
ctx.fillText(children, x, y, clientWidth);
}
return (
<canvas ref={ref}></canvas>
)
}
export default WidthAutoLabel;
如果你写的组件用到了样式代码,那需要在src
文件中增加index.d.ts
文件,这样tsx
文件才能支持引入css
样式:
declare module '*.scss' {
const content: any;
export default content;
}
3.2 调试组件
为了能在开发时实时预览组件的效果,需要在demo
目录下新建index.html
和index.jsx
两个文件,index.html
的内容以下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>text-width-auto-label</title>
<style>
html, body, p {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="dev.js"></script>
</body>
</html>
index.html
文件中引入了文件名为 dev.js
的脚本,缘由是在开发过程(使用webpack-dev-sevrer
启动开发服务)时,并不会实际在src
目录下生成 dev.js
,打包好的文件是在内存中的,若是要实时预览效果,须要在html
中引入。
index.jsx
的内容以下:
import React from "react"
import { createRoot } from 'react-dom/client';
import WidthAutoLabel from '../../src/index'; // 引入组件
import './index.scss';
// import WidthAutoLabel from 'text-width-auto-label';
const App = () => {
return (
<div className="container">
<div className="text">
<p>示例文本:</p><p>文本宽度自适应标签组件</p>
</div>
<div className="normal">
<p>宽度100px:</p><p>文本宽度自适应标签组件</p>
</div>
<div className="style1">
<p>宽度100px:</p><p><WidthAutoLabel>文本宽度自适应标签组件</WidthAutoLabel></p>
</div>
<div className="style2">
<p>宽度80px:</p><p><WidthAutoLabel>文本宽度自适应标签组件</WidthAutoLabel></p>
</div>
<div className="style3">
<p>宽度50px:</p><p><WidthAutoLabel>文本宽度自适应标签组件</WidthAutoLabel></p>
</div>
</div>
);
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
index.scss
的内容以下:
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 30px;
font-size: 14px;
font-weight: bold;
color: #333;
.text {
display: flex;
align-items: center;
p:nth-child(2) {
color: #f00;
}
}
.normal {
display: flex;
align-items: center;
margin-top: 10px;
p:nth-child(2) {
width: 100px;
height: 32px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border: 1px solid #ccc;
}
}
.style1 {
display: flex;
align-items: center;
margin-top: 10px;
p:nth-child(2) {
width: 100px;
height: 32px;
border: 1px solid #ccc;
}
}
.style2 {
display: flex;
align-items: center;
margin-top: 10px;
p:nth-child(2) {
width: 70px;
height: 32px;
border: 1px solid #ccc;
}
}
.style3 {
display: flex;
align-items: center;
margin-top: 10px;
p:nth-child(2) {
width: 40px;
height: 32px;
border: 1px solid #ccc;
}
}
}
如上文件的代码编写完,就可在根目录下打开cmd
,执行npm run dev
命令,当看到下面的截图结果,则表示编译成功:
然后在浏览器上输入http://127.0.0.1:8686/
,则会看到下图的结果:
3.3 打包与测试
完成组件的开发,在发包以前,需要对本身组件的功能进行打包和测试。前面已经在package.json
中添加了打包组件的命令,然而在执行npm run build
以前,还需要在package.json
中修改main
字段,用作是声明组件的入口文件。
"main": "/src/index.tsx",
至此,package.json
的完整代码如下:
{
"name": "text-width-auto-label",
"version": "0.0.1",
"description": "文本宽度自适应标签组件",
"main": "/src/index.tsx",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"dev": "webpack-dev-server --config config/webpack.dev.config.js",
"build": "webpack --config config/webpack.prod.config.js"
},
"keywords": [
"react",
"text-auto",
"width-auto",
"text-width-auto"
],
"author": "jacky010",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.17.10",
"@babel/core": "^7.18.5",
"@babel/preset-env": "^7.18.2",
"@babel/preset-react": "^7.17.12",
"@babel/preset-typescript": "^7.17.12",
"@types/react": "^18.0.14",
"@types/react-dom": "^18.0.5",
"babel-loader": "^8.2.5",
"mini-css-extract-plugin": "^2.6.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"style-loader": "^3.3.1",
"postcss": "^8.4.14",
"postcss-loader": "^7.0.0",
"postcss-preset-env": "^7.7.2",
"css-loader": "^6.7.1",
"sass-loader": "^13.0.0",
"node-sass": "^7.0.1",
"ts-loader": "^9.3.1",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.2",
"webpack-merge": "^5.8.0"
}
}
接下来,则可使用npm run build
命令打包开发好的组件,出现下面的结果则表明打包成功:
此时,项目根目录的lib
目录下会出现index.js
文件(如果组件含有样式文件,那将会出现index.css
文件),这里我的组件没有样式文件,则不会有index.css
文件:
打包的任务已完成,现在是要来进行发布前的测试了,使用npm link
把打包以后的组件引入到全局node-modules
中,如下:
然后进入demo/src
文件夹下,执行npm link text-width-auto-label
命令(注意:这里的text-width-auto-label
一定要是package.json
配置的name
,如果自己再取一个包名一定要去修改package.json
中的name
才会成功,否则将会报错,切记!切记!切记!);而后修改demo/src/index.tsx
的内容:
import React from "react"
import { createRoot } from 'react-dom/client';
// import WidthAutoLabel from '../../src/index'; // 注释它引入组件
import './index.scss';
import WidthAutoLabel from 'text-width-auto-label'; // 引入node-modules中的组件
const App = () => {
return (
<div className="container">
<div className="text">
<p>示例文本:</p><p>文本宽度自适应标签组件</p>
</div>
<div className="normal">
<p>宽度100px:</p><p>文本宽度自适应标签组件</p>
</div>
<div className="style1">
<p>宽度100px:</p><p><WidthAutoLabel>文本宽度自适应标签组件</WidthAutoLabel></p>
</div>
<div className="style2">
<p>宽度80px:</p><p><WidthAutoLabel>文本宽度自适应标签组件</WidthAutoLabel></p>
</div>
<div className="style3">
<p>宽度50px:</p><p><WidthAutoLabel>文本宽度自适应标签组件</WidthAutoLabel></p>
</div>
</div>
);
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
同样能看到下图,则表示打包的组件测试验证通过;
四、编写Readme文档
接下来,就要做发布组件到npm
的readme
文档的编写了。这里采用的是readme-md-generator插件,它是一个非常流行的readme
自动生成工具。
执行npx readme-md-generator
命令,接着可以做一些配置,也可以一路enter
键,最后再去生成的文件中做详细配置:
五、发布
万事俱备只欠东风,接下来就是发布组件的最后一步了。
5.1 注册npm账号
要发布一个npm
包,咱们需使用以下命令添加一个npm
的帐号(若是已经添加过的这一步能够跳过):
npm adduser
按提示信息输入本身的用户名、密码及邮箱后,若是命令行输出Logged in as <user> on https://registry.npmjs.org/
,则说明帐号注册并登录成功。若是已经有npm
帐号,则直接使用npm login
登录。
因为国内使用npm
官方源安装包的时候比较慢,基本上在国内开发都会修改npm
源地址,在发包以前,必定要切换到npm
源才能够,否则就会报出以下错误:
error: no_perms Private mode enable, only admin can publish this module
可使用npm config list
查看当前使用的源地址,若是不是官方地址,则能够经过下面的命令切换npm
源:
npm config set registry http://registry.npmjs.org
然后登录npm
,执行npm login
命令,输入用户名、密码、邮箱即可;最后使用默认的发包方式,执行package.json
中的脚本:
npm run pub
当看到下图就表示发包成功:
这时登录npm
就可以看到你刚刚发布的包了,如下:
如果别人对你发布的包感兴趣或者适用于他的项目场景,他可以通过以下命令下载你的包并在他的项目中进行使用:
npm i text-width-auto-label
下面就是我新创建的react
项目,并下载了text-width-auto-label
组件测试得到的效果图,如下:
经过测试,该组件可以在项目中使用,所以小伙伴们可以放心下载使用。但是如果你的项目的ts
和webpack
的版本不是最新版本可能会有问题,因为我这个组件使用的插件均是最新版本,所以可能会存在兼容版本问题,若出现报错问题可以自行更新相应的插件版本,若不想更新版本,那就将代码复制出来自行在项目中创建组件使用吧……
若出现上图ts
标红提示,可以选择ts
选择忽略或继续向下选择文件:
最后,这篇文章要聊的主题就聊完了,如果你也想发布包到npm
但苦于没有很好的讲解文章,你不妨可以参考此篇文章,我相信如果认真读完这篇你一定会成功地把自己的组件包发布成功到npm
上的,赶紧进来码住并按着方法一步步去实现吧。(至于文中提到的组件没有细致的去讲解他的实现过程,将会在下一篇文章进行讲解,敬请期待^_^)
资源
之前发布的包资源请前往以下地址:
npm地址:https://www.npmjs.com/settings/jacky010/packages
github仓库:https://github.com/Jacky010
后语
伙伴们,如果觉得本文对你有些许帮助,点个👍或者➕个关注再走呗^_^ 。另外如果本文章有问题或有不理解的部分,欢迎大家在评论区评论指出,我们一起讨论共勉。
- 点赞
- 收藏
- 关注作者
评论(0)