likes
comments
collection
share

发布自己的npm包

作者站长头像
站长
· 阅读数 12

学习了很久的前端,突然发觉没有一个属于自己的npm包,是多么不完美的一件事; 于是乎,创建了一个属于自己的包,并记录下来了; 以下是我的艰辛路程,希望有用[🙈][🙈][🙈]

以下事例项目源自: directory-doc(项目文件目录生成)

注册npm账号(已有账号可·忽略此步骤)

进入npm官网首页后需要 注册(有账号的可以直接登录, 直接到本地登录)

发布自己的npm包

发布自己的npm包

  • 账号需要邮箱验证,要准备一个邮箱号
  • 账号创建后,需要登录的,
  • 每次登录还要给邮箱发一个一次性验证码,感觉好麻烦

发布自己的npm包

新建项目

远程新建项目( 本地clone远程厂库代码)

这里就不做多余的介绍了,感兴趣的可以自己查询 github / 码云进行了解哦~~

本地新建项目 (大佬可自行操作·忽略此步骤)

  • npm init 初始化项目
        npm init
    

可以一直回车确认 生成配置文件package.json (后期自己在package.json中添加修改)

发布自己的npm包

package.json 解读

初始化后的package.json是这样的:

发布自己的npm包

  • 主要是项目的基本信息,包括名称,版本,描述,仓库,作者等,部分会展示在 npm 官网上

发布自己的npm包

发布自己的npm包

name:项目名称

"name": "directory-doc"

version: 项目版本号

"version": "1.0.0"

name + version 能共同构成一个完全唯一的项目标识符,所以它两是最重要的两个字段

description: 项目描述,会展示在npm官网

"description": "Build the project file directory",

repository:项目的仓库地址以及版本控制信息

"repository": {
    "type": "git",
    "url": "https://github.com/ForeverJuvenile/directory.git"
}

keywords:项目技术关键词,例如directory-doc关键词(好的关键词可以增加曝光率)

"keywords":  [
  "node",     
  "directory",    
  "javascript",
  "doc",
  "markdown"
]

homepage: 项目主页的链接:一般是github项目地址,或者项目官网

"homepage": "https://github.com/ForeverJuvenile/directory.git"

author: 项目作者(一般项目作者姓名 + 邮箱 + gihub首页)

"author": "Forever Chen <loveforever10.05.sir@gmail.com>(https://github.com/ForeverJuvenile)"

files: npm包作为依赖安装时要包括的文件(files字段优先级最大,不会被npmignore.gitignore覆盖)

"files": [
  "src"
],

以下文件固定包含,无法配置

  • package.json
  • README.md
  • CHANGES / CHANGELOG / HISTORY
  • LICENCE / LICENSE

以下文件总是被忽略的,与配置无关

  • .git
  • .DS_Store
  • node_modules
  • .npmrc
  • npm-debug.log
  • package-lock.json

type: 产生用于定义package.json文件和该文件所在目录根目录中.js文件和无拓展名文件的处理方式。值为'moduel'则当作es模块处理;值为'commonjs'则被当作commonJs模块处理

"type": "module"

bin: 用来指定各个内部命令对应的可执行文件的位置(directory-doc即作为命令名)

"bin": {
    "directory-doc": "src/index.js"
}

main: 指定了加载的入口文件(默认是:index.js)

"main": "src/index.js"

license: 项目开源许可证。项目的版权拥有人可以使用开源许可证来限制源码的使用、复制、修改和再发布等行为。常见的开源许可证有 BSDMITApache 等; 可参考:如何选择开源许可证? - 阮一峰的网络日志 (ruanyifeng.com))

"license": "MIT"

bugs: 项目bug反馈地址,通常是github issue页面的链接

"bugs": "https://github.com/directory/core/issues"

...等等还有很多,不多做介绍~~

添加许可证

在项目根目录新建 LICENSE 文件

  • 内容可以直接copy以上内容,修改Copyright (c)后的日期和名称即可
MIT License

Copyright (c) 2023 Forever Chen

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.

接下来就可以写属于自己的npm工程代码了~~~

开发

  • 在项目根目录新建 src 文件夹

用于存放项目代码

src文件夹下新建一个index.js文件:

#!/usr/bin/env node
import fs  from 'fs';
import path  from 'path';
import chalk from 'chalk';
import { program } from 'commander';
import { fileURLToPath } from 'url';
import EventProcessingCenter from './event.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const pack = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
console.log(chalk.blueBright.bold(`directory-doc v${pack.version}`));

/**node_modules .git and other folders do not need to be recursive*/
let filterFolder = ['node_modules', '.git'];

program
    .name(pack.name)
    .version(pack.version, '-v, --version', 'output the current version');

program
    .command('init')
    .description('Build the project file directory')
    .option('-i, --ignore [ignore...]', 'You can ignore specific directory name', )
    .option('-e, --export <file>', 'You can define the file name for the export', 'directory.md')
    .action((options) => {
        filterFolder = typeof options.ignore === 'undefined' ? filterFolder : options.ignore;
        const processingCenter = new EventProcessingCenter({filterFolder})
        processingCenter.useInquirer(`${options.export}`);
    })
program.parse(process.argv);

// Try the following:
//  node .\index.js -v
//  node .\index.js init
//  node .\index.js init -i  node_modules
//  node .\index.js init -i  node_modules .git
//  node .\index.js init -e  directory.txt
//  node .\index.js init -e  directory.md

!/usr/bin/env node: 告诉编译器我要写node;

说明:

node cli命令开发:commander

node 的控制台console.log()美化:chalk

src文件夹下新建一个event.js 文件:

#!/usr/bin/env node
import path from 'path';
import fs  from 'fs';
import ora from 'ora';
import inquirer from 'inquirer';
import chalk from 'chalk';

const warning = chalk.yellow;
const cyanBright = chalk.cyanBright;

/**loading */
const spinner = ora({
    text: 'Loading unicorns...',
    color: 'green'
});

const directoryEnum = {
    outermost: '    |',
    start: '    |—— ',
    line: '     #'
}

const target = process.cwd();
const rootName = target.split('\\')[target.split('\\').length -1];

const treeSort = (data, objectPath) => {
    // TODO: 要求:文件夹> 文件 > 字母
    return data.sort((a, b) => {
        const stateA = fs.statSync(path.join(objectPath, a))
        const stateB = fs.statSync(path.join(objectPath, b))
        if (stateA.isDirectory() && stateB.isDirectory()) return a > b ? 1 : -1;
        if (!stateA.isDirectory() && !stateB.isDirectory())return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
        if (stateA.isDirectory() && !stateB.isDirectory()) return -1;
        if (!stateA.isDirectory() && stateB.isDirectory()) return 1;
        return 1
    });
}

const EventProcessingCenter = class {
    constructor(props){
        this.filterFolder = props.filterFolder;
        this.directory = ``;
        this.tree = {
            root: rootName,
            children: []
        }
    }

    useQueryFile(filterFolder) {
        const buildTree = (objectPath, start = '') => {
            const docs = treeSort(fs.readdirSync(objectPath), objectPath);
            docs.forEach(item => {
                const absolutePath = path.join(objectPath, item)
                const state = fs.statSync(absolutePath);
                /**folder*/
                if(state.isDirectory() && !filterFolder.includes(item)) {
                    this.directory += `${start}${directoryEnum.start}${item}${directoryEnum.line}\n`;
                    const level = objectPath.split('\\').length > 1 ? objectPath.split('\\').length -2 : 0;
                    const newStart = directoryEnum.outermost.repeat(level)
                    buildTree(path.join(`${objectPath}\\${item}`), newStart);
                } else {
                    /**file*/
                    this.directory += `${start}${directoryEnum.start}${item}${directoryEnum.line}\n`;
                }
            })
        }
        return {
            buildTree
        }
    }

    run() {
        spinner.start();
        setTimeout(() => {
            const { buildTree } = this.useQueryFile(this.filterFolder);
            buildTree(process.cwd());
            const data =  `${`# ${rootName} 项目目录\n\n`}` + '```ts\n' + this.directory + '```\n'
            fs.writeFile(this.fileName, data, (error) => {
                if(error){
                    console.log(error(new Error(`write ${ this.fileName} error`, { err })))
                    return;
                }
            })
            spinner.succeed('success !');
        }, 1000)
    }

    useInquirer (fileName){
        const docs = fs.readdirSync(process.cwd());
        this.fileName = fileName;
        if(docs.includes(fileName)) {
            const coverInit = [
                {
                    type:'confirm',
                    name: 'confirm',
                    message: warning(`${cyanBright(fileName)} already exists in the Target directory ${cyanBright(process.cwd())} folder, do you want to continue? (y/n)`),
                    default: 'y'
                },
            ];
            inquirer.prompt(coverInit).then(({confirm}) => {
                if(confirm) {
                    this.run();
                } else console.log(cyanBright('--- Directory has been canceled ---'))
            });
            return;
        }
        this.run();
    }
}

export default EventProcessingCenter

说明:

node读写:fs.readFile

node控制台进度条(loading)美化: ora

inquirer 交互式命令行用户界面: inquirer

添加运行命令

直接在package.json中运行脚本命令

  "scripts": {
    "start": "node src/index.js",
    "init": "npm run start init",
  },

或者执行命令

npm pkg set scripts.start="node src/index.js"
npm pkg set scripts.init="npm run start init"

执行命令

  • 终端:
    #查看 directory
    npm run start

发布自己的npm包

    #directory 初始化
    npm run init

项目根目录不存在directory.md文件的情况: 发布自己的npm包

项目根目录存在directory.md文件,会问询,需要二次确认(回车/y): 发布自己的npm包

执行命令后会根目录创建directory.md 文件

directory.md一览:

    |—— .git     #
    |—— .husky     #
    |    |—— _     #
    |    |    |—— .gitignore     #
    |    |    |—— husky.sh     #
    |    |—— commit-msg     #
    |—— node_modules     #
    |—— public     #
    |    |—— logo.svg     #
    |—— src     #
    |    |—— event.js     #
    |    |—— index.js     #
    |—— .commitlintrc.cjs     #
    |—— .czrc     #
    |—— .editorconfig     #
    |—— .gitignore     #
    |—— LICENSE     #
    |—— package.json     #
    |—— pnpm-lock.yaml     #
    |—— README.md     #

本地调试

# 本地全局安装 
npm link 

directory 文件包会被全局安装在本地npm node_nodules 文件夹中;

路径一般是:C:\Users\用户名\AppData\Roaming\node\node_modules ; 以node或node管理工具 安装目录为准

发布自己的npm包

这个时候 执行一下 directory-doc 试一下;

发布自己的npm包

这样就可以了哇!

接下来就可以发布我们的新包了~~

本地登录npm(为发布做准备)

  • 执行 npm login 登录 npm
   npm login

依次输入账号&密码&邮箱就好了;

发布自己的npm包

上传远程npm包

  • 执行npm publish
# npm publish [<tarball>|<folder>] [--tag <tag>] [--access <public|restricted>] [--otp otpcode] [--dry-run]
npm publish

重点注意:如果本地是淘宝镜像,要切换到npm官方货源上;淘宝镜像只是提供下载,如果要npm loginnpm publish 登录发布自己的作品,必须切换到官方的货源,毕竟你是要发布到npm 上,而不是淘宝上的;

publish ERR: 版本异常:

  • 当前发布版本 小于 线上版本 发布自己的npm包

  • 当前发布版本 等于 线上版本 发布自己的npm包

  • 未登录(本地登录就好了) 发布自己的npm包

镜像包错误:

  • 使用的淘宝镜像 发布自己的npm包 应该切换到官方镜像上:
# 切换镜像到npmjs
npm config set registry=https://registry.npmjs.org/
# 查看镜像
npm get registry

发布自己的npm包

发布成功发布自己的npm包

弃用/删除npm包

发布自己的npm包

注意: 包一旦弃用/删除72小时内 是不允许修改推送的

结束了~ 感兴趣的可以在评论区讨论的哦~ 以上仅是个人言论及观点,如有不同,欢迎评论区讨论~

发布自己的npm包 👋💃🏻🕺🏻💨