Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
238 changes: 127 additions & 111 deletions Cargo.lock

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,11 @@ indexmap = "2.9.0"
indoc = "2.0.5"
infer = "0.19.0"
insta = "1.43.1"
itertools = "0.14.0"
itertools = "0.15.0"
itoa = "1.0.15"
json-escape-simd = "3"
json-strip-comments = "3"
jsonschema = { version = "0.46.5", default-features = false }
jsonschema = { version = "0.47.0", default-features = false }
junction = "1.4.1"
libtest-mimic = "0.8.2"
memchr = "2.7.4"
Expand Down Expand Up @@ -247,7 +247,7 @@ pnp = "0.12.9"
percent-encoding = "2.3.1"
petgraph = "0.8.2"
pretty_assertions = "1.4.1"
phf = "0.13.0"
phf = { version = "0.14.0", features = ["macros"] }
prettyplease = "0.2.32"
proc-macro2 = "1"
pty_terminal_test = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "4003f65a3e5e3d957ff81b157e85e6ee41cc59fc" }
Expand Down Expand Up @@ -276,7 +276,7 @@ simdutf8 = "0.1.5"
smallvec = { version = "1.15.1", features = ["union"] }
snapshot_test = { git = "https://github.com/voidzero-dev/vite-task.git", rev = "4003f65a3e5e3d957ff81b157e85e6ee41cc59fc" }
string_cache = "0.9.0"
sugar_path = { version = "2.0.1", features = ["cached_current_dir"] }
sugar_path = { version = "3", features = ["cached_current_dir"] }
supports-color = "3"
syn = { version = "2", default-features = false }
tar = "0.4.43"
Expand Down Expand Up @@ -317,7 +317,7 @@ xxhash-rust = "0.8.15"
zip = { version = "7.2", default-features = false, features = ["deflate-flate2-zlib-rs"] }

# oxc crates with the same version
oxc = { version = "0.139.0", features = [
oxc = { version = "0.140.0", features = [
"ast_visit",
"transformer",
"minifier",
Expand All @@ -329,22 +329,22 @@ oxc = { version = "0.139.0", features = [
"regular_expression",
"cfg",
] }
oxc_allocator = { version = "0.139.0", features = ["pool"] }
oxc_ast = "0.139.0"
oxc_ecmascript = "0.139.0"
oxc_parser = "0.139.0"
oxc_span = "0.139.0"
oxc_napi = "0.139.0"
oxc_str = "0.139.0"
oxc_minify_napi = "0.139.0"
oxc_parser_napi = "0.139.0"
oxc_transform_napi = "0.139.0"
oxc_traverse = "0.139.0"
oxc_allocator = { version = "0.140.0", features = ["pool"] }
oxc_ast = "0.140.0"
oxc_ecmascript = "0.140.0"
oxc_parser = "0.140.0"
oxc_span = "0.140.0"
oxc_napi = "0.140.0"
oxc_str = "0.140.0"
oxc_minify_napi = "0.140.0"
oxc_parser_napi = "0.140.0"
oxc_transform_napi = "0.140.0"
oxc_traverse = "0.140.0"

# oxc crates in their own repos
oxc_index = { version = "5", features = ["rayon", "serde"] }
oxc_resolver = { version = "11.23.0", features = ["yarn_pnp"] }
oxc_resolver_napi = { version = "11.23.0", default-features = false, features = ["yarn_pnp"] }
oxc_resolver = { version = "11.24.2", features = ["yarn_pnp"] }
oxc_resolver_napi = { version = "11.24.2", default-features = false, features = ["yarn_pnp"] }
oxc_sourcemap = "8.1.0"

# rolldown crates
Expand Down
43 changes: 27 additions & 16 deletions packages/cli/binding/index.d.cts
Original file line number Diff line number Diff line change
Expand Up @@ -1631,11 +1631,16 @@ export declare class BindingDevEngine {
getBundleState(): Promise<BindingBundleState>;
ensureLatestBuildOutput(): Promise<BindingResult<undefined>>;
triggerFullBuild(): void;
invalidate(
caller: string,
firstInvalidatedBy?: string | undefined | null,
): Promise<BindingResult<Array<BindingClientHmrUpdate>>>;
registerModules(clientId: string, modules: Array<string>): Promise<void>;
/**
* Client-connect signal (the clientId hello): creates the per-client session
* with an empty ship map. Reconnects arrive as fresh clientIds.
*/
registerClient(clientId: string): Promise<void>;
/**
* Delivery notification from the serving middleware: the response for
* `filename` completed, so record its modules as shipped to that client.
*/
notifyPayloadDelivered(filename: string): Promise<void>;
removeClient(clientId: string): Promise<void>;
close(): Promise<void>;
/**
Expand All @@ -1645,7 +1650,7 @@ export declare class BindingDevEngine {
* The module was previously stubbed with a proxy, and now we need to compile the
* actual module and its dependencies.
*/
compileEntry(moduleId: string, clientId: string): Promise<string>;
compileEntry(moduleId: string, clientId: string): Promise<BindingLazyChunkOutput>;
}

export declare class BindingLoadPluginContext {
Expand Down Expand Up @@ -2355,19 +2360,18 @@ export interface BindingGeneratedCodeOptions {
preset?: string;
}

export interface BindingHmrBoundaryOutput {
boundary: string;
acceptedVia: string;
}

export type BindingHmrUpdate =
| {
type: 'Patch';
code: string;
filename: string;
sourcemap?: string;
sourcemapFilename?: string;
hmrBoundaries: Array<BindingHmrBoundaryOutput>;
sourcemapFilename?: string /**
* Stable ids of the changed modules — the `changedIds` of the push envelope.
* The client walks from these on its own graph.
*/;
changedIds: Array<string> /** Per-client envelope sequence number. */;
seq: number;
}
| { type: 'FullReload'; reason?: string }
| { type: 'Noop' };
Expand Down Expand Up @@ -2538,6 +2542,15 @@ export interface BindingJsWatchChangeEvent {
event: string;
}

/**
* The client-facing slice of a lazy-compile result. The carried modules and
* stamps stay server-side as the engine's pending-payload entry.
*/
export interface BindingLazyChunkOutput {
code: string;
filename: string;
}

export interface BindingLog {
message: string;
id?: string;
Expand Down Expand Up @@ -2843,8 +2856,7 @@ export declare const enum BindingPropertyWriteSideEffects {

export declare const enum BindingRebuildStrategy {
Always = 0,
Auto = 1,
Never = 2,
Never = 1,
}

export interface BindingReplacePluginConfig {
Expand Down Expand Up @@ -3189,7 +3201,6 @@ export interface JsOutputChunk {
map?: BindingSourcemap;
sourcemapFilename?: string;
preliminaryFilename: string;
preliminarySourcemapFilename?: string;
}

/** Error emitted from native side, it only contains kind and message, no stack trace. */
Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/migration/migrator/eslint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,15 @@ export async function migrateEslintToOxlint(

// Steps 1-2: Only run @oxlint/migrate if there's an eslint config at root
if (eslintConfigFile) {
// Pin @oxlint/migrate to the bundled oxlint version.
// @oxlint/migrate ships alongside oxlint but can lag a freshly released
// oxlint version by a few days, so an exact pin (e.g. `@1.74.0`) fails to
// resolve while only `@1.73.0` is published. Pin to the bundled oxlint's
// major so `vp dlx` picks the latest published, compatible @oxlint/migrate;
// it upgrades automatically once the matching release lands.
// @ts-expect-error — resolved at runtime from dist/ → dist/versions.js
const { versions } = await import('../versions.js');
const migratePackage = `@oxlint/migrate@${versions.oxlint}`;
const oxlintMajor = String(versions.oxlint).split('.')[0];
const migratePackage = `@oxlint/migrate@${oxlintMajor}`;
const migrateArgs = [
'--merge',
...(!hasBaseUrlInTsconfig(projectPath) ? ['--type-aware'] : []),
Expand Down
85 changes: 74 additions & 11 deletions packages/core/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@ async function bundleTsdown() {

// Re-build tsdown cli plus the bundled `@tsdown/exe` and `@tsdown/css`
// extensions as stable named entries (`tsdown-exe.js`, `tsdown-css.js`).
// These named entries are a public contract (see the `command_pack_tsdown_extensions`
// snapshot test). `@tsdown/exe` also has to be wired into tsdown's runtime
// loader because it is loaded via `importWithError` (a runtime string import
// rolldown cannot follow); `@tsdown/css` is loaded via a static
// `import("@tsdown/css")` that rolldown bundles into its own chunk
// automatically (see wireBundledTsdownExtensions).
await build({
input: {
run: join(tsdownSourceDir, 'dist/run.mjs'),
Expand Down Expand Up @@ -587,6 +593,63 @@ async function brandTsdown() {
'output("error", `${bold(red`error:`)} ${format(msgs).replace(/^([A-Za-z]*Error):\\s*/, "")}`);',
},
];

// The branded logger replacements above reference `bold`, `red`, and `yellow`.
// tsdown's own logger only imported the badge colors it actually used (e.g.
// `bgRed`, `bgYellow`), so depending on rolldown's chunking the chunk the
// logger lands in may not import all of these — leaving the branded output to
// crash at runtime with `ReferenceError: bold is not defined`. Locate the
// shared chunk that re-exports the color helpers (and the minified aliases
// rolldown assigned them) so we can add any missing imports to patched chunks.
const brandedColorNames = ['bold', 'red', 'yellow'];
let colorChunkBase: string | undefined;
const colorChunkAliases: Record<string, string> = {};
for (const candidateFile of loggerCandidateFiles) {
const content = await readFile(candidateFile, 'utf-8');
const exportBlock = (content.match(/export\s*\{[^}]*\}/g) ?? []).join('\n');
const aliases: Record<string, string> = {};
for (const name of brandedColorNames) {
const match = exportBlock.match(new RegExp(`\\b${name} as (\\w+)`));
if (match) {
aliases[name] = match[1];
}
}
if (brandedColorNames.every((name) => name in aliases)) {
colorChunkBase = parse(candidateFile).base;
Object.assign(colorChunkAliases, aliases);
break;
}
}
if (!colorChunkBase) {
throw new Error(
'brandTsdown: could not locate the color-helper chunk for branded logger imports',
);
}
const colorChunkImport = `"./${colorChunkBase}"`;

// Add imports for any color helper the branded replacements reference but the
// patched chunk does not already have in scope.
const ensureBrandedColorImports = (content: string, file: string): string => {
// When the logger lives in the color chunk itself, the helpers are local
// bindings and need no import.
if (parse(file).base === colorChunkBase) {
return content;
}
const missing = brandedColorNames.filter(
(name) => !new RegExp(`\\bas ${name}\\b`).test(content),
);
if (missing.length === 0) {
return content;
}
const specifiers = missing.map((name) => `${colorChunkAliases[name]} as ${name}`).join(', ');
const escapedBase = colorChunkBase.replaceAll('.', '\\.');
const importRe = new RegExp(`(import \\{[^}]*)(\\} from "\\./${escapedBase}")`);
if (importRe.test(content)) {
return content.replace(importRe, `$1, ${specifiers}$2`);
}
return `import { ${specifiers} } from ${colorChunkImport};\n${content}`;
};

let loggerPatched = false;

for (const candidateFile of loggerCandidateFiles) {
Expand All @@ -601,6 +664,7 @@ async function brandTsdown() {
if (!changed) {
continue;
}
content = ensureBrandedColorImports(content, candidateFile);
await writeFile(candidateFile, content, 'utf-8');
console.log(`Branded tsdown logger prefixes in ${candidateFile}`);
loggerPatched = true;
Expand All @@ -623,18 +687,21 @@ async function wireBundledTsdownExtensions() {
throw new Error('wireBundledTsdownExtensions: no chunk found in dist/tsdown/');
}

// Route `@tsdown/exe` and `@tsdown/css` to the bundled entries.
// Wire the two bundled extensions into the bundled tsdown:
// - `importWithError("@tsdown/exe")` dynamically imports a runtime string,
// which rolldown cannot follow, so rewrite the call site to the chunk.
// - `pkgExists("@tsdown/css")` resolves the top-level package at runtime;
// since it is bundled now, force it on and point the import at the chunk.
// since it is bundled now, force the gate on so the css path is taken.
// The gated `import("@tsdown/css")` is a static specifier that rolldown
// already follows and bundles into its own chunk (rewriting the call site
// to that chunk), so no manual import rewrite is needed for css.
// `@tsdown/css` still imports `lightningcss` (a native module that cannot be
// bundled), which resolves to core's own `lightningcss` dependency.
let exeWired = false;
let cssWired = false;
// The `import("@tsdown/css")` call site may already be deduped to the bundled
// entry by rolldown; track whether the bundled load ends up referenced either
// way so a silent miss (no rewrite and no dedup) fails the build.
// Confirm rolldown actually bundled the css plugin into a local chunk instead
// of leaving a bare `import("@tsdown/css")` that would fail at runtime.
const cssLoadPattern = /\{\s*CssPlugin\s*\}\s*=\s*await import\("\.\/[^"]+"\)/;
let cssLoadWired = false;
for (const chunkFile of chunkFiles) {
let content = await readFile(chunkFile, 'utf-8');
Expand All @@ -649,11 +716,7 @@ async function wireBundledTsdownExtensions() {
cssWired = true;
changed = true;
}
if (content.includes('import("@tsdown/css")')) {
content = content.replaceAll('import("@tsdown/css")', 'import("./tsdown-css.js")');
changed = true;
}
if (content.includes('import("./tsdown-css.js")')) {
if (cssLoadPattern.test(content)) {
cssLoadWired = true;
}
if (changed) {
Expand All @@ -667,7 +730,7 @@ async function wireBundledTsdownExtensions() {
throw new Error('wireBundledTsdownExtensions: `pkgExists("@tsdown/css")` not found');
}
if (!cssLoadWired) {
throw new Error('wireBundledTsdownExtensions: bundled `./tsdown-css.js` is never imported');
throw new Error('wireBundledTsdownExtensions: bundled `CssPlugin` chunk is never imported');
}
}

Expand Down
8 changes: 4 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
"@oxc-node/core": "catalog:",
"@tsdown/css": "catalog:",
"@tsdown/exe": "catalog:",
"@vitejs/devtools": "^0.4.0",
"@vitejs/devtools": "^0.4.1",
"es-module-lexer": "^1.7.0",
"hookable": "^6.0.1",
"magic-string": "^0.30.21",
Expand Down Expand Up @@ -214,8 +214,8 @@
"node": "^20.19.0 || ^22.18.0 || >=24.11.0"
},
"bundledVersions": {
"vite": "8.1.4",
"rolldown": "1.1.5",
"tsdown": "0.22.7"
"vite": "8.1.5",
"rolldown": "1.2.0",
"tsdown": "0.22.12"
}
}
4 changes: 2 additions & 2 deletions packages/tools/.upstream-versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"rolldown": {
"repo": "https://github.com/rolldown/rolldown.git",
"branch": "main",
"hash": "f09947ab017d6df74299f691853dcfc4f4f0f86e"
"hash": "03e1e3422cd85495c9863ff3bc3b24212d9f4be2"
},
"vite": {
"repo": "https://github.com/vitejs/vite.git",
"branch": "main",
"hash": "a477454442eff649b430f9e3c6caf2500fcb7183"
"hash": "5e7fe129a4dde4f41934083b25e490059985f4e6"
}
}
Loading
Loading