- 背景
为了解决这个问题,做了以下探索,特此记录以下操作的流程,中间有很多细节本文不详细解释,因为还有很多配置不是很明白,找个机会系统讲一下npm相关知识
- 为什么要搭建npm私有仓库(私服)
eg:
1.有三个项目A、B 、C
2.在A项目写了一个select组件,又不能将这个组件发布到npm公共社区里
没有npm私服时的做法:
这时如果B和C项目要用带这个组件的话,只能去A项目里复制一份粘贴到B和C项目
如果后面select组件更新了的话,又只能复制粘贴
如上这种情况,耗费时间。有时还会忘了哪个项目的组件时最新的。一个组件都这么麻烦,如果是多个组件和多个项目,维护简直要疯了。
如果我们有npm私有仓库的做法:
私有npm私有仓库的好处
- 自定义组件发布到私有仓库的流程(Windows系统)
1.创建组件
md gldSelect
cd gldSelect
2.创建以下文件或文件夹
md build
md src
package.json
{
"name": "gld-select",
"version": "1.0.1",
"description": "This is an optimized Select component",
"main": "build/index.js",
"peerDependencies": {
"react-dom": "^16.12.0",
"react": "^16.4.0"
},
"scripts": {
"start": "webpack --watch",
"build": "webpack"
},
"author": {
"name": "szyy"
},
"license": "ISC",
"dependencies": {
"antd": "^3.26.5",
"prop-types": "^15.7.2",
"react": "^16.4.0",
"react-dom": "^16.12.0",
"webpack": "^4.12.0"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"webpack-cli": "^3.3.10"
}
}
webpack.config.js
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'index.js',
libraryTarget: 'commonjs2'
// libraryTarget: 'umd'
},
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src'),
exclude: /(node_modules|bower_components|build)/,
use: {
loader: 'babel-loader',
options: {
presets: ["env", "stage-0"]
}
}
}
]
},
externals: {
'react': 'commonjs react'
}
};
.babelrc
{
"presets": ["env", "stage-0"],
"plugins": [
"transform-object-rest-spread",
"transform-react-jsx",
"transform-class-properties"
]
}
3.将自定义组件写入到src/index.js
src/index.js
import React from "react";
import { Select } from "antd";
import PropTypes from "prop-types";
export default class GldSelect extends Select {
static propTypes = {
dataSource: PropTypes.array,
onChange: PropTypes.func,
};
static defaultProps = {
dataSource: [],
};
init(source, isSearch) {
this.pageNum = 1;
this.scrollPos = 0;
this.dataSource = source;
this.setState({
displayVals: this.dataSource.length > this.pageSize ?
this.dataSource.slice(0, this.pageSize * this.pageNum) : this.dataSource,
isSearch: isSearch,
});
}
constructor(props) {
super(props);
this.pageSize = 20;
this.pageNum = 1;
this.scrollPos = 0;
this.dataSource = props.dataSource;
this.state = {
displayVals: this.dataSource.length > this.pageSize ?
this.dataSource.slice(0, this.pageSize * this.pageNum) : this.dataSource,
isSearch: false,
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
const { dataSource } = this.props;
const ds = [].concat(dataSource);
const preDs = [].concat(prevProps.dataSource);
if (preDs.sort().toString() !== ds.sort().toString()) {
this.init(dataSource)
}
}
handleChange = v => {
const { onChange, dataSource } = this.props;
const { isSearch } = this.state;
onChange && onChange(v);
/*点击事件分为两种情况:
* 1.一般的下拉框选中,这时只需要执行props中的onChange
* 2.根据关键字搜索后,点击选中时,需要恢复到初始的状态
* */
if (isSearch === true) {
this.init(dataSource)
}
};
handleSearch = v => {
v = v || "";
const filterWord = v.trim();
const { dataSource } = this.props;
const newSource = dataSource.filter(item =>
item.includes(filterWord)
);
this.init(newSource, true);
};
handlePopupScroll = e => {
const { displayVals } = this.state;
e.persist();
const { target } = e;
const st = target.scrollTop;
if (st === 0 && this.scrollPos) {
target.scrollTop = this.scrollPos;
}
if (
st + target.offsetHeight + 2 >= target.scrollHeight &&
displayVals.length < this.dataSource.length
) {
this.pageNum += 1;
this.setState({ displayVals: this.dataSource.slice(0, this.pageSize * this.pageNum) });
this.scrollPos = st;
} else {
this.scrollPos = 0;
}
};
render() {
const { dataSource, ...rest } = this.props;
const { displayVals } = this.state;
return (
<Select
{...rest}
onChange={this.handleChange}
onPopupScroll={this.handlePopupScroll}
dropdownMatchSelectWidth={false}
maxTagCount={5}
onSearch={this.handleSearch}
>
{displayVals.map((opt) => (
<Select.Option key={opt} value={opt}>
{opt}
</Select.Option>
))}
</Select>
);
}
}
4.安装相关npm包并打包
npm i
npm run build
npm link
npm link将用于在我们开发测试项目时对我们的组件进行开发测试。
5.测试组件是否可用,首先创建一个测试项目
npm i -g create-react-app
md gld-test
cd gld-test
create-react-app .
npm link gldSelect
测试项目创建成功后,在src/App.js中添加自定义组件gldSelect,并完成测试
import GldSelect from 'gldSelect';
...
/>
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import GldSelect from 'gldSelect';
import {
Button,
Row,
Col,
Card,
Form,
Select,
DatePicker,
Icon,
Tabs,
notification,
Table,
} from 'antd';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<div>
<Form>
<Row>
<Col span={9}>
<Form.Item>
<GldSelect
style={{ width: '100%' }}
placeholder="选择预算版本号"
mode="multiple"
dataSource={[1,3,4,5,7,8,9,0]}
/>
</Form.Item>
</Col>
</Row>
</Form>
</div>
</div>
);
}
export default App;
添加完成后,执行
npm start
如果在测试项目中可用,则该自定义组件可用,可上传至npm私有库
6.将自定义组件上传私有库
cd gldSelect
安装nrm
npm install -g nrm
nrm(npm registry manager )是npm的镜像源管理工具,有时候国外资源太慢,使用这个就可以快速地在 npm 源间切换
添加私有库镜像源
nrm add szyy http://xx.xxx.x.xxx:xxxx/repository/npm-host/
切换到私有库镜像源
nrm use szyy
登录私有库镜像源账号
npm login
(按照要求输入账号密码以及邮箱)
登录成功后,执行以下命令完成组件上传
npm publish
7.项目中使用自定义组件
通过npm的config命令配置指向国内私有镜像源:以后所有从仓库获取的包都从这个地址获取,不走国外的地址
npm config set registry http://xx.xxx.x.xxx:xxxx/repository/npm/
在项目中执行以下命令,安装自定义组件
npm install gld-select
在js文件代码中直接引用
import GldSelect from 'gld-select'
- 参考资料