Skip to content

Deprecate autolink functionality to copy useWinUI3 flags from react-native.config.js into ExperimentalFeatures.props #14761

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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": "prerelease",
"comment": "Remove deprecated autolink functionality to copy useWinUI3 flags from react-native.config.js into ExperimentalFeatures.props",
"packageName": "@react-native-windows/cli",
"email": "copilot@github.com",
"dependentChangeType": "prerelease"
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import fs from '@react-native-windows/fs';
import path from 'path';
import chalk from 'chalk';
import {performance} from 'perf_hooks';
import {XMLSerializer} from '@xmldom/xmldom';

import {Ora} from 'ora';
const formatter = require('xml-formatter');


import type {
Command,
Expand Down Expand Up @@ -114,9 +114,6 @@ export class AutoLinkWindows {

verboseMessage('Parsing dependencies...', verbose);

this.changesNecessary =
(await this.ensureXAMLDialect()) || this.changesNecessary;

// Generating cs/cpp files for app code consumption
if (projectLang === 'cs') {
this.changesNecessary =
Expand Down Expand Up @@ -693,180 +690,6 @@ export class AutoLinkWindows {
return changesNecessary;
}

protected getExperimentalFeaturesPropsXml() {
const experimentalFeaturesProps = path.join(
path.dirname(this.getSolutionFile()),
'ExperimentalFeatures.props',
);
if (fs.existsSync(experimentalFeaturesProps)) {
const experimentalFeaturesContents = configUtils.readProjectFile(
experimentalFeaturesProps,
);
return {
path: experimentalFeaturesProps,
content: experimentalFeaturesContents,
};
}
return undefined;
}

public async ensureXAMLDialect() {
let changesNeeded = false;
const useWinUI3FromConfig = this.getWindowsConfig().useWinUI3;
const experimentalFeatures = this.getExperimentalFeaturesPropsXml();
if (experimentalFeatures) {
const useWinUI3FromExperimentalFeatures =
configUtils
.tryFindPropertyValue(experimentalFeatures.content, 'UseWinUI3')
?.toLowerCase() === 'true';
// Check if WinUI2xVersion is specified in experimental features
const targetWinUI2xVersion = configUtils.tryFindPropertyValue(
experimentalFeatures.content,
'WinUI2xVersion',
);
// Check if WinUI3Version is specified in experimental features
const targetWinUI3xVersion = configUtils.tryFindPropertyValue(
experimentalFeatures.content,
'WinUI3Version',
);
// Use the UseWinUI3 value in react-native.config.js, or if not present, the value from ExperimentalFeatures.props
changesNeeded = await this.updatePackagesConfigXAMLDialect(
useWinUI3FromConfig !== undefined
? useWinUI3FromConfig
: useWinUI3FromExperimentalFeatures,
targetWinUI2xVersion,
targetWinUI3xVersion,
);
if (useWinUI3FromConfig !== undefined) {
// Make sure ExperimentalFeatures.props matches the value that comes from react-native.config.js
const node =
experimentalFeatures.content.getElementsByTagName('UseWinUI3');
const newValue = useWinUI3FromConfig ? 'true' : 'false';
changesNeeded = node.item(0)?.textContent !== newValue || changesNeeded;
if (!this.options.check && changesNeeded) {
node.item(0)!.textContent = newValue;
const experimentalFeaturesOutput =
new XMLSerializer().serializeToString(experimentalFeatures.content);
await this.updateFile(
experimentalFeatures.path,
experimentalFeaturesOutput,
);
}
}
}
return changesNeeded;
}

protected getPackagesConfigXml() {
const projectFile = this.getProjectFile();
const packagesConfig = path.join(
path.dirname(projectFile),
'packages.config',
);

if (fs.existsSync(packagesConfig)) {
return {
path: packagesConfig,
content: configUtils.readProjectFile(packagesConfig),
};
}
return undefined;
}

private async updatePackagesConfigXAMLDialect(
useWinUI3: boolean,
targetWinUI2xVersion: string | null,
targetWinUI3xVersion: string | null,
) {
let changed = false;
const packagesConfig = this.getPackagesConfigXml();
if (packagesConfig) {
// if we don't have a packages.config, then this is a C# project, in which case we use <PackageReference> and dynamically pick the right XAML package.
const project = this.getWindowsConfig();

const winUIPropsPath = path.join(
resolveRnwRoot(project),
'PropertySheets/WinUI.props',
);
const winuiPropsContents = configUtils.readProjectFile(winUIPropsPath);

// Use the given WinUI2xVersion, otherwise fallback to WinUI.props
const winui2xVersion =
targetWinUI2xVersion ??
configUtils.tryFindPropertyValue(winuiPropsContents, 'WinUI2xVersion');

// Use the given WinUI3Version, otherwise fallback to WinUI.props
const winui3Version =
targetWinUI3xVersion ??
configUtils.tryFindPropertyValue(winuiPropsContents, 'WinUI3Version');

const dialects = [
{id: 'Microsoft.WindowsAppSDK', version: winui3Version!},
{id: 'Microsoft.UI.Xaml', version: winui2xVersion!},
];
const keepPkg = useWinUI3 ? dialects[0] : dialects[1];
const removePkg = useWinUI3 ? dialects[1] : dialects[0];

changed = this.updatePackagesConfig(
packagesConfig,
[removePkg],
[keepPkg],
);

if (!this.options.check && changed) {
const serializer = new XMLSerializer();
const output = serializer.serializeToString(packagesConfig.content);
const formattedXml = formatter(output, {indentation: ' '});
await this.updateFile(packagesConfig.path, formattedXml);
}
}
return changed;
}

private updatePackagesConfig(
packagesConfig: {path: string; content: Document},
removePkgs: {id: string; version: string}[],
keepPkgs: {id: string; version: string}[],
) {
let changed = false;
const packageElements =
packagesConfig.content.documentElement.getElementsByTagName('package');

const nodesToRemove: Element[] = [];

for (let i = 0; i < packageElements.length; i++) {
const packageElement = packageElements.item(i)!;
const idAttr = packageElement!.getAttributeNode('id');
const id = idAttr!.value;
const keepPkg = keepPkgs.find(pkg => pkg.id === id);
if (removePkgs.find(pkg => pkg.id === id)) {
nodesToRemove.push(packageElement);
changed = true;
} else if (keepPkg) {
changed =
changed || keepPkg.version !== packageElement.getAttribute('version');
packageElement.setAttribute('version', keepPkg.version!);
keepPkgs = keepPkgs.filter(pkg => pkg.id !== keepPkg.id);
}
}

nodesToRemove.forEach(pkg =>
packagesConfig.content.documentElement.removeChild(pkg),
);

keepPkgs.forEach(keepPkg => {
const newPkg = packagesConfig.content.createElement('package');

Object.entries(keepPkg).forEach(([attr, value]) => {
newPkg.setAttribute(attr, value as string);
});
newPkg.setAttribute('targetFramework', 'native');
packagesConfig.content.documentElement.appendChild(newPkg);
changed = true;
});
return changed;
}

/** @return The CLI command to invoke autolink-windows independently */
public getAutolinkWindowsCommand() {
const folder = this.windowsAppConfig.folder;
Expand Down
Loading
Loading