寫一個(gè)包并發(fā)布到 npm 上面

說到npm包都會(huì)給人一種特別高大上的感覺,并且自己寫了一個(gè)包之后如果有人用那么就會(huì)產(chǎn)生莫大的成就感,程序員的快樂就是這么簡單。
想必有產(chǎn)生寫npm包想法的人都對(duì)模塊化比較熟悉,并且對(duì)于react、vue兩者之一都比較熟練了。
下面呢我們就是使用react來寫一個(gè)自己的npm包,我們呢會(huì)使用自己封裝的webpack腳手架來寫,如果有興趣同學(xué)可以來看一下我的自我沉淀webpack5+react+eslint+tslint[1]文章。接下來的內(nèi)容呢也是基于此來說明的。
這里也有現(xiàn)成的腳手架[2]
一、不同點(diǎn)
npm包的目錄結(jié)構(gòu)和普通的腳手架結(jié)構(gòu)有所不同
1.啟動(dòng)目錄不同:以往我們習(xí)慣將entry文件寫在src中,但是npm包的入口文件不能寫在src中,因?yàn)閚pm是將我們的源代碼打包,不可以包括html。
所以將index.jsx和index.html文件提取到example文件中?!咀⒁狻縠xample文件要和src同級(jí)。
結(jié)構(gòu)和內(nèi)容如下
index.jsx
import React from 'react';
import { render } from 'react-dom';
import ReactDemo from '../src';
const App = () => <ReactDemo />;
render(<App />, document.getElementById('root'));
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
然后在src/index.jsx文件中 導(dǎo)出
import App1 from './App';
export default App1;
二 配置npm包的打包運(yùn)行文件
在 config文件夾中新建webpack.npm.js文件
配置文件內(nèi)容差不多。如下:詳細(xì)配置請(qǐng)移步 自我沉淀webpack5+react+eslint+tslint[3]
externals劃重點(diǎn):這個(gè)可以告訴npm打包的時(shí)候不許將下面幾種東西打包進(jìn)去哦。
const { resolve } = require('path');
const cssLoaders = [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
auto: (resourcePath) => resourcePath.endsWith('.less'),
localIdentName: '[local]_[hash:base64:10]',
},
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [['autoprefixer'], require('postcss-preset-env')()],
},
},
},
];
module.exports = {
entry: './src/index.tsx',
mode: process.env.NODE_ENV,
externals: {
antd: 'antd',
react: 'React',
},
output: {
libraryTarget: 'umd',
filename: 'index.js',
path: resolve(resolve(__dirname, '..'), 'dist'),
clean: true,
},
resolve: {
alias: {
'@': resolve(resolve(__dirname, '..'), 'src/'),
},
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
mainFiles: ['index'],
},
devServer: {
hot: true,
port: 3002,
host: '127.0.0.1',
compress: true,
open: true,
proxy: {
'/api': {
target: 'http://127.0.0.1:3002',
pathRewrite: { '^/api': '' },
secure: false,
},
},
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
include: resolve(resolve(__dirname, '..'), ''),
exclude: /node_modules/,
enforce: 'pre',
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
// 緩存:第二次構(gòu)建時(shí),會(huì)讀取之前的緩存
cacheDirectory: true,
},
},
],
},
{
test: /\.tsx$/,
loader: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: [...cssLoaders],
},
{
test: /\.less$/,
use: [...cssLoaders, 'less-loader'],
},
{
test: /\.s[ac]ss$/,
use: [...cssLoaders, 'sass-loader'],
},
{
exclude: /.(html|less|css|sass|js|jsx|ts|tsx)$/,
test: /\.(jpg|jpe|png|gif)$/,
loader: 'file-loader',
options: {
name: 'imgs/[name].[ext]',
outputPath: 'other',
},
},
{
test: /\.(ect|ttf|svg|woff)$/,
use: {
loader: 'file-loader',
options: {
name: 'icon/[name].[ext]',
},
},
},
],
},
};
下面著重說一下package.json中的內(nèi)容
name: 包名,后續(xù)在npm中搜索全靠它
version:版本號(hào),每發(fā)布一次npm包就要增加一個(gè)版本,每個(gè)版本不能重復(fù)。
description:描述
main: 本包向外暴露的文件,很重要,一定要和你打包出來的文件名一模一樣,我的叫做"dist/index.js"
private: true/false 是否為私有。一般為false否則只有自己能使用
flies: 暴露的文件夾, 有哪些文件夾提交到npm上面 格式為[ "dist" ]
keywords: npm檢索的關(guān)鍵字
author: 作者
license: ISC
peerDependencies: 代表著當(dāng)前npm包依賴下面這幾種環(huán)境。
完整配置
{
"name": "new_webpack_action2",
"version": "1.0.24",
"m": "",
"main": "dist/index.js",
"private": false,
"flies": [
"dist"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "export NODE_ENV=development && npx webpack serve --config config/webpack.dev.js",
"build": "export NODE_ENV=production && npx webpack --config config/webpack.prod.js",
"npm": "export NODE_ENV=production && npx webpack --config config/webpack.npm.js"
},
"keywords": [
"react",
"javascript",
"npm"
],
"author": "[email protected]",
"license": "ISC",
"devDependencies": {
"@ant-design/icons": "4.7.0",
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@types/lodash": "^4.14.178",
"@types/react": "^17.0.19",
"@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.11.0",
"@typescript-eslint/parser": "^5.11.0",
"autoprefixer": "^10.3.2",
"babel-loader": "^8.2.2",
"babel-plugin-import": "^1.13.3",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"eslint": "^8.8.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.2.0",
"html-webpack-externals-plugin": "^3.8.0",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.1",
"less-loader": "^10.0.1",
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.2.0",
"postcss-loader": "^6.1.1",
"postcss-preset-env": "^7.4.2",
"sass": "^1.38.0",
"sass-loader": "^12.1.0",
"speed-measure-webpack-plugin": "^1.5.0",
"style-loader": "^3.2.1",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"terser-webpack-plugin": "^5.1.4",
"thread-loader": "^3.0.4",
"ts-loader": "^9.2.5",
"tslint": "^6.1.3",
"typescript": "^4.5.5",
"webpack": "^5.68.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.0.0",
"webpack-merge": "^5.8.0",
"workbox-webpack-plugin": "^6.4.2"
},
"dependencies": {
"antd": "4.18.8",
"axios": "^0.26.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-router-dom": "5.2.0"
},
"peerDependencies": {
"@ant-design/icons": "4.7.0",
"antd": "4.18.8",
"bizcharts": "4.1.15",
"rc-footer": "0.6.6",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-router-dom": "5.2.0"
},
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
}
三、發(fā)布
如果是第一次發(fā)布包,執(zhí)行以下命令,然后輸入前面注冊(cè)好的NPM賬號(hào),密碼和郵箱,將提示創(chuàng)建成功
npm adduser
如果不是第一次發(fā)布包,執(zhí)行以下命令進(jìn)行登錄,同樣輸入NPM賬號(hào),密碼和郵箱
npm login
注意:npm adduser成功的時(shí)候默認(rèn)你已經(jīng)登陸了,所以不需要再進(jìn)行npm login了
接著先進(jìn)入項(xiàng)目文件夾下,然后輸入以下命令進(jìn)行發(fā)布
npm publish
當(dāng)終端顯示如下面的信息時(shí),就代表版本號(hào)為1.0.0(你的package.json中的版本號(hào))的包發(fā)布成功啦!前往NPM官網(wǎng)就可以查到你的包
+ 你的文件名@0.1.0
四、報(bào)錯(cuò)
1、如果出現(xiàn)
npm ERR! code E403
npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/ghost-watermarkdemo - Forbidden
npm ERR! 403 In most cases, you or one of your dependencies are requesting
npm ERR! 403 a package version that is forbidden by your security policy, or
npm ERR! 403 on a server you do not have access to.
以下幾種原因會(huì)導(dǎo)致
賬號(hào)密碼錯(cuò)誤 (請(qǐng)檢查npm官網(wǎng)的賬號(hào)密碼)
包重名 (請(qǐng)檢查npm官網(wǎng)上是否有同名項(xiàng)目,名字取決于 package.js 的項(xiàng)目名字段)
網(wǎng)絡(luò)原因
鏡像源問題
新注冊(cè)的用戶郵箱未激活。 登陸你的郵箱去激活(如下)

2、 如果出現(xiàn)

需要在你的package.json中 private改為false或者刪除
更新已經(jīng)發(fā)布的包
更新包的操作和發(fā)布包的操作是一樣的
npm publish
但是每次更新時(shí),必須修改版本號(hào)后才能更新,比如將1.0.0修改為1.0.1后才能更新發(fā)布。
這里的包版本管理規(guī)則都是一樣的,采用的是semver(語義化版本),意思就是版本號(hào):大改.中改.小改
五、## 從npm上面卸載自己發(fā)布的包
進(jìn)入自己項(xiàng)目的目錄執(zhí)行。npm unpublish --force 出現(xiàn):
npm WARN using --force Recommended protections disabled.
-包名@0.1.0
則卸載成功,這時(shí)在npm上面就搜索不到了
參考資料
https://juejin.cn/post/7002157698108096543: https://juejin.cn/post/7002157698108096543
[2]https://github.com/ghost-myl/npm-custom_webpack: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fghost-myl%2Fnpm-custom_webpack
[3]https://juejin.cn/post/7002157698108096543: https://juejin.cn/post/7002157698108096543
關(guān)于本文
來自:夏末海棠
https://juejin.cn/post/7072652104837365774
