-
Notifications
You must be signed in to change notification settings - Fork 27.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace optionalDependencies with script for installing swc #33496
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,3 +43,4 @@ test-timings.json | |
|
||
# Cache | ||
*.tsbuildinfo | ||
packages/next/native |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
The ISC License | ||
|
||
Copyright (c) Isaac Z. Schlueter and Contributors | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted, provided that the above | ||
copyright notice and this permission notice appear in all copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR | ||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"name":"tar","main":"index.js","author":"Isaac Z. Schlueter <[email protected]> (http://blog.izs.me/)","license":"ISC"} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,256 @@ | ||||||
const fs = require('fs') | ||||||
const os = require('os') | ||||||
const path = require('path') | ||||||
const tar = require('next/dist/compiled/tar') | ||||||
|
||||||
if (process.env.NEXT_SWC_SKIP_INSTALL) { | ||||||
process.exit(0) | ||||||
} | ||||||
|
||||||
const MAX_VERSIONS_TO_CACHE = 5 | ||||||
const { version } = require('next/package.json') | ||||||
|
||||||
// @ts-ignore https://nodejs.org/api/report.html | ||||||
const { glibcVersionRuntime } = process.report.getReport().header | ||||||
const isGlibc = !!glibcVersionRuntime | ||||||
|
||||||
/** | ||||||
@type { | ||||||
[platform: string]: { | ||||||
[arch: string]: { | ||||||
// function to check if the arch/platform fully matches | ||||||
// e.g. check if linux is glibc/musl | ||||||
check?: () => {} | ||||||
packageName: string | ||||||
}[] | ||||||
} | ||||||
} | ||||||
*/ | ||||||
const packageMap = { | ||||||
win32: { | ||||||
x64: [ | ||||||
{ | ||||||
packageName: '@next/swc-win32-x64-msvc', | ||||||
}, | ||||||
], | ||||||
ia32: [ | ||||||
{ | ||||||
packageName: '@next/swc-win32-ia32-msvc', | ||||||
}, | ||||||
], | ||||||
arm64: [ | ||||||
{ | ||||||
packageName: '@next/swc-win32-arm64-msvc', | ||||||
}, | ||||||
], | ||||||
}, | ||||||
linux: { | ||||||
x64: [ | ||||||
{ | ||||||
packageName: '@next/swc-linux-x64-gnu', | ||||||
check: () => isGlibc, | ||||||
}, | ||||||
{ | ||||||
packageName: '@next/swc-linux-x64-musl', | ||||||
check: () => !isGlibc, | ||||||
}, | ||||||
], | ||||||
arm64: [ | ||||||
{ | ||||||
packageName: '@next/swc-linux-arm64-gnu', | ||||||
check: () => isGlibc, | ||||||
}, | ||||||
{ | ||||||
packageName: '@next/swc-linux-arm64-musl', | ||||||
check: () => !isGlibc, | ||||||
}, | ||||||
], | ||||||
arm: [ | ||||||
{ | ||||||
packageName: '@next/swc-linux-arm-gnueabihf', | ||||||
}, | ||||||
], | ||||||
}, | ||||||
darwin: { | ||||||
x64: [ | ||||||
{ | ||||||
packageName: '@next/swc-darwin-x64', | ||||||
}, | ||||||
], | ||||||
arm64: [ | ||||||
{ | ||||||
packageName: '@next/swc-darwin-arm64', | ||||||
}, | ||||||
], | ||||||
}, | ||||||
android: { | ||||||
arm64: [ | ||||||
{ | ||||||
packageName: '@next/swc-android-arm64', | ||||||
}, | ||||||
], | ||||||
}, | ||||||
} | ||||||
|
||||||
let activePackage = packageMap[process.platform]?.[process.arch]?.find( | ||||||
(item) => { | ||||||
return typeof item.check === 'undefined' ? true : item.check() | ||||||
ijjk marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
) | ||||||
|
||||||
if (!activePackage) { | ||||||
// TODO: should this be a hard error even though it fails install? | ||||||
// should we fallback to wasm in this case? | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Falling back to wasm makes sense to me. Thats the advantage of this custom install script π There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, falling back to wasm sounds good to me. |
||||||
console.error( | ||||||
`Error: unsupported next-swc platform: ` + | ||||||
`${process.platform} ${process.arch} (glibc: ${isGlibc})\n` + | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
`Please report this on the feedback thread here: https://github.com/vercel/next.js/discussions/30468` | ||||||
) | ||||||
process.exit(0) | ||||||
} | ||||||
|
||||||
const localFileName = `next-swc.${activePackage.packageName.substr(10)}.node` | ||||||
const tarFileName = `${activePackage.packageName.substr(6)}-${version}.tgz` | ||||||
const outputDirectory = path.join(__dirname, 'native') | ||||||
|
||||||
const exists = (filePath) => { | ||||||
return fs.promises | ||||||
.access(filePath, fs.constants.F_OK) | ||||||
.then(() => true) | ||||||
.catch(() => false) | ||||||
} | ||||||
const rmFile = (filePath) => { | ||||||
return fs.promises.unlink(filePath).catch(() => {}) | ||||||
} | ||||||
|
||||||
;(async () => { | ||||||
const outputFilepath = path.join(outputDirectory, localFileName) | ||||||
const versionFilepath = path.join(outputDirectory, 'version.txt') | ||||||
|
||||||
// check if native folder already has an extracted copy of swc, | ||||||
// nothing further is needed if so | ||||||
if ( | ||||||
(await exists(outputFilepath)) && | ||||||
(await fs.promises.readFile(versionFilepath, 'utf8')) === version | ||||||
) { | ||||||
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 = (() => { | ||||||
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 { | ||||||
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 | ||||||
})() | ||||||
|
||||||
const extractFromTar = async () => { | ||||||
await fs.promises.mkdir(outputDirectory).catch((err) => { | ||||||
if (err.code !== 'EEXIST') throw err | ||||||
}) | ||||||
const tarFilepath = path.join(cacheDirectory, tarFileName) | ||||||
const readStream = fs.createReadStream(tarFilepath) | ||||||
const writeStream = fs.createWriteStream(outputFilepath) | ||||||
let foundEntry = false | ||||||
|
||||||
await new Promise((resolve, reject) => { | ||||||
readStream | ||||||
.pipe(tar.t()) | ||||||
.on('entry', (entry) => { | ||||||
if (entry.path.endsWith('.node')) { | ||||||
foundEntry = true | ||||||
entry | ||||||
.pipe(writeStream) | ||||||
.on('error', (err) => reject(err)) | ||||||
.on('finish', () => resolve()) | ||||||
} | ||||||
}) | ||||||
.on('error', (err) => reject(err)) | ||||||
.on('finish', () => { | ||||||
if (!foundEntry) { | ||||||
reject(new Error(`Failed to find entry in ${tarFilepath}`)) | ||||||
} | ||||||
}) | ||||||
}) | ||||||
await rmFile(versionFilepath) | ||||||
await fs.promises.writeFile(versionFilepath, version) | ||||||
} | ||||||
|
||||||
// check cache first if it exists copy from there | ||||||
if (await exists(path.join(cacheDirectory, tarFileName))) { | ||||||
await extractFromTar() | ||||||
return | ||||||
} | ||||||
|
||||||
// download fresh copy and populate cache | ||||||
const fetch = require('next/dist/compiled/node-fetch') | ||||||
|
||||||
const tempFile = path.join( | ||||||
cacheDirectory, | ||||||
`${tarFileName}.temp-${Date.now()}` | ||||||
) | ||||||
await fs.promises.mkdir(cacheDirectory, { recursive: true }) | ||||||
|
||||||
try { | ||||||
await fetch( | ||||||
`https://registry.npmjs.org/${activePackage.packageName}/-/${tarFileName}` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some setups don't have access to the npm registry so this will be problematic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this is problematic because package config like Perhaps we need to double down on getting this feature into npm now that yarn just landed it in yarnpkg/berry#3981 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can definitely handle loading the registry from the package config. I think it's gonna be a while before this is sorted out/performant in relevant package managers so the custom install script will still be needed for a bit. |
||||||
).then((res) => { | ||||||
if (!res.ok) { | ||||||
throw new Error(`request failed with status ${res.status}`) | ||||||
} | ||||||
const cacheWriteStream = fs.createWriteStream(tempFile) | ||||||
|
||||||
return new Promise((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() | ||||||
|
||||||
for (let i = MAX_VERSIONS_TO_CACHE - 1; i++; i < cacheFiles.length) { | ||||||
await rmFile(path.join(cacheDirectory, cacheFiles[i])) | ||||||
} | ||||||
} | ||||||
} catch (err) { | ||||||
await rmFile(tempFile) | ||||||
console.error(`Failed to download swc binary from npm`, err) | ||||||
process.exit(1) | ||||||
} | ||||||
})() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this block any more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should for most cases although we could leave it in case users want to manually install the package and skip our custom install script π€