基于typescript发布npm包的流程
- 最近把在项目中写的插件封装了一下发到了npm,基于ts,顺便把流程记录下来~
- 也可以直接到GitHub下载该模板,快速生成自己的npm包~ 地址给到,star给到了么❤️~
主要是有几个流程:
- 初始化仓库 / 初始化文件夹 / 初始化 npm / 初始化 tsc
- 修改 tsconfig.json 配置
- 添加 tslint 校验代码规则以及 editorconfig,prettier 统一代码风格
- 设置 git 提交的校验钩子
- 配置webpack
- 编写插件代码
- 添加单元测试
- 写一个单元测试示例
- 完善文档信息
- 完善 package.json 的描述信息
- 发布到 npm
- 添加 Travis(持续集成)
- ...
初始化仓库 / 初始化文件夹 / 初始化 npm / 初始化 tsc
第一步就是各种初始化,比较简单 执行下面几个命令 然后打开文件夹就好~
- Github 创建远程仓库
- git clone https://github.com/xxx/xxx.git
- npm init -y
- npm i typescript -D
- tsc --init
修改 tsconfig.json 配置
经过第一步的tsc --init 不出意外在根目录应该就有了tsconfig.json文件,该文件是ts的编译配置文件,可以各种自定义的配置,参考我的如下:
{
"compilerOptions": {
"outDir": "./lib",
// "sourceMap": true,
"noImplicitAny": false, //在表达式和声明上有隐含的 any类型时报错。
"target": "es5", //指定ECMAScript目标版本 "ES3"(默认)
"lib": ["dom", "dom.iterable", "esnext"], // 编译过程中需要引入的库文件的列表
"allowJs": true, //允许编译javascript文件。
"skipLibCheck": true, //忽略所有的声明文件( *.d.ts)的类型检查
"esModuleInterop": true, //允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查
"allowSyntheticDefaultImports": true, // 同上
"strict": true, //启用所有严格类型检查选项
"forceConsistentCasingInFileNames": true, //禁止对同一个文件的不一致的引用
"module": "commonjs", //指定生成哪个模块系统代码
"moduleResolution": "node", //决定如何处理模块。
"resolveJsonModule": true,
"isolatedModules": true, //将每个文件作为单独的模块(与“ts.transpileModule”类似)。
// "noEmit": true,//不生成输出文件
"jsx": "react",
"baseUrl": "./",
"paths": { "*": ["types/*"] }
// "suppressImplicitAnyIndexErrors": true, //阻止对缺少索引签名的索引对象报错
},
"exclude": ["node_modules"],
"include": ["src/**/*"]
}
还有很多其他配置,具体请传送至ts官网
添加 tslint 校验代码规则以及 editorconfig,prettier 统一代码风格
这个其实可以不配置,不过现在前端发展都是各种工程化/团队化了,还是推荐配置下,反之也不难~
首先安装
- npm i prettier tslint tslint-config-prettier -D
大概解释下每个包的用途:
prettier: 按照一定的规则来美化你的代码,可配合eslint/tslint等一起使用,效果更佳
tslint: typescript代码校验规则
tslint-config-prettier: 使prettier搭配tslint一起使用
- 新建 tslint.json 文件并配置
一些命名规范可以去自定义配置,供参考
{
"extends": ["tslint:recommended", "tslint-config-prettier"],
"rules": {
"no-console": false,
"object-literal-sort-keys": false,
"member-access": false,
"ordered-imports": false
},
"linterOptions": {
"exclude": ["**/*.json", "node_modules"]
}
}
- 新建 .prettierrc 文件并配置
供参考
{
"trailingComma": "all",
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"endOfLine": "lf",
"printWidth": 120,
"overrides": [
{
"files": ["*.md", "*.json", "*.yml", "*.yaml"],
"options": {
"tabWidth": 2
}
}
]
}
- 新建 .editorconfig 文件并配置
用来配置一些编辑器的习惯,供参考
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
[{*.json,*.md,*.yml,*.*rc}]
indent_style = space
indent_size = 2
- 在 package.json 中配置 scripts 快捷命令
供参考
{
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
"lint": "tslint -p tsconfig.json"
}
- 新建.gitignore 文件并配置
Git 应当使用 .gitignore 文件忽略那些编译结果,以及 NPM 依赖的包文件
供参考
/node_modules/
/dist/
*.log
- 新建.npmignore 文件并配置
npm 打包发布的时候,会默认把当前目录下所有文件打包。但是 Git 仓库中,有些东西是不需要 发布到 NPM 的,因此我们需要使用一个文件 .npmignore 来忽略这些文件
供参考
/.git/
/.vscode/
/node_modules/
.gitignore
.npmignore
.prettierrc
.editorconfig
tslint.json
tsconfig.json
note.md
*.log
设置 git 提交的校验钩子
安装
- npm i pre-commit -S
pre-commit可以自动在.git/下生成 pre-commit 脚本
安装完毕后在package.json中进行配置:
"pre-commit": [
"lint"
],
此处的lint命令就是scripts的lint,再次提交 git commit 会先执行 scripts 下的 {"lint": "tslint -p tsconfig.json"}
进行 tslint 代码检查
如果想忽略代码检查可以执行git commit -m'描述' --no-verify(或者-n)进行直接提交
配置webpack并编写插件代码
- 配置webpack的目的是启动一个端口为 3001 的本地服务 供自己和他人 example 使用
目录结构
目录信息
doc/ 目录存放的是写的文档
example/ 目录是本地开始测试时候要用到的
lib/ 目录是编译tsx文件后将要发布到npm的目录 (tsconfig.json中需要配置outDir为该目录)
src/ 目录是该插件具体开发的目录
src/component 目录是该插件目录
src/types 目录是开发过程中定义类型的目录
- example/src/index.html
NPMPluginTemplate
- example/src/index.tsx
import React from 'react'
import { render } from 'react-dom'
import App from './app'
render( , document.getElementById('root'))
- example/src/app.tsx (注意,PluginDemo组件是从根目录的src/component中引入的)
import * as React from 'react'
import { PluginDemo } from '../../src/component/PluginDemo/index'
// tslint:disable-next-line:no-empty-interface
interface IProps {}
// tslint:disable-next-line:no-empty-interface
interface IStates {}
class App extends React.Component {
render() {
return (
)
}
}
export default App
- src/component/PluginDemo/index.tsx
import PluginDemo from './PluginDemo'
export { PluginDemo }
- src/component/PluginDemo/PluginDemo.tsx
import * as React from 'react'
interface IObjects {
[propsName: string]: any
}
// tslint:disable-next-line:no-empty-interface
interface IProps {
title?: string
}
interface IStates {
name?: string
}
class PluginDemo extends React.Component {
constructor(props: IProps) {
super(props)
this.state = {
name: '',
}
}
render() {
const { title } = this.props
return (
{title}
)
}
}
export default PluginDemo
根目录输入命令touch webpack.confifg.js 来新建webpack配置文件
这里的webpack只是简单的处理tsx文件/图片等等,供参考
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const htmlWebpackPlugin = new HtmlWebpackPlugin({
template: path.join(__dirname, './example/src/index.html'),
filename: './index.html',
})
module.exports = {
entry: path.join(__dirname, './example/src/index.tsx'),
output: {
path: path.join(__dirname, 'example/dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.tsx?/,
loader: 'ts-loader',
},
{
// pre/nomal/post - loader的执行顺序 - 前/中/后
enforce: 'pre',
test: /\.tsx?/,
loader: 'source-map-loader',
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpg|gif|mp4)$/,
use: {
loader: 'url-loader',
options: {
limit: 20,
},
},
},
],
},
//映射工具
// devtool: 'source-map',
//处理路径解析
resolve: {
//extensions 拓展名
extensions: ['.tsx', '.ts', '.js', '.jsx', '.json'],
},
plugins: [htmlWebpackPlugin],
devServer: {
port: 3005,
},
}
package.json中增加脚本
"scripts": {
"start": "webpack-dev-server --open development",
"build": "tsc",
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
"lint": "tslint -p tsconfig.json"
},
- 其中start是本地启动服务并开发,build是根据根目录的tsconfig.json文件来执行tsx解析并最终打包到根目录的lib/文件夹内
- 运行命令 npm start 即可看到服务启动,打开浏览器http://localhost:xxxx/ ,也正常显示.
- 运行命令 npm run build 即可看到lib文件夹内已经有了解析后的tsx文件.
到这儿,我们npm包基本的开发流程算是通了~接下来就是测试~
添加单元测试以及编写测试示例
本测试是基于Jest + Enzyme的,所以首先要安装模块
安装 jest
- npm i jest @types/jest ts-jest -D
创建 jestconfig.json 文件
- touch jestconfig.json
{
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["tsx", "ts", "js", "jsx", "json", "node"]
}
更多配置请移至jest官网
添加 package.json 里的 scripts
- "test": "jest --config jestconfig.json"
根目录src/下新建 test 目录
- mkdir test
编写一个简单的测试用例,观察测试流程是否跑通
- touch demo.test.ts
test('1加1等于2', () => {
expect(1 + 1).toBe(2)
})
- 运行命令npm run test
- all passed
此处有一点要注意,tsconfig.json 中如果设置了 isolatedmodules 为 true 的话,直接 test(...)会报错
All files must be modules when the '--isolatedModules' flag is provided
将其改成false就好
ok,测试的流程通了,但是我们要测react组件,所以接下来还要安装另一个包Enzyme
安装 Enzyme
- npm i enzyme @types/enzyme enzyme-adapter-react-16 -D
改文件名
- 因为此时要测试 react,要将文件名改为 demo.test.tsx
编写 react 组件的测试用例
- demo.test.tsx
更多API请移至enzyme官网
import React from 'react'
import { shallow, configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import { PluginDemo } from '../component/PluginDemo/index'
configure({ adapter: new Adapter() })
const setup = () => {
// 模拟 props
const props = {
title: '测试组件的props传值',
}
// 通过 enzyme 提供的 shallow(浅渲染) 创建组件
const wrapper = shallow( )
return {
props,
wrapper,
}
}
describe('测试demo组件', () => {
const { wrapper, props } = setup()
// 通过传递模拟的props,测试组件是否正常渲染
it('props', () => {
// 详细用法见 Enzyme 文档 http://airbnb.io/enzyme/docs/api/shallow.html
expect(wrapper.text()).toEqual('测试组件的props传值')
})
})
- 运行命令npm run test
- all passed~
编写文档信息
touch README.md
介绍该插件,越详尽越好...
完善 package.json 的描述信息
供参考
{
"name": "npm-plugin-template",
"version": "1.0.0",
"description": "An NPM plug-in template by typescript",
"main": "lib/index.js",
"scripts": {
"start": "webpack-dev-server --open development",
"build": "tsc",
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
"lint": "tslint -p tsconfig.json",
"test": "jest --config jestconfig.json"
},
"keywords": [
"npm",
"plugin",
"typescript"
],
"author": "FunkyTiger",
"pre-commit": [
"lint"
],
"repository": {
"type": "git",
"url": "git+https://github.com/funky-tiger/npm-plugin-template.git"
},
"license": "ISC",
"bugs": {
"url": "https://github.com/funky-tiger/npm-plugin-template/issues"
},
"homepage": "https://github.com/funky-tiger/npm-plugin-template#readme",
"dependencies": {
"@types/html-webpack-plugin": "^3.2.1",
"@types/node": "^12.6.8",
"@types/react": "^16.8.20",
"@types/react-dom": "^16.8.4",
"@types/react-router-dom": "^4.3.4",
"@types/webpack": "^4.32.1",
"media-show-card": "^1.0.3",
"pre-commit": "^1.2.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1",
"ts-node": "^8.3.0"
},
"devDependencies": {
"@types/enzyme": "^3.10.3",
"@types/jest": "^24.0.16",
"css-loader": "^3.1.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"file-loader": "^4.1.0",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.8.0",
"prettier": "^1.18.2",
"source-map-loader": "^0.2.4",
"style-loader": "^0.23.1",
"ts-jest": "^24.0.2",
"ts-loader": "^6.0.3",
"tslint": "^5.18.0",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.5.2",
"url-loader": "^2.1.0",
"webpack": "^4.34.0",
"webpack-cli": "^3.3.4",
"webpack-dev-server": "^3.7.2"
}
}
发布到 npm
未注册 ? npm adduser : 发布
登录
- npm login
发布
- npm publish
> 此处注意 npm login / npm publish 时要切换到官方镜像
> 官方 npm config set registry https://registry.npmjs.org/
> 国内 npm config set registry http://registry.npm.taobao.org/
添加 Travis(持续集成)
本文章篇幅已够长,添加持续集成,添加 Travis 构建徽章,添加代码覆盖率...等等会在以后的文章中说明
2019-08-01 更新
持续集成已完成 点击传送 ❤️
本模板地址:npm-plugin-template