Skip to content

Commit ecd0ad7

Browse files
authored
[Win32] Reduce usage of long paths in assets which can cause long path issues (#11839)
* Add saveAssetPlugin to fix long path assets * Change files * comment * add back compat * fix * turn off win32 assetPlugin for now * fix * fix * fix * Change files * fix
1 parent 3d85c58 commit ecd0ad7

9 files changed

+136
-5
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Add saveAssetPlugin to fix long path assets",
4+
"packageName": "@office-iss/react-native-win32",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Fix CoreApp build failure with latest VS version",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/@office-iss/react-native-win32/metro.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ if (
1414

1515
const {makeMetroConfig} = require('@rnw-scripts/metro-dev-config');
1616
module.exports = makeMetroConfig();
17+
// Enable this when RN CLI gets support for saveAssetPlugins: https://github.com/react-native-community/cli/pull/2002
18+
// module.exports.transformer.assetPlugins = [require.resolve('./metroShortPathAssetDataPlugin.js')];
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @ts-check
2+
/**
3+
* @typedef {import("metro").AssetData} AssetData;
4+
**/
5+
6+
/**
7+
* @param {AssetData & {__useShortPath: boolean}} asset
8+
* @returns {Promise<AssetData>}
9+
*/
10+
async function metroShortPathAssetDataPlugin(asset) {
11+
asset.__useShortPath = true;
12+
return Promise.resolve(asset);
13+
}
14+
15+
module.exports = metroShortPathAssetDataPlugin;

packages/@office-iss/react-native-win32/overrides.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@
233233
"baseFile": "packages/react-native/Libraries/DevToolsSettings/DevToolsSettingsManager.android.js",
234234
"baseHash": "1c9eb481e8ed077fa650e3ea34837e2a31310366"
235235
},
236+
{
237+
"type": "platform",
238+
"file": "src/Libraries/Image/assetPaths.js"
239+
},
236240
{
237241
"type": "derived",
238242
"file": "src/Libraries/Image/Image.win32.js",

packages/@office-iss/react-native-win32/react-native.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ module.exports = {
77
projectConfig: (projectRoot, projectParams) => null,
88
dependencyConfig: (projectRoot, dependencyParams) => null,
99
npmPackageName: '@office-iss/react-native-win32',
10+
// Enable once CLI config supports it - https://github.com/react-native-community/cli/pull/2002
11+
// saveAssetsPlugin: '@office-iss/react-native-win32/saveAssetPlugin'
1012
},
1113
},
1214
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// @ts-check
2+
const path = require('path');
3+
const ensureShortPath = require('./Libraries/Image/assetPaths');
4+
5+
/**
6+
* @typedef {import("metro").AssetData} AssetData;
7+
**/
8+
9+
/**
10+
* @param {AssetData} asset
11+
* @param {number} scale
12+
* @returns {string}
13+
*/
14+
function getAssetDestPath(asset, scale) {
15+
const suffix = scale === 1 ? '' : `@${scale}x`;
16+
const fileName = `${asset.name + suffix}.${asset.type}`;
17+
return path.join(
18+
// Assets can have relative paths outside of the project root.
19+
// Replace `../` with `_` to make sure they don't end up outside of
20+
// the expected assets directory.
21+
ensureShortPath(asset.httpServerLocation.substr(1).replace(/\.\.\//g, '_')),
22+
fileName,
23+
);
24+
}
25+
26+
/**
27+
* @param {ReadonlyArray<AssetData>} assets
28+
* @param {string} _platform
29+
* @param {string | undefined} _assetsDest
30+
* @param {string | undefined} _assetCatalogDest
31+
* @param {(asset: AssetData, allowedScales: number[] | undefined, getAssetDestPath: (asset: AssetData, scale: number) => string) => void} addAssetToCopy
32+
*/
33+
function saveAssetsWin32(
34+
assets,
35+
_platform,
36+
_assetsDest,
37+
_assetCatalogDest,
38+
addAssetToCopy,
39+
) {
40+
assets.forEach((asset) =>
41+
addAssetToCopy(asset, undefined, getAssetDestPath),
42+
);
43+
}
44+
45+
module.exports = saveAssetsWin32;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
*
5+
* @flow strict-local
6+
* @format
7+
*/
8+
9+
'use strict';
10+
11+
// Some windows machines may not have long paths enabled
12+
// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
13+
// Assets in nested node_modules (common when using pnpm) - end up creating very long paths
14+
// Using this function we shorten longer paths to prevent paths from hitting the path limit
15+
function ensureShortPath(str: string): string {
16+
if (str.length < 40) return str;
17+
18+
const assetsPrefix = 'assets/';
19+
20+
if (!str.startsWith(assetsPrefix)) {
21+
console.warn(`Unexpected asset uri - ${str} may not load correctly.`);
22+
}
23+
24+
const postStr = str.slice(assetsPrefix.length);
25+
var hash = 0,
26+
i,
27+
chr;
28+
for (i = 0; i < postStr.length; i++) {
29+
chr = postStr.charCodeAt(i);
30+
hash = (hash << 5) - hash + chr;
31+
hash |= 0; // Convert to 32bit integer
32+
}
33+
return assetsPrefix + hash.toString();
34+
}
35+
36+
module.exports = ensureShortPath;

packages/@office-iss/react-native-win32/src/Libraries/Image/resolveAssetSource.win32.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
const resolveAssetSource = require('./resolveAssetSource.js'); // Get base impl
1212
const Platform = require('../Utilities/Platform');
13+
const ensureShortPath = require('./assetPaths.js');
1314

1415
type IPackagerAsset = {
1516
__packager_asset: boolean,
@@ -56,7 +57,7 @@ class AssetResolverLateScaleResolution {
5657
*/
5758
_scaledAssetURLInBundle() {
5859
const path = this._resolver.bundleUrl || 'file://';
59-
return this._fromSource(path + this._getAssetPath());
60+
return this._fromSource(path + this._getAssetPath(true));
6061
}
6162

6263
/**
@@ -66,7 +67,7 @@ class AssetResolverLateScaleResolution {
6667
_assetServerURL() {
6768
return this._fromSource(
6869
this._resolver.serverUrl +
69-
this._getAssetPath() +
70+
this._getAssetPath(false) +
7071
'?platform=' +
7172
Platform.OS +
7273
'&hash=' +
@@ -77,8 +78,8 @@ class AssetResolverLateScaleResolution {
7778
/**
7879
* Returns a path like 'assets/AwesomeModule/icon.png'
7980
*/
80-
_getAssetPath(): string {
81-
const assetDir = this._getBasePath();
81+
_getAssetPath(local: boolean): string {
82+
const assetDir = this._getBasePath(local);
8283
return (
8384
assetDir +
8485
'/' +
@@ -88,7 +89,19 @@ class AssetResolverLateScaleResolution {
8889
);
8990
}
9091

91-
_getBasePath() {
92+
_getBasePath(local: boolean) {
93+
if (local) {
94+
const safePath = this._resolver.asset.httpServerLocation
95+
.substr(1)
96+
.replace(/\.\.\//g, '_');
97+
// If this asset was created with the newer saveAssetPlugin, then we should shorten the path
98+
// This conditional is added to allow back compat of older bundles which might have been created without the saveAssetPlugin
99+
if (this._resolver.asset.__useShortPath) {
100+
return ensureShortPath(safePath);
101+
}
102+
return safePath;
103+
}
104+
92105
let basePath = this._resolver.asset.httpServerLocation;
93106
if (basePath[0] === '/') {
94107
basePath = basePath.substr(1);

0 commit comments

Comments
 (0)