The web is a great way to render an app's interface on any platform, but you often need to do the same action differently based on what platform you're on. WebView callbacks are message-based on iOS, but are direct method calls on Android.
Doing checks all over your code is ugly, so you might decide to export a different object depending on the platform:
interface PlatformApi {
getIdToken: () => Promise<string>;
getDeviceName: () => string;
}
function getPlatformImplementation(): PlatformApi {
if (onAndroid) {
return {
getIdToken: async () => {
// Android implementation
},
getDeviceName: () => {
// Android implementation
}
}
}
if (onIOS) {
return {
getIdToken: async () => {
// iOS implementation
},
getDeviceName: () => {
// iOS implementation
}
}
}
// ...
}
export const platform: PlatformApi = getPlatformImplementation();
This is better and allows anyone to make a platform call by calling platform.getDeviceName()
.
But it's still not ideal. You're bundling up code for all platforms, leaving some useless JS weight that Webpack doesn't know how to tree-shake away.
Module replacement
Our savior here is Webpack's NormalModuleReplacementPlugin
. You can make a "stub" module that everything imports and calls:
MyAppPlatform.ts:
export const platform: PlatformApi = {
getIdToken: async (): Promise<string> => "",
getDeviceName: (): string => "",
}
But then in different webpack configs, do a different module replacement for each platform. For example, with Android, first make a module that exports an object that implements the shared PlatformApi
interface with Android-specific code:
AndroidPlatform.ts:
export const platform: PlatformApi = {
getIdToken: async (): Promise<string> => {
// Android implementation
},
getDeviceName: (): string => {
// Android implementation
},
}
Then tell Webpack to swap out the stub module with your platform module:
webpack.config.js
const webpack = require('webpack');
// Inside the webpack config object:
plugins: [
new webpack.NormalModuleReplacementPlugin(
/\/MyAppPlatform$/,
(resource) => {
resource.request = resource.request.replace(
/MyAppPlatform$/,
"AndroidPlatform");
}),
],
You have to do the fancy replacement function because people might be referencing your module from anywhere. The regex is looking for any import that ends in /MyAppPlatform
, then replacing that last section with the AndroidPlatform
module that's right beside it. It's a good idea to add your app name to your stub modules, so you don't accidentally replace some module deep inside a library you're using. This happened to me!
Anyway, with the Webpack module replacement approach to dependency injection, you get:
- Type safety because all platforms and callers have to conform to the same interface
- Efficient bundling because every platform is built with only its own code
- Easy use of the module with calls like
platform.getDeviceName()
Top comments (0)