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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add Metro config options to kit config, removing the need to manually control Metro config. Update test app.",
"packageName": "@rnx-kit/config",
"email": "afoxman@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Publish plugin options type",
"packageName": "@rnx-kit/metro-plugin-cyclic-dependencies-detector",
"email": "afoxman@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Publish plugin options type",
"packageName": "@rnx-kit/metro-plugin-duplicates-checker",
"email": "afoxman@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "fix TPN tests",
"packageName": "@rnx-kit/third-party-notices",
"email": "afoxman@microsoft.com",
"dependentChangeType": "none"
}
10 changes: 10 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,24 @@
"dependencies": {
"@rnx-kit/config": "0.2.9",
"@rnx-kit/dep-check": "^1.5.11",
"@rnx-kit/metro-plugin-cyclic-dependencies-detector": "1.0.2",
"@rnx-kit/metro-plugin-duplicates-checker": "1.1.2",
"@rnx-kit/metro-serializer": "1.0.0",
"@rnx-kit/metro-service": "1.0.2",
"@rnx-kit/third-party-notices": "1.0.1",
"chalk": "^4.1.0"
},
"devDependencies": {
"@react-native-community/cli-types": "^5.0.1",
"@types/metro-config": "*",
"rnx-kit-scripts": "*"
},
"depcheck": {
"ignoreMatches": [
"metro",
"metro-config"
]
},
"eslintConfig": {
"extends": "@rnx-kit/eslint-config"
}
Expand Down
46 changes: 35 additions & 11 deletions packages/cli/src/bundle.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import chalk from "chalk";
import fs from "fs";
import path from "path";
import type { Config as CLIConfig } from "@react-native-community/cli-types";
import {
AllPlatforms,
BundleDefinitionWithRequiredParameters,
getKitConfig,
getBundleDefinition,
getBundlePlatformDefinition,
getKitConfig,
} from "@rnx-kit/config";
import type { Config as CLIConfig } from "@react-native-community/cli-types";
import { loadMetroConfig, bundle, BundleArgs } from "@rnx-kit/metro-service";
import { bundle, BundleArgs, loadMetroConfig } from "@rnx-kit/metro-service";
import chalk from "chalk";
import fs from "fs";
import path from "path";
import { customizeMetroConfig, validateMetroConfig } from "./metro-config";

type CLIBundleOptions = {
id?: string;
Expand Down Expand Up @@ -88,19 +89,20 @@ export async function rnxBundle(
maxWorkers,
resetCache,
});
if (!validateMetroConfig(metroConfig)) {
return Promise.resolve();
}

// get the kit config's bundle definition using the optional id
const definition = getKitConfigBundleDefinition(id);
if (!definition) {
// bundling is disabled, or the kit has no bundle definitions
console.log(
"skipping bundle -- kit configuration does not defined any bundles"
console.warn(
"skipping bundling -- kit configuration does not define any bundles"
);
return Promise.resolve();
}

console.log("Generating metro bundle(s)" + (id ? ` for id ${id}...` : "..."));

// get the list of target platforms, favoring the command-line over the bundle definition
let targetPlatforms: AllPlatforms[] = [];
if (cliBundleOptions.platform) {
Expand All @@ -109,9 +111,22 @@ export async function rnxBundle(
targetPlatforms = definition.targets;
}

if (targetPlatforms.length === 0) {
console.error("no target platforms given");
return Promise.reject(
new Error(
"No target platforms given. Update the kit configuration to include a target platform, or provide a target platform on the command-line."
)
);
}

// create a bundle for each target platform
for (const targetPlatform of targetPlatforms) {
// unpack the platform-specific bundle definition
const platformDefinition = getBundlePlatformDefinition(
definition,
targetPlatform
);
let {
entryPath,
distPath,
Expand All @@ -121,7 +136,9 @@ export async function rnxBundle(
sourceMapPath,
sourceMapSourceRootPath,
sourceMapUseAbsolutePaths,
} = getBundlePlatformDefinition(definition, targetPlatform);
} = platformDefinition;
const { detectCyclicDependencies, detectDuplicateDependencies } =
platformDefinition;

// apply command-line overrides to the platform-specific bundle definition
entryPath = cliBundleOptions.entryPath ?? entryPath;
Expand Down Expand Up @@ -153,12 +170,19 @@ export async function rnxBundle(
sourceMapPath = path.join(distPath, sourceMapPath);
}

customizeMetroConfig(
metroConfig,
detectCyclicDependencies,
detectDuplicateDependencies
);

// ensure all output directories exist
ensureDirectoryExists(path.dirname(bundlePath));
sourceMapPath && ensureDirectoryExists(path.dirname(sourceMapPath));
assetsPath && ensureDirectoryExists(assetsPath);

// create the bundle
console.log(`Bundling ${targetPlatform}...`);
await bundle(
{
assetsDest: assetsPath,
Expand Down
72 changes: 72 additions & 0 deletions packages/cli/src/metro-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { ConfigT, InputConfigT, SerializerConfigT } from "metro-config";
import {
CyclicDependencies,
PluginOptions as CyclicDetectorOptions,
} from "@rnx-kit/metro-plugin-cyclic-dependencies-detector";
import {
DuplicateDependencies,
Options as DuplicateDetectorOptions,
} from "@rnx-kit/metro-plugin-duplicates-checker";
import { MetroSerializer, MetroPlugin } from "@rnx-kit/metro-serializer";

function reportError(prop: string) {
console.error(
"error: Metro configuration property '%o' should not be set. This command may overwrite it, based on command-line arguments or kit configuration.",
prop
);
}

// Validate the Metro configuration. Make sure that any reserved
// fields haven't been set by the app. We reserve certain fields
// so that we can control them through configuration.
export function validateMetroConfig(metroConfig: ConfigT): boolean {
if (metroConfig.serializer.customSerializer) {
reportError("customSerializer");
return false;
}

return true;
}

// Customize the Metro configuration
export function customizeMetroConfig(
metroConfigReadonly: InputConfigT,
detectCyclicDependencies: boolean | CyclicDetectorOptions,
detectDuplicateDependencies: boolean | DuplicateDetectorOptions
): void {
// We will be making changes to the Metro configuration. Coerce from a
// type with readonly props to a type where the props are writeable.
const metroConfig = metroConfigReadonly as InputConfigT;

const plugins: MetroPlugin[] = [];
if (typeof detectDuplicateDependencies === "boolean") {
plugins.push(DuplicateDependencies());
} else if (typeof detectDuplicateDependencies === "object") {
plugins.push(DuplicateDependencies(detectDuplicateDependencies));
}
if (typeof detectCyclicDependencies === "boolean") {
plugins.push(CyclicDependencies());
} else if (typeof detectCyclicDependencies === "object") {
plugins.push(CyclicDependencies(detectCyclicDependencies));
}

if (plugins.length > 0) {
// MetroSerializer acts as a CustomSerializer, and it works with both
// older and newer versions of Metro. Older versions expect a return
// value, while newer versions expect a promise.
//
// Its return type is the union of both the value and the promise.
// This makes TypeScript upset because for any given version of Metro,
// it's one or the other. Not both.
//
// Since it can handle either scenario, just coerce it to whatever
// the current version of Metro expects.
const serializer = MetroSerializer(
plugins
) as SerializerConfigT["customSerializer"];

metroConfig.serializer.customSerializer = serializer;
} else {
delete metroConfig.serializer.customSerializer;
}
}
2 changes: 2 additions & 0 deletions packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"semver": "^7.0.0"
},
"devDependencies": {
"@rnx-kit/metro-plugin-cyclic-dependencies-detector": "*",
"@rnx-kit/metro-plugin-duplicates-checker": "*",
"@types/metro": "*",
"@types/node": "^14.15.0",
"@types/semver": "^7.0.0",
Expand Down
32 changes: 32 additions & 0 deletions packages/config/src/bundleConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { OutputOptions } from "metro";
import type { PluginOptions as CyclicDetectorOptions } from "@rnx-kit/metro-plugin-cyclic-dependencies-detector";
import type { Options as DuplicateDetectorOptions } from "@rnx-kit/metro-plugin-duplicates-checker";

/**
* List of supported kit platforms.
Expand Down Expand Up @@ -40,6 +42,36 @@ export type BundleRequiredParameters = {
* @default "index"
*/
bundlePrefix: string;

/**
* Choose whether to detect cycles in the dependency graph. If true, then a default set
* of options will be used. Otherwise the object allows for fine-grained control over
* the detection process.
*
* @default true
*/
detectCyclicDependencies: boolean | CyclicDetectorOptions;

/**
* Choose whether to detect duplicate packages in the dependency graph.
*
* A duplicate error happens when a package is imported from two or more unique paths,
* even if the versions are all the same. Duplicate packages increase bundle size and
* can lead to unexpected errors.
*
* If true, then a default set of options will be used. Otherwise the object allows for
* fine-grained control over the detection process.
*
* @default true
*/
detectDuplicateDependencies: boolean | DuplicateDetectorOptions;

/**
* Choose whether to type-check source files using TypeScript.
*
* @default true
*/
typescriptValidation: boolean;
};

export type BundleParameters = Partial<BundleRequiredParameters> & {
Expand Down
3 changes: 3 additions & 0 deletions packages/config/src/getBundleDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export function getBundleDefinition(
distPath: "dist",
assetsPath: "dist",
bundlePrefix: "index",
detectCyclicDependencies: true,
detectDuplicateDependencies: true,
typescriptValidation: true,
};
if (typeof config === "boolean") {
return defaultDefinition;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { MetroPlugin } from "@rnx-kit/metro-serializer";
import type { Graph, Module, SerializerOptions } from "metro";
import { detectCycles, PluginOptions } from "./detectCycles";

export type { PluginOptions } from "./detectCycles";

export function CyclicDependencies(
pluginOptions: PluginOptions = {}
): MetroPlugin {
Expand Down
1 change: 1 addition & 0 deletions packages/metro-plugin-duplicates-checker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "./checkForDuplicatePackages";

export { checkForDuplicatePackages };
export type { Options };

export function checkForDuplicatePackagesInFile(
sourceMap: string,
Expand Down
13 changes: 0 additions & 13 deletions packages/test-app/metro+esbuild.config.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
const { makeMetroConfig } = require("@rnx-kit/metro-config");
const {
CyclicDependencies,
} = require("@rnx-kit/metro-plugin-cyclic-dependencies-detector");
const {
DuplicateDependencies,
} = require("@rnx-kit/metro-plugin-duplicates-checker");
const {
MetroSerializer,
esbuildTransformerConfig,
} = require("@rnx-kit/metro-serializer-esbuild");

module.exports = makeMetroConfig({
projectRoot: __dirname,
serializer: {
customSerializer: MetroSerializer([
CyclicDependencies(),
DuplicateDependencies({ ignoredModules: ["react-is"] }),
]),
},
transformer: esbuildTransformerConfig,
});
13 changes: 0 additions & 13 deletions packages/test-app/metro.config.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
const { makeMetroConfig } = require("@rnx-kit/metro-config");
const {
CyclicDependencies,
} = require("@rnx-kit/metro-plugin-cyclic-dependencies-detector");
const {
DuplicateDependencies,
} = require("@rnx-kit/metro-plugin-duplicates-checker");
const { MetroSerializer } = require("@rnx-kit/metro-serializer");

module.exports = makeMetroConfig({
projectRoot: __dirname,
serializer: {
customSerializer: MetroSerializer([
CyclicDependencies(),
DuplicateDependencies({ ignoredModules: ["react-is"] }),
]),
},
});
7 changes: 7 additions & 0 deletions packages/test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@
"distPath": "dist",
"assetsPath": "dist",
"bundlePrefix": "main",
"detectCyclicDependencies": true,
"detectDuplicateDependencies": {
"ignoredModules": [
"react-is"
]
},
"typescriptValidation": true,
"targets": [
"android",
"ios",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Array [
"https://spdx.org/licenses/MIT.html",
],
"name": "@rnx-kit/cli",
"path": "~/rnx-kit/node_modules/@rnx-kit/cli",
"path": "~/node_modules/@rnx-kit/cli",
"version": "1.2.3-fixedVersionForTesting",
},
Object {
Expand Down Expand Up @@ -167,7 +167,7 @@ THE SOFTWARE.",
"http://github.com/browserify/console-browserify/raw/master/LICENSE",
],
"name": "console-browserify",
"path": "~/rnx-kit/node_modules/console-browserify",
"path": "~/node_modules/console-browserify",
"version": "1.2.3-fixedVersionForTesting",
},
Object {
Expand Down Expand Up @@ -199,7 +199,7 @@ THE SOFTWARE.
"https://spdx.org/licenses/MIT.html",
],
"name": "md5.js",
"path": "~/rnx-kit/node_modules/md5.js",
"path": "~/node_modules/md5.js",
"version": "1.2.3-fixedVersionForTesting",
},
]
Expand Down
Loading