diff --git a/change/@office-iss-react-native-win32-43b80e6f-96e4-479c-b4d7-46c1fb056647.json b/change/@office-iss-react-native-win32-43b80e6f-96e4-479c-b4d7-46c1fb056647.json new file mode 100644 index 00000000000..cbfde873a6f --- /dev/null +++ b/change/@office-iss-react-native-win32-43b80e6f-96e4-479c-b4d7-46c1fb056647.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Add saveAssetPlugin to fix long path assets", + "packageName": "@office-iss/react-native-win32", + "email": "30809111+acoates-ms@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-c92794d1-18fd-49dc-a15e-a50992c2413c.json b/change/react-native-windows-c92794d1-18fd-49dc-a15e-a50992c2413c.json new file mode 100644 index 00000000000..073827d1473 --- /dev/null +++ b/change/react-native-windows-c92794d1-18fd-49dc-a15e-a50992c2413c.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Fix CoreApp build failure with latest VS version", + "packageName": "react-native-windows", + "email": "acoates@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/@office-iss/react-native-win32/metro.config.js b/packages/@office-iss/react-native-win32/metro.config.js index 3cbf6a97b3d..c27321c5ad2 100644 --- a/packages/@office-iss/react-native-win32/metro.config.js +++ b/packages/@office-iss/react-native-win32/metro.config.js @@ -14,3 +14,5 @@ if ( const {makeMetroConfig} = require('@rnw-scripts/metro-dev-config'); module.exports = makeMetroConfig(); +// Enable this when RN CLI gets support for saveAssetPlugins: https://github.com/react-native-community/cli/pull/2002 +// module.exports.transformer.assetPlugins = [require.resolve('./metroShortPathAssetDataPlugin.js')]; diff --git a/packages/@office-iss/react-native-win32/metroShortPathAssetDataPlugin.js b/packages/@office-iss/react-native-win32/metroShortPathAssetDataPlugin.js new file mode 100644 index 00000000000..8e0f9fe19f5 --- /dev/null +++ b/packages/@office-iss/react-native-win32/metroShortPathAssetDataPlugin.js @@ -0,0 +1,15 @@ +// @ts-check +/** + * @typedef {import("metro").AssetData} AssetData; + **/ + +/** + * @param {AssetData & {__useShortPath: boolean}} asset + * @returns {Promise} + */ +async function metroShortPathAssetDataPlugin(asset) { + asset.__useShortPath = true; + return Promise.resolve(asset); + } + +module.exports = metroShortPathAssetDataPlugin; \ No newline at end of file diff --git a/packages/@office-iss/react-native-win32/overrides.json b/packages/@office-iss/react-native-win32/overrides.json index d403cf988be..ab6e43638d3 100644 --- a/packages/@office-iss/react-native-win32/overrides.json +++ b/packages/@office-iss/react-native-win32/overrides.json @@ -233,6 +233,10 @@ "baseFile": "packages/react-native/Libraries/DevToolsSettings/DevToolsSettingsManager.android.js", "baseHash": "1c9eb481e8ed077fa650e3ea34837e2a31310366" }, + { + "type": "platform", + "file": "src/Libraries/Image/assetPaths.js" + }, { "type": "derived", "file": "src/Libraries/Image/Image.win32.js", diff --git a/packages/@office-iss/react-native-win32/react-native.config.js b/packages/@office-iss/react-native-win32/react-native.config.js index dacf5861050..6d3aeb41517 100644 --- a/packages/@office-iss/react-native-win32/react-native.config.js +++ b/packages/@office-iss/react-native-win32/react-native.config.js @@ -7,6 +7,8 @@ module.exports = { projectConfig: (projectRoot, projectParams) => null, dependencyConfig: (projectRoot, dependencyParams) => null, npmPackageName: '@office-iss/react-native-win32', + // Enable once CLI config supports it - https://github.com/react-native-community/cli/pull/2002 + // saveAssetsPlugin: '@office-iss/react-native-win32/saveAssetPlugin' }, }, }; diff --git a/packages/@office-iss/react-native-win32/saveAssetPlugin.js b/packages/@office-iss/react-native-win32/saveAssetPlugin.js new file mode 100644 index 00000000000..c559cdd21a2 --- /dev/null +++ b/packages/@office-iss/react-native-win32/saveAssetPlugin.js @@ -0,0 +1,45 @@ +// @ts-check +const path = require('path'); +const ensureShortPath = require('./Libraries/Image/assetPaths'); + +/** + * @typedef {import("metro").AssetData} AssetData; + **/ + +/** + * @param {AssetData} asset + * @param {number} scale + * @returns {string} + */ +function getAssetDestPath(asset, scale) { + const suffix = scale === 1 ? '' : `@${scale}x`; + const fileName = `${asset.name + suffix}.${asset.type}`; + return path.join( + // Assets can have relative paths outside of the project root. + // Replace `../` with `_` to make sure they don't end up outside of + // the expected assets directory. + ensureShortPath(asset.httpServerLocation.substr(1).replace(/\.\.\//g, '_')), + fileName, + ); +} + +/** + * @param {ReadonlyArray} assets + * @param {string} _platform + * @param {string | undefined} _assetsDest + * @param {string | undefined} _assetCatalogDest + * @param {(asset: AssetData, allowedScales: number[] | undefined, getAssetDestPath: (asset: AssetData, scale: number) => string) => void} addAssetToCopy + */ +function saveAssetsWin32( + assets, + _platform, + _assetsDest, + _assetCatalogDest, + addAssetToCopy, +) { + assets.forEach((asset) => + addAssetToCopy(asset, undefined, getAssetDestPath), + ); +} + +module.exports = saveAssetsWin32; \ No newline at end of file diff --git a/packages/@office-iss/react-native-win32/src/Libraries/Image/assetPaths.js b/packages/@office-iss/react-native-win32/src/Libraries/Image/assetPaths.js new file mode 100644 index 00000000000..101f3b25af0 --- /dev/null +++ b/packages/@office-iss/react-native-win32/src/Libraries/Image/assetPaths.js @@ -0,0 +1,36 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * @flow strict-local + * @format + */ + +'use strict'; + +// Some windows machines may not have long paths enabled +// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation +// Assets in nested node_modules (common when using pnpm) - end up creating very long paths +// Using this function we shorten longer paths to prevent paths from hitting the path limit +function ensureShortPath(str: string): string { + if (str.length < 40) return str; + + const assetsPrefix = 'assets/'; + + if (!str.startsWith(assetsPrefix)) { + console.warn(`Unexpected asset uri - ${str} may not load correctly.`); + } + + const postStr = str.slice(assetsPrefix.length); + var hash = 0, + i, + chr; + for (i = 0; i < postStr.length; i++) { + chr = postStr.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; // Convert to 32bit integer + } + return assetsPrefix + hash.toString(); +} + +module.exports = ensureShortPath; diff --git a/packages/@office-iss/react-native-win32/src/Libraries/Image/resolveAssetSource.win32.js b/packages/@office-iss/react-native-win32/src/Libraries/Image/resolveAssetSource.win32.js index d6db1868a96..a9cf8121135 100644 --- a/packages/@office-iss/react-native-win32/src/Libraries/Image/resolveAssetSource.win32.js +++ b/packages/@office-iss/react-native-win32/src/Libraries/Image/resolveAssetSource.win32.js @@ -10,6 +10,7 @@ const resolveAssetSource = require('./resolveAssetSource.js'); // Get base impl const Platform = require('../Utilities/Platform'); +const ensureShortPath = require('./assetPaths.js'); type IPackagerAsset = { __packager_asset: boolean, @@ -56,7 +57,7 @@ class AssetResolverLateScaleResolution { */ _scaledAssetURLInBundle() { const path = this._resolver.bundleUrl || 'file://'; - return this._fromSource(path + this._getAssetPath()); + return this._fromSource(path + this._getAssetPath(true)); } /** @@ -66,7 +67,7 @@ class AssetResolverLateScaleResolution { _assetServerURL() { return this._fromSource( this._resolver.serverUrl + - this._getAssetPath() + + this._getAssetPath(false) + '?platform=' + Platform.OS + '&hash=' + @@ -77,8 +78,8 @@ class AssetResolverLateScaleResolution { /** * Returns a path like 'assets/AwesomeModule/icon.png' */ - _getAssetPath(): string { - const assetDir = this._getBasePath(); + _getAssetPath(local: boolean): string { + const assetDir = this._getBasePath(local); return ( assetDir + '/' + @@ -88,7 +89,19 @@ class AssetResolverLateScaleResolution { ); } - _getBasePath() { + _getBasePath(local: boolean) { + if (local) { + const safePath = this._resolver.asset.httpServerLocation + .substr(1) + .replace(/\.\.\//g, '_'); + // If this asset was created with the newer saveAssetPlugin, then we should shorten the path + // This conditional is added to allow back compat of older bundles which might have been created without the saveAssetPlugin + if (this._resolver.asset.__useShortPath) { + return ensureShortPath(safePath); + } + return safePath; + } + let basePath = this._resolver.asset.httpServerLocation; if (basePath[0] === '/') { basePath = basePath.substr(1);