diff --git a/crates/napi/src/next_api/project.rs b/crates/napi/src/next_api/project.rs index cf80a01c723b6..8f170fa7ce722 100644 --- a/crates/napi/src/next_api/project.rs +++ b/crates/napi/src/next_api/project.rs @@ -256,7 +256,7 @@ pub struct ProjectInstance { exit_receiver: tokio::sync::Mutex>, } -#[napi(ts_return_type = "{ __napiType: \"Project\" }")] +#[napi(ts_return_type = "Promise<{ __napiType: \"Project\" }>")] pub async fn project_new( options: NapiProjectOptions, turbo_engine_options: NapiTurboEngineOptions, @@ -421,7 +421,7 @@ async fn benchmark_file_io(directory: Vc) -> Result, options: NapiPartialProjectOptions, @@ -439,7 +439,7 @@ pub async fn project_update( Ok(()) } -#[napi(ts_return_type = "{ __napiType: \"Project\" }")] +#[napi] pub async fn project_shutdown( #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External, ) { diff --git a/crates/napi/src/turbopack.rs b/crates/napi/src/turbopack.rs index 565ec53a29a6b..bff2f71c0b96a 100644 --- a/crates/napi/src/turbopack.rs +++ b/crates/napi/src/turbopack.rs @@ -208,8 +208,3 @@ impl From for RouteHas { } } } - -#[napi] -pub async fn experimental_turbo(_unused: Buffer) -> napi::Result<()> { - unimplemented!("__experimental_turbo is not yet implemented"); -} diff --git a/package.json b/package.json index 015c55aecf06c..9c5f4c291b99a 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "update-google-fonts": "node ./scripts/update-google-fonts.js", "patch-next": "node scripts/patch-next.cjs", "unpack-next": "node scripts/unpack-next.cjs", - "swc-build-native": "pnpm --filter=@next/swc build-native", + "swc-build-native": "node scripts/build-native.cjs", + "swc-build-wasm": "node scripts/build-wasm.cjs", "sweep": "node scripts/sweep.cjs" }, "devDependencies": { diff --git a/packages/next/src/build/collect-build-traces.ts b/packages/next/src/build/collect-build-traces.ts index 7c926ff1f5613..cb23aa5bd2793 100644 --- a/packages/next/src/build/collect-build-traces.ts +++ b/packages/next/src/build/collect-build-traces.ts @@ -27,6 +27,7 @@ import { normalizeAppPath } from '../shared/lib/router/utils/app-paths' import isError from '../lib/is-error' import type { NodeFileTraceReasons } from '@vercel/nft' import type { RoutesUsingEdgeRuntime } from './utils' +import type { ExternalObject, TurboTasks } from './swc/generated-native' const debug = debugOriginal('next:build:build-traces') @@ -107,7 +108,7 @@ export async function collectBuildTraces({ }) { const startTime = Date.now() debug('starting build traces') - let turboTasksForTrace: unknown + let turboTasksForTrace: ExternalObject let bindings = await loadBindings() const runTurbotrace = async function () { diff --git a/packages/next/src/build/swc/generated-native.d.ts b/packages/next/src/build/swc/generated-native.d.ts new file mode 100644 index 0000000000000..0f7888462ef24 --- /dev/null +++ b/packages/next/src/build/swc/generated-native.d.ts @@ -0,0 +1,382 @@ +// Manual additions to make the generated types below work. + +import type { TurbopackResult } from './types' + +export type TurboTasks = { readonly __tag: unique symbol } +export type ExternalEndpoint = { readonly __tag: unique symbol } +export type RefCell = { readonly __tag: unique symbol } +export type NapiRouteHas = { + type: string + key?: string + value?: string + readonly __tag: unique symbol +} + +export function lightningCssTransform(args: object): Promise +export function lightningCssTransformStyleAttribute( + args: object +): Promise + +// GENERATED-TYPES-BELOW +// DO NOT MANUALLY EDIT THESE TYPES +// You can regenerate this file by running `pnpm swc-build-native` in the root of the repo. + +/* tslint:disable */ +/* eslint-disable */ + +/* auto-generated by NAPI-RS */ + +export class ExternalObject { + readonly '': { + readonly '': unique symbol + [K: symbol]: T + } +} +export interface TransformOutput { + code: string + map?: string + output?: string +} +export function mdxCompile( + value: string, + option: Buffer, + signal?: AbortSignal | undefined | null +): Promise +export function mdxCompileSync(value: string, option: Buffer): string +export function minify( + input: Buffer, + opts: Buffer, + signal?: AbortSignal | undefined | null +): Promise +export function minifySync(input: Buffer, opts: Buffer): TransformOutput +export interface NapiEndpointConfig {} +export interface NapiServerPath { + path: string + contentHash: string +} +export interface NapiWrittenEndpoint { + type: string + entryPath?: string + clientPaths: Array + serverPaths: Array + config: NapiEndpointConfig +} +export function endpointWriteToDisk(endpoint: { + __napiType: 'Endpoint' +}): Promise +export function endpointServerChangedSubscribe( + endpoint: { __napiType: 'Endpoint' }, + issues: boolean, + func: (...args: any[]) => any +): { __napiType: 'RootTask' } +export function endpointClientChangedSubscribe( + endpoint: { __napiType: 'Endpoint' }, + func: (...args: any[]) => any +): { __napiType: 'RootTask' } +export interface NapiEnvVar { + name: string + value: string +} +export interface NapiDraftModeOptions { + previewModeId: string + previewModeEncryptionKey: string + previewModeSigningKey: string +} +export interface NapiProjectOptions { + /** + * A root path from which all files must be nested under. Trying to access + * a file outside this root will fail. Think of this as a chroot. + */ + rootPath: string + /** A path inside the root_path which contains the app/pages directories. */ + projectPath: string + /** + * next.config's distDir. Project initialization occurs eariler than + * deserializing next.config, so passing it as separate option. + */ + distDir?: string + /** Whether to watch he filesystem for file changes. */ + watch: boolean + /** The contents of next.config.js, serialized to JSON. */ + nextConfig: string + /** The contents of ts/config read by load-jsconfig, serialized to JSON. */ + jsConfig: string + /** A map of environment variables to use when compiling code. */ + env: Array + /** + * A map of environment variables which should get injected at compile + * time. + */ + defineEnv: NapiDefineEnv + /** The mode in which Next.js is running. */ + dev: boolean + /** The server actions encryption key. */ + encryptionKey: string + /** The build id. */ + buildId: string + /** Options for draft mode. */ + previewProps: NapiDraftModeOptions + /** The browserslist query to use for targeting browsers. */ + browserslistQuery: string +} +/** [NapiProjectOptions] with all fields optional. */ +export interface NapiPartialProjectOptions { + /** + * A root path from which all files must be nested under. Trying to access + * a file outside this root will fail. Think of this as a chroot. + */ + rootPath?: string + /** A path inside the root_path which contains the app/pages directories. */ + projectPath?: string + /** + * next.config's distDir. Project initialization occurs eariler than + * deserializing next.config, so passing it as separate option. + */ + distDir?: string | undefined | null + /** Whether to watch he filesystem for file changes. */ + watch?: boolean + /** The contents of next.config.js, serialized to JSON. */ + nextConfig?: string + /** The contents of ts/config read by load-jsconfig, serialized to JSON. */ + jsConfig?: string + /** A map of environment variables to use when compiling code. */ + env?: Array + /** + * A map of environment variables which should get injected at compile + * time. + */ + defineEnv?: NapiDefineEnv + /** The mode in which Next.js is running. */ + dev?: boolean + /** The server actions encryption key. */ + encryptionKey?: string + /** The build id. */ + buildId?: string + /** Options for draft mode. */ + previewProps?: NapiDraftModeOptions + /** The browserslist query to use for targeting browsers. */ + browserslistQuery?: string +} +export interface NapiDefineEnv { + client: Array + edge: Array + nodejs: Array +} +export interface NapiTurboEngineOptions { + /** An upper bound of memory that turbopack will attempt to stay under. */ + memoryLimit?: number +} +export function projectNew( + options: NapiProjectOptions, + turboEngineOptions: NapiTurboEngineOptions +): Promise<{ __napiType: 'Project' }> +export function projectUpdate( + project: { __napiType: 'Project' }, + options: NapiPartialProjectOptions +): Promise +export function projectShutdown(project: { + __napiType: 'Project' +}): Promise +export interface AppPageNapiRoute { + /** The relative path from project_path to the route file */ + originalName?: string + htmlEndpoint?: ExternalObject + rscEndpoint?: ExternalObject +} +export interface NapiRoute { + /** The router path */ + pathname: string + /** The relative path from project_path to the route file */ + originalName?: string + /** The type of route, eg a Page or App */ + type: string + pages?: Array + endpoint?: ExternalObject + htmlEndpoint?: ExternalObject + rscEndpoint?: ExternalObject + dataEndpoint?: ExternalObject +} +export interface NapiMiddleware { + endpoint: ExternalObject +} +export interface NapiInstrumentation { + nodeJs: ExternalObject + edge: ExternalObject +} +export interface NapiEntrypoints { + routes: Array + middleware?: NapiMiddleware + instrumentation?: NapiInstrumentation + pagesDocumentEndpoint: ExternalObject + pagesAppEndpoint: ExternalObject + pagesErrorEndpoint: ExternalObject +} +export function projectEntrypointsSubscribe( + project: { __napiType: 'Project' }, + func: (...args: any[]) => any +): { __napiType: 'RootTask' } +export function projectHmrEvents( + project: { __napiType: 'Project' }, + identifier: string, + func: (...args: any[]) => any +): { __napiType: 'RootTask' } +export interface HmrIdentifiers { + identifiers: Array +} +export function projectHmrIdentifiersSubscribe( + project: { __napiType: 'Project' }, + func: (...args: any[]) => any +): { __napiType: 'RootTask' } +export interface NapiUpdateMessage { + updateType: string + value?: NapiUpdateInfo +} +export interface NapiUpdateInfo { + duration: number + tasks: number +} +/** + * Subscribes to lifecycle events of the compilation. + * Emits an [UpdateMessage::Start] event when any computation starts. + * Emits an [UpdateMessage::End] event when there was no computation for the + * specified time (`aggregation_ms`). The [UpdateMessage::End] event contains + * information about the computations that happened since the + * [UpdateMessage::Start] event. It contains the duration of the computation + * (excluding the idle time that was spend waiting for `aggregation_ms`), and + * the number of tasks that were executed. + * + * The signature of the `func` is `(update_message: UpdateMessage) => void`. + */ +export function projectUpdateInfoSubscribe( + project: { __napiType: 'Project' }, + aggregationMs: number, + func: (...args: any[]) => any +): void +export interface StackFrame { + isServer: boolean + isInternal?: boolean + file: string + line?: number + column?: number + methodName?: string +} +export function projectTraceSource( + project: { __napiType: 'Project' }, + frame: StackFrame +): Promise +export function projectGetSourceForAsset( + project: { __napiType: 'Project' }, + filePath: string +): Promise +/** Runs exit handlers for the project registered using the [`ExitHandler`] API. */ +export function projectOnExit(project: { __napiType: 'Project' }): Promise +export function rootTaskDispose(rootTask: { __napiType: 'RootTask' }): void +export interface NapiIssue { + severity: string + stage: string + filePath: string + title: any + description?: any + detail?: any + source?: NapiIssueSource + documentationLink: string + subIssues: Array +} +export interface NapiIssueSource { + source: NapiSource + range?: NapiIssueSourceRange +} +export interface NapiIssueSourceRange { + start: NapiSourcePos + end: NapiSourcePos +} +export interface NapiSource { + ident: string + content?: string +} +export interface NapiSourcePos { + line: number + column: number +} +export interface NapiDiagnostic { + category: string + name: string + payload: Record +} +export function parse( + src: string, + options: Buffer, + filename?: string | undefined | null, + signal?: AbortSignal | undefined | null +): Promise +export function transform( + src: string | Buffer | undefined, + isModule: boolean, + options: Buffer, + signal?: AbortSignal | undefined | null +): Promise +export function transformSync( + src: string | Buffer | undefined, + isModule: boolean, + options: Buffer +): object +export function startTurbopackTraceServer(path: string): void +export interface NextBuildContext { + /** The root directory of the workspace. */ + root?: string + /** The project's directory. */ + dir?: string + /** + * next.config.js's distDir. Current there's some early stage setup + * requires this Before construct a context to read next.config.js, + * which we passes separately here. + */ + distDir?: string + /** The build ID. */ + buildId?: string + /** The rewrites, as computed by Next.js. */ + rewrites?: NapiRewrites + defineEnv: NapiDefineEnv +} +/** Keep in sync with [`next_core::next_config::Rewrites`] */ +export interface NapiRewrites { + fallback: Array + afterFiles: Array + beforeFiles: Array +} +/** Keep in sync with [`next_core::next_config::Rewrite`] */ +export interface NapiRewrite { + source: string + destination: string + basePath?: boolean + locale?: boolean + has?: Array + missing?: Array +} +export function createTurboTasks( + memoryLimit?: number | undefined | null +): ExternalObject +export function runTurboTracing( + options: Buffer, + turboTasks?: ExternalObject | undefined | null +): Promise> +export function getTargetTriple(): string +export function initHeapProfiler(): ExternalObject +export function teardownHeapProfiler( + guardExternal: ExternalObject +): void +/** + * Initialize tracing subscriber to emit traces. This configures subscribers + * for Trace Event Format . + */ +export function initCustomTraceSubscriber( + traceOutFilePath?: string | undefined | null +): ExternalObject +/** + * Teardown currently running tracing subscriber to flush out remaining traces. + * This should be called when parent node.js process exits, otherwise generated + * trace may drop traces in the buffer. + */ +export function teardownTraceSubscriber( + guardExternal: ExternalObject +): void diff --git a/packages/next/src/build/swc/generated-wasm.d.ts b/packages/next/src/build/swc/generated-wasm.d.ts new file mode 100644 index 0000000000000..e1175f6c118e3 --- /dev/null +++ b/packages/next/src/build/swc/generated-wasm.d.ts @@ -0,0 +1,53 @@ +// DO NOT MANUALLY EDIT THESE TYPES +// You can regenerate this file by running `pnpm swc-build-wasm` in the root of the repo. + +/* tslint:disable */ +/* eslint-disable */ +/** + * @param {string} value + * @param {any} opts + * @returns {any} + */ +export function mdxCompileSync(value: string, opts: any): any +/** + * @param {string} value + * @param {any} opts + * @returns {Promise} + */ +export function mdxCompile(value: string, opts: any): Promise +/** + * @param {string} s + * @param {any} opts + * @returns {any} + */ +export function minifySync(s: string, opts: any): any +/** + * @param {string} s + * @param {any} opts + * @returns {Promise} + */ +export function minify(s: string, opts: any): Promise +/** + * @param {any} s + * @param {any} opts + * @returns {any} + */ +export function transformSync(s: any, opts: any): any +/** + * @param {any} s + * @param {any} opts + * @returns {Promise} + */ +export function transform(s: any, opts: any): Promise +/** + * @param {string} s + * @param {any} opts + * @returns {any} + */ +export function parseSync(s: string, opts: any): any +/** + * @param {string} s + * @param {any} opts + * @returns {Promise} + */ +export function parse(s: string, opts: any): Promise diff --git a/packages/next/src/build/swc/index.ts b/packages/next/src/build/swc/index.ts index fca71144564ae..06eb571c85afd 100644 --- a/packages/next/src/build/swc/index.ts +++ b/packages/next/src/build/swc/index.ts @@ -9,27 +9,52 @@ import { eventSwcLoadFailure } from '../../telemetry/events/swc-load-failure' import { patchIncorrectLockfile } from '../../lib/patch-incorrect-lockfile' import { downloadNativeNextSwc, downloadWasmSwc } from '../../lib/download-swc' import type { - TurboRuleConfigItem, NextConfigComplete, TurboLoaderItem, - TurboRuleConfigItemOrShortcut, + TurboRuleConfigItem, TurboRuleConfigItemOptions, + TurboRuleConfigItemOrShortcut, } from '../../server/config-shared' import { isDeepStrictEqual } from 'util' import { type DefineEnvPluginOptions, getDefineEnv, } from '../webpack/plugins/define-env-plugin' -import type { __ApiPreviewProps } from '../../server/api-utils' import { getReactCompilerLoader } from '../get-babel-loader-config' import { TurbopackInternalError } from '../../server/dev/turbopack-utils' +import type { + ExternalObject, + NapiPartialProjectOptions, + NapiProjectOptions, + TurboTasks, +} from './generated-native' +import type { + Binding, + DefineEnv, + Endpoint, + HmrIdentifiers, + Project, + ProjectOptions, + Route, + TurboEngineOptions, + TurbopackResult, + TurbopackStackFrame, + Update, + UpdateMessage, + WrittenEndpoint, +} from './types' + +type RawBindings = typeof import('./generated-native') +type RawWasmBindings = typeof import('./generated-wasm') & { + default?(): Promise +} const nextVersion = process.env.__NEXT_VERSION as string const ArchName = arch() const PlatformName = platform() -const infoLog = (...args: any[]) => { +function infoLog(...args: any[]) { if (process.env.NEXT_PRIVATE_BUILD_WORKER) { return } @@ -41,23 +66,19 @@ const infoLog = (...args: any[]) => { /** * Based on napi-rs's target triples, returns triples that have corresponding next-swc binaries. */ -export const getSupportedArchTriples: () => Record = () => { +export function getSupportedArchTriples(): Record { const { darwin, win32, linux, freebsd, android } = platformArchTriples return { darwin, win32: { arm64: win32.arm64, - ia32: win32.ia32.filter( - (triple: { abi: string }) => triple.abi === 'msvc' - ), - x64: win32.x64.filter((triple: { abi: string }) => triple.abi === 'msvc'), + ia32: win32.ia32.filter((triple) => triple.abi === 'msvc'), + x64: win32.x64.filter((triple) => triple.abi === 'msvc'), }, linux: { // linux[x64] includes `gnux32` abi, with x64 arch. - x64: linux.x64.filter( - (triple: { abi: string }) => triple.abi !== 'gnux32' - ), + x64: linux.x64.filter((triple) => triple.abi !== 'gnux32'), arm64: linux.arm64, // This target is being deprecated, however we keep it in `knownDefaultWasmFallbackTriples` for now arm: linux.arm, @@ -142,7 +163,7 @@ let lastNativeBindingsLoadErrorCode: | string | undefined = undefined let nativeBindings: Binding -let wasmBindings: any +let wasmBindings: Binding let downloadWasmPromise: any let pendingBindings: any let swcTraceFlushGuard: any @@ -151,42 +172,6 @@ let downloadNativeBindingsPromise: Promise | undefined = undefined export const lockfilePatchPromise: { cur?: Promise } = {} -export interface Binding { - isWasm: boolean - turbo: { - startTrace: any - nextBuild?: any - createTurboTasks?: any - createProject: ( - options: ProjectOptions, - turboEngineOptions?: TurboEngineOptions - ) => Promise - startTurbopackTraceServer: (path: string) => void - } - mdx: { - compile: any - compileSync: any - } - minify: any - minifySync: any - transform: any - transformSync: any - parse: any - - getTargetTriple(): string | undefined - - initCustomTraceSubscriber?: any - teardownTraceSubscriber?: any - initHeapProfiler?: any - teardownHeapProfiler?: any - css: { - lightning: { - transform(transformOptions: any): Promise - transformStyleAttr(attrOptions: any): Promise - } - } -} - export async function loadBindings( useWasmBinary: boolean = false ): Promise { @@ -302,15 +287,15 @@ async function tryLoadNativeWithFallback(attempts: Array) { await downloadNativeBindingsPromise try { - let bindings = loadNative(nativeBindingsDirectory) - return bindings + return loadNative(nativeBindingsDirectory) } catch (a: any) { - attempts.concat(a) + attempts.push(...[].concat(a)) } + return undefined } -async function tryLoadWasmWithFallback(attempts: any) { +async function tryLoadWasmWithFallback(attempts: any[]) { try { let bindings = await loadWasm('') // @ts-expect-error TODO: this event has a wrong type. @@ -319,8 +304,8 @@ async function tryLoadWasmWithFallback(attempts: any) { nativeBindingsErrorCode: lastNativeBindingsLoadErrorCode, }) return bindings - } catch (a) { - attempts = attempts.concat(a) + } catch (a: any) { + attempts.push(...[].concat(a)) } try { @@ -349,8 +334,8 @@ async function tryLoadWasmWithFallback(attempts: any) { Log.warn(attempt) } return bindings - } catch (a) { - attempts = attempts.concat(a) + } catch (a: any) { + attempts.push(...[].concat(a)) } } @@ -369,6 +354,7 @@ function loadBindingsSync() { } logLoadFailure(attempts) + throw new Error('Failed to load bindings', { cause: attempts }) } let loggingLoadFailure = false @@ -395,80 +381,9 @@ function logLoadFailure(attempts: any, triedWasm = false) { process.exit(1) }) } -export interface ProjectOptions { - /** - * A root path from which all files must be nested under. Trying to access - * a file outside this root will fail. Think of this as a chroot. - */ - rootPath: string - - /** - * A path inside the root_path which contains the app/pages directories. - */ - projectPath: string - - /** - * The next.config.js contents. - */ - nextConfig: NextConfigComplete - - /** - * Jsconfig, or tsconfig contents. - * - * Next.js implicitly requires to read it to support few options - * https://nextjs.org/docs/architecture/nextjs-compiler#legacy-decorators - * https://nextjs.org/docs/architecture/nextjs-compiler#importsource - */ - jsConfig: { - compilerOptions: object - } - - /** - * A map of environment variables to use when compiling code. - */ - env: Record - - defineEnv: DefineEnv - - /** - * Whether to watch the filesystem for file changes. - */ - watch: boolean - - /** - * The mode in which Next.js is running. - */ - dev: boolean - - /** - * The server actions encryption key. - */ - encryptionKey: string - - /** - * The build id. - */ - buildId: string - - /** - * Options for draft mode. - */ - previewProps: __ApiPreviewProps - - /** - * The browserslist query to use for targeting browsers. - */ - browserslistQuery: string -} type RustifiedEnv = { name: string; value: string }[] -export interface DefineEnv { - client: RustifiedEnv - edge: RustifiedEnv - nodejs: RustifiedEnv -} - export function createDefineEnv({ isTurbopack, clientRouterFilters, @@ -510,275 +425,6 @@ export function createDefineEnv({ return defineEnv } -export interface TurboEngineOptions { - /** - * An upper bound of memory that turbopack will attempt to stay under. - */ - memoryLimit?: number -} - -export type StyledString = - | { - type: 'text' - value: string - } - | { - type: 'code' - value: string - } - | { - type: 'strong' - value: string - } - | { - type: 'stack' - value: StyledString[] - } - | { - type: 'line' - value: StyledString[] - } - -export interface Issue { - severity: string - stage: string - filePath: string - title: StyledString - description?: StyledString - detail?: StyledString - source?: { - source: { - ident: string - content?: string - } - range?: { - start: { - // 0-indexed - line: number - // 0-indexed - column: number - } - end: { - // 0-indexed - line: number - // 0-indexed - column: number - } - } - } - documentationLink: string - subIssues: Issue[] -} - -export interface Diagnostics { - category: string - name: string - payload: unknown -} - -export type TurbopackResult = T & { - issues: Issue[] - diagnostics: Diagnostics[] -} - -export interface Middleware { - endpoint: Endpoint -} - -export interface Instrumentation { - nodeJs: Endpoint - edge: Endpoint -} - -export interface Entrypoints { - routes: Map - middleware?: Middleware - instrumentation?: Instrumentation - pagesDocumentEndpoint: Endpoint - pagesAppEndpoint: Endpoint - pagesErrorEndpoint: Endpoint -} - -interface BaseUpdate { - resource: { - headers: unknown - path: string - } - diagnostics: unknown[] - issues: Issue[] -} - -interface IssuesUpdate extends BaseUpdate { - type: 'issues' -} - -interface EcmascriptMergedUpdate { - type: 'EcmascriptMergedUpdate' - chunks: { [moduleName: string]: { type: 'partial' } } - entries: { [moduleName: string]: { code: string; map: string; url: string } } -} - -interface PartialUpdate extends BaseUpdate { - type: 'partial' - instruction: { - type: 'ChunkListUpdate' - merged: EcmascriptMergedUpdate[] | undefined - } -} - -export type Update = IssuesUpdate | PartialUpdate - -export interface HmrIdentifiers { - identifiers: string[] -} - -/** @see https://github.com/vercel/next.js/blob/415cd74b9a220b6f50da64da68c13043e9b02995/packages/next-swc/crates/napi/src/next_api/project.rs#L824-L833 */ -export interface TurbopackStackFrame { - isServer: boolean - isInternal?: boolean - file: string - /** 1-indexed, unlike source map tokens */ - line?: number - /** 1-indexed, unlike source map tokens */ - column?: number | null - methodName?: string -} - -export type UpdateMessage = - | { - updateType: 'start' - } - | { - updateType: 'end' - value: UpdateInfo - } - -export interface UpdateInfo { - duration: number - tasks: number -} - -export interface Project { - update(options: Partial): Promise - - entrypointsSubscribe(): AsyncIterableIterator> - - hmrEvents(identifier: string): AsyncIterableIterator> - - hmrIdentifiersSubscribe(): AsyncIterableIterator< - TurbopackResult - > - - getSourceForAsset(filePath: string): Promise - - traceSource( - stackFrame: TurbopackStackFrame - ): Promise - - updateInfoSubscribe( - aggregationMs: number - ): AsyncIterableIterator> - - shutdown(): Promise - - onExit(): Promise -} - -export type Route = - | { - type: 'conflict' - } - | { - type: 'app-page' - pages: { - originalName: string - htmlEndpoint: Endpoint - rscEndpoint: Endpoint - }[] - } - | { - type: 'app-route' - originalName: string - endpoint: Endpoint - } - | { - type: 'page' - htmlEndpoint: Endpoint - dataEndpoint: Endpoint - } - | { - type: 'page-api' - endpoint: Endpoint - } - -export interface Endpoint { - /** Write files for the endpoint to disk. */ - writeToDisk(): Promise> - - /** - * Listen to client-side changes to the endpoint. - * After clientChanged() has been awaited it will listen to changes. - * The async iterator will yield for each change. - */ - clientChanged(): Promise> - - /** - * Listen to server-side changes to the endpoint. - * After serverChanged() has been awaited it will listen to changes. - * The async iterator will yield for each change. - */ - serverChanged( - includeIssues: boolean - ): Promise> -} - -interface EndpointConfig { - dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static' - dynamicParams?: boolean - revalidate?: 'never' | 'force-cache' | number - fetchCache?: - | 'auto' - | 'default-cache' - | 'only-cache' - | 'force-cache' - | 'default-no-store' - | 'only-no-store' - | 'force-no-store' - runtime?: 'nodejs' | 'edge' - preferredRegion?: string -} - -export type ServerPath = { - path: string - contentHash: string -} - -export type WrittenEndpoint = - | { - type: 'nodejs' - /** The entry path for the endpoint. */ - entryPath: string - /** All client paths that have been written for the endpoint. */ - clientPaths: string[] - /** All server paths that have been written for the endpoint. */ - serverPaths: ServerPath[] - config: EndpointConfig - } - | { - type: 'edge' - /** All client paths that have been written for the endpoint. */ - clientPaths: string[] - /** All server paths that have been written for the endpoint. */ - serverPaths: ServerPath[] - config: EndpointConfig - } - | { - type: 'none' - clientPaths: [] - serverPaths: [] - config: EndpointConfig - } - function rustifyEnv(env: Record): RustifiedEnv { return Object.entries(env) .filter(([_, value]) => value != null) @@ -790,7 +436,7 @@ function rustifyEnv(env: Record): RustifiedEnv { // TODO(sokra) Support wasm option. function bindingToApi( - binding: any, + binding: RawBindings, _wasm: boolean ): Binding['turbo']['createProject'] { type NativeFunction = ( @@ -825,7 +471,9 @@ function bindingToApi( */ function subscribe( useBuffer: boolean, - nativeFunction: NativeFunction + nativeFunction: + | NativeFunction + | ((callback: (err: Error, value: T) => void) => Promise) ): AsyncIterableIterator { type BufferItem = | { err: Error; value: undefined } @@ -846,7 +494,7 @@ function bindingToApi( // The native function will call this every time it emits a new result. We // either need to notify a waiting consumer, or buffer the new result until // the consumer catches up. - const emitResult = (err: Error | undefined, value: T | undefined) => { + function emitResult(err: Error | undefined, value: T | undefined) { if (waiting) { let { resolve, reject } = waiting waiting = undefined @@ -859,8 +507,10 @@ function bindingToApi( } } - const iterator = (async function* () { - const task = await withErrorCause(() => nativeFunction(emitResult)) + async function* createIterator() { + const task = await withErrorCause<{ __napiType: 'RootTask' } | void>(() => + nativeFunction(emitResult) + ) try { while (!canceled) { if (buffer.length > 0) { @@ -881,9 +531,13 @@ function bindingToApi( } throw e } finally { - binding.rootTaskDispose(task) + if (task) { + binding.rootTaskDispose(task) + } } - })() + } + + const iterator = createIterator() iterator.return = async () => { canceled = true if (waiting) waiting.reject(cancel) @@ -893,8 +547,22 @@ function bindingToApi( } async function rustifyProjectOptions( + options: ProjectOptions + ): Promise { + return { + ...options, + nextConfig: await serializeNextConfig( + options.nextConfig, + options.projectPath! + ), + jsConfig: JSON.stringify(options.jsConfig), + env: rustifyEnv(options.env), + } + } + + async function rustifyPartialProjectOptions( options: Partial - ): Promise { + ): Promise { return { ...options, nextConfig: @@ -902,12 +570,11 @@ function bindingToApi( (await serializeNextConfig(options.nextConfig, options.projectPath!)), jsConfig: options.jsConfig && JSON.stringify(options.jsConfig), env: options.env && rustifyEnv(options.env), - defineEnv: options.defineEnv, } } class ProjectImpl implements Project { - private _nativeProject: { __napiType: 'Project' } + private readonly _nativeProject: { __napiType: 'Project' } constructor(nativeProject: { __napiType: 'Project' }) { this._nativeProject = nativeProject @@ -917,7 +584,7 @@ function bindingToApi( await withErrorCause(async () => binding.projectUpdate( this._nativeProject, - await rustifyProjectOptions(options) + await rustifyPartialProjectOptions(options) ) ) } @@ -1091,16 +758,13 @@ function bindingToApi( } updateInfoSubscribe(aggregationMs: number) { - const subscription = subscribe>( - true, - async (callback) => - binding.projectUpdateInfoSubscribe( - this._nativeProject, - aggregationMs, - callback - ) + return subscribe>(true, async (callback) => + binding.projectUpdateInfoSubscribe( + this._nativeProject, + aggregationMs, + callback + ) ) - return subscription } shutdown(): Promise { @@ -1113,15 +777,18 @@ function bindingToApi( } class EndpointImpl implements Endpoint { - private _nativeEndpoint: { __napiType: 'Endpoint' } + private readonly _nativeEndpoint: { __napiType: 'Endpoint' } constructor(nativeEndpoint: { __napiType: 'Endpoint' }) { this._nativeEndpoint = nativeEndpoint } async writeToDisk(): Promise> { - return await withErrorCause(() => - binding.endpointWriteToDisk(this._nativeEndpoint) + return await withErrorCause( + () => + binding.endpointWriteToDisk(this._nativeEndpoint) as Promise< + TurbopackResult + > ) } @@ -1304,10 +971,10 @@ function bindingToApi( } } - const createProject: Binding['turbo']['createProject'] = async ( - options, + return async function createProject( + options: ProjectOptions, turboEngineOptions - ) => { + ) { return new ProjectImpl( await binding.projectNew( await rustifyProjectOptions(options), @@ -1315,8 +982,6 @@ function bindingToApi( ) ) } - - return createProject } async function loadWasm(importPath = '') { @@ -1333,15 +998,31 @@ async function loadWasm(importPath = '') { // the import path must be exact when not in node_modules pkgPath = path.join(importPath, pkg, 'wasm.js') } - let bindings = await import(pathToFileURL(pkgPath).toString()) + let bindings: RawWasmBindings = await import( + pathToFileURL(pkgPath).toString() + ) if (pkg === '@next/swc-wasm-web') { - bindings = await bindings.default() + bindings = await bindings.default!() } infoLog('next-swc build: wasm build @next/swc-wasm-web') // Note wasm binary does not support async intefaces yet, all async // interface coereces to sync interfaces. wasmBindings = { + css: { + lightning: { + transform: function (_options: any) { + throw new Error( + '`css.lightning.transform` is not supported by the wasm bindings.' + ) + }, + transformStyleAttr: function (_options: any) { + throw new Error( + '`css.lightning.transformStyleAttr` is not supported by the wasm bindings.' + ) + }, + }, + }, isWasm: true, transform(src: string, options: any) { // TODO: we can remove fallback to sync interface once new stable version of next-swc gets published (current v12.2) @@ -1369,15 +1050,37 @@ async function loadWasm(importPath = '') { return undefined }, turbo: { - startTrace: () => { + startTrace() { Log.error('Wasm binding does not support trace yet') }, + createTurboTasks: function ( + _memoryLimit?: number | undefined + ): ExternalObject { + throw new Error( + '`turbo.createTurboTasks` is not supported by the wasm bindings.' + ) + }, + createProject: function ( + _options: ProjectOptions, + _turboEngineOptions?: TurboEngineOptions | undefined + ): Promise { + throw new Error( + '`turbo.createProject` is not supported by the wasm bindings.' + ) + }, + startTurbopackTraceServer: function (_traceFilePath: string): void { + throw new Error( + '`turbo.startTurbopackTraceServer` is not supported by the wasm bindings.' + ) + }, }, mdx: { - compile: (src: string, options: any) => - bindings.mdxCompile(src, getMdxOptions(options)), - compileSync: (src: string, options: any) => - bindings.mdxCompileSync(src, getMdxOptions(options)), + compile(src: string, options: any) { + return bindings.mdxCompile(src, getMdxOptions(options)) + }, + compileSync(src: string, options: any) { + return bindings.mdxCompileSync(src, getMdxOptions(options)) + }, }, } return wasmBindings @@ -1403,10 +1106,10 @@ function loadNative(importPath?: string) { return nativeBindings } - const customBindings = !!__INTERNAL_CUSTOM_TURBOPACK_BINDINGS + const customBindings: RawBindings = !!__INTERNAL_CUSTOM_TURBOPACK_BINDINGS ? require(__INTERNAL_CUSTOM_TURBOPACK_BINDINGS) : null - let bindings: any = customBindings + let bindings: RawBindings = customBindings let attempts: any[] = [] const NEXT_TEST_NATIVE_DIR = process.env.NEXT_TEST_NATIVE_DIR @@ -1524,17 +1227,18 @@ function loadNative(importPath?: string) { initHeapProfiler: bindings.initHeapProfiler, teardownHeapProfiler: bindings.teardownHeapProfiler, turbo: { - startTrace: (options = {}, turboTasks: unknown) => { + startTrace(options = {}, turboTasks: ExternalObject) { initHeapProfiler() return (customBindings ?? bindings).runTurboTracing( toBuffer({ exact: true, ...options }), turboTasks ) }, - createTurboTasks: (memoryLimit?: number): unknown => - bindings.createTurboTasks(memoryLimit), + createTurboTasks(memoryLimit?: number): ExternalObject { + return bindings.createTurboTasks(memoryLimit) + }, createProject: bindingToApi(customBindings ?? bindings, false), - startTurbopackTraceServer: (traceFilePath) => { + startTurbopackTraceServer(traceFilePath) { Log.warn( 'Turbopack trace server started. View trace at https://turbo-trace-viewer.vercel.app/' ) @@ -1542,17 +1246,23 @@ function loadNative(importPath?: string) { }, }, mdx: { - compile: (src: string, options: any) => - bindings.mdxCompile(src, toBuffer(getMdxOptions(options))), - compileSync: (src: string, options: any) => - bindings.mdxCompileSync(src, toBuffer(getMdxOptions(options))), + compile(src: string, options: any) { + return bindings.mdxCompile(src, toBuffer(getMdxOptions(options))) + }, + compileSync(src: string, options: any) { + bindings.mdxCompileSync(src, toBuffer(getMdxOptions(options))) + }, }, css: { lightning: { - transform: (transformOptions: any) => - bindings.lightningCssTransform(transformOptions), - transformStyleAttr: (transformAttrOptions: any) => - bindings.lightningCssTransformStyleAttribute(transformAttrOptions), + transform(transformOptions: any) { + return bindings.lightningCssTransform(transformOptions) + }, + transformStyleAttr(transformAttrOptions: any) { + return bindings.lightningCssTransformStyleAttribute( + transformAttrOptions + ) + }, }, }, } @@ -1597,11 +1307,6 @@ export async function minify(src: string, options: any): Promise { return bindings.minify(src, options) } -export function minifySync(src: string, options: any): string { - let bindings = loadBindingsSync() - return bindings.minifySync(src, options) -} - export async function parse(src: string, options: any): Promise { let bindings = await loadBindings() let parserOptions = getParserOptions(options) @@ -1627,11 +1332,11 @@ export function getBinaryMetadata() { * Initialize trace subscriber to emit traces. * */ -export const initCustomTraceSubscriber = (traceFileName?: string): void => { +export function initCustomTraceSubscriber(traceFileName?: string) { if (!swcTraceFlushGuard) { // Wasm binary doesn't support trace emission let bindings = loadNative() - swcTraceFlushGuard = bindings.initCustomTraceSubscriber(traceFileName) + swcTraceFlushGuard = bindings.initCustomTraceSubscriber?.(traceFileName) } } @@ -1641,39 +1346,45 @@ export const initCustomTraceSubscriber = (traceFileName?: string): void => { * only available by manually building next-swc with specific flags. * Calling in release build will not do anything. */ -export const initHeapProfiler = () => { +export function initHeapProfiler() { try { if (!swcHeapProfilerFlushGuard) { let bindings = loadNative() - swcHeapProfilerFlushGuard = bindings.initHeapProfiler() + swcHeapProfilerFlushGuard = bindings.initHeapProfiler?.() } } catch (_) { // Suppress exceptions, this fn allows to fail to load native bindings } } +function once(fn: () => void): () => void { + let executed = false + + return function (): void { + if (!executed) { + executed = true + + fn() + } + } +} + /** * Teardown heap profiler, if possible. * * Same as initialization, this is not available in release build of next-swc by default * and calling it will not do anything. */ -export const teardownHeapProfiler = (() => { - let flushed = false - return (): void => { - if (!flushed) { - flushed = true - try { - let bindings = loadNative() - if (swcHeapProfilerFlushGuard) { - bindings.teardownHeapProfiler(swcHeapProfilerFlushGuard) - } - } catch (e) { - // Suppress exceptions, this fn allows to fail to load native bindings - } +export const teardownHeapProfiler = once(() => { + try { + let bindings = loadNative() + if (swcHeapProfilerFlushGuard) { + bindings.teardownHeapProfiler?.(swcHeapProfilerFlushGuard) } + } catch (e) { + // Suppress exceptions, this fn allows to fail to load native bindings } -})() +}) /** * Teardown swc's trace subscriber if there's an initialized flush guard exists. @@ -1684,19 +1395,13 @@ export const teardownHeapProfiler = (() => { * * instead parent process manually drops guard when process gets signal to exit. */ -export const teardownTraceSubscriber = (() => { - let flushed = false - return (): void => { - if (!flushed) { - flushed = true - try { - let bindings = loadNative() - if (swcTraceFlushGuard) { - bindings.teardownTraceSubscriber(swcTraceFlushGuard) - } - } catch (e) { - // Suppress exceptions, this fn allows to fail to load native bindings - } +export const teardownTraceSubscriber = once(() => { + try { + let bindings = loadNative() + if (swcTraceFlushGuard) { + bindings.teardownTraceSubscriber?.(swcTraceFlushGuard) } + } catch (e) { + // Suppress exceptions, this fn allows to fail to load native bindings } -})() +}) diff --git a/packages/next/src/build/swc/types.ts b/packages/next/src/build/swc/types.ts new file mode 100644 index 0000000000000..4dcc27159a9b4 --- /dev/null +++ b/packages/next/src/build/swc/types.ts @@ -0,0 +1,383 @@ +import type { NextConfigComplete } from '../../server/config-shared' +import type { __ApiPreviewProps } from '../../server/api-utils' +import type { ExternalObject, RefCell, TurboTasks } from './generated-native' + +export interface Binding { + isWasm: boolean + turbo: { + startTrace(options: any, turboTasks: ExternalObject): any + createTurboTasks(memoryLimit?: number): ExternalObject + createProject( + options: ProjectOptions, + turboEngineOptions?: TurboEngineOptions + ): Promise + startTurbopackTraceServer(traceFilePath: string): void + + nextBuild?: any + } + mdx: { + compile(src: string, options: any): any + compileSync(src: string, options: any): any + } + minify(src: string, options: any): Promise + minifySync(src: string, options: any): any + transform(src: string, options: any): Promise + transformSync(src: string, options: any): any + parse(src: string, options: any): Promise + + getTargetTriple(): string | undefined + + initCustomTraceSubscriber?(traceOutFilePath?: string): ExternalObject + teardownTraceSubscriber?(guardExternal: ExternalObject): void + initHeapProfiler?(): ExternalObject + teardownHeapProfiler?(guardExternal: ExternalObject): void + css: { + lightning: { + transform(transformOptions: any): Promise + transformStyleAttr(transformAttrOptions: any): Promise + } + } +} + +export type StyledString = + | { + type: 'text' + value: string + } + | { + type: 'code' + value: string + } + | { + type: 'strong' + value: string + } + | { + type: 'stack' + value: StyledString[] + } + | { + type: 'line' + value: StyledString[] + } + +export interface Issue { + severity: string + stage: string + filePath: string + title: StyledString + description?: StyledString + detail?: StyledString + source?: { + source: { + ident: string + content?: string + } + range?: { + start: { + // 0-indexed + line: number + // 0-indexed + column: number + } + end: { + // 0-indexed + line: number + // 0-indexed + column: number + } + } + } + documentationLink: string + subIssues: Issue[] +} + +export interface Diagnostics { + category: string + name: string + payload: unknown +} + +export type TurbopackResult = T & { + issues: Issue[] + diagnostics: Diagnostics[] +} + +export interface TurboEngineOptions { + /** + * An upper bound of memory that turbopack will attempt to stay under. + */ + memoryLimit?: number +} + +export interface Middleware { + endpoint: Endpoint +} + +export interface Instrumentation { + nodeJs: Endpoint + edge: Endpoint +} + +export interface Entrypoints { + routes: Map + middleware?: Middleware + instrumentation?: Instrumentation + pagesDocumentEndpoint: Endpoint + pagesAppEndpoint: Endpoint + pagesErrorEndpoint: Endpoint +} + +interface BaseUpdate { + resource: { + headers: unknown + path: string + } + diagnostics: unknown[] + issues: Issue[] +} + +interface IssuesUpdate extends BaseUpdate { + type: 'issues' +} + +interface EcmascriptMergedUpdate { + type: 'EcmascriptMergedUpdate' + chunks: { [moduleName: string]: { type: 'partial' } } + entries: { [moduleName: string]: { code: string; map: string; url: string } } +} + +interface PartialUpdate extends BaseUpdate { + type: 'partial' + instruction: { + type: 'ChunkListUpdate' + merged: EcmascriptMergedUpdate[] | undefined + } +} + +export type Update = IssuesUpdate | PartialUpdate + +export interface HmrIdentifiers { + identifiers: string[] +} + +/** @see https://github.com/vercel/next.js/blob/415cd74b9a220b6f50da64da68c13043e9b02995/packages/next-swc/crates/napi/src/next_api/project.rs#L824-L833 */ +export interface TurbopackStackFrame { + isServer: boolean + isInternal?: boolean + file: string + /** 1-indexed, unlike source map tokens */ + line?: number + /** 1-indexed, unlike source map tokens */ + column?: number + methodName?: string +} + +export type UpdateMessage = + | { + updateType: 'start' + } + | { + updateType: 'end' + value: UpdateInfo + } + +export interface UpdateInfo { + duration: number + tasks: number +} + +export interface Project { + update(options: Partial): Promise + + entrypointsSubscribe(): AsyncIterableIterator> + + hmrEvents(identifier: string): AsyncIterableIterator> + + hmrIdentifiersSubscribe(): AsyncIterableIterator< + TurbopackResult + > + + getSourceForAsset(filePath: string): Promise + + traceSource( + stackFrame: TurbopackStackFrame + ): Promise + + updateInfoSubscribe( + aggregationMs: number + ): AsyncIterableIterator> + + shutdown(): Promise + + onExit(): Promise +} + +export type Route = + | { + type: 'conflict' + } + | { + type: 'app-page' + pages: { + originalName: string + htmlEndpoint: Endpoint + rscEndpoint: Endpoint + }[] + } + | { + type: 'app-route' + originalName: string + endpoint: Endpoint + } + | { + type: 'page' + htmlEndpoint: Endpoint + dataEndpoint: Endpoint + } + | { + type: 'page-api' + endpoint: Endpoint + } + +export interface Endpoint { + /** Write files for the endpoint to disk. */ + writeToDisk(): Promise> + + /** + * Listen to client-side changes to the endpoint. + * After clientChanged() has been awaited it will listen to changes. + * The async iterator will yield for each change. + */ + clientChanged(): Promise> + + /** + * Listen to server-side changes to the endpoint. + * After serverChanged() has been awaited it will listen to changes. + * The async iterator will yield for each change. + */ + serverChanged( + includeIssues: boolean + ): Promise> +} + +interface EndpointConfig { + dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static' + dynamicParams?: boolean + revalidate?: 'never' | 'force-cache' | number + fetchCache?: + | 'auto' + | 'default-cache' + | 'only-cache' + | 'force-cache' + | 'default-no-store' + | 'only-no-store' + | 'force-no-store' + runtime?: 'nodejs' | 'edge' + preferredRegion?: string +} + +export type ServerPath = { + path: string + contentHash: string +} + +export type WrittenEndpoint = + | { + type: 'nodejs' + /** The entry path for the endpoint. */ + entryPath: string + /** All client paths that have been written for the endpoint. */ + clientPaths: string[] + /** All server paths that have been written for the endpoint. */ + serverPaths: ServerPath[] + config: EndpointConfig + } + | { + type: 'edge' + /** All client paths that have been written for the endpoint. */ + clientPaths: string[] + /** All server paths that have been written for the endpoint. */ + serverPaths: ServerPath[] + config: EndpointConfig + } + | { + type: 'none' + clientPaths: [] + serverPaths: [] + config: EndpointConfig + } + +export interface ProjectOptions { + /** + * A root path from which all files must be nested under. Trying to access + * a file outside this root will fail. Think of this as a chroot. + */ + rootPath: string + + /** + * A path inside the root_path which contains the app/pages directories. + */ + projectPath: string + + /** + * The next.config.js contents. + */ + nextConfig: NextConfigComplete + + /** + * Jsconfig, or tsconfig contents. + * + * Next.js implicitly requires to read it to support few options + * https://nextjs.org/docs/architecture/nextjs-compiler#legacy-decorators + * https://nextjs.org/docs/architecture/nextjs-compiler#importsource + */ + jsConfig: { + compilerOptions: object + } + + /** + * A map of environment variables to use when compiling code. + */ + env: Record + + defineEnv: DefineEnv + + /** + * Whether to watch the filesystem for file changes. + */ + watch: boolean + + /** + * The mode in which Next.js is running. + */ + dev: boolean + + /** + * The server actions encryption key. + */ + encryptionKey: string + + /** + * The build id. + */ + buildId: string + + /** + * Options for draft mode. + */ + previewProps: __ApiPreviewProps + + /** + * The browserslist query to use for targeting browsers. + */ + browserslistQuery: string +} + +export interface DefineEnv { + client: RustifiedEnv + edge: RustifiedEnv + nodejs: RustifiedEnv +} + +export type RustifiedEnv = { name: string; value: string }[] diff --git a/packages/next/src/client/components/react-dev-overlay/server/middleware-turbopack.ts b/packages/next/src/client/components/react-dev-overlay/server/middleware-turbopack.ts index 5768b17925cab..02f6036470e43 100644 --- a/packages/next/src/client/components/react-dev-overlay/server/middleware-turbopack.ts +++ b/packages/next/src/client/components/react-dev-overlay/server/middleware-turbopack.ts @@ -12,7 +12,7 @@ import { import fs, { constants as FS } from 'fs/promises' import { launchEditor } from '../internal/helpers/launchEditor' import type { StackFrame } from 'next/dist/compiled/stacktrace-parser' -import type { Project, TurbopackStackFrame } from '../../../../build/swc' +import type { Project, TurbopackStackFrame } from '../../../../build/swc/types' const currentSourcesByFile: Map> = new Map() export async function batchedTraceSource( diff --git a/packages/next/src/server/dev/extract-modules-from-turbopack-message.ts b/packages/next/src/server/dev/extract-modules-from-turbopack-message.ts index c83de42e2aa3c..d1afedca754c8 100644 --- a/packages/next/src/server/dev/extract-modules-from-turbopack-message.ts +++ b/packages/next/src/server/dev/extract-modules-from-turbopack-message.ts @@ -1,4 +1,4 @@ -import type { Update as TurbopackUpdate } from '../../build/swc' +import type { Update as TurbopackUpdate } from '../../build/swc/types' export function extractModulesFromTurbopackMessage( data: TurbopackUpdate | TurbopackUpdate[] diff --git a/packages/next/src/server/dev/hot-reloader-turbopack.ts b/packages/next/src/server/dev/hot-reloader-turbopack.ts index ffc2828f29f78..b27f201c93d9e 100644 --- a/packages/next/src/server/dev/hot-reloader-turbopack.ts +++ b/packages/next/src/server/dev/hot-reloader-turbopack.ts @@ -15,13 +15,13 @@ import type { TurbopackConnectedAction, } from './hot-reloader-types' import { HMR_ACTIONS_SENT_TO_BROWSER } from './hot-reloader-types' -import type { Update as TurbopackUpdate } from '../../build/swc' -import { - createDefineEnv, - type Endpoint, - type TurbopackResult, - type WrittenEndpoint, -} from '../../build/swc' +import type { + Update as TurbopackUpdate, + Endpoint, + WrittenEndpoint, + TurbopackResult, +} from '../../build/swc/types' +import { createDefineEnv } from '../../build/swc' import * as Log from '../../build/output/log' import { getVersionInfo, diff --git a/packages/next/src/server/dev/hot-reloader-types.ts b/packages/next/src/server/dev/hot-reloader-types.ts index 383a84c336228..bc8b4250d20b7 100644 --- a/packages/next/src/server/dev/hot-reloader-types.ts +++ b/packages/next/src/server/dev/hot-reloader-types.ts @@ -4,7 +4,7 @@ import type { Duplex } from 'stream' import type { webpack } from 'next/dist/compiled/webpack/webpack' import type getBaseWebpackConfig from '../../build/webpack-config' import type { RouteDefinition } from '../route-definitions/route-definition' -import type { Project, Update as TurbopackUpdate } from '../../build/swc' +import type { Project, Update as TurbopackUpdate } from '../../build/swc/types' import type { VersionInfo } from './parse-version-info' import type { DebugInfo } from '../../client/components/react-dev-overlay/types' diff --git a/packages/next/src/server/dev/turbopack-utils.ts b/packages/next/src/server/dev/turbopack-utils.ts index d0a2ce9fa169b..b5491988f8405 100644 --- a/packages/next/src/server/dev/turbopack-utils.ts +++ b/packages/next/src/server/dev/turbopack-utils.ts @@ -5,14 +5,14 @@ import type { SetupOpts, } from '../lib/router-utils/setup-dev-bundler' import type { - Endpoint, - Entrypoints as RawEntrypoints, Issue, StyledString, TurbopackResult, + Endpoint, + Entrypoints as RawEntrypoints, Update as TurbopackUpdate, WrittenEndpoint, -} from '../../build/swc' +} from '../../build/swc/types' import { decodeMagicIdentifier, MAGIC_IDENTIFIER_REGEX, diff --git a/packages/next/src/server/dev/turbopack/types.ts b/packages/next/src/server/dev/turbopack/types.ts index a236516c89025..31b26fa383157 100644 --- a/packages/next/src/server/dev/turbopack/types.ts +++ b/packages/next/src/server/dev/turbopack/types.ts @@ -1,4 +1,8 @@ -import type { Endpoint, Instrumentation, Middleware } from '../../../build/swc' +import type { + Endpoint, + Instrumentation, + Middleware, +} from '../../../build/swc/types' export interface GlobalEntrypoints { app: Endpoint | undefined diff --git a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts index 6112f96e2fbd7..0ecbb70493f0b 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @@ -9,7 +9,8 @@ import type { MiddlewareRouteMatch } from '../../../shared/lib/router/utils/midd import type { PropagateToWorkersField } from './types' import type { NextJsHotReloaderInterface } from '../../dev/hot-reloader-types' -import { createDefineEnv, type Project } from '../../../build/swc' +import { createDefineEnv } from '../../../build/swc' +import type { Project } from '../../../build/swc/types' import fs from 'fs' import { mkdir } from 'fs/promises' import url from 'url' @@ -953,7 +954,7 @@ async function startWatcher(opts: SetupOpts) { file: frameFile, methodName: frame.methodName, line: frame.lineNumber ?? 0, - column: frame.column, + column: frame.column ?? undefined, isServer: true, } ) @@ -1124,7 +1125,7 @@ async function traceTurbopackErrorStack( file: f.file!, methodName: f.methodName, line: f.lineNumber ?? 0, - column: f.column, + column: f.column ?? undefined, isServer: true, }) diff --git a/packages/next/types/$$compiled.internal.d.ts b/packages/next/types/$$compiled.internal.d.ts index 41baafea72cb1..76832e459fb4e 100644 --- a/packages/next/types/$$compiled.internal.d.ts +++ b/packages/next/types/$$compiled.internal.d.ts @@ -1,6 +1,5 @@ /* eslint-disable import/no-extraneous-dependencies */ declare module 'next/package.json' -declare module 'next/dist/compiled/@napi-rs/triples' declare module 'next/dist/compiled/postcss-value-parser' declare module 'next/dist/compiled/icss-utils' declare module 'next/dist/compiled/postcss-modules-values' @@ -38,6 +37,10 @@ declare module 'VAR_USERLAND' declare module 'VAR_MODULE_DOCUMENT' declare module 'VAR_MODULE_APP' +declare module 'next/dist/compiled/@napi-rs/triples' { + export * from '@napi-rs/triples' +} + declare module 'next/dist/compiled/@next/react-refresh-utils/dist/ReactRefreshWebpackPlugin' { import m from '@next/react-refresh-utils/ReactRefreshWebpackPlugin' export = m diff --git a/scripts/build-native.cjs b/scripts/build-native.cjs index ffece0adc1367..1de8cd81660fa 100755 --- a/scripts/build-native.cjs +++ b/scripts/build-native.cjs @@ -4,9 +4,10 @@ const { NEXT_DIR, booleanArg, execAsyncWithOutput, + execFn, namedValueArg, } = require('./pack-util.cjs') -const { rmSync } = require('fs') +const fs = require('fs') const path = require('path') const args = process.argv.slice(2) @@ -16,14 +17,16 @@ booleanArg(args, '--no-build') namedValueArg(args, '--project') const targetDir = path.join(NEXT_DIR, 'target') +const nextSwcDir = path.join(NEXT_DIR, 'packages/next-swc') module.exports = (async () => { for (let i = 0; i < 2; i++) { try { await execAsyncWithOutput( - 'Build native modules', - ['pnpm', 'run', 'swc-build-native', ...args], + 'Build native bindings', + ['pnpm', 'run', 'build-native', ...args], { + cwd: nextSwcDir, shell: process.platform === 'win32' ? 'powershell.exe' : false, env: { CARGO_TERM_COLOR: 'always', @@ -38,11 +41,11 @@ module.exports = (async () => { .toString() .includes('the compiler unexpectedly panicked. this is a bug.') ) { - rmSync(path.join(targetDir, 'release/incremental'), { + fs.rmSync(path.join(targetDir, 'release/incremental'), { recursive: true, force: true, }) - rmSync(path.join(targetDir, 'debug/incremental'), { + fs.rmSync(path.join(targetDir, 'debug/incremental'), { recursive: true, force: true, }) @@ -54,4 +57,32 @@ module.exports = (async () => { } break } + + execFn( + 'Copy generated types to `next/src/build/swc/generated-native.d.ts`', + () => writeTypes() + ) })() + +function writeTypes() { + const generatedTypesPath = path.join( + NEXT_DIR, + 'packages/next-swc/native/index.d.ts' + ) + const vendoredTypesPath = path.join( + NEXT_DIR, + 'packages/next/src/build/swc/generated-native.d.ts' + ) + const generatedTypesMarker = '// GENERATED-TYPES-BELOW\n' + const generatedNotice = + '// DO NOT MANUALLY EDIT THESE TYPES\n// You can regenerate this file by running `pnpm swc-build-native` in the root of the repo.\n\n' + + const generatedTypes = fs.readFileSync(generatedTypesPath, 'utf8') + let vendoredTypes = fs.readFileSync(vendoredTypesPath, 'utf8') + + vendoredTypes = vendoredTypes.split(generatedTypesMarker)[0] + vendoredTypes = + vendoredTypes + generatedTypesMarker + generatedNotice + generatedTypes + + fs.writeFileSync(vendoredTypesPath, vendoredTypes) +} diff --git a/scripts/build-wasm.cjs b/scripts/build-wasm.cjs new file mode 100755 index 0000000000000..d46793d2699ae --- /dev/null +++ b/scripts/build-wasm.cjs @@ -0,0 +1,82 @@ +#!/usr/bin/env node + +const { + NEXT_DIR, + booleanArg, + execAsyncWithOutput, + execFn, + namedValueArg, +} = require('./pack-util.cjs') +const fs = require('fs') +const path = require('path') + +const args = process.argv.slice(2) + +// strip --no-build and --project when called from pack-next.cjs +booleanArg(args, '--no-build') +namedValueArg(args, '--project') + +const targetDir = path.join(NEXT_DIR, 'target') +const nextSwcDir = path.join(NEXT_DIR, 'packages/next-swc') + +module.exports = (async () => { + for (let i = 0; i < 2; i++) { + try { + await execAsyncWithOutput( + 'Build wasm bindings', + ['pnpm', 'run', 'build-wasm', ...args], + { + cwd: nextSwcDir, + shell: process.platform === 'win32' ? 'powershell.exe' : false, + env: { + CARGO_TERM_COLOR: 'always', + TTY: '1', + ...process.env, + }, + } + ) + } catch (e) { + if ( + e.stderr + .toString() + .includes('the compiler unexpectedly panicked. this is a bug.') + ) { + fs.rmSync(path.join(targetDir, 'release/incremental'), { + recursive: true, + force: true, + }) + fs.rmSync(path.join(targetDir, 'debug/incremental'), { + recursive: true, + force: true, + }) + continue + } + delete e.stdout + delete e.stderr + throw e + } + break + } + + execFn( + 'Copy generated types to `next/src/build/swc/generated-wasm.d.ts`', + () => writeTypes() + ) +})() + +function writeTypes() { + const generatedTypesPath = path.join(NEXT_DIR, 'crates/wasm/pkg/wasm.d.ts') + const vendoredTypesPath = path.join( + NEXT_DIR, + 'packages/next/src/build/swc/generated-wasm.d.ts' + ) + + const generatedNotice = + '// DO NOT MANUALLY EDIT THESE TYPES\n// You can regenerate this file by running `pnpm swc-build-wasm` in the root of the repo.\n\n' + + const generatedTypes = fs.readFileSync(generatedTypesPath, 'utf8') + + const vendoredTypes = generatedNotice + generatedTypes + + fs.writeFileSync(vendoredTypesPath, vendoredTypes) +} diff --git a/scripts/pack-util.cjs b/scripts/pack-util.cjs index 5158315ab4031..4a39815244a68 100644 --- a/scripts/pack-util.cjs +++ b/scripts/pack-util.cjs @@ -81,6 +81,19 @@ function execAsyncWithOutput(title, command, opts) { exports.execAsyncWithOutput = execAsyncWithOutput +/** + * @template T + * @param {string} title + * @param {() => T} fn + * @returns {T} + */ +function execFn(title, fn) { + logCommand(title, fn.toString()) + return fn() +} + +exports.execFn = execFn + /** * @param {string | string[]} command */ diff --git a/scripts/patch-next.cjs b/scripts/patch-next.cjs index a009710ffcff6..b1cce71e1b1e7 100644 --- a/scripts/patch-next.cjs +++ b/scripts/patch-next.cjs @@ -3,7 +3,7 @@ const { NEXT_DIR, exec, - logCommand, + execFn, booleanArg, packageFiles, } = require('./pack-util.cjs') @@ -21,11 +21,6 @@ const noNativeBuild = booleanArg(args, '--no-native-build') const PROJECT_DIR = path.resolve(args[0]) -async function execFn(title, fn) { - logCommand(title, fn.toString()) - return fn() -} - function realPathIfAny(path) { try { return fs.realpathSync(path) diff --git a/test/development/basic/next-rs-api.test.ts b/test/development/basic/next-rs-api.test.ts index 6d3f8b4d13a0d..961f6f6a3d79b 100644 --- a/test/development/basic/next-rs-api.test.ts +++ b/test/development/basic/next-rs-api.test.ts @@ -1,17 +1,16 @@ import { NextInstance, createNext } from 'e2e-utils' import { trace } from 'next/dist/trace' import { PHASE_DEVELOPMENT_SERVER } from 'next/constants' -import { - createDefineEnv, +import { createDefineEnv, loadBindings } from 'next/dist/build/swc' +import type { Diagnostics, Entrypoints, Issue, - loadBindings, Project, StyledString, TurbopackResult, UpdateInfo, -} from 'next/dist/build/swc' +} from 'next/dist/build/swc/types' import loadConfig from 'next/dist/server/config' import path from 'path'