npm是什么
npm是Node Package Manager的缩写,是一个用于JavaScript包管理的工具。它是Node.js生态系统中的一部分,用于帮助开发者在项目中管理和共享代码包。npm的主要功能包括:
包管理: npm允许开发者通过命令行工具安装、升级、删除和管理JavaScript包(也称为模块或库)。这些包可以包含代码、依赖关系、配置文件等,以便在项目中共享和重用。
依赖管理: npm能够管理项目所需的依赖关系,并确保这些依赖关系的正确版本被安装。这有助于确保项目的稳定性和可重复性。
版本控制: npm使用语义版本控制(Semantic Versioning)来管理包的版本。这有助于开发者了解何时可以安全地更新其项目中的依赖项。
命令行工具: npm提供了一个命令行界面,使开发者能够方便地执行各种与包管理相关的任务,如安装、升级、搜索和发布包等。
包的发布和共享: 开发者可以使用npm将他们的JavaScript包发布到npm注册表中,这是一个用于存储和共享JavaScript包的在线仓库。其他开发者可以通过npm安装并使用这些包。
什么是打包
在软件开发中,打包(Packaging)是指将应用程序或项目的各个组成部分(例如源代码文件、依赖项、配置文件等)整合到一个或多个文件中,以便于分发、部署或交付给最终用户。前端常见的项目构建工具(如Webpack、Parcel、Rollup等),则打包通常包括构建输出物,这些文件包含了经过编译、压缩和优化的代码。在深入对比Webpack、Parcel、Rollup打包工具中,我们总结了,rollup相比于webpack更适合打包一些第三方的类库,因此本文主要通过rollup来进行打包。
什么是npm域级包
npm的域级包(Scoped Packages)是一种npm包的命名约定,它允许开发者在包的名称前添加一个命名空间,通常使用“@”符号。这个命名空间通常与组织、公司或个人的名称相关联,以确保包名称的唯一性。
域级包的命名格式如下:
@scope/package-name
其中,@scope是域名,可以是组织、公司或个人的名称,package-name是包的实际名称。例如,一个名为my-package的公司或组织example的域级包可以命名为:
@example/my-package
使用域级包的优势包括:
唯一性: 通过使用域名作为前缀,可以确保包名称的唯一性。即使不同的组织、公司或个人使用相同的包名称,由于它们的域名不同,这些包也会被区分开来。
组织和结构: 域级包有助于将包组织成逻辑上相关的集合。这对于大型项目、公司内部项目或共享包的情况非常有用。
要发布和安装域级包,可以使用npm publish和npm install命令,并在包名称前加上域名前缀。例如:
#?发布域级包
npm?publish?--access?public
#?安装域级包
npm?install?@example/my-package
发布指定文件
当你使用npm publish命令发布包时,默认情况下,npm会将整个项目目录发布到npm注册表。如果你只想发布项目的特定文件或目录,可以通过.npmignore文件或.gitignore文件来定义不需要发布的文件或目录,从而实现选择性发布。
以下是一些步骤,以及如何使用.npmignore或.gitignore来限制发布的文件:
使用 .npmignore 文件
创建 .npmignore 文件: 在项目根目录下创建一个名为.npmignore的文件。
编辑 .npmignore 文件: 在.npmignore文件中列出你不想发布的文件或目录,就像编辑.gitignore文件一样。
#?.npmignore
#?不发布的文件或目录
node_modules/
test/
docs/
运行 npm publish 命令: 确保.npmignore文件包含你希望排除的文件和目录后,运行npm publish命令。
使用 .gitignore 文件
如果项目中已经存在.gitignore文件,你也可以使用它来达到相同的目的。
编辑 .gitignore 文件: 在.gitignore文件中列出你不想发布的文件或目录。
#?.gitignore
#?不发布的文件或目录
node_modules/
test/
docs/
在 package.json 文件中添加 "files" 字段: 如果你使用.gitignore而不是.npmignore,你还需要在package.json文件中添加一个"files"字段,以确保只发布.gitignore中列出的文件。
//?package.json
{
"name":?"your-package",
"version":?"1.0.0",
"files":?[
"dist/",
"src/",
"README.md"
],
//?其他?package.json?配置...
}
运行 npm publish 命令: 确保.gitignore文件包含你希望排除的文件和目录,然后运行npm publish命令。
npm包项目开发
创建项目
创建名为hello-npm文件夹,然后在该目录下,执行npm init -y初始化一下
/hello-npm
├─?.babelrc.json????//babel配置文件
├─?.gitignore
├─?LICENSE
├─?package-lock.json
├─?package.json
├─?README.md
├─?rollup.config.js??//rollup构建配置文件
├─?src
│??├─?deepClone.ts
│??├─?index.ts??????//主入口文件
│??...
└─?tsconfig.json????//ts配置文件?可以用tsc命令生成
安装依赖
//ts相关包
npm?i?-D?typescript?tslib
//rollup相关包
npm?i?-D?rollup?@rollup/plugin-node-resolve?rollup-plugin-commonjs?rollup-plugin-typescript
//babel相关
npm?i?-D?@rollup/plugin-babel
npm?i?-D?@babel/core?@babel/preset-env
配置tscofig.json
全局安装typescript,用tsc命令生成tscofig.json文件,你也可以自己手动新建
npm?i?-g?typescript
tsc?--init
//?tsconfig.json
{
"compilerOptions":?{
"target":?"es5",??//?要改成es5,不然babel转换es6的时候有些转换不了
"module":?"commonjs",
"esModuleInterop":?true,
"forceConsistentCasingInFileNames":?true,
"strict":?true,
"noImplicitAny":?false,
"noImplicitThis":?false,
"skipLibCheck":?true
}
}
配置babel
//?.babelrc.json
{
"presets":?[
"@babel/env"
]
}
配置rollup
//?rollup.config.js
import?resolve?from?"@rollup/plugin-node-resolve";
import?babel?from?"@rollup/plugin-babel";
import?commonjs?from?"rollup-plugin-commonjs";
import?typescript?from?"rollup-plugin-typescript";
export?default?{
input:?"src/index.ts",?//?打包入口
output:?{
//?打包出口
file:?"dist/index.js",
format:?"umd",?//?umd是兼容amd/cjs/iife的通用打包格式,适合浏览器
name:?"utilibs",?//?cdn方式引入时挂载在window上面用的就是这个名字
sourcemap:?true,
},
plugins:?[
//?打包插件
resolve(),?//?查找和打包node_modules中的第三方模块
commonjs(),?//?将?CommonJS?转换成?ES2015?模块供?Rollup?处理
typescript(),?//?解析TypeScript
babel({?babelHelpers:?"bundled"?}),?//?babel配置,编译es6
],
};
实现deepClone方法
这里我们故意用es6的箭头函数来写,后面测试babel有没有把他编译成es5的语法
//?src/deepClone.ts
/**
*?深拷贝
*?@param?obj?需要深拷贝的对象
*?@returns
*/
const?deepClone?=?(obj:?Object)?=>?{
//?不是引用类型或者是null的话直接返回
if?(typeof?obj?!==?"object"?||?typeof?obj?==?null)?{
return?obj;
}
//?初始化结果
let?result:?object;
if?(obj?instanceof?Array)?{
result?=?[];
}?else?{
result?=?{};
}
for?(let?key?in?obj)?{
//?保证不是原型上的属性
if?(obj.hasOwnProperty(key))?{
//?递归调用
result[key]?=?deepClone(obj[key]);
}
}
return?result;
};
export?default?deepClone;
在主入口文件处引入刚刚写的深拷贝
//?src/index.ts
import?deepClone?from?"./deepClone";
export?{
deepClone
};
配置package.json
增加一个main属性,到时候打包发布到npm以后在项目里import引入时的主入口文件,和rollup的打包出口文件相对应
增加script打包命令
{
"name":?"ldp-hello-npm",
"version":?"1.0.0",
"description":?"js通用方法库",
"main":?"index.js",????//主入口文件
"type":?"module",
"scripts":?{
"build":?"rollup?--config",
"test":?"echo?\"Error:?no?test?specified\"?&&?exit?1"
},
"keywords":?[
"toolkit",
"rollup",
"typescript"
],
"author":?"lh",
"license":?"ISC",
"devDependencies":?{
"@babel/core":?"^7.23.7",
"@babel/preset-env":?"^7.23.7",
"@rollup/plugin-babel":?"^6.0.4",
"@rollup/plugin-node-resolve":?"^15.2.3",
"rollup":?"^4.9.2",
"rollup-plugin-commonjs":?"^10.1.0",
"rollup-plugin-typescript":?"^1.0.1",
"tslib":?"^2.6.2",
"typescript":?"^5.3.3"
}
}
打包编译
npm?run?build
可以看到打包成功了
看看dist目录下的产物
可以看到确实生成了一个index.js对应我们rollup配置的打包出口文件,这也是我们package.json中配置的main属性到时候导入要用到的入口文件
刚才故意用箭头函数写的深拷贝也成功被转成了es5的语法
本地调试模拟npm包安装导入验证
准备一个正常的项目,我们这里使用vite工具搭建一个简易的名为vite-project项目,如要搭建完善的项目脚手架请看我之前发的公众号
npm?create?vue@latest
cd?
npm?install
npm?run?dev
把我们hello-npm中的package.josn和README.md拷贝到dist目录下
用npm link链接到全局
cd?dist
npm?link
提示成功,可以去自己全局安装的npm的node_modules下找一找是不是有
接下里去vite-project项目里link这个包, 这里注意要带上包名(package.json里面配置的name)
npm?link?ldp-hello-npm
取消link
npm?unlink?ldp-hello-npm
这里用npm link引入以后有eslint的报错的话加个.eslintignore文件忽略链接到的这个地址的eslint检查
在vite-project的main.ts里面引入写上这样一段代码测试一下有没有生效
//?src/main.ts
import?{?deepClone?}?from?"ldp-hello-npm";
const?obj:?any?=?{?a:?100,?b:?{?c:?200?}?};
const?objCopy?=?deepClone(obj);
obj.b.c?=?300;
console.log("obj:",?obj);
console.log("objCopy",?objCopy);
这里import引入utilibs时没有代码补全提示,并且启动有找不到模块的报错
我们回到hello-npm项目中:
tsconfig.json配置修改
{
"compilerOptions":?{
"target":?"es5",??//?要改成es5,不然babel转换es6的时候有些转换不了
"module":?"commonjs",
"declaration":?true,?//?根据ts文件自动生成.d.ts声明文件和js文件
"emitDeclarationOnly":?true,?//?只输出.d.ts声明文件,不生成js文件
"outDir":?"./dist",?//?输出目录
"esModuleInterop":?true,
"forceConsistentCasingInFileNames":?true,
"strict":?true,
"noImplicitAny":?false,
"noImplicitThis":?false,
"skipLibCheck":?true
}
}
修改package.json,增加scripts脚本和types属性
"types":?"index.d.ts",??//?声明文件的主入口
"scripts":?{
"build:types":?"tsc",
"build":?"npm?run?build:types?&&?rollup?--config",
"test":?"echo?\"Error:?no?test?specified\"?&&?exit?1"
},
重新去执行npm run build,就可以生成声明文件了
切回vite-project项目看看,就有引入提示了
如果以前改过npm的镜像地址,比如使用了淘宝的镜像,就先改回来
npm?config?set?registry?https://registry.npmjs.org/
本地验证结束,自然是要发布到npm上去了,先去npm官网上面注册一个npm的账号
注意dist目录下要有package.json和README.md, npm官网上显示的信息都是这里面读取;
package.json里的version每次发布都要比之前的大
cd?dist
npm?addUser?or?npm?login?//?添加账户输入账号密码邮箱和邮箱验证码
打开链接验证(由于我添加过之后再次执行上边命令就会验证账号)
如果不知道当前登录的账号可以用who命令查看身份:
npm?who?am?i
npm?profile?get?//?查看绑定的账号信息
还可以用下面命令退出当前账号
npm?logout
登录成功就可以将我们的包推送到npm上去了
npm?publish
发布npm包时可能会遇到的一些坑
邮箱未验证
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?ERR!?publish?Failed?PUT?403
npm?ERR!?code?E403
npm?ERR!?You?do?not?have?permission?to?publish?"your-package".?Are?you?logged?in?as?the?correct?user??:?your-package
包和别人的包重名了。修改包名3. 源设置成第三方源,比如设置了淘宝镜像。只要把源设为默认的即可
npm包开发模板源码:https://github.com/LHNB521/hello-npm.git
领取专属 10元无门槛券
私享最新 技术干货