react-native-bootsplash
Version:
Display a bootsplash on your app starts. Hide it when you want.
325 lines (323 loc) • 9.56 kB
JavaScript
;
import * as Expo from "@expo/config-plugins";
import { assignColorValue } from "@expo/config-plugins/build/android/Colors";
import { addImports } from "@expo/config-plugins/build/android/codeMod";
import { mergeContents } from "@expo/config-plugins/build/utils/generateCode";
import childProcess from "child_process";
import path from "path";
import semver from "semver";
import { dedent } from "ts-dedent";
import util from "util";
import { hfs, log, PACKAGE_NAME, requireAddon, setLoggerMode, transformProps, writeAndroidAssets, writeGenericAssets, writeIOSAssets, writeWebAssets } from "./utils.js";
const promisifiedExec = util.promisify(childProcess.exec);
const exec = cmd => promisifiedExec(cmd).then(({
stdout,
stderr
}) => stdout || stderr);
const withoutExpoSplashScreen = Expo.createRunOncePlugin(expoConfig => expoConfig, "expo-splash-screen", "skip");
const withAndroidAssets = (expoConfig, rawProps) => Expo.withDangerousMod(expoConfig, ["android", async config => {
const {
platformProjectRoot,
projectRoot
} = config.modRequest;
const props = await transformProps(projectRoot, rawProps);
const addon = requireAddon(props);
const androidOutputPath = path.resolve(platformProjectRoot, "app", "src", "main", "res");
await writeAndroidAssets({
androidOutputPath,
props
});
await addon?.writeAndroidAssets({
androidOutputPath,
props
});
return config;
}]);
const withAndroidManifest = expoConfig => Expo.withAndroidManifest(expoConfig, config => {
config.modResults.manifest.application?.forEach(application => {
if (application.$["android:name"] === ".MainApplication") {
const {
activity
} = application;
activity?.forEach(activity => {
if (activity.$["android:name"] === ".MainActivity") {
activity.$["android:theme"] = "@style/BootTheme";
}
});
}
});
return config;
});
const withMainActivity = expoConfig => Expo.withMainActivity(expoConfig, config => {
const {
modResults
} = config;
const withImports = addImports(modResults.contents.replace(/(\/\/ )?setTheme\(R\.style\.AppTheme\)/, "// setTheme(R.style.AppTheme)"), ["android.os.Bundle", "com.zoontek.rnbootsplash.RNBootSplash"], false);
// indented with 4 spaces
const withInit = mergeContents({
src: withImports,
comment: " //",
tag: "bootsplash-init",
offset: 0,
anchor: /super\.onCreate\((null|savedInstanceState)\)/,
newSrc: " RNBootSplash.init(this, R.style.BootTheme)"
});
return {
...config,
modResults: {
...modResults,
contents: withInit.contents
}
};
});
const withAndroidStyles = (expoConfig, rawProps) => Expo.withAndroidStyles(expoConfig, async config => {
const {
projectRoot
} = config.modRequest;
const {
android,
brand
} = await transformProps(projectRoot, rawProps);
const {
darkContentBarsStyle
} = android;
const {
modResults
} = config;
const {
resources
} = modResults;
const {
style = []
} = resources;
const item = [{
$: {
name: "postBootSplashTheme"
},
_: "@style/AppTheme"
}, {
$: {
name: "bootSplashBackground"
},
_: "@color/bootsplash_background"
}, {
$: {
name: "bootSplashLogo"
},
_: "@drawable/bootsplash_logo"
}];
if (brand != null) {
item.push({
$: {
name: "bootSplashBrand"
},
_: "@drawable/bootsplash_brand"
});
}
if (darkContentBarsStyle != null) {
item.push({
$: {
name: "darkContentBarsStyle"
},
_: String(darkContentBarsStyle)
});
}
const withBootTheme = [...style.filter(({
$
}) => $.name !== "BootTheme"), {
$: {
name: "BootTheme",
parent: "Theme.BootSplash"
},
item
}];
return {
...config,
modResults: {
...modResults,
resources: {
...resources,
style: withBootTheme
}
}
};
});
const withAndroidColors = (expoConfig, rawProps) => Expo.withAndroidColors(expoConfig, async config => {
const {
projectRoot
} = config.modRequest;
const {
background
} = await transformProps(projectRoot, rawProps);
config.modResults = assignColorValue(config.modResults, {
name: "bootsplash_background",
value: background.hex
});
return config;
});
const withAndroidColorsNight = (expoConfig, rawProps) => Expo.withAndroidColorsNight(expoConfig, async config => {
const {
projectRoot
} = config.modRequest;
const props = await transformProps(projectRoot, rawProps);
const addon = requireAddon(props);
return addon != null ? await addon.withAndroidColorsNight({
config,
props
}) : config;
});
const withIOSAssets = (expoConfig, rawProps) => Expo.withDangerousMod(expoConfig, ["ios", async config => {
const {
platformProjectRoot,
projectName = "",
projectRoot
} = config.modRequest;
const props = await transformProps(projectRoot, rawProps);
const addon = requireAddon(props);
const iosOutputPath = path.resolve(platformProjectRoot, projectName);
await writeIOSAssets({
iosOutputPath,
props
});
await addon?.writeIOSAssets({
iosOutputPath,
props
});
return config;
}]);
const withAppDelegate = expoConfig => Expo.withAppDelegate(expoConfig, config => {
const {
modResults
} = config;
const {
language
} = modResults;
if (language !== "swift") {
throw new Error(`Cannot modify the project AppDelegate as it's not in a supported language: ${language}`);
}
const withHeader = mergeContents({
src: modResults.contents,
comment: "//",
tag: "bootsplash-header",
offset: 1,
anchor: /import Expo/,
newSrc: "import RNBootSplash"
});
const withRootView = mergeContents({
src: withHeader.contents,
comment: "//",
tag: "bootsplash-init",
offset: 1,
anchor: /class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {/,
newSrc: dedent`
public override func customize(_ rootView: UIView) {
super.customize(rootView)
RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView)
}
`
});
return {
...config,
modResults: {
...modResults,
contents: withRootView.contents
}
};
});
const withInfoPlist = expoConfig => Expo.withInfoPlist(expoConfig, config => {
config.modResults["UILaunchStoryboardName"] = "BootSplash";
return config;
});
const withXcodeProject = expoConfig => Expo.withXcodeProject(expoConfig, config => {
const {
projectName = ""
} = config.modRequest;
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
filepath: path.join(projectName, "BootSplash.storyboard"),
groupName: projectName,
project: config.modResults,
isBuildFile: true
});
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
filepath: path.join(projectName, "Colors.xcassets"),
groupName: projectName,
project: config.modResults,
isBuildFile: true
});
return config;
});
const withWebAssets = (expoConfig, rawProps) => Expo.withDangerousMod(expoConfig, [expoConfig.platforms?.includes("ios") ? "ios" : "android", async config => {
const {
projectRoot
} = config.modRequest;
const props = await transformProps(projectRoot, rawProps);
const addon = requireAddon(props);
const fileName = "public/index.html";
const htmlTemplatePath = path.resolve(projectRoot, fileName);
if (!hfs.exists(htmlTemplatePath)) {
await exec(`npx expo customize ${fileName}`);
}
await writeWebAssets({
htmlTemplatePath,
props
});
await addon?.writeWebAssets({
htmlTemplatePath,
props
});
return config;
}]);
const withGenericAssets = (expoConfig, rawProps) => Expo.withDangerousMod(expoConfig, [expoConfig.platforms?.includes("ios") ? "ios" : "android", async config => {
const {
projectRoot
} = config.modRequest;
const props = await transformProps(projectRoot, rawProps);
const addon = requireAddon(props);
await writeGenericAssets({
props
});
await addon?.writeGenericAssets({
props
});
return config;
}]);
export const withBootSplash = Expo.createRunOncePlugin((expoConfig, rawProps) => {
const {
platforms = [],
sdkVersion = "0.1.0"
} = expoConfig;
setLoggerMode({
type: "plugin"
});
if (semver.lt(sdkVersion, "54.0.0")) {
log.error("Requires Expo 54.0.0 (or higher)");
process.exit(1);
}
if (!rawProps?.logo) {
log.error("Missing required parameter 'logo'");
process.exit(1);
}
const plugins = [withoutExpoSplashScreen, withGenericAssets];
if (platforms.includes("android")) {
plugins.push(withAndroidAssets, withAndroidManifest, withMainActivity, withAndroidStyles, withAndroidColors, withAndroidColorsNight);
}
if (platforms.includes("ios")) {
plugins.push(withIOSAssets, withAppDelegate, withInfoPlist, withXcodeProject);
}
if (platforms.includes("web")) {
plugins.push(withWebAssets);
}
const licenseKey = process.env["BOOTSPLASH_LICENSE_KEY"];
if ("licenseKey" in rawProps) {
log.warn("Passing licenseKey directly in plugin config is deprecated as it could be leaked by expo-constants. Use the BOOTSPLASH_LICENSE_KEY environment variable instead.");
}
const rawPropsWithEnvVariable = {
...rawProps,
...(licenseKey != null && {
licenseKey
})
};
return Expo.withPlugins(expoConfig, plugins.map(plugin => [plugin, rawPropsWithEnvVariable]));
}, PACKAGE_NAME);
export default config => [PACKAGE_NAME, config];
//# sourceMappingURL=expo.js.map