传送门: to My Blogjavascript
这个话题的目的实际上是想深度的挖一挖 package.json 这个文件都有哪些属性、均可觉得咱们提供哪些解决问题的方法。目前前端工程化最离不开的就是 package.json 这个文件了,以这个文件为出发点,构造了庞大的前端摩天大楼。这篇文章的前半段大都是枚举 package.json 的各类属性,请择食。后面有类最佳实践的内容,最后是你们关心的 package.json 的进化体 -- package-lock.json。css
1. 想要完全了解 package.json 首先须要枚举它的属性。html
2. 以后,咱们会了解这个文件在工程化中起到了多么重要的做用。前端
3. 在深度认识这个文件以后,咱们要考虑的是如何用好它,为咱们的工程化铺平道路。vue
4. Let's Go !java
5. 以后了解一下最关键的 scripts 能够进行什么骚操做。node
6. Package.json 是怎么变成 package-lock.json 和 yarn.lock 的。linux
任何理性的知识整理都是根据时间线索从它的源头开始的,咱们第一次接触到 package.json 这个文件的时候应该是从NPM这个包管理工具开始的。NPM 表面来看它可能只是一个包管理工具,其实本质上是前端工程化的一个统一的解决方案。NPM 通过几回大版本的迭代和中途 yarn 的冲击,已经变成了一个及其成熟的系统了。目前NPM的包数量在193K个,仅次于Go。git
那么问题来了,NPM是如何提供解决方案的呢?固然是提供了良好的包管理方案了,上古时期的前端模块划分是经过js/css/html/img进行文件分类的,这样的按文件划分方案的不足可想而知,没有清晰的功能模块概念,复用困难。github
NPM 是如何管理模块的呢?NPM的推出,将具备统一功能的一些js/html等放到一个Package里,这些Package统一的执行,达到这个Package开发者开发时候的功能,积极面向对象编程。
Package愈来愈多怎么办?随着开发者创造力的提高,Package会愈来愈多,NPM同时提供了一个NPM源为世界上的开发者同事提供下载安装使用的服务。
单个Package在没有安装下载以前不知道其功能的怎么办?单个包功能不清,NPM就提供了应对策略 -- Package.json 文件,进行规范的包内容介绍,规范的依赖关系,规范的内置脚本执行,许可规范等。README文件,为这个包提供功能显示。NPM也默认读取项目根目录下的 README.md 进行展现。
所以 package.json 实际上是NPM对于包管理和项目管理的一种规范。也是为开发者减小没必要要的理解负担,经过package.json 的丰富属性能够很好的解决并度过前端工程规范的初级阶段。
经过 package.json 的起源了解到,package.json 的属性都是NPM对于工程化的规范的产物,既然规则是NPM指定的,那么咱们想要获得NPM对于包管理的便利条件,也就一样须要遵照这样的规范。这些规范的细节提现就在package.json 这个文件提供的属性上面。
NPM 官方 package.json文档: docs.npmjs.com/files/packa… 在官方文档的帮助下能够快速的了解其丰富属性。
package.json 会受到NPM配置的影响
若是你的工程想要公布在NPM网站上供全世界访问和使用,name和version这个字段能够说是两个P0级别的属性且是必须的,由于这两个属性是搜索的必须条件。若是是本身开发的项目没有Publish的计划,这两个属性是可选的,咱们能够用这两个属性去标识咱们的代码和项目。
• 既然NPM是规范的制定者,固然也为这个属性增长了一些规范,须要了解:
1. 名称必须小于或等于214个字符。
2. 名称不能以点或下划线开头。
3. 名称不能含有大写字母。
4. 该名称最终成为URL的一部分,命令行上的参数和文件夹名称。所以,名称不能包含任何非URL安全字符。
• 一些官方建议:
请勿使用与核心节点模块相同的名称。
不要在名称中加上“js”或“node”
该名称可能会做为参数传递给require(),所以它应该是简短的,但也是合理描述的。
起名的时候能够先查看 npmjs.org 是否有命名重复
• 类最佳实践Tips:
使用 @ 开头: 例如: @vue/cli,
使用短线链接 a-b-c
不适用 js/node。若是想要区分引擎,在对应的属性里面限制便可。
命名可使用npm-scope命名做用域做为开头。
综上: name属性是在publish 的时候很关键的属性。能够有效区分咱们的项目。
和name同样,若是想要publish你的package,version 和 name 会一块儿组装造成这个代码包的npm平台识别标识,npm官方但愿更新内容应该随着版本一块儿提供。不发包的状况下,version是可选的。对于version版本号具体的规范,不在本话题讨论范围内。
• 一些官方建议:
• Tips:
2. node-semver:对于version 官方提供了一个npm插件解决这个问题。有一点的学习成本。
这个属性做为一个程序包的说明使用,其值是一个字符串,用来描述这个项目。对于publish的项目来讲,这个属性,影响用户在npmjs.org的搜索。 npm search
例如: vue.js 的description:
// Vue 的package.json 的描述。
{
"description": "Reactive, component-oriented view layer for modern web interfaces.",
}
复制代码
这个属性就比较日常了,就像你在写一篇文档的时候,须要提取一些关键词同样,这个属性是一个字符串数组。仅此而已。
项目主页的URL地址。 字符串。
对于这个属性的值,最好是项目地址的文档目录。例如GitHub的ReadMe
{
"homepage": "https://github.com/owner/project#readme"
}
复制代码
bugs是一个对象,表示使用者遇到问题时候及时获取帮助。若是只想提供一个地址URL,能够是一个字符串。有两个属性:
1. url
2. email
"bugs": {
"url": "https://github.com/owner/project/issues",
"email": "project@hostname.com"
}
复制代码
若是是单许可证,是一个字符串。
若是是多许可证,是一个对象数组。
最多见的许可证是: MIT 。
对于许可证的相关姿式,详见: 推荐阅读 做为前端,你须要了解的开源协议知识
做者和贡献者,单独开发者使用 author, 一群人的共同开发请使用 contributors 贡献者。有name, email, url 三个属性可选。
{
"name": "Barney Rubble",
"email": "b@rubble.com",
"url" : "http://barnyrubble.tumblr.com/"
}
复制代码
例如: "Barney Rubble <b@rubble.com> (barnyrubble.tumblr.com/)"
• files 属性的值是一个数组
• 内容是模块下的文件名和文件夹名,这些被列举的文件会被包含在npm模块中。
• 可使用 .npmignore 文件进行文件排除,这个配置的优先级大于 files 属性的值。
• .npmignore 会存在于项目的根目录中。
• 如下的内容会被自动永久默认的包含在files中: ◦ package.json ◦ README ◦ CHANGES / CHANGELOG / HISTORY ◦ LICENSE / LICENCE ◦ NOTICE ◦ main 属性中的文件
• 某些文件会被NPM自动忽略: ◦ .git ◦ CVS ◦ .svn ◦ .hg ◦ .lock-wscript ◦ .wafpickle-N ◦ ..swp ◦ .DS_Store ◦ ._ ◦ npm-debug.log ◦ .npmrc ◦ node_modules ◦ config.gypi ◦ *.orig ◦ package-lock.json (use shrinkwrap instead)
• main 字段的值是一个字符串
• main 的内容是这个模块的主入口文件相对于package.json的文件路径。
• 当其余项目使用 import/require 等进行引入的时候,其实就是引用 main 的文件的 export 内容。
• 其实在main的对应文件中,你只须要提供导出的主要代码,不须要长篇大论的实现。
若是您的模块要在客户端使用,则应使用浏览器字段而不是主字段。这有助于提示用户它可能依赖于Node.js模块中不可用的基元。(例如window)
• bin 属性的值是一个对象。
• bin 适用于在代码中有可执行文件的时候。意思是若是你的模块有独立的命令须要执行的时候,可使用这个属性添加自定义的脚本。
• 如下代码中, test.js 是一个可执行的脚本,在这个文件的开头须要使用 #!/usr/bin/env node
// package.json
{
"bin":{
"test" : "./test.js"
}
}
复制代码
• 在安装带有bin属性的模块时,若是是全局安装,可执行文件会被注册到 xxx/bin。
• 若是是普通安装,全部的可执行文件都会注册到 ./node_modules/.bin/下。
• man 属性的值是一个字符串或者是一个数组。
• man 的用法很相似与 shell 命令的 man
• man 也是为了给咱们的模块提供一个可供理解的doc。
• direcotries 的值是一个对象。
• direcotries 的配置目前没有功能性,只具有解释性。
• direcotries 目前用来解释各个文件在哪里
• 如 : direcotries.lib 指定lib目录位置
• 除了 lib 以外,npm的direcotries还分别配置了如下位置:
• repository 是一个对象。
• repository 指定代码的提交目录,对想提PR的人有帮助。
• scripts 是一个对象。
• scripts 用来编写模块中使用的脚本。
• npm 在一些阶段提供了对 scripts 的钩子,能够在其中使用。
• config 是一个对象。
• config 是模块中常驻的配置项。
• npm 提供了使用和修改config的方法。
• dependencies 的值是一个对象。
• dependencies 用于配置模块依赖的模块列表。key是模块名称,value是版本范围
• dependencies 是生产环境的依赖,不要把测试工具或transpilers写到dependencies中。
• dependencies 的版本范围有不少种写法。
• URL 做为依赖
◦ 将在安装时下载并安装在您的软件包本地。
• Git URL
◦ 规则: <protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]
◦ demo:
git+ssh://git@github.com:npm/cli.git#v1.0.27
git+ssh://git@github.com:npm/cli#semver:^5.0
git+https://isaacs@github.com/npm/cli.git
git://github.com/npm/cli.git#v1.0.27
• GitHub URLs
◦ 前面和Git Urls 同样,后面能够加 commit 后缀。
• Local Paths
◦ "bar": "file:../foo/bar"可使用本地的代码路径。
• 用户下载安装你发布的模块,你的某些依赖不但愿被引用。或者这些只是在你开发的时候给予你便利使用
• 在业务项目中,这部份内容只用于dev开发中,不在生产环境使用。
• 在部分的引用依赖须要使用npm link或者npm install进行安装。
有时候作一些插件开发,好比grunt等工具的插件,它们每每是在grunt的某个版本的基础上开发的,而在他们的代码中并不会出现require("grunt")这样的依赖,dependencies配置里边也不会写上grunt的依赖,为了说明此模块只能做为插件跑在宿主的某个版本范围下,能够配置peerDependencies:
{
"name": "tea-latte",
"version": "1.3.5",
"peerDependencies": {
"tea": "2.x"
}
}
复制代码
上面这个配置确保再npm install的时候tea-latte会和2.x版本的tea一块儿安装,并且它们两个的依赖关系是同级的:
├── tea-latte@1.3.5
└── tea@2.2.0
这个配置的目的是让npm知道,若是要使用此插件模块,请确保安装了兼容版本的宿主模块。
• 是个数组。
• 捆绑须要一块儿安装的内容。
若是可使用依赖项,可是若是没法找到或没法安装,您但愿npm继续,那么您能够将它放在optionalDependencies 对象中。这是包名称到版本或URL的映射,就像 dependencies对象同样。不一样之处在于构建失败不会致使安装失败。
• 指定您的东西所使用的node的版本。
• 版本范围和依赖安装保持一致。
• node版本: { "engines" : { "node" : ">=0.10.3 <0.12" } }
• npm 版本: { "engines" : { "npm" : "~1.0.20" } }
• 模块设置了engine-strict配置标志,会产生警告。
• 此功能已在npm 3.0.0中删除
• 在npm 3.0.0以前,此功能用于处理此程序包,就像用户已设置同样engine-strict。它已再也不使用。
• 指定操做系统
• "os" : [ "darwin", "linux" ]
• "os" : [ "!win32" ]
• 原理是使用node 的 process.platform来判断。
• 若是您的代码仅在某些cpu体系结构上运行,则能够指定哪些代码。
• "cpu" : [ "x64", "ia32" ]
• 与os选项同样,您也能够将架构列入黑名单:
• "cpu" : [ "!arm", "!mips" ]
• 主机架构由肯定 process.arch
• npm 只会发布 "private": false的包。
• 能够指定为 true 来进行标记。防止误发布。
这是一组将在发布时使用的配置值。若是要设置标记,注册表或访问权限,则特别方便,以便确保给定的包未标记为“latest”,发布到全局公共注册表或默认状况下做用域模块是私有的。
能够覆盖任何配置值,但只有“tag”,“registry”和“access”可能对于发布而言很重要。
请参阅npm-config以查看能够覆盖的配置选项列表。
例如: "scripts": {"start": "node server.js"}
例如: "scripts":{"install": "node-gyp rebuild"}
例如: "contributors": [...]
若是AUTHORS包的根目录中有文件,则npm会将每一行视为一种Name (url)格式,其中email和url是可选的。以a #或空白开头的行将被忽略。
这个话题得从一个使人失望的事情提及,在一段时间之前咱们使用npm安装依赖的时候,会出现这样一个现象。 咱们的项目只依赖于A,A依赖于B,B依赖于C。会出现下面的依赖树
-- A @0.1.0 -- B @0.0.1 -- C @0.0.1
这个时候B发生了版本更新,就会致使B的版本发生变化,也就是说咱们的整个依赖树都发生了变化。这种现象有的时候会致使不少不可预知的问题。因此引入了一个临时过分的方案 -- npm shrinkwrap
咱们打开npm的package code,发现生成 package-lock.json 和 生成 shrinkwrap.json 的代码处于同一处,查看npm的config的时候能够更加肯定, package-lock是shrinkwrap.json 的替代品,这一方法在 npm5 以上获得的更新。这一个更新灵感可能来自于 yarn.lock 。
咱们继续看 config 中和这两个文件有关系的属性内容。
config.shrinkwrap
config.package-lock
从以上doc中能够得出的一个简单的结论: shrinkwrap 和 package-lock 是同一个东西,这将帮助咱们理解 NPM 是如何生成Package-lock.json 的。
在npm的自身module中,生成 package-lock 和 shrinkwrap 的代码同处于: npm/lib/shrinkwrap.js 中。在这个代码module中,使用了ES6 的module,对外exports了两个方法
treeToShrinkwrap:
function treeToShrinkwrap (tree) {
validate('O', arguments)
var pkginfo = {}
if (tree.package.name) pkginfo.name = tree.package.name
if (tree.package.version) pkginfo.version = tree.package.version
if (tree.children.length) {
pkginfo.requires = true
shrinkwrapDeps(pkginfo.dependencies = {}, tree, tree)
}
return pkginfo
}
复制代码
createShrinkwrap:
function createShrinkwrap (tree, opts, cb) {
opts = opts || {}
lifecycle(tree.package, 'preshrinkwrap', tree.path, function () {
const pkginfo = treeToShrinkwrap(tree)
chain([
[lifecycle, tree.package, 'shrinkwrap', tree.path],
[shrinkwrap_, tree.path, pkginfo, opts],
[lifecycle, tree.package, 'postshrinkwrap', tree.path]
], iferr(cb, function (data) {
cb(null, pkginfo)
}))
})
}
复制代码
package-lock.json 每次 npm install / uninstall / update 都会去更新。
pacakge-lock 是npm 自动触发的。 shrinkwrap 是须要手动 npm shrinkwrap 的。
package-lock 是生成 node_modules 的依据,也是肯定每次的 node_modules 相同的保证。
它描述了生成的确切树,以便后续安装可以生成相同的树。
package-lock.json和npm-shrinkwrap.json同时存在于包的根,package-lock.json将被彻底忽略。
package-lock 和 shrinKwrap 本质上应属于统一个文件,他们拥有相同的文件格式。
文件格式:
▪ 解释: 包的名称,这是一个包锁。这必须与之相符 package.json。
▪ srouce:
▪ 来自于 treeToShrinkwrap 方法的 if (tree.package.name) pkginfo.name = tree.package.name。
▪ tree.package.name的package 的赋值在 npm/lib/install 的 readPackageJson 方法。
▪ 综上: 这里的name 是从 package.json 的 name 字段获取的。
▪ 这个包的版本。也是来自于 package.json。
▪ if (tree.package.version) pkginfo.version = tree.package.version
▪ 一个整数版本从1开始。
▪ 来自于 pkginfo.lockfileVersion = PKGLOCK_VERSION
▪ PKGLOCK_VERSION 是 npm/lib/npm.js中定义的 从 1开始。
◦ pkginfo.lockfileVersion 除了再npm.js 中定义以外还在 lib/install/read-shrinkwrap.js中 进行过比对。
if (parsed && parsed.lockfileVersion !== PKGLOCK_VERSION) {
log.warn('read-shrinkwrap', `This version of npm is compatible with lockfileVersion@${PKGLOCK_VERSION}, but ${name} was generated for lockfileVersion@${parsed.lockfileVersion || 0}. I'll try to do my best with it!`)
}
复制代码
▪ 听说是验证完整性的。
▪ 表示安装已在NODE_PRESERVE_SYMLINKS启用环境变量的状况下完成 。安装程序应该坚持认为此属性的值与该环境变量匹配。
▪ version
▪ 安装这个包的版本。
▪ 这是此资源的标准子资源完整性。
▪ 资源的URL路径
▪ 根据package.json 中依赖项version来源的不一样而有区别
▪ bundled
▪ 若是为true,则这是捆绑的依赖项,将由父模块安装。安装时,此模块将在提取阶段从父模块中提取,而不是做为单独的依赖项安装。
▪ 若是为true,则此依赖关系仅是顶级模块的开发依赖关系或者是一个的传递依赖关系。对于依赖性而言,这是错误的,这些依赖性既是顶级的开发依赖性,也是顶级的非开发依赖性的传递依赖性。
▪ 若是为true,那么此依赖关系或者只是顶级模块的可选依赖项或者是一个的传递依赖项。对于依赖项来讲这是错误的,这些依赖项既是顶级的可选依赖项,也是顶级的非可选依赖项的传递依赖项。
▪ 这是模块名称到版本的映射。这是该模块所需的全部内容的列表,不管它将在何处安装。版本应该经过正常的匹配规则匹配咱们dependencies或高于咱们的级别的依赖 。
▪ 此依赖项的依赖项,与顶层彻底相同。
不少时候咱们无心识的将package-lock.json放入.gitignore 中,相信读过这篇文章以后,你应该把他解禁了。
only 修改你的pacakge.json 并不会当即同步触发package-lock.json ,须要手动 npm install 一次,若是你忽略掉了这个步骤,你的package-lock就不会达到你想要的结果。
想同步修改package.json和package-lock.json 请使用 npm install xxx --save 命令。
不少同窗是使用NVM安装node.js 的,这样的好处是能够快速的切换node 版本,避免由于node版本带来的问题。
• node路径: /Users/xxx/.nvm/versions/node/v10.14.1/bin/node
• npm路径: /Users/xxx/.nvm/versions/node/v10.14.1/bin/npm
• 以上路径没有找到的状况下: 使用 where node来查找