rsnext/packages/next/lib/download-wasm-swc.ts
OJ Kwon 17244b8a23
feat(next/swc): enable wasm first binding load for the platforms (#38883)
<!--
Thanks for opening a PR! Your contribution is much appreciated.
In order to make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below.
Choose the right checklist for the change that you're making:
-->

This PR enables a path to loading wasm binding first for the few platforms we'll attempt to remove native binaries. As a first step, this change does not actually removes native bindings, but will try to load wasm binding first and use it if loading success. 

It may take some time to actually remove native bindings, I expect we may need to fix few regressions from wasm bindings for some places like loading / installing itself for the platforms not being used widely, meanwhile native bindings can be used as a fallback.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)

Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-23 14:59:13 -05:00

137 lines
4.5 KiB
TypeScript

import os from 'os'
import fs from 'fs'
import path from 'path'
import * as Log from '../build/output/log'
import { execSync } from 'child_process'
import tar from 'next/dist/compiled/tar'
import fetch from 'next/dist/compiled/node-fetch'
import { fileExists } from './file-exists'
const MAX_VERSIONS_TO_CACHE = 5
export async function downloadWasmSwc(
version: string,
wasmDirectory: string,
variant: 'nodejs' | 'web' = 'nodejs'
) {
const pkgName = `@next/swc-wasm-${variant}`
const tarFileName = `${pkgName.substring(6)}-${version}.tgz`
const outputDirectory = path.join(wasmDirectory, pkgName)
if (await fileExists(outputDirectory)) {
// if the package is already downloaded a different
// failure occurred than not being present
return
}
// 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 = await (async () => {
let result
const envDefined = process.env['NEXT_SWC_PATH']
if (envDefined) {
result = envDefined
} else {
let systemCacheDirectory
if (process.platform === 'linux') {
systemCacheDirectory =
process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache')
} else if (process.platform === 'darwin') {
systemCacheDirectory = path.join(os.homedir(), 'Library', 'Caches')
} else if (process.platform === 'win32') {
systemCacheDirectory =
process.env.LOCALAPPDATA ||
path.join(os.homedir(), 'AppData', 'Local')
} else {
/// 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')
}
if (!path.isAbsolute(result)) {
// It is important to resolve to the absolute path:
// - for unzipping to work correctly;
// - so that registry directory matches between installation and execution.
// INIT_CWD points to the root of `npm/yarn install` and is probably what
// the user meant when typing the relative path.
result = path.resolve(process.env['INIT_CWD'] || process.cwd(), result)
}
return result
})()
await fs.promises.mkdir(outputDirectory, { recursive: true })
const extractFromTar = async () => {
await tar.x({
file: path.join(cacheDirectory, tarFileName),
cwd: outputDirectory,
strip: 1,
})
}
if (!(await fileExists(path.join(cacheDirectory, tarFileName)))) {
Log.info('Downloading WASM swc package...')
await fs.promises.mkdir(cacheDirectory, { recursive: true })
const tempFile = path.join(
cacheDirectory,
`${tarFileName}.temp-${Date.now()}`
)
let registry = `https://registry.npmjs.org/`
try {
const output = execSync('npm config get registry').toString().trim()
if (output.startsWith('http')) {
registry = output.endsWith('/') ? output : `${output}/`
}
} catch (_) {}
await fetch(`${registry}${pkgName}/-/${tarFileName}`).then((res) => {
if (!res.ok) {
throw new Error(`request failed with status ${res.status}`)
}
const cacheWriteStream = fs.createWriteStream(tempFile)
return new Promise<void>((resolve, reject) => {
res.body
.pipe(cacheWriteStream)
.on('error', (err) => reject(err))
.on('finish', () => resolve())
}).finally(() => cacheWriteStream.close())
})
await fs.promises.rename(tempFile, path.join(cacheDirectory, tarFileName))
}
await extractFromTar()
const cacheFiles = await fs.promises.readdir(cacheDirectory)
if (cacheFiles.length > MAX_VERSIONS_TO_CACHE) {
cacheFiles.sort((a, b) => {
if (a.length < b.length) return -1
return a.localeCompare(b)
})
// prune oldest versions in cache
for (let i = 0; i++; i < cacheFiles.length - MAX_VERSIONS_TO_CACHE) {
await fs.promises
.unlink(path.join(cacheDirectory, cacheFiles[i]))
.catch(() => {})
}
}
}