发布于 2026-01-06 2 阅读
0

如何在前端项目中以原生方式配置路径别名

如何在前端项目中以原生方式配置路径别名

我们将深入了解该imports字段package.json及其在路径别名配置中的应用。此外,我们还将探讨常用开发工具如何支持此字段,并确定各种使用场景下的最佳配置。

内容

关于路径别名

项目通常会发展成复杂的嵌套目录结构。因此,导入路径可能会变得更长、更混乱,这不仅会影响代码的美观,还会使人难以理解导入代码的来源。

使用路径别名可以解决这个问题,它允许定义相对于预定义目录的导入语句。这种方法不仅解决了理解导入路径的问题,还简化了重构过程中代码的移动。

// Without Aliases
import { apiClient } from '../../../../shared/api';
import { ProductView } from '../../../../entities/product/components/ProductView';
import { addProductToCart } from '../../../add-to-cart/actions';

// With Aliases
import { apiClient } from '#shared/api';
import { ProductView } from '#entities/product/components/ProductView';
import { addProductToCart } from '#features/add-to-cart/actions';
Enter fullscreen mode Exit fullscreen mode

Node.js 中有很多库可用于配置路径别名,例如alias-hqtsconfig-paths。然而,在查阅 Node.js 文档时,我发现了一种无需依赖第三方库即可配置路径别名的方法。此外,这种方法无需构建步骤即可使用别名。

本文将探讨Node.js 子路径导入以及如何使用它来配置路径别名。我们还将探讨它们在前端生态系统中的支持情况。

进口领域

从 Node.js v12.19.0 开始,开发者可以使用子路径导入 (Subpath Imports)在 npm 包中声明路径别名。这可以通过文件imports中的相应字段来实现package.json。无需将包发布到 npm,package.json只需在任何目录中创建一个文件即可。因此,这种方法也适用于私有项目。

💡 这里有个有趣的事实:Node.jsimports早在 2020 年就通过名为“ Node.js 中的裸模块规范解析”的 RFC 引入了对该字段的支持。虽然该 RFC 主要针对的exports是允许声明 npm 包入口点的字段,但 `<module>`exports和 ` imports<module>` 字段的功能完全不同,即使它们的名称和语法相似。

理论上,原生支持路径别名具有以下优点:

  • 无需安装任何第三方库。
  • 运行代码时无需预先构建或动态处理导入。
  • 任何使用标准导入解析机制的基于 Node.js 的工具都支持别名。
  • 代码导航和自动完成功能应该在代码编辑器中正常工作,无需任何额外设置。

我尝试在我的项目中配置路径别名,并在实践中测试了这些语句。

配置路径别名

例如,我们考虑一个具有以下目录结构的项目:

my-awesome-project
├── src/
│   ├── entities/
│   │    └── product/
│   │        └── components/
│   │            └── ProductView.js
│   ├── features/
│   │    └── add-to-cart/
│   │        └── actions/
│   │            └── index.js
│   └── shared/
│       └── api/
│            └── index.js
└── package.json
Enter fullscreen mode Exit fullscreen mode

要配置路径别名,您可以按照文档中的说明添加几行代码package.json。例如,如果您想允许相对于src目录的导入,请将以下imports字段添加到package.json

{
    "name": "my-awesome-project",
    "imports": {
        "#*": "./src/*"
    }
}
Enter fullscreen mode Exit fullscreen mode

要使用已配置的别名,导入语句可以这样写:

import { apiClient } from '#shared/api';
import { ProductView } from '#entities/product/components/ProductView';
import { addProductToCart } from '#features/add-to-cart/actions';
Enter fullscreen mode Exit fullscreen mode

从设置阶段开始,我们就面临第一个限制:字段中的条目imports必须以#符号开头。这确保它们与包说明符(例如)区分开来@。我认为这个限制很有用,因为它允许开发人员快速确定导入中何时使用了路径别名,以及别名配置的位置。

要为常用模块添加更多路径别名,imports可以按如下方式修改该字段:

{
    "name": "my-awesome-project",
    "imports": {
        "#modules/*": "./path/to/modules/*",
        "#logger": "./src/shared/lib/logger.js",
        "#*": "./src/*"
    }
}
Enter fullscreen mode Exit fullscreen mode

文章结尾如果以“其他一切都能开箱即用”这句话来结尾就非常理想了。然而,实际上,如果你打算使用这个imports字段,可能会遇到一些困难。

Node.js 的局限性

如果您计划将路径别名与CommonJS 模块一起使用,那么我有个坏消息要告诉您:以下代码将无法正常工作。

const { apiClient } = require('#shared/api');
const { ProductView } = require('#entities/product/components/ProductView');
const { addProductToCart } = require('#features/add-to-cart/actions');
Enter fullscreen mode Exit fullscreen mode

在 Node.js 中使用路径别名时,必须遵循 ESM 世界的模块解析规则。这适用于 ES 模块和 CommonJS 模块,并由此产生了两个必须满足的新要求:

  1. 必须指定文件的完整路径,包括文件扩展名。
  2. 不允许指定目录路径并期望导入文件。必须指定文件index.js的完整路径。index.js

为了使 Node.js 能够正确解析模块,应按如下方式更正导入语句:

const { apiClient } = require('#shared/api/index.js');
const { ProductView } = require('#entities/product/components/ProductView.js');
const { addProductToCart } = require('#features/add-to-cart/actions/index.js');
Enter fullscreen mode Exit fullscreen mode

在包含大量 CommonJS 模块的项目中配置字段时,这些限制可能会导致问题imports。但是,如果您已经在使用 ES 模块,那么您的代码就满足所有要求。此外,如果您使用打包工具构建代码,则可以绕过这些限制。我们将在下文中讨论如何做到这一点。

TypeScript 对子路径导入的支持

为了正确解析导入的模块以进行类型检查,TypeScript 需要支持该imports字段。此功能从 4.8.1 版本开始支持,但前提是必须满足上述 Node.js 的限制。

要使用该imports字段进行模块解析,需要在文件中配置一些选项tsconfig.json

{
    "compilerOptions": {
        /* Specify what module code is generated. */
        "module": "esnext",
        /* Specify how TypeScript looks up a file from a given module specifier. */
        "moduleResolution": "nodenext"
    }
}
Enter fullscreen mode Exit fullscreen mode

此配置使该imports字段的功能与在 Node.js 中相同。这意味着,如果您忘记在模块导入中包含文件扩展名,TypeScript 将生成错误警告。

// OK
import { apiClient } from '#shared/api/index.js';

// Error: Cannot find module '#src/shared/api/index' or its corresponding type declarations.
import { apiClient } from '#shared/api/index';

// Error: Cannot find module '#src/shared/api' or its corresponding type declarations.
import { apiClient } from '#shared/api';

// Error: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './relative.js'?
import { foo } from './relative';
Enter fullscreen mode Exit fullscreen mode

我不想重写所有的导入语句,因为我的大多数项目都使用打包工具来构建代码,而且我在导入模块时从不添加文件扩展名。为了绕过这个限制,我找到了一种方法,将项目配置如下:

{
    "name": "my-awesome-project",
    "imports": {
        "#*": [
            "./src/*",
            "./src/*.ts",
            "./src/*.tsx",
            "./src/*.js",
            "./src/*.jsx",
            "./src/*/index.ts",
            "./src/*/index.tsx",
            "./src/*/index.js",
            "./src/*/index.jsx"
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

这种配置允许以通常的方式导入模块,而无需指定扩展名。即使导入路径指向目录,这种方法也有效。

// OK
import { apiClient } from '#shared/api/index.js';

// OK
import { apiClient } from '#shared/api/index';

// OK
import { apiClient } from '#shared/api';

// Error: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './relative.js'?
import { foo } from './relative';
Enter fullscreen mode Exit fullscreen mode

我们还有一个关于使用相对路径导入的问题。这个问题与路径别名无关。TypeScript 会抛出错误,因为我们将模块解析配置为使用相对路径模式。幸运的是,最新的TypeScript 5.0 版本nodenext新增了一种模块解析模式,无需在导入语句中指定完整路径。要启用此模式,需要在配置文件中配置一些选项。tsconfig.json

{
    "compilerOptions": {
        /* Specify what module code is generated. */
        "module": "esnext",
        /* Specify how TypeScript looks up a file from a given module specifier. */
        "moduleResolution": "bundler"
    }
}
Enter fullscreen mode Exit fullscreen mode

设置完成后,相对路径的导入功能将照常运行。

// OK
import { apiClient } from '#shared/api/index.js';

// OK
import { apiClient } from '#shared/api/index';

// OK
import { apiClient } from '#shared/api';

// OK
import { foo } from './relative';
Enter fullscreen mode Exit fullscreen mode

现在,我们可以通过该字段充分利用路径别名,imports而无需对如何编写导入路径施加任何其他限制。

使用 TypeScript 编写代码

使用tsc编译器构建源代码时,可能需要进行额外的配置。TypeScript 的一个限制是,使用该imports字段时,代码无法构建为 CommonJS 模块格式。因此,代码必须编译为 ESM 格式,并且type必须将该字段添加到package.jsonNode.js 中才能运行编译后的代码。

{
    "name": "my-awesome-project",
    "type": "module",
    "imports": {
        "#*": "./src/*"
    }
}
Enter fullscreen mode Exit fullscreen mode

如果你的代码被编译到一个单独的目录(例如 `/usr/bin`)中build/,Node.js 可能找不到该模块,因为路径别名会指向原始位置(例如 `/usr/bin`)src/。为了解决这个问题,可以在 `.js` 文件中使用条件导入路径。这样就可以从 ` /usr/bin` 目录而不是 `/ usr/bin` 目录package.json导入已编译的代码build/src/

{
    "name": "my-awesome-project",
    "type": "module",
    "imports": {
        "#*": {
            "default": "./src/*",
            "production": "./build/*"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

要使用特定的导入条件,应该使用该--conditions标志启动 Node.js。

node --conditions=production build/index.js
Enter fullscreen mode Exit fullscreen mode

代码打包器对子路径导入的支持

代码打包工具通常使用它们自己的模块解析实现,而不是 Node.js 内置的模块解析。因此,对它们来说,实现对该字段的支持至关重要imports。我已经在我的项目中使用 Webpack、Rollup 和 Vite 测试了路径别名,现在可以分享我的测试结果。

这是我用来测试打包工具的路径别名配置。我使用了与 TypeScript 相同的技巧,避免在导入语句中指定文件的完整路径。

{
    "name": "my-awesome-project",
    "type": "module",
    "imports": {
        "#*": [
            "./src/*",
            "./src/*.ts",
            "./src/*.tsx",
            "./src/*.js",
            "./src/*.jsx",
            "./src/*/index.ts",
            "./src/*/index.tsx",
            "./src/*/index.js",
            "./src/*/index.jsx"
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

Webpack

Webpack从 v5.0 版本开始支持imports字段。路径别名无需任何额外配置即可正常工作。以下是我用于构建 TypeScript 测试项目的 Webpack 配置:

const config = {
    mode: 'development',
    devtool: false,
    entry: './src/index.ts',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-typescript'],
                    },
                },
            },
        ],
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx'],
    },
};

export default config;
Enter fullscreen mode Exit fullscreen mode

维特

Vite 4.2.0 版本新增了对该imports字段的支持。然而,4.3.3 版本修复了一个重要的 bug,因此建议至少使用此版本。在 Vite 中,路径别名在两种模式下均无需额外配置即可正常工作因此,我创建了一个配置完全为空的测试项目。devbuild

Rollup

虽然 Vite 内部使用了 Rollup,但该imports字段并非开箱即用。要启用它,您需要安装@rollup/plugin-node-resolve11.1.0 或更高版本的插件。以下是一个配置示例:

import { nodeResolve } from '@rollup/plugin-node-resolve';
import { babel } from '@rollup/plugin-babel';

export default [
    {
        input: 'src/index.ts',
        output: {
            name: 'mylib',
            file: 'build.js',
            format: 'es',
        },
        plugins: [
            nodeResolve({
                extensions: ['.ts', '.tsx', '.js', '.jsx'],
            }),
            babel({
                presets: ['@babel/preset-typescript'],
                extensions: ['.ts', '.tsx', '.js', '.jsx'],
            }),
        ],
    },
];
Enter fullscreen mode Exit fullscreen mode

遗憾的是,在这种配置下,路径别名仅在 Node.js 的限制范围内有效。这意味着您必须指定完整的文件路径,包括文件扩展名。在字段中指定数组imports并不能绕过此限制,因为 Rollup 只会使用数组中的第一个路径。

我认为可以使用 Rollup 插件解决这个问题,但我没有尝试过,因为我主要用 Rollup 开发小型库。对我来说,重写整个项目的导入路径更容易些。

测试运行器中对子路径导入的支持

测试运行器是另一类严重依赖模块解析机制的开发工具。它们通常使用自己的模块解析实现,类似于代码打包器。因此,该imports字段可能无法按预期工作。

幸运的是,我测试过的工具都运行良好。我分别使用 Jest v29.5.0 和 Vite v0.30.1 测试了路径别名。在这两种情况下,路径别名都能无缝运行,无需任何额外设置或限制。Jest 自 v29.4.0 版本起就支持imports字段。而 Vitest 的支持程度完全取决于 Vite 的版本,Vite 的版本必须至少为 v4.2.0。

代码编辑器对子路径导入的支持

imports目前,常用库对这一功能的支持相当完善。但是,代码编辑器的情况如何呢?我在一个使用了路径别名的项目中测试了代码导航,特别是“跳转到定义”功能。结果发现,代码编辑器对这一功能的支持存在一些问题

VS Code

对于 VS Code 而言,TypeScript 的版本至关重要。TypeScript 语言服务器负责分析和处理 JavaScript 和 TypeScript 代码。根据您的设置,VS Code 将使用内置的 TypeScript 版本或项目中安装的版本。我测试了importsVS Code v1.77.3 与 TypeScript v5.0.4 结合使用时的字段支持情况。

VS Code 在路径别名方面存在以下问题:

  1. importsTypeScript只有在模块解析设置设为 `true`nodenext或 `false`时才会使用该字段bundler。因此,要在 VS Code 中使用它,您需要在项目中指定模块解析。
  2. IntelliSense 目前不支持使用该字段建议导入路径。此问题imports已提交待解决的 issue 。

为了绕过这两个问题,你可以在文件中复制路径别名配置tsconfig.json。如果你不使用 TypeScript,也可以在其他地方执行相同的操作jsconfig.json

// tsconfig.json OR jsconfig.json
{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "#*": ["./src/*"]
        }
    }
}

// package.json
{
    "name": "my-awesome-project",
    "imports": {
        "#*": "./src/*"
    }
}
Enter fullscreen mode Exit fullscreen mode

WebStorm

自 2021.3 版本起(我测试的是 2022.3.4 版本),WebStorm支持imports字段。此功能与 TypeScript 版本无关,因为 WebStorm 使用的是自己的代码分析器。但是,WebStorm 在支持路径别名方面存在一些其他问题:

  1. 编辑器严格遵守 Node.js 对路径别名使用的限制。如果未显式指定文件扩展名,代码导航将无法正常工作。导入目录时也存在同样的问题index.js
  2. WebStorm 存在一个 bug,会导致无法在字段中使用路径数组imports。在这种情况下,代码导航将完全停止工作。
{
    "name": "my-awesome-project",

    // OK
    "imports": {
        "#*": "./src/*"
    },

    // This breaks code navigation
    "imports": {
        "#*": ["./src/*", "./src/*.ts", "./src/*.tsx"]
    }
}
Enter fullscreen mode Exit fullscreen mode

幸运的是,我们可以使用与解决 VS Code 中所有问题相同的技巧。具体来说,我们可以在配置tsconfig.json文件中复制路径别名配置jsconfig.json。这样就可以不受限制地使用路径别名了。

推荐配置

根据我imports在各种项目中使用该字段的实验和经验,我已经确定了不同类型项目的最佳路径别名配置。

如果没有 TypeScript 或 Bundler

此配置适用于源代码在 Node.js 中运行且无需额外构建步骤的项目。要使用此配置,请按照以下步骤操作:

  1. imports在文件中配置该字段package.json。本例中只需进行非常基本的配置即可。
  2. 为了使代码编辑器中的代码导航功能正常工作,需要在jsconfig.json文件中配置路径别名。
// jsconfig.json
{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "#*": ["./src/*"]
        }
    }
}

// package.json
{
    "name": "my-awesome-project",
    "imports": {
        "#*": "./src/*"
    }
}
Enter fullscreen mode Exit fullscreen mode

使用 TypeScript 构建代码

此配置适用于源代码使用 TypeScript 编写并使用tsc编译器构建的项目。在此配置中,务必配置以下内容:

  1. imports文件中的字段package.json在这种情况下,需要添加条件路径别名,以确保 Node.js 能够正确解析已编译的代码。
  2. 在文件中启用 ESM 包格式package.json是必要的,因为 TypeScript 只有在使用该字段时才能编译 ESM 格式的代码imports
  3. tsconfig.json文件中设置 ESM 模块格式moduleResolution。这将允许 TypeScript 在导入时提示遗漏的文件扩展名。如果未指定文件扩展名,代码在编译后将无法在 Node.js 中运行。
  4. 要修复代码编辑器中的代码导航问题,必须在文件中重复路径别名tsconfig.json
// tsconfig.json
{
    "compilerOptions": {
        "module": "esnext",
        "moduleResolution": "nodenext",
        "baseUrl": "./",
        "paths": {
            "#*": ["./src/*"]
        },
        "outDir": "./build"
    }
}

// package.json
{
    "name": "my-awesome-project",
    "type": "module",
    "imports": {
        "#*": {
            "default": "./src/*",
            "production": "./build/*"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

使用捆绑器构建代码

此配置适用于源代码已打包的项目。在这种情况下,无需使用 TypeScript。如果未使用 TypeScript,所有设置都可以在一个jsconfig.json文件中完成。此配置的主要特点是允许您绕过 Node.js 在导入时指定文件扩展名的限制。

请务必配置以下内容:

  1. imports在文件中配置该字段package.json。在这种情况下,您需要为每个别名添加一个路径数组。这样,打包工具无需指定文件扩展名即可找到导入的模块。
  2. 要修复代码编辑器中的代码导航问题,需要在 atsconfig.jsonjsconfig.json文件中重复路径别名。
// tsconfig.json
{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "#*": ["./src/*"]
        }
    }
}

// package.json
{
    "name": "my-awesome-project",
    "imports": {
        "#*": [
            "./src/*",
            "./src/*.ts",
            "./src/*.tsx",
            "./src/*.js",
            "./src/*.jsx",
            "./src/*/index.ts",
            "./src/*/index.tsx",
            "./src/*/index.js",
            "./src/*/index.jsx"
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

结论

imports与通过第三方库配置路径别名相比,直接在字段中配置路径别名有利有弊。虽然这种方法已被常用开发工具支持(截至 2023 年 4 月),但它也存在一些局限性。

这种方法具有以下优点:

  • 无需“即时”编译或转译代码即可使用路径别名。

  • 大多数流行的开发工具都支持路径别名,无需任何额外配置。这一点已在 Webpack、Vite、Jest 和 Vitest 中得到验证。

  • package.json这种方法提倡在一个可预测的位置(文件)配置路径别名。

  • 配置路径别名不需要安装第三方库。

然而,目前存在一些暂时的缺点,但随着开发工具的不断发展,这些缺点将会消除:

  • 即使是常用的代码编辑器在支持该imports字段时也存在问题。为了避免这些问题,您可以使用该jsconfig.json文件。但是,这会导致路径别名配置在两个文件中重复。

  • 某些开发工具可能无法imports直接与该字段兼容。例如,Rollup 需要安装额外的插件。

  • 在 Node.js 中使用该imports字段会给导入路径添加新的约束。这些约束与 ES 模块的约束相同,但可能会使该imports字段的使用变得更加困难。

  • Node.js 的约束可能会导致 Node.js 与其他开发工具在实现上存在差异。例如,代码打包工具可能会忽略 Node.js 的约束。这些差异有时会使配置变得复杂,尤其是在设置 TypeScript 时。

那么,使用字段来配置路径别名是否值得呢imports?我认为对于新项目来说,是的,这种方法比使用第三方库更值得。

在未来几年,该imports字段很有可能成为许多开发人员配置路径别名的标准方法,因为它与传统的配置方法相比具有显著优势。

但是,如果您已经有一个配置了路径别名的项目,切换到该imports字段不会带来显著的好处。

希望这篇文章对您有所帮助,您能从中有所收获。感谢阅读!

实用链接

  1. 用于实施进出口的 RFC
  2. 一系列测试,旨在更好地了解导入字段的功能
  3. Node.js 中 imports 字段的文档
  4. Node.js 对 ES 模块中导入路径的限制
文章来源:https://dev.to/nodge/the-native-way-to-configure-path-aliases-in-frontend-projects-ce4