expo-modules-autolinking 0.0.3 → 0.3.2
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/build/ExpoModuleConfig.d.ts +24 -0
- package/build/ExpoModuleConfig.js +42 -0
- package/build/ExpoModuleConfig.js.map +1 -0
- package/build/autolinking.d.ts +0 -4
- package/build/autolinking.js +104 -25
- package/build/autolinking.js.map +1 -1
- package/build/index.js +11 -5
- package/build/index.js.map +1 -1
- package/build/platforms/android.js +23 -6
- package/build/platforms/android.js.map +1 -1
- package/build/platforms/ios.d.ts +4 -0
- package/build/platforms/ios.js +38 -1
- package/build/platforms/ios.js.map +1 -1
- package/build/types.d.ts +22 -2
- package/build/types.js.map +1 -1
- package/e2e/TestUtils.ts +53 -0
- package/jest.config.js +5 -0
- package/package.json +5 -3
- package/scripts/android/autolinking_implementation.gradle +248 -0
- package/scripts/ios/autolinking_manager.rb +137 -0
- package/scripts/ios/cocoapods/target_definition.rb +22 -0
- package/scripts/ios/cocoapods/user_project_integrator.rb +61 -0
- package/scripts/ios/constants.rb +7 -0
- package/scripts/ios/package.rb +33 -0
- package/scripts/ios/project_integrator.rb +102 -0
- package/src/ExpoModuleConfig.ts +38 -0
- package/src/autolinking.ts +120 -24
- package/src/index.ts +15 -10
- package/src/platforms/android.ts +28 -9
- package/src/platforms/ios.ts +45 -0
- package/src/types.ts +25 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
|
|
2
|
+
module Expo
|
|
3
|
+
module ProjectIntegrator
|
|
4
|
+
include Pod
|
|
5
|
+
|
|
6
|
+
# Integrates targets in the project and generates modules providers.
|
|
7
|
+
def self.integrate_targets_in_project(targets, project)
|
|
8
|
+
# Find the targets that use expo modules and need the modules provider
|
|
9
|
+
targets_with_modules_provider = targets.select do |target|
|
|
10
|
+
autolinking_manager = target.target_definition.autolinking_manager
|
|
11
|
+
autolinking_manager.present?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Find existing PBXGroup for modules providers.
|
|
15
|
+
generated_group = modules_providers_group(project, targets_with_modules_provider.any?)
|
|
16
|
+
|
|
17
|
+
# Return early when the modules providers group has not been auto-created in the line above.
|
|
18
|
+
return if generated_group.nil?
|
|
19
|
+
|
|
20
|
+
# Remove existing groups for targets without modules provider.
|
|
21
|
+
generated_group.groups.each do |group|
|
|
22
|
+
# Remove the group if there is no target for this group.
|
|
23
|
+
if targets.none? { |target| target.target_definition.name == group.name && targets_with_modules_provider.include?(target) }
|
|
24
|
+
recursively_remove_group(group)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
targets_with_modules_provider.sort_by(&:name).each do |target|
|
|
29
|
+
# The user target name (without `Pods-` prefix which is a part of `target.name`)
|
|
30
|
+
target_name = target.target_definition.name
|
|
31
|
+
|
|
32
|
+
UI.message '- Generating the provider for ' << target_name.green << ' target' do
|
|
33
|
+
# PBXNativeTarget of the user target
|
|
34
|
+
native_target = project.native_targets.find { |native_target| native_target.name == target_name }
|
|
35
|
+
|
|
36
|
+
# Shorthand ref for the autolinking manager.
|
|
37
|
+
autolinking_manager = target.target_definition.autolinking_manager
|
|
38
|
+
|
|
39
|
+
# Absolute path to `Pods/Target Support Files/<pods target name>/<modules provider file>` within the project path
|
|
40
|
+
modules_provider_path = File.join(target.support_files_dir, autolinking_manager.modules_provider_name)
|
|
41
|
+
|
|
42
|
+
# Run `expo-modules-autolinking` command to generate the file
|
|
43
|
+
autolinking_manager.generate_package_list(target_name, modules_provider_path)
|
|
44
|
+
|
|
45
|
+
# PBXGroup for generated files per target
|
|
46
|
+
generated_target_group = generated_group.find_subpath(target_name, true)
|
|
47
|
+
|
|
48
|
+
# PBXGroup uses relative paths, so we need to strip the absolute path
|
|
49
|
+
modules_provider_relative_path = Pathname.new(modules_provider_path).relative_path_from(generated_target_group.real_path).to_s
|
|
50
|
+
|
|
51
|
+
if generated_target_group.find_file_by_path(modules_provider_relative_path).nil?
|
|
52
|
+
# Create new PBXFileReference if the modules provider is not in the group yet
|
|
53
|
+
modules_provider_file_reference = generated_target_group.new_file(modules_provider_path)
|
|
54
|
+
|
|
55
|
+
if native_target.source_build_phase.files_references.find { |ref| ref.present? && ref.path == modules_provider_relative_path }.nil?
|
|
56
|
+
# Put newly created PBXFileReference to the source files of the native target
|
|
57
|
+
native_target.add_file_references([modules_provider_file_reference])
|
|
58
|
+
project.mark_dirty!
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Remove the generated group if it has nothing left inside
|
|
65
|
+
if targets_with_modules_provider.empty?
|
|
66
|
+
recursively_remove_group(generated_group)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.recursively_remove_group(group)
|
|
71
|
+
return if group.nil?
|
|
72
|
+
|
|
73
|
+
UI.message '- Removing ' << group.name.green << ' group' do
|
|
74
|
+
group.recursive_children.each do |child|
|
|
75
|
+
UI.message ' - Removing a reference to ' << child.name.green
|
|
76
|
+
child.remove_from_project
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
group.remove_from_project
|
|
80
|
+
group.project.mark_dirty!
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# CocoaPods doesn't properly remove file references from the build phase
|
|
85
|
+
# They appear as nils and it's safe to just delete them from native targets
|
|
86
|
+
def self.remove_nils_from_source_files(project)
|
|
87
|
+
project.native_targets.each do |native_target|
|
|
88
|
+
native_target.source_build_phase.files.each do |build_file|
|
|
89
|
+
next unless build_file.file_ref.nil?
|
|
90
|
+
|
|
91
|
+
build_file.remove_from_project
|
|
92
|
+
project.mark_dirty!
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.modules_providers_group(project, autocreate = false)
|
|
98
|
+
project.main_group.find_subpath(Constants::GENERATED_GROUP_NAME, autocreate)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
end # module ExpoAutolinkingExtension
|
|
102
|
+
end # module Expo
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RawExpoModuleConfig, SupportedPlatform } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A class that wraps the raw config (`expo-module.json` or `unimodule.json`).
|
|
5
|
+
*/
|
|
6
|
+
export class ExpoModuleConfig {
|
|
7
|
+
constructor(readonly rawConfig: RawExpoModuleConfig) {}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Whether the module supports given platform.
|
|
11
|
+
*/
|
|
12
|
+
supportsPlatform(platform: SupportedPlatform): boolean {
|
|
13
|
+
return this.rawConfig.platforms?.includes(platform) ?? false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns a list of names of Swift native modules classes to put to the generated modules provider file.
|
|
18
|
+
*/
|
|
19
|
+
iosModulesClassNames() {
|
|
20
|
+
return this.rawConfig.ios?.modulesClassNames ?? [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns serializable raw config.
|
|
25
|
+
*/
|
|
26
|
+
toJSON(): RawExpoModuleConfig {
|
|
27
|
+
return this.rawConfig;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Reads the config at given path and returns the config wrapped by `ExpoModuleConfig` class.
|
|
33
|
+
*/
|
|
34
|
+
export function requireAndResolveExpoModuleConfig(path: string): ExpoModuleConfig {
|
|
35
|
+
// TODO: Validate the raw config against a schema.
|
|
36
|
+
// TODO: Support for `*.js` files, not only static `*.json`.
|
|
37
|
+
return new ExpoModuleConfig(require(path) as RawExpoModuleConfig);
|
|
38
|
+
}
|
package/src/autolinking.ts
CHANGED
|
@@ -2,8 +2,10 @@ import chalk from 'chalk';
|
|
|
2
2
|
import glob from 'fast-glob';
|
|
3
3
|
import findUp from 'find-up';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
|
+
import { createRequire } from 'module';
|
|
5
6
|
import path from 'path';
|
|
6
7
|
|
|
8
|
+
import { requireAndResolveExpoModuleConfig } from './ExpoModuleConfig';
|
|
7
9
|
import {
|
|
8
10
|
GenerateOptions,
|
|
9
11
|
ModuleDescriptor,
|
|
@@ -13,8 +15,24 @@ import {
|
|
|
13
15
|
SearchResults,
|
|
14
16
|
} from './types';
|
|
15
17
|
|
|
16
|
-
//
|
|
17
|
-
const
|
|
18
|
+
// Names of the config files. From lowest to highest priority.
|
|
19
|
+
const EXPO_MODULE_CONFIG_FILENAMES = ['unimodule.json', 'expo-module.config.json'];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Path to the `package.json` of the closest project in the current working dir.
|
|
23
|
+
*/
|
|
24
|
+
const projectPackageJsonPath = findUp.sync('package.json', { cwd: process.cwd() }) as string;
|
|
25
|
+
|
|
26
|
+
// This won't happen in usual scenarios, but we need to unwrap the optional path :)
|
|
27
|
+
if (!projectPackageJsonPath) {
|
|
28
|
+
throw new Error(`Couldn't find "package.json" up from path "${process.cwd()}"`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Custom `require` that resolves from the current working dir instead of this script path.
|
|
33
|
+
* **Requires Node v12.2.0**
|
|
34
|
+
*/
|
|
35
|
+
const projectRequire = createRequire(projectPackageJsonPath);
|
|
18
36
|
|
|
19
37
|
/**
|
|
20
38
|
* Resolves autolinking search paths. If none is provided, it accumulates all node_modules when
|
|
@@ -25,17 +43,10 @@ export async function resolveSearchPathsAsync(
|
|
|
25
43
|
cwd: string
|
|
26
44
|
): Promise<string[]> {
|
|
27
45
|
return searchPaths && searchPaths.length > 0
|
|
28
|
-
? searchPaths.map(searchPath => path.resolve(cwd, searchPath))
|
|
46
|
+
? searchPaths.map((searchPath) => path.resolve(cwd, searchPath))
|
|
29
47
|
: await findDefaultPathsAsync(cwd);
|
|
30
48
|
}
|
|
31
49
|
|
|
32
|
-
/**
|
|
33
|
-
* Finds project's package.json and returns its path.
|
|
34
|
-
*/
|
|
35
|
-
export async function findPackageJsonPathAsync(): Promise<string | null> {
|
|
36
|
-
return (await findUp('package.json', { cwd: process.cwd() })) ?? null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
50
|
/**
|
|
40
51
|
* Looks up for workspace's `node_modules` paths.
|
|
41
52
|
*/
|
|
@@ -59,19 +70,32 @@ export async function findModulesAsync(providedOptions: SearchOptions): Promise<
|
|
|
59
70
|
const results: SearchResults = {};
|
|
60
71
|
|
|
61
72
|
for (const searchPath of options.searchPaths) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
const bracedFilenames = '{' + EXPO_MODULE_CONFIG_FILENAMES.join(',') + '}';
|
|
74
|
+
const paths = await glob([`*/${bracedFilenames}`, `@*/*/${bracedFilenames}`], {
|
|
75
|
+
cwd: searchPath,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// If the package has multiple configs (e.g. `unimodule.json` and `expo-module.config.json` during the transition time)
|
|
79
|
+
// then we want to give `expo-module.config.json` the priority.
|
|
80
|
+
const uniqueConfigPaths: string[] = Object.values(
|
|
81
|
+
paths.reduce<Record<string, string>>((acc, configPath) => {
|
|
82
|
+
const dirname = path.dirname(configPath);
|
|
83
|
+
|
|
84
|
+
if (!acc[dirname] || configPriority(configPath) > configPriority(acc[dirname])) {
|
|
85
|
+
acc[dirname] = configPath;
|
|
86
|
+
}
|
|
87
|
+
return acc;
|
|
88
|
+
}, {})
|
|
67
89
|
);
|
|
68
90
|
|
|
69
|
-
for (const packageConfigPath of
|
|
91
|
+
for (const packageConfigPath of uniqueConfigPaths) {
|
|
70
92
|
const packagePath = await fs.realpath(path.join(searchPath, path.dirname(packageConfigPath)));
|
|
71
|
-
const
|
|
93
|
+
const expoModuleConfig = requireAndResolveExpoModuleConfig(
|
|
94
|
+
path.join(packagePath, path.basename(packageConfigPath))
|
|
95
|
+
);
|
|
72
96
|
const { name, version } = require(path.join(packagePath, 'package.json'));
|
|
73
97
|
|
|
74
|
-
if (options.exclude?.includes(name) || !
|
|
98
|
+
if (options.exclude?.includes(name) || !expoModuleConfig.supportsPlatform(options.platform)) {
|
|
75
99
|
continue;
|
|
76
100
|
}
|
|
77
101
|
|
|
@@ -82,8 +106,12 @@ export async function findModulesAsync(providedOptions: SearchOptions): Promise<
|
|
|
82
106
|
|
|
83
107
|
if (!results[name]) {
|
|
84
108
|
// The revision that was found first will be the main one.
|
|
85
|
-
// An array of duplicates
|
|
86
|
-
results[name] = {
|
|
109
|
+
// An array of duplicates and the config are needed only here.
|
|
110
|
+
results[name] = {
|
|
111
|
+
...currentRevision,
|
|
112
|
+
config: expoModuleConfig,
|
|
113
|
+
duplicates: [],
|
|
114
|
+
};
|
|
87
115
|
} else if (
|
|
88
116
|
results[name].path !== packagePath &&
|
|
89
117
|
results[name].duplicates?.every(({ path }) => path !== packagePath)
|
|
@@ -92,7 +120,69 @@ export async function findModulesAsync(providedOptions: SearchOptions): Promise<
|
|
|
92
120
|
}
|
|
93
121
|
}
|
|
94
122
|
}
|
|
95
|
-
|
|
123
|
+
|
|
124
|
+
// It doesn't make much sense to strip modules if there is only one search path.
|
|
125
|
+
// Workspace root usually doesn't specify all its dependencies (see Expo Go),
|
|
126
|
+
// so in this case we should link everything.
|
|
127
|
+
if (options.searchPaths.length <= 1) {
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
return filterToProjectDependencies(results);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Filters out packages that are not the dependencies of the project.
|
|
135
|
+
*/
|
|
136
|
+
function filterToProjectDependencies(results: SearchResults) {
|
|
137
|
+
const filteredResults: SearchResults = {};
|
|
138
|
+
const visitedPackages = new Set<string>();
|
|
139
|
+
|
|
140
|
+
// Helper for traversing the dependency hierarchy.
|
|
141
|
+
function visitPackage(packageJsonPath: string) {
|
|
142
|
+
const packageJson = require(packageJsonPath);
|
|
143
|
+
|
|
144
|
+
// Prevent getting into the recursive loop.
|
|
145
|
+
if (visitedPackages.has(packageJson.name)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
visitedPackages.add(packageJson.name);
|
|
149
|
+
|
|
150
|
+
// Iterate over the dependencies to find transitive modules.
|
|
151
|
+
for (const dependencyName in packageJson.dependencies) {
|
|
152
|
+
const dependencyResult = results[dependencyName];
|
|
153
|
+
|
|
154
|
+
if (!filteredResults[dependencyName]) {
|
|
155
|
+
let dependencyPackageJsonPath: string;
|
|
156
|
+
|
|
157
|
+
if (dependencyResult) {
|
|
158
|
+
filteredResults[dependencyName] = dependencyResult;
|
|
159
|
+
dependencyPackageJsonPath = path.join(dependencyResult.path, 'package.json');
|
|
160
|
+
} else {
|
|
161
|
+
try {
|
|
162
|
+
dependencyPackageJsonPath = projectRequire.resolve(`${dependencyName}/package.json`);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// Some packages don't include package.json in its `exports` field,
|
|
165
|
+
// but none of our packages do that, so it seems fine to just ignore that type of error.
|
|
166
|
+
// Related issue: https://github.com/react-native-community/cli/issues/1168
|
|
167
|
+
if (error.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
|
|
168
|
+
console.warn(
|
|
169
|
+
chalk.yellow(`⚠️ Cannot resolve the path to "${dependencyName}" package.`)
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Visit the dependency package.
|
|
177
|
+
visitPackage(dependencyPackageJsonPath);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Visit project's package.
|
|
183
|
+
visitPackage(projectPackageJsonPath);
|
|
184
|
+
|
|
185
|
+
return filteredResults;
|
|
96
186
|
}
|
|
97
187
|
|
|
98
188
|
/**
|
|
@@ -104,8 +194,7 @@ export async function findModulesAsync(providedOptions: SearchOptions): Promise<
|
|
|
104
194
|
export async function mergeLinkingOptionsAsync<OptionsType extends SearchOptions>(
|
|
105
195
|
providedOptions: OptionsType
|
|
106
196
|
): Promise<OptionsType> {
|
|
107
|
-
const
|
|
108
|
-
const packageJson = packageJsonPath ? require(packageJsonPath) : {};
|
|
197
|
+
const packageJson = require(projectPackageJsonPath);
|
|
109
198
|
const baseOptions = packageJson.expo?.autolinking;
|
|
110
199
|
const platformOptions = providedOptions.platform && baseOptions?.[providedOptions.platform];
|
|
111
200
|
const finalOptions = Object.assign(
|
|
@@ -126,7 +215,7 @@ export async function mergeLinkingOptionsAsync<OptionsType extends SearchOptions
|
|
|
126
215
|
*/
|
|
127
216
|
export function verifySearchResults(searchResults: SearchResults): number {
|
|
128
217
|
const cwd = process.cwd();
|
|
129
|
-
const relativePath: (pkg: PackageRevision) => string = pkg => path.relative(cwd, pkg.path);
|
|
218
|
+
const relativePath: (pkg: PackageRevision) => string = (pkg) => path.relative(cwd, pkg.path);
|
|
130
219
|
let counter = 0;
|
|
131
220
|
|
|
132
221
|
for (const moduleName in searchResults) {
|
|
@@ -198,3 +287,10 @@ export async function generatePackageListAsync(
|
|
|
198
287
|
);
|
|
199
288
|
}
|
|
200
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Returns the priority of the config at given path. Higher number means higher priority.
|
|
293
|
+
*/
|
|
294
|
+
function configPriority(fullpath: string): number {
|
|
295
|
+
return EXPO_MODULE_CONFIG_FILENAMES.indexOf(path.basename(fullpath));
|
|
296
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,11 @@ function registerSearchCommand<OptionsType extends SearchOptions>(
|
|
|
28
28
|
'Package names to exclude when looking up for modules.',
|
|
29
29
|
(value, previous) => (previous ?? []).concat(value)
|
|
30
30
|
)
|
|
31
|
+
.option(
|
|
32
|
+
'-p, --platform [platform]',
|
|
33
|
+
'The platform that the resulting modules must support. Available options: "ios", "android"',
|
|
34
|
+
'ios'
|
|
35
|
+
)
|
|
31
36
|
.action(async (searchPaths, providedOptions) => {
|
|
32
37
|
const options = await mergeLinkingOptionsAsync<OptionsType>({
|
|
33
38
|
...providedOptions,
|
|
@@ -45,21 +50,21 @@ function registerResolveCommand<OptionsType extends ResolveOptions>(
|
|
|
45
50
|
commandName: string,
|
|
46
51
|
fn: (search: SearchResults, options: OptionsType) => any
|
|
47
52
|
) {
|
|
48
|
-
return registerSearchCommand<OptionsType>(commandName, fn)
|
|
49
|
-
'-p, --platform [platform]',
|
|
50
|
-
'The platform that the resulted modules must support. Available options: "ios", "android"',
|
|
51
|
-
'ios'
|
|
52
|
-
);
|
|
53
|
+
return registerSearchCommand<OptionsType>(commandName, fn);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
module.exports = async function(args: string[]) {
|
|
56
|
+
module.exports = async function (args: string[]) {
|
|
56
57
|
// Searches for available expo modules.
|
|
57
|
-
registerSearchCommand('search', async results => {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
registerSearchCommand<SearchOptions & { json?: boolean }>('search', async (results, options) => {
|
|
59
|
+
if (options.json) {
|
|
60
|
+
console.log(JSON.stringify(results));
|
|
61
|
+
} else {
|
|
62
|
+
console.log(require('util').inspect(results, false, null, true));
|
|
63
|
+
}
|
|
64
|
+
}).option<boolean>('-j, --json', 'Output results in the plain JSON format.', () => true, false);
|
|
60
65
|
|
|
61
66
|
// Checks whether there are no resolving issues in the current setup.
|
|
62
|
-
registerSearchCommand('verify', results => {
|
|
67
|
+
registerSearchCommand('verify', (results) => {
|
|
63
68
|
const numberOfDuplicates = verifySearchResults(results);
|
|
64
69
|
if (!numberOfDuplicates) {
|
|
65
70
|
console.log('✅ Everything is fine!');
|
package/src/platforms/android.ts
CHANGED
|
@@ -31,6 +31,12 @@ export async function resolveModuleAsync(
|
|
|
31
31
|
cwd: revision.path,
|
|
32
32
|
ignore: ['**/node_modules/**'],
|
|
33
33
|
});
|
|
34
|
+
|
|
35
|
+
// Just in case where the module doesn't have its own `build.gradle`.
|
|
36
|
+
if (!buildGradleFile) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
const sourceDir = path.dirname(path.join(revision.path, buildGradleFile));
|
|
35
41
|
|
|
36
42
|
return {
|
|
@@ -46,20 +52,27 @@ async function generatePackageListFileContentAsync(
|
|
|
46
52
|
modules: ModuleDescriptor[],
|
|
47
53
|
namespace: string
|
|
48
54
|
): Promise<string> {
|
|
49
|
-
|
|
55
|
+
// TODO: Instead of ignoring `expo` here, make the package class paths configurable from `expo-module.config.json`.
|
|
56
|
+
const packagesClasses = await findAndroidPackagesAsync(
|
|
57
|
+
modules.filter((module) => module.packageName !== 'expo')
|
|
58
|
+
);
|
|
50
59
|
|
|
51
60
|
return `package ${namespace};
|
|
52
61
|
|
|
53
62
|
import java.util.Arrays;
|
|
54
63
|
import java.util.List;
|
|
55
|
-
import
|
|
64
|
+
import expo.modules.core.interfaces.Package;
|
|
56
65
|
|
|
57
66
|
public class ExpoModulesPackageList {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
${packagesClasses.map(packageClass => ` new ${packageClass}()`).join(',\n')}
|
|
67
|
+
private static class LazyHolder {
|
|
68
|
+
static final List<Package> packagesList = Arrays.<Package>asList(
|
|
69
|
+
${packagesClasses.map((packageClass) => ` new ${packageClass}()`).join(',\n')}
|
|
61
70
|
);
|
|
62
71
|
}
|
|
72
|
+
|
|
73
|
+
public static List<Package> getPackageList() {
|
|
74
|
+
return LazyHolder.packagesList;
|
|
75
|
+
}
|
|
63
76
|
}
|
|
64
77
|
`;
|
|
65
78
|
}
|
|
@@ -68,7 +81,7 @@ async function findAndroidPackagesAsync(modules: ModuleDescriptor[]): Promise<st
|
|
|
68
81
|
const classes: string[] = [];
|
|
69
82
|
|
|
70
83
|
await Promise.all(
|
|
71
|
-
modules.map(async module => {
|
|
84
|
+
modules.map(async (module) => {
|
|
72
85
|
const files = await glob('src/**/*Package.{java,kt}', {
|
|
73
86
|
cwd: module.sourceDir,
|
|
74
87
|
});
|
|
@@ -76,10 +89,16 @@ async function findAndroidPackagesAsync(modules: ModuleDescriptor[]): Promise<st
|
|
|
76
89
|
for (const file of files) {
|
|
77
90
|
const fileContent = await fs.readFile(path.join(module.sourceDir, file), 'utf8');
|
|
78
91
|
|
|
92
|
+
const packageRegex = (() => {
|
|
93
|
+
if (process.env.EXPO_SHOULD_USE_LEGACY_PACKAGE_INTERFACE) {
|
|
94
|
+
return /\bimport\s+org\.unimodules\.core\.(interfaces\.Package|BasePackage)\b/;
|
|
95
|
+
} else {
|
|
96
|
+
return /\bimport\s+expo\.modules\.core\.(interfaces\.Package|BasePackage)\b/;
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
|
|
79
100
|
// Very naive check to skip non-expo packages
|
|
80
|
-
if (
|
|
81
|
-
!/\bimport\s+org\.unimodules\.core\.(interfaces\.Package|BasePackage)\b/.test(fileContent)
|
|
82
|
-
) {
|
|
101
|
+
if (!packageRegex.test(fileContent)) {
|
|
83
102
|
continue;
|
|
84
103
|
}
|
|
85
104
|
|
package/src/platforms/ios.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import glob from 'fast-glob';
|
|
2
|
+
import fs from 'fs-extra';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
|
|
4
5
|
import { ModuleDescriptor, PackageRevision, SearchOptions } from '../types';
|
|
@@ -27,5 +28,49 @@ export async function resolveModuleAsync(
|
|
|
27
28
|
podName,
|
|
28
29
|
podspecDir,
|
|
29
30
|
flags: options.flags,
|
|
31
|
+
modulesClassNames: revision.config?.iosModulesClassNames(),
|
|
30
32
|
};
|
|
31
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generates Swift file that contains all autolinked Swift packages.
|
|
37
|
+
*/
|
|
38
|
+
export async function generatePackageListAsync(
|
|
39
|
+
modules: ModuleDescriptor[],
|
|
40
|
+
targetPath: string
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
const className = path.basename(targetPath, path.extname(targetPath));
|
|
43
|
+
const generatedFileContent = await generatePackageListFileContentAsync(modules, className);
|
|
44
|
+
|
|
45
|
+
await fs.outputFile(targetPath, generatedFileContent);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generates the string to put into the generated package list.
|
|
50
|
+
*/
|
|
51
|
+
async function generatePackageListFileContentAsync(
|
|
52
|
+
modules: ModuleDescriptor[],
|
|
53
|
+
className: string
|
|
54
|
+
): Promise<string> {
|
|
55
|
+
const modulesToProvide = modules.filter((module) => module.modulesClassNames.length > 0);
|
|
56
|
+
const pods = modulesToProvide.map((module) => module.podName);
|
|
57
|
+
const classNames = [].concat(...modulesToProvide.map((module) => module.modulesClassNames));
|
|
58
|
+
|
|
59
|
+
return `/**
|
|
60
|
+
* Automatically generated by expo-modules-autolinking.
|
|
61
|
+
*
|
|
62
|
+
* This autogenerated class provides a list of classes of native Expo modules,
|
|
63
|
+
* but only these that are written in Swift and use the new API for creating Expo modules.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
import ExpoModulesCore
|
|
67
|
+
${pods.map((podName) => `import ${podName}\n`).join('')}
|
|
68
|
+
@objc(${className})
|
|
69
|
+
public class ${className}: ModulesProvider {
|
|
70
|
+
public override func getModuleClasses() -> [AnyModule.Type] {
|
|
71
|
+
return [${classNames.map((className) => `\n ${className}.self`).join(',')}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { ExpoModuleConfig } from './ExpoModuleConfig';
|
|
2
|
+
|
|
3
|
+
export type SupportedPlatform = 'ios' | 'android' | 'web';
|
|
2
4
|
|
|
3
5
|
export interface SearchOptions {
|
|
4
6
|
// Available in the CLI
|
|
@@ -17,13 +19,14 @@ export interface ResolveOptions extends SearchOptions {
|
|
|
17
19
|
|
|
18
20
|
export interface GenerateOptions extends ResolveOptions {
|
|
19
21
|
target: string;
|
|
20
|
-
namespace
|
|
22
|
+
namespace?: string;
|
|
21
23
|
empty?: boolean;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export type PackageRevision = {
|
|
25
27
|
path: string;
|
|
26
28
|
version: string;
|
|
29
|
+
config?: ExpoModuleConfig;
|
|
27
30
|
duplicates?: PackageRevision[];
|
|
28
31
|
};
|
|
29
32
|
|
|
@@ -32,3 +35,23 @@ export type SearchResults = {
|
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
export type ModuleDescriptor = Record<string, any>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Represents a raw config from `expo-module.json`.
|
|
41
|
+
*/
|
|
42
|
+
export interface RawExpoModuleConfig {
|
|
43
|
+
/**
|
|
44
|
+
* An array of supported platforms.
|
|
45
|
+
*/
|
|
46
|
+
platforms?: SupportedPlatform[];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* iOS-specific config.
|
|
50
|
+
*/
|
|
51
|
+
ios?: {
|
|
52
|
+
/**
|
|
53
|
+
* Names of Swift native modules classes to put to the generated modules provider file.
|
|
54
|
+
*/
|
|
55
|
+
modulesClassNames?: string[];
|
|
56
|
+
};
|
|
57
|
+
}
|