kiota icon indicating copy to clipboard operation
kiota copied to clipboard

ES6 imports breaking client in Expo

Open BrianUribe6 opened this issue 1 year ago • 7 comments

What are you generating using Kiota, clients or plugins?

API Client/SDK

In what context or format are you using Kiota?

Nuget tool

Client library/SDK language

TypeScript

Describe the bug

Relative Imports now include a "index.js" suffix which seems to be causing issues when bundling in React native/expo.

Possible related issue #4950

Imports are currently generated as 
// @ts-ignore
import { DocumentsRequestBuilderNavigationMetadata, type DocumentsRequestBuilder } from './documents/index.js';
// @ts-ignore
import { ProfilesRequestBuilderNavigationMetadata, ProfilesRequestBuilderRequestsMetadata, type ProfilesRequestBuilder } from './profiles/index.js';
// @ts-ignore
import { ThreadsRequestBuilderNavigationMetadata, type ThreadsRequestBuilder } from './threads/index.js';

Example error message by Expo

Android Bundling failed 63ms C:\Users\[REDACTED]\node_modules\expo-router\entry.js (1 module)
Unable to resolve "../../../models/index.js" from "lib\api\v1\auth\register\index.ts"

Expected behavior

Imports should be generated as

// @ts-ignore
import { DocumentsRequestBuilderNavigationMetadata, type DocumentsRequestBuilder } from './documents';
// @ts-ignore
import { ProfilesRequestBuilderNavigationMetadata, ProfilesRequestBuilderRequestsMetadata, type ProfilesRequestBuilder } from './profiles';
// @ts-ignore
import { ThreadsRequestBuilderNavigationMetadata, type ThreadsRequestBuilder } from './threads';

Or provide an additional flag to override the default behaviour.

How to reproduce

Generate any typescript client with kiota generate

Open API description file

No response

Kiota Version

1.18.0+5c6b5d0ef23865ba2f9d9f0b9fe4b944cf26b1ec

Latest Kiota version known to work for scenario above?(Not required)

1.15

Known Workarounds

  • Search and replace to remove "/index.js".
  • Downgrade to version 1.15

Configuration

No response

Debug output

Click to expand log ```
</details>


### Other information

This is most likely due to  #4815 

BrianUribe6 avatar Sep 27 '24 15:09 BrianUribe6

Thanks for raising this @BrianUribe6

To confirm, any chance you've updated your project to use the config as outlined at this link? https://learn.microsoft.com/en-us/openapi/kiota/quickstarts/typescript#project-configuration

andrueastman avatar Oct 01 '24 13:10 andrueastman

Yes, some of the settings are already defined in "expo/tsconfig.base", but to make sure I included them again. I'd like to re-emphasize that by completely removing "/index" from every import the client works as expected even with expo's default tsconfig.

tsconfig.json

{
	"extends": "expo/tsconfig.base",
	"compilerOptions": {
		"strict": true,
		"paths": {
			"@/*": ["./*"]
		},
		"lib": ["DOM", "ESNext", "ES2015"],
		"esModuleInterop": true,
		"forceConsistentCasingInFileNames": true,
		"moduleResolution": "NodeNext",
		"module": "NodeNext"
	},
	"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
}

expo/tsconfig.base

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Expo",

  "compilerOptions": {
    "allowJs": true,
    "esModuleInterop": true,
    "jsx": "react-native",
    "lib": ["DOM", "ESNext"],
    "moduleResolution": "node",
    "noEmit": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "target": "ESNext"
  },

  "exclude": ["node_modules", "babel.config.js", "metro.config.js", "jest.config.js"]
}

BrianUribe6 avatar Oct 01 '24 19:10 BrianUribe6

for anybody following along this conversation, a PR was started at #6173 but we still need more evidence this would be a positive change. Please join there.

baywet avatar Feb 20 '25 14:02 baywet

We still need this, right now we are using this workaround: fix-generated-api-client.js

// eslint-disable-next-line @typescript-eslint/no-require-imports
const fs = require("fs");
// eslint-disable-next-line @typescript-eslint/no-require-imports
const path = require("path");

const directory = "./lib/api-client";
const search = "/index.js';";
const replace = "/index';";

function processDirectory(dir) {
  const files = fs.readdirSync(dir);

  files.forEach((file) => {
    const fullPath = path.join(dir, file);
    const stat = fs.statSync(fullPath);

    if (stat.isDirectory()) {
      processDirectory(fullPath);
    } else if (stat.isFile()) {
      const content = fs.readFileSync(fullPath, "utf-8");
      if (content.includes(search)) {
        const updatedContent = content.replace(
          new RegExp(search, "g"),
          replace
        );
        fs.writeFileSync(fullPath, updatedContent, "utf-8");
        console.log(`Updated: ${fullPath}`);
      }
    }
  });
}

processDirectory(directory);
console.log("Replacement complete.");

and these commands in package.json:

    "download-openapi": "curl https://someurl/openapi/v1.json -o openapi.json",
    "generate-client": "kiota generate -l typescript -d ./openapi.json -c ApiClient -o ./lib/api-client --clean-output && node fix-generated-api-client.js",
    "update-client": "npm run download-openapi && npm run generate-client",

HavenDV avatar Apr 14 '25 09:04 HavenDV

Hey everyone,

We had a PR opened at #6173 where I had left a bunch of questions which were never answered.

Here is the reason why I originally added index.js in the es6 module change

You cannot omit the file extension or the index.js file name. This behavior has been inherited by Node's ESM implementation, but it is not a part of the ECMAScript specification.

There's technically a segment of the population that might transpile but not bundle the end result. In which case, if the backend is not smart enough to map the request to index.js, things will break. The question then becomes: is that population bigger or smaller than the rest of the ecosystem which does NOT want index.js?

Also, since the path is valid, I'd argue that React native/expo should be able to handle it properly.

I think a couple of additional data points would help us make an educated decision here:

  • are there other scenarios in which the index.js breaks imports?
  • do react native and expo have issues about that? have we tried creating one?
  • can we find authoritative guidance that'd contradict MDN?

Getting answers to those questions would help make a decision with a high degree of confidence here.

baywet avatar Apr 14 '25 19:04 baywet

@baywet as it is Kiota may not work on any bundled front-end library/framework just because of a file extension

This is a barebones example where I just generated a client using the sample Pet Store OpenAPI Spec and tried instantiating it https://codesandbox.io/p/sandbox/y6m7v7

As you can see it is not even usable because of the imports.

Since the time I submitted this issue my team and I have moved to other generators which are more suitable for typescript such as openapi-typescript. I think Kiota should take advantage of the rich type system that TypeScript has instead of adding anything in the runtime.

BrianUribe6 avatar Apr 17 '25 14:04 BrianUribe6

Any news on this? It breaks with NextJs v16 out of the box as well. Since the tool produces .ts files why does it add .js as extensions anyway? Should it not use .ts consistently at least?

rudfoss-rr avatar Nov 12 '25 11:11 rudfoss-rr