diff --git a/package.json b/package.json index 2523da929a6..62a647ba318 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "test.e2e.firefox": "playwright test starters --browser=firefox --config starters/playwright.config.ts", "test.e2e.webkit": "playwright test starters --browser=webkit --config starters/playwright.config.ts", "serve": "yarn node -r esbuild-register --inspect starters/dev-server.ts 3300", + "serve.debug": "yarn node --inspect-brk -r esbuild-register --inspect starters/dev-server.ts 3300", "docs.fetch.hackMD": "yarn node --trace-warnings -r esbuild-register scripts/docs_sync/fetch_hackmd.ts", "docs.sync": "yarn node scripts/docs_sync", "lint": "yarn lint.eslint && yarn lint.prettier && yarn lint.rust", diff --git a/packages/docs/src/components/repl/repl-options.tsx b/packages/docs/src/components/repl/repl-options.tsx index b4d7fbd0ff9..e4a8bab1d74 100644 --- a/packages/docs/src/components/repl/repl-options.tsx +++ b/packages/docs/src/components/repl/repl-options.tsx @@ -85,7 +85,7 @@ const StoreOption = (props: StoreOptionProps) => { const MODE_OPTIONS = ['development', 'production']; -const ENTRY_STRATEGY_OPTIONS = ['component', 'hook', 'single', 'smart']; +const ENTRY_STRATEGY_OPTIONS = ['component', 'hook', 'single', 'smart', 'inline']; interface StoreOptionProps { label: string; diff --git a/packages/qwik/src/core/api.md b/packages/qwik/src/core/api.md index c9b8fadb56a..d336c88130a 100644 --- a/packages/qwik/src/core/api.md +++ b/packages/qwik/src/core/api.md @@ -113,7 +113,7 @@ export interface Context { // @public (undocumented) export interface CorePlatform { - chunkForSymbol: (symbolName: string) => string | undefined; + chunkForSymbol: (symbolName: string) => [string, string] | undefined; importSymbol: (element: Element, url: string | URL, symbol: string) => ValueOrPromise; isServer: boolean; // (undocumented) @@ -290,6 +290,9 @@ export function immutable(input: T): Readonly; // @alpha export function implicit$FirstArg(fn: (first: QRL, ...rest: REST) => RET): (first: FIRST, ...rest: REST) => RET; +// @alpha (undocumented) +export function inlinedQrl(symbol: T, symbolName: string, lexicalScopeCapture?: any[]): QRL; + // @public (undocumented) export interface InvokeContext { // (undocumented) @@ -529,6 +532,8 @@ export interface SnapshotResult { // (undocumented) listeners: SnapshotListener[]; // (undocumented) + objs: any[]; + // (undocumented) state: SnapshotState; } diff --git a/packages/qwik/src/core/import/qrl.ts b/packages/qwik/src/core/import/qrl.ts index 48539951754..1e3d0f7e319 100644 --- a/packages/qwik/src/core/import/qrl.ts +++ b/packages/qwik/src/core/import/qrl.ts @@ -13,6 +13,7 @@ import { tryGetInvokeContext } from '../use/use-core'; let runtimeSymbolId = 0; const RUNTIME_QRL = '/runtimeQRL'; +const INLINED_QRL = '/inlinedQRL'; // https://regexr.com/68v72 const EXTRACT_IMPORT_PATH = /\(\s*(['"])([^\1]+)\1\s*\)/; @@ -109,11 +110,7 @@ export function qrl( } // Unwrap subscribers - if (Array.isArray(lexicalScopeCapture)) { - for (let i = 0; i < lexicalScopeCapture.length; i++) { - lexicalScopeCapture[i] = unwrapSubscriber(lexicalScopeCapture[i]); - } - } + unwrapLexicalScope(lexicalScopeCapture); const qrl = new QRLInternal(chunk, symbol, null, symbolFn, null, lexicalScopeCapture); const ctx = tryGetInvokeContext(); if (ctx && ctx.element) { @@ -133,6 +130,34 @@ export function runtimeQrl(symbol: T, lexicalScopeCapture: any[] = EMPTY_ARRA ); } +/** + * @alpha + */ +export function inlinedQrl( + symbol: T, + symbolName: string, + lexicalScopeCapture: any[] = EMPTY_ARRAY +): QRL { + // Unwrap subscribers + return new QRLInternal( + INLINED_QRL, + symbolName, + symbol, + null, + null, + unwrapLexicalScope(lexicalScopeCapture) + ); +} + +function unwrapLexicalScope(lexicalScope: any[] | null) { + if (Array.isArray(lexicalScope)) { + for (let i = 0; i < lexicalScope.length; i++) { + lexicalScope[i] = unwrapSubscriber(lexicalScope[i]); + } + } + return lexicalScope; +} + export interface QRLSerializeOptions { platform?: CorePlatform; element?: Element; @@ -141,12 +166,20 @@ export interface QRLSerializeOptions { export function stringifyQRL(qrl: QRL, opts: QRLSerializeOptions = {}) { const qrl_ = toInternalQRL(qrl); - const symbol = qrl_.symbol; + let symbol = qrl_.symbol; + let chunk = qrl_.chunk; const refSymbol = qrl_.refSymbol ?? symbol; const platform = opts.platform; const element = opts.element; - const chunk = platform ? platform.chunkForSymbol(refSymbol) ?? qrl_.chunk : qrl_.chunk; - + if (platform) { + const result = platform.chunkForSymbol(refSymbol); + if (result) { + chunk = result[0]; + if (!qrl_.refSymbol) { + symbol = result[1]; + } + } + } const parts: string[] = [chunk]; if (symbol && symbol !== 'default') { parts.push('#', symbol); diff --git a/packages/qwik/src/core/index.ts b/packages/qwik/src/core/index.ts index 738fa28f6a4..a092d78cfb5 100644 --- a/packages/qwik/src/core/index.ts +++ b/packages/qwik/src/core/index.ts @@ -36,7 +36,7 @@ export type { SnapshotState, SnapshotResult } from './object/store.public'; // Internal Runtime ////////////////////////////////////////////////////////////////////////////////////////// export { $, implicit$FirstArg } from './import/qrl.public'; -export { qrl } from './import/qrl'; +export { qrl, inlinedQrl } from './import/qrl'; export type { QRL, EventHandler } from './import/qrl.public'; export type { Props } from './props/props.public'; diff --git a/packages/qwik/src/core/object/store.ts b/packages/qwik/src/core/object/store.ts index 70a4b583bb9..7dc5c122ae5 100644 --- a/packages/qwik/src/core/object/store.ts +++ b/packages/qwik/src/core/object/store.ts @@ -144,6 +144,7 @@ export interface SnapshotListener { export interface SnapshotResult { state: SnapshotState; listeners: SnapshotListener[]; + objs: any[]; } export function snapshotState(containerEl: Element): SnapshotResult { @@ -351,6 +352,7 @@ export function snapshotState(containerEl: Element): SnapshotResult { objs: convertedObjs, subs, }, + objs, listeners, }; } diff --git a/packages/qwik/src/core/platform/types.ts b/packages/qwik/src/core/platform/types.ts index a74c6180492..5f8c03ae70b 100644 --- a/packages/qwik/src/core/platform/types.ts +++ b/packages/qwik/src/core/platform/types.ts @@ -21,5 +21,5 @@ export interface CorePlatform { /** * Takes a qrl and serializes into a string */ - chunkForSymbol: (symbolName: string) => string | undefined; + chunkForSymbol: (symbolName: string) => [string, string] | undefined; } diff --git a/packages/qwik/src/optimizer/cli/src/main.rs b/packages/qwik/src/optimizer/cli/src/main.rs index a850de0ec0a..59d036f9328 100644 --- a/packages/qwik/src/optimizer/cli/src/main.rs +++ b/packages/qwik/src/optimizer/cli/src/main.rs @@ -121,6 +121,7 @@ fn optimize( entry_strategy: optimizer_input.strategy, explicity_extensions: optimizer_input.explicity_extensions, dev: true, + scope: None, })?; result.write_to_fs(¤t_dir.join(optimizer_input.dest).absolutize()?)?; diff --git a/packages/qwik/src/optimizer/core/src/code_move.rs b/packages/qwik/src/optimizer/core/src/code_move.rs index 8ad663bdc60..13f2fe32597 100644 --- a/packages/qwik/src/optimizer/core/src/code_move.rs +++ b/packages/qwik/src/optimizer/core/src/code_move.rs @@ -315,7 +315,7 @@ pub fn transform_arrow_fn( ast::BlockStmtOrExpr::BlockStmt(mut block) => { let mut stmts = Vec::with_capacity(1 + block.stmts.len()); if !scoped_idents.is_empty() { - stmts.push(create_use_closure(qwik_ident, scoped_idents)); + stmts.push(create_use_lexical_scope(qwik_ident, scoped_idents)); } stmts.append(&mut block.stmts); ast::ArrowExpr { @@ -329,7 +329,7 @@ pub fn transform_arrow_fn( ast::BlockStmtOrExpr::Expr(expr) => { let mut stmts = Vec::with_capacity(2); if !scoped_idents.is_empty() { - stmts.push(create_use_closure(qwik_ident, scoped_idents)); + stmts.push(create_use_lexical_scope(qwik_ident, scoped_idents)); } stmts.push(create_return_stmt(expr)); ast::ArrowExpr { @@ -352,7 +352,7 @@ pub fn transform_fn(node: ast::FnExpr, qwik_ident: &Id, scoped_idents: &[Id]) -> .map_or(0, |body| body.stmts.len()), ); if !scoped_idents.is_empty() { - stmts.push(create_use_closure(qwik_ident, scoped_idents)); + stmts.push(create_use_lexical_scope(qwik_ident, scoped_idents)); } if let Some(mut body) = node.function.body { stmts.append(&mut body.stmts); @@ -376,7 +376,7 @@ const fn create_return_stmt(expr: Box) -> ast::Stmt { }) } -fn create_use_closure(qwik_ident: &Id, scoped_idents: &[Id]) -> ast::Stmt { +fn create_use_lexical_scope(qwik_ident: &Id, scoped_idents: &[Id]) -> ast::Stmt { ast::Stmt::Decl(ast::Decl::Var(ast::VarDecl { span: DUMMY_SP, declare: false, @@ -386,7 +386,7 @@ fn create_use_closure(qwik_ident: &Id, scoped_idents: &[Id]) -> ast::Stmt { span: DUMMY_SP, init: Some(Box::new(ast::Expr::Call(create_internal_call( qwik_ident, - &USE_CLOSURE, + &USE_LEXICAL_SCOPE, vec![], None, )))), diff --git a/packages/qwik/src/optimizer/core/src/entry_strategy.rs b/packages/qwik/src/optimizer/core/src/entry_strategy.rs index eb75d19f4c8..076097a2329 100644 --- a/packages/qwik/src/optimizer/core/src/entry_strategy.rs +++ b/packages/qwik/src/optimizer/core/src/entry_strategy.rs @@ -16,6 +16,7 @@ lazy_static! { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum EntryStrategy { + Inline, Single, Hook, Component, @@ -33,6 +34,21 @@ pub trait EntryPolicy: Send + Sync { ) -> Option; } +#[derive(Default, Clone)] +pub struct InlineStrategy; + +impl EntryPolicy for InlineStrategy { + fn get_entry_for_sym( + &self, + _symbol: &str, + _path: &PathData, + _context: &[String], + _hook_data: &HookData, + ) -> Option { + Some(ENTRY_HOOKS.clone()) + } +} + #[derive(Default, Clone)] pub struct SingleStrategy; @@ -143,6 +159,7 @@ impl EntryPolicy for ManualStrategy { pub fn parse_entry_strategy(strategy: EntryStrategy) -> Box { match strategy { + EntryStrategy::Inline => Box::new(InlineStrategy::default()), EntryStrategy::Single => Box::new(SingleStrategy::default()), EntryStrategy::Hook => Box::new(PerHookStrategy::default()), EntryStrategy::Component => Box::new(PerComponentStrategy::default()), diff --git a/packages/qwik/src/optimizer/core/src/lib.rs b/packages/qwik/src/optimizer/core/src/lib.rs index f37deafecc2..cf67519dfab 100644 --- a/packages/qwik/src/optimizer/core/src/lib.rs +++ b/packages/qwik/src/optimizer/core/src/lib.rs @@ -47,6 +47,7 @@ pub struct TransformFsOptions { pub transpile: bool, pub explicity_extensions: bool, pub dev: bool, + pub scope: Option, } #[derive(Serialize, Debug, Deserialize)] @@ -67,12 +68,14 @@ pub struct TransformModulesOptions { pub entry_strategy: EntryStrategy, pub explicity_extensions: bool, pub dev: bool, + pub scope: Option, } #[cfg(feature = "fs")] pub fn transform_fs(config: TransformFsOptions) -> Result { let root_dir = Path::new(&config.root_dir); let mut paths = vec![]; + let is_inline = matches!(config.entry_strategy, EntryStrategy::Inline); let entry_policy = &*parse_entry_strategy(config.entry_strategy); find_files(root_dir, &mut paths)?; @@ -100,8 +103,10 @@ pub fn transform_fs(config: TransformFsOptions) -> Result Result Result { + let is_inline = matches!(config.entry_strategy, EntryStrategy::Inline); let entry_policy = &*parse_entry_strategy(config.entry_strategy); #[cfg(feature = "parallel")] let iterator = config.input.par_iter(); @@ -129,6 +135,8 @@ pub fn transform_modules(config: TransformModulesOptions) -> Result { pub code: &'a str, pub entry_policy: &'a dyn EntryPolicy, pub dev: bool, + pub scope: Option<&'a String>, + pub is_inline: bool, } #[derive(Debug, Serialize, Deserialize, Default)] @@ -208,7 +210,9 @@ pub fn transform_code(config: TransformCodeOptions) -> Result { + + useStyles$('somestring'); + const state = useStore({ + count: 0 + }); + + // Double count watch + useClientEffect$(() => { + state.count = thing.doStuff(); + }); + + return ( +
console.log(mongodb)}> +
+ ); +}); + + +============================= test.tsx == + +import * as qwik from "@builder.io/qwik"; +import { componentQrl } from "@builder.io/qwik"; +import { useClientEffectQrl } from "@builder.io/qwik"; +import { useStylesQrl } from "@builder.io/qwik"; +import { useStore } from '@builder.io/qwik'; +import { thing } from 'dependency'; +import mongodb from 'mongodb'; +export const Child = /*#__PURE__*/ componentQrl(qwik.inlinedQrl(()=>{ + useStylesQrl(qwik.inlinedQrl('somestring', "Child_component_useStyles_qBZTuFM0160")); + const state = useStore({ + count: 0 + }); + // Double count watch + useClientEffectQrl(qwik.inlinedQrl(()=>{ + const [state] = qwik.useLexicalScope(); + state.count = thing.doStuff(); + }, "Child_component_useClientEffect_kYRT1iERt9g", [ + state + ])); + return
{ + return console.log(mongodb); + }, "Child_component_div_onClick_elliVSnAiOQ")}> + +
; +}, "Child_component_9GyF01GDKqw")); + +== DIAGNOSTICS == + +[] diff --git a/packages/qwik/src/optimizer/core/src/test.rs b/packages/qwik/src/optimizer/core/src/test.rs index e1237e8806a..d1ae954b63c 100644 --- a/packages/qwik/src/optimizer/core/src/test.rs +++ b/packages/qwik/src/optimizer/core/src/test.rs @@ -18,6 +18,7 @@ macro_rules! test_input { explicity_extensions: input.explicity_extensions, entry_strategy: input.entry_strategy, dev: input.dev, + scope: input.scope, }); if input.snapshot { match &res { @@ -984,6 +985,39 @@ export const Child = component$(() => { }); } +#[test] +fn example_inlined_entry_strategy() { + test_input!(TestInput { + code: r#" +import { component$, useClientEffect$, useStore, useStyles$ } from '@builder.io/qwik'; +import { thing } from 'dependency'; +import mongodb from 'mongodb'; + +export const Child = component$(() => { + + useStyles$('somestring'); + const state = useStore({ + count: 0 + }); + + // Double count watch + useClientEffect$(() => { + state.count = thing.doStuff(); + }); + + return ( +
console.log(mongodb)}> +
+ ); +}); + +"# + .to_string(), + entry_strategy: EntryStrategy::Inline, + ..TestInput::default() + }); +} + #[test] fn example_use_server_mount() { test_input!(TestInput { @@ -1162,6 +1196,7 @@ export const Greeter = component$(() => { dev: true, entry_strategy: EntryStrategy::Hook, transpile: true, + scope: None, }); let ref_hooks: Vec<_> = res .unwrap() @@ -1189,6 +1224,7 @@ export const Greeter = component$(() => { dev: option.0, entry_strategy: option.1, transpile: option.2, + scope: None, }); let hooks: Vec<_> = res @@ -1229,6 +1265,7 @@ struct TestInput { pub explicity_extensions: bool, pub snapshot: bool, pub dev: bool, + pub scope: Option, } impl TestInput { @@ -1243,6 +1280,7 @@ impl TestInput { explicity_extensions: false, snapshot: true, dev: true, + scope: None, } } } diff --git a/packages/qwik/src/optimizer/core/src/transform.rs b/packages/qwik/src/optimizer/core/src/transform.rs index 844b87b6334..8ddd8f8d2bf 100644 --- a/packages/qwik/src/optimizer/core/src/transform.rs +++ b/packages/qwik/src/optimizer/core/src/transform.rs @@ -1,4 +1,4 @@ -use crate::code_move::fix_path; +use crate::code_move::{fix_path, transform_function_expr}; use crate::collector::{ collect_from_pat, new_ident_from_id, GlobalCollect, Id, IdentCollector, ImportKind, }; @@ -110,7 +110,9 @@ pub struct QwikTransformOptions<'a> { pub explicity_extensions: bool, pub comments: Option<&'a SingleThreadedComments>, pub global_collect: GlobalCollect, + pub scope: Option<&'a String>, pub dev: bool, + pub is_inline: bool, } fn convert_signal_word(id: &JsWord) -> Option { @@ -180,7 +182,18 @@ impl<'a> QwikTransform<'a> { } } - fn register_context_name(&mut self) -> (JsWord, JsWord, JsWord, u64) { + fn register_context_name( + &mut self, + custom_symbol: Option, + ) -> (JsWord, JsWord, JsWord, u64) { + if let Some(custom_symbol) = custom_symbol { + return ( + custom_symbol.clone(), + custom_symbol.clone(), + custom_symbol, + 0, + ); + } let mut display_name = self.stack_ctxt.join("_"); if self.stack_ctxt.is_empty() { display_name += "s_"; @@ -200,7 +213,9 @@ impl<'a> QwikTransform<'a> { } let mut hasher = DefaultHasher::new(); let local_file_name = self.options.path_data.path.to_slash_lossy(); - + if let Some(scope) = self.options.scope { + hasher.write(scope.as_bytes()); + } hasher.write(local_file_name.as_bytes()); hasher.write(display_name.as_bytes()); let hash = hasher.finish(); @@ -227,7 +242,25 @@ impl<'a> QwikTransform<'a> { expr: first_arg, .. }) = node.args.pop() { - self.create_synthetic_qhook(*first_arg, HookKind::Function, QHOOK.clone()) + let custom_symbol = if let Some(ast::ExprOrSpread { + expr: second_arg, .. + }) = node.args.pop() + { + if let ast::Expr::Lit(ast::Lit::Str(second_arg)) = *second_arg { + Some(second_arg.value) + } else { + None + } + } else { + None + }; + + self.create_synthetic_qhook( + *first_arg, + HookKind::Function, + QHOOK.clone(), + custom_symbol, + ) } else { node } @@ -238,11 +271,13 @@ impl<'a> QwikTransform<'a> { first_arg: ast::Expr, ctx_kind: HookKind, ctx_name: JsWord, + custom_symbol: Option, ) -> ast::CallExpr { let can_capture = can_capture_scope(&first_arg); let first_arg_span = first_arg.span(); - let (symbol_name, display_name, hash, hook_hash) = self.register_context_name(); + let (symbol_name, display_name, hash, hook_hash) = + self.register_context_name(custom_symbol); let canonical_filename = JsWord::from(symbol_name.as_ref().to_ascii_lowercase()); // Collect descendent idents @@ -327,53 +362,60 @@ impl<'a> QwikTransform<'a> { scoped_idents = vec![]; } - let hook_data = HookData { - extension: self.options.extension.clone(), - local_idents, - scoped_idents: scoped_idents.clone(), - parent_hook: self.hook_stack.last().cloned(), - ctx_kind, - ctx_name, - origin: self.options.path_data.path.to_slash_lossy().into(), - display_name, - hash, - }; - - let entry = self.options.entry_policy.get_entry_for_sym( - &symbol_name, - self.options.path_data, - &self.stack_ctxt, - &hook_data, - ); - - let mut filename = format!( - "./{}", - entry - .as_ref() - .map(|e| e.as_ref()) - .unwrap_or(&canonical_filename) - ); - if self.options.explicity_extensions { - filename.push('.'); - filename.push_str(&self.options.extension); - } - let import_path = if !self.hook_stack.is_empty() { - fix_path("a", "a", &filename).unwrap() + if self.options.is_inline { + create_inline_qrl( + &self.qwik_ident, + transform_function_expr(folded, &self.qwik_ident, &scoped_idents), + &symbol_name, + &scoped_idents, + ) } else { - fix_path("a", &self.options.path_data.path, &filename).unwrap() - }; - - let o = create_inline_qrl(&self.qwik_ident, import_path, &symbol_name, &scoped_idents); - self.hooks.push(Hook { - entry, - span, - canonical_filename, - name: symbol_name, - data: hook_data, - expr: Box::new(folded), - hash: hook_hash, - }); - o + let hook_data = HookData { + extension: self.options.extension.clone(), + local_idents, + scoped_idents: scoped_idents.clone(), + parent_hook: self.hook_stack.last().cloned(), + ctx_kind, + ctx_name, + origin: self.options.path_data.path.to_slash_lossy().into(), + display_name, + hash, + }; + let entry = self.options.entry_policy.get_entry_for_sym( + &symbol_name, + self.options.path_data, + &self.stack_ctxt, + &hook_data, + ); + let mut filename = format!( + "./{}", + entry + .as_ref() + .map(|e| e.as_ref()) + .unwrap_or(&canonical_filename) + ); + if self.options.explicity_extensions { + filename.push('.'); + filename.push_str(&self.options.extension); + } + let import_path = if !self.hook_stack.is_empty() { + fix_path("a", "a", &filename).unwrap() + } else { + fix_path("a", &self.options.path_data.path, &filename).unwrap() + }; + + let o = create_qrl(&self.qwik_ident, import_path, &symbol_name, &scoped_idents); + self.hooks.push(Hook { + entry, + span, + canonical_filename, + name: symbol_name, + data: hook_data, + expr: Box::new(folded), + hash: hook_hash, + }); + o + } } fn handle_jsx(&mut self, node: ast::CallExpr) -> ast::CallExpr { @@ -412,7 +454,7 @@ impl<'a> QwikTransform<'a> { Some(ast::JSXAttrValue::JSXExprContainer(ast::JSXExprContainer { span: DUMMY_SP, expr: ast::JSXExpr::Expr(Box::new(ast::Expr::Call( - self.create_synthetic_qhook(*expr, HookKind::Event, ctx_name), + self.create_synthetic_qhook(*expr, HookKind::Event, ctx_name, None), ))), })) } else { @@ -651,6 +693,7 @@ impl<'a> Fold for QwikTransform<'a> { *node.value, HookKind::Event, ident.sym.clone(), + None, ))), } } else { @@ -673,6 +716,7 @@ impl<'a> Fold for QwikTransform<'a> { *node.value, HookKind::Event, s.value.clone(), + None, ))), } } else { @@ -730,7 +774,7 @@ impl<'a> Fold for QwikTransform<'a> { let is_synthetic = global_collect.imports.get(&new_local).unwrap().synthetic; - if is_synthetic && self.hook_stack.is_empty() { + if self.options.is_inline || (is_synthetic && self.hook_stack.is_empty()) { self.extra_module_items.insert( new_local.clone(), create_synthetic_named_import(&new_local, &import.source), @@ -783,6 +827,7 @@ impl<'a> Fold for QwikTransform<'a> { *arg.expr, HookKind::Function, ctx_name.clone(), + None, ))) .fold_with(self), ..arg @@ -950,7 +995,7 @@ fn create_synthetic_named_import(local: &Id, src: &JsWord) -> ast::ModuleItem { })) } -fn create_inline_qrl(qwik_ident: &Id, url: JsWord, symbol: &str, idents: &[Id]) -> ast::CallExpr { +fn create_qrl(qwik_ident: &Id, url: JsWord, symbol: &str, idents: &[Id]) -> ast::CallExpr { let mut args = vec![ ast::Expr::Arrow(ast::ArrowExpr { is_async: false, @@ -1002,6 +1047,40 @@ fn create_inline_qrl(qwik_ident: &Id, url: JsWord, symbol: &str, idents: &[Id]) create_internal_call(qwik_ident, &QRL, args, None) } +fn create_inline_qrl( + qwik_ident: &Id, + expr: ast::Expr, + symbol: &str, + idents: &[Id], +) -> ast::CallExpr { + let mut args = vec![ + expr, + ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: symbol.into(), + raw: None, + })), + ]; + + // Injects state + if !idents.is_empty() { + args.push(ast::Expr::Array(ast::ArrayLit { + span: DUMMY_SP, + elems: idents + .iter() + .map(|id| { + Some(ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Ident(new_ident_from_id(id))), + }) + }) + .collect(), + })) + } + + create_internal_call(qwik_ident, &INLINED_QRL, args, None) +} + pub fn create_internal_call( qwik_ident: &Id, fn_name: &JsWord, diff --git a/packages/qwik/src/optimizer/core/src/words.rs b/packages/qwik/src/optimizer/core/src/words.rs index 26a5227519f..943d60c3908 100644 --- a/packages/qwik/src/optimizer/core/src/words.rs +++ b/packages/qwik/src/optimizer/core/src/words.rs @@ -8,12 +8,13 @@ lazy_static! { pub static ref CHILDREN: JsWord = JsWord::from("children"); pub static ref HANDLE_WATCH: JsWord = JsWord::from("handleWatch"); pub static ref QRL: JsWord = JsWord::from("qrl"); + pub static ref INLINED_QRL: JsWord = JsWord::from("inlinedQrl"); pub static ref QHOOK: JsWord = JsWord::from("$"); pub static ref QWIK_INTERNAL: JsWord = JsWord::from("qwik"); pub static ref BUILDER_IO_QWIK: JsWord = JsWord::from("@builder.io/qwik"); pub static ref BUILDER_IO_QWIK_JSX: JsWord = JsWord::from("@builder.io/qwik/jsx-runtime"); pub static ref QCOMPONENT: JsWord = JsWord::from("component$"); - pub static ref USE_CLOSURE: JsWord = JsWord::from("useLexicalScope"); + pub static ref USE_LEXICAL_SCOPE: JsWord = JsWord::from("useLexicalScope"); pub static ref USE_SERVER_MOUNT: JsWord = JsWord::from("useServerMount$"); pub static ref H: JsWord = JsWord::from("h"); pub static ref FRAGMENT: JsWord = JsWord::from("Fragment"); diff --git a/packages/qwik/src/optimizer/src/api.md b/packages/qwik/src/optimizer/src/api.md index 919efb10bb1..6ef9e316a19 100644 --- a/packages/qwik/src/optimizer/src/api.md +++ b/packages/qwik/src/optimizer/src/api.md @@ -42,8 +42,10 @@ export interface Diagnostic { // @alpha (undocumented) export type DiagnosticType = 'Error' | 'Warning' | 'SourceError'; +// Warning: (ae-forgotten-export) The symbol "InlineEntryStrategy" needs to be exported by the entry point index.d.ts +// // @alpha (undocumented) -export type EntryStrategy = SingleEntryStrategy | HookEntryStrategy | ComponentEntryStrategy | SmartEntryStrategy | ManualEntryStrategy; +export type EntryStrategy = InlineEntryStrategy | SingleEntryStrategy | HookEntryStrategy | ComponentEntryStrategy | SmartEntryStrategy | ManualEntryStrategy; // @alpha (undocumented) export interface GlobalInjections { diff --git a/packages/qwik/src/optimizer/src/optimizer.ts b/packages/qwik/src/optimizer/src/optimizer.ts index b42bec1a966..9e80ce240bf 100644 --- a/packages/qwik/src/optimizer/src/optimizer.ts +++ b/packages/qwik/src/optimizer/src/optimizer.ts @@ -68,6 +68,7 @@ const transformFsAsync = async ( sourceMaps: fsOpts.sourceMaps, transpile: fsOpts.transpile, dev: fsOpts.dev, + scope: fsOpts.scope, input, }; @@ -84,6 +85,7 @@ const convertOptions = (opts: any) => { transpile: false, explicityExtensions: false, dev: true, + scope: undefined, }; Object.entries(opts).forEach(([key, value]) => { if (value != null) { diff --git a/packages/qwik/src/optimizer/src/types.ts b/packages/qwik/src/optimizer/src/types.ts index 94822b197b2..7b2806a541e 100644 --- a/packages/qwik/src/optimizer/src/types.ts +++ b/packages/qwik/src/optimizer/src/types.ts @@ -76,6 +76,7 @@ export interface TransformOptions { transpile?: boolean; explicityExtensions?: boolean; dev?: boolean; + scope?: string; } /** @@ -186,6 +187,7 @@ export type DiagnosticType = 'Error' | 'Warning' | 'SourceError'; * @alpha */ export type EntryStrategy = + | InlineEntryStrategy | SingleEntryStrategy | HookEntryStrategy | ComponentEntryStrategy @@ -197,6 +199,13 @@ export type EntryStrategy = */ export type MinifyMode = 'simplify' | 'none'; +/** + * @alpha + */ +export interface InlineEntryStrategy { + type: 'inline'; +} + /** * @alpha */ diff --git a/packages/qwik/src/server/api.md b/packages/qwik/src/server/api.md index 39891f86e52..d2aa15c93ed 100644 --- a/packages/qwik/src/server/api.md +++ b/packages/qwik/src/server/api.md @@ -54,7 +54,7 @@ export interface PrefetchStrategy { } // @public (undocumented) -export type QrlMapper = (symbolName: string) => string | undefined; +export type QrlMapper = (symbolName: string) => [string, string] | undefined; // @alpha (undocumented) export interface QwikBundle { @@ -171,6 +171,8 @@ export interface SnapshotResult { // // (undocumented) listeners: SnapshotListener[]; + // (undocumented) + objs: any[]; // Warning: (ae-forgotten-export) The symbol "SnapshotState" needs to be exported by the entry point index.d.ts // // (undocumented) diff --git a/packages/qwik/src/server/platform.ts b/packages/qwik/src/server/platform.ts index 546f60599c2..88d8616289b 100644 --- a/packages/qwik/src/server/platform.ts +++ b/packages/qwik/src/server/platform.ts @@ -70,7 +70,7 @@ function createPlatform(document: any, opts: SerializeDocumentOptions) { return qrlMapper(symbolName); } if (qrlMap) { - return qrlMap[symbolName]; + return [qrlMap[symbolName], symbolName]; } return undefined; }, diff --git a/packages/qwik/src/server/prefetch-strategy.ts b/packages/qwik/src/server/prefetch-strategy.ts index af5c739a0e8..eb1ad06d14a 100644 --- a/packages/qwik/src/server/prefetch-strategy.ts +++ b/packages/qwik/src/server/prefetch-strategy.ts @@ -9,6 +9,7 @@ import type { } from './types'; import { QRL_PREFIX } from '../core/object/store'; import { parseQRL } from '../core/import/qrl'; +import { isQrl } from '../core/import/qrl-class'; export function getPrefetchResources( snapshotResult: SnapshotResult | null, @@ -61,7 +62,7 @@ function getEventDocumentPrefetch( ) { const prefetchResources: PrefetchResource[] = []; const listeners = snapshotResult?.listeners; - const stateObjs = snapshotResult?.state?.objs; + const stateObjs = snapshotResult?.objs; const urls = new Set(); if (Array.isArray(listeners)) { @@ -82,11 +83,8 @@ function getEventDocumentPrefetch( if (Array.isArray(stateObjs)) { for (const obj of stateObjs) { - if (typeof obj === 'string' && obj.startsWith(QRL_PREFIX)) { - const q = parseQRL(obj); - if (q?.symbol) { - addBundle(manifest, urls, prefetchResources, buildBase, manifest.mapping[q.symbol]); - } + if (isQrl(obj)) { + addBundle(manifest, urls, prefetchResources, buildBase, manifest.mapping[obj.symbol]); } } } diff --git a/packages/qwik/src/server/types.ts b/packages/qwik/src/server/types.ts index 91eb9ed081d..797b26b2996 100644 --- a/packages/qwik/src/server/types.ts +++ b/packages/qwik/src/server/types.ts @@ -66,7 +66,7 @@ export { QwikManifest, QwikBundle, QwikSymbol, GlobalInjections }; /** * @public */ -export type QrlMapper = (symbolName: string) => string | undefined; +export type QrlMapper = (symbolName: string) => [string, string] | undefined; /** * @public diff --git a/packages/qwik/src/testing/api.md b/packages/qwik/src/testing/api.md index 39891f86e52..d2aa15c93ed 100644 --- a/packages/qwik/src/testing/api.md +++ b/packages/qwik/src/testing/api.md @@ -54,7 +54,7 @@ export interface PrefetchStrategy { } // @public (undocumented) -export type QrlMapper = (symbolName: string) => string | undefined; +export type QrlMapper = (symbolName: string) => [string, string] | undefined; // @alpha (undocumented) export interface QwikBundle { @@ -171,6 +171,8 @@ export interface SnapshotResult { // // (undocumented) listeners: SnapshotListener[]; + // (undocumented) + objs: any[]; // Warning: (ae-forgotten-export) The symbol "SnapshotState" needs to be exported by the entry point index.d.ts // // (undocumented) diff --git a/scripts/api.ts b/scripts/api.ts index 37161ad5196..2d6c2d93aff 100644 --- a/scripts/api.ts +++ b/scripts/api.ts @@ -41,7 +41,7 @@ function createTypesApi( if (msg.text.includes('Analysis will use')) { return; } - console.log('🥶', msg.text); + console.log('🥶', msg); }, }); if (!result.succeeded) { @@ -56,7 +56,7 @@ function createTypesApi( function generateServerReferenceModules(config: BuildConfig) { // server-modules.d.ts - const referenceDts = `/// + const referenceDts = `/// declare module '@qwik-client-manifest' { const manifest: QwikManifest; export { manifest }; diff --git a/scripts/release.ts b/scripts/release.ts index 58fc0e79212..afdc2d3a3d4 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -245,7 +245,7 @@ export async function releaseVersionPrompt(pkgName: string, currentVersion: stri choices: SEMVER_RELEASE_TYPES.map((v) => { return { title: `${v} ${semver.inc(currentVersion, v)}`, - value: semver.inc(currentVersion, v), + value: semver.inc(currentVersion, v)!, }; }), }); diff --git a/starters/dev-server.ts b/starters/dev-server.ts index 62799a1c1f2..0902e10ac66 100644 --- a/starters/dev-server.ts +++ b/starters/dev-server.ts @@ -141,7 +141,7 @@ async function buildApp(appDir: string) { target: 'ssr', buildMode: 'development', srcDir: appSrcDir, - entryStrategy: { type: 'single' }, + entryStrategy: { type: 'inline' }, manifestInput: clientManifest, }), ],