码路工人 CoderMonkey
转载请注明做者与出处
原文连接yuquejavascript
之前 JavaScript 只能运行在浏览器端,html
有了 Nodejs 环境,JavaScript 也能够在后端运行了。java
NPM 是用于 Nodejs 的 JavaScript 包管理器,node
提供了大量的轮子,让老司机程序员瞬间提速起飞。webpack
在工做学习之余,git
不(nu)甘(li)寂(shang)寞(jin)的码农们有机会也要尝试下,程序员
造造轮子造造车。github
如下为码路工人造轮过程,web
记录开发一个简单的JS数据结构包,npm
分享出来与工友们学习探讨。
本例中码路工人作了一个使用JavaScript实现数据结构的包
npm install data-struct-jshttps://github.com/codermonkie/data-struct-js
前提是安装了 Nodejs,NPM 会随 Nodejs 一块儿安装。
若是没有 Node 环境,应该也不会有 NPM 包开发的需求了。
既然是 NPM 包,首先天然是要有 NPM 的帐户。
注册很简单,去官网。
另:学习的话,这里的中文文档网站不错:
记住用户名和密码,在发布时要用到。
码路工人这个项目就叫 data-struct-js
了。
克隆到本地。
git clone https://github.com/CoderMonkie/data-struct-js.git复制代码
VSCode 打开 data-struct-js 文件夹,终端中执行
npm init -y复制代码
不加 -y 的话,须要一步步的确认或手动输入
加上 -y 的参数就会所有以默认生成
上面这个命令自动为咱们生成了 package.json 文件。
{
"name": "data-struct-js",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/CoderMonkie/data-struct-js.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/CoderMonkie/data-struct-js/issues"
},
"homepage": "https://github.com/CoderMonkie/data-struct-js#readme"
}复制代码
各项目根据实际作适当修改。
其中的 url 地址,
是根据咱们 github 项目的 git 信息来生成的。
git 信息存在于 .git
文件夹中。
各属性咱们在修改时再作具体说明。
这里说明一下相关属性项目,
其中,
必须包含的项目有:name
和 version
做者信息格式官方建议
Your Name <email@example.com> (http://example.com)复制代码
版权类型,本项目中改成了限制最小的 MIT 类型。
本例中修改完成后的 package.json 文件:
{
"name": "data-struct-js",
"version": "0.0.1",
"description": "Provide commonly used data-structures for javascript.",
"main": "index.js",
"scripts": {
"compile": "npx babel src --out-dir compiled"
},
"repository": {
"type": "git",
"url": "git+https://github.com/CoderMonkie/data-struct-js.git"
},
"keywords": [
"data-structure",
"javascript"
],
"author": "Mao NianYou <maonianyou@gmail.com> (http://github.com/codermonkie)",
"license": "MIT",
"bugs": {
"url": "https://github.com/CoderMonkie/data-struct-js/issues"
},
"homepage": "https://github.com/CoderMonkie/data-struct-js#readme"
}
复制代码
除了上面列出的之外,
还会有 dependencies
跟 devDependencies
,
分别表示项目的依赖包以及项目开发中用到的依赖包。
这里不写出来,是由于,当咱们要添加依赖时,
经过 npm install --save
或 npm install --save-dev
来安装,
所依赖的包及版本信息都会添加到上面对应的属性中,
基本无需手动编辑。
能够比这个更复杂,
本例中最基本的结构包括如下:
data-struct-js
|--examples
| |--(for sample files)
|--lib
| |--(for compiled files)
|--src
| |--(for development files)
|--.babelrc
|--.gitignore
|--.npmignore
|--index.js
|--LICENSE
|--package.json
|--README.md
|复制代码
*注:上面有子层次的表示文件夹,其它为文件
(注:本文项目中将 webpack 配置在了 examples 下的样例工程中 )
.babelrc
babel 让咱们可使用最新 JavaScript 语法,
而不用担忧浏览器兼容问题,
由于它能够帮咱们把 ES6 的代码编译为 ES5。
示例:
{
"presets": [
"@babel/preset-env"
]
}复制代码
.gitignore
在此配置 git 版本管理要忽略的文件或文件夹。
新建仓库时根据选择能够生成默认的配置。
本身编辑的话,至少应该有如下:
examples/dist/
lib/
node_modules/
.npmignore复制代码
.npmignore
非 npm 发布对象配置。
示例:
examples/
node_modules/
src/
.babelrc
.gitignore
.npmignore复制代码
LICENSE
在新建仓库时根据选择的版权类型会自动生成该文件。
示例:
MIT License
Copyright (c) 2019 Mao NianYou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
复制代码
package.json
项目配置管理文件,参照上文 3.1 中已做的说明。
README.md
使用 markdown 标记的项目说明文档,
介绍本身项目状况。好比:
# 背景介绍
# 项目介绍
# 查看示例
# 使用说明
# License
# 联系
# 贡献者/鸣谢复制代码
根据以上大项目填写具体内容。
配置完上面的内容,到了主要环节,
开发咱们的 NPM 包。
在本次开发中,咱们当前仅用到了 babel 相关的包。
在 VSCode 终端(或其它命令行工具)执行如下命令:
npm install --save-dev @babel/cli
npm install --save-dev @babel/core
npm install --save-dev @babel/preset-env复制代码
也能够简写为如下一条命令:
npm i -D @babel/cli @babel/core @babel/preset-env复制代码
--save-dev 等同于 -D
会添加依赖到 devDependencies 中
--save 等同于 -S
会添加依赖到 dependencies 中
由于代码中用到了类属性注,仅安装以上的话,运行 babel 会报错,
根据错误信息,还需安装 @babel/plugin-proposal-class-properties
注:参见下文代码 ArrayBasedStruct.js
Error: Cannot find module '@babel/plugin-proposal-class-properties' from...复制代码
npm i -D @babel/plugin-proposal-class-properties复制代码
{
"presets": [
"@babel/preset-env"
],
"plugins": [
["@babel/plugin-proposal-class-properties",
{
"loose": true
}
]
]
}复制代码
安装完成后,package.json 文件中就增长了 devDependencies 字段。
{
"name": "data-struct-js",
"version": "0.0.1",
"description": "Provide commonly used data-structures for javascript.",
"main": "index.js",
"scripts": {
"compile": "npx babel src --out-dir compiled"
},
"repository": {
"type": "git",
"url": "git+https://github.com/CoderMonkie/data-struct-js.git"
},
"keywords": [
"data-structure",
"javascript"
],
"author": "CoderMonkey <maonianyou@gmail.com> (http://github.com/codermonkie)",
"license": "MIT",
"bugs": {
"url": "https://github.com/CoderMonkie/data-struct-js/issues"
},
"homepage": "https://github.com/CoderMonkie/data-struct-js#readme",
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/preset-env": "^7.7.6"
}
}
复制代码
由于本文主题是讲如何从零发布一个本身的 npm 包,
代码部分仅举一例说明,完整代码可查看:
[Github](github.com/CoderMonkie…)
如下为本项目中栈结构的实现代码,
其中引用到的其它文件,
一并贴在下方以供参考。
data-struct-js/src/Stack.js
import {
deepCopy,
isFunction
} from './common_utils'
import ArrayBasedStruct from './ArrayBasedStruct'
/**
*栈结构
*
* @export
* @class Stack
* @extends {ArrayBasedStruct}
*/
export default class Stack extends ArrayBasedStruct {
constructor() {
super()
}
/**
*将新元素入栈
*
* @param {*} element
* @memberof Stack
*/
push(element) {
return this.__items.push(element)
}
/**
*栈顶元素出栈
*
* @returns 栈顶元素
* @memberof Stack
*/
pop() {
return this.__items.pop()
}
/**
*查看栈顶元素
*
* @returns 栈顶元素
* @memberof Stack
*/
peek() {
if (!this.__items.length) return undefined
return deepCopy(this.__items[this.__items.length - 1])
}
/**
*遍历栈结构
*
* @param {function} callback
* @param {boolean} [reversal=false]
* @memberof Stack
*/
traverse(callback, reversal = false) {
// 检查回调函数
if (!isFunction(callback)) return
var items = this.getItems(this.__items)
var from = reversal ? items.length - 1 : 0
var to = reversal ? 0 : items.length - 1
// 循环条件
var loopCondition = function (current) {
if (reversal) {
return current >= to
} else {
return current <= to
}
}
// 游标前进
var stepIn = function (current) {
if (reversal) {
return current - 1
} else {
return current + 1
}
}
// 进行遍历
for (var index = from; loopCondition(index); index = stepIn(index)) {
var element = items[index];
callback(element, index)
}
}
/**
*转为字符串
*
* @returns
* @memberof Stack
*/
toString() {
return this.__items.map(element => element.toString()).join(' ')
}
}复制代码
data-struct-js/src/common_utils.js
本身实现的经常使用方法
/**
*深拷贝
*
* @export
* @param {*} source 要拷贝的对象
* @returns 深拷贝结果
*/
export function deepCopy(source) {
var dest
if(Array.isArray(source)) {
dest = []
for (let i = 0; i < source.length; i++) {
dest[i] =deepCopy(source[i])
}
}
else if(toString.call(source) === '[object Object]') {
dest = {}
for(var p in source){
if(source.hasOwnProperty(p)){
dest[p]=deepCopy(source[p])
}
}
}
else {
dest = source
}
return dest
}
/**
*判断传入参数是否为函数
*
* @export
* @param {*} func 参数(函数)
* @returns true:是函数 false:不是函数
*/
export function isFunction (func) {
if (!func || toString.call(func) !== '[object Function]') return false
return true
}复制代码
data-struct-js/src/ArrayBasedStruct.js
给栈结构提供的基类
import { deepCopy } from "./common_utils"
/**
*基于数组实现的数据结构的基类
*
* @class ArrayBasedStruct
*/
class ArrayBasedStruct {
constructor() {
this.__items = []
}
/**
*获取全部元素
*
* @returns 元素集合
* @memberof Stack
*/
getItems () {
return deepCopy(this.__items)
}
/**
*数据结构实例中是否包含元素
*
* @readonly
* @memberof ArrayBasedStruct
*/
get isEmpty() {
return this.__items.length === 0
}
/**
*数据结构实例的元素个数
*
* @readonly
* @memberof ArrayBasedStruct
*/
get size() {
return this.__items.length
}
/**
*清空数据结构中的元素
*
* @memberof ArrayBasedStruct
*/
clear() {
this.__items.length = 0
}
}
export default ArrayBasedStruct复制代码
经过 index.js 文件统一导出
data-struct-js/index.js
import { Stack } from './lib/Stack'
export { Stack }复制代码
注:目前本示例中只有 Stack,后续添加的也都是在这里统一导出
还记得咱们在 package.json 中配置的脚本命令吗,
"scripts": {
"compile": "npx babel src/ --out-dir lib"
},复制代码
执行方法:
npm run compile复制代码
执行结果:
> data-struct-js@0.0.1 compile F:\path\to\data-struct-js
> npx babel src/ --out-dir lib
Successfully compiled 3 files with Babel.复制代码
编译执行成功,能够看到 data-struct-js/lib/
文件夹下生成了编译后的文件。
咱们作的包在被别人使用时,下载和引入的就是这些编译后的文件。
跟普通项目开发同样 NPM 包开发也须要测试。
经过测试,减小或避免产生 bug。
data-struct-js/examples/
路径下,新建咱们的测试用项目。
cd examples
npm init
package name: data_struct_js_demo复制代码
生成的 package.json 项目文件:
{
"name": "data_struct_js_demo",
"version": "1.0.0",
"description": "demo for data-struct-js",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT"
}复制代码
告诉你个小秘密:
这个工程位于咱们主工程的目录底下,
这里要用到的依赖包,若是本工程没有安装,
则会自动查找上层目录,
也就是,外层安装了的话就可用。
另外,
找到根目录也没有的话还会查看全局安装,
都没有的时候才会报错
注意:
如下安装方式,不适用于本次示例,
由于 demo 工程位于 package 工程目录下,
会致使递归引用。
仅当不在同一路径下建立新工程时能够。
npm install --save-dev ../../data-struct-js复制代码
因此,这里咱们须要手动引用咱们包里的文件。
data-struct-js/examples/src/index.js
// 直接引入原文件
import Stack from '../../src/Stack'
// 或编译后的也能够
// import Stack from '../../lib/Stack'复制代码
注:
这里有个坑,就是模块化方式必须统一!
也就是,若是代码里用 ES6 的模块化方式(import/export),
那么这里若是用 Node 的模块化(require/module.exports=),
就会报错。(new 导出的class的时候报:xxx is not a constructor)
=>
data-struct-js/index.js
也是这样
这里列出咱们本次用到的开发依赖:
(为了不太长,分两行写)
npm i -D webpack webpack-cli webpack-dev-server
npm i -D html-webpack-plugin clean-webpack-plugin复制代码
data-struct-js/examples/package.json
{
"name": "data_struct_js_demo",
"version": "1.0.0",
"description": "Demo for data-struct-js",
"main": "index.js",
"scripts": {
},
"author": "CoderMonkey <maonianyou@foxmail.com>",
"license": "MIT",
"devDependencies": {
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.41.3",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0"
}
}
复制代码
data-struct-js/examples/webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const htmlWebpackPlugin = new HtmlWebpackPlugin({
template: path.join(__dirname, "./src/index.html"),
filename: "./index.html"
});
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
module.exports = {
mode: 'development',
entry: path.join(__dirname, "./src/index.js"),
output: {
path: path.join(__dirname, "dist/"),
filename: "[name].[hash:6].js"
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: "babel-loader",
exclude: /node_modules/
}
]
},
plugins: [
htmlWebpackPlugin, new CleanWebpackPlugin()
],
resolve: {
extensions: [".js", ".jsx"]
},
devServer: {
port: 8080
}
};
复制代码
data-struct-js/examples/package.json
// ...略...
"scripts": {
"start": "webpack-dev-server --open",
"build": "webpack --config webpack.config.js"
},
// ...略...复制代码
data-struct-js/examples/src/index.js
// 直接引入原文件
import Stack from '../../src/Stack'
var stack = new Stack()
for (var i = 0; i < 5; i++) {
stack.push(i)
}
console.log('isEmpty: ', stack.isEmpty)
console.log('size: ', stack.size)
console.log(stack.toString())
console.log(`pop: `, stack.pop())
stack.traverse((ele,index)=>{
console.log(`Traversing-Stack:${index}: ${ele}`)
})
stack.traverse((ele,index)=>{
console.log(`Traversing-Stack:${index}: ${ele}`)
}, true)
console.log(`claer: `, stack.clear())
console.log('isEmpty: ', stack.isEmpty)
console.log('size: ', stack.size)复制代码
data-struct-js/examples/src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Demo of data-struct-js</title>
</head>
<body>
</body>
</html>复制代码
执行咱们配置的脚本命令,
启动本地服务,并打开网页:
注意当前目录:data-struct-js/examples
npm run start复制代码
将输出结果附在代码后面方便阅读
// 直接引入原文件
import Stack from '../../src/Stack'
var stack = new Stack()
for (var i = 0; i < 5; i++) {
stack.push(i)
}
console.log('isEmpty: ', stack.isEmpty)
// isEmpty: false
console.log('size: ', stack.size)
// size: 5
console.log(stack.toString())
// 0 1 2 3 4
console.log(`pop: `, stack.pop())
// pop: 4
stack.traverse((ele,index)=>{
console.log(`Traversing-Stack:${index}: ${ele}`)
})
// Traversing-Stack:0: 0
// Traversing-Stack:1: 1
// Traversing-Stack:2: 2
// Traversing-Stack:3: 3
stack.traverse((ele,index)=>{
console.log(`Traversing-Stack:${index}: ${ele}`)
}, true)
// Traversing-Stack:3: 3
// Traversing-Stack:2: 2
// Traversing-Stack:1: 1
// Traversing-Stack:0: 0
console.log(`claer: `, stack.clear())
// claer: undefined
console.log('isEmpty: ', stack.isEmpty)
// isEmpty: true
console.log('size: ', stack.size)
// size: 0复制代码
TODO
这样就可以简单地确认结果了,
以后咱们再考虑使用测试框架。
结果代表运行正确符合预期。
这样咱们就能够着手发布了。
注:路径回到本项目根目录,即 data-struct-js/
未登陆状态下直接发布的话会报如下错误:
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! [no_perms] Private mode enable, only admin can publish
this module [no_perms] Private mode enable,
only admin can publish this module: data-struct-js复制代码
登陆的话,执行:
npm login复制代码
npm login 根据提示输入 username 跟 password,
而后报了错,发现 npm 源用的 taobao,
这里注意:发布 npm 包时,要将源切换到 npm。
npm ERR! code E409
npm ERR! Registry returned 409 for PUT on
https://registry.npm.taobao.org/-/user/org.couchdb.
user:maonianyou: [conflict] User maonianyou already exists复制代码
码路工人这里用的 nrm 来管理 安装源。
切换:
> nrm use npm
Registry has been set to: https://registry.npmjs.org/复制代码
成功登陆:
> npm login
Username: yourname
Password: (yourpassword)
Email: (this IS public) yourname@somemail.com
Logged in as yourname on https://registry.npmjs.org/.复制代码
注册用户时邮箱未验证也是会报错的:
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! you must verify your email before publishing a new package:
https://www.npmjs.com/email-edit : your-package复制代码
首先要在 npm 官网检索一下有没有重名的包,
不然发布的话会因没有权限报如下错误:
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! You do not have permission to publish "xxxxxxx". Are you logged in as
the correct user? : xxxxxxxxx复制代码
注意:
- 先编译 npm run compile
- 再发布 npm publish
npm run compile
npm publish复制代码
为了方便,咱们配置一条脚本命令:
data-struct-js/package.json
// ...略...
"scripts": {
"dopublish": "npm run compile && npm publish"
},
// ...略...复制代码
之后就能够:
npm run dopublish复制代码
>npm publish
npm notice
npm notice package: data-struct-js@0.0.1
npm notice === Tarball Contents ===
npm notice 866B package.json
npm notice 55B index.js
npm notice 1.1kB LICENSE
npm notice 26B README.md
npm notice 1.8kB lib/ArrayBasedStruct.js
npm notice 947B lib/common_utils.js
npm notice 5.1kB lib/Stack.js
npm notice === Tarball Details ===
npm notice name: data-struct-js
npm notice version: 0.0.1
npm notice package size: 3.7 kB
npm notice unpacked size: 9.9 kB
npm notice shasum: 2001495314blsd9gaj9g0b7b15aewr6we5207aac
npm notice integrity: sha512-UzOx7tFP8/qJp[...]DPJ1wherlFJyQ==
npm notice total files: 7
npm notice
+ data-struct-js@0.0.1复制代码
到 npm 官网查看发布的 data-struct-js
在本身的工程中安装测试一下。
npm install data-struct-js复制代码
引入使用
import { Stack } from 'data-struct-js'复制代码
参照:
5.3 确认运行结果
npm unpublish --force your-package-name复制代码
npm unpublish <package-name>@<version>复制代码
看到网上有说 :“
超过24小时就不能删除了”
但查看官网,是 72 小时内可撤销发布,超过 72 小时的就须要联系 npm Support 了。
*没有实践确认,以官网说明为准吧*
增长功能或者修改bug后,
就要更新咱们的 package。
这里主要涉及到版本管理。
修改项目代码后,版本号不变,
继续发布的话,就会报错:
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! You cannot publish over the previously published versions:
0.0.1. : data-struct-js复制代码
这里引用 npm 官方文档的表格,
加入部分中文翻译。
Code status | Stage | Rule | Example version | |
1 |
First release 首次发布 |
New product 新产品 |
Start with 1.0.0 | 1.0.0 |
2 |
Backward compatible bug fixes 向后兼容 bug 修改 |
Patch release 发布补丁更新 |
Increment the third digit 第三位数增加 |
1.0.1 |
3 |
Backward compatible new features 向后兼容 增长新特性 |
Minor release |
Increment the middle digit and reset last digit to zero 中间的版本号增加,同时末尾版本号清零 |
1.1.0 |
4 |
Changes that break backward compatibility 不向后兼容的变动 |
Major release 发布主版本更新 |
Increment the first digit and reset middle and last digits to zero 首位版本号增加,第二第三都清零 |
2.0.0 |
上面,除了 1 是首次新发布,后面 2 3 4 分别为 patch / minor / major。
data-struct-js/pakcage.json
{
"version": "1.0.1"
}复制代码
npm publishs复制代码
npm version <update_type>
<update_type>为:
- patch 0.0.*
- major *.0.0
- minor 1.*.0
npm version patch
npm publish复制代码
以上命令,
就是将咱们的包按照语义化的版本管理,
自动变动版本号,而后发布。
执行如下命令,可修改默认的初始化内容
> npm set init.author.email "example-user@example.com"
> npm set init.author.name "example_user"
> npm set init.license "MIT"复制代码
还记得咱们上面用到的 babel 相关的几个包吗,
@babel/cli
@babel/core
@babel/preset-env复制代码
你会注意到,它的这种格式,
跟安装咱们此次开发的包不同
npm install @babel/cli
npm install data-struct-js复制代码
这个 @scopename
就是 scoped 的限定,
如包名:@codermonkey/data-struct-js
由于私有包是收费的,冠名只能设为公开,
因此发布时需加上如下参数:
npm publish --access public复制代码
发布出来就是:
@yourscope/packagename
1.0.0 · Public · Published ... ago
Scope 用以关联一系列的包,
每一个用户和组织都有本身的 Scope,
前面咱们提到发布包不能够重名,
其实若是重名的话,
只要不在同一个 Scope 下应该也是能够的。
不知道会不会违反包名相似的规则
不过也有与本示例稍微相似的包名,咱们的仍是发布成功了
能够在登陆的时候指定 scope 名称:
npm login --registry=http://reg.example.com --scope=@myco复制代码
也能够在 config 中配置指定源的 scope:
npm config set @myco:registry http://reg.example.com复制代码
更多关于 Scope 详情,请参看官网:
*注:本文参考了 NPM 官网文档
码路工人的微信公众号
~欢迎联系交流指点斧正~
-end-