依赖解析

当 Parcel 构建你的源代码时,它会发现依赖项,这允许将代码拆分为单独的文件并在多个地方重复使用。依赖项描述了如何找到包含你依赖的代码的文件,以及有关如何构建它的元数据。

依赖项说明符

#

依赖项说明符是一个字符串,用于描述依赖项相对于导入它的文件的位置。例如,在 JavaScript 中,import 语句或 require 函数可用于创建依赖项。在 CSS 中,可以使用 @importurl()。通常,这些依赖项不会指定一个完整的绝对路径,而是指定一个较短的说明符,由 Parcel 和其他工具解析为绝对路径。

Parcel 实现了一个增强的 Node 模块解析算法 版本。它负责将依赖项说明符转换为可以从文件系统加载的绝对路径。除了许多工具支持的标准依赖项说明符之外,Parcel 还支持一些其他说明符类型和功能。

相对说明符

#

相对说明符以 ... 开头,并解析相对于导入文件的文件。

/path/to/project/src/client.js
import './utils.js';
import '../constants.js';

在上面的示例中,第一个导入将解析为 /path/to/project/src/utils.js,第二个将解析为 /path/to/project/constants.js

文件扩展名

#

建议在所有导入说明符中包含完整的文件扩展名。这既可以提高依赖项解析性能,又可以减少歧义。

也就是说,为了与 Node 中的 CommonJS 和 TypeScript 兼容,Parcel 允许省略某些文件类型的文件扩展名。可以省略的文件扩展名包括 .ts.tsx.mjs.js.jsx.cjs.json。导入所有其他文件类型都需要文件扩展名。

以下示例解析为与上面相同的文件。

/path/to/project/src/client.js
import './utils';
import '../constants';

请注意,只有从 JavaScript 或 TypeScript 文件导入时才能省略这些文件。在 HTML 和 CSS 等其他文件类型中定义的依赖项始终需要文件扩展名。

在 TypeScript 文件中,Parcel 还会尝试用它们的 TypeScript 等效项(.ts.tsx.mts.cts)替换 JavaScript 扩展名,包括 .js.jsx.mjs.cjs。例如,对 ./foo.js 的依赖项将解析为 ./foo.ts。这与 TSC 的行为相匹配。但是,与 TSC 不同,如果原始 ./foo.js 存在,它将被使用,而不是 TS 版本,这与 Node 和其他捆绑器的行为相匹配。

目录索引文件

#

在 JavaScript、Typescript 和其他基于 JS 的语言中,依赖项说明符可以解析为目录而不是文件。如果目录包含 package.json 文件,则主条目将按 包条目 部分中所述进行解析。如果没有 package.json,它将尝试解析为目录中的索引文件,例如 index.jsindex.ts。上面列出的所有扩展名都支持索引文件。

/path/to/project/src/app.js
import './client';

例如,如果 /path/to/project/src/client 是一个目录,则上述说明符可以解析为 /path/to/project/src/client/index.js

裸说明符

#

裸规范符以除 ./~# 之外的任何字符开头。在 JavaScript、TypeScript 和其他基于 JS 的语言中,它们解析为 node_modules 中的一个包。对于其他类型的文件,如 HTML 和 CSS,裸规范符的处理方式与 相对规范符 相同。

/path/to/project/src/client/index.js
import 'react';

在上述示例中,react 可能解析为类似 /path/to/project/node_modules/react/index.js 的内容。确切位置将取决于 node_modules 目录的位置,以及包内的配置。

node_modules 目录从导入文件向上搜索。搜索在项目根目录停止。例如,如果导入文件位于 /path/to/project/src/client/index.js,则将搜索以下位置

找到模块目录后,将解析包条目。有关此过程的更多详细信息,请参阅 包条目

包子路径

#

裸规范符还可以在包内指定子路径。例如,一个包可以发布多个入口点,而不仅仅是一个入口点。

import 'lodash/clone';

上述示例将如上所述在 node_modules 目录中解析 lodash,然后在包中解析 clone 模块,而不是其主入口点。例如,这可以是 node_modules/lodash/clone.js 文件。

内置模块

#

Parcel 包括许多内置 Node.js 模块的垫片,例如 pathurl。当依赖项规范符引用其中一个模块名称时,内置模块优先于以相同名称安装在 node_modules 中的任何模块。在为 node 环境构建时,内置模块将从包中排除,否则将包含垫片。有关内置模块的完整列表,请参阅 Node 文档

在为 Electron 环境构建时,electron 模块也被视为内置模块并从包中排除。

绝对规范符

#

绝对规范符以 / 开头,并解析相对于项目根目录的文件。项目根目录是项目的基目录,通常包含包管理器锁定文件(例如 yarn.lockpackage-lock.json)或源代码控制目录(例如 .git)。绝对规范符可用于避免在深度嵌套层次结构中出现非常长的相对路径。

import '/src/client.js';

上述示例可以放置在项目目录结构中的任何文件中的任何位置,并且始终解析为 /path/to/project/src/client.js

波浪号规范符

#

波形符规范符以 ~ 开头,并解析为相对于导入文件最近的包根目录。包根目录是一个带有 package.json 文件的目录,通常在 node_modules 中找到,或作为单一存储库中包的根目录。波形符规范符与绝对规范符有类似的用途,但当您有多个包时,波形符规范符更有用。

/path/to/project/packages/frontend/src/client/index.js
import '~/src/utils.js';

上述示例将解析为 /path/to/project/packages/frontend/src/utils.js

哈希规范符

#

哈希规范符以 # 字符开头,其行为取决于它们所包含的文件类型。在 JavaScript 和 TypeScript 文件中,哈希规范符被视为内部包导入,如下所述。在其他文件中,这些被视为相对 URL 哈希。

package.json 中的 "imports" 字段可用于定义适用于包中 JavaScript 或 TypeScript 文件中的导入规范符的私有映射。这允许包根据环境定义条件导入,如下所述 文档Node.js 文档 中所述。

/path/to/project/package.json
{
"imports": {
"#dep": {
"node": "dep-node",
"browser": "dep-browser"
}
}
}
/path/to/project/src/index.js
import '#dep';

查询参数

#

依赖项规范符还可以包括查询参数,该参数指定已解析文件的转换选项。例如,您可以在加载图像时指定图像的宽度和高度以调整其大小。

.logo {
background: url(logo.png?width=400&height=400);
}

有关图像的更多详细信息,请参阅 图像转换器 文档。您还可以在自定义 转换器 插件中使用查询参数。

注意:CommonJS 规范符(由 require 函数创建)不支持查询参数。

URL 方案

#

依赖项规范符可以使用 URL 方案来定位 命名管道。这些允许您指定一个不同的管道来编译文件,而不是默认管道。例如,bundle-text: 方案可用于将已编译的包内联为文本。有关更多详细信息,请参阅 包内联

有一些保留的 URL 方案可能不用于命名管道,并具有内置行为。

Glob 指定符

#

Parcel 支持通过 glob 一次导入多个文件,但是,由于 glob 导入是非标准的,因此它们未包含在默认 Parcel 配置中。要启用它们,请将 @parcel/resolver-glob 添加到 .parcelrc

.parcelrc
{
"extends": "@parcel/config-default",
"resolvers": ["@parcel/resolver-glob", "..."]
}

启用后,你可以使用 ./files/*.js 之类的指定符导入多个文件。这将返回一个对象,其键对应于文件名。

import * as files from './files/*.js';

等同于

import * as foo from './files/foo.js';
import * as bar from './files/bar.js';

let files = {
foo,
bar
};

具体来说,glob 模式的动态部分将成为对象的键。如果有多个动态部分,则将返回一个嵌套对象。例如,如果存在 pages/profile/index.js 文件,则以下内容将与之匹配。

import * as pages from './pages/*/*.js';

console.log(pages.profile.index);

这也适用于 bundle-text: 之类的 URL 方案,以及动态导入。在使用动态导入时,结果对象将包括文件名到函数的映射。可以调用每个函数来加载已解析的模块。这意味着每个文件都是按需加载的,而不是全部预先加载。

let files = import('./files/*.js');

async function doSomething() {
let foo = await files.foo();
let bar = await files.bar();
return foo + bar;
}

Glob 还可以用于从 npm 包导入文件

import * as locales from '@company/pkg/i18n/*.js';

console.log(locales.en.message);

Glob 导入也适用于 CSS

@import "./components/*.css";

等同于

@import "./components/button.css";
@import "./components/dropdown.css";

包条目

#

在解析包目录时,将参考 package.json 文件以确定包条目。Parcel 按以下字段(按顺序)进行检查

如果未设置这些字段或它们指向的文件不存在,则解析将回退到索引文件。有关更多详细信息,请参阅 目录索引文件

软件包导出

#

package.json 中的 "exports" 字段可用于定义软件包的公开可访问入口点。这些字段还可以基于环境定义条件行为,从而允许根据模块的导入位置(例如,节点或浏览器)更改解析。

启用软件包导出

#

默认情况下,软件包导出处于禁用状态,因为它们可能会破坏未考虑它们的现有项目。您可以通过将以下内容添加到项目根目录中的 package.json 文件来启用支持。

/path/to/project/package.json
{
"@parcel/resolver-default": {
"packageExports": true
}
}

单个导出

#

如果软件包只有一个导出的模块,则 "exports" 字段可以定义为字符串

package.json
{
"name": "foo",
"exports": "./dist/index.js"
}

当用户在上述示例中导入 "foo" 软件包时,将解析 node_modules/foo/dist/index.js 模块。

多个导出

#

如果软件包导出多个模块,则 "exports" 字段可以提供映射,定义在哪里可以找到这些导出中的每一个。"." 导出定义了主入口点,其他条目定义为子路径。

package.json
{
"name": "foo",
"exports": {
".": "./dist/index.js",
"./bar": "./dist/bar.js"
}
}

使用上述软件包,用户可以导入 "foo"(解析为 node_modules/foo/dist/index.js)或 "foo/bar"(解析为 node_modules/foo/dist/bar.js)。

任何未明确导出包含 "exports" 字段的软件包的子路径都将无法被使用者访问。例如,尝试在上述软件包中导入 "foo/abc" 将导致构建时错误。

* 字符可以用作导出映射中的通配符。映射的左侧只能出现一个 *,匹配的字符串将替换为右侧的每个实例。这使您可以避免手动列出要导出的每个文件。

package.json
{
"name": "foo",
"exports": {
"./*": "./dist/*.js"
}
}

在上面的示例中,dist 目录中的所有 .js 文件都从包中导出,不带扩展名。例如,导入 "foo/bar" 将解析为 node_modules/foo/dist/bar.js

条件导出

#

"exports" 字段还可以在不同的 环境 或其他条件下为同一规范符定义不同的映射。例如,一个包可能为 ES 模块和 CommonJS 或 Node 和浏览器提供不同的入口点。

package.json
{
"name": "foo",
"exports": {
"node": "./dist/node.js",
"default": "./dist/default.js"
}
}

在上面的示例中,如果消费者从 Node 环境导入 "foo" 模块,它将解析为 node_modules/foo/dist/node.js,否则将解析为 node_modules/foo/default.js

条件导出也可以嵌套在子路径映射中。

package.json
{
"name": "foo",
"exports": {
"./bar": {
"node": "./dist/bar-node.js",
"default": "./dist/bar-default.js"
}
}
}

这允许导入 "foo/bar" 以解析为 Node 和其他环境的不同文件。

条件导出也可以相互嵌套以创建更复杂的逻辑。

package.json
{
"name": "foo",
"exports": {
"node": {
"import": "./dist/node.mjs",
"require": "./dist/node.cjs"
},
"default": "./dist/default.js"
}
}

上面的示例定义了模块的不同版本,具体取决于包是在 Node 环境中通过 ESM import 还是 CommonJS require 加载的。

Parcel 支持以下导出条件

解析导出条件的顺序遵循它们在 package.json 中定义的顺序,而不是上面列出的顺序。

更多示例

#

有关 package.json 导出的更多详细信息,请参阅 Node.js 文档

别名

#

别名可用于覆盖依赖项的正常解析。例如,您可能希望使用不同的但兼容 API 的替换项覆盖模块,或将依赖项映射到从 CDN 加载的库定义的全局变量。

别名通过 package.json 中的 alias 字段定义。它们可以在最近的 package.json 中本地定义,该 package.json 位于包含依赖项的源文件附近,或在项目根目录中的 package.json 中全局定义。全局别名适用于项目中的所有文件和包,包括 node_modules 中的那些文件和包。

包别名

#

包别名将 node_modules 依赖项映射到不同的包或项目中的本地文件。例如,要在项目中的两个文件中以及 node_modules 中的任何其他库中用 Preact 替换 reactreact-dom,您可以在项目根目录中的 package.json 中定义一个全局别名。

package.json
{
"alias": {
"react": "preact/compat",
"react-dom": "preact/compat"
}
}

您还可以使用从定义别名的 package.json 中的相对路径将模块映射到项目中的文件。

package.json
{
"alias": {
"react": "./my-react.js"
}
}

还支持仅给模块的特定 子路径 设置别名。此示例将 lodash/clone 的别名设置为 tiny-clonelodash 包中的其他导入不会受到影响。

package.json
{
"alias": {
"lodash/clone": "tiny-clone"
}
}

反过来也适用:如果给整个模块设置了别名,则该包的任何子路径导入都将在带别名的模块中解析。例如,如果您将 lodash 的别名设置为 my-lodash 并导入了 lodash/clone,这将解析为 my-lodash/clone

文件别名

#

别名还可以定义为相对路径,以使用不同的文件替换包中的特定文件。可以使用 alias 字段无条件替换文件,或使用 sourcebrowser 字段有条件地进行替换。有关这些字段的详细信息,请参见上面的 包条目

例如,要使用特定于浏览器的版本替换某个文件,您可以使用 browser 字段。

package.json
{
"browser": {
"./fs.js": "./fs-browser.js"
}
}

现在,如果在浏览器环境中导入了 my-module/fs.js,实际上将获取 my-module/fs-browser.js。这适用于外部导入(例如 包子路径),以及模块内部导入。

Glob 别名

#

还可以使用 glob 定义文件别名,这允许使用单个模式替换许多文件。替换可以包括模式,例如 $1,以访问捕获的 glob 匹配项。可以使用 alias 字段无条件替换文件,或者使用 sourcebrowser 字段有条件替换文件。有关这些字段的详细信息,请参阅上面的 包条目

例如,可以使用 source 字段在包中的已编译代码和原始源代码之间提供映射。当模块是符号链接的,或者在单一仓库中时,这将允许 Parcel 从源代码编译模块,而不是使用预编译版本。

package.json
{
"source": {
"./dist/*": "./src/$1"
}
}

现在,任何时候导入 dist 目录中的文件,都将加载 src 文件夹中的相应文件。

Shim 别名

#

文件或包可以别名化为 false 以从构建中排除,并替换为空模块。例如,这可能有助于从仅适用于 Node.js 的浏览器构建中排除某些模块。

package.json
{
"alias": {
"fs": false
}
}

全局别名

#

文件或包还可以别名化为将在运行时定义的全局变量。例如,可以从 CDN 加载特定库。在解析对该库的任何依赖项时,它将被替换为对该全局变量的引用,而不是被捆绑。

这可以通过创建具有 global 属性的对象的别名来完成。以下示例将 jquery 依赖项说明符别名化为全局变量 $

package.json
{
"alias": {
"jquery": {
"global": "$"
}
}
}

TSConfig

#

Parcel 支持 TypeScript 的 tsconfig.json 配置文件中定义的一些设置,包括 baseUrlpathsmoduleSuffixes。Parcel 从包含依赖项的文件向上搜索以查找最近的 tsconfig.json 文件。它还支持使用 extends 选项将来自多个 tsconfig 的设置合并在一起。有关详细信息,请参阅 TypeScript 文档

baseUrl

#

baseUrl 字段定义了解决 裸规范符 的基本目录。

tsconfig.json
{
"compilerOptions": {
"baseUrl": "./src"
}
}
src/App.js
import 'Home';

在上述示例中,如果 Home 存在,它将解析为 src/Home.js。否则,它将回退到 node_modules/Home,例如。

paths

#

paths 字段可用于定义从 裸规范符 到文件路径的映射。您还可以使用 * 字符定义通配符模式。

paths 字段中引用的文件路径相对于 baseUrl(如果已定义),否则相对于包含 tsconfig.json 文件的目录。

tsconfig.json
{
"compilerOptions": {
"paths": {
"jquery": ["./vendor/jquery/dist/jquery"],
"app/*": ["./src/app/*"]
}
}
}
src/App.js
import 'jquery';
import 'app/foo';

在上述示例中,jquery 解析为 ./vendor/jquery/dist/jquery.js,而 app/foo 解析为 ./src/app/foo.js

moduleSuffixes

#

moduleSuffixes 字段允许您指定在解析模块时要搜索的文件名后缀。

tsconfig.json
{
"compilerOptions": {
"moduleSuffixes": [".ios", ".native", ""]
}
}
src/App.js
import './foo';

在上述示例中,Parcel 将查找 ./foo.ios.ts./foo.native.ts./foo.ts(除了其他扩展名,如 .tsx.js 等)。

配置其他工具

#

本部分介绍如何配置其他工具以配合 Parcel 对 Node 解析算法的扩展。

TypeScript

#

需要配置 TypeScript 以支持 Parcel 的特性,例如绝对和波形依赖项规范符以及别名。这可以通过在 tsconfig.json 中使用 paths 选项来完成。有关更多信息,请参阅 TypeScript 模块解析文档

例如,若要将波形路径映射到根目录,可以使用此配置

tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~*": ["./*"]
}
}
}

还可以通过在项目中创建 环境模块 声明来启用对 URL 架构 的支持。例如,若要将使用 bundle-text: 架构加载的依赖项映射到字符串,可以使用以下声明。这可以放置在项目中任何位置的文件中,例如 parcel.d.ts

parcel.d.ts
declare module 'bundle-text:*' {
const value: string;
export default value;
}

Flow

#

需要配置 Flow 以支持绝对和波形规范符以及别名。这可以通过在 .flowconfig 中使用 module.name_mapper 特性来完成。

例如,若要将绝对规范符映射到从项目根目录解析,可以使用此配置

.flowconfig
[options]
module.name_mapper='^\/\(.*\)$' -> '<PROJECT_ROOT>/\1'

若要启用 URL 架构,您需要创建一个到 .flow 声明文件 的映射,该文件导出预期类型。例如,若要将使用 bundle-text: 架构加载的依赖项映射到字符串,您可以创建一个名为 bundle-text.js.flow 的文件,并将所有引用该架构的依赖项映射到它。

bundle-text.js.flow
// @flow
declare var value: string;
export default value;
.flowconfig
[options]
module.name_mapper='^bundle-text:.*$' -> '<PROJECT_ROOT>/bundle-text.js'