目录
如何将TS转换为ES6 modules
在create-react-app中运行的代码,首先通过tsc将.tsc文件转换为.jsx文件(ES6 modules),
关于tsc的配置都写在tsconfig.json中,create-react-app有一个默认的tsconfig.json,但这个tsconfig.json和开发环境相关,而我们使用的tsconfig和最后打包模块相关的,所以我们单独新建tsconfig.build.json:
{
"compilerOptions": {
"outDir": "dist",//输出文件路径
"module": "esnext",//输出的module类型
"target": "es5",// 编译后符合什么样的ES标准
"declaration": true,// 会为每个js文件生成对应的.d.ts类型文件
"jsx": "react",// 编译出来的的文件,里面的代码就能用reactCreateElement方法代替JSX语法
//"moduleResolution":"Node",//
/*
allowSyntheticDefaultImports默认为false,此时引入默认模块:import * as React from 'react'
为true时:import React from 'react'。我们使用的都是这种,所以这里还要配置为true
*/
//"allowSyntheticDefaultImports": true,
},
"include": [// 要编译的文件
"src"
],
"exclude": [//不编译的文件,**代表任意长度,*代表全匹配
"src/**/*.test.tsx",
"src/**/*.stories.tsx",
"src/setupTests.ts",
]
}
在package.json中的"script"中添加命令:"build-ts": "tsc -p tsconfig.build.json"
,他的作用是把ts文件打包成es module文件。配置好了之后运行:npm run build-ts
,然后报错了:cannot find module '@babel/types'
这是因为TS处理模块的方式和node不一样,模块加载分为相对路径和绝对路径,默认是classic方法,即相对路径,
如果改为node方法,即绝对路径:
所以我们还需要在tsconfig.build.json:中配置:"moduleResolution":"Node"
然后有运行:npm run build-ts
,继续报错:
这是因为allowSyntheticDefaultImports默认为false,此时引入默认模块:import * as React from 'react'
,为true时:import React from 'react'
,我们使用的都是这种,所以这里还要配置还需要在tsconfig.build.json中配置allowSyntheticDefaultImports为true:"allowSyntheticDefaultImports": true
生成最终的样式文件
在create-react-app中已经安装使用了sass,现在可以使用node-sass来将sass文件编译成css文件:node-sass 源文件地址 输出的css文件地址
。先在package.json中的"scripts"中新增:"build-css": "node-sass ./src/styles/index.scss ./build/index.css"
,并修改:"build": "npm run build-ts && npm run build-css"
。然后在项目终端先删除之前打包的build文件:rm -rf build
,然后再重新打包:npm run build
,成功打包。
以后每次打包前都需要删除之前打包的build文件,我们希望在运行npm run build
后,系统自动帮我们打包前都自动帮我们删除了之前打包的build文件。使用rimraf完成跨平台的删除任务,在项目终端输入:npm install rimraf --save-dev
,然后在package.json中的"scripts"中新增:"clean": "rimraf ./dist"
,并修改:"build": "npm run clean && npm run build-ts && npm run build-css"
。
以后每次打包直接运行npm run build
即可,无需手动删除之前打包的build文件
使用npm link本地测试组件库
包发布之前需要在本地测试一下打包出来的格式文件是否好用,假如我有两个项目,一个是本地测试项目vikingTest,一个是本地项目vikingShip组件库,前者要使用后者作为他的依赖,那么在前者的package.json中配置:'vikingship:'0.1.0'
,假如vikingShip已经发布到npm上,我们可以使用npm install
来安装。现在我们修改了vikingShip的内容,需要在vikingTest中测试,最笨的方法就是,修改vikingShip并发布到线上,发布为一个新的版本0.1.1,发布完成后,在vikingTest的package.json中修改vikingShip的版本号为:'vikingship:'0.1.1'
,然后再重新安装vikingShip。
我们希望vikingTest可以直接关联本地vikingShip,这样vikingShip的修改就是实时的,不用重新安装,你引用的就是他本地的文件,那么我们可以在vikingShip文件夹终端:npm link
创建软连接到全局,再在vikingTest终端:npm link vikingship
创建软连接,连接到vikingShip刚创建的软连接(他是全局的),然后该软连接再连接到vikingShip。vikingTest import vikingShip时,他需要知道怎样引入哪几个文件,所以需要在vikingShip的package.json中添加:
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts"
然后在vikingTest的package.json中"dependencies"添加个假依赖:”vikingship": "0.1.0"“
然后在app.tsx中使用vikingShip,再使用npm start
运行即可
发布到NPM,添加CICD支持
- 注册账号:
方法一:NPM上注册账号
方法二:终端输入npm whoami
查看自己的身份,如果报错说明你没登录,使用npm adduser
登录/注册账户,输入用户名密码邮箱即可。
注意,由于npm太慢了,很多人用的是淘宝的镜像,终端输入:
npm config ls
查看当前信息metrics-registry,如果不是https://registry.npmjs.org/
,需要删除镜像,切换回https://registry.npmjs.org/
,否则你无法注册登录
- 配置package.json:
{
"name": "vikingship",// package名
/*
版本号。
格式:主版本号.次版本号.修订号
主版本号:做了不兼容的API修改
次版本号:做了向下兼容的功能性新增
修订号:做了向下兼容的问题修正
*/
"version": "0.1.4",
"description": "React components library",// 描述包的内容
"author": "Viking Zhang",// 作者
"private": false,// 是否是私有的包
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",// 开源软件遵守的协议
"keywords": [// 关键字,便于搜索
"Component",
"UI",
"React"
],
"homepage": "http://vikingship.xyz",// 项目主页
"repository": {// github仓库
"type": "git",
"url": "https://github.com/vikingmute/vikingship"
},
"files": [// 把哪些文件上传到npm中
"dist"// 注意:之前我们把打包后的文件输出到build文件中,这种情况下,这里应该写"build"。
// 现在我们把打包后的文件输出到dist文件中,所以这里写的dist,包括全文所有输出文件的地方,都改为dist
],
"dependencies": {
//图标
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/react-fontawesome": "^0.1.8",
"axios": "^0.25.0",// 发送请求
"classnames": "^2.2.6",// 合并类名
"react-transition-group": "^4.3.0",
// 声明types定义
"@types/classnames": "^2.2.9",
"@types/jest": "24.0.23",
"@types/node": "12.12.14",
"@types/react": "^16.9.13",
"@types/react-dom": "16.9.4",
"@types/react-transition-group": "^4.2.3",
"@types/storybook__addon-info": "^5.2.1",
"node-sass": "^4.14.1",// 预处理器
"react": "^16.12.0",
"react-docgen-typescript-loader": "^3.6.0",
"react-dom": "^16.12.0",
"react-scripts": "3.2.0",// 开发的工具
"typescript": "3.7.2"// TS编译器
},
"scripts": {
"start": "react-scripts start",
"clean": "rimraf ./dist",
"build": "npm run clean && npm run build-ts && npm run build-css",
"test": "react-scripts test",
"eject": "react-scripts eject",
"build-ts": "tsc -p tsconfig.build.json",// 把ts文件打包成es module文件
"build-css": "node-sass ./src/styles/index.scss ./dist/index.css",
"storybook": "start-storybook -p 9009 -s public",// 把storybook生成静态文档页面
"build-storybook": "build-storybook -s public",
//在publish之前我们要编译生成一下最终的代码,所以我们要添加一个钩子函数prepublishOnly,他会自主运行我们在prepublishOnly中配置的代码
"prepublishOnly": "npm run build"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@storybook/addon-actions": "^5.2.8",
"@storybook/addon-info": "^5.3.21",
"@storybook/addon-links": "^5.2.8",
"@storybook/addons": "^5.2.8",
"@storybook/react": "^5.2.8",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"cross-env": "^7.0.0",
"husky": "^4.2.1",
"rimraf": "^3.0.1",
}
}
- 在项目终端输入:
npm publish
发布
精简package.json中的"dependencies"
依赖分为两类:
(1)“dependencies”:运行项目业务逻辑需要依赖的第三方库,当运行npm install
时,这些依赖都会被解析下载到node_modules中
(2)“devDependencies”:开发模式工作流(与核心业务逻辑和最终生成的模块无关的任务,这些任务又支撑着核心业务的开发过程,以及程序从开发环境向生产环境的支撑过程,如单元测试、语法转换、CSS预处理器、程序构建等)下需要依赖的第三方库都可以声明到该类之下
目前,我们"dependencies"中有很多库,当运行npm install
时,这些库都会被安装,但是在使用时,很多都没有被用到,这完全是浪费,所以我们需要把这些库都移动到"devDependencies"中:
- types用于声明types定义,和最终的产品没有关系,移动到"devDependencies"
- node-sass是预处理器,将sass文件转化为css文件,移动到"devDependencies"
- react-scripts是开发工具,是creat-react-app自带的脚本,移动到"devDependencies"
- typescript是TS编译器,移动到"devDependencies"
- 当react项目使用组件库时,组件库有react版本,react项目也有react版本,react会被安装两次,当有两份react同时引到源代码中时,会报一个hook错误,react是我们整个组件库的核心依赖库,所以组件库运行的前提是核心依赖库必须先下载安装,不能脱离核心依赖库而被单独依赖并引入,npm的package.json中有一个字段"peerDependencies"提供核心依赖库的信息,告诉用户,如果你想使用我的插件或库,你必须先安装这些依赖。我们在该package.json中添加该字段:
"peerDependencies": {
// 核心库:react、react-dom 且版本都大于等于16.8.0 16.8.0之后才引入了react hook
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
然后在"dependencies"中去掉这两项,这样就能解决react安装多次的问题,但是在开发时我们还需要这两个库,所以我们需要将这两项(刚从"dependencies"中去掉内容)移动到"devDependencies"中,在本地npm install
时就有这两个依赖了
添加发布和commit前检查
单元测试用来保证代码中尽量减少bug,代码规范有助于项目的维护,为了防止bug和不规范的代码被commit,并push到远端,还要防止他们被publish,被用户直接使用,需要一些钩子函数,首先验证开发者是否通过代码规范和单元测试,然后才能进行commit和publish。
- 代码规范的检查:在开发环境中,creat-react-app自带eslint,会自动启动。现在我们需要添加一个单独的npm命令,一运行,他就检查代码规范,也就是说把代码规范检查这一功能单独剥离出来,我手动启动。在package.json中"scripts"中添加命令:
"lint": "eslint --ext js,ts,tsx src --max-warnings 5"
在src中检查扩展名(–ext)文件js,ts,tsx
–max-warnings 5:运行允许最多的warnings数量为5
- 添加测试用例:在package.json中"scripts"中已经有test命令了,但这个test命令默认在开发时使用,他不会返回最终通过/没通过的结果,而是处于watch模式下,我们需要一个测试命令,和lint一样,可以直接返回结果,但是我们发现,在不同操作环境下,设置测试命令的代码是不一样的。推荐使用cross-env,他能很方便的跨平台设置环境变量,在项目终端安装:
npm install cross-env --save-dev
,然后在package.json中"scripts"中添加命令:"test:nowatch": "cross-env CI=true react-scripts test"
。终端运行:npm run test:nowatch
,会出现测试结果。 - 更改package.json中"scripts"的prepublishOnly:
"prepublishOnly": "npm run test:nowatch && npm run lint && npm run build"
- 以上,我们就把代码规范检查、添加测试用例两个需求剥离出来了,并且在publish前会进行测试和代码规范检查。接下来我们可以使用husky让程序在push和commit前也进行测试和代码规范检查。在项目终端安装:
npm install husky --save-dev
,然后在package.json中添加:
"husky": {
"hooks": {
"pre-commit": "npm run test:nowatch && npm run lint"
}
},
综上,package.json代码:
{
"name": "vikingship",
"version": "0.1.4",
"description": "React components library",
"author": "Viking Zhang",
"private": false,
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"keywords": [
"Component",
"UI",
"React"
],
"homepage": "http://vikingship.xyz",
"repository": {
"type": "git",
"url": "https://github.com/vikingmute/vikingship"
},
"files": [
"dist"
],
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/react-fontawesome": "^0.1.8",
"axios": "^0.25.0",
"classnames": "^2.2.6",
"react-transition-group": "^4.3.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"scripts": {
"start": "react-scripts start",
"clean": "rimraf ./dist",
"lint": "eslint --ext js,ts,tsx src --max-warnings 5",
"build": "npm run clean && npm run build-ts && npm run build-css",
"test": "react-scripts test",
"test:nowatch": "cross-env CI=true react-scripts test",
"eject": "react-scripts eject",
"build-ts": "tsc -p tsconfig.build.json",
"build-css": "node-sass ./src/styles/index.scss ./dist/index.css",
"storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public",
"prepublishOnly": "npm run test:nowatch && npm run lint && npm run build"
},
"husky": {
"hooks": {
"pre-commit": "npm run test:nowatch && npm run lint"
}
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@storybook/addon-actions": "^5.2.8",
"@storybook/addon-info": "^5.3.21",
"@storybook/addon-links": "^5.2.8",
"@storybook/addons": "^5.2.8",
"@storybook/react": "^5.2.8",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@types/classnames": "^2.2.9",
"@types/jest": "24.0.23",
"@types/node": "12.12.14",
"@types/react": "^16.9.13",
"@types/react-dom": "16.9.4",
"@types/react-transition-group": "^4.2.3",
"@types/storybook__addon-info": "^5.2.1",
"cross-env": "^7.0.0",
"husky": "^4.2.1",
"node-sass": "^4.14.1",
"react": "^16.12.0",
"react-docgen-typescript-loader": "^3.6.0",
"react-dom": "^16.12.0",
"react-scripts": "3.2.0",
"rimraf": "^3.0.1",
"typescript": "3.7.2"
}
}
使用storybook生成静态文档页面
新建src/welcome.stories.tsx作为文档页面的首页,代码如下:
import React from 'react'
import { storiesOf } from '@storybook/react'
storiesOf('Welcome page', module)
.add('welcome', () => {
return (
<>
<h1>欢迎来到 vikingship 组件库</h1>
<p>vikingship 是为慕课网课程打造的一套教学组件库,从零到一让大家去学习</p>
<h3>安装试试</h3>
<code>
npm install vikingship --save
</code>
</>
)
}, { info : { disable: true }})
接下来将其配置为文档页面的首页,在.storybook/config.tsx的config函数中配置:
const loaderFn = () => {
const allExports = [require('../src/welcome.stories.tsx')];
const req = require.context('../src/components', true, /\.stories\.tsx$/);
req.keys().forEach(fname => allExports.push(req(fname)));
return allExports;
};
configure(loaderFn, module);