Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
614b361
chore(turbopack-node): remove some outdated codes
xusd320 Nov 14, 2025
cd3c104
refactor(turbopack-node): support execution by napi and worker_threads
xusd320 Nov 18, 2025
b9b8fc1
Merge branch 'canary' into refactor/turbopack-node-napi
xusd320 Nov 18, 2025
da23168
feat(turbopack-node): run webpack loaders via napi, keep features for…
xusd320 Nov 25, 2025
9d25693
merge master
xusd320 Nov 25, 2025
0190326
chore: remove build.rs in turbopack-node
xusd320 Nov 25, 2025
e59a6b7
fix: turbopack ci
xusd320 Nov 25, 2025
c388c0d
chore: rename types
xusd320 Nov 25, 2025
eaf47da
fix: features conflict when cargo clippy
xusd320 Nov 25, 2025
bc0c59e
chore: rename types
xusd320 Nov 26, 2025
54c7f93
perf(turbopack-node): use u32 as worker operation task_id
xusd320 Nov 26, 2025
e2e9387
chore: remove unused deps
xusd320 Nov 26, 2025
93f220a
Merge branch 'canary' into refactor/turbopack-node-napi
xusd320 Nov 26, 2025
3fb7eee
refactor(turbopack-node): using async waiting pool creation message
xusd320 Nov 26, 2025
9cc5cc9
fix(turbopack-node): use correct binding path to create loader pool
xusd320 Nov 26, 2025
27d3ee4
fix: clippy again
xusd320 Nov 26, 2025
f496ba6
feat(turbopack-node): restore wait_or_kill operation for worker thread
xusd320 Nov 27, 2025
b4f96c8
fix(turbopack-node): sass-loader failed via worker thread
xusd320 Nov 27, 2025
4f25a16
merge upstream/canary
xusd320 Nov 27, 2025
b97a541
chore: fix cargo fmt
xusd320 Nov 27, 2025
5124441
feat(turbopack-node): restore scale for process pool
xusd320 Nov 27, 2025
29eb97c
fix: cleanup worker threads channel when project shutdown
xusd320 Nov 28, 2025
3370647
fix(turbopack-node): sass-loader failed via worker thread again
xusd320 Nov 29, 2025
7e52e86
Merge branch 'canary' into refactor/turbopack-node-napi
xusd320 Nov 29, 2025
0cae1b9
Update evaluate.ts
xusd320 Nov 29, 2025
f0ed5d9
fix(turbopack-node): async loop running in loaders worker thread
xusd320 Dec 3, 2025
a2d8839
fix(turbpack-node): gracefully loader worker kill
xusd320 Dec 4, 2025
5b1574b
Merge branch 'canary' into refactor/turbopack-node-napi
xusd320 Dec 4, 2025
3c338cd
fix(turbpack-node): env lost in loader worker thread
xusd320 Dec 4, 2025
d155198
Merge branch 'canary' into refactor/turbopack-node-napi
xusd320 Dec 4, 2025
0123f6b
perf(turbopack-node): use RcStr for worker thread messages
xusd320 Dec 5, 2025
53b7d8a
fix(turbpack-node): cwd lost in loader worker thread and avoid env de…
xusd320 Dec 5, 2025
89a6426
Merge branch 'canary' into refactor/turbopack-node-napi
xusd320 Dec 5, 2025
f438bce
fix: napi dts
xusd320 Dec 5, 2025
159b600
fix(turbopack-node): bring back sass-loader resolving fixing
xusd320 Dec 5, 2025
0e31540
feat(turbopack-node): support worker pool scaling
xusd320 Dec 5, 2025
ea4fcad
Merge branch 'canary' into refactor/turbopack-node-napi
xusd320 Dec 5, 2025
e8286c9
chore: remove gracefullyKillWorker timeout
xusd320 Dec 5, 2025
0a28ae8
fix(turbopack-node): js -> rust message channel been blocked
xusd320 Dec 6, 2025
fd66107
chore: better apis name
xusd320 Dec 6, 2025
fc3a149
ci: debugging
xusd320 Dec 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ turbopack-env = { path = "turbopack/crates/turbopack-env" }
turbopack-image = { path = "turbopack/crates/turbopack-image" }
turbopack-json = { path = "turbopack/crates/turbopack-json" }
turbopack-mdx = { path = "turbopack/crates/turbopack-mdx" }
turbopack-node = { path = "turbopack/crates/turbopack-node" }
turbopack-node = { path = "turbopack/crates/turbopack-node", default-features = false }
turbopack-resolve = { path = "turbopack/crates/turbopack-resolve" }
turbopack-static = { path = "turbopack/crates/turbopack-static" }
turbopack-swc-utils = { path = "turbopack/crates/turbopack-swc-utils" }
Expand Down Expand Up @@ -422,6 +422,8 @@ napi = { version = "2", default-features = false, features = [
"napi5",
"compat-mode",
] }
napi-derive = "2"
napi-build = "2"
notify = "8.1.0"
once_cell = "1.17.1"
owo-colors = "4.2.2"
Expand Down
20 changes: 10 additions & 10 deletions crates/napi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ workspace = true

[package.metadata.cargo-shear]
ignored = [
# we need to set features on these packages when building for WASM, but we don't directly use them
"getrandom",
"iana-time-zone",
# the plugins feature needs to set a feature on this transitively depended-on package, we never
# directly import it
"turbopack-ecmascript-plugins",
# we need to set features on these packages when building for WASM, but we don't directly use them
"getrandom",
"iana-time-zone",
# the plugins feature needs to set a feature on this transitively depended-on package, we never
# directly import it
"turbopack-ecmascript-plugins",
]

[dependencies]
Expand All @@ -64,7 +64,7 @@ flate2 = { workspace = true }
futures-util = { workspace = true }
owo-colors = { workspace = true }
napi = { workspace = true }
napi-derive = "2"
napi-derive = { workspace = true }
next-custom-transforms = { workspace = true }
next-taskless = { workspace = true }
rand = { workspace = true }
Expand Down Expand Up @@ -116,14 +116,15 @@ next-core = { workspace = true }
mdxjs = { workspace = true, features = ["serializable"] }

turbo-tasks-malloc = { workspace = true, default-features = false, features = [
"custom_allocator"
"custom_allocator",
] }

turbopack-core = { workspace = true }
turbopack-ecmascript-hmr-protocol = { workspace = true }
turbopack-trace-utils = { workspace = true }
turbopack-trace-server = { workspace = true }
turbopack-ecmascript-plugins = { workspace = true, optional = true }
turbopack-node = { workspace = true, default-features = false, features = ["worker_pool"] }

[target.'cfg(windows)'.dependencies]
windows-sys = "0.60"
Expand All @@ -144,8 +145,7 @@ tokio = { workspace = true, features = ["full"] }

[build-dependencies]
anyhow = { workspace = true }
napi-build = "2"
napi-build = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
vergen-gitcl = { workspace = true }

2 changes: 1 addition & 1 deletion crates/next-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ turbopack-browser = { workspace = true }
turbopack-core = { workspace = true }
turbopack-css = { workspace = true }
turbopack-ecmascript = { workspace = true }
turbopack-node = { workspace = true }
turbopack-node = { workspace = true, default-features = false }
turbopack-nodejs = { workspace = true }
turbopack-resolve = { workspace = true }
turbopack-wasm = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/next-build-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ turbo-tasks = { workspace = true }
turbo-tasks-backend = { workspace = true }
turbo-tasks-malloc = { workspace = true }
turbopack-trace-utils = { workspace = true }

turbopack-node = { workspace = true, default-features = false, features = ["process_pool"]}
2 changes: 1 addition & 1 deletion crates/next-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ turbopack-ecmascript-plugins = { workspace = true, features = [
] }
turbopack-ecmascript-runtime = { workspace = true }
turbopack-image = { workspace = true }
turbopack-node = { workspace = true }
turbopack-node = { workspace = true, default-features = false }
turbopack-nodejs = { workspace = true }
turbopack-resolve = { workspace = true }
turbopack-static = { workspace = true }
Expand Down
22 changes: 22 additions & 0 deletions packages/next/src/build/swc/generated-native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,28 @@ export declare class ExternalObject<T> {
[K: symbol]: T
}
}
export interface NapiPoolOptions {
filename: RcStr
concurrency: number
env: Record<string, string>
cwd: RcStr
}
export interface WorkerTermination {
filename: RcStr
workerId: number
}
export declare function recvPoolRequest(): Promise<NapiPoolOptions>
export declare function recvWorkerTermination(): Promise<WorkerTermination>
export declare function recvWorkerRequest(filename: RcStr): Promise<number>
export declare function recvMessageInWorker(workerId: number): Promise<string>
export declare function notifyWorkerAck(
taskId: number,
workerId: number
): Promise<void>
export declare function sendTaskMessage(
taskId: number,
message: string
): Promise<void>
export declare function lockfileTryAcquireSync(
path: string
): { __napiType: 'Lockfile' } | null
Expand Down
32 changes: 25 additions & 7 deletions packages/next/src/build/swc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import type {
WrittenEndpoint,
} from './types'
import { throwTurbopackInternalError } from '../../shared/lib/turbopack/internal-error'
import { runLoaderWorkerPool } from './loaderWorkerPool'

type RawBindings = typeof import('./generated-native')
type RawWasmBindings = typeof import('./generated-wasm') & {
Expand Down Expand Up @@ -494,6 +495,7 @@ const normalizePathOnWindows = (p: string) =>
// TODO(sokra) Support wasm option.
function bindingToApi(
binding: RawBindings,
bindingPath: string,
_wasm: boolean
): Binding['turbo']['createProject'] {
type NativeFunction<T> = (
Expand Down Expand Up @@ -674,6 +676,13 @@ function bindingToApi(

constructor(nativeProject: { __napiType: 'Project' }) {
this._nativeProject = nativeProject

if (
typeof binding.recvPoolRequest === 'function' &&
typeof binding.recvWorkerTermination === 'function'
) {
runLoaderWorkerPool(binding, bindingPath)
}
}

async update(options: PartialProjectOptions) {
Expand Down Expand Up @@ -1361,30 +1370,34 @@ function loadNative(importPath?: string) {
throw new Error('cannot run loadNative when `NEXT_TEST_WASM` is set')
}

const customBindingsPath = !!__INTERNAL_CUSTOM_TURBOPACK_BINDINGS
? require.resolve(__INTERNAL_CUSTOM_TURBOPACK_BINDINGS)
: null
const customBindings: RawBindings = !!__INTERNAL_CUSTOM_TURBOPACK_BINDINGS
? require(__INTERNAL_CUSTOM_TURBOPACK_BINDINGS)
: null
let bindings: RawBindings = customBindings
let bindingsPath = customBindingsPath
let attempts: any[] = []

const NEXT_TEST_NATIVE_DIR = process.env.NEXT_TEST_NATIVE_DIR
for (const triple of triples) {
if (NEXT_TEST_NATIVE_DIR) {
try {
const bindingForTest = `${NEXT_TEST_NATIVE_DIR}/next-swc.${triple.platformArchABI}.node`
// Use the binary directly to skip `pnpm pack` for testing as it's slow because of the large native binary.
bindings = require(
`${NEXT_TEST_NATIVE_DIR}/next-swc.${triple.platformArchABI}.node`
)
bindings = require(bindingForTest)
bindingsPath = require.resolve(bindingForTest)
infoLog(
'next-swc build: local built @next/swc from NEXT_TEST_NATIVE_DIR'
)
break
} catch (e) {}
} else {
try {
bindings = require(
`@next/swc/native/next-swc.${triple.platformArchABI}.node`
)
const normalBinding = `@next/swc/native/next-swc.${triple.platformArchABI}.node`
bindings = require(normalBinding)
bindingsPath = require.resolve(normalBinding)
infoLog('next-swc build: local built @next/swc')
break
} catch (e) {}
Expand All @@ -1402,6 +1415,7 @@ function loadNative(importPath?: string) {
: `@next/swc-${triple.platformArchABI}`
try {
bindings = require(pkg)
bindingsPath = require.resolve(pkg)
if (!importPath) {
checkVersionMismatch(require(`${pkg}/package.json`))
}
Expand Down Expand Up @@ -1480,7 +1494,11 @@ function loadNative(importPath?: string) {
initCustomTraceSubscriber: bindings.initCustomTraceSubscriber,
teardownTraceSubscriber: bindings.teardownTraceSubscriber,
turbo: {
createProject: bindingToApi(customBindings ?? bindings, false),
createProject: bindingToApi(
customBindings ?? bindings,
customBindingsPath ?? bindingsPath!,
false
),
startTurbopackTraceServer(traceFilePath, port) {
Log.warn(
`Turbopack trace server started. View trace at https://trace.nextjs.org${port != null ? `?port=${port}` : ''}`
Expand Down
96 changes: 96 additions & 0 deletions packages/next/src/build/swc/loaderWorkerPool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Worker } from 'worker_threads'

const loaderWorkers: Record<string, Array<Worker>> = {}

const KillMsg = '__kill__'

export async function runLoaderWorkerPool(
binding: typeof import('./generated-native'),
bindingPath: string
) {
await Promise.all([
runPoolScaler(binding, bindingPath),
runWorkerTerminator(binding),
])
}

async function runPoolScaler(
binding: typeof import('./generated-native'),
bindingPath: string
) {
while (true) {
try {
console.log('[loaderWorkerPool] waiting for pool request')
let poolOptions = await binding.recvPoolRequest()
console.log('[loaderWorkerPool] received pool request', {
...poolOptions,
env: 'omitted',
})
const { filename, concurrency, env, cwd } = poolOptions
// Wildcard of "*" meaning to scale all of pools even with different poolId
const workers =
filename === '*'
? Object.values(loaderWorkers).flat()
: loaderWorkers[filename] || (loaderWorkers[filename] = [])
if (workers.length < concurrency) {
for (let i = workers.length; i < concurrency; i++) {
const worker = new Worker(filename, {
workerData: {
poolId: filename,
bindingPath,
cwd,
},
env,
})
workers.push(worker)
}
} else if (workers.length > concurrency) {
const workersToKill = workers.splice(0, workers.length - concurrency)
workersToKill.forEach(terminateWorker)
}
} catch (_) {
// rust channel closed, do nothing
return
}
}
}

async function runWorkerTerminator(
binding: typeof import('./generated-native')
) {
while (true) {
try {
console.log('[loaderWorkerPool] waiting for worker termination')
const { filename, workerId } = await binding.recvWorkerTermination()
console.log('[loaderWorkerPool] received worker termination', {
filename,
workerId,
})
const workers = loaderWorkers[filename]
const workerIdx = workers.findIndex(
(worker) => worker.threadId === workerId
)
if (workerIdx > -1) {
const workersToKill = workers.splice(workerIdx, 1)
workersToKill.forEach(terminateWorker)
}
} catch (_) {
// rust channel closed, do nothing
return
}
}
}

async function terminateWorker(worker: Worker) {
await new Promise<void>((resolve) => {
const onMessage = (msg: any) => {
if (msg === KillMsg) {
worker.off('message', onMessage)
resolve()
}
}
worker.on('message', onMessage)
worker.postMessage(KillMsg)
})
await worker.terminate()
}
6 changes: 6 additions & 0 deletions turbopack/crates/turbo-rcstr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@ impl AsRef<[u8]> for RcStr {
}
}

impl AsRef<str> for RcStr {
fn as_ref(&self) -> &str {
self.as_str()
}
}

impl From<RcStr> for BytesStr {
fn from(value: RcStr) -> Self {
Self::from_str_slice(value.as_str())
Expand Down
Loading
Loading