diff --git a/.changeset/assets-loader-posix-public-path.md b/.changeset/assets-loader-posix-public-path.md new file mode 100644 index 000000000..32c73ba04 --- /dev/null +++ b/.changeset/assets-loader-posix-public-path.md @@ -0,0 +1,5 @@ +--- +"@callstack/repack": patch +--- + +Fix the assets loader failing on Windows. Public paths for both remote and extracted assets were joined with `path.join`, which rewrites the separators to backslashes on Windows; for remote assets that turned `https://…` into a string `new URL` rejects, so any bundle containing a remote asset failed with `TypeError: Invalid URL`. Both are URLs rather than filesystem paths and are now joined with `path.posix.join`, which produces the same output on Linux and macOS as before. diff --git a/packages/repack/src/loaders/assetsLoader/__tests__/windowsPaths.test.ts b/packages/repack/src/loaders/assetsLoader/__tests__/windowsPaths.test.ts new file mode 100644 index 000000000..2d474228f --- /dev/null +++ b/packages/repack/src/loaders/assetsLoader/__tests__/windowsPaths.test.ts @@ -0,0 +1,71 @@ +import path from 'node:path'; +import { convertToRemoteAssets } from '../convertToRemoteAssets.js'; +import { extractAssets } from '../extractAssets.js'; +import type { Asset } from '../types.js'; + +// Run the loader helpers with Windows path semantics: `path.join` uses +// backslashes, while `path.posix` stays available as it is on Windows. +jest.mock('node:path', () => { + const { win32 } = jest.requireActual('node:path'); + return { __esModule: true, default: win32, ...win32 }; +}); + +const pathSeparatorRegexp = new RegExp(`\\${path.sep}`, 'g'); + +const assets: Asset[] = [ + { data: Buffer.from(''), dimensions: null, filename: 'logo.png', scale: 1 }, +]; + +describe('assets loader public paths on Windows', () => { + it.each([ + [ + 'http://localhost:9999', + 'src\\img', + 'http://localhost:9999/assets/src/img', + ], + [ + 'https://cdn.example.com/assets/', + 'node_modules\\pkg\\img', + 'https://cdn.example.com/assets/assets/node_modules/pkg/img', + ], + ])( + 'builds a valid remote URL from %s', + (remotePublicPath, resourceDirname, expected) => { + const code = convertToRemoteAssets({ + assets, + assetsDirname: 'assets', + remotePublicPath, + resourceDirname, + resourceExtensionType: 'png', + resourceFilename: 'logo', + resourcePath: 'logo.png', + suffixPattern: '', + pathSeparatorRegexp, + }); + + expect(code).toContain(`"httpServerLocation":"${expected}"`); + } + ); + + it.each([ + [undefined, 'assets/src/img'], + ['/public', '/public/assets/src/img'], + ])('joins publicPath %s with forward slashes', (publicPath, expected) => { + const code = extractAssets( + { + resourcePath: 'logo.png', + resourceDirname: 'src\\img', + resourceFilename: 'logo', + resourceExtensionType: 'png', + assets, + suffixPattern: '', + assetsDirname: 'assets', + pathSeparatorRegexp, + publicPath, + }, + { debug: () => {} } + ); + + expect(code).toContain(`httpServerLocation: "${expected}"`); + }); +}); diff --git a/packages/repack/src/loaders/assetsLoader/convertToRemoteAssets.ts b/packages/repack/src/loaders/assetsLoader/convertToRemoteAssets.ts index 3b3b08572..b8e95f1a4 100644 --- a/packages/repack/src/loaders/assetsLoader/convertToRemoteAssets.ts +++ b/packages/repack/src/loaders/assetsLoader/convertToRemoteAssets.ts @@ -26,8 +26,10 @@ export function convertToRemoteAssets({ .join(assetsDirname, resourceDirname) .replace(pathSeparatorRegexp, '/'); - // works on both unix & windows - const publicPathURL = new URL(path.join(remotePublicPath, assetPath)); + // `remotePublicPath` is a URL, not a filesystem path, so it is joined with + // `path.posix` — `path.join` would rewrite the separators on Windows and + // produce something `new URL` rejects. + const publicPathURL = new URL(path.posix.join(remotePublicPath, assetPath)); const size = getAssetSize(assets); diff --git a/packages/repack/src/loaders/assetsLoader/extractAssets.ts b/packages/repack/src/loaders/assetsLoader/extractAssets.ts index f50d91e80..665182c4d 100644 --- a/packages/repack/src/loaders/assetsLoader/extractAssets.ts +++ b/packages/repack/src/loaders/assetsLoader/extractAssets.ts @@ -36,7 +36,10 @@ export function extractAssets( .replace(pathSeparatorRegexp, '/'); if (customPublicPath) { - publicPath = path.join(customPublicPath, publicPath); + // `publicPath` is served over HTTP and always uses forward slashes, so it + // is joined with `path.posix` regardless of the platform the bundle is + // built on. + publicPath = path.posix.join(customPublicPath, publicPath); } const size = getAssetSize(assets);