Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
58 changes: 56 additions & 2 deletions packages/optimizer/core/src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ pub struct SegmentData {
pub hash: Atom,
pub need_transform: bool,
pub migrated_root_vars: Vec<ast::ModuleItem>,
/// When the bundler inlines capture variables, the explicit captures array
/// from inlinedQrl may contain non-Ident expressions. We preserve the original
/// array to use directly in the .w() call, bypassing scoped_idents.
pub raw_capture_exprs: Option<ast::ArrayLit>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -584,10 +588,20 @@ impl<'a> QwikTransform<'a> {
folded
};

// When explicit captures are provided (third arg of inlinedQrl), we always preserve
// the original array for the .w() call. The function body has _captures[N] indices
// that match this array's order, so it must be passed through as-is. This is
// important because bundlers (e.g. Rolldown) may inline capture variables before
// our optimizer runs, turning simple Idents into complex expressions.
let mut raw_capture_array: Option<ast::ArrayLit> = None;
let (scoped_idents, captures) = {
if let Some(scoped) = third_arg {
match &*scoped.expr {
ast::Expr::Array(array) => {
// Always preserve the original array for .w()
raw_capture_array = Some(array.clone());
// Extract Ident-only entries for scoped_idents (used by segment
// file generation and other bookkeeping, not for .w())
let idents: Vec<Id> = array
.elems
.iter()
Expand Down Expand Up @@ -635,6 +649,7 @@ impl<'a> QwikTransform<'a> {
need_transform: false,
hash,
migrated_root_vars: Vec::new(),
raw_capture_exprs: raw_capture_array,
};
// Preprocessed inlinedQrl from libs are always emitted — stripping is meant for user code without the user having to write guards; libs can put guards themselves.
// App-level $() calls go through _create_synthetic_qsegment which has its own strip check.
Expand Down Expand Up @@ -938,6 +953,7 @@ impl<'a> QwikTransform<'a> {
need_transform: false,
hash,
migrated_root_vars: Vec::new(),
raw_capture_exprs: None,
};

return (
Expand Down Expand Up @@ -1017,6 +1033,7 @@ impl<'a> QwikTransform<'a> {
need_transform: true,
hash,
migrated_root_vars: Vec::new(),
raw_capture_exprs: None,
};
let should_emit = self.should_emit_segment(&segment_data);
if should_emit {
Expand Down Expand Up @@ -1938,7 +1955,26 @@ impl<'a> QwikTransform<'a> {
_QRL.clone()
};

self.emit_captures(&segment_data.captures, &mut args);
// Injects state — prefer the original captures array from inlinedQrl (preserves
// correct indices), otherwise build from scoped_idents for $() calls.
if let Some(ref raw_array) = segment_data.raw_capture_exprs {
args.push(ast::Expr::Array(raw_array.clone()));
} else if !segment_data.scoped_idents.is_empty() {
args.push(ast::Expr::Array(ast::ArrayLit {
span: DUMMY_SP,
elems: segment_data
.scoped_idents
.iter()
.map(|id| {
Some(ast::ExprOrSpread {
spread: None,
expr: Box::new(ast::Expr::Ident(new_ident_from_id(id))),
})
})
.collect(),
}))
}

self.create_internal_call(&fn_callee, args, true)
}

Expand Down Expand Up @@ -2006,7 +2042,25 @@ impl<'a> QwikTransform<'a> {
_INLINED_QRL.clone()
};

self.emit_captures(&segment_data.captures, &mut args);
// Injects state — prefer the original captures array from inlinedQrl (preserves
// correct indices), otherwise build from scoped_idents for $() calls.
if let Some(ref raw_array) = segment_data.raw_capture_exprs {
args.push(ast::Expr::Array(raw_array.clone()));
} else if !segment_data.scoped_idents.is_empty() {
args.push(ast::Expr::Array(ast::ArrayLit {
span: DUMMY_SP,
elems: segment_data
.scoped_idents
.iter()
.map(|id| {
Some(ast::ExprOrSpread {
spread: None,
expr: Box::new(ast::Expr::Ident(new_ident_from_id(id))),
})
})
.collect(),
}))
}
self.create_internal_call(&fn_callee, args, true)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ function isBundlePartOfRoute(bundle: QwikBundle, routeAndLayoutPaths: string[])
}
for (const bundleOrigin of bundle.origins) {
const originPath = removeExtension(bundleOrigin);
return routeAndLayoutPaths.some((path) => path.endsWith(originPath));
if (routeAndLayoutPaths.some((path) => path.endsWith(originPath))) {
return true;
}
}
return false;
}
3 changes: 2 additions & 1 deletion packages/qwik-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@qwik.dev/qwik-vite",
"private": true,
"devDependencies": {
"image-size": "1.2.1"
"image-size": "1.2.1",
"rolldown": "1.0.0-rc.12"
}
}
9 changes: 6 additions & 3 deletions packages/qwik-vite/src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ export function computeTotals(graph: QwikManifest['bundles']): void {
const preloaderRegex = /[/\\](core|qwik)[/\\]dist[/\\]preloader\.(|c|m)js$/;
const coreRegex = /[/\\](core|qwik)[/\\]dist[/\\]core(\.min|\.prod)?\.(|c|m)js$/;
const qwikLoaderRegex = /[/\\](core|qwik)[/\\](dist[/\\])?qwikloader(\.debug)?\.[^/]*js$/;
const handlersRegex = /[/\\](core|qwik)[/\\]handlers\.(|c|m)js$/;
/**
* Generates the Qwik build manifest from the Rollup output bundles. It also figures out the bundle
* files for the preloader, core, qwikloader and handlers. This information is used during SSR.
Expand Down Expand Up @@ -486,16 +487,13 @@ export function generateManifestFromBundles(
}
}
const bundleImports = outputBundle.imports
// Tree shaking might remove imports
.filter((i) => outputBundle.code.includes(path.basename(i)))
.map((i) => getBundleName(i))
.filter((i) => i !== preloaderBundleName && i !== coreBundleName && i !== qwikHandlersName)
.filter(Boolean) as string[];
if (bundleImports.length > 0) {
bundle.imports = bundleImports;
}
const bundleDynamicImports = outputBundle.dynamicImports
.filter((i) => outputBundle.code.includes(path.basename(i)))
.map((i) => getBundleName(i))
.filter(Boolean) as string[];
if (bundleDynamicImports.length > 0) {
Expand All @@ -510,6 +508,8 @@ export function generateManifestFromBundles(
manifest.core = bundleFileName;
} else if (qwikLoaderRegex.test(outputBundle.facadeModuleId)) {
manifest.qwikLoader = bundleFileName;
} else if (handlersRegex.test(outputBundle.facadeModuleId)) {
qwikHandlersName = bundleFileName;
}
}
// Rollup doesn't provide the moduleIds in the outputBundle but Vite does
Expand All @@ -529,6 +529,9 @@ export function generateManifestFromBundles(
if (!manifest.qwikLoader && modulePaths.some((m) => qwikLoaderRegex.test(m))) {
manifest.qwikLoader = bundleFileName;
}
if (!qwikHandlersName && modulePaths.some((m) => handlersRegex.test(m))) {
qwikHandlersName = bundleFileName;
}
}

manifest.bundles[bundleFileName] = bundle;
Expand Down
20 changes: 0 additions & 20 deletions packages/qwik-vite/src/plugins/bundle-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,22 +115,6 @@ export function convertManifestToBundleGraph(

const names = Object.keys(graph);
const map = new Map<string, { index: number; deps: Set<string> }>();
const clearTransitiveDeps = (
parentDeps: Set<string>,
bundleName: string,
seen: Set<string> = new Set()
) => {
const bundle = graph[bundleName];
for (const dep of bundle.imports!) {
if (parentDeps.has(dep)) {
parentDeps.delete(dep);
}
if (!seen.has(dep)) {
seen.add(dep);
clearTransitiveDeps(parentDeps, dep, seen);
}
}
};

/**
* First pass to collect minimal dependency lists and allocate space for dependencies. Minimal
Expand All @@ -141,13 +125,9 @@ export function convertManifestToBundleGraph(
const bundle = graph[bundleName];
// external dependencies are not included in `graph`
const deps = new Set(bundle.imports!);
for (const depName of deps) {
clearTransitiveDeps(deps, depName);
}
const dynDeps = new Set(bundle.dynamicImports!);
const depProbability = new Map<string, number>();
for (const depName of dynDeps) {
clearTransitiveDeps(dynDeps, depName);
const dep = graph[depName];

// Calculate the probability of the dependency
Expand Down
Loading