本文要点
package.json是对下载的包或模块的描述信息,如果你要上传包到npm服务器也要有对应的模块说明。说明包括项目名称、版本、作者等等。package.json必须是一个严格的json格式,也就是说每一个字段都要使用双引号,不论是key值还是value值。
package.json是对下载的包或模块的描述信息,如果你要上传包到npm服务器也要有对应的模块说明。说明包括项目名称、版本、作者等等。package.json必须是一个严格的json格式,也就是说每一个字段都要使用双引号,不论是key值还是value值。
如果你要在npm上发布你的包,则package.json必须要有两个字段:name和version。其中name是包的名称,version是版本,对于这两个字段会有些约束。
除了这两个字段外,还有其他对应的描述信息,下面对常用的字段做一个说明。
这两个都是对应的描述信息,使用两个字段其中的某些内容进行搜索,可以在npm官网搜索到相关的包。不过这两个也有他们的区别,前者对应一个字符串,是对项目的一个简要说明,可以是一段描述的语句。而后者是一个字符串数组,就像写论文开头的关键字,我们可以看一下vue项目的package.json,他里面的description和keyword如下:
"description": "Reactive, component-oriented view layer for modern web interfaces.",
"keyword": [
"vue"
]
可以看出,keyword字段必须是一个数组,即使数组中只有一个成员。另外,数组的成员要使用双引号,而数组的[]表示不需要双引号。
作者
该包的官网地址
字段指定了程序的主入口文件,使用遵循CommonJS规范的require(‘moduleName’)就会加载main字段指定的目录下的文件。这个字段的默认值是模块根目录下面的index.js,也就是说如果不指定main字段,在其他模块引用此模块会默认加载根目录下的index.js文件。
files是一个包含项目中的文件的数组。如果命名了一个文件夹,那也会包含文件夹中的文件。(除非被其他条件忽略了)
你也可以提供一个.npmignore文件,让即使被包含在files字段中得文件被留下。其实就像.gitignore一样。
你项目的提交问题的url和(或)邮件地址。
{
"url" : "http://github.com/owner/project/issues",
"email" : "[email protected]"
}
你可以指定一个或者两个。如果你只想提供一个url,那就直接用字符串。
如果提供了url,它会被npm bugs命令使用。
此字段指定了使用ES6的module模块引入该模块时加载的文件路径,即使用import ‘xx’ from './xx’所指定的路径。关于main字段和module字段的区别会在下面讲。
dependencies字段和devDependencies字段:这两个字段表示项目的依赖,前者表示项目正常运行(生产环境)时需要的依赖,后者指开发(开发环境)时所需要的依赖。开发环境和生产环境后面会做说明。这两个字段都是一个对象,对象的成员又指定的模块和对应的版本组成,表示依赖的模块和版本范围,注意是版本范围。
由脚本命令组成的字典,这些命令运行在包的各个生命周期中。这里的键是生命周期事件名,值是要运行的命令。比如下面的字段:
"script": {
"install": "scripts/install.js",
"postinstall" : "scripts/install.js",
"uninstall" : "scripts/uninstall.js",
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"build": "node build/build.js"
}
比如上面的install,在安装npm包时就应用后面的命令,即npm install会运行"script/install.js"命令。上面前三个是npm安装的生命周期,后面的几个就是自定义的命令了,比如使用npm run build运行build后面的命令。
npm 允许在package.json文件里面,使用scripts字段定义脚本命令。 优点: 项目的相关脚本,可以集中在一个地方。
不同项目的脚本命令,只要功能相同,就可以有同样的对外接口。用户不需要知道怎么测试你的项目,只要运行npm run test即可。
可以利用 npm 提供的很多辅助功能。
npm 脚本的原理非常简单。每当执行npm run,就会自动新建一个 Shell,在这个 Shell 里面执行指定的脚本命令。因此,只要是 Shell(一般是 Bash)可以运行的命令,就可以写在 npm 脚本里面。
比较特别的是,npm run新建的这个 Shell,会将当前目录的node_modules/.bin子目录加入PATH变量,执行结束后,再将PATH变量恢复原样。
这意味着,当前目录的node_modules/.bin子目录里面的所有脚本,都可以直接用脚本名调用,而不必加上路径。比如,当前项目的依赖里面有 Mocha,只要直接写mocha test就可以了。
*通配符
*表示任意文件名,**表示任意一层子目录。
"lint": "jshint *.js"
"lint": "jshint **/*.js"
如果要将通配符传入原始命令,防止被 Shell 转义,要将星号转义。
"test": "tap test/\*.js"
脚本传参符号: –
"server": "webpack-dev-server --mode=development --open --iframe=true ",
脚本执行顺序
并行执行(即同时的平行执行),可以使用&符号
npm run script1.js & npm run script2.js
继发执行(即只有前一个任务成功,才执行下一个任务),可以使用&&符号
npm run script1.js && npm run script2.js
脚本钩子
npm 脚本有pre和post两个钩子, 可以在这两个钩子里面,完成一些准备工作和清理工作
"clean": "rimraf ./dist && mkdir dist",
"prebuild": "npm run clean",
"build": "cross-env NODE_ENV=production webpack"
npm 默认提供下面这些钩子:
prepublish,postpublish
preinstall,postinstall
preuninstall,postuninstall
preversion,postversion
pretest,posttest
prestop,poststop
prestart,poststart
prerestart,postrestart
拿到package.json的变量
npm 脚本有一个非常强大的功能,就是可以使用 npm 的内部变量。
首先,通过npm_package_前缀,npm 脚本可以拿到package.json里面的字段。比如,下面是一个package.json。
// package.json
{
"name": "foo",
"version": "1.2.5",
"scripts": {
"view": "node view.js"
}
}
我们可以在自己的js中这样:
console.log(process.env.npm_package_name); // foo
console.log(process.env.npm_package_version); // 1.2.5
cross-env让这一切变得简单,不同平台使用唯一指令,无需担心跨平台问题:
"start": "cross-env NODE_ENV=production node server/index.js",
表示你的项目所运行的node版本。不指定engine字段,或者用*表示不限制node版本。
{
"engines" : {
"node" : ">=0.10.3 <0.12" } }
并且,像dependensies一样,如果你不指定版本或者指定“*”作为版本,那么所有版本的node都可以。
如果指定一个“engines”字段,那么npm会需要node在里面,如果“engines”被省略,npm会假定它在node上工作。
你也可以用“engines”字段来指定哪一个npm版本能更好地初始化你的程序,如:
{
"engines" : {
"npm" : "~1.0.20" } }
记住,除非用户设置engine-strict标记,这个字段只是建议值
如果你设置 “private”: true,npm就不会发布它。
这是一个防止意外发布私有库的方式。如果你要确定给定的包是只发布在特定registry(如内部registry)的,用publishConfig hash的描述来重写registry的publish-time配置参数
指定你的代码存放的地方。这个对希望贡献的人有帮助。如果git仓库在github上,那么npm docs
命令能找到你。
"repository" : {
"type" : "git" ,
"url" : "http://github.com/isaacs/npm.git"
}
"repository" : {
"type" : "svn" ,
"url" : "http://v8.googlecode.com/svn/trunk/"
}
URL应该是公开的(即便是只读的)能直接被未经过修改的版本控制程序处理的url。不应该是一个html的项目页面。因为它是给计算机看的。
它是一个命令名和本地文件名的映射。在安装时,如果是全局安装,npm将会使用符号链接把这些文件链接到prefix/bin,如果是本地安装,会链接到./node_modules/.bin/。
通俗点理解就是我们全局安装, 我们就可以在命令行中执行这个文件, 本地安装我们可以在当前工程目录的命令行中执行该文件。
"bin": {
"gynpm": "./bin/index.js"
}
要注意: 这个index.js文件的头部必须有这个#!/usr/bin/env node节点, 否则脚本将在没有节点可执行文件的情况下启动
{
"name": "cc",
"version": "1.0.0",
"description": "",
"main": "index.js",
"bin": {
"mason": "./index.js"
},
"scripts": {
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
}
}
在package.json的同级目录新建index.js文件
#!/usr/bin/env node
console.log('cool')
然后在项目目录下执行: mac下: sudo npm i -g, window下: npm i -g
接下来你在任意目录新开一个命令行, 输入mason, 你讲看到
cool字段。
不知道通过这个小实验能不能帮助大家更好的理解这个bin的作用。像我们常用的vue-cli, create-react-app等都是通过bin属性将命令映射到了全局上。
生产包
开发包
字段详解
main字段和module字段
要理解main字段和module字段的不同要先明白Tree-Shaking的机制,简单来讲,Tree-Shaking的作用是将没有用到的代码全部过滤掉,但是在使用main字段作为入口导入的文件是遵循CommonJS规范的,也就是说暴露出的代码是作为export对象的一个属性,举例来讲,如果暴露出一个add的方法作为export的属性,那在外部访问的时候如果使用"a"+"dd"的方式访问,打包工具是无法检测到是否使用了add这个方法,因为访问js的对象属性实在是太灵活了。如果使用ES6的module新特性,则不需要将其作为export对象的属性暴露出来的,而且ES6提出的module模块有更好的模块特性。这样的话打包的时候打包工具就能够检测到暴露出来的那些方法没有用到的,以便除去这些代码。
在package.json文档中并没有提到module字段,那为什么会出现module的字段,其实这是rollup最早提出的概念pkg.module。在早期很多npm包都是遵循CommonJS规范的,那时package.json长这样:
{
"name": "xxx",
"version": "1.0.0",
"main": "lib/index.js"
}
在模块中使用require(‘xxx’),会将require的参数(xxx)作为一个包来进行查找,读取该目录下package.json文件,取得main字段指定的文件作为入口。但在ES6盛行之后有了module模块,并且这是官方标准,用起来更顺手,而CommonJS还是传统格式,所以rollup利用ES6的module新特性,在打包的性能上有了一个很好的提高。
那既然main字段可以作为文件的入口,何必还要module字段的呢?原因有以下两点:
按照约定发布在npm上面的包都是基于ES5规范的,而且通常我们在使用babel工具将ES6编译为ES5的过程中是将node_modules目录下的文件过滤掉的,也就是说node_modules目录下的文件将不会被babel工具编译,这是因为避免babel编译node_modules目录下的文件会极大提高编译速度。但如果我们把main字段直接指向我们用ES6语法写的源码上用户就要打开babel编译工具配置其编译node_modules目录下的文件。
如果用户开发的是node环境下的项目使用到我们发布的基于ES6语法写的源码,可能连打包的步骤都不会有,而他的是node环境恰巧不支持ES6的语法,那代码就会报错。
因此main字段应该是指向编译后的ES5的代码,于是就应该出现一种新的字段解决这个问题,此时module字段就出现了,那现在的package.json长这样
{
"name": "xxx",
"version": "1.0.0",
"main": "lib/index.js",
"module": "lib/index.esm.js"
}
以上说明,我们的main字段应该指向基于CommonJS规范而使用ES5语法写的源码,而moudle字段应该是指向遵循ES6模块规范的(也就是说使用ES6模块导出的,这样是为了能够让打包工具使用Tree-Shaking机制打包代码)而使用ES5语法编写的源码,这是为了避免用户在使用babel编译工具屏蔽掉了node_modules目录中的文件。我们可以看一下Vue源码中package.json中main和module字段指定的源码就是这样做的。
在webpack2.0版本以上打包过程中,也逐渐开始支持module字段。** 在打包过程中,如果遇到module字段会优先使用module字段表示的路径下的文件,如果module不存在,则用main字段表示的作为入口,并按照CommonJS的规范打包。**