是否可以使用webpack加载器从文件生成Typescript接口?

zd287kbt  于 2023-10-19  发布在  Webpack
关注(0)|答案(4)|浏览(143)

我试图创建一个webpack加载器,将包含API数据结构描述的文件转换为一组TypeScript接口。
在我的具体案例中,文件是JSON,但这最终应该是无关紧要的-文件只是描述Web应用程序后端和前端之间交互的共享数据源。在下面的MCVE中,您可以看到JSON文件包含一个空对象,以强调文件的类型和内容与问题无关。
我目前的尝试报告了两个错误(我假设第二个错误是由第一个错误引起的):

  1. [at-loader]: Child process failed to process the request: Error: Could not find file: '/private/tmp/ts-loader/example.api'.
  1. ERROR in ./example.api
  2. Module build failed: Error: Final loader didn't return a Buffer or String

如何使用webpack loader生成TypeScript代码?

package.json

  1. {
  2. "name": "so-example",
  3. "version": "1.0.0",
  4. "main": "index.js",
  5. "license": "MIT",
  6. "scripts": {
  7. "build": "webpack"
  8. },
  9. "dependencies": {
  10. "awesome-typescript-loader": "^3.2.3",
  11. "typescript": "^2.6.1",
  12. "webpack": "^3.8.1"
  13. }
  14. }

webpack.js

  1. const path = require('path');
  2. module.exports = {
  3. entry: './index.ts',
  4. output: {
  5. filename: 'output.js',
  6. },
  7. resolveLoader: {
  8. alias: {
  9. 'my-own-loader': path.resolve(__dirname, "my-own-loader.js"),
  10. },
  11. },
  12. module: {
  13. rules: [
  14. {
  15. test: /\.api$/,
  16. exclude: /node_modules/,
  17. use: ["awesome-typescript-loader", "my-own-loader"],
  18. },
  19. {
  20. test: /\.tsx?$/,
  21. exclude: /node_modules/,
  22. loader: "awesome-typescript-loader",
  23. },
  24. ]
  25. },
  26. };

my-own-loader.js

  1. module.exports = function(source) {
  2. return `
  3. interface DummyContent {
  4. name: string;
  5. age?: number;
  6. }
  7. `;
  8. };

index.ts

  1. import * as foo from './example';
  2. console.log(foo);

example.API

  1. {}

我认识到还有其他的代码生成技术。例如,我可以使用一些构建工具将JSON文件转换为TypeScript并将其检入。我在寻找一个更有活力的解决方案。
my-own-loader.js不会导出json,而是字符串。
这是正确的,就像加载图像文件并不总是导出二进制数据,但有时会输出表示图像元数据的JavaScript数据结构。
为什么需要从json文件定义typescript接口?这些接口会被用于类型脚本编译吗?
是。我想导入一个描述我的API数据结构的文件,并自动生成相应的TypeScript接口。拥有一个共享文件允许前端和后端总是同意将存在什么数据。

pdtvr36n

pdtvr36n1#

首先,提供MCVE的荣誉。这是一个非常有趣的问题。我使用的代码将这个答案放在一起是基于MCVE和is available here

文件丢失?

这确实是一个最无益的错误信息。该文件显然位于该位置,但TypeScript将拒绝加载任何不具有可接受扩展名的内容。
这个错误本质上隐藏了真实的错误,

  1. TS6054: File 'c:/path/to/project/example.api' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts', '.js', '.jsx'.

这可以通过入侵typescript.js并手动添加文件来验证。它很难看,就像侦探工作经常做的那样(从v2.6.1中的第95141行开始):

  1. for (var _i = 0, rootFileNames_1 = rootFileNames; _i < rootFileNames_1.length; _i++) {
  2. var fileName = rootFileNames_1[_i];
  3. this.createEntry(fileName, ts.toPath(fileName, this.currentDirectory, getCanonicalFileName));
  4. }
  5. this.createEntry("c:/path/to/project/example.api", ts.toPath("c:/path/to/project/example.api", this.currentDirectory, getCanonicalFileName));

从概念上讲,您只是在加载器之间传递一个字符串,但事实证明文件名**在这里很重要。

可能的解决办法

我没有看到用awesome-typescript-loader实现这一点的方法,但是如果你愿意使用ts-loader,你当然可以从具有任意扩展名的文件中生成TypeScript,编译该TypeScript,然后将其注入到你的output.js中。
ts-loader有一个appendTsSuffixTo选项,可以用来解决众所周知的file extension pain。你的webpack配置可能看起来像like this,如果你走的是这个路线:

  1. rules: [
  2. {
  3. test: /\.api$/,
  4. exclude: /node_modules/,
  5. use: [
  6. { loader: "ts-loader" },
  7. { loader: "my-own-loader" }
  8. ]
  9. },
  10. {
  11. test: /\.tsx?$/,
  12. exclude: /node_modules/,
  13. loader: "ts-loader",
  14. options: {
  15. appendTsSuffixTo: [/\.api$/]
  16. }
  17. }
  18. ]

接口和DX注意事项

接口被编译器擦除。这可以通过对类似this的对象运行tsc来演示

  1. interface DummyContent {
  2. name: string;
  3. age?: number;
  4. }

this相比

  1. interface DummyContent {
  2. name: string;
  3. age?: number;
  4. }
  5. class DummyClass {
  6. printMessage = () => {
  7. console.log("message");
  8. }
  9. }
  10. var dummy = new DummyClass();
  11. dummy.printMessage();

为了提供良好的开发人员体验,您可能只需要将这些接口写入开发环境中的文件。您不需要将它们写出来用于生产构建,也不需要(或不想)将它们签入版本控制。
开发人员可能需要把它们写出来,这样他们的IDE就有了可以使用的东西。您可以将*.api.ts添加到.gitignore,并将它们保留在存储库之外,但我怀疑它们需要存在于开发人员的目录中。
例如,在我的示例代码库中,一个新的开发人员必须运行npm install(像往常一样)npm run build(在本地环境中生成接口)来消除所有红色的波浪线。

展开查看全部
vatpfxk5

vatpfxk52#

我知道这是一个老问题,但我最近遇到了类似的问题,所以它仍然相关。
一种解决方法是将declare module "*.api";添加到index.d.ts文件中。但这有一个失去类型安全性的巨大缺点,因为速记模块声明中的所有内容都是any类型。所以你也可以不生成TypeScript接口来开始。
我设法解决了它使用一些巫毒教,我不完全理解,但它似乎是工作。

package.json

请注意,我在撰写本文时使用的是所有内容的最新版本,并且使用ts-loader而不是弃用的awesome-typescript-loader

  1. {
  2. "name": "so-example",
  3. "version": "1.0.0",
  4. "main": "index.js",
  5. "license": "MIT",
  6. "scripts": {
  7. "build": "webpack"
  8. },
  9. "dependencies": {
  10. "ts-loader": "^9.2.8",
  11. "typescript": "^4.6.3",
  12. "webpack": "^5.70.0",
  13. "webpack-cli": "^4.9.2"
  14. }
  15. }

webpack.config.ts

appendTsSuffixTo的需求是有据可查的,这是必要的,但还不够。
我发现了几乎没有文档记录的ts-loaderresolveModuleName选项。通过浏览ts-loader代码并观察函数的输入和输出,我设法拼凑出了您下面看到的自定义解析器函数。我们必须将.ts扩展附加到resolvedFileName,以欺骗TypeScript编译器接受该文件。
注意,我们需要对两个规则使用ts-loader配置,因此我将其提取到一个变量中。

  1. const path = require('path');
  2. const tsLoader = {
  3. loader: "ts-loader",
  4. options: {
  5. appendTsSuffixTo: [/\.api$/],
  6. resolveModuleName: (moduleName, containingFile, compilerOptions, compilerHost, parentResolver) => {
  7. if (/\.api$/.test(moduleName)) {
  8. const fileName = path.resolve(path.dirname(containingFile), moduleName);
  9. return {
  10. resolvedModule: {
  11. originalFileName: fileName,
  12. resolvedFileName: fileName + '.ts',
  13. resolvedModule: undefined,
  14. isExternalLibraryImport: false,
  15. },
  16. failedLookupLocations: [],
  17. };
  18. }
  19. return parentResolver(moduleName, containingFile, compilerOptions, compilerHost);
  20. },
  21. },
  22. };
  23. module.exports = {
  24. entry: './index.ts',
  25. output: {
  26. filename: 'output.js',
  27. },
  28. resolveLoader: {
  29. alias: {
  30. 'my-own-loader': path.resolve(__dirname, "my-own-loader.js"),
  31. },
  32. },
  33. module: {
  34. rules: [
  35. {
  36. test: /\.api$/,
  37. exclude: /node_modules/,
  38. use: [tsLoader, "my-own-loader"],
  39. },
  40. {
  41. test: /\.tsx?$/,
  42. exclude: /node_modules/,
  43. use: [tsLoader],
  44. },
  45. ]
  46. },
  47. };

tsconfig.json

我不知道为什么这是必需的,但没有它,我得到TS18002: The 'files' list in config file 'tsconfig.json' is empty.

  1. {}

my-own-loader.js

我添加了一个值来表明我们保留了类型安全。

  1. module.exports = function(source) {
  2. return `
  3. export interface DummyContent {
  4. name: string;
  5. age?: number;
  6. }
  7. export const DUMMY_VALUE: DummyContent = {
  8. name: "Jon Snow",
  9. age: 24,
  10. }
  11. `;
  12. };

index.ts

请注意,我导入的文件包括.api扩展名。也许可以更改resolveModuleName函数,使其在没有扩展的情况下工作,但我没有这样做。实际上,我喜欢看到这里的扩展作为一个线索,一些特别的事情正在发生。

  1. import { DUMMY_VALUE } from './example.api';
  2. console.log(DUMMY_VALUE.name);
  3. console.log(DUMMY_VALUE.youKnowNothing); // Does not compile
展开查看全部
arknldoa

arknldoa3#

如果你的API遵循swagger规范,你可以使用npm包swagger-ts-generator来生成TypeScript文件:

Swagger TypeScript代码生成器

Node模块基于Swagger v2格式的Webapi Meta数据为Angular(2及以上)生成TypeScript代码。
基本上,你给予它一个swagger URL,它就会生成TypeScript。这些示例是针对Gulp的,但它们应该很好地移植到WebPack:

  1. var swagger = {
  2. url: 'http://petstore.swagger.io/v2/swagger.json',
  3. //url: 'http://127.0.0.1/ZIB.WebApi.v2/swagger/docs/v1',
  4. swaggerFile: folders.swaggerFolder + files.swaggerJson,
  5. swaggerFolder: folders.swaggerFolder,
  6. swaggerTSGeneratorOptions: {
  7. modelFolder: folders.srcWebapiFolder,
  8. enumTSFile: folders.srcWebapiFolder + 'enums.ts',
  9. enumLanguageFiles: [
  10. folders.srcLanguagesFolder + 'nl.json',
  11. folders.srcLanguagesFolder + 'en.json',
  12. ],
  13. modelModuleName: 'webapi.models',
  14. enumModuleName: 'webapi.enums',
  15. enumRef: './enums',
  16. namespacePrefixesToRemove: [
  17. ],
  18. typeNameSuffixesToRemove: [
  19. ]
  20. }
  21. }
展开查看全部
mfuanj7w

mfuanj7w4#

我从Mike Patricks版本创建了一个分叉,它有一些优点。
从加载器中导出“抽象类”而不是类型或接口。
更好的开发者体验

  • 不需要导入一些具体的类/变量
  • 将给予正确的打字第一次建立
  • 需要创建globals.d.ts

https://github.com/gwynjudd/ts-generating-loader

相关问题