Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,45 @@ on:
- main

jobs:
test-sdk-52:
runs-on: ubuntu-latest
env:
EXPO_SDK_TARGET: 52
steps:
- uses: actions/checkout@v4

- uses: actions/setup-java@v4
with:
distribution: "zulu"
java-version: "21"

- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "18"

- name: Install dependencies
run: npm install

- name: Run integration for SDK 52
run: ./test/run-integration.sh

test-sdk-51:
runs-on: ubuntu-latest
env:
EXPO_SDK_TARGET: 51
steps:
- uses: actions/checkout@v4

- uses: actions/setup-java@v4
with:
distribution: "zulu"
java-version: "21"

- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ To benefit from tree shaking, add the babel plugin to your project's babel confi

The `flagsModule` path must match the runtime `mergePath` in your committed flags.yml file. This plugin replaces the `BuildFlags` imports with the literal boolean values which allows the build pipeline to strip unreachable paths.

### Flagged Autolinking
### Flagged Autolinking (for RN >=75)

If your feature relies on native module behaviour, you may want to avoid linking that module if the build flag is off. To do so, specify the absolute name or relative path to the module in the base definition for your flag:

Expand All @@ -73,6 +73,8 @@ modules:
branch: some-branch-with-build
```

In order to enable this you need to pass `flaggedAutolinking: true` as an option to the expo config plugin.

Locally-referenced modules aren't currently supported (until [this 'exclude' exclusion](https://github.com/expo/expo/blob/24d5ae5f288013df19ac09a3406c6a507d781ddb/packages/expo-modules-autolinking/src/autolinking/findModules.ts#L52) can be overridden).

## Goals
Expand Down
3 changes: 3 additions & 0 deletions bin/build-flags-autolinking.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

require('../build/cli/autolinking.js')
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"author": "Wes Johnson <[email protected]>",
"license": "MIT",
"bin": {
"build-flags": "bin/build-flags.sh"
"build-flags": "bin/build-flags.sh",
"build-flags-autolinking": "bin/build-flags-autolinking.sh"
},
"main": "build/api/index.js",
"files": [
Expand Down Expand Up @@ -38,6 +39,7 @@
"scripts": {
"build": "tsc",
"test": "npm run test:unit && EXPO_SDK_TARGET=51 ./test/run-integration.sh",
"test:next": "EXPO_SDK_TARGET=52 ./test/run-integration.sh",
"test:unit": "./node_modules/.bin/jest"
}
}
94 changes: 94 additions & 0 deletions src/cli/autolinking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const commandArgs = process.argv.slice(2);

const expoAutolinking = require("expo-modules-autolinking");

const logMethod = console.log;
const logCallArgs: any[] = [];
console.log = (...args) => {
logCallArgs.push(args);
};

const findConfigFromArgs = (args: string[][]) => {
const match = args.find(
(arg) =>
Array.isArray(arg) &&
typeof arg[0] === "string" &&
arg[0][0] === "{" &&
arg[0][arg[0].length - 1] === "}"
);
if (!match) {
throw new Error(
"expo-build-flags autolinking CLI: No match for expected react-native-config object in stdout"
);
}

try {
return JSON.parse(match[0]);
} catch (e: any) {
throw new Error(
`expo-build-flags autolinking CLI: Failed to parse react-native-config JSON from stdout: ${e.message}`
);
}
};

type ConfigOutput = {
root: string;
reactNativePath: string;
project: {
ios?: { sourceDir: string };
android?: { sourceDir: string };
};
dependencies: Record<string, { name: string; root: string; platforms: any }>;
};

const getPlatform = () => {
const platformIndex = commandArgs.indexOf("-p");
if (platformIndex === -1) {
throw new Error(
"expo-build-flags autolinking CLI: No platform (-p) argument provided"
);
}

const platform = commandArgs[platformIndex + 1];
if (platform !== "ios" && platform !== "android") {
throw new Error(
`expo-build-flags autolinking CLI: Invalid platform provided "${platform}"`
);
}

return platform;
};

const getExclusions = () => {
return commandArgs.reduce((acc, arg, idx) => {
const next = commandArgs[idx + 1];
if (arg === "-x" && typeof next === "string") {
return acc.concat(next);
}
return acc;
}, [] as string[]);
};

const processConfig = (config: ConfigOutput) => {
const excludedDependencies = getExclusions();
const updatedConfig = {
...config,
dependencies: Object.fromEntries(
Object.entries(config.dependencies).filter(([key]) => {
return !excludedDependencies.includes(key);
})
),
};

return JSON.stringify(updatedConfig);
};

expoAutolinking([
"react-native-config",
"--json",
"--platform",
getPlatform(),
]).then(() => {
console.log = logMethod;
console.log(processConfig(findConfigFromArgs(logCallArgs)));
});
102 changes: 92 additions & 10 deletions src/config-plugin/withFlaggedAutolinking.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import fs from "fs";
import { jest, describe, it, expect, beforeEach } from "@jest/globals";
import {
updatePodfileAutolinkCall,
updateGradleAutolinkCall,
updateGradleReactNativeAutolinkCall,
updateGradleExpoModulesAutolinkCall,
updatePodfileReactNativeAutolinkCallForSDK51,
updatePodfileExpoModulesAutolinkCall,
} from "./withFlaggedAutolinking";

describe("withFlaggedAutolinking", () => {
describe("updatePodfileAutolinkCall", () => {
describe("updatePodfileExpoModulesAutolinkCall", () => {
const podfileContents = fs.readFileSync(
"src/config-plugin/fixtures/Podfile",
"utf8"
);

it("should replace use_expo_modules! with exclude options in call", () => {
const updatedContents = updatePodfileAutolinkCall(podfileContents, {
exclude: ["react-native-device-info"],
});
const updatedContents = updatePodfileExpoModulesAutolinkCall(
podfileContents,
{
exclude: ["react-native-device-info"],
}
);
const updatedLine = updatedContents
.split("\n")
.find((line) =>
Expand All @@ -30,16 +35,56 @@ describe("withFlaggedAutolinking", () => {
});
});

describe("updateGradleAutolinkCall", () => {
describe("updateGradleReactNativeAutolinkCall", () => {
const gradleSettingsContents = fs.readFileSync(
"src/config-plugin/fixtures/settings.gradle",
"utf8"
);

it("should override config command before passing to RN autolinking call", () => {
const updatedContents = updateGradleReactNativeAutolinkCall(
gradleSettingsContents,
{
exclude: ["react-native-device-info", "some-other-module"],
}
);
const updatedLineIndex = updatedContents
.split("\n")
.findIndex((line) =>
line.trim().startsWith("// expo-build-flags autolinking override")
);

expect(updatedLineIndex).toBeGreaterThan(-1);

const snapshotLineOffset = updatedLineIndex + 1;
const linesToSnapshot = 5;
const matchLines = updatedContents
.split("\n")
.slice(snapshotLineOffset, snapshotLineOffset + linesToSnapshot)
.join("\n");
expect(matchLines).toMatchInlineSnapshot(`
" command = [
'./node_modules/.bin/build-flags-autolinking',
'-p', 'android',
'-x', 'react-native-device-info', '-x', 'some-other-module'
].toList()"
`);
});
});

describe("updateGradleExpoModulesAutolinkCall", () => {
const gradleSettingsContents = fs.readFileSync(
"src/config-plugin/fixtures/settings.gradle",
"utf8"
);

it("should replace useExpoModules() with exclude options in call", () => {
const updatedContents = updateGradleAutolinkCall(gradleSettingsContents, {
exclude: ["react-native-device-info", "some-other-module"],
});
const updatedContents = updateGradleExpoModulesAutolinkCall(
gradleSettingsContents,
{
exclude: ["react-native-device-info", "some-other-module"],
}
);
const lines = updatedContents.split("\n");
const updatedLine = lines.find((line) =>
line.trim().startsWith("useExpoModules")
Expand All @@ -50,4 +95,41 @@ describe("withFlaggedAutolinking", () => {
);
});
});

describe("updatePodfileReactNativeAutolinkCallForSDK51", () => {
const podfileContents = fs.readFileSync(
"src/config-plugin/fixtures/Podfile",
"utf8"
);

it("should replace origin_autolinking_method.call(config_command) with custom autolinking command", () => {
const updatedContents = updatePodfileReactNativeAutolinkCallForSDK51(
podfileContents,
{
exclude: ["react-native-device-info", "react-native-reanimated"],
}
);
const updatedLineIndex = updatedContents
.split("\n")
.findIndex((line) =>
line.trim().startsWith("# expo-build-flags autolinking override")
);

expect(updatedLineIndex).toBeGreaterThan(-1);

const snapshotLineOffset = updatedLineIndex + 1;
const linesToSnapshot = 5;
const matchLines = updatedContents
.split("\n")
.slice(snapshotLineOffset, snapshotLineOffset + linesToSnapshot)
.join("\n");
expect(matchLines).toMatchInlineSnapshot(`
" config_command = [
'../node_modules/.bin/build-flags-autolinking',
'-p', 'ios',
'-x', 'react-native-device-info', '-x', 'react-native-reanimated'
]"
`);
});
});
});
Loading