@keepsnappy/react-native 0.1.0-alpha.19 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -9
- package/app.plugin.js +20 -17
- package/build-config.js +99 -0
- package/js/babel-transformer.js +13 -6
- package/metro.js +19 -12
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -33,18 +33,33 @@ Workers write atomic, content-addressed fragments under `.keepsnappy/components/
|
|
|
33
33
|
|
|
34
34
|
`app.config.js` / `app.json`:
|
|
35
35
|
|
|
36
|
-
```
|
|
37
|
-
plugins: [
|
|
36
|
+
```json
|
|
37
|
+
"plugins": [["@keepsnappy/react-native", { "measureFromLaunch": true, "renderTimes": true }]]
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Native code needs no further setup: Expo autolinking links the module from `expo-module.config.json` (iOS pod `KeepSnappy`, Android `expo.modules.keepsnappy.KeepSnappyModule`) when the
|
|
40
|
+
The build switch is `KEEPSNAPPY=1` in the build environment (EAS build profile `env`, CI). Without it the build carries no SDK, whatever the options say. Native code needs no further setup: Expo autolinking links the module from `expo-module.config.json` (iOS pod `KeepSnappy`, Android `expo.modules.keepsnappy.KeepSnappyModule`) when the switch is on. Set `KEEPSNAPPY` the same way for `expo prebuild` (or the EAS build) and for the JS bundle, then `pod install` / rebuild.
|
|
41
|
+
|
|
42
|
+
### Plugin options
|
|
43
|
+
|
|
44
|
+
`build-config.js` resolves them for the config plugin and for Metro (which reads them from the app config), so prebuild and the bundle agree.
|
|
45
|
+
|
|
46
|
+
| Option | Default | Meaning | Stands for |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `measureFromLaunch` | `true` | Measures from the first frame. `false`: markers only until a settings link turns collection on. | `EXPO_PUBLIC_KEEPSNAPPY_AUTO=1` |
|
|
49
|
+
| `renderTimes` | `true` | Records React render times per component, with React's profiling renderer. | `EXPO_PUBLIC_KEEPSNAPPY_PROFILE=1` + `KEEPSNAPPY_REACT_PROFILING=1`; with `waitForActivation`: `KEEPSNAPPY_REACT_PROFILING=switch` |
|
|
50
|
+
| `waitForActivation` | `false` | Dormant: nothing runs until a KeepSnappy activation link, from the next launch on. For one build that serves other e2e jobs too. Wins over `measureFromLaunch`. | `EXPO_PUBLIC_KEEPSNAPPY_MODE=dormant` |
|
|
51
|
+
| `tapText` | `true` | `false`: never reads text on screen to name a tap. | `EXPO_PUBLIC_KEEPSNAPPY_TAP_TEXT=0` |
|
|
52
|
+
|
|
53
|
+
Unknown options and non-boolean values fail prebuild and Metro. The key is never an option: it stays a CI secret (`KEEPSNAPPY_KEY`). Builds that push their own data (BrowserStack, Android) read `EXPO_PUBLIC_KEEPSNAPPY_KEY` and `EXPO_PUBLIC_KEEPSNAPPY_INGEST_URL` from the build environment.
|
|
54
|
+
|
|
55
|
+
The older variables keep working. A variable set (non-empty) in the environment wins over its option. A build switched on only by `EXPO_PUBLIC_KEEPSNAPPY=1` leaves unset options off, so it builds exactly as before the options existed (`test/build-inputs.test.ts` pins this against alpha.19).
|
|
41
56
|
|
|
42
57
|
### What the config plugin does
|
|
43
58
|
|
|
44
|
-
It decides whether the native module is linked, from `EXPO_PUBLIC_KEEPSNAPPY` at prebuild time:
|
|
59
|
+
It decides whether the native module is linked, from the build switch (`KEEPSNAPPY` or `EXPO_PUBLIC_KEEPSNAPPY`) at prebuild time:
|
|
45
60
|
|
|
46
61
|
- **Flag off:** the package is excluded from Expo autolinking. iOS: the Podfile's `use_expo_modules!` becomes `use_expo_modules!({ exclude: ['<package>'] })` (this also reaches the `expo-configure-project.sh` build phase that regenerates `ExpoModulesProvider.swift`). Android: `expoAutolinking.exclude` is set in `settings.gradle` before `expoAutolinking.useExpoModules()`. `expo-modules-autolinking` 57 reads exclusions only from the app's `package.json` and from these call sites. Result: no `KeepSnappy` pod, no Android project, no provider entry.
|
|
47
|
-
- **Flag on:** the exclusion is removed again (prebuild without `--clean` edits existing files) and `extra.keepsnappy = { native: true, profilingRenderer }` is added to the app config, so flag-on and flag-off builds have different native fingerprints. `profilingRenderer` is `always` (`KEEPSNAPPY_REACT_PROFILING=1`), `switch` (`=switch`) or `off`,
|
|
62
|
+
- **Flag on:** the exclusion is removed again (prebuild without `--clean` edits existing files) and `extra.keepsnappy = { native: true, profilingRenderer }` is added to the app config, so flag-on and flag-off builds have different native fingerprints. `profilingRenderer` is `always` (`KEEPSNAPPY_REACT_PROFILING=1`), `switch` (`=switch`) or `off`, resolved at prebuild time from the variable or the `renderTimes` option, so the three builds fingerprint differently too. Set `KEEPSNAPPY_REACT_PROFILING` the same way for prebuild and for the bundle.
|
|
48
63
|
|
|
49
64
|
A Podfile whose `use_expo_modules!` already takes arguments (or a settings.gradle without `expoAutolinking.useExpoModules()`) cannot be edited safely; with the flag off, prebuild then fails with a message saying what to add. No Info.plist, entitlement or manifest change is needed: the module only uses `os_signpost`/`OSLog`, `android.os.Trace`, logcat and the app's own files directory.
|
|
50
65
|
|
|
@@ -53,18 +68,19 @@ A Podfile whose `use_expo_modules!` already takes arguments (or a settings.gradl
|
|
|
53
68
|
1. Appends `js/hook.polyfill.js` to `serializer.getPolyfills`, so a `__REACT_DEVTOOLS_GLOBAL_HOOK__` exists before React evaluates (it chains onto an existing hook instead of replacing it; in dev it defers to React DevTools). In a dormant build it first reads the activation and installs nothing when not activated.
|
|
54
69
|
2. Sets `transformer.babelTransformerPath` to `js/babel-transformer.js`, which delegates to the app's transformer and
|
|
55
70
|
- prepends `import "<relative path>/js/sdk/boot";` to the entry file (`package.json` `main`), which puts the SDK into the graph;
|
|
56
|
-
- inlines `process.env.EXPO_PUBLIC_*` in the SDK's own files with the build-time values. `babel-preset-expo` does not inline `EXPO_PUBLIC_*` inside `node_modules`, so without this step the SDK would read `undefined` at runtime.
|
|
71
|
+
- inlines `process.env.EXPO_PUBLIC_*` in the SDK's own files with the build-time values: the `EXPO_PUBLIC_KEEPSNAPPY*` values resolved from the options and the environment (`KEEPSNAPPY_BUILD_VARS`), other names from the environment. `babel-preset-expo` does not inline `EXPO_PUBLIC_*` inside `node_modules`, so without this step the SDK would read `undefined` at runtime.
|
|
57
72
|
3. Appends the boot module to `serializer.getModulesRunBeforeMainModule` (ignored by Expo's serializer, harmless: the boot import is the entry's first statement; kept for plain Metro).
|
|
58
73
|
|
|
59
74
|
The upstream transformer path, entry file and boot file reach Metro's transform workers through `KEEPSNAPPY_UPSTREAM_BABEL_TRANSFORMER`, `KEEPSNAPPY_ENTRY_FILE` and `KEEPSNAPPY_BOOT_FILE` (internal, set by `withKeepSnappy`).
|
|
60
75
|
|
|
61
76
|
## Environment / config contract
|
|
62
77
|
|
|
63
|
-
All values are read at **bundle time** (Expo CLI loads `.env*` into the Metro process). Change them, then rebuild the JS bundle with a clean cache (`--reset-cache` / `expo start -c`).
|
|
78
|
+
All values are read at **bundle time** (Expo CLI loads `.env*` into the Metro process). Change them, then rebuild the JS bundle with a clean cache (`--reset-cache` / `expo start -c`). The plugin options above stand for some of them; a variable set here wins over its option.
|
|
64
79
|
|
|
65
80
|
| Variable (current name) | Proposed public name | Values | Effect |
|
|
66
81
|
|---|---|---|---|
|
|
67
|
-
| `
|
|
82
|
+
| `KEEPSNAPPY` | | `1` = on | The build switch. With it, unset plugin options take their defaults. |
|
|
83
|
+
| `EXPO_PUBLIC_KEEPSNAPPY` | `KEEPSNAPPY` | `1` = on, anything else = off | Build flag (older form of the switch). Off (and no `KEEPSNAPPY=1`): Metro config untouched, SDK not in the bundle. On: hook polyfill + SDK boot in the bundle. |
|
|
68
84
|
| `EXPO_PUBLIC_KEEPSNAPPY_MODE` | `KEEPSNAPPY_MODE` | `dormant` / `auto` | `auto`: measure from the first frame (same as `KEEPSNAPPY_AUTO=1`). `dormant`: nothing runs until activated (below); once activated the process measures like `auto`. Unset: `auto` if `KEEPSNAPPY_AUTO=1`, else manual (markers until a settings link turns collection on). |
|
|
69
85
|
| `EXPO_PUBLIC_KEEPSNAPPY_AUTO` | `KEEPSNAPPY_AUTO` | `1` = on | Measure-only builds: collection (React hook walk, PerformanceObserver, log to console and native file) is on from the first frame, with no deep link needed. Off: only markers until a settings link turns collection on. Equivalent to `KEEPSNAPPY_MODE=auto`; `KEEPSNAPPY_MODE` wins when both are set. |
|
|
70
86
|
| `EXPO_PUBLIC_KEEPSNAPPY_INGEST_URL` | `KEEPSNAPPY_INGEST_URL` | URL of the farm ingest (`POST /v1/ingest`) | Push client. Push is on only if both the URL and the key are set. |
|
|
@@ -77,7 +93,7 @@ All values are read at **bundle time** (Expo CLI loads `.env*` into the Metro pr
|
|
|
77
93
|
| `EXPO_PUBLIC_KEEPSNAPPY_CPUPROF`, `..._CPUPROF_HZ` | `KEEPSNAPPY_CPUPROF` | `1` = on; Hz (default 250) | Default of `cpuprof`/`cpuhz`: Hermes sampling profiler (iOS), `Documents/keepsnappy/cpu-<process>-<span>.cpuprofile`. Inside a run it samples from the first window to `flush=1` and stops and dumps there, so the synchronous dump never lands in a measured window; otherwise one file per span of overlapping windows, dumped when the last one ends. `<process>` is the launch's `processId` (startup line), so launches never overwrite each other. |
|
|
78
94
|
| `KEEPSNAPPY_REACT_PROFILING` (Metro process; also read by the config plugin) | | `1` / `switch` / unset | `1`: `withKeepSnappy` resolves RN's production renderer to `ReactFabric-profiling` (fiber durations, updaters), flag or not; for dedicated perf builds (docs/plans/37). `switch` (flag on only, ignored with the flag off): both Fabric renderers go into the bundle and each process loads one at launch (below). Unset: production renderer only. |
|
|
79
95
|
|
|
80
|
-
Only the names above are read today. The proposed names drop the `EXPO_PUBLIC_` prefix because the values never need to be readable by app code: the transformer inlines them into the SDK only, so a non-prefixed `KEEPSNAPPY=1` would also keep the flag out of the app's own `process.env` surface and match the CLI / CI wording (`KEEPSNAPPY=1` in doc 23). Migration plan: accept both names (new name wins), then drop the old ones.
|
|
96
|
+
Only the names above are read today; of the proposed names only `KEEPSNAPPY` exists (alpha.20), the rest are plugin options now. The proposed names drop the `EXPO_PUBLIC_` prefix because the values never need to be readable by app code: the transformer inlines them into the SDK only, so a non-prefixed `KEEPSNAPPY=1` would also keep the flag out of the app's own `process.env` surface and match the CLI / CI wording (`KEEPSNAPPY=1` in doc 23). Migration plan: accept both names (new name wins), then drop the old ones.
|
|
81
97
|
|
|
82
98
|
### Renderer switch (`KEEPSNAPPY_REACT_PROFILING=switch`)
|
|
83
99
|
|
package/app.plugin.js
CHANGED
|
@@ -1,29 +1,32 @@
|
|
|
1
1
|
// Expo config plugin for the KeepSnappy SDK.
|
|
2
2
|
//
|
|
3
3
|
// The native module (iOS pod KeepSnappy, Android expo.modules.keepsnappy) is linked
|
|
4
|
-
// by Expo autolinking from expo-module.config.json. With the build
|
|
5
|
-
// (EXPO_PUBLIC_KEEPSNAPPY is
|
|
6
|
-
// excludes the package from Expo autolinking,
|
|
7
|
-
// native code: no pod, no Android project, no
|
|
8
|
-
// the
|
|
9
|
-
// the existing files) and records the
|
|
10
|
-
// fingerprints of
|
|
11
|
-
// React renderer the bundle carries (KEEPSNAPPY_REACT_PROFILING
|
|
12
|
-
// `always` (=1),
|
|
4
|
+
// by Expo autolinking from expo-module.config.json. With the build switch off
|
|
5
|
+
// (neither KEEPSNAPPY nor the older EXPO_PUBLIC_KEEPSNAPPY is "1" when
|
|
6
|
+
// `expo prebuild` runs) this plugin excludes the package from Expo autolinking,
|
|
7
|
+
// so a store build carries no SDK native code: no pod, no Android project, no
|
|
8
|
+
// ExpoModulesProvider entry. With the switch on it removes that exclusion again
|
|
9
|
+
// (prebuild without --clean edits the existing files) and records the switch in
|
|
10
|
+
// the app config, so the native fingerprints of on and off builds differ. It
|
|
11
|
+
// also records which React renderer the bundle carries (KEEPSNAPPY_REACT_PROFILING
|
|
12
|
+
// or the renderTimes option, see build-config.js and metro.js): `always` (=1),
|
|
13
|
+
// `switch` (=switch) or `off`.
|
|
14
|
+
//
|
|
15
|
+
// The plugin options (measureFromLaunch, renderTimes, waitForActivation, tapText)
|
|
16
|
+
// are resolved in build-config.js; Metro reads the same options from the app config.
|
|
13
17
|
//
|
|
14
18
|
// expo-modules-autolinking reads exclusions only from the app's package.json and
|
|
15
19
|
// from command arguments, so the Podfile's `use_expo_modules!` call and
|
|
16
20
|
// settings.gradle's `expoAutolinking` extension are where the exclusion goes.
|
|
17
21
|
|
|
18
22
|
const { withPodfile, withSettingsGradle } = require('expo/config-plugins');
|
|
23
|
+
const { PACKAGE_NAMES, resolveBuild } = require('./build-config');
|
|
19
24
|
|
|
20
25
|
const PACKAGE_NAME = require('./package.json').name;
|
|
21
|
-
const PACKAGE_NAMES = [...new Set([PACKAGE_NAME, '@keepsnappy/react-native'])];
|
|
22
26
|
const namesFor = name => name === PACKAGE_NAME ? PACKAGE_NAMES : [name];
|
|
27
|
+
// Written into every store build's Podfile and settings.gradle, so into its fingerprint: keep the text as it is.
|
|
23
28
|
const TAG = 'keepsnappy: native module excluded, EXPO_PUBLIC_KEEPSNAPPY is not 1';
|
|
24
29
|
|
|
25
|
-
const isEnabled = () => (process.env.EXPO_PUBLIC_KEEPSNAPPY) === '1';
|
|
26
|
-
|
|
27
30
|
const profilingRenderer = (value = (process.env.KEEPSNAPPY_REACT_PROFILING)) => {
|
|
28
31
|
if (value === '1') {
|
|
29
32
|
return 'always';
|
|
@@ -44,7 +47,7 @@ const editPodfile = (contents, enabled, name = PACKAGE_NAME) => {
|
|
|
44
47
|
}
|
|
45
48
|
throw new Error(
|
|
46
49
|
`${name}: cannot exclude the native module, the Podfile has no plain \`use_expo_modules!\` call. ` +
|
|
47
|
-
`Add exclude: [${quoted(name)}] to it yourself, or set
|
|
50
|
+
`Add exclude: [${quoted(name)}] to it yourself, or set KEEPSNAPPY=1.`,
|
|
48
51
|
);
|
|
49
52
|
}
|
|
50
53
|
const indent = match[1];
|
|
@@ -70,19 +73,19 @@ const editSettingsGradle = (contents, enabled, language = 'groovy', name = PACKA
|
|
|
70
73
|
if (at < 0) {
|
|
71
74
|
throw new Error(
|
|
72
75
|
`${name}: cannot exclude the native module, settings.gradle has no \`expoAutolinking.useExpoModules()\` call. ` +
|
|
73
|
-
'Set expoAutolinking.exclude yourself, or set
|
|
76
|
+
'Set expoAutolinking.exclude yourself, or set KEEPSNAPPY=1.',
|
|
74
77
|
);
|
|
75
78
|
}
|
|
76
79
|
lines.splice(at, 0, gradleLine(language, name));
|
|
77
80
|
return lines.join('\n');
|
|
78
81
|
};
|
|
79
82
|
|
|
80
|
-
const withKeepSnappy = config => {
|
|
81
|
-
const enabled =
|
|
83
|
+
const withKeepSnappy = (config, options) => {
|
|
84
|
+
const { enabled, vars } = resolveBuild(options);
|
|
82
85
|
if (enabled) {
|
|
83
86
|
config.extra = {
|
|
84
87
|
...config.extra,
|
|
85
|
-
keepsnappy: { native: true, profilingRenderer: profilingRenderer() },
|
|
88
|
+
keepsnappy: { native: true, profilingRenderer: profilingRenderer(vars.KEEPSNAPPY_REACT_PROFILING ?? '') },
|
|
86
89
|
};
|
|
87
90
|
}
|
|
88
91
|
config = withPodfile(config, mod => {
|
package/build-config.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// The KeepSnappy build settings, resolved once for the config plugin (app.plugin.js) and Metro (metro.js), so prebuild and
|
|
2
|
+
// the bundle agree.
|
|
3
|
+
//
|
|
4
|
+
// The build switch is KEEPSNAPPY=1 in the build environment (EAS profile env, CI). The older EXPO_PUBLIC_KEEPSNAPPY=1
|
|
5
|
+
// still turns the SDK on. Without either, the build carries no SDK: the plugin options are not read.
|
|
6
|
+
//
|
|
7
|
+
// What a build measures comes from the plugin options in app.json, `["@keepsnappy/react-native", { ... }]`. Each option
|
|
8
|
+
// stands for older variables, and a variable set (non-empty) in the environment wins over the option:
|
|
9
|
+
// measureFromLaunch EXPO_PUBLIC_KEEPSNAPPY_AUTO=1 (with EXPO_PUBLIC_KEEPSNAPPY_MODE, one setting)
|
|
10
|
+
// waitForActivation EXPO_PUBLIC_KEEPSNAPPY_MODE=dormant (same setting; wins over measureFromLaunch)
|
|
11
|
+
// renderTimes EXPO_PUBLIC_KEEPSNAPPY_PROFILE=1, KEEPSNAPPY_REACT_PROFILING=1
|
|
12
|
+
// (waiting for activation: KEEPSNAPPY_REACT_PROFILING=switch, the activation asks for render times)
|
|
13
|
+
// tapText EXPO_PUBLIC_KEEPSNAPPY_TAP_TEXT=0 when false
|
|
14
|
+
// Options left out take DEFAULTS under KEEPSNAPPY=1. A build switched on only by EXPO_PUBLIC_KEEPSNAPPY=1 leaves them
|
|
15
|
+
// out, so it builds exactly as before the options existed.
|
|
16
|
+
//
|
|
17
|
+
// The key is never an option: it stays a CI secret. Builds that push their own data (BrowserStack, Android) read
|
|
18
|
+
// EXPO_PUBLIC_KEEPSNAPPY_KEY and EXPO_PUBLIC_KEEPSNAPPY_INGEST_URL from the build environment.
|
|
19
|
+
|
|
20
|
+
const PACKAGE_NAME = require('./package.json').name;
|
|
21
|
+
const PACKAGE_NAMES = [...new Set([PACKAGE_NAME, '@keepsnappy/react-native'])];
|
|
22
|
+
|
|
23
|
+
const DEFAULTS = { measureFromLaunch: true, renderTimes: true, waitForActivation: false, tapText: true };
|
|
24
|
+
const UNSET = { measureFromLaunch: false, renderTimes: false, waitForActivation: false, tapText: true };
|
|
25
|
+
|
|
26
|
+
const isSet = value => value !== undefined && value !== '';
|
|
27
|
+
|
|
28
|
+
const isEnabled = () => process.env.KEEPSNAPPY === '1' || process.env.EXPO_PUBLIC_KEEPSNAPPY === '1';
|
|
29
|
+
|
|
30
|
+
function checkOptions(options, name = PACKAGE_NAME) {
|
|
31
|
+
if (options === undefined || options === null) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
if (typeof options !== 'object' || Array.isArray(options)) {
|
|
35
|
+
throw new Error(`${name}: the plugin options must be an object, such as { "measureFromLaunch": true }`);
|
|
36
|
+
}
|
|
37
|
+
for (const [key, value] of Object.entries(options)) {
|
|
38
|
+
if (!(key in DEFAULTS)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`${name}: unknown option "${key}"; the options are ${Object.keys(DEFAULTS).join(', ')}.` +
|
|
41
|
+
(/key|token|secret|ingest|url/i.test(key)
|
|
42
|
+
? ' The key never goes in app.json: it stays a CI secret (KEEPSNAPPY_KEY); builds for BrowserStack or Android read EXPO_PUBLIC_KEEPSNAPPY_KEY and EXPO_PUBLIC_KEEPSNAPPY_INGEST_URL from the build environment.'
|
|
43
|
+
: ''),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (typeof value !== 'boolean') {
|
|
47
|
+
throw new Error(`${name}: ${key} must be true or false, not ${JSON.stringify(value)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return options;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The values the SDK reads: every EXPO_PUBLIC_KEEPSNAPPY* variable of the environment and KEEPSNAPPY_REACT_PROFILING,
|
|
54
|
+
// plus the ones the options stand for.
|
|
55
|
+
function resolveBuild(rawOptions) {
|
|
56
|
+
const options = checkOptions(rawOptions);
|
|
57
|
+
const vars = Object.fromEntries(
|
|
58
|
+
Object.entries(process.env).filter(([name]) => name.startsWith('EXPO_PUBLIC_KEEPSNAPPY') || name === 'KEEPSNAPPY_REACT_PROFILING'),
|
|
59
|
+
);
|
|
60
|
+
if (!isEnabled()) {
|
|
61
|
+
return { enabled: false, vars };
|
|
62
|
+
}
|
|
63
|
+
const unset = process.env.KEEPSNAPPY === '1' ? DEFAULTS : UNSET;
|
|
64
|
+
const option = key => options[key] ?? unset[key];
|
|
65
|
+
vars.EXPO_PUBLIC_KEEPSNAPPY = '1';
|
|
66
|
+
if (!isSet(process.env.EXPO_PUBLIC_KEEPSNAPPY_MODE) && !isSet(process.env.EXPO_PUBLIC_KEEPSNAPPY_AUTO)) {
|
|
67
|
+
if (option('waitForActivation')) {
|
|
68
|
+
vars.EXPO_PUBLIC_KEEPSNAPPY_MODE = 'dormant';
|
|
69
|
+
} else if (option('measureFromLaunch')) {
|
|
70
|
+
vars.EXPO_PUBLIC_KEEPSNAPPY_AUTO = '1';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const dormant = vars.EXPO_PUBLIC_KEEPSNAPPY_MODE === 'dormant';
|
|
74
|
+
if (option('renderTimes')) {
|
|
75
|
+
if (!isSet(process.env.KEEPSNAPPY_REACT_PROFILING)) {
|
|
76
|
+
vars.KEEPSNAPPY_REACT_PROFILING = dormant ? 'switch' : '1';
|
|
77
|
+
}
|
|
78
|
+
if (!dormant && !isSet(process.env.EXPO_PUBLIC_KEEPSNAPPY_PROFILE)) {
|
|
79
|
+
vars.EXPO_PUBLIC_KEEPSNAPPY_PROFILE = '1';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!option('tapText') && !isSet(process.env.EXPO_PUBLIC_KEEPSNAPPY_TAP_TEXT)) {
|
|
83
|
+
vars.EXPO_PUBLIC_KEEPSNAPPY_TAP_TEXT = '0';
|
|
84
|
+
}
|
|
85
|
+
return { enabled: true, vars };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// The options of the plugin entry in the app's Expo config (app.json, app.config.js or app.config.ts), as prebuild
|
|
89
|
+
// passes them to the plugin. Metro reads them here because it does not run config plugins.
|
|
90
|
+
function appPluginOptions(projectRoot) {
|
|
91
|
+
const { getConfig } = require('expo/config');
|
|
92
|
+
const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true });
|
|
93
|
+
// A path into the package (`@keepsnappy/react-native/app.plugin`) is the same plugin.
|
|
94
|
+
const isOurs = name => typeof name === 'string' && PACKAGE_NAMES.some(p => name === p || name.startsWith(`${p}/`));
|
|
95
|
+
const entry = (exp.plugins ?? []).find(plugin => isOurs(Array.isArray(plugin) ? plugin[0] : plugin));
|
|
96
|
+
return Array.isArray(entry) ? entry[1] : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { DEFAULTS, PACKAGE_NAMES, isEnabled, checkOptions, resolveBuild, appPluginOptions };
|
package/js/babel-transformer.js
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
//
|
|
5
5
|
// babel-preset-expo inlines EXPO_PUBLIC_* only outside node_modules, and the
|
|
6
6
|
// SDK is installed into node_modules, so this wrapper inlines the SDK's own
|
|
7
|
-
// EXPO_PUBLIC_* reads with the build-time values
|
|
7
|
+
// EXPO_PUBLIC_* reads with the build-time values: the EXPO_PUBLIC_KEEPSNAPPY*
|
|
8
|
+
// values withKeepSnappy resolved from the plugin options and the environment
|
|
9
|
+
// (KEEPSNAPPY_BUILD_VARS, see build-config.js), any other name from the environment.
|
|
8
10
|
|
|
9
11
|
const path = require('path');
|
|
12
|
+
const { isEnabled } = require('../build-config');
|
|
10
13
|
|
|
11
14
|
const upstreamPath = (process.env.KEEPSNAPPY_UPSTREAM_BABEL_TRANSFORMER);
|
|
12
15
|
const entryFile = (process.env.KEEPSNAPPY_ENTRY_FILE);
|
|
@@ -15,8 +18,9 @@ const sdkVersion = process.env.KEEPSNAPPY_SDK_VERSION;
|
|
|
15
18
|
const clientPackages = JSON.parse(process.env.KEEPSNAPPY_CLIENT_PACKAGES ?? '[]');
|
|
16
19
|
const projectRoot = process.env.KEEPSNAPPY_PROJECT_ROOT;
|
|
17
20
|
const componentCacheKey = process.env.KEEPSNAPPY_COMPONENT_CACHE_KEY ?? '';
|
|
21
|
+
const buildVars = process.env.KEEPSNAPPY_BUILD_VARS ? JSON.parse(process.env.KEEPSNAPPY_BUILD_VARS) : null;
|
|
18
22
|
|
|
19
|
-
if (!upstreamPath || !entryFile || !bootFile || !sdkVersion) {
|
|
23
|
+
if (!upstreamPath || !entryFile || !bootFile || !sdkVersion || !buildVars) {
|
|
20
24
|
throw new Error(
|
|
21
25
|
'KeepSnappy Babel transformer loaded without withKeepSnappy env (KEEPSNAPPY_*)',
|
|
22
26
|
);
|
|
@@ -27,16 +31,19 @@ const upstream = require(upstreamPath);
|
|
|
27
31
|
const packageRoot = path.resolve(__dirname, '..') + path.sep;
|
|
28
32
|
const ENV_REF = /\bprocess\.env\.(EXPO_PUBLIC_[A-Z0-9_]+)\b/g;
|
|
29
33
|
|
|
34
|
+
const buildValue = name =>
|
|
35
|
+
name.startsWith('EXPO_PUBLIC_KEEPSNAPPY') ? buildVars[name] : process.env[name];
|
|
36
|
+
|
|
30
37
|
const inlineEnv = src =>
|
|
31
38
|
src.replace(ENV_REF, (_match, name) =>
|
|
32
|
-
|
|
39
|
+
buildValue(name) === undefined ? 'undefined' : JSON.stringify(buildValue(name)),
|
|
33
40
|
);
|
|
34
41
|
|
|
35
42
|
const envSignature = () =>
|
|
36
|
-
Object.keys(
|
|
43
|
+
Object.keys(buildVars)
|
|
37
44
|
.filter(name => name.startsWith('EXPO_PUBLIC_KEEPSNAPPY'))
|
|
38
45
|
.sort()
|
|
39
|
-
.map(name => `${name}=${
|
|
46
|
+
.map(name => `${name}=${buildVars[name]}`)
|
|
40
47
|
.join('&');
|
|
41
48
|
|
|
42
49
|
const bootImport = () => {
|
|
@@ -56,7 +63,7 @@ const absolute = (filename, options) =>
|
|
|
56
63
|
module.exports = {
|
|
57
64
|
...upstream,
|
|
58
65
|
transform(args) {
|
|
59
|
-
if (
|
|
66
|
+
if (!isEnabled()) return upstream.transform(args);
|
|
60
67
|
const file = absolute(args.filename, args.options);
|
|
61
68
|
const original = args;
|
|
62
69
|
if (file === entryFile) {
|
package/metro.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// withKeepSnappy(metroConfig): the only line an app adds to metro.config.js.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// The build settings come from build-config.js: the switch KEEPSNAPPY=1 (or the
|
|
4
|
+
// older EXPO_PUBLIC_KEEPSNAPPY=1), the plugin options in the app config and the
|
|
5
|
+
// older variables, which win over the options. Below, "the flag" is that switch
|
|
6
|
+
// and KEEPSNAPPY_REACT_PROFILING is the resolved value (the renderTimes option or
|
|
7
|
+
// the variable). The resolved EXPO_PUBLIC_KEEPSNAPPY_* values reach the SDK
|
|
8
|
+
// transformer as KEEPSNAPPY_BUILD_VARS.
|
|
9
|
+
//
|
|
10
|
+
// With the flag on it
|
|
4
11
|
// 1. appends the React DevTools hook as a Metro polyfill, so the hook exists
|
|
5
12
|
// before any module (React included) evaluates;
|
|
6
13
|
// 2. wraps the Babel transformer so the app's entry module imports the SDK boot
|
|
@@ -11,9 +18,9 @@
|
|
|
11
18
|
// zustand store's setState through the cause tracker (REQ-MEAS-32 timers).
|
|
12
19
|
// Without the flag it returns the config object untouched.
|
|
13
20
|
//
|
|
14
|
-
// KEEPSNAPPY_REACT_PROFILING=1 (
|
|
15
|
-
// production renderer to its profiling build (fiber
|
|
16
|
-
// selfBaseDuration, root.memoizedUpdaters), the same rewrite as
|
|
21
|
+
// KEEPSNAPPY_REACT_PROFILING=1 (the variable alone also with the flag off)
|
|
22
|
+
// resolves React Native's production renderer to its profiling build (fiber
|
|
23
|
+
// actualDuration / selfBaseDuration, root.memoizedUpdaters), the same rewrite as
|
|
17
24
|
// @callstack/inspector's withInspector. Nothing else in the bundle changes.
|
|
18
25
|
//
|
|
19
26
|
// KEEPSNAPPY_REACT_PROFILING=switch (flag on only) bundles both Fabric renderers
|
|
@@ -22,6 +29,7 @@
|
|
|
22
29
|
|
|
23
30
|
const fs = require('fs');
|
|
24
31
|
const path = require('path');
|
|
32
|
+
const { appPluginOptions, isEnabled, resolveBuild } = require('./build-config');
|
|
25
33
|
|
|
26
34
|
const POLYFILL_PATH = path.join(__dirname, 'js', 'hook.polyfill.js');
|
|
27
35
|
const BOOT_PATH = path.join(__dirname, 'js', 'sdk', 'boot.ts');
|
|
@@ -30,10 +38,7 @@ const TRANSFORMER_PATH = path.join(__dirname, 'js', 'babel-transformer.js');
|
|
|
30
38
|
const ENV_UPSTREAM = 'KEEPSNAPPY_UPSTREAM_BABEL_TRANSFORMER';
|
|
31
39
|
const ENV_ENTRY = 'KEEPSNAPPY_ENTRY_FILE';
|
|
32
40
|
const ENV_BOOT = 'KEEPSNAPPY_BOOT_FILE';
|
|
33
|
-
|
|
34
|
-
const isEnabled = () => (process.env.EXPO_PUBLIC_KEEPSNAPPY) === '1';
|
|
35
|
-
const isProfiling = () => (process.env.KEEPSNAPPY_REACT_PROFILING) === '1';
|
|
36
|
-
const isSwitch = () => (process.env.KEEPSNAPPY_REACT_PROFILING) === 'switch';
|
|
41
|
+
const ENV_VARS = 'KEEPSNAPPY_BUILD_VARS';
|
|
37
42
|
|
|
38
43
|
const SWITCH_PATH = path.join(__dirname, 'js', 'renderer-switch.js');
|
|
39
44
|
// The names renderer-switch.js requires; they resolve next to RN's shim.
|
|
@@ -187,16 +192,17 @@ const resolveEntry = projectRoot => {
|
|
|
187
192
|
|
|
188
193
|
function withKeepSnappy(input, { clientPackages = [] } = {}) {
|
|
189
194
|
if (!isEnabled()) {
|
|
190
|
-
return
|
|
195
|
+
return process.env.KEEPSNAPPY_REACT_PROFILING === '1' ? withReactProfiling(input) : input;
|
|
191
196
|
}
|
|
197
|
+
const projectRoot = input.projectRoot ?? process.cwd();
|
|
198
|
+
const { vars } = resolveBuild(appPluginOptions(projectRoot));
|
|
192
199
|
let config = input;
|
|
193
|
-
if (
|
|
200
|
+
if (vars.KEEPSNAPPY_REACT_PROFILING === '1') {
|
|
194
201
|
config = withReactProfiling(input);
|
|
195
|
-
} else if (
|
|
202
|
+
} else if (vars.KEEPSNAPPY_REACT_PROFILING === 'switch') {
|
|
196
203
|
config = withRendererSwitch(input);
|
|
197
204
|
}
|
|
198
205
|
config = withStoreWrap(config);
|
|
199
|
-
const projectRoot = config.projectRoot ?? process.cwd();
|
|
200
206
|
const entry = resolveEntry(projectRoot);
|
|
201
207
|
const upstream = config.transformer?.babelTransformerPath;
|
|
202
208
|
if (!entry || !upstream) {
|
|
@@ -211,6 +217,7 @@ function withKeepSnappy(input, { clientPackages = [] } = {}) {
|
|
|
211
217
|
process.env[ENV_UPSTREAM] = upstream;
|
|
212
218
|
process.env[ENV_ENTRY] = entry;
|
|
213
219
|
process.env[ENV_BOOT] = BOOT_PATH;
|
|
220
|
+
process.env[ENV_VARS] = JSON.stringify(vars);
|
|
214
221
|
const packages = [...new Set(clientPackages)].sort();
|
|
215
222
|
process.env.KEEPSNAPPY_CLIENT_PACKAGES = JSON.stringify(packages);
|
|
216
223
|
process.env.KEEPSNAPPY_PROJECT_ROOT = projectRoot;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keepsnappy/react-native",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.20",
|
|
4
4
|
"description": "KeepSnappy in-app SDK for React Native / Expo: zero-code measurement windows, React commit hook, native signposts and log file",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"app.plugin.js",
|
|
16
|
+
"build-config.js",
|
|
16
17
|
"metro.js",
|
|
17
18
|
"expo-module.config.json",
|
|
18
19
|
"js",
|