Is it possible to generate Typescript interfaces from files with a webpack loader?

前端 未结 2 980

I am attempting to create a webpack loader that converts a file containing a description of API data structures into a set of TypeScript interfaces.

In my concrete case,

2条回答
  •  南笙
    南笙 (楼主)
    2021-02-19 22:39

    First off, kudos for providing an MCVE. This is a really interesting question. The code I worked with to put this answer together is based on said MCVE, and is available here.

    Missing File?

    This is a most unhelpful error message indeed. The file is clearly in that location, but TypeScript will refuse to load anything that doesn't have an acceptable extension.

    This error is essentially hiding the real error, which is

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

    This can be verified by hacking into typescript.js, and manually adding the file. It's ugly, as detective work often is (starts at line 95141 in v2.6.1):

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

    Conceptually, you're just passing a string between loaders, but it turns out the file name is important here.

    A possible fix

    I didn't see a way to do this with awesome-typescript-loader, but if you're willing to use ts-loader instead, you can certainly generate TypeScript from files with arbitrary extensions, compile that TypeScript, and inject it into your output.js.

    ts-loader has an appendTsSuffixTo option, that can be used to work around the well-known file extension pain. Your webpack config might look something like this if you went that route:

    rules: [
      {
        test: /\.api$/,
        exclude: /node_modules/,
        use: [
          { loader: "ts-loader" },
          { loader: "my-own-loader" }
        ]
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: "ts-loader",
        options: {
          appendTsSuffixTo: [/\.api$/]
        }
      }
    ]
    

    Note on interfaces and DX

    Interfaces are erased by the compiler. This can be demonstrated by running tsc against something like this

    interface DummyContent {
        name: string;
        age?: number;
    }
    

    vs. this

    interface DummyContent {
        name: string;
        age?: number;
    }
    
    class DummyClass {
        printMessage = () => {
            console.log("message");
        }
    }
    
    var dummy = new DummyClass();
    dummy.printMessage();
    

    In order to provide a nice developer experience, you may need to write these interfaces to a file in the dev environment only. You don't need to write them out for a production build, and you don't need (or want) to check them into version control.

    Developers probably need to have them written out so their IDE has something to sink its teeth into. You might add *.api.ts to .gitignore, and keep them out of the repository, but I suspect they'll need to exist in the developers' workspaces.

    For example, in my sample repo, a new developer would have to run npm install (as usual) and npm run build (to generate the interfaces in their local environment) to get rid of all their red squigglies.

提交回复
热议问题