跳转至

package.json

简介

npm 模块就是一个包含 package.json 文件的目录。

npm 模块分成 CommonJS 和 ES 两种格式。以下情况会以 ES 格式处理模块。

  • 脚本后缀名为.mjs
  • 脚本后缀名为.js,并且 package.json 文件的type字段的值为module
  • node运行时带有--input-type=module,这时通过--eval--print参数传入的代码字符串会被当作 ES 格式处理。
  • 脚本包含一些 ES 才有的语法特征,比如import语句、export语句、import.meta等。

以下情况会以 CommonJS 格式处理模块。

  • 脚本后缀名为.cjs
  • 脚本后缀名为.js,并且 package.json 文件的type字段的值为commonjs
  • node运行时带有--input-type=commonjs,这时通过--eval--print参数传入的代码字符串会被当作 CommonJS 格式处理。

其他情况下,Node.js 目前会以 CommonJS 格式处理模块,不过以后可能会改成 ES 格式。

files 字段

files字段是一个数组,里面指定了一组文件。当模块发布到 NPM 网站时,这组文件会被包括。这个字段是可选的,如果没有指定内容,那么发布时所有文件都会被包括在内。如果files字段包含目录名,该目录里面的所有文件都会被计入。

{
  "name": "@adam_baldwin/wombats",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "files": [
   "index.js"
  ],
  ...
}

npm不会发布.gitignore里面列出的文件和目录。项目的根目录或子目录里面,还可以放置一个.npmignore文件,该文件会覆盖.gitignore,里面指定的文件和目录不会被发布。

项目的根目录下,files字段优先级最高;子目录下,.npmignore优先。files字段指定的文件,不会被.npmignore.gitignore排除。

以下文件,发布的时候总是会包含。

  • package.json
  • README
  • CHANGES / CHANGELOG / HISTORY
  • LICENSE / LICENCE
  • NOTICE
  • main字段里面的文件

READMECHANGESLICENSENOTICE这四个文件名,可以采取任意的大小写组合。

以下文件,发布的时候总是会被排除。

  • .git
  • CVS
  • .svn
  • .hg
  • .lock-wscript
  • .wafpickle-N
  • .*.swp
  • .DS_Store
  • ._*
  • npm-debug.log
  • .npmrc
  • node_modules
  • config.gypi
  • *.orig
  • package-lock.json

基本上,npm 会发布files字段指定的文件和目录,以及那些总是会包含在内的文件(比如package.json),然后再除去那些被其他规则排除的文件和目录。

npm-packlist 模块会列出所有将要打包发布的文件和模块。npm pack命令则会将那些将要发布的内容打成一个tgz压缩包,放在项目的根目录下。

字段

main

main字段指定模块的入口文件。比如,当前模块叫做foo,那么require('foo')时,加载哪个文件,由该字段指定。

main字段的值应该是一个相对路径,相对于模块的根目录进行计算。

如果没有指定main字段,那么默认的入口文件是模块根目录的index.js

exports

exports字段也是指定模块的入口文件,它比main字段更强大。一般来说,优先使用exports字段,只有对 Node.js 10 和更低的版本,才推荐使用main字段。如果同时定义了exportsmain两个字段,前者的优先级更高。

另外,exports字段只针对模块的消费者,并不在模块内部使用。

exports字段可以指定模块的唯一入口。

{
  "exports": "./index.js"
}

上面字段中,exports指定了唯一入口,这意味着只能从模块名加载(require('pkg')),而不能从子路径加载,比如require('pkg/subpath.js')会报错。

为了保证模块的兼容性,常常同时定义mainexports

{
  "main": "./index.js",
  "exports": "./index.js"
}

如果当前模块有多个入口,可以在exports字段里面一一指定。

{
  "exports": {
    ".": "./index.js",
    "./submodule.js": "./src/submodule.js"
  }
}

上面示例中,模块有两个入口,.表示通过模块名加载,./submodule.js表示通过模块的子路径加载,写成import submodule from 'es-module-package/submodule.js';

这时,exports字段的值是一个对象。前面,exports的值为字符串,其实是简写形式。

{
  "exports": "./index.js"
}
// 等同于
{
  "exports": {
    ".": "./index.js"
  }
}

一般来说,对于同一个入口,模块作者最好同时指定有.js后缀名和没有.js后缀名两种情况。

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/index": "./lib/index.js",
    "./lib/index.js": "./lib/index.js",
    "./feature": "./feature/index.js",
    "./feature/index": "./feature/index.js",
    "./feature/index.js": "./feature/index.js",
    "./package.json": "./package.json"
  }
}

上面示例中,就同时指定了./lib/index./lib/index.js两种入口形式。

exports字段也可以使用通配符。

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/*": "./lib/*.js",
    "./lib/*.js": "./lib/*.js",
    "./feature": "./feature/index.js",
    "./feature/*": "./feature/*.js",
    "./feature/*.js": "./feature/*.js",
    "./package.json": "./package.json"
  }
} 

上面示例中,exports字段里面使用了通配符,指定了lib目录和feature目录下的所有入口对应的脚本文件。注意,入口包括.js后缀名和没有后缀名两种情况。另外,通配符*包括了多层目录的情况。

由于exports允许逐一指定不同入口对应的脚本文件,如果以后修改了脚本名称,可以只修改exports字段,不改变入口。另外,exports也排除某些内部脚本被加载。

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./feature/*.js": "./feature/*.js",
    "./feature/internal/*": null
  }
}

上面示例中,exports字段共有三个映射,其中第二个映射是指定加载featrue子目录下的脚本,而第三个映射则是排除feature目录的子目录internal/*下的脚本。

exports还支持条件加载,即不同的情况加载不同的脚本。

{
  "exports": {
    "import": "./index-module.js",
    "require": "./index-require.cjs"
  },
  "type": "module"
}

上面示例中,import指定 ES 格式加载时,入口文件为./index-module.js,CommonJS 格式加载时(require命令),入口文件为./index-require.cjs

exports支持以下的条件关键词。

  • node-addons:Node.js 的扩展
  • node:Node.js 环境
  • import:ES 格式加载时,特指使用importimport()加载时。
  • require:CommonJS 格式加载时。
  • default:其他条件都不匹配时的默认入口文件,总是放在最后。

下面是一个例子。

{
  "exports": {
    ".": "./index.js",
    "./feature.js": {
      "node": "./feature-node.js",
      "default": "./feature.js"
    }
  }
}

exports还支持嵌套条件。

{
  "exports": {
    "node": {
      "import": "./feature-node.mjs",
      "require": "./feature-node.cjs"
    },
    "default": "./feature.mjs"
  }
}

注意,exports的映射不允许指向外部模块。

imports

imports字段用于模块的内部,为入口文件指定别名。它的路径总是从#开始,避免与实际路径相混淆。

{
  "imports": {
    "#dep": {
      "node": "dep-node-native",
      "default": "./dep-polyfill.js"
    }
  },
  "dependencies": {
    "dep-node-native": "^1.0.0"
  }
}

上面示例中,imports字段的意思是,加载#dep这个别名时(import dep from "#def";),对于 Node.js 加载外部模块dep-node-native,对于构建工具则会加载本地文件./dep-polyfill.js