-
Notifications
You must be signed in to change notification settings - Fork 94
feat(macos): add macOS support for config plugins #2160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// @ts-check | ||
const { withMod } = require("@expo/config-plugins"); | ||
|
||
/** | ||
* @typedef {import("@expo/config-plugins").ExportedConfig} ExportedConfig | ||
* @typedef {import("@expo/config-plugins").ExportedConfigWithProps} ExportedConfigWithProps | ||
* @typedef {import("@expo/config-plugins").Mod} Mod | ||
* @typedef {import("@expo/config-plugins").ModConfig} ModConfig | ||
* @typedef {ExportedConfigWithProps & { macos?: { infoPlist?: Record<string, unknown> }}} ExportedConfigWithPropsMac | ||
*/ | ||
|
||
const macosPlatform = /** @type {keyof ModConfig} */ ("macos"); | ||
|
||
/** | ||
* Provides the `ReactNativeHost` file for modification. | ||
* @param {ExportedConfig} config Exported config | ||
* @param {Mod} action Method to run on the mod when the config is compiled | ||
* @returns {ExportedConfig} Modified config | ||
*/ | ||
function withReactNativeHost(config, action) { | ||
return withMod(config, { | ||
platform: macosPlatform, | ||
mod: "reactNativeHost", | ||
action, | ||
}); | ||
} | ||
|
||
/** | ||
* Provides the `AppDelegate` file for modification. | ||
* @see {@link https://github.com/expo/expo/blob/sdk-51/packages/%40expo/config-plugins/src/plugins/ios-plugins.ts#L101} | ||
* @param {ExportedConfig} config Exported config | ||
* @param {Mod} action Method to run on the mod when the config is compiled | ||
* @returns {ExportedConfig} Modified config | ||
*/ | ||
function withAppDelegate(config, action) { | ||
return withMod(config, { | ||
platform: macosPlatform, | ||
mod: "appDelegate", | ||
action, | ||
}); | ||
} | ||
|
||
/** | ||
* Provides the `Info.plist` file for modification. | ||
* @see {@link https://github.com/expo/expo/blob/sdk-51/packages/%40expo/config-plugins/src/plugins/ios-plugins.ts#L116} | ||
* @param {ExportedConfig} config Exported config | ||
* @param {Mod} action Method to run on the mod when the config is compiled | ||
* @returns {ExportedConfig} Modified config | ||
*/ | ||
function withInfoPlist(config, action) { | ||
return withMod(config, { | ||
platform: macosPlatform, | ||
mod: "infoPlist", | ||
async action(cfg) { | ||
/** @type {ExportedConfigWithPropsMac} */ | ||
const config = await action(cfg); | ||
if (!config.macos) { | ||
config.macos = {}; | ||
} | ||
config.macos.infoPlist = config.modResults; | ||
return config; | ||
}, | ||
}); | ||
} | ||
|
||
/** | ||
* Provides the main `.xcodeproj` for modification. | ||
* @see {@link https://github.com/expo/expo/blob/sdk-51/packages/%40expo/config-plugins/src/plugins/ios-plugins.ts#L173} | ||
* @param {ExportedConfig} config Exported config | ||
* @param {Mod} action Method to run on the mod when the config is compiled | ||
* @returns {ExportedConfig} Modified config | ||
*/ | ||
function withXcodeProject(config, action) { | ||
return withMod(config, { | ||
platform: macosPlatform, | ||
mod: "xcodeproj", | ||
action, | ||
}); | ||
} | ||
|
||
exports.withAppDelegate = withAppDelegate; | ||
exports.withInfoPlist = withInfoPlist; | ||
exports.withXcodeProject = withXcodeProject; | ||
exports.withReactNativeHost = withReactNativeHost; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// @ts-check | ||
import { createRequire } from "node:module"; | ||
import * as path from "node:path"; | ||
import { BaseMods } from "../ExpoConfigPlugins.mjs"; | ||
import { makeFilePathModifier, makeNullProvider } from "../provider.mjs"; | ||
|
||
const require = createRequire(import.meta.url); | ||
|
||
/** | ||
* @param {import("../types.js").CustomModProvider} modifyFilePath | ||
* @returns {import("../types.js").IosModFileProviders} | ||
*/ | ||
export function createModFileProviders(modifyFilePath) { | ||
const modifyReactNativeHostFilePath = makeFilePathModifier( | ||
path.dirname(require.resolve("@rnx-kit/react-native-host/package.json")) | ||
); | ||
|
||
const nullProvider = makeNullProvider(); | ||
|
||
// https://github.com/expo/expo/blob/sdk-51/packages/%40expo/config-plugins/src/plugins/withIosBaseMods.ts | ||
const expoProviders = BaseMods.getIosModFileProviders(); | ||
|
||
/** @type {import("../types.js").IosModFileProviders} */ | ||
const defaultProviders = { | ||
dangerous: expoProviders.dangerous, | ||
finalized: expoProviders.finalized, | ||
appDelegate: modifyFilePath( | ||
expoProviders.appDelegate, | ||
"ReactTestApp/AppDelegate.swift" | ||
), | ||
expoPlist: nullProvider, | ||
xcodeproj: modifyFilePath( | ||
expoProviders.xcodeproj, | ||
"ReactTestApp.xcodeproj/project.pbxproj" | ||
), | ||
infoPlist: modifyFilePath(expoProviders.infoPlist, "Info.plist"), | ||
entitlements: nullProvider, | ||
podfile: makeNullProvider({ | ||
path: "", | ||
language: /** @type {const} */ ("rb"), | ||
contents: "", | ||
}), | ||
podfileProperties: makeNullProvider(), | ||
}; | ||
|
||
// `@rnx-kit/react-native-host` files | ||
defaultProviders["reactNativeHost"] = modifyReactNativeHostFilePath( | ||
expoProviders.appDelegate, | ||
"cocoa/ReactNativeHost.mm" | ||
); | ||
|
||
return defaultProviders; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
import { BaseMods, evalModsAsync } from "../ExpoConfigPlugins.mjs"; | ||
import { getAndroidModFileProviders } from "./withAndroidBaseMods.mjs"; | ||
import { getIosModFileProviders } from "./withIosBaseMods.mjs"; | ||
import { getMacOsModFileProviders } from "./withMacOsBaseMods.mjs"; | ||
|
||
/** @type {import("@expo/config-plugins").withDefaultBaseMods} */ | ||
export const withDefaultBaseMods = (config, props) => { | ||
|
@@ -13,6 +14,12 @@ export const withDefaultBaseMods = (config, props) => { | |
...props, | ||
providers: getAndroidModFileProviders(), | ||
}); | ||
config = BaseMods.withGeneratedBaseMods(config, { | ||
...props, | ||
// @ts-expect-error `macos` is not assignable to type `android | ios` | ||
platform: "macos", | ||
Comment on lines
+19
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, Expo config plugins hard-code the string "ios" all over the place, consequentially searching in the I opened a big PR last weekend to add macOS support to Expo config plugins, but I am not optimistic about it being reviewed anytime soon. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand this correctly, this part just registers what's available for the |
||
providers: getMacOsModFileProviders(), | ||
}); | ||
return config; | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,17 @@ | ||
// @ts-check | ||
import { createRequire } from "node:module"; | ||
import * as path from "node:path"; | ||
import { createModFileProviders } from "./cocoaBaseMods.mjs"; | ||
import { BaseMods } from "../ExpoConfigPlugins.mjs"; | ||
import { makeFilePathModifier, makeNullProvider } from "../provider.mjs"; | ||
import { makeFilePathModifier } from "../provider.mjs"; | ||
|
||
const modifyFilePath = makeFilePathModifier("node_modules/.generated/ios"); | ||
|
||
const require = createRequire(import.meta.url); | ||
const modifyReactNativeHostFilePath = makeFilePathModifier( | ||
path.dirname(require.resolve("@rnx-kit/react-native-host/package.json")) | ||
); | ||
|
||
const nullProvider = makeNullProvider(); | ||
|
||
// https://github.com/expo/expo/blob/93cd0503117d5a25f8b80ed7b30ec5bed3a67c24/packages/@expo/config-plugins/src/plugins/withIosBaseMods.ts | ||
const expoProviders = BaseMods.getIosModFileProviders(); | ||
|
||
/** @type {typeof expoProviders & Record<string, unknown>} */ | ||
const defaultProviders = { | ||
dangerous: expoProviders.dangerous, | ||
finalized: expoProviders.finalized, | ||
appDelegate: modifyFilePath( | ||
expoProviders.appDelegate, | ||
"ReactTestApp/AppDelegate.swift" | ||
), | ||
expoPlist: nullProvider, | ||
xcodeproj: modifyFilePath( | ||
expoProviders.xcodeproj, | ||
"ReactTestApp.xcodeproj/project.pbxproj" | ||
), | ||
infoPlist: modifyFilePath(expoProviders.infoPlist, "Info.plist"), | ||
entitlements: nullProvider, | ||
podfile: makeNullProvider({ | ||
path: "", | ||
language: /** @type {const} */ ("rb"), | ||
contents: "", | ||
}), | ||
podfileProperties: makeNullProvider(), | ||
}; | ||
const defaultProviders = createModFileProviders(modifyFilePath); | ||
|
||
// `react-native-test-app` files | ||
defaultProviders["sceneDelegate"] = modifyFilePath( | ||
expoProviders.appDelegate, | ||
BaseMods.getIosModFileProviders().appDelegate, | ||
"ReactTestApp/SceneDelegate.swift" | ||
); | ||
|
||
// `@rnx-kit/react-native-host` files | ||
defaultProviders["reactNativeHost"] = modifyReactNativeHostFilePath( | ||
expoProviders.appDelegate, | ||
"cocoa/ReactNativeHost.mm" | ||
); | ||
|
||
export function getIosModFileProviders() { | ||
return defaultProviders; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// @ts-check | ||
import { createModFileProviders } from "./cocoaBaseMods.mjs"; | ||
import { makeFilePathModifier } from "../provider.mjs"; | ||
|
||
const modifyFilePath = makeFilePathModifier("node_modules/.generated/macos"); | ||
const defaultProviders = createModFileProviders(modifyFilePath); | ||
|
||
export function getMacOsModFileProviders() { | ||
return defaultProviders; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really clever. 👏
Given RNTA has a predictable structure, a lot of the complexity in the plugins (e.g. searching for all possible locations for an Info.plist or Xcodeproj) can be totally sidestepped, as you've done here. And that side-steps a lot of the coupling to
ios
.I am not sure to what extent this sidesteps all of their hard-coding of the
ios
platform, though. But given how your test passes, maybe it's quite a lot. Surely at least one of these will end up referring toconfig.ios
or theios
folder at some point, though? Do you have an impression of the extent to which Expo config plugins will work?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I've understood, the providers are just functions for making modifications. They don't have props that are platform specific, but may be pointing to some iOS specific paths as you're suggesting. We should be overwriting all the props here (TypeScript would complain otherwise). Save for the null providers, I think we can say we are complete.
That said, these providers aren't supposed to be platform specific. The
with*
functions (e.g.withAppDelegate
) are platform specific, and we need a separate set for macOS. That's what I've done here:react-native-test-app/plugins/macos.js
Lines 35 to 79 in 622a257
Which means that for now, we only support these four functions. And you will need to import them from
react-native-test-app/plugins/macos.js
.