你真的了解package.json吗?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你真的了解package.json吗?相关的知识,希望对你有一定的参考价值。
参考技术A 当运行 npm install 命令的时候,会根据 package.json 文件中的配置自动下载所需的模块,也就是配置项目所需的运行和开发环境。如下面的文件package.json文件中最重要的就是name和version字段,这两项是必填的。名称和版本一起构成一个标识符,该标识符被认为是完全唯一的。对包的更改应该与对版本的更改一起进行。
name必须小于等于214个字符,不能以.或_开头,不能有大写字母,因为名称最终成为URL的一部分因此不能包含任何非URL安全字符。 npm官方建议我们不要使用与核心节点模块相同的名称。不要在名称中加js或node。如果需要可以使用engines来指定运行环境。
该名称会作为参数传递给require,因此它应该是简短的,但也需要具有合理的描述性。
version一般的格式是x.x.x, 并且需要遵循该规则。
package.json文件中最重要的就是name和version字段,这两项是必填的。名称和版本一起构成一个标识符,该标识符被认为是唯一的。每次发布时version不能与已存在的一致。
description 是一个字符串,用于编写描述信息。有助于人们在 npm 库中搜索的时候发现你的模块。
keywords 是一个字符串组成的数组,有助于人们在 npm 库中搜索的时候发现你的模块。
homepage 项目的主页地址。
bugs用于项目问题的反馈issue地址或者一个邮箱。
license是当前项目的协议,让用户知道他们有何权限来使用你的模块,以及使用该模块有哪些限制。
author是具体一个人,contributors表示一群人,他们都表示当前项目的共享者。同时每个人都是一个对象。具有name字段和可选的url及email字段。
也可以写成一个字符串
files属性的值是一个数组,内容是模块下文件名或者文件夹名,如果是文件夹名,则文件夹下所有的文件也会被包含进来(除非文件被另一些配置排除了)
可以在模块根目录下创建一个.npmignore文件,写在这个文件里边的文件即便被写在files属性里边也会被排除在外,这个文件的写法与.gitignore类似。
main字段指定了加载的入口文件,require导入的时候就会加载这个文件。这个字段的默认值是模块根目录下面的index.js。
bin项用来指定每个内部命令对应的可执行文件的位置。如果你编写的是一个node工具的时候一定会用到bin字段。
当我们编写一个cli工具的时候,需要指定工具的运行命令,比如常用的webpack模块,它的运行命令就是webpack。
当我们执行webpack命令的时候就会执行bin/index.js文件中的代码。
在模块以依赖的方式被安装,如果存在bin选项。在node_modules/.bin/生成对应的文件, Npm会寻找这个文件,在node_modules/.bin/目录下建立符号链接。由于node_modules/.bin/目录会在运行时加入系统的PATH变量,因此在运行npm时,就可以不带路径,直接通过命令来调用这些脚本。
所有node_modules/.bin/目录下的命令,都可以用npm run [命令]的格式运行。在命令行下,键入npm run,然后按tab键,就会显示所有可以使用的命令。
man用来指定当前模块的man文档的位置。
directories制定一些方法来描述模块的结构, 用于告诉用户每个目录在什么位置。
指定一个代码存放地址,对想要为你的项目贡献代码的人有帮助
scripts指定了运行脚本命令的npm命令行缩写,比如start指定了运行npm run start时,所要执行的命令。
使用scripts字段可以快速地执行shell命令,可以理解为alias。
scripts可以直接使用node_modules中安装的模块,这区别于直接运行需要使用npx命令。
config字段用于添加命令行的环境变量。
然后,在server.js脚本就可以引用config字段的值。
用户可以通过npm config set来修改这个值。
dependencies字段指定了项目运行所依赖的模块,devDependencies指定项目开发所需要的模块。
它们的值都是一个对象。该对象的各个成员,分别由模块名和对应的版本要求组成,表示依赖的模块及其版本范围。
当安装依赖的时候使用--save参数表示将该模块写入dependencies属性,--save-dev表示将该模块写入devDependencies属性。
对象的每一项通过一个键值对表示,前面是模块名称,后面是对应模块的版本号。版本号遵循“大版本.次要版本.小版本”的格式规定。
当我们开发一个模块的时候,如果当前模块与所依赖的模块同时依赖一个第三方模块,并且依赖的是两个不兼容的版本时就会出现问题。
比如,你的项目依赖A模块和B模块的1.0版,而A模块本身又依赖B模块的2.0版。
大多数情况下,这不构成问题,B模块的两个版本可以并存,同时运行。但是,有一种情况,会出现问题,就是这种依赖关系将暴露给用户。
最典型的场景就是插件,比如A模块是B模块的插件。用户安装的B模块是1.0版本,但是A插件只能和2.0版本的B模块一起使用。这时,用户要是将1.0版本的B的实例传给A,就会出现问题。因此,需要一种机制,在模板安装的时候提醒用户,如果A和B一起安装,那么B必须是2.0模块。
peerDependencies字段,就是用来供插件指定其所需要的主工具的版本。可以通过peerDependencies字段来限制,使用myless模块必须依赖less模块的3.9.x版本.
注意,从npm 3.0版开始,peerDependencies不再会默认安装了。就是初始化的时候不会默认带出。
bundledDependencies指定发布的时候会被一起打包的模块.
如果一个依赖模块可以被使用, 同时你也希望在该模块找不到或无法获取时npm继续运行,你可以把这个模块依赖放到optionalDependencies配置中。这个配置的写法和dependencies的写法一样,不同的是这里边写的模块安装失败不会导致npm install失败。
engines字段指明了该模块运行的平台,比如Node或者npm的某个版本或者浏览器。
可以指定你的模块只能在哪个操作系统上运行
限制模块只能在某种架构的cpu下运行
如果这个属性被设置为true,npm将拒绝发布它,这是为了防止一个私有模块被无意间发布出去。
这个配置是会在模块发布时生效,用于设置发布用到的一些值得集合。如果你不想模块被默认标记为最新的,或者默认发布到公共仓库,可以在这里配置tag或仓库地址。
通常publishConfig会配合private来使用,如果你只想让模块被发布到一个特定的npm仓库,如一个内部的仓库。
preferGlobal的值是布尔值,表示当用户不将该模块安装为全局模块时(即不用–global参数),要不要显示警告,表示该模块的本意就是安装为全局模块。
browser指定该模板供浏览器使用的版本。Browserify这样的浏览器打包工具,通过它就知道该打包哪个文件。
你真的了解 defer 吗?
目录
[Go]你真的了解 defer 吗?
深入理解 defer 分上下两篇文章,本文为上篇,主要介绍如下内容:
-
为什么需要 defer;
-
defer 语法及语义;
-
defer 使用要点;
-
defer 语句中的函数到底是在 return 语句之后被调用还是 return 语句之前被调用。
为什么需要 defer
先来看一段没有使用 defer 的代码:
func f() r := getResource() //0,获取资源 ...... if ... r.release() //1,释放资源 return ...... if ... r.release() //2,释放资源 return ...... if ... r.release() //3,释放资源 return ...... r.release() //4,释放资源 return
f() 函数首先通过调用 getResource() 获取了某种资源(比如打开文件,加锁等),然后进行了一些我们不太关心的操作,但这些操作可能会导致 f() 函数提前返回,为了避免资源泄露,所以每个 return 之前都调用了 r.release() 函数对资源进行释放。这段代码看起来并不糟糕,但有两个小问题:代码臃肿和可维护性比较差。臃肿倒是其次,主要问题在于代码的可维护性差,因为随着开发和维护的进行,修改代码在所难免,一旦对 f() 函数进行修改添加某个提前返回的分支,就很有可能在提前 return 时忘记调用 r.release() 释放资源,从而导致资源泄漏。
那么我们如何改善上述两个问题呢?一个不错的方案就是通过 defer 调用 r.release() 来释放资源:
func f() r := getResource() //0,获取资源 defer r.release() //1,注册延迟调用函数,f()函数返回时才会调用r.release函数释放资源 ...... if ... return ...... if ... return ...... if ... return ...... return
可以看到通过使用 defer 调用 r.release(),我们不需要在每个 return 之前都去手动调用 r.release() 函数,代码确实精简了一点,重要的是不管以后加多少提前 return 的代码,都不会出现资源泄露的问题,因为不管在什么地方 return ,r.release() 函数始终都会被调用。
关注我 code 杂坛,了解更多......
defer 语法及语义
defer语法很简单,直接在普通写法的函数调用之前加 defer 关键字即可:
defer xxx(arg0, arg1, arg2, ......)
defer 表示对紧跟其后的 xxx() 函数延迟到 defer 语句所在的当前函数返回时再进行调用。比如前文代码中注释 1 处的 defer r.release() 表示等 f() 函数返回时再调用 r.release() 。下文我们称 defer 语句中的函数叫 defer函数。
defer 使用要点
对 defer 的使用需要注意如下几个要点:
-
延迟对函数进行调用;
-
即时对函数的参数进行求值;
-
根据 defer 顺序反序调用;
下面我们用例子来简单的看一下这几个要点。
defer 函数延迟调用
func f() defer fmt.Println("defer") fmt.Println("begin") fmt.Println("end") return
这段代码首先会输出 begin 字符串,然后是 end ,最后才输出 defer 字符串。
defer 函数参数即时求值
func g(i int) fmt.Println("g i:", i) func f() i := 100 defer g(i) //1 fmt.Println("begin i:", i) i = 200 fmt.Println("end i:", i) return
这段代码首先输出 begin i: 100,然后输出 end i: 200,最后输出 g i: 100 ,可以看到 g() 函数虽然在f函数返回时才被调用,但传递给 g() 函数的参数还是100,因为代码 1 处的 defer g(i) 这条语句执行时 i 的值是100。也就是说 defer 函数会被延迟调用,但传递给 defer 函数的参数会在 defer 语句处就被准备好。
关注我 code 杂坛,了解更多......
反序调用
func f() defer fmt.Println("defer01") fmt.Println("begin") defer fmt.Println("defer02") fmt.Println("----") defer fmt.Println("defer03") fmt.Println("end") return
这段程序的输出如下:
begin ---- end defer03 defer02 defer01
可以看出f函数返回时,第一个 defer 函数最后被执行,而最后一个 defer 函数却第一个被执行。
defer 函数的执行与 return 语句之间的关系
到目前为止,defer 看起来都还比较好理解。下面我们开始把问题复杂化
package main import "fmt" var g = 100 func f() (r int) defer func() g = 200 () fmt.Printf("f: g = %d\\n", g) return g func main() i := f() fmt.Printf("main: i = %d, g = %d\\n", i, g)
输出:
$ ./defer f: g = 100 main: i = 100, g = 200
这个输出还是比较容易理解,f() 函数在执行 return g 之前 g 的值还是100,所以 main() 函数获得的 f() 函数的返回值是100,因为 g 已经被 defer 函数修改成了200,所以在 main 中输出的 g 的值为200,看起来 defer 函数在 return g 之后才运行。下面稍微修改一下上面的程序:
package main import "fmt" var g = 100 func f() (r int) r = g defer func() r = 200 () fmt.Printf("f: r = %d\\n", r) r = 0 return r func main() i := f() fmt.Printf("main: i = %d, g = %d\\n", i, g)
输出:
$ ./defer f: r = 100 main: i = 200, g = 100
从这个输出可以看出,defer 函数修改了 f() 函数的返回值,从这里看起来 defer 函数的执行发生在 return r 之前,然而上一个例子我们得出的结论是 defer 函数在 return 语句之后才被调用执行,这两个结论很矛盾,到底是怎么回事呢?
仅仅从go语言的角度来说确实不太好理解,我们需要深入到汇编来分析一下。
老套路,使用 gdb 反汇编一下 f() 函数:
0x0000000000488a30 <+0>: mov %fs:0xfffffffffffffff8,%rcx 0x0000000000488a39 <+9>: cmp 0x10(%rcx),%rsp 0x0000000000488a3d <+13>: jbe 0x488b33 <main.f+259> 0x0000000000488a43 <+19>: sub $0x68,%rsp 0x0000000000488a47 <+23>: mov %rbp,0x60(%rsp) 0x0000000000488a4c <+28>: lea 0x60(%rsp),%rbp 0x0000000000488a51 <+33>: movq $0x0,0x70(%rsp) # 初始化返回值r为0 0x0000000000488a5a <+42>: mov 0xbd66f(%rip),%rax # 0x5460d0 <main.g> 0x0000000000488a61 <+49>: mov %rax,0x70(%rsp) # r = g 0x0000000000488a66 <+54>: movl $0x8,(%rsp) 0x0000000000488a6d <+61>: lea 0x384a4(%rip),%rax # 0x4c0f18 0x0000000000488a74 <+68>: mov %rax,0x8(%rsp) 0x0000000000488a79 <+73>: lea 0x70(%rsp),%rax 0x0000000000488a7e <+78>: mov %rax,0x10(%rsp) 0x0000000000488a83 <+83>: callq 0x426c00 <runtime.deferproc> 0x0000000000488a88 <+88>: test %eax,%eax 0x0000000000488a8a <+90>: jne 0x488b23 <main.f+243> 0x0000000000488a90 <+96>: mov 0x70(%rsp),%rax 0x0000000000488a95 <+101>: mov %rax,(%rsp) 0x0000000000488a99 <+105>: callq 0x408950 <runtime.convT64> 0x0000000000488a9e <+110>: mov 0x8(%rsp),%rax 0x0000000000488aa3 <+115>: xorps %xmm0,%xmm0 0x0000000000488aa6 <+118>: movups %xmm0,0x50(%rsp) 0x0000000000488aab <+123>: lea 0x101ee(%rip),%rcx # 0x498ca0 0x0000000000488ab2 <+130>: mov %rcx,0x50(%rsp) 0x0000000000488ab7 <+135>: mov %rax,0x58(%rsp) 0x0000000000488abc <+140>: nop 0x0000000000488abd <+141>: mov 0xd0d2c(%rip),%rax # 0x5597f0 <os.Stdout> 0x0000000000488ac4 <+148>: lea 0x495f5(%rip),%rcx # 0x4d20c0 <go.itab.*os.File,io.Writer> 0x0000000000488acb <+155>: mov %rcx,(%rsp) 0x0000000000488acf <+159>: mov %rax,0x8(%rsp) 0x0000000000488ad4 <+164>: lea 0x31ddb(%rip),%rax # 0x4ba8b6 0x0000000000488adb <+171>: mov %rax,0x10(%rsp) 0x0000000000488ae0 <+176>: movq $0xa,0x18(%rsp) 0x0000000000488ae9 <+185>: lea 0x50(%rsp),%rax 0x0000000000488aee <+190>: mov %rax,0x20(%rsp) 0x0000000000488af3 <+195>: movq $0x1,0x28(%rsp) 0x0000000000488afc <+204>: movq $0x1,0x30(%rsp) 0x0000000000488b05 <+213>: callq 0x480b20 <fmt.Fprintf> 0x0000000000488b0a <+218>: movq $0x0,0x70(%rsp) # r = 0 # ---- 下面5条指令对应着go代码中的 return r 0x0000000000488b13 <+227>: nop 0x0000000000488b14 <+228>: callq 0x427490 <runtime.deferreturn> 0x0000000000488b19 <+233>: mov 0x60(%rsp),%rbp 0x0000000000488b1e <+238>: add $0x68,%rsp 0x0000000000488b22 <+242>: retq # --------------------------- 0x0000000000488b23 <+243>: nop 0x0000000000488b24 <+244>: callq 0x427490 <runtime.deferreturn> 0x0000000000488b29 <+249>: mov 0x60(%rsp),%rbp 0x0000000000488b2e <+254>: add $0x68,%rsp 0x0000000000488b32 <+258>: retq 0x0000000000488b33 <+259>: callq 0x44f300 <runtime.morestack_noctxt> 0x0000000000488b38 <+264>: jmpq 0x488a30 <main.f>
f() 函数本来很简单,但里面使用了闭包和 Printf,所以汇编代码看起来比较复杂,这里我们只挑重点出来说。f() 函数最后 2 条语句被编译器翻译成了如下6条汇编指令:
0x0000000000488b0a <+218>: movq $0x0,0x70(%rsp) # r = 0 # ---- 下面5条指令对应着go代码中的 return r 0x0000000000488b13 <+227>: nop 0x0000000000488b14 <+228>: callq 0x427490 <runtime.deferreturn> # deferreturn会调用defer注册的函数 0x0000000000488b19 <+233>: mov 0x60(%rsp),%rbp # 调整栈 0x0000000000488b1e <+238>: add $0x68,%rsp # 调整栈 0x0000000000488b22 <+242>: retq # 从f()函数返回 # ---------------------------
这6条指令中的第一条指令对应到的go语句是 r = 0,因为 r = 0 之后的下一行语句是 return r ,所以这条指令相当于把 f() 函数的返回值保存到了栈上,然后第三条指令调用了 runtime.deferreturn 函数,该函数会去调用我们在 f() 函数开始处使用 defer 注册的函数修改 r 的值为200,所以我们在main函数拿到的返回值是200,后面三条指令完成函数调用栈的调整及返回。
从这几条指令可以得出,准确的说,defer 函数的执行既不是在 return 之后也不是在 return 之前,而是一条go语言的 return 语句包含了对 defer 函数的调用,即 return 会被翻译成如下几条伪指令
保存返回值到栈上 调用defer函数 调整函数栈 retq指令返回
到此我们已经知道,前面说的矛盾其实并非矛盾,只是从Go语言层面来理解不好理解而已,一旦我们深入到汇编层面,一切都会显得那么自然,正所谓汇编之下了无秘密。
总结
-
defer 主要用于简化编程(以及实现 panic/recover ,后面会专门写一篇相关文章来介绍)
-
defer 实现了函数的延迟调用;
-
defer 使用要点:延迟调用,即时求值和反序调用;
-
go 语言的 return 会被编译器翻译成多条指令,其中包括保存返回值,调用defer注册的函数以及实现函数返回。
本文我们主要从使用的角度介绍了defer 的基础知识,下一篇文章我们将会深入 runtime.deferproc 和 runtime.deferreturn 这两个函数分析 defer 的实现机制。
关注我 code 杂坛,了解更多......
以上是关于你真的了解package.json吗?的主要内容,如果未能解决你的问题,请参考以下文章
前端项目中package.json到底是什么,又充当着什么作用呢?一文来带你了解package.json!
前端项目中package.json到底是什么,又充当着什么作用呢?一文来带你了解package.json!