Vite monorepo 中不明确的别名

Est*_*ask 5 typescript rollupjs monorepo yarn-workspaces vite

Vite monorepo 会出现问题,@由于单独的 tsconfig 文件(可以在 IDE 中可见),TypeScript 会尊重别名,但 Vite 在构建时不会在工作区之间进行区分。

该项目使用 Yarn 1.x 和工作区、TypeScript 4.9、Vite 3.2、Lerna 6.4(此时不应影响问题)

monorepo 的项目结构很常见:

packages/
  foo-bar/
    src/
      index.ts
    package.json
    tsconfig.json
    vite.config.ts
    yarn.lock
  foo-baz/
    (same as above)
  foo-shared/
    src/
      qux.ts
      quux.ts
    package.json
    tsconfig.json
    yarn.lock
lerna.json
package.json
tsconfig.json
yarn.lock
Run Code Online (Sandbox Code Playgroud)

当一个包 ( foo-bar) 从另一个包 ( ) 导入模块时foo-shared

包/foo-bar/src/index.ts

import qux from `@foo/shared/qux';
Run Code Online (Sandbox Code Playgroud)

另一个包在构建时将本地别名导入解析为错误的包,因为 Vite 不知道 tsconfig 别名:

包/foo-shared/src/qux.ts

import quux from `@/quux'; // resolves to packages/foo-bar/src/quux.ts and errors
Run Code Online (Sandbox Code Playgroud)

错误是这样的:

[vite:load-fallback] 无法加载 ...\packages\foo-bar\src/quux (由 ../foo-shared/src/qux.ts 导入):ENOENT:没有这样的文件或目录,打开 ' ...\packages\foo-bar\src\stores\quux' 构建期间出现错误:

foo-shared目前是一个虚拟包,它不是独立构建的,只是别名并在其他包上使用。

packages/foo-bar/vite.config.ts

  // ...
  export default defineConfig({
    resolve: {
      alias: {
        '@': path.join(__dirname, './src'),
        '@foo/shared': path.join(__dirname, '../foo-shared/src'),
      },
    },
    / * some irrelevant options */
  });
Run Code Online (Sandbox Code Playgroud)

packages/foo-bar/tsconfig.jsonpackages/foo-shared/tsconfig.json类似:

{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@foo/shared/*": ["../foo-shared/src/*"]
    },
    "typeRoots": [
      "./node_modules/@types",
      "../../node_modules/@types",
    ]
  },
  "include": [
     "src/**/*.ts",
     "src/**/*.d.ts",
     "src/**/*.vue"
   ],
  "exclude": [
    "node_modules"
  ],
}
Run Code Online (Sandbox Code Playgroud)

我尝试resolve.aliasvite-tsconfig-paths插件替换但没有成功。它根本不会影响开箱即用的别名,而且我不能确定它是否适用于这种情况。

如何配置Vite@根据父模块的路径将“ ”开头的路径解析为不同的路径?

sta*_*all 1

来自Vite 文档中的resolve.alias选项

@rollup/plugin-alias将作为其条目选项传递。可以是一个对象,也可以是一对数组{ find, replacement, customResolver }

不幸的是,在撰写本文时,rollup 的解析别名插件的自述文件是......稀疏的:

类型:Function | Object
默认:null

指示插件使用替代解析算法,而不是 Rollup 的解析器。有关该钩子的更多信息,请参阅Rollup 文档resolveId。有关详细示例,请参阅:自定义解析器

customResolver如果您实际上想知道如何编写一个而不是使用另一个主要是预先构建的示例,则所引用的“详细示例”根本没有指导意义(人们会想知道resolveId钩子是什么,以及它是如何实现的)相关。作为参考,我正在查看 v4.0.3 的文档。希望它们将来会更好)

它的类型声明文件有助于填补空白。您可以在这里找到它: https: //github.com/rollup/plugins/blob/master/packages/alias/types/index.d.ts,您将在其中看到类似以下内容的内容:

import type { Plugin, PluginHooks } from 'rollup';

type MapToFunction<T> = T extends Function ? T : never;

export type ResolverFunction = MapToFunction<PluginHooks['resolveId']>;

export interface ResolverObject {
  buildStart?: PluginHooks['buildStart'];
  resolveId: ResolverFunction;
}

export interface Alias {
  find: string | RegExp;
  replacement: string;
  customResolver?: ResolverFunction | ResolverObject | null;
}

export interface RollupAliasOptions {
  /** blah blah not relevant for vite.js */
  customResolver?: /* blah blah not relevant for vite.js */;

  /**
   * Specifies an `Object`, or an `Array` of `Object`,
   * which defines aliases used to replace values in `import` or `require` statements.
   * With either format, the order of the entries is important,
   * in that the first defined rules are applied first.
   */
  entries?: readonly Alias[] | { [find: string]: string };
}
Run Code Online (Sandbox Code Playgroud)

特别是,文档评论的最后一部分RollupAliasOptions#entries很重要。我敢打赌,您可以通过重新resolve.alias排序 vite.config.js 中的条目来解决您的问题:

import type { Plugin, PluginHooks } from 'rollup';

type MapToFunction<T> = T extends Function ? T : never;

export type ResolverFunction = MapToFunction<PluginHooks['resolveId']>;

export interface ResolverObject {
  buildStart?: PluginHooks['buildStart'];
  resolveId: ResolverFunction;
}

export interface Alias {
  find: string | RegExp;
  replacement: string;
  customResolver?: ResolverFunction | ResolverObject | null;
}

export interface RollupAliasOptions {
  /** blah blah not relevant for vite.js */
  customResolver?: /* blah blah not relevant for vite.js */;

  /**
   * Specifies an `Object`, or an `Array` of `Object`,
   * which defines aliases used to replace values in `import` or `require` statements.
   * With either format, the order of the entries is important,
   * in that the first defined rules are applied first.
   */
  entries?: readonly Alias[] | { [find: string]: string };
}
Run Code Online (Sandbox Code Playgroud)

现在,如果这不起作用,或者您发现自己将来想做任何这还不够的事情,您可以编写一个自定义解析器(看看该类型如何Alias有一个customResolver字段?)。这应该回答您的最终问题:“如何配置 Vite 以@根据父模块的路径将以“”开头的路径解析为不同的路径?

为此,您可以在 rollup/plugin-alias 文档中查看链接文档:https://rollupjs.org/plugin-development/#resolveid。以下是文档中的一些相关摘录(特别是请注意importer参数):

类型: ResolveIdHook
种类: 异步,首先
以前的: buildStart如果我们正在解析入口点,moduleParsed如果我们正在解析导入,或者作为resolveDynamicImport. 此外,可以在插件挂钩的构建阶段通过调用this.emitFile发出入口点或随时通过调用this.resolve手动解析 id 来触发此挂钩
下一个: load如果解析的 id 尚未加载,否则buildEnd
alias: {
  '@foo/shared': path.join(__dirname, '../foo-shared/src'), // moved to be first
  '@': path.join(__dirname, './src'),
}
Run Code Online (Sandbox Code Playgroud)

定义自定义解析器。解析器对于定位第三方依赖项等很有用。这source是导入者,与导入语句中所写的完全一样,即 for

type ResolveIdHook = (
  source: string,
  importer: string | undefined,
  options: {
      assertions: Record<string, string>;
      custom?: { [plugin: string]: any };
      isEntry: boolean;
  }
) => ResolveIdResult;

type ResolveIdResult = string | null | false | PartialResolvedId;

interface PartialResolvedId {
  id: string;
  external?: boolean | 'absolute' | 'relative';
  assertions?: Record<string, string> | null;
  meta?: { [plugin: string]: any } | null;
  moduleSideEffects?: boolean | 'no-treeshake' | null;
  resolvedBy?: string | null;
  syntheticNamedExports?: boolean | string | null;
}
Run Code Online (Sandbox Code Playgroud)

源将是"../bar.js".

importer是导入模块的完全解析的 ID。在解析入口点时,导入器通常会是undefined. this.emitFile这里的一个例外是通过此处生成的入口点,您可以提供一个importer参数。

[...]

返回null遵循其他resolveId函数并最终遵循默认的解析行为。返回应被视为外部模块且不包含在捆绑包中的false信号。source如果相对导入发生这种情况,则 id 将以与external使用该选项时相同的方式重新规范化。

[...]