diff --git a/packages/next/build/swc/index.js b/packages/next/build/swc/index.js index 054f42d961d8805..bbc60d0c06c5cd3 100644 --- a/packages/next/build/swc/index.js +++ b/packages/next/build/swc/index.js @@ -13,6 +13,20 @@ const ArchName = arch() const PlatformName = platform() const triples = platformArchTriples[PlatformName][ArchName] || [] +// These are the platforms we'll try to load wasm bindings first, +// only try to load native bindings if loading wasm binding somehow fails. +// Fallback to native binding is for migration period only, +// once we can verify loading-wasm-first won't cause visible regressions, +// we'll not include native bindings for these platform at all. +const knownDefaultWasmFallbackTriples = [ + 'aarch64-linux-android', + 'x86_64-unknown-freebsd', + 'aarch64-pc-windows-msvc', + 'arm-linux-androideabi', + 'armv7-unknown-linux-gnueabihf', + 'i686-pc-windows-msvc', +] + let nativeBindings let wasmBindings let downloadWasmPromise @@ -35,44 +49,30 @@ export async function loadBindings() { } let attempts = [] - try { - return resolve(loadNative()) - } catch (a) { - attempts = attempts.concat(a) + const shouldLoadWasmFallbackFirst = triples.some( + (triple) => + !!triple?.raw && knownDefaultWasmFallbackTriples.includes(triple.raw) + ) + + if (shouldLoadWasmFallbackFirst) { + const fallbackBindings = await tryLoadWasmWithFallback(attempts) + if (fallbackBindings) { + return resolve(fallbackBindings) + } } try { - let bindings = await loadWasm() - eventSwcLoadFailure({ wasm: 'enabled' }) - return resolve(bindings) + return resolve(loadNative()) } catch (a) { attempts = attempts.concat(a) } - try { - // if not installed already download wasm package on-demand - // we download to a custom directory instead of to node_modules - // as node_module import attempts are cached and can't be re-attempted - // x-ref: https://github.com/nodejs/modules/issues/307 - const wasmDirectory = path.join( - path.dirname(require.resolve('next/package.json')), - 'wasm' - ) - if (!downloadWasmPromise) { - downloadWasmPromise = downloadWasmSwc(nextVersion, wasmDirectory) - } - await downloadWasmPromise - let bindings = await loadWasm(pathToFileURL(wasmDirectory).href) - eventSwcLoadFailure({ wasm: 'fallback' }) - - // still log native load attempts so user is - // aware it failed and should be fixed - for (const attempt of attempts) { - Log.warn(attempt) + // For these platforms we already tried to load wasm and failed, skip reattempt + if (!shouldLoadWasmFallbackFirst) { + const fallbackBindings = await tryLoadWasmWithFallback(attempts) + if (fallbackBindings) { + return resolve(fallbackBindings) } - return resolve(bindings) - } catch (a) { - attempts = attempts.concat(a) } logLoadFailure(attempts, true) @@ -80,6 +80,42 @@ export async function loadBindings() { return pendingBindings } +async function tryLoadWasmWithFallback(attempts) { + try { + let bindings = await loadWasm() + eventSwcLoadFailure({ wasm: 'enabled' }) + return bindings + } catch (a) { + attempts = attempts.concat(a) + } + + try { + // if not installed already download wasm package on-demand + // we download to a custom directory instead of to node_modules + // as node_module import attempts are cached and can't be re-attempted + // x-ref: https://github.com/nodejs/modules/issues/307 + const wasmDirectory = path.join( + path.dirname(require.resolve('next/package.json')), + 'wasm' + ) + if (!downloadWasmPromise) { + downloadWasmPromise = downloadWasmSwc(nextVersion, wasmDirectory) + } + await downloadWasmPromise + let bindings = await loadWasm(pathToFileURL(wasmDirectory).href) + eventSwcLoadFailure({ wasm: 'fallback' }) + + // still log native load attempts so user is + // aware it failed and should be fixed + for (const attempt of attempts) { + Log.warn(attempt) + } + return bindings + } catch (a) { + attempts = attempts.concat(a) + } +} + function loadBindingsSync() { let attempts = [] try { @@ -136,7 +172,7 @@ async function loadWasm(importPath = '') { if (pkg === '@next/swc-wasm-web') { bindings = await bindings.default() } - Log.info('Using experimental wasm build of next-swc') + Log.info('Using wasm build of next-swc') // Note wasm binary does not support async intefaces yet, all async // interface coereces to sync interfaces. diff --git a/packages/next/lib/download-wasm-swc.ts b/packages/next/lib/download-wasm-swc.ts index f1d3ac2eaa5bf40..1074d1d2f91a829 100644 --- a/packages/next/lib/download-wasm-swc.ts +++ b/packages/next/lib/download-wasm-swc.ts @@ -26,7 +26,7 @@ export async function downloadWasmSwc( // get platform specific cache directory adapted from playwright's handling // https://github.com/microsoft/playwright/blob/7d924470d397975a74a19184c136b3573a974e13/packages/playwright-core/src/utils/registry.ts#L141 - const cacheDirectory = (() => { + const cacheDirectory = await (async () => { let result const envDefined = process.env['NEXT_SWC_PATH'] @@ -44,8 +44,23 @@ export async function downloadWasmSwc( process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local') } else { - console.error(new Error('Unsupported platform: ' + process.platform)) - process.exit(0) + /// Attempt to use generic tmp location for these platforms + if (process.platform === 'freebsd' || process.platform === 'android') { + for (const dir of [ + path.join(os.homedir(), '.cache'), + path.join(os.tmpdir()), + ]) { + if (await fileExists(dir)) { + systemCacheDirectory = dir + break + } + } + } + + if (!systemCacheDirectory) { + console.error(new Error('Unsupported platform: ' + process.platform)) + process.exit(0) + } } result = path.join(systemCacheDirectory, 'next-swc') }