diff --git a/packages/assets/.gitignore b/packages/assets/.gitignore new file mode 100644 index 000000000000..40d93a0332c9 --- /dev/null +++ b/packages/assets/.gitignore @@ -0,0 +1,5 @@ +# Dependencies +/node_modules + +# Build output +/dist diff --git a/packages/assets/package.json b/packages/assets/package.json index c8e5100a2cc4..daec3fd2949a 100644 --- a/packages/assets/package.json +++ b/packages/assets/package.json @@ -11,7 +11,17 @@ "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/assets#readme", "keywords": ["assets", "registry", "react-native", "support"], "bugs": "https://github.com/facebook/react-native/issues", - "engines": { - "node": ">=18" - } + "exports": { + "./path-support": "./src/path-support.js", + "./path-support.js": "./src/path-support.js", + "./registry": "./src/registry.js", + "./registry.js": "./src/registry.js", + "./package.json": "./package.json" + }, + "files": [ + "dist", + "path-support.js", + "registry.js", + "src" + ] } diff --git a/packages/assets/path-support.js b/packages/assets/path-support.js index f0a85af33ff9..330871ae702c 100644 --- a/packages/assets/path-support.js +++ b/packages/assets/path-support.js @@ -4,89 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @format - * @flow strict + * @format strict + * @flow */ 'use strict'; -import type {PackagerAsset} from './registry.js'; - -const androidScaleSuffix = { - '0.75': 'ldpi', - '1': 'mdpi', - '1.5': 'hdpi', - '2': 'xhdpi', - '3': 'xxhdpi', - '4': 'xxxhdpi', -}; - -const ANDROID_BASE_DENSITY = 160; - -/** - * FIXME: using number to represent discrete scale numbers is fragile in essence because of - * floating point numbers imprecision. - */ -function getAndroidAssetSuffix(scale: number): string { - if (scale.toString() in androidScaleSuffix) { - return androidScaleSuffix[scale.toString()]; - } - // NOTE: Android Gradle Plugin does not fully support the nnndpi format. - // See https://issuetracker.google.com/issues/72884435 - if (Number.isFinite(scale) && scale > 0) { - return Math.round(scale * ANDROID_BASE_DENSITY) + 'dpi'; - } - throw new Error('no such scale ' + scale.toString()); -} - -// See https://developer.android.com/guide/topics/resources/drawable-resource.html -const drawableFileTypes = new Set([ - 'gif', - 'jpeg', - 'jpg', - 'ktx', - 'png', - 'svg', - 'webp', - 'xml', -]); - -function getAndroidResourceFolderName( - asset: PackagerAsset, - scale: number, -): string | $TEMPORARY$string<'raw'> { - if (!drawableFileTypes.has(asset.type)) { - return 'raw'; - } - const suffix = getAndroidAssetSuffix(scale); - if (!suffix) { - throw new Error( - "Don't know which android drawable suffix to use for scale: " + - scale + - '\nAsset: ' + - JSON.stringify(asset, null, '\t') + - '\nPossible scales are:' + - JSON.stringify(androidScaleSuffix, null, '\t'), - ); - } - return 'drawable-' + suffix; -} - -function getAndroidResourceIdentifier(asset: PackagerAsset): string { - return (getBasePath(asset) + '/' + asset.name) - .toLowerCase() - .replace(/\//g, '_') // Encode folder structure in file name - .replace(/([^a-z0-9_])/g, '') // Remove illegal chars - .replace(/^assets_/, ''); // Remove "assets_" prefix -} - -function getBasePath(asset: PackagerAsset): string { - const basePath = asset.httpServerLocation; - return basePath.startsWith('/') ? basePath.slice(1) : basePath; -} - -module.exports = { - getAndroidResourceFolderName, - getAndroidResourceIdentifier, - getBasePath, -}; +module.exports = require('./src/path-support'); diff --git a/packages/assets/registry.js b/packages/assets/registry.js index 02470da3c496..48ef37b4d85c 100644 --- a/packages/assets/registry.js +++ b/packages/assets/registry.js @@ -10,29 +10,4 @@ 'use strict'; -export type PackagerAsset = { - +__packager_asset: boolean, - +fileSystemLocation: string, - +httpServerLocation: string, - +width: ?number, - +height: ?number, - +scales: Array, - +hash: string, - +name: string, - +type: string, - ... -}; - -const assets: Array = []; - -function registerAsset(asset: PackagerAsset): number { - // `push` returns new array length, so the first asset will - // get id 1 (not 0) to make the value truthy - return assets.push(asset); -} - -function getAssetByID(assetId: number): PackagerAsset { - return assets[assetId - 1]; -} - -module.exports = {registerAsset, getAssetByID}; +module.exports = require('./src/registry.flow'); diff --git a/packages/assets/src/path-support.flow.js b/packages/assets/src/path-support.flow.js new file mode 100644 index 000000000000..664549fcc61d --- /dev/null +++ b/packages/assets/src/path-support.flow.js @@ -0,0 +1,84 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict + */ + +import type {PackagerAsset} from './registry.flow.js'; + +const androidScaleSuffix = { + '0.75': 'ldpi', + '1': 'mdpi', + '1.5': 'hdpi', + '2': 'xhdpi', + '3': 'xxhdpi', + '4': 'xxxhdpi', +}; + +const ANDROID_BASE_DENSITY = 160; + +/** + * FIXME: using number to represent discrete scale numbers is fragile in essence because of + * floating point numbers imprecision. + */ +function getAndroidAssetSuffix(scale: number): string { + if (scale.toString() in androidScaleSuffix) { + return androidScaleSuffix[scale.toString()]; + } + // NOTE: Android Gradle Plugin does not fully support the nnndpi format. + // See https://issuetracker.google.com/issues/72884435 + if (Number.isFinite(scale) && scale > 0) { + return Math.round(scale * ANDROID_BASE_DENSITY) + 'dpi'; + } + throw new Error('no such scale ' + scale.toString()); +} + +// See https://developer.android.com/guide/topics/resources/drawable-resource.html +const drawableFileTypes = new Set([ + 'gif', + 'jpeg', + 'jpg', + 'ktx', + 'png', + 'svg', + 'webp', + 'xml', +]); + +export function getAndroidResourceFolderName( + asset: PackagerAsset, + scale: number, +): string | $TEMPORARY$string<'raw'> { + if (!drawableFileTypes.has(asset.type)) { + return 'raw'; + } + const suffix = getAndroidAssetSuffix(scale); + if (!suffix) { + throw new Error( + "Don't know which android drawable suffix to use for scale: " + + scale + + '\nAsset: ' + + JSON.stringify(asset, null, '\t') + + '\nPossible scales are:' + + JSON.stringify(androidScaleSuffix, null, '\t'), + ); + } + return 'drawable-' + suffix; +} + +export function getAndroidResourceIdentifier(asset: PackagerAsset): string { + return (getBasePath(asset) + '/' + asset.name) + .toLowerCase() + .replace(/\//g, '_') // Encode folder structure in file name + .replace(/([^a-z0-9_])/g, '') // Remove illegal chars + .replace(/^assets_/, ''); // Remove "assets_" prefix +} + +export function getBasePath(asset: PackagerAsset): string { + const basePath = asset.httpServerLocation; + return basePath.startsWith('/') ? basePath.slice(1) : basePath; +} diff --git a/packages/assets/src/path-support.js b/packages/assets/src/path-support.js new file mode 100644 index 000000000000..569c7449daf5 --- /dev/null +++ b/packages/assets/src/path-support.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +/*:: +export type * from './path-support.flow'; +*/ + +if (process.env.BUILD_EXCLUDE_BABEL_REGISTER != null) { + require('../../../scripts/build/babel-register').registerForMonorepo(); +} + +export * from './path-support.flow'; diff --git a/packages/assets/src/registry.flow.js b/packages/assets/src/registry.flow.js new file mode 100644 index 000000000000..79117d49378d --- /dev/null +++ b/packages/assets/src/registry.flow.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +export type PackagerAsset = { + +__packager_asset: boolean, + +fileSystemLocation: string, + +httpServerLocation: string, + +width: ?number, + +height: ?number, + +scales: Array, + +hash: string, + +name: string, + +type: string, + ... +}; + +const assets: Array = []; + +export function registerAsset(asset: PackagerAsset): number { + // `push` returns new array length, so the first asset will + // get id 1 (not 0) to make the value truthy + return assets.push(asset); +} + +export function getAssetByID(assetId: number): PackagerAsset { + return assets[assetId - 1]; +} diff --git a/packages/assets/src/registry.js b/packages/assets/src/registry.js new file mode 100644 index 000000000000..22e1d0ebbcf7 --- /dev/null +++ b/packages/assets/src/registry.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +/*:: +export type * from './registry.flow'; +*/ + +if (process.env.BUILD_EXCLUDE_BABEL_REGISTER != null) { + require('../../../scripts/build/babel-register').registerForMonorepo(); +} + +export * from './registry.flow'; diff --git a/packages/rn-tester/metro.config.js b/packages/rn-tester/metro.config.js index 3c41387cc19b..517293b17552 100644 --- a/packages/rn-tester/metro.config.js +++ b/packages/rn-tester/metro.config.js @@ -23,6 +23,7 @@ const config = { // Make Metro able to resolve required external dependencies watchFolders: [ path.resolve(__dirname, '../../node_modules'), + path.resolve(__dirname, '../../scripts/build'), // babel-register.js path.resolve(__dirname, '../assets'), path.resolve(__dirname, '../community-cli-plugin'), path.resolve(__dirname, '../dev-middleware'), diff --git a/scripts/build/config.js b/scripts/build/config.js index a2014995b829..713efdef447f 100644 --- a/scripts/build/config.js +++ b/scripts/build/config.js @@ -42,6 +42,10 @@ export type BuildConfig = $ReadOnly<{ */ const buildConfig /*: BuildConfig */ = { packages: { + assets: { + target: 'node', + emitTypeScriptDefs: true, + }, 'community-cli-plugin': { target: 'node', },