diff --git a/.changeset/eighty-phones-deny.md b/.changeset/eighty-phones-deny.md new file mode 100644 index 000000000..3a55e3a77 --- /dev/null +++ b/.changeset/eighty-phones-deny.md @@ -0,0 +1,5 @@ +--- +"@callstack/repack": minor +--- + +Added `getAssetTransformRules` utility function to simplify setting up asset transformation rules in both Webpack and Rspack projects. diff --git a/apps/tester-federation-v2/configs/rspack.host-app.mjs b/apps/tester-federation-v2/configs/rspack.host-app.mjs index 3305fe9d3..c8bc361cf 100644 --- a/apps/tester-federation-v2/configs/rspack.host-app.mjs +++ b/apps/tester-federation-v2/configs/rspack.host-app.mjs @@ -31,10 +31,7 @@ export default (env) => { module: { rules: [ ...Repack.getJsTransformRules(), - { - test: Repack.getAssetExtensionsRegExp(), - use: '@callstack/repack/assets-loader', - }, + ...Repack.getAssetTransformRules(), ], }, plugins: [ diff --git a/apps/tester-federation-v2/configs/rspack.mini-app.mjs b/apps/tester-federation-v2/configs/rspack.mini-app.mjs index 7b1049641..e4bab08f1 100644 --- a/apps/tester-federation-v2/configs/rspack.mini-app.mjs +++ b/apps/tester-federation-v2/configs/rspack.mini-app.mjs @@ -31,13 +31,7 @@ export default (env) => { module: { rules: [ ...Repack.getJsTransformRules(), - { - test: Repack.getAssetExtensionsRegExp(), - use: { - loader: '@callstack/repack/assets-loader', - options: { inline: true }, - }, - }, + ...Repack.getAssetTransformRules({ inline: true }), ], }, plugins: [ diff --git a/apps/tester-federation-v2/configs/webpack.host-app.mjs b/apps/tester-federation-v2/configs/webpack.host-app.mjs index 52c258767..bda300d68 100644 --- a/apps/tester-federation-v2/configs/webpack.host-app.mjs +++ b/apps/tester-federation-v2/configs/webpack.host-app.mjs @@ -37,10 +37,7 @@ export default (env) => { use: 'babel-loader', type: 'javascript/auto', }, - { - test: Repack.getAssetExtensionsRegExp(), - use: '@callstack/repack/assets-loader', - }, + ...Repack.getAssetTransformRules(), ], }, plugins: [ diff --git a/apps/tester-federation-v2/configs/webpack.mini-app.mjs b/apps/tester-federation-v2/configs/webpack.mini-app.mjs index 6036fb448..7da2aa8ef 100644 --- a/apps/tester-federation-v2/configs/webpack.mini-app.mjs +++ b/apps/tester-federation-v2/configs/webpack.mini-app.mjs @@ -37,13 +37,7 @@ export default (env) => { use: 'babel-loader', type: 'javascript/auto', }, - { - test: Repack.getAssetExtensionsRegExp(), - use: { - loader: '@callstack/repack/assets-loader', - options: { inline: true }, - }, - }, + ...Repack.getAssetTransformRules({ inline: true }), ], }, plugins: [ diff --git a/apps/tester-federation/configs/rspack.host-app.mjs b/apps/tester-federation/configs/rspack.host-app.mjs index dff22d341..18a7b79f7 100644 --- a/apps/tester-federation/configs/rspack.host-app.mjs +++ b/apps/tester-federation/configs/rspack.host-app.mjs @@ -33,10 +33,7 @@ export default (env) => { module: { rules: [ ...Repack.getJsTransformRules(), - { - test: Repack.getAssetExtensionsRegExp(), - use: '@callstack/repack/assets-loader', - }, + ...Repack.getAssetTransformRules(), ], }, plugins: [ diff --git a/apps/tester-federation/configs/rspack.mini-app.mjs b/apps/tester-federation/configs/rspack.mini-app.mjs index 4109fc338..518634195 100644 --- a/apps/tester-federation/configs/rspack.mini-app.mjs +++ b/apps/tester-federation/configs/rspack.mini-app.mjs @@ -33,13 +33,7 @@ export default (env) => { module: { rules: [ ...Repack.getJsTransformRules(), - { - test: Repack.getAssetExtensionsRegExp(), - use: { - loader: '@callstack/repack/assets-loader', - options: { inline: true }, - }, - }, + ...Repack.getAssetTransformRules({ inline: true }), ], }, plugins: [ diff --git a/apps/tester-federation/configs/webpack.host-app.mjs b/apps/tester-federation/configs/webpack.host-app.mjs index e863e3d92..63bc87d37 100644 --- a/apps/tester-federation/configs/webpack.host-app.mjs +++ b/apps/tester-federation/configs/webpack.host-app.mjs @@ -37,10 +37,7 @@ export default (env) => { use: 'babel-loader', type: 'javascript/auto', }, - { - test: Repack.getAssetExtensionsRegExp(), - use: '@callstack/repack/assets-loader', - }, + ...Repack.getAssetTransformRules(), ], }, plugins: [ diff --git a/apps/tester-federation/configs/webpack.mini-app.mjs b/apps/tester-federation/configs/webpack.mini-app.mjs index 3648fec17..42802ee9c 100644 --- a/apps/tester-federation/configs/webpack.mini-app.mjs +++ b/apps/tester-federation/configs/webpack.mini-app.mjs @@ -37,13 +37,7 @@ export default (env) => { use: 'babel-loader', type: 'javascript/auto', }, - { - test: Repack.getAssetExtensionsRegExp(), - use: { - loader: '@callstack/repack/assets-loader', - options: { inline: true }, - }, - }, + ...Repack.getAssetTransformRules({ inline: true }), ], }, plugins: [ diff --git a/packages/repack/src/loaders/assetsLoader/options.ts b/packages/repack/src/loaders/assetsLoader/options.ts index a00446435..6f7632771 100644 --- a/packages/repack/src/loaders/assetsLoader/options.ts +++ b/packages/repack/src/loaders/assetsLoader/options.ts @@ -1,6 +1,17 @@ import type { LoaderContext } from '@rspack/core'; import { validate } from 'schema-utils'; +export interface AssetLoaderRemoteOptions { + enabled: boolean; + assetPath?: (args: { + resourcePath: string; + resourceFilename: string; + resourceDirname: string; + resourceExtensionType: string; + }) => string; + publicPath: string; +} + // Note: publicPath could be obtained from webpack config in the future export interface AssetLoaderOptions { platform?: string; @@ -8,16 +19,7 @@ export interface AssetLoaderOptions { scalableAssetResolutions?: string[]; inline?: boolean; publicPath?: string; - remote?: { - enabled: boolean; - assetPath?: (args: { - resourcePath: string; - resourceFilename: string; - resourceDirname: string; - resourceExtensionType: string; - }) => string; - publicPath: string; - }; + remote?: AssetLoaderRemoteOptions; } export interface AssetLoaderContext extends LoaderContext {} diff --git a/packages/repack/src/utils/__tests__/__snapshots__/getAssetTransformRules.test.ts.snap b/packages/repack/src/utils/__tests__/__snapshots__/getAssetTransformRules.test.ts.snap new file mode 100644 index 000000000..03bc40042 --- /dev/null +++ b/packages/repack/src/utils/__tests__/__snapshots__/getAssetTransformRules.test.ts.snap @@ -0,0 +1,111 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getAssetTransformRules should add SVGR rule when svg="svgr" 1`] = ` +[ + { + "test": /\\\\\\.\\(bmp\\|gif\\|jpg\\|jpeg\\|png\\|psd\\|webp\\|tiff\\|m4v\\|mov\\|mp4\\|mpeg\\|mpg\\|webm\\|aac\\|aiff\\|caf\\|m4a\\|mp3\\|wav\\|html\\|pdf\\|yaml\\|yml\\|otf\\|ttf\\|zip\\|obj\\)\\$/, + "use": { + "loader": "@callstack/repack/assets-loader", + "options": { + "inline": undefined, + "remote": undefined, + }, + }, + }, + { + "test": /\\\\\\.svg\\$/, + "use": { + "loader": "@svgr/webpack", + "options": { + "native": true, + }, + }, + }, +] +`; + +exports[`getAssetTransformRules should add URI rule when svg="uri" 1`] = ` +[ + { + "test": /\\\\\\.\\(bmp\\|gif\\|jpg\\|jpeg\\|png\\|psd\\|webp\\|tiff\\|m4v\\|mov\\|mp4\\|mpeg\\|mpg\\|webm\\|aac\\|aiff\\|caf\\|m4a\\|mp3\\|wav\\|html\\|pdf\\|yaml\\|yml\\|otf\\|ttf\\|zip\\|obj\\)\\$/, + "use": { + "loader": "@callstack/repack/assets-loader", + "options": { + "inline": undefined, + "remote": undefined, + }, + }, + }, + { + "test": /\\\\\\.svg\\$/, + "type": "asset/inline", + }, +] +`; + +exports[`getAssetTransformRules should add XML rule when svg="xml" 1`] = ` +[ + { + "test": /\\\\\\.\\(bmp\\|gif\\|jpg\\|jpeg\\|png\\|psd\\|webp\\|tiff\\|m4v\\|mov\\|mp4\\|mpeg\\|mpg\\|webm\\|aac\\|aiff\\|caf\\|m4a\\|mp3\\|wav\\|html\\|pdf\\|yaml\\|yml\\|otf\\|ttf\\|zip\\|obj\\)\\$/, + "use": { + "loader": "@callstack/repack/assets-loader", + "options": { + "inline": undefined, + "remote": undefined, + }, + }, + }, + { + "test": /\\\\\\.svg\\$/, + "type": "asset/source", + }, +] +`; + +exports[`getAssetTransformRules should return default asset transform rules when no options provided 1`] = ` +[ + { + "test": /\\\\\\.\\(bmp\\|gif\\|jpg\\|jpeg\\|png\\|psd\\|svg\\|webp\\|tiff\\|m4v\\|mov\\|mp4\\|mpeg\\|mpg\\|webm\\|aac\\|aiff\\|caf\\|m4a\\|mp3\\|wav\\|html\\|pdf\\|yaml\\|yml\\|otf\\|ttf\\|zip\\|obj\\)\\$/, + "use": { + "loader": "@callstack/repack/assets-loader", + "options": { + "inline": undefined, + "remote": undefined, + }, + }, + }, +] +`; + +exports[`getAssetTransformRules should return rules with inline option when provided 1`] = ` +[ + { + "test": /\\\\\\.\\(bmp\\|gif\\|jpg\\|jpeg\\|png\\|psd\\|svg\\|webp\\|tiff\\|m4v\\|mov\\|mp4\\|mpeg\\|mpg\\|webm\\|aac\\|aiff\\|caf\\|m4a\\|mp3\\|wav\\|html\\|pdf\\|yaml\\|yml\\|otf\\|ttf\\|zip\\|obj\\)\\$/, + "use": { + "loader": "@callstack/repack/assets-loader", + "options": { + "inline": true, + "remote": undefined, + }, + }, + }, +] +`; + +exports[`getAssetTransformRules should return rules with remote options when provided 1`] = ` +[ + { + "test": /\\\\\\.\\(bmp\\|gif\\|jpg\\|jpeg\\|png\\|psd\\|svg\\|webp\\|tiff\\|m4v\\|mov\\|mp4\\|mpeg\\|mpg\\|webm\\|aac\\|aiff\\|caf\\|m4a\\|mp3\\|wav\\|html\\|pdf\\|yaml\\|yml\\|otf\\|ttf\\|zip\\|obj\\)\\$/, + "use": { + "loader": "@callstack/repack/assets-loader", + "options": { + "inline": undefined, + "remote": { + "enabled": true, + "publicPath": "https://example.com/assets", + }, + }, + }, + }, +] +`; diff --git a/packages/repack/src/utils/__tests__/getAssetTransformRules.test.ts b/packages/repack/src/utils/__tests__/getAssetTransformRules.test.ts new file mode 100644 index 000000000..a78c2d586 --- /dev/null +++ b/packages/repack/src/utils/__tests__/getAssetTransformRules.test.ts @@ -0,0 +1,57 @@ +import { getAssetTransformRules } from '../getAssetTransformRules.js'; + +describe('getAssetTransformRules', () => { + it('should return default asset transform rules when no options provided', () => { + const rules = getAssetTransformRules(); + expect(rules).toMatchSnapshot(); + }); + + it('should return rules with inline option when provided', () => { + const rules = getAssetTransformRules({ inline: true }); + + // @ts-ignore + expect(rules[0]?.use?.options?.inline).toEqual(true); + expect(rules).toMatchSnapshot(); + }); + + it('should return rules with remote options when provided', () => { + const remoteOptions = { publicPath: 'https://example.com/assets' }; + const rules = getAssetTransformRules({ remote: remoteOptions }); + + // @ts-ignore + expect(rules[0]?.use?.options?.remote).toHaveProperty('enabled', true); + expect(rules).toMatchSnapshot(); + }); + + it('should add SVGR rule when svg="svgr"', () => { + const rules = getAssetTransformRules({ svg: 'svgr' }); + + expect(rules).toHaveLength(2); + expect(rules[1]?.use?.loader).toEqual('@svgr/webpack'); + expect(rules).toMatchSnapshot(); + }); + + it('should add XML rule when svg="xml"', () => { + const rules = getAssetTransformRules({ svg: 'xml' }); + + expect(rules).toHaveLength(2); + // @ts-ignore + expect(rules[1]?.type).toEqual('asset/source'); + expect(rules).toMatchSnapshot(); + }); + + it('should add URI rule when svg="uri"', () => { + const rules = getAssetTransformRules({ svg: 'uri' }); + + expect(rules).toHaveLength(2); + // @ts-ignore + expect(rules[1]?.type).toEqual('asset/inline'); + expect(rules).toMatchSnapshot(); + }); + + it('should exclude .svg from main asset extensions when svg option is provided', () => { + const rules = getAssetTransformRules({ svg: 'uri' }); + const ruleTest = rules[0]?.test; + expect(ruleTest.test('test.svg')).toEqual(false); + }); +}); diff --git a/packages/repack/src/utils/getAssetTransformRules.ts b/packages/repack/src/utils/getAssetTransformRules.ts new file mode 100644 index 000000000..4a25ee1e4 --- /dev/null +++ b/packages/repack/src/utils/getAssetTransformRules.ts @@ -0,0 +1,85 @@ +import type { AssetLoaderRemoteOptions } from '../loaders/assetsLoader/options.js'; +import { + ASSET_EXTENSIONS, + getAssetExtensionsRegExp, +} from './assetExtensions.js'; + +function getSvgRule(type: 'svgr' | 'xml' | 'uri') { + if (type === 'svgr') { + return { + test: /\.svg$/, + use: { loader: '@svgr/webpack', options: { native: true } }, + }; + } + + return { + test: /\.svg$/, + type: type === 'xml' ? 'asset/source' : 'asset/inline', + }; +} + +/** + * Interface for {@link getAssetTransformRules} options. + */ +interface GetAssetTransformRulesOptions { + /** + * Whether to inline assets as base64 URIs. + */ + inline?: boolean; + + /** + * Configuration for remote asset loading. + */ + remote?: Omit; + + /** + * Determines how SVG files should be processed: + * - 'svgr': Uses `@svgr/webpack` to transform SVGs into React Native components + * - 'xml': Loads SVGs as raw XML source to be used with SvgXml from react-native-svg + * - 'uri': Loads SVGs as inline URIs to be used with SvgUri from react-native-svg + */ + svg?: 'svgr' | 'xml' | 'uri'; +} + +/** + * Creates `module.rules` configuration for handling assets in React Native applications. + * + * @param options Configuration options + * @param options.inline Whether to inline assets as base64 URIs (defaults to false) + * @param options.remote Configuration for remote asset loading with publicPath and optional assetPath function + * @param options.svg Determines how SVG files should be processed ('svgr', 'xml', or 'uri') + * + * @returns Array of webpack/rspack rules for transforming assets + */ +export function getAssetTransformRules({ + inline, + remote, + svg, +}: GetAssetTransformRulesOptions = {}) { + const extensions = svg + ? ASSET_EXTENSIONS.filter((ext) => ext !== 'svg') + : ASSET_EXTENSIONS; + + const remoteOptions = remote + ? { + enabled: true, + ...remote, + } + : undefined; + + const rules = []; + + rules.push({ + test: getAssetExtensionsRegExp(extensions), + use: { + loader: '@callstack/repack/assets-loader', + options: { inline, remote: remoteOptions }, + }, + }); + + if (svg) { + rules.push(getSvgRule(svg)); + } + + return rules; +} diff --git a/packages/repack/src/utils/index.ts b/packages/repack/src/utils/index.ts index b5a555658..c286d14cc 100644 --- a/packages/repack/src/utils/index.ts +++ b/packages/repack/src/utils/index.ts @@ -8,3 +8,4 @@ export * from './getJsTransformRules.js'; export * from './getSwcLoaderOptions.js'; export * from './getFlowTransformRules.js'; export * from './getCodegenTransformRules.js'; +export * from './getAssetTransformRules.js'; diff --git a/templates_v5/rspack.config.cjs b/templates_v5/rspack.config.cjs index bff7e465a..ba917076f 100644 --- a/templates_v5/rspack.config.cjs +++ b/templates_v5/rspack.config.cjs @@ -16,10 +16,7 @@ module.exports = { module: { rules: [ ...Repack.getJsTransformRules(), - { - test: Repack.getAssetExtensionsRegExp(), - use: '@callstack/repack/assets-loader', - }, + ...Repack.getAssetTransformRules(), ], }, plugins: [new Repack.RepackPlugin()], diff --git a/templates_v5/rspack.config.mjs b/templates_v5/rspack.config.mjs index 74645c8eb..2c282f1eb 100644 --- a/templates_v5/rspack.config.mjs +++ b/templates_v5/rspack.config.mjs @@ -21,10 +21,7 @@ export default { module: { rules: [ ...Repack.getJsTransformRules(), - { - test: Repack.getAssetExtensionsRegExp(), - use: '@callstack/repack/assets-loader', - }, + ...Repack.getAssetTransformRules(), ], }, plugins: [new Repack.RepackPlugin()], diff --git a/templates_v5/webpack.config.cjs b/templates_v5/webpack.config.cjs index b9498e773..fd7a3762e 100644 --- a/templates_v5/webpack.config.cjs +++ b/templates_v5/webpack.config.cjs @@ -21,10 +21,7 @@ module.exports = { use: 'babel-loader', type: 'javascript/auto', }, - { - test: Repack.getAssetExtensionsRegExp(), - use: '@callstack/repack/assets-loader', - }, + ...Repack.getAssetTransformRules(), ], }, optimization: { diff --git a/templates_v5/webpack.config.mjs b/templates_v5/webpack.config.mjs index 0a266d69c..b27ef3491 100644 --- a/templates_v5/webpack.config.mjs +++ b/templates_v5/webpack.config.mjs @@ -26,10 +26,7 @@ export default { use: 'babel-loader', type: 'javascript/auto', }, - { - test: Repack.getAssetExtensionsRegExp(), - use: '@callstack/repack/assets-loader', - }, + ...Repack.getAssetTransformRules(), ], }, optimization: { diff --git a/website/src/5.x/api/utils/_meta.json b/website/src/5.x/api/utils/_meta.json index 11510b429..b45f5789b 100644 --- a/website/src/5.x/api/utils/_meta.json +++ b/website/src/5.x/api/utils/_meta.json @@ -1,6 +1,7 @@ [ "constants", "get-asset-extension-regexp", + "get-asset-transform-rules", "get-codegen-transform-rules", "get-dirname", "get-flow-transform-rules", diff --git a/website/src/5.x/api/utils/get-asset-transform-rules.md b/website/src/5.x/api/utils/get-asset-transform-rules.md new file mode 100644 index 000000000..8efe33248 --- /dev/null +++ b/website/src/5.x/api/utils/get-asset-transform-rules.md @@ -0,0 +1,96 @@ +# getAssetTransformRules + +A helper function that generates `module.rules` configuration for handling assets in React Native applications. + +:::tip +This helper function allows you to create a single configuration for all assets in your project. If you need more granular control over asset processing, refer to the [`assetsLoader`](/api/loaders/assets-loader) documentation. +::: + +## Parameters + +```ts +interface GetAssetTransformRulesOptions { + inline?: boolean; + remote?: { + publicPath: string; + assetPath?: (args: { + resourcePath: string; + resourceFilename: string; + resourceDirname: string; + resourceExtensionType: string; + }) => string; + }; + svg?: "svgr" | "xml" | "uri"; +} +``` + +### options + +Configuration options for asset transformations. + +### options.inline + +- Type: `boolean` + +Whether to inline assets as base64 URIs. + +:::tip +Learn more about the inlining assets in the [Inlining Assets guide](/docs/guides/inlining-assets). +::: + +### options.remote + +- Type: `object` + +Configuration for remote asset loading. + +:::tip +Learn more about using remote assets in the [Remote Assets guide](/docs/guides/remote-assets). +::: + +### options.remote.publicPath + +- Type: `string` +- Required: `true` + +Public path for loading remote assets. + +See [`assetsLoader` documentation](/api/loaders/assets-loader#remotepublicpath) for reference. + +### options.remote.assetPath + +- Type: `(args: { resourcePath: string; resourceFilename: string; resourceDirname: string; resourceExtensionType: string; }) => string` + +A function to customize how the asset path is generated for remote assets. + +See [`assetsLoader` documentation](/api/loaders/assets-loader#remoteassetpath) for reference. + +### options.svg + +- Type: `'svgr' | 'xml' | 'uri'` + +Determines how SVG files should be processed: + +- `'svgr'`: Uses `@svgr/webpack` to transform SVGs into React Native components +- `'xml'`: Loads SVGs as raw XML source to be used with `SvgXml` from `react-native-svg` +- `'uri'`: Loads SVGs as inline URIs to be used with `SvgUri` from `react-native-svg` + +:::tip +Learn more about using SVG in the [SVG guide](/docs/guides/svg). +::: + +## Example + +```js title=rspack.config.cjs +const Repack = require("@callstack/repack"); + +module.exports = { + module: { + rules: [ + ...Repack.getAssetTransformRules({ + svg: "svgr", + }), + ], + }, +}; +```