We are using TypeScript to write our React Native application and encountered an issue with platform specific extensions provided by RN.
React Native will detect when a file has a .ios. or .android. extension and load the right file for each platform when requiring them from other components.
For example, you can have these files in your project:
BigButton.ios.js
BigButton.android.js
With this setup, you can just require the files from a different component without paying attention to the platform in which the app will run.
import BigButton from './components/BigButton';
When we use tsc to compile our code, it won't recognize the android or ios platform extension. What about have a customPlatform
option that's similar to allowJs
that auto prepend the platform name to supportedTypeScriptExtensions
?
function getSupportedExtensions(options) {
if (options && options.customPlatform) {
return ts.supportedTypeScriptExtensions.concat(ts.supportedTypeScriptExtensions.map((ext) => `.${options.customPlatform}${ext}`))
}
return options && options.allowJs ? allSupportedExtensions : ts.supportedTypeScriptExtensions;
}
I think this problem might be better solved with the glob pattern support in tsconfig.json (https://github.com/Microsoft/TypeScript/pull/5980). Modifying getSupportedExtensions
wouldn't scale.
Makes sense. Love to see that change going in soon. Currently blocking our RN project.
Do not think globs will help here. the suggesion in the OP seems like a valid one. we should allow additional lookup locations, so that users can specify that, so "additionalExtensions" : [ "ios.ts", "ios.tsx", "android.ts", "android.tsx" ]
.
the workaround for now, would be create a .d.ts file next to your platform specific modules, something like:
/// components/BigButton.d.ts
export * from "./BigButton.ios";
this way your import for ./components/BigButton
will resolve to ./components/BigButton.d.ts
at design time getting you the shape of the module. and at runtime, it should do the right thing and bind to the platform specific version. there should not be any additional changes required.
@mhegazy If the exported types in BigButton.android.tsx
diverges accidentally from BigButton.ios.tsx
, then this won't be picked up at compile time, correct?
Is it possible to keep full compile time type checking?
The compiler does not know that BigButton.android.tsx
and BigButton.ios.tsx
are two implementation of the same module, and thus, should have the same shape. so they could diverge.
One possible solution here is to have modules implement interfaces, as previously suggested in https://github.com/Microsoft/TypeScript/issues/420. This will force the compiler to check the shape of the module against an interface.
here is another workaround to get it to type check today, courtesy of @RyanCavanaugh:
import * as ios from "./BigButton.ios";
import * as android from "./BigButton.ios";
declare var _test: typeof ios;
declare var _test: typeof android;
/// export to get the shape of the module
export * from "./BigButton.ios";
now if ios
and android
ever diverge, you will get an error on the redeclaration of _test
. no code will be generated for this.
Recommendations here is to use path mapping support, see https://github.com/Microsoft/TypeScript-Handbook/blob/release-2.0/pages/Module%20Resolution.md#path-mapping
so your tsconfig.json should contain something like:
{
"compilerOptions": {
"paths": {
"*": ["*", "*.ios", "*.android"]
}
}
}
this will tell the compiler when resolving an import to BigButton
to look at:
For checking that the different implementation for the modules all have the same shape is tracked by #420
closing in favor of #420
@mhegazy How can I use the paths
compiler option? It isn't documented in the Compiler Options documentation.
Paths is a TS 2.0 feature. documentation is available at: https://github.com/Microsoft/TypeScript-Handbook/blob/release-2.0/pages/Module%20Resolution.md#path-mapping
should be merged in main documentation once 2.0 ships out.
@mhegazy As far as I can tell (when testing it) the path mapping solution only applies to non-relative imports. So I cannot import A from '../otherfolder/stuff'
. Am I missing something obvious here? Any help is appreciated
Having the same issue as @sondremare. Is there an updated recommended way to deal with this particular RN feature?
Can't believe this issue still persists. The idea of allowing custom extension, or more specifically a _filename suffix support_, with ts
or tsx
at the end as in file.suffix.ts
would be a great plus to the language.
@mhegazy This was closed in favour of https://github.com/Microsoft/TypeScript/issues/420, but as @sondremare mentioned the "paths"
option doesn't work for relative imports, and #420 doesn't cover that. Any plans to address it?
Any plans to address it?
it is not clear what path mapping means with a relative path. so no plans really.,
i think that at this point the "umbrella" solution is the way to go.
On our index files we should tell to the compiler which components are needed at compile time.
having something like this on my index file (that is platform specific) for me makes sense:
index.ios.ts:
export * from "./BigButton.ios";
index.android.ts:
export * from "./BigButton.android";
Note that with webpack resolve.extensions
this isn't necessarily a RN-specific issue, you could, for example have .ltr.tsx
and .rtl.tsx
variants, or .client.ts
and .server.ts
for isomorphic apps, etc..., essentially any time you may have multiple output bundles that should be using different code.
Most helpful comment
@mhegazy As far as I can tell (when testing it) the path mapping solution only applies to non-relative imports. So I cannot
import A from '../otherfolder/stuff'
. Am I missing something obvious here? Any help is appreciated