包管理基础知识
在本文中,我们将详细探讨包管理器,以了解如何在自己的项目中使用它们——安装项目工具依赖、保持它们最新等等。
预备知识 | 熟悉核心 HTML、CSS 和 JavaScript 语言。 |
---|---|
目标 | 了解包管理器和包仓库是什么、为什么需要它们,以及如何使用它们的基础知识。 |
项目中的依赖项
依赖项是第三方软件,可能由其他人编写,理想情况下可以为您解决一个单一问题。一个 Web 项目可以有任意数量的依赖项,从无到多,并且您的依赖项可能包含您未明确安装的子依赖项——您的依赖项可能有它们自己的依赖项。
您的项目可能需要的一个有用依赖项的简单示例是一些代码,用于将相对日期计算为人类可读的文本。您当然可以自己编写这些代码,但很有可能其他人已经解决了这个问题——为什么要浪费时间重新发明轮子呢?此外,可靠的第三方依赖项可能已经在许多不同情况下进行了测试,使其比您自己的解决方案更健壮且跨浏览器兼容。
项目依赖项可以是一个完整的 JavaScript 库或框架(例如 React 或 Vue),也可以是一个非常小的实用工具(例如我们的人类可读日期库),或者它可以是一个命令行工具(例如 Prettier 或 ESLint),我们在之前的文章中讨论过。
如果没有现代的构建工具,像这样的依赖项可以通过简单的 <script>
元素包含在您的项目中,但这可能无法直接开箱即用,您可能需要一些现代工具来将您的代码和依赖项捆绑在一起,以便在 Web 上发布。捆绑包是一个通常用于指代 Web 服务器上的单个文件的术语,该文件包含您的软件的所有 JavaScript——通常尽可能地进行压缩,以帮助减少您的软件下载并在访问者浏览器中显示所需的时间。
此外,如果您找到一个您想替代当前工具的更好工具,或者您的依赖项发布了您想要更新的新版本,会发生什么?对于少数依赖项来说,这并不太痛苦,但在拥有许多依赖项的大型项目中,这种事情会变得非常难以跟踪。使用 包管理器(例如 npm)更有意义,因为它将确保代码被干净地添加和删除,以及许多其他优点。
包管理器到底是什么?
我们已经遇到过 npm,但从 npm 本身来看,包管理器是一个管理项目依赖项的系统。
包管理器将提供一种安装新依赖项(也称为“包”)、管理包在文件系统上的存储位置以及提供发布自己的包的功能的方法。
理论上,您可能不需要包管理器,您可以手动下载和存储项目依赖项,但包管理器将无缝处理包的安装和卸载。如果您不使用它,您将不得不手动处理:
- 查找所有正确的包 JavaScript 文件。
- 检查它们以确保它们没有已知的漏洞。
- 下载它们并将它们放在项目中的正确位置。
- 编写代码以将包包含在您的应用程序中(这通常使用 JavaScript 模块完成,这是另一个值得阅读和理解的主题)。
- 对所有包的子依赖项执行相同的操作,这可能有数十甚至数百个。
- 如果您想删除包,请再次删除所有文件。
此外,包管理器处理重复的依赖项(这在前端开发中变得重要且常见)。
对于 npm(以及基于 JavaScript 和 Node 的包管理器),您有两种安装依赖项的选项。正如我们在上一篇文章中提到的,依赖项可以全局安装或本地安装到您的项目中。虽然全局安装的优点更多,但本地安装的优点更重要——例如代码可移植性和版本锁定。
例如,如果您的项目依赖于具有特定配置的 webpack,您需要确保如果将该项目安装到另一台机器上或很久以后再回到该项目,配置仍然可以正常工作。如果安装了不同版本的 webpack,它可能不兼容。为了缓解这个问题,依赖项是本地安装到项目中的。
要真正看到本地依赖项的优势,您只需尝试下载并运行一个现有项目——如果它能正常工作并且所有依赖项都能开箱即用,那么您应该感谢本地依赖项使代码具有可移植性。
包注册表
为了使包管理器工作,它需要知道从哪里安装包,这以包注册表的形式出现。注册表是包发布并可以从中安装的中心位置。npm 除了是包管理器之外,也是 JavaScript 包最常用的包注册表的名称。npm 注册表位于 npmjs.com。
npm 不是唯一的选择。您可以管理自己的包注册表——像 Microsoft Azure 这样的产品允许您创建 npm 注册表的代理(这样您就可以覆盖或锁定某些包),GitHub 也提供包注册表服务,并且随着时间的推移可能会出现更多选择。
重要的是要确保您选择了最适合您的注册表。许多项目将使用 npm,我们将在本模块的其余示例中坚持使用它。
使用包生态系统
让我们通过一个示例来帮助您开始使用包管理器和注册表来安装命令行实用程序。
我们将使用 Vite 来创建一个空白网站。在下一篇文章中,我们将扩展工具链以包含更多工具,并向您展示如何部署网站。
Vite 提供了一些 初始化模板,其中包含所有必要的依赖项和配置,可帮助您在真实项目中快速启动。为了演示,我们将从头开始配置一个,使用 React 模板作为参考。
将应用设置为 npm 包
首先,创建一个新目录来存储我们的实验性应用程序,放在一个容易再次找到的合理位置。我们将其命名为 npm-experiment
,但您可以随意命名。
mkdir npm-experiment
cd npm-experiment
接下来,让我们将我们的应用初始化为一个 npm 包,这将创建一个配置文件——package.json
——它允许我们保存我们的配置细节,以防我们以后想重新创建这个环境,甚至将包发布到 npm 注册表(尽管这与我们的文章无关,因为我们正在开发一个应用程序,而不是一个可重用的库)。
确保您在 npm-experiment
目录内,然后键入以下命令:
npm init
现在会问你一些问题;npm 将根据你的回答创建一个默认的 package.json
文件。请注意,这些都与我们的目的无关,因为它们只在你将包发布到注册表并由其他人安装和导入时才使用。
name
:识别应用的名称。只需按 Return 键接受默认的npm-experiment
。version
:应用的起始版本号。同样,只需按 Return 键接受默认的1.0.0
。description
:应用程序用途的简要描述。我们在这里省略它,但你也可以输入任何你喜欢的内容。按 Return。entry point
:这将是其他人导入您的包时运行的 JavaScript 文件。它对我们没有用处,所以只需按 Return。test command
、git repository
和keywords
:暂时按 Return 键将它们留空。author
:项目的作者。输入您的姓名,然后按 Return。license
:发布包所依据的许可证。暂时按 Return 键接受默认值。
再按一次 Return 键以接受这些设置。
进入你的 npm-experiment
目录,现在你应该会找到一个 package.json 文件。打开它,它应该看起来像这样:
{
"name": "npm-experiment",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Your name",
"license": "ISC"
}
我们将向 package.json 添加两行代码:
"type": "module"
,这会使 Node 将所有.js
文件解释为 ES 模块,而不是旧的 CommonJS 模块。这是一个普遍的好习惯。"private": true
,这可以防止您意外地将包发布到 npm 注册表。
将这些行添加到 "name"
下方:
{
"name": "npm-experiment",
"type": "module",
"private": true
// …
}
所以这就是定义您的包的配置文件。目前来说这很好,让我们继续。
安装 Vite
我们首先安装 Vite,它是我们网站的构建工具。它负责将我们的 HTML、CSS 和 JavaScript 文件打包成一个针对浏览器优化的捆绑包。
npm install --save-dev vite
完成所有操作后,再看一下您的 package.json 文件。您会看到 npm 添加了一个新字段 devDependencies
:
{
"devDependencies": {
"vite": "^5.2.13"
}
}
这是 npm 的魔力之一——如果将来您将代码库移动到另一个位置、另一台机器上,您可以通过运行 npm install
命令重新创建相同的设置,npm 将查看依赖项并为您安装它们。
一个缺点是 Vite 只能在我们的 npm-experiment
应用内部使用;您无法在不同的目录中运行它。但优点大于缺点。
请注意,我们选择将 vite
安装为开发依赖项。这种差异对于应用程序来说很少重要,但对于库来说,这意味着当其他人安装您的包时,他们不会隐式安装 Vite。通常,对于应用程序,源代码中导入的任何包都是真正的依赖项,而用于开发(通常作为命令行工具)的任何包都是开发依赖项。通过删除 --save-dev
标志来安装真正的依赖项。
您还会发现许多新文件被创建:
node_modules
:运行 Vite 所需的依赖文件。npm 已为您下载了所有这些文件。package-lock.json
:这是一个锁定文件,存储了复制node_modules
目录所需的精确信息。这确保只要锁定文件不变,node_modules
目录在不同机器上将是相同的。
您无需担心这些文件,因为它们由 npm 管理。如果您使用 Git,应该将 node_modules
添加到您的 .gitignore
文件中,但通常应该保留 package-lock.json
,因为如前所述,它用于同步不同机器上的 node_modules
状态。
设置我们的示例应用程序
无论如何,继续设置。
在 Vite 中,index.html
文件是核心。它定义了你应用的起点,Vite 将使用它来查找构建应用所需的其他文件。在你的 npm-experiment
目录中创建一个 index.html
文件,并给它以下内容:
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<title>My test page</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
请注意,<script>
元素创建了对名为 src/main.jsx
的文件的依赖,该文件声明了应用程序 JavaScript 逻辑的入口点。创建 src
文件夹并在该文件夹中创建 main.jsx
,但暂时将其留空。
注意: type="module"
属性很重要。它告诉浏览器将脚本视为 ES 模块,这允许我们在 JavaScript 代码中使用 import
和 export
语法。文件扩展名是 .jsx
,因为在下一篇文章中,我们将向其中添加 React JSX 语法。浏览器不理解 JSX,但 Vite 会将其转换为常规 JavaScript,就像浏览器理解一样!
使用 Vite 玩转
现在我们将运行我们新安装的 Vite 工具。在您的终端中,运行以下命令:
npx vite
您应该会在终端中看到类似以下内容的输出:
VITE v5.2.13 ready in 326 ms ➜ Local: https://:5173/ ➜ Network: use --host to expose ➜ press h + enter to show help
现在我们已准备好从完整的 JavaScript 包生态系统中受益。首先,现在有一个本地 Web 服务器在 https://:5173
运行。你暂时看不到任何东西,但很酷的是,当你对应用程序进行更改时,Vite 会自动重建并刷新服务器,这样你就可以立即看到更新的效果。
您可以使用 Ctrl + C 随时停止开发服务器,并使用相同的命令再次启动。如果您决定让它继续运行,您可以打开一个新的终端窗口来运行其他命令。
现在来一些页面内容。作为演示,让我们向页面添加一个图表。我们将使用 plotly.js 包,这是一个数据可视化库。通过运行以下命令安装它:
npm install plotly.js-dist-min
请注意我们如何在没有 --save-dev
标志的情况下安装。如前所述,这是因为我们将在源代码中实际使用此包,而不仅仅是作为命令行工具。此命令将向您的 package.json
文件添加一个新的 "dependencies"
对象,其中包含 plotly.js-dist-min
。
注意:在这里,我们为您选择了完成任务的包。在编写自己的代码时,在查找和安装依赖项时请考虑以下问题:
- 我根本需要依赖项吗?是否可以使用内置功能完成,或者是否足够简单可以自己编写?
- 我具体需要做什么?您越详细,就越有可能找到一个完全符合您需求的包。您可以在 npm 或 Google 上搜索关键词。此外,更喜欢小型包而不是大型包,因为后者在安装、运行等方面可能会导致性能问题。
- 依赖项是否可信且维护良好?检查上次发布版本的时间、作者是谁以及包的每周下载量。确定包的可信度是一项需要经验的技能,因为您必须考虑包需要更新的可能性,或者有多少人可能需要它等因素。
在 src/main.jsx
文件中,添加以下代码并保存:
import Plotly from "plotly.js-dist-min";
const root = document.getElementById("root");
Plotly.newPlot(
root,
[
{
x: [1, 2, 3, 4, 5],
y: [1, 2, 4, 8, 16],
},
],
{
margin: { t: 0 },
},
);
回到 https://:5173
,您会在页面上看到一个图表。更改不同的数字,每次保存文件时都会看到图表更新。
为生产构建我们的代码
然而,这段代码尚未准备好用于生产环境。大多数构建工具系统,包括 Vite,都有“开发模式”和“生产模式”。重要的区别在于,您在开发中使用的许多有用功能在最终站点中是不需要的,因此会为生产环境剥离掉,例如,“热模块替换”、“实时重新加载”以及“未压缩和注释的源代码”。尽管远非详尽,但这些是一些常见的 Web 开发功能,它们在开发阶段非常有用,但在生产环境中用处不大。在生产环境中,它们只会膨胀您的站点。
现在使用 Ctrl + C 停止正在运行的 Vite 开发服务器。
我们现在可以为我们的简陋示例网站进行假想部署做好准备。Vite 提供了一个额外的 build
命令来生成适合发布的打包文件。
运行以下命令:
npx vite build
您应该会看到类似以下的输出:
vite v5.2.13 building for production... ✓ 6 modules transformed. dist/index.html 0.32 kB │ gzip: 0.24 kB dist/assets/index-BlYAJQFz.js 3,723.18 kB │ gzip: 1,167.74 kB (!) Some chunks are larger than 500 kB after minification. Consider: - Using dynamic import() to code-split the application - Use build.rollupOptions.output.manualChunks to improve chunking: https://rollup.node.org.cn/configuration-options/#output-manualchunks - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit. ✓ built in 4.36s
Vite 将创建一个名为 dist
的目录。如果您查看它,它包含一个 index.html
,它看起来与根目录的非常相似,只是 script
的源现在被替换为 assets
文件夹的路径。assets
文件夹包含转换后的 JavaScript 输出,现在已进行精简和优化以用于生产。
注意:您可能会担心警告说有一个块太大了。这是意料之中的,因为我们正在加载一个在后台做了很多事情的库(想象一下自己编写所有代码来绘制相同的图表)。目前,我们不需要担心它。
包管理器客户端简要指南
本教程使用 npm 安装了 Vite 包,但如前所述,还有其他替代方案。至少值得了解它们的存在,并对这些工具之间的常见命令有一些模糊的了解。您已经看到了一些实际操作,但让我们看看其他的。
列表会随着时间增长,但在撰写本文时,有以下主要的包管理器可用:
- npm 在 npmjs.org
- pnpm 在 pnpm.js.org
- Yarn 在 yarnpkg.com
npm 和 pnpm 从命令行角度来看是相似的——事实上,pnpm 旨在与 npm 提供的参数选项完全一致。它的不同之处在于,它使用不同的方法来下载和存储计算机上的包,旨在减少所需的总磁盘空间。
在下面的示例中,凡是出现 npm 的地方,pnpm 都可以替换,并且命令将正常工作。
Yarn 在安装过程方面通常被认为比 npm 更快(尽管您的体验可能有所不同)。这对于开发人员来说很重要,因为等待依赖项安装(并复制到计算机)可能会浪费大量时间。
然而,值得注意的是,安装 npm 注册表中的包不需要 npm 包管理器。pnpm 和 Yarn 可以使用与 npm 相同的 package.json
格式,并且可以从 npm 和其他包注册表安装任何包。
让我们回顾一下您希望使用包管理器执行的常见操作。
注意:我们将演示 npm 和 Yarn 命令。它们不应在同一个项目中运行。您应该使用 npm 或 Yarn 设置项目,并始终使用该包管理器的命令。
初始化一个新项目
npm init
yarn init
如上所示,这将提示并引导您完成一系列问题以描述您的项目(名称、许可证、描述等),然后为您生成一个 package.json
,其中包含有关您的项目及其依赖项的元信息。
安装依赖项
npm install vite
yarn add vite
我们还在上面看到了 install
的实际应用。这会将 vite
包及其自身的依赖项直接添加到工作目录中,位于一个名为 node_modules
的子目录中。
默认情况下,此命令将安装 vite
的最新版本,但您也可以控制此操作。您可以请求 vite@4
,它会为您提供最新的 4.x 版本(即 4.5.3)。或者您可以尝试 vite@^4.0.0
,这意味着 4.0.0 或更高版本(与上述含义相同)。
更新依赖项
npm update
yarn upgrade
这将查看当前已安装的依赖项,并在可用更新的情况下,在包中指定的范围内更新它们。
该范围在您的 package.json
文件中依赖项的版本中指定,例如 "vite": "^5.2.13"
— 在这种情况下,插入符号 ^
表示 5.2.13 之后(包括)的所有次要和补丁版本,直到(但不包括)6.0.0。
这是通过一个名为 semver 的系统确定的,这从文档来看可能有点复杂,但可以通过只考虑摘要信息和版本由 MAJOR.MINOR.PATCH
表示来简化,例如 2.0.1 是主要版本 2,补丁版本 1。尝试 semver 值的一个极好方法是使用 semver 计算器。
需要记住的是,npm update
不会将依赖项升级到超出 package.json
中定义的范围——要做到这一点,您需要专门安装该版本。
更多命令
您可以在线找到有关 npm 和 yarn 的单个命令的更多信息。同样,pnpm 命令将与 npm 保持一致,并有一些额外的功能。
创建自己的命令
包管理器还支持创建自己的命令并从命令行执行它们。例如,之前我们使用 npx
调用 vite
命令来启动 Vite 开发服务器。我们可以创建以下命令:
npm run dev
# or yarn run dev
这将运行一个自定义脚本,以“开发模式”启动我们的项目。事实上,我们经常在所有项目中包含此脚本,因为本地开发设置通常与生产环境的运行方式略有不同。
如果你尝试在之前的测试项目中运行这个命令,它很可能会提示“dev script is missing”。这是因为 npm、Yarn(以及类似的工具)正在寻找你的 package.json
文件的 scripts
属性中名为 dev
的属性。所以,让我们在我们的 package.json
中创建一个自定义的简写命令——“dev”。如果你按照之前的教程操作,你的 npm-experiment 目录中应该有一个 package.json
文件。打开它,它的 scripts
成员应该看起来像这样:
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}
更新它,使它看起来像这样,并保存文件:
{
"scripts": {
"dev": "vite"
}
}
我们已经添加了一个自定义的 dev
命令作为 npm 脚本。
现在尝试在您的终端中运行以下命令,确保您位于 npm-experiment
目录中:
npm run dev
这应该会启动 Vite 并启动与之前相同的本地开发服务器。
请注意,我们在此处定义的脚本不再需要 npx
前缀。这是因为 npm(和 yarn)命令很聪明,它们会先搜索项目中本地安装的命令行工具,然后才尝试通过传统方法(您的计算机通常存储和允许查找软件的地方)查找它们。您可以了解有关 run
命令的技术细节,尽管在大多数情况下,您自己的脚本都能正常运行。
这个特定的命令可能看起来没有必要——npm run dev
比 npx vite
需要输入更多的字符,但它是一种抽象。它允许我们在未来向 dev
命令添加更多工作,例如设置环境变量、生成临时文件等,而无需使命令复杂化。
你可以在 scripts
属性中添加各种有助于你工作的东西。例如,Vite 在模板中推荐了这些:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
总结
至此,我们的包管理器之旅告一段落。我们的下一步是构建一个示例工具链,将我们目前所学的一切付诸实践。