如何在库项目中拥有绝对导入路径?
How to have absolute import paths in a library project?
我有一个库,其工作区包含两个项目,一个用于库本身,一个用于测试应用程序。
├── 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 模块导入,我在未知路径上遇到很多错误 Can't resolve '@lib/...'
如何表达库路径以便它们在客户端应用程序中公开?或者打包库时如何翻译库路径?
作为附带问题,我想知道为什么不以相反的方式进行扩展。为什么 tsconfig.json
文件不是在 projects/midi-lib/tsconfig.lib.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
然后安装依赖[=40=]
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
映射?
您在 tsconfig.json
中建立的 paths
映射纯粹是一个 编译时 映射。 它对 TypeScript 编译器生成的代码没有影响。 这就是为什么您在 运行 时失败的原因。这是 TypeScript 项目 reported 的东西,建议 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
的形式发布,那么您可以更改 paths
地图,这样您就有了 midi-lib/*
:
的地图,而不是 @lib/*
"midi-lib/*": ["projects/midi-lib/src/*"],
(请注意,就 TypeScript 而言,@
符号没有特殊含义。另请注意,如果您的包打算与范围一起安装,例如 @midi-project/midi-lib
那么您需要tsconfig.json
映射中的范围也是:"@midi-project/midi-lib/*": ...
)
基本上,这里的目标是设置一个映射,允许您在项目中导入模块其方式与项目的使用者从中导入单个模块的方式完全相同 .如果您的模块的使用者将使用 import { ParseService } from "midi-lib/lib/service/parse.service"
导入 ParseService
,那么在您自己的代码中,当您想使用该模块时,您将使用相同的 import
。 (请注意,您是否告诉消费者直接导入该模块并不重要。如果消费者直接导入该模块,那么什么路径会他们使用?)所以相同的路径在编译时和 运行 时(或捆绑时)起作用。 在编译时,tsc
转换路径。在 运行 时间或打包时间,Node 的模块解析算法(或可以遵循相同算法的工具,如 Webpack 或 Rollup)转换路径。
使用此选项可以节省多少输入在很大程度上取决于您选择的名称以及您构建库的方式。
理论上,您可以在 运行 ng build
之后执行一个步骤,该步骤将遍历 ng build
生成的文件并将模块名称中的 @lib
替换为它应该指向的实际路径。问题:
这不仅仅是 运行使用单个工具或在配置选项中翻转标志的问题。也许像 rollup
这样的工具可以转换 JS 文件,但您现在需要了解它的工作原理并为其编写配置。
AFAIK 没有现成的工具可以根据需要转换 .d.ts
文件。您很可能必须编写自己的工具。
您还需要修补由 Angular AOT 编译器生成的 AOT 编译元数据,因为它还包含模块引用,而这些引用由您的库的使用者使用。 AFAIK,不存在这样的工具。所以在这里你也必须自己动手。
如果新的 Angular 版本更改了 AOT 编译元数据的格式或添加了需要修补的不同类型的元数据文件,您的构建过程可能会中断。我从经验中知道这一点:我有几个高度实验性的软件包 Angular 应用程序。由于历史原因,他们完全绕过了使用 Angular CLI 进行构建。从版本 4 开始的每个 Angular 升级都会破坏这些应用程序的构建过程。它通常与 AOT 编译元数据的处理方式有关。
正如其他人所说,typescript 不会修改您的 @app
和 @lib
导入。我 运行 遇到了同样的问题,试图在库包中使用绝对路径。您需要做的是准备您的库以便使用 rollup 或类似的东西发布。
Rollup 有各种插件,我不会介绍完整的设置,但您需要的是一个插件,它将重写您的导入。这个插件看起来像它:https://github.com/bitshiftza/rollup-plugin-ts-paths
对于汇总配置的其余部分,您可能需要 rollup-plugin-node-resolve and rollup-plugin-commonjs, as well as a plugin to handle typescript (rollup-plugin-typescript),或者您可以使用 babel 的较新路线。搜索一些指南,因为有一些流行的用 typescript 编写的库使用 rollup 来准备打包代码(React,一个)。
编码愉快。
我有一个库,其工作区包含两个项目,一个用于库本身,一个用于测试应用程序。
├── 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 模块导入,我在未知路径上遇到很多错误 Can't resolve '@lib/...'
如何表达库路径以便它们在客户端应用程序中公开?或者打包库时如何翻译库路径?
作为附带问题,我想知道为什么不以相反的方式进行扩展。为什么 tsconfig.json
文件不是在 projects/midi-lib/tsconfig.lib.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
然后安装依赖[=40=]
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
映射?
您在 tsconfig.json
中建立的 paths
映射纯粹是一个 编译时 映射。 它对 TypeScript 编译器生成的代码没有影响。 这就是为什么您在 运行 时失败的原因。这是 TypeScript 项目 reported 的东西,建议 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
的形式发布,那么您可以更改 paths
地图,这样您就有了 midi-lib/*
:
@lib/*
"midi-lib/*": ["projects/midi-lib/src/*"],
(请注意,就 TypeScript 而言,@
符号没有特殊含义。另请注意,如果您的包打算与范围一起安装,例如 @midi-project/midi-lib
那么您需要tsconfig.json
映射中的范围也是:"@midi-project/midi-lib/*": ...
)
基本上,这里的目标是设置一个映射,允许您在项目中导入模块其方式与项目的使用者从中导入单个模块的方式完全相同 .如果您的模块的使用者将使用 import { ParseService } from "midi-lib/lib/service/parse.service"
导入 ParseService
,那么在您自己的代码中,当您想使用该模块时,您将使用相同的 import
。 (请注意,您是否告诉消费者直接导入该模块并不重要。如果消费者直接导入该模块,那么什么路径会他们使用?)所以相同的路径在编译时和 运行 时(或捆绑时)起作用。 在编译时,tsc
转换路径。在 运行 时间或打包时间,Node 的模块解析算法(或可以遵循相同算法的工具,如 Webpack 或 Rollup)转换路径。
使用此选项可以节省多少输入在很大程度上取决于您选择的名称以及您构建库的方式。
理论上,您可以在 运行 ng build
之后执行一个步骤,该步骤将遍历 ng build
生成的文件并将模块名称中的 @lib
替换为它应该指向的实际路径。问题:
这不仅仅是 运行使用单个工具或在配置选项中翻转标志的问题。也许像
rollup
这样的工具可以转换 JS 文件,但您现在需要了解它的工作原理并为其编写配置。AFAIK 没有现成的工具可以根据需要转换
.d.ts
文件。您很可能必须编写自己的工具。您还需要修补由 Angular AOT 编译器生成的 AOT 编译元数据,因为它还包含模块引用,而这些引用由您的库的使用者使用。 AFAIK,不存在这样的工具。所以在这里你也必须自己动手。
如果新的 Angular 版本更改了 AOT 编译元数据的格式或添加了需要修补的不同类型的元数据文件,您的构建过程可能会中断。我从经验中知道这一点:我有几个高度实验性的软件包 Angular 应用程序。由于历史原因,他们完全绕过了使用 Angular CLI 进行构建。从版本 4 开始的每个 Angular 升级都会破坏这些应用程序的构建过程。它通常与 AOT 编译元数据的处理方式有关。
正如其他人所说,typescript 不会修改您的 @app
和 @lib
导入。我 运行 遇到了同样的问题,试图在库包中使用绝对路径。您需要做的是准备您的库以便使用 rollup 或类似的东西发布。
Rollup 有各种插件,我不会介绍完整的设置,但您需要的是一个插件,它将重写您的导入。这个插件看起来像它:https://github.com/bitshiftza/rollup-plugin-ts-paths
对于汇总配置的其余部分,您可能需要 rollup-plugin-node-resolve and rollup-plugin-commonjs, as well as a plugin to handle typescript (rollup-plugin-typescript),或者您可以使用 babel 的较新路线。搜索一些指南,因为有一些流行的用 typescript 编写的库使用 rollup 来准备打包代码(React,一个)。
编码愉快。