Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
Created withRunOnce (#2965)
Browse files Browse the repository at this point in the history
* Created withRunOnce

* update

* added internal object

* Update Config.ts

* Update history.ts

* Update core-plugins-test.ts

* Update core-plugins-test.ts
  • Loading branch information
EvanBacon authored Nov 30, 2020
1 parent e345e79 commit 223aed1
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 3 deletions.
5 changes: 4 additions & 1 deletion packages/config-plugins/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
*/
import * as AndroidConfig from './android';
import * as IOSConfig from './ios';
import * as History from './utils/history';
import * as WarningAggregator from './utils/warnings';

export { IOSConfig, AndroidConfig };

export { WarningAggregator };
export { WarningAggregator, History };

export { withExpoIOSPlugins, withExpoAndroidPlugins } from './plugins/expo-plugins';

Expand All @@ -19,6 +20,8 @@ export * from './Plugin.types';

export {
withPlugins,
withRunOnce,
createRunOncePlugin,
withDangerousMod,
withExtendedMod,
withInterceptedMod,
Expand Down
5 changes: 4 additions & 1 deletion packages/config-plugins/src/ios/Facebook.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ExpoConfig } from '@expo/config-types';

import { createRunOncePlugin } from '../plugins/core-plugins';
import { createInfoPlistPlugin } from '../plugins/ios-plugins';
import { InfoPlist } from './IosConfig.types';
import { appendScheme } from './Scheme';
Expand All @@ -16,7 +17,9 @@ type ExpoConfigFacebook = Pick<

const fbSchemes = ['fbapi', 'fb-messenger-api', 'fbauth2', 'fbshareextension'];

export const withFacebook = createInfoPlistPlugin(setFacebookConfig, 'withFacebook');
const withUnversionedFacebookIOS = createInfoPlistPlugin(setFacebookConfig, 'withFacebook');

export const withFacebook = createRunOncePlugin(withUnversionedFacebookIOS, 'expo-facebook-ios');

/**
* Getters
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,60 @@
import { ConfigPlugin, ExportedConfig } from '../../Plugin.types';
import { withExtendedMod, withPlugins } from '../core-plugins';
import { createRunOncePlugin, withExtendedMod, withPlugins, withRunOnce } from '../core-plugins';
import { evalModsAsync } from '../mod-compiler';

describe(withRunOnce, () => {
it('runs plugins multiple times without withRunOnce', () => {
const pluginA: ConfigPlugin = jest.fn(config => config);

withPlugins({ extra: [] } as any, [
// Prove unsafe runs as many times as it was added
pluginA,
pluginA,
]);

// Unsafe runs multiple times
expect(pluginA).toBeCalledTimes(2);
});

it('prevents running different plugins with same id', () => {
const pluginA: ConfigPlugin = jest.fn(config => config);
const pluginB: ConfigPlugin = jest.fn(config => config);

const pluginId = 'foo';

const safePluginA = createRunOncePlugin(pluginA, pluginId);
// A different plugin with the same ID as (A), this proves
// that different plugins can be prevented when using the same ID.
const safePluginB = createRunOncePlugin(pluginB, pluginId);

withPlugins({ extra: [] } as any, [
// Run plugin twice
safePluginA,
safePluginB,
]);

// Prove that each plugin is only run once
expect(pluginA).toBeCalledTimes(1);
expect(pluginB).toBeCalledTimes(0);
});

it('prevents running the same plugin twice', () => {
const pluginA: ConfigPlugin = jest.fn(config => config);
const pluginId = 'foo';

const safePluginA = createRunOncePlugin(pluginA, pluginId);

withPlugins({ extra: [] } as any, [
// Run plugin twice
safePluginA,
safePluginA,
]);

// Prove that each plugin is only run once
expect(pluginA).toBeCalledTimes(1);
});
});

describe(withPlugins, () => {
it('compiles plugins in the correct order', () => {
const pluginA: ConfigPlugin = config => {
Expand Down
39 changes: 39 additions & 0 deletions packages/config-plugins/src/plugins/core-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Mod,
ModPlatform,
} from '../Plugin.types';
import { addHistoryItem, getHistoryItem, PluginHistoryItem } from '../utils/history';

function ensureArray<T>(input: T | T[]): T[] {
if (Array.isArray(input)) {
Expand Down Expand Up @@ -34,6 +35,44 @@ export const withPlugins: ConfigPlugin<
}, config);
};

/**
* Prevents the same plugin from being run twice.
* Used for migrating from unversioned expo config plugins to versioned plugins.
*
* @param config
* @param name
*/
export const withRunOnce: ConfigPlugin<{
plugin: ConfigPlugin<void>;
name: PluginHistoryItem['name'];
version?: PluginHistoryItem['version'];
}> = (config, { plugin, name, version }) => {
// Detect if a plugin has already been run on this config.
if (getHistoryItem(config, name)) {
return config;
}

// Push the history item so duplicates cannot be run.
config = addHistoryItem(config, { name, version });

return plugin(config);
};

/**
* Helper method for creating mods from existing config functions.
*
* @param action
*/
export function createRunOncePlugin<T>(
plugin: ConfigPlugin<T>,
name: string,
version?: string
): ConfigPlugin<T> {
return (config, props) => {
return withRunOnce(config, { plugin: c => plugin(c, props), name, version });
};
}

/**
* Mods that don't modify any data, all unresolved functionality is performed inside a dangerous mod.
* All dangerous mods run first before other mods.
Expand Down
35 changes: 35 additions & 0 deletions packages/config-plugins/src/utils/deprecation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ExpoConfig } from '@expo/config-types';
import chalk from 'chalk';

import { ConfigPlugin, ModPlatform } from '../Plugin.types';
import * as WarningAggregator from './warnings';

export function wrapWithWithDeprecationWarning<T>({
plugin,
platform,
packageName,
unversionedName,
updateUrl,
shouldWarn,
}: {
plugin: ConfigPlugin<T>;
updateUrl: string;
platform: ModPlatform;
packageName: string;
unversionedName: string;
shouldWarn: (config: ExpoConfig) => boolean;
}): ConfigPlugin<T> {
return (config, props) => {
// Only warn if the user intends to enable an API for their app, otherwise there will be a flood of messages for every API.
if (shouldWarn(config)) {
WarningAggregator.addWarningForPlatform(
platform,
'deprecated-plugin',
`Unversioned "${unversionedName}" plugin is deprecated, please update your Expo config to using the versioned plugin "${packageName}". Guide ${chalk.underline(
updateUrl
)}`
);
}
return plugin(config, props);
};
}
36 changes: 36 additions & 0 deletions packages/config-plugins/src/utils/history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ExpoConfig } from '@expo/config-types';

import { ModPlatform } from '../Plugin.types';

export type PluginHistoryItem = {
name: string;
version: string;
platform?: ModPlatform;
};

export function getHistoryItem(
config: Pick<ExpoConfig, '_internal'>,
name: string
): PluginHistoryItem | null {
return config._internal?.pluginHistory?.[name] ?? null;
}

export function addHistoryItem(
config: ExpoConfig,
item: Omit<PluginHistoryItem, 'version'> & { version?: string }
): ExpoConfig {
if (!config._internal) {
config._internal = {};
}
if (!config._internal.pluginHistory) {
config._internal.pluginHistory = {};
}

if (!item.version) {
item.version = 'UNVERSIONED';
}

config._internal.pluginHistory[item.name] = item;

return config;
}
15 changes: 15 additions & 0 deletions packages/config-plugins/src/utils/warnings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ModPlatform } from '../Plugin.types';

type WarningArray = [string, string, string | undefined];
let _warningsIOS: WarningArray[] = [];
let _warningsAndroid: WarningArray[] = [];
Expand All @@ -18,6 +20,19 @@ export function addWarningIOS(tag: string, text: string, link?: string) {
_warningsIOS = [..._warningsIOS, [tag, text, link]];
}

export function addWarningForPlatform(
platform: ModPlatform,
tag: string,
text: string,
link?: string
) {
if (platform === 'ios') {
addWarningIOS(tag, text, link);
} else {
addWarningAndroid(tag, text, link);
}
}

export function flushWarningsAndroid() {
const result = _warningsAndroid;
_warningsAndroid = [];
Expand Down

0 comments on commit 223aed1

Please sign in to comment.