When Not to Use Lock Files with Node.js
“可是在我的机器上能工作啊!”这种场景可能是调试 bug 时最常见的问题。这通常是由于出错的机器和你自己的机器上系统的底层依赖性不同的结果。所以 yarn 和 npm 在引入了所谓的“lock file”,来跟踪你依赖项确切的版本。但是当你在开发要发布到 npm 的包时,应避免使用这类 lock file 。在本文中,我们将讨论为什么要这样。
如果你开发像 Web 服务器之类的程序,那么 lock file 是非常有用的。但是如果将库或 CLI 发布到 npm,则永远不要发布 lock file。因为如果你使用它,则意味着你和你的用户可能在使用不同版本的依赖项。
lock file 描述了整个依赖关系树,它在创建时被解析,包括具有特定版本的嵌套依赖关系。在 npm
名为 package-lock.json
,在 yarn
中名为 yarn.lock
。在这两个npm
和yarn
它们被放置旁边你的package.json
。
package-lock.json
的内容应该是这样:
1{
2 "name": "lockfile-demo",
3 "version": "1.0.0",
4 "lockfileVersion": 1,
5 "requires": true,
6 "dependencies": {
7 "ansi-styles": {
8 "version": "3.2.1",
9 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
10 "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
11 "requires": {
12 "color-convert": "^1.9.0"
13 }
14 },
15 "chalk": {
16 "version": "2.4.2",
17 "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
18 "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
19 "requires": {
20 "ansi-styles": "^3.2.1",
21 "escape-string-regexp": "^1.0.5",
22 "supports-color": "^5.3.0"
23 }
24 }
25 }
26}
yarn.lock
的格式不同,但也包含类似的信息:
1# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2# yarn lockfile v1
3
4ansi-styles@^3.2.1:
5 version "3.2.1"
6 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
7 integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
8 dependencies:
9 color-convert "^1.9.0"
10
11chalk@^2.4.2:
12 version "2.4.2"
13 resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
14 integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
15 dependencies:
16 ansi-styles "^3.2.1"
17 escape-string-regexp "^1.0.5"
18 supports-color "^5.3.0"
两者都包含一些重要的信息:
既然 lock file 中已经列出了所有的依赖项,拿为什么还要将它们写在 package.json
中呢?为什么我们需要两个文件?
package.json
中 dependencies
字段显示你的项目应该安装的依赖项,但不显示这些依赖项的依赖项。依赖项可以指定精确版本或 semver 范围。对于 semver 范围,npm
或 yarn
将h会选择最适合的版本。
这意味着,如果在发布新版本时多次运行 npm install
,有可能会得到相同版本的依赖项。例如用 npm install twilio
安装 twilio
这样的依赖项,那么 package.json
中的依赖项可能会存在类似于这样的条目:
1{
2 "dependencies": {
3 "twilio": "^3.30.3"
4 }
5}
如果你查阅 npm 网站上的 semver 文档,就会看到 ^
意味着任何大于 3.30.3
的版本和小于 4.0.0
都是有效版本。因此,如果在发布新版本时你没有锁定文件,npm install
或 yarn install
会自动安装一个,你的 package.json
将不会被更新。但是 lock file 的内容会有所不同。
如果 npm
或 yarn
找到它们各自的 lock file,将使用它们代替模块安装。这对于持续集成(CI)等情况尤其有用。对于此这种场景,你可以针对相应的包管理器使用特殊命令或标志:
1npm ci # will install exactly what's in the package-lock.json
2yarn install --frozen-lock-file # will install exactly what's in yarn.lock without updating it
当你在构建 Web 程序或服务器之类的应用时,这非常有用,因为我们希望在 CI 环境中模拟用户的行为。因此,如果在源代码控制(如 git)中跟踪我们的 lock file,就可以确保每个开发人员以及服务器或构建系统还有 CI 系统都能够使用相同版本的依赖项。
那么当我们编写要发布到 npm 的库时,为什么不能做同样的事呢?要回答这个问题,首先要讨论发布的工作原理。
与某些人想的相反,你发布到 npm 的内容并不总是与 GitHub 上或项目中的内容完全相同。发布模块的方式是 npm
将通过检查 package.json
和 .npmignore
文件中的 files
键或者如果没有`来确定应该发布的文件。 gitignore
文件。还有一些文件总是包含在内,有些文件将永远被排除在外。你可以在 npm page 上找到这些文件的完整列表。例如,.git
目录始终会被忽略。
之后 npm
将会获取文件列表,并用 npm pack
将它们一起打包成 tarball
。如果要查看打包的文件,可以在项目中运行 npm pack --dry-run
,能看到包含所有文件的输出:
执行 "npm pack --dry-run"
那个 tarball 将被上传到 npm注册表。运行此命令时你可能会注意到加入你已经有了一个 package-lock.json
,它实际上没有被捆绑。这是因为 package-lock.json
将始终被忽略。
这意味着如果另一个开发人员安装了你发布的软件包,他们永远不会下载你的 package-lock.json
,因此在安装过程中将会完全忽略它。
这可能会导致“在我的机器上能够工作”的意外,因为你的 CI 和开发环境可能会选择不同的依赖项版本。那么我们可以做些什么呢?
首先,应该停止跟踪我们的 lock file。如果你用的是git,请将以下内容添加到项目中的 .gitignore
文件中:
1yarn.lock
2package-lock.json
Yarn 的文档说即使你创建了库,也应该签入 yarn.lock
,但是如果你想确保自己能够保证与用户相同的体验,我建议将其添加到 .gitignore
。
你可以通过在项目里的 .npmrc
文件中添加以下内容来关闭 package-lock.json
文件的生成:
1package-lock=false
对于 yarn
,你可以通过添加 yarn install --no-lockfile
标志保证不生成 lock file。
摆脱了 package-lock.json
并不意味着无法固定我们所拥有的依赖关系和子依赖关系。我们可以用另一个名为 npm-shrinkwrap.json
的文件。
它与 package-lock.json
基本相同,并由 npm shrinkwrap
生成并实际的打包并发布到 npm
注册表中。
因此,通过将 npm shrinkwrap
添加到 npm
脚本作为 prepack
脚本甚至是 git commit hook,可以确保在你的开发环境中,与你的用户和 CI 中使用相同版本的依赖项。
一个重要的提示:通过使用 shrinkwrap 文件,你可以确定精确的版本,但它也会阻止人们获得自动安装的关键补丁程序。 npm
强烈反对库的 shrinkwrap
的用例。
不幸的是,虽然 npm docs中有很多相关内容,但有时很难找到你想要的东西。如果你想更好地了解安装或打包的内容,那么你一个常见标志就是 --dry-run
。运行该命令而不会影响你的系统。例如 npm install --dry-run
并不会将依赖项安装到你的文件系统,或者 npm publish --dry-run
实际上也不会发布该包。
以下是你可能想要查看的一些命令:
1npm ci --dry-run # mock-installs based on package-lock.json or npm-shrinkwrap.json
2npm pack --dry-run # lists all files that would be packaged up as well as meta info
3npm install <dep> --verbose --dry-run # will run the installation of a package in verbose mode without actually installing it to your file system