开发者问题收集

如何在库项目中拥有绝对导入路径?

2019-07-09
27326

我有一个库,它的工作区包含两个项目,一个用于库本身,一个用于测试应用程序。

├── projects
    ├── midi-app
    └── midi-lib

在工作区 tsconfig.json 文件中,我配置了一些 @app@lib 路径:

"paths": {
  "@app/*": ["projects/midi-app/src/app/*"],
  "@lib/*": ["projects/midi-lib/src/lib/*"],
  "midi-lib": [
    "dist/midi-lib"
  ],
  "midi-lib/*": [
    "dist/midi-lib/*"
  ]
}

有一个 projects/midi-lib/tsconfig.lib.json 文件,它扩展了上面的 tsconfig.json 文件:

"extends": "../../tsconfig.json",

有一个 public-api.ts 文件,其中包含:

export * from './lib/midi-lib.module';

我可以将此库与测试应用程序一起使用。

但是,当我尝试在另一个客户端应用程序、另一个工作区中将其作为 Node 模块导入时,我在未知的路径 无法解析“@lib/...”

如何表达库路径,以便它们在客户端应用程序中公开?或者如何在打包库时翻译库路径?

作为一个附带问题,我想知道为什么不反过来进行扩展。为什么不是在 projects/midi-lib/tsconfig.lib.json 文件上扩展的 tsconfig.json 文件?

下面是我打包然后使用库的方法:

要打包库,请在父 package.json 文件的脚本数组中添加以下脚本

"copy-license": "cp ./LICENSE.md ./dist/midi-lib",
"copy-readme": "cp ./README.md ./dist/midi-lib",
"copy-files": "npm run copy-license && npm run copy-readme",
"build-lib": "ng build midi-lib",
"npm-pack": "cd dist/midi-lib && npm pack",
"package": "npm run build-lib && npm run copy-files && npm run npm-pack",

并运行命令: npm run package

然后安装依赖项

npm install ../midi-lib/dist/midi-lib/midi-lib-0.0.1.tgz

并在应用程序模块中导入模块 在 app.module.ts 文件中有:

import { MidiLibModule } from 'midi-lib';
@NgModule({
  imports: [
    MidiLibModule

最后将组件插入模板

<midi-midi-lib></midi-midi-lib>

当库安装在客户端应用程序中时,它有很多 node_modules/midi-lib 目录中的 .d.ts 文件:

├── bundles
├── esm2015
│   └── lib
│       ├── device
│       ├── keyboard
│       ├── model
│       │   ├── measure
│       │   └── note
│       │       ├── duration
│       │       └── pitch
│       ├── service
│       ├── sheet
│       ├── soundtrack
│       ├── store
│       ├── synth
│       └── upload
├── esm5
│   └── lib
│       ├── device
│       ├── keyboard
│       ├── model
│       │   ├── measure
│       │   └── note
│       │       ├── duration
│       │       └── pitch
│       ├── service
│       ├── sheet
│       ├── soundtrack
│       ├── store
│       ├── synth
│       └── upload
├── fesm2015
├── fesm5
└── lib
    ├── device
    ├── keyboard
    ├── model
    │   ├── measure
    │   └── note
    │       ├── duration
    │       └── pitch
    ├── service
    ├── sheet
    ├── soundtrack
    ├── store
    ├── synth
    └── upload

例如这个 lib/service/melody.service.d.ts 文件

import { SoundtrackStore } from '@lib/store/soundtrack-store';
import { ParseService } from '@lib/service/parse.service';
import { CommonService } from './common.service';
export declare class MelodyService {
    private soundtrackStore;
    private parseService;
    private commonService;
    constructor(soundtrackStore: SoundtrackStore, parseService: ParseService, commonService: CommonService);
    addSomeMelodies(): void;
    private addSoundtrack;
    private generateNotes;
}

可以看出,它包含对 @lib 路径映射的引用,而客户端应用程序中并不知道该路径映射。

我还尝试使用 baseUrl 属性作为解决方法,但这也无济于事,因为在安装库时未指定此 baseUrl 值。

为什么使用命令 npm run package 打包库时无法解析 paths 映射?

2个回答

您在 tsconfig.json 中建立的 paths 映射纯粹是 编译时 映射。 它对 TypeScript 编译器生成的代码没有影响。 这就是您在运行时失败的原因。这已 报告 给 TypeScript 项目,建议 tsc 应自动转换发出代码中的模块路径以符合 paths 建立的映射。 TS 开发人员回应称 tsc 工作正常,解决方案是配置一个模块加载器,在运行时执行类似于 paths 建立的映射。


根据您描述的情况,我认为 应该这样做。

我假设 midi-app 是一个不打算分发的测试应用程序。您应该能够继续使用您拥有的 paths 映射而不会出现任何问题。(您没有提到运行此应用程序的任何问题。因此,您的工具似乎已经解决了运行时问题。)

对于 midi-lib ,我将不再依赖 paths 建立的映射,而只使用相对路径。 这是一个库,供其他人使用。因此,任何在运行时(或捆绑时)修复模块名称映射的配置都必须由库的使用者处理。使用 Webpack 的使用者必须在其 Webpack 配置中添加配置以提供正确的映射。使用 Rollup 的使用者必须对 Rollup 执行相同的操作。使用 SystemJS 的使用者必须对 SystemJS 执行相同的操作,等等。

此外,所需的配置可能会变得复杂,具体取决于使用库的上下文。只要您的库是 唯一 需要将 @lib 映射到某个路径的库,必须添加到 Webpack(或 SystemJS 等)的映射就可以是 全局 的。模块捆绑器或模块加载器将始终用您的路径替换 @lib ,这很好,因为您的包是 唯一 需要替换 @lib 的包。但是,假设另一个库作者做了和你完全一样的事情,并且你的库的使用者也使用了另一个库。现在你遇到了这样一种情况:在某些情况下, @lib 必须映射到一个路径,而在其他情况下,必须映射到另一个路径。这 可以 配置,但需要更复杂的配置。

我重点关注了在捆绑期间或在运行时加载模块时解析模块的问题,但还有另一个问题。消费者还需要使用特殊配置来配置他们的 tsc 编译,因为 .d.ts 文件

如果你在代码中只使用相对路径,那么你的库的使用者就不必担心添加特殊配置来满足你的库的特殊需求。


可能会有一种适合您的案例的特殊情况。如果您的库将以 MIDI-LIB 发布 Midi-lib/* 的地图:

976053220

(请注意, @ 符号没有特殊含义涉及Typescript。代码>映射也: “@midi-project/midi-lib/*”:...

基本上,此处的目标是设置映射,以允许您您的项目中的导入模块 完全相同的项目的消费者将从其导入单个模块 。如果您的模块的消费者将带有 parseservice 带有 import {parseservice}从“ midi-lib/lib/service/parse.service.service”导入 'd要使用该模块时,请使用相同的 导入 。 (请注意,您是否告诉消费者直接导入该模块都没关系。 如果消费者要直接导入模块,则 他们将使用什么路径?) 因此,相同的路径在编译时和运行时(或捆绑时间)工作。 在编译时间, tsc 转换路径。在运行时间或捆绑时间,Node的模块分辨率算法(或可以遵循相同算法的工具,例如WebPack或lollup)转换路径。

用此路径保存多少键入,高度取决于您选择的名称以及您如何构建库。


从理论上讲,您可以在运行 ng构建 后可以跨越文件。由 ng构建 生成,并用它应该指向的实际路径替换 @lib 。此问题:

  1. 不仅是运行单个工具或在配置选项中翻转标志的问题。也许像 lollup 这样的工具可以转换JS文件,但是您现在需要了解其工作原理并为其编写配置。

  2. afaik,没有随时可用的工具可以在需要时转换 .d.ts 文件。您很可能必须编写自己的工具。

  3. 您还需要修补由Angular AOT编译器生成的AOT编译元数据,因为它还包含模块参考,并且这些参考文献由您的库消费者使用。 Afaik,没有这样的工具。所以在这里您也必须自己滚动。

  4. 如果新的Angular版本更改AOT汇编元数据的格式或添加需要修补的不同类型的元数据文件,则您的构建过程可能会破坏。我从经验中知道这一点:我有几个包裹是高度实验的角度应用。出于历史原因,它们完全绕过使用Angular CLI进行建筑。第4版的每个角度升级都在这些应用程序的构建过程中打破了一些东西。这通常与AOT汇编元数据的处理方式有关。

Louis
2019-07-31

正如其他人所说,typescript 不会修改您的 @app@lib 导入。我在尝试在库包中使用绝对路径时遇到了同样的问题。您需要使用 rollup 或类似工具准备要发布的库。

Rollup 有各种插件,我不会介绍完整的设置,但您需要的是一个可以 重写您的导入 的插件。这个插件看起来可以做到: https://github.com/bitshiftza/rollup-plugin-ts-paths

对于其余的汇总配置,您可能需要 rollup-plugin-node-resolve rollup-plugin-commonjs ,以及一个处理 typescript 的插件( rollup-plugin-typescript ),或者你可以选择较新的路线,即使用 babel。搜索一些指南,因为有一些用 typescript 编写的流行库使用 rollup 来准备其代码以进行打包(React 就是其中之一)。

祝你编码愉快。

Matthias
2019-08-01