diff --git a/Makefile b/Makefile index a078eee13df..ecd034c0035 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,12 @@ lint: test: cargo test +test-update: + if ! cargo test; then \ + cd packages/qwik/src/optimizer/core/src/snapshots/ && for i in *.new; do f=$$(basename $$i .new); mv $$i $$f; done; \ + cargo test; \ + fi + publish-core: cd src/optimizer/core && cargo publish --all-features diff --git a/README.md b/README.md index abf20ac5c66..9abdd544175 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ Cloudflare Pages Server Netlify Server Node Servers - Vercel Server + Vercel Edge + Vercel Serverless Create Qwik CLI Deno Server AWS Server diff --git a/flake.nix b/flake.nix index 036b873f468..f81901f3304 100644 --- a/flake.nix +++ b/flake.nix @@ -28,6 +28,9 @@ # Provides rustc and cargo ((rust-bin.fromRustupToolchainFile ./rust-toolchain).override { + # For rust-analyzer + extensions = [ "rust-src" ]; + # For building wasm targets = [ "wasm32-unknown-unknown" ]; }) ]; diff --git a/package.json b/package.json index dcc2c756224..1a7c31729be 100644 --- a/package.json +++ b/package.json @@ -194,6 +194,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", "test.rust": "make test", + "test.rust.update": "make test-update", "test.unit": "vitest packages", "test.unit.debug": "vitest --inspect-brk packages", "test.vite": "playwright test starters/e2e/qwikcity --browser=chromium --config starters/playwright.config.ts", diff --git a/packages/docs/adapters/cloudflare-pages/vite.config.ts b/packages/docs/adapters/cloudflare-pages/vite.config.ts index 2e2873222de..63bd788e184 100644 --- a/packages/docs/adapters/cloudflare-pages/vite.config.ts +++ b/packages/docs/adapters/cloudflare-pages/vite.config.ts @@ -18,7 +18,7 @@ export default extendConfig(baseConfig, () => { exclude: ['/demo/*', '/shop/*'], origin: (process.env.CF_PAGES_BRANCH !== 'main' ? process.env.CF_PAGES_URL : null) ?? - 'https://qwik.dev', + 'https://qwik.builder.io', }, }), ], diff --git a/packages/docs/public/logos/social-card.jpg b/packages/docs/public/logos/social-card.jpg index 70dea8f9ae9..0402940d4dc 100644 Binary files a/packages/docs/public/logos/social-card.jpg and b/packages/docs/public/logos/social-card.jpg differ diff --git a/packages/docs/src/components/builder-content/index.tsx b/packages/docs/src/components/builder-content/index.tsx index 97fcb73b30d..c915bf32a2d 100644 --- a/packages/docs/src/components/builder-content/index.tsx +++ b/packages/docs/src/components/builder-content/index.tsx @@ -114,7 +114,7 @@ export async function getBuilderContent({ ); qwikUrl.searchParams.set('apiKey', apiKey); qwikUrl.searchParams.set('userAttributes.urlPath', urlPath); - qwikUrl.searchParams.set('userAttributes.site', 'qwik.dev'); + qwikUrl.searchParams.set('userAttributes.site', 'qwik.builder.io'); if (cacheBust) { qwikUrl.searchParams.set('cachebust', 'true'); } diff --git a/packages/docs/src/routes/docs/deployments/cloudflare-pages/index.mdx b/packages/docs/src/routes/docs/deployments/cloudflare-pages/index.mdx index 214c859e201..585e2ce991b 100644 --- a/packages/docs/src/routes/docs/deployments/cloudflare-pages/index.mdx +++ b/packages/docs/src/routes/docs/deployments/cloudflare-pages/index.mdx @@ -10,6 +10,7 @@ contributors: - igorbabko - mrhoodz - dario-piotrowicz + - matthewlal updated_at: '2023-10-03T18:53:23Z' created_at: '2023-04-06T21:28:28Z' --- @@ -38,6 +39,14 @@ The adapter will add a new `vite.config.ts` within the `adapters/` directory, an Additionally, within the `package.json`, the `build.server` and `deploy` scripts will be updated. +Take note of your nodejs version in your local environment by running the `node -v` command: +```shell +node -v +v20.11.1 +``` + +When using `npm create qwik@latest` to setup your Qwik app it will likely use a different nodejs version than what Cloudflare Pages uses by default (v16.20.2). + ## Production build To build the application for production, use the `build` command, this command will automatically run `npm run build.server` and `npm run build.client`: @@ -52,6 +61,15 @@ npm run build After installing the integration using `npm run qwik add cloudflare-pages`, the project is ready to be deployed to Cloudflare Pages. +If the nodejs version is different in your environment than Cloudflare Pages (v16.20.2) you'll need to add a `NODE_VERSION` environment variable and set the value to the version that you got from running the `node -v` command in your environment: + +```shell +node -v +v20.11.1 +``` + +To do this in Cloudflare go to **Workers & Pages > YOUR_PROJECT > Settings > Environment variables > Production (and Preview) > Add variables > Save ** + Please refer to the [Cloudflare Pages docs](https://developers.cloudflare.com/pages/framework-guides/deploy-a-qwik-site/) for more information on how to deploy your site. Note that you will need a [Cloudflare account](https://dash.cloudflare.com/sign-up?lang=en-US) in order to complete this step. diff --git a/packages/qwik/src/napi/build.rs b/packages/qwik/src/napi/build.rs index 9fc23678893..f8bfd67ec90 100644 --- a/packages/qwik/src/napi/build.rs +++ b/packages/qwik/src/napi/build.rs @@ -1,5 +1,5 @@ extern crate napi_build; fn main() { - napi_build::setup(); + napi_build::setup(); } diff --git a/packages/qwik/src/napi/src/lib.rs b/packages/qwik/src/napi/src/lib.rs index 9c6f7c399e7..faab5c69c83 100644 --- a/packages/qwik/src/napi/src/lib.rs +++ b/packages/qwik/src/napi/src/lib.rs @@ -15,27 +15,27 @@ static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; #[allow(clippy::needless_pass_by_value)] #[js_function(1)] fn transform_fs(ctx: CallContext) -> Result { - let opts = ctx.get::(0)?; - let config: qwik_core::TransformFsOptions = ctx.env.from_js_value(opts)?; + let opts = ctx.get::(0)?; + let config: qwik_core::TransformFsOptions = ctx.env.from_js_value(opts)?; - let result = qwik_core::transform_fs(config).unwrap(); - ctx.env.to_js_value(&result) + let result = qwik_core::transform_fs(config).unwrap(); + ctx.env.to_js_value(&result) } #[allow(clippy::needless_pass_by_value)] #[js_function(1)] fn transform_modules(ctx: CallContext) -> Result { - let opts = ctx.get::(0)?; - let config: qwik_core::TransformModulesOptions = ctx.env.from_js_value(opts)?; + let opts = ctx.get::(0)?; + let config: qwik_core::TransformModulesOptions = ctx.env.from_js_value(opts)?; - let result = qwik_core::transform_modules(config).unwrap(); - ctx.env.to_js_value(&result) + let result = qwik_core::transform_modules(config).unwrap(); + ctx.env.to_js_value(&result) } #[module_exports] fn init(mut exports: JsObject) -> Result<()> { - exports.create_named_method("transform_fs", transform_fs)?; - exports.create_named_method("transform_modules", transform_modules)?; + exports.create_named_method("transform_fs", transform_fs)?; + exports.create_named_method("transform_modules", transform_modules)?; - Ok(()) + Ok(()) } diff --git a/packages/qwik/src/optimizer/cli/src/main.rs b/packages/qwik/src/optimizer/cli/src/main.rs index 2f48a78973c..f677e6eb6b3 100644 --- a/packages/qwik/src/optimizer/cli/src/main.rs +++ b/packages/qwik/src/optimizer/cli/src/main.rs @@ -9,24 +9,24 @@ use path_absolutize::Absolutize; use qwik_core::{transform_fs, EmitMode, EntryStrategy, MinifyMode, TransformFsOptions}; struct OptimizerInput { - glob: Option, - manifest: Option, - core_module: Option, - scope: Option, - src: PathBuf, - dest: PathBuf, - mode: EmitMode, - strategy: EntryStrategy, - transpile_ts: bool, - transpile_jsx: bool, - preserve_filenames: bool, - minify: MinifyMode, - sourcemaps: bool, - explicit_extensions: bool, + glob: Option, + manifest: Option, + core_module: Option, + scope: Option, + src: PathBuf, + dest: PathBuf, + mode: EmitMode, + strategy: EntryStrategy, + transpile_ts: bool, + transpile_jsx: bool, + preserve_filenames: bool, + minify: MinifyMode, + sourcemaps: bool, + explicit_extensions: bool, } fn main() -> Result<(), Box> { - let matches = Command::new("qwik") + let matches = Command::new("qwik") .version("1.0") .arg_required_else_help(true) .subcommand_required(true) @@ -85,85 +85,85 @@ fn main() -> Result<(), Box> { ) .get_matches(); - // You can check for the existence of subcommands, and if found use their - // matches just as you would the top level app - if let Some(matches) = matches.subcommand_matches("optimize") { - // "$ myapp test" was run - let strategy = match matches.value_of("strategy") { - Some("inline") => EntryStrategy::Inline, - Some("hook") => EntryStrategy::Hook, - Some("single") => EntryStrategy::Single, - Some("component") => EntryStrategy::Component, - Some("smart") | None => EntryStrategy::Smart, - _ => panic!("Invalid strategy option"), - }; + // You can check for the existence of subcommands, and if found use their + // matches just as you would the top level app + if let Some(matches) = matches.subcommand_matches("optimize") { + // "$ myapp test" was run + let strategy = match matches.value_of("strategy") { + Some("inline") => EntryStrategy::Inline, + Some("hook") => EntryStrategy::Hook, + Some("single") => EntryStrategy::Single, + Some("component") => EntryStrategy::Component, + Some("smart") | None => EntryStrategy::Smart, + _ => panic!("Invalid strategy option"), + }; - let minify = match matches.value_of("minify") { - Some("none") => MinifyMode::None, - Some("simplify") | None => MinifyMode::Simplify, - _ => panic!("Invalid minify option"), - }; + let minify = match matches.value_of("minify") { + Some("none") => MinifyMode::None, + Some("simplify") | None => MinifyMode::Simplify, + _ => panic!("Invalid minify option"), + }; - let mode = match matches.value_of("mode") { - Some("dev") => EmitMode::Dev, - Some("prod") => EmitMode::Prod, - Some("lib") | None => EmitMode::Lib, - _ => panic!("Invalid mode option"), - }; - optimize(OptimizerInput { - src: matches.value_of_t_or_exit("src"), - dest: matches.value_of_t_or_exit("dest"), - manifest: matches.value_of("manifest").map(|s| s.into()), - core_module: matches.value_of("core_module").map(|s| s.into()), - scope: matches.value_of("scope").map(|s| s.into()), - mode, - glob: None, - strategy, - minify, - explicit_extensions: matches.is_present("extensions"), - transpile_jsx: !matches.is_present("no-jsx"), - transpile_ts: !matches.is_present("no-ts"), - preserve_filenames: matches.is_present("preserve-filenames"), - sourcemaps: matches.is_present("sourcemaps"), - })?; - } - Ok(()) + let mode = match matches.value_of("mode") { + Some("dev") => EmitMode::Dev, + Some("prod") => EmitMode::Prod, + Some("lib") | None => EmitMode::Lib, + _ => panic!("Invalid mode option"), + }; + optimize(OptimizerInput { + src: matches.value_of_t_or_exit("src"), + dest: matches.value_of_t_or_exit("dest"), + manifest: matches.value_of("manifest").map(|s| s.into()), + core_module: matches.value_of("core_module").map(|s| s.into()), + scope: matches.value_of("scope").map(|s| s.into()), + mode, + glob: None, + strategy, + minify, + explicit_extensions: matches.is_present("extensions"), + transpile_jsx: !matches.is_present("no-jsx"), + transpile_ts: !matches.is_present("no-ts"), + preserve_filenames: matches.is_present("preserve-filenames"), + sourcemaps: matches.is_present("sourcemaps"), + })?; + } + Ok(()) } fn optimize( - optimizer_input: OptimizerInput, + optimizer_input: OptimizerInput, ) -> Result> { - let current_dir = std::env::current_dir()?; - let src_dir = current_dir.join(optimizer_input.src).canonicalize()?; + let current_dir = std::env::current_dir()?; + let src_dir = current_dir.join(optimizer_input.src).canonicalize()?; - let result = transform_fs(TransformFsOptions { - src_dir: src_dir.to_string_lossy().to_string(), - vendor_roots: vec![], - glob: optimizer_input.glob, - source_maps: optimizer_input.sourcemaps, - minify: optimizer_input.minify, - transpile_jsx: optimizer_input.transpile_jsx, - transpile_ts: optimizer_input.transpile_ts, - preserve_filenames: optimizer_input.preserve_filenames, - entry_strategy: optimizer_input.strategy, - explicit_extensions: optimizer_input.explicit_extensions, - core_module: optimizer_input.core_module, - root_dir: None, + let result = transform_fs(TransformFsOptions { + src_dir: src_dir.to_string_lossy().to_string(), + vendor_roots: vec![], + glob: optimizer_input.glob, + source_maps: optimizer_input.sourcemaps, + minify: optimizer_input.minify, + transpile_jsx: optimizer_input.transpile_jsx, + transpile_ts: optimizer_input.transpile_ts, + preserve_filenames: optimizer_input.preserve_filenames, + entry_strategy: optimizer_input.strategy, + explicit_extensions: optimizer_input.explicit_extensions, + core_module: optimizer_input.core_module, + root_dir: None, - mode: optimizer_input.mode, - scope: optimizer_input.scope, + mode: optimizer_input.mode, + scope: optimizer_input.scope, - manual_chunks: None, - strip_exports: None, - strip_ctx_name: None, - strip_event_handlers: false, - reg_ctx_name: None, - is_server: None, - })?; + manual_chunks: None, + strip_exports: None, + strip_ctx_name: None, + strip_event_handlers: false, + reg_ctx_name: None, + is_server: None, + })?; - result.write_to_fs( - ¤t_dir.join(optimizer_input.dest).absolutize()?, - optimizer_input.manifest, - )?; - Ok(result) + result.write_to_fs( + ¤t_dir.join(optimizer_input.dest).absolutize()?, + optimizer_input.manifest, + )?; + Ok(result) } diff --git a/packages/qwik/src/optimizer/core/benches/transform.rs b/packages/qwik/src/optimizer/core/benches/transform.rs index 206a037e000..c4e12ccca1e 100644 --- a/packages/qwik/src/optimizer/core/benches/transform.rs +++ b/packages/qwik/src/optimizer/core/benches/transform.rs @@ -7,7 +7,7 @@ use test::Bencher; #[bench] fn transform_todo_app(b: &mut Bencher) { - b.iter(|| { + b.iter(|| { let code = r#" import { Fragment, diff --git a/packages/qwik/src/optimizer/core/src/add_side_effect.rs b/packages/qwik/src/optimizer/core/src/add_side_effect.rs index 20706cbbf95..53c85b7e469 100644 --- a/packages/qwik/src/optimizer/core/src/add_side_effect.rs +++ b/packages/qwik/src/optimizer/core/src/add_side_effect.rs @@ -10,56 +10,56 @@ use swc_ecmascript::ast; use swc_ecmascript::visit::{VisitMut, VisitMutWith}; pub struct SideEffectVisitor<'a> { - global_collector: &'a GlobalCollect, - imports: HashSet, - path_data: &'a PathData, - src_dir: &'a Path, + global_collector: &'a GlobalCollect, + imports: HashSet, + path_data: &'a PathData, + src_dir: &'a Path, } impl<'a> SideEffectVisitor<'a> { - pub fn new( - global_collector: &'a GlobalCollect, - path_data: &'a PathData, - src_dir: &'a Path, - ) -> Self { - Self { - global_collector, - path_data, - src_dir, - imports: HashSet::new(), - } - } + pub fn new( + global_collector: &'a GlobalCollect, + path_data: &'a PathData, + src_dir: &'a Path, + ) -> Self { + Self { + global_collector, + path_data, + src_dir, + imports: HashSet::new(), + } + } } impl<'a> VisitMut for SideEffectVisitor<'a> { - fn visit_mut_import_decl(&mut self, node: &mut ast::ImportDecl) { - if node.src.value.starts_with('.') { - self.imports.insert(node.src.value.clone()); - } - } - fn visit_mut_module(&mut self, node: &mut ast::Module) { - node.visit_mut_children_with(self); - let mut imports: Vec<_> = self.global_collector.imports.values().collect(); - imports.sort_by_key(|i| i.source.clone()); + fn visit_mut_import_decl(&mut self, node: &mut ast::ImportDecl) { + if node.src.value.starts_with('.') { + self.imports.insert(node.src.value.clone()); + } + } + fn visit_mut_module(&mut self, node: &mut ast::Module) { + node.visit_mut_children_with(self); + let mut imports: Vec<_> = self.global_collector.imports.values().collect(); + imports.sort_by_key(|i| i.source.clone()); - for import in imports { - if import.source.starts_with('.') && !self.imports.contains(&import.source) { - let abs_dir = self.path_data.abs_dir.to_slash_lossy(); - let relative = relative_path::RelativePath::new(&abs_dir); - let final_path = relative.join(import.source.as_ref()).normalize(); - if final_path.starts_with(self.src_dir.to_str().unwrap()) { - node.body.insert( - 0, - ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import(ast::ImportDecl { - asserts: None, - span: DUMMY_SP, - specifiers: vec![], - type_only: false, - src: Box::new(ast::Str::from(import.source.clone())), - })), - ); - } - } - } - } + for import in imports { + if import.source.starts_with('.') && !self.imports.contains(&import.source) { + let abs_dir = self.path_data.abs_dir.to_slash_lossy(); + let relative = relative_path::RelativePath::new(&abs_dir); + let final_path = relative.join(import.source.as_ref()).normalize(); + if final_path.starts_with(self.src_dir.to_str().unwrap()) { + node.body.insert( + 0, + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import(ast::ImportDecl { + asserts: None, + span: DUMMY_SP, + specifiers: vec![], + type_only: false, + src: Box::new(ast::Str::from(import.source.clone())), + })), + ); + } + } + } + } } diff --git a/packages/qwik/src/optimizer/core/src/clean_side_effects.rs b/packages/qwik/src/optimizer/core/src/clean_side_effects.rs index d257ab152bd..3f22ecb9bb4 100644 --- a/packages/qwik/src/optimizer/core/src/clean_side_effects.rs +++ b/packages/qwik/src/optimizer/core/src/clean_side_effects.rs @@ -2,58 +2,58 @@ use swc_common::{Mark, Spanned}; use swc_ecmascript::ast; use swc_ecmascript::visit::VisitMut; pub struct Treeshaker { - pub marker: CleanMarker, - pub cleaner: CleanSideEffects, + pub marker: CleanMarker, + pub cleaner: CleanSideEffects, } pub struct CleanSideEffects { - pub did_drop: bool, - pub mark: Mark, + pub did_drop: bool, + pub mark: Mark, } pub struct CleanMarker { - pub mark: Mark, + pub mark: Mark, } impl Treeshaker { - pub fn new() -> Self { - let mark = Mark::new(); - Self { - marker: CleanMarker { mark }, - cleaner: CleanSideEffects { - did_drop: false, - mark, - }, - } - } + pub fn new() -> Self { + let mark = Mark::new(); + Self { + marker: CleanMarker { mark }, + cleaner: CleanSideEffects { + did_drop: false, + mark, + }, + } + } } impl VisitMut for CleanMarker { - fn visit_mut_module_item(&mut self, node: &mut ast::ModuleItem) { - if let ast::ModuleItem::Stmt(ast::Stmt::Expr(expr)) = node { - expr.span = expr.span.apply_mark(self.mark); - } - } + fn visit_mut_module_item(&mut self, node: &mut ast::ModuleItem) { + if let ast::ModuleItem::Stmt(ast::Stmt::Expr(expr)) = node { + expr.span = expr.span.apply_mark(self.mark); + } + } } impl VisitMut for CleanSideEffects { - fn visit_mut_module(&mut self, node: &mut ast::Module) { - let it = node.body.extract_if(|item| { - if item.span().has_mark(self.mark) { - return false; - } - match item { - ast::ModuleItem::Stmt(ast::Stmt::Expr(expr)) => match *expr.expr { - ast::Expr::New(_) | ast::Expr::Call(_) => { - self.did_drop = true; - true - } - _ => false, - }, - _ => false, - } - }); - // Consume the iterator to force the extraction. - for _ in it {} - } + fn visit_mut_module(&mut self, node: &mut ast::Module) { + let it = node.body.extract_if(|item| { + if item.span().has_mark(self.mark) { + return false; + } + match item { + ast::ModuleItem::Stmt(ast::Stmt::Expr(expr)) => match *expr.expr { + ast::Expr::New(_) | ast::Expr::Call(_) => { + self.did_drop = true; + true + } + _ => false, + }, + _ => false, + } + }); + // Consume the iterator to force the extraction. + for _ in it {} + } } diff --git a/packages/qwik/src/optimizer/core/src/code_move.rs b/packages/qwik/src/optimizer/core/src/code_move.rs index 310e14e8d02..0100fde8e08 100644 --- a/packages/qwik/src/optimizer/core/src/code_move.rs +++ b/packages/qwik/src/optimizer/core/src/code_move.rs @@ -1,7 +1,7 @@ use crate::collector::{new_ident_from_id, GlobalCollect, Id, ImportKind}; use crate::parse::{ - emit_source_code, might_need_handle_watch, HookAnalysis, PathData, TransformModule, - TransformOutput, + emit_source_code, might_need_handle_watch, HookAnalysis, PathData, TransformModule, + TransformOutput, }; use crate::transform::{add_handle_watch, create_synthetic_named_import}; use crate::words::*; @@ -18,416 +18,416 @@ use swc_ecmascript::ast; use swc_ecmascript::utils::private_ident; macro_rules! id { - ($ident: expr) => { - ($ident.sym.clone(), $ident.span.ctxt()) - }; + ($ident: expr) => { + ($ident.sym.clone(), $ident.span.ctxt()) + }; } pub struct NewModuleCtx<'a> { - pub expr: Box, - pub path: &'a PathData, - pub name: &'a str, - pub local_idents: &'a [Id], - pub scoped_idents: &'a [Id], - pub global: &'a GlobalCollect, - pub core_module: &'a JsWord, - pub is_entry: bool, - pub need_handle_watch: bool, - pub need_transform: bool, - pub explicit_extensions: bool, - pub leading_comments: SingleThreadedCommentsMap, - pub trailing_comments: SingleThreadedCommentsMap, + pub expr: Box, + pub path: &'a PathData, + pub name: &'a str, + pub local_idents: &'a [Id], + pub scoped_idents: &'a [Id], + pub global: &'a GlobalCollect, + pub core_module: &'a JsWord, + pub is_entry: bool, + pub need_handle_watch: bool, + pub need_transform: bool, + pub explicit_extensions: bool, + pub leading_comments: SingleThreadedCommentsMap, + pub trailing_comments: SingleThreadedCommentsMap, } pub fn new_module(ctx: NewModuleCtx) -> Result<(ast::Module, SingleThreadedComments), Error> { - let comments = SingleThreadedComments::from_leading_and_trailing( - ctx.leading_comments, - ctx.trailing_comments, - ); - let max_cap = ctx.global.imports.len() + ctx.global.exports.len(); - let mut module = ast::Module { - span: DUMMY_SP, - body: Vec::with_capacity(max_cap), - shebang: None, - }; + let comments = SingleThreadedComments::from_leading_and_trailing( + ctx.leading_comments, + ctx.trailing_comments, + ); + let max_cap = ctx.global.imports.len() + ctx.global.exports.len(); + let mut module = ast::Module { + span: DUMMY_SP, + body: Vec::with_capacity(max_cap), + shebang: None, + }; - let has_scoped_idents = ctx.need_transform && !ctx.scoped_idents.is_empty(); - let use_lexical_scope = if has_scoped_idents { - let new_local = id!(private_ident!(&USE_LEXICAL_SCOPE.clone())); - module - .body - .push(create_synthetic_named_import(&new_local, ctx.core_module)); - Some(new_local) - } else { - None - }; + let has_scoped_idents = ctx.need_transform && !ctx.scoped_idents.is_empty(); + let use_lexical_scope = if has_scoped_idents { + let new_local = id!(private_ident!(&USE_LEXICAL_SCOPE.clone())); + module + .body + .push(create_synthetic_named_import(&new_local, ctx.core_module)); + Some(new_local) + } else { + None + }; - for id in ctx.local_idents { - if let Some(import) = ctx.global.imports.get(id) { - let specifier = match import.kind { - ImportKind::Named => ast::ImportSpecifier::Named(ast::ImportNamedSpecifier { - is_type_only: false, - span: DUMMY_SP, - imported: if import.specifier == id.0 { - None - } else { - Some(ast::ModuleExportName::Ident(ast::Ident::new( - import.specifier.clone(), - DUMMY_SP, - ))) - }, - local: new_ident_from_id(id), - }), - ImportKind::Default => ast::ImportSpecifier::Default(ast::ImportDefaultSpecifier { - span: DUMMY_SP, - local: new_ident_from_id(id), - }), - ImportKind::All => ast::ImportSpecifier::Namespace(ast::ImportStarAsSpecifier { - span: DUMMY_SP, - local: new_ident_from_id(id), - }), - }; - module - .body - .push(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import( - ast::ImportDecl { - span: DUMMY_SP, - type_only: false, - asserts: import.asserts.clone(), - src: Box::new(ast::Str { - span: DUMMY_SP, - value: fix_path( - &ctx.path.abs_dir, - &ctx.path.base_dir, - import.source.as_ref(), - )?, - raw: None, - }), - specifiers: vec![specifier], - }, - ))); - } else if let Some(export) = ctx.global.exports.get(id) { - let filename = if ctx.explicit_extensions { - &ctx.path.file_name - } else { - &ctx.path.file_stem - }; - let imported = export - .as_ref() - .map(|e| ast::ModuleExportName::Ident(ast::Ident::new(e.clone(), DUMMY_SP))); - module - .body - .push(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import( - ast::ImportDecl { - span: DUMMY_SP, - type_only: false, - asserts: None, - src: Box::new(ast::Str { - span: DUMMY_SP, - value: fix_path( - &ctx.path.abs_dir, - &ctx.path.base_dir, - &format!("./{}", filename), - )?, - raw: None, - }), - specifiers: vec![ast::ImportSpecifier::Named(ast::ImportNamedSpecifier { - is_type_only: false, - span: DUMMY_SP, - imported, - local: new_ident_from_id(id), - })], - }, - ))); - } - } + for id in ctx.local_idents { + if let Some(import) = ctx.global.imports.get(id) { + let specifier = match import.kind { + ImportKind::Named => ast::ImportSpecifier::Named(ast::ImportNamedSpecifier { + is_type_only: false, + span: DUMMY_SP, + imported: if import.specifier == id.0 { + None + } else { + Some(ast::ModuleExportName::Ident(ast::Ident::new( + import.specifier.clone(), + DUMMY_SP, + ))) + }, + local: new_ident_from_id(id), + }), + ImportKind::Default => ast::ImportSpecifier::Default(ast::ImportDefaultSpecifier { + span: DUMMY_SP, + local: new_ident_from_id(id), + }), + ImportKind::All => ast::ImportSpecifier::Namespace(ast::ImportStarAsSpecifier { + span: DUMMY_SP, + local: new_ident_from_id(id), + }), + }; + module + .body + .push(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import( + ast::ImportDecl { + span: DUMMY_SP, + type_only: false, + asserts: import.asserts.clone(), + src: Box::new(ast::Str { + span: DUMMY_SP, + value: fix_path( + &ctx.path.abs_dir, + &ctx.path.base_dir, + import.source.as_ref(), + )?, + raw: None, + }), + specifiers: vec![specifier], + }, + ))); + } else if let Some(export) = ctx.global.exports.get(id) { + let filename = if ctx.explicit_extensions { + &ctx.path.file_name + } else { + &ctx.path.file_stem + }; + let imported = export + .as_ref() + .map(|e| ast::ModuleExportName::Ident(ast::Ident::new(e.clone(), DUMMY_SP))); + module + .body + .push(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import( + ast::ImportDecl { + span: DUMMY_SP, + type_only: false, + asserts: None, + src: Box::new(ast::Str { + span: DUMMY_SP, + value: fix_path( + &ctx.path.abs_dir, + &ctx.path.base_dir, + &format!("./{}", filename), + )?, + raw: None, + }), + specifiers: vec![ast::ImportSpecifier::Named(ast::ImportNamedSpecifier { + is_type_only: false, + span: DUMMY_SP, + imported, + local: new_ident_from_id(id), + })], + }, + ))); + } + } - let expr = if let Some(use_lexical_scope) = use_lexical_scope { - Box::new(transform_function_expr( - *ctx.expr, - &use_lexical_scope, - ctx.scoped_idents, - )) - } else { - ctx.expr - }; + let expr = if let Some(use_lexical_scope) = use_lexical_scope { + Box::new(transform_function_expr( + *ctx.expr, + &use_lexical_scope, + ctx.scoped_idents, + )) + } else { + ctx.expr + }; - module.body.push(create_named_export(expr, ctx.name)); - if ctx.need_handle_watch { - // Inject qwik internal import - add_handle_watch(&mut module.body, ctx.core_module); - } - Ok((module, comments)) + module.body.push(create_named_export(expr, ctx.name)); + if ctx.need_handle_watch { + // Inject qwik internal import + add_handle_watch(&mut module.body, ctx.core_module); + } + Ok((module, comments)) } pub fn fix_path, D: AsRef>( - src: S, - dest: D, - ident: &str, + src: S, + dest: D, + ident: &str, ) -> Result { - let src = src.as_ref(); - let dest = dest.as_ref(); - if ident.starts_with('.') { - let diff = pathdiff::diff_paths(src, dest); + let src = src.as_ref(); + let dest = dest.as_ref(); + if ident.starts_with('.') { + let diff = pathdiff::diff_paths(src, dest); - if let Some(diff) = diff { - let normalize = diff.to_slash_lossy(); - let relative = relative_path::RelativePath::new(&normalize); - let final_path = relative.join(ident).normalize(); - let final_str = final_path.as_str(); - return Ok(if final_str.starts_with('.') { - JsWord::from(final_str) - } else { - JsWord::from(format!("./{}", final_str)) - }); - } - } + if let Some(diff) = diff { + let normalize = diff.to_slash_lossy(); + let relative = relative_path::RelativePath::new(&normalize); + let final_path = relative.join(ident).normalize(); + let final_str = final_path.as_str(); + return Ok(if final_str.starts_with('.') { + JsWord::from(final_str) + } else { + JsWord::from(format!("./{}", final_str)) + }); + } + } - Ok(JsWord::from(ident)) + Ok(JsWord::from(ident)) } fn create_named_export(expr: Box, name: &str) -> ast::ModuleItem { - ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDecl(ast::ExportDecl { - span: DUMMY_SP, - decl: ast::Decl::Var(Box::new(ast::VarDecl { - span: DUMMY_SP, - kind: ast::VarDeclKind::Const, - declare: false, - decls: vec![ast::VarDeclarator { - span: DUMMY_SP, - definite: false, - name: ast::Pat::Ident(ast::BindingIdent::from(ast::Ident::new( - JsWord::from(name), - DUMMY_SP, - ))), - init: Some(expr), - }], - })), - })) + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDecl(ast::ExportDecl { + span: DUMMY_SP, + decl: ast::Decl::Var(Box::new(ast::VarDecl { + span: DUMMY_SP, + kind: ast::VarDeclKind::Const, + declare: false, + decls: vec![ast::VarDeclarator { + span: DUMMY_SP, + definite: false, + name: ast::Pat::Ident(ast::BindingIdent::from(ast::Ident::new( + JsWord::from(name), + DUMMY_SP, + ))), + init: Some(expr), + }], + })), + })) } #[test] fn test_fix_path() { - assert_eq!( - fix_path("src", "", "./state.qwik.mjs").unwrap(), - JsWord::from("./src/state.qwik.mjs") - ); + assert_eq!( + fix_path("src", "", "./state.qwik.mjs").unwrap(), + JsWord::from("./src/state.qwik.mjs") + ); - assert_eq!( - fix_path("src/path", "", "./state").unwrap(), - JsWord::from("./src/path/state") - ); + assert_eq!( + fix_path("src/path", "", "./state").unwrap(), + JsWord::from("./src/path/state") + ); - assert_eq!( - fix_path("src", "", "../state").unwrap(), - JsWord::from("./state") - ); - assert_eq!( - fix_path("a", "a", "./state").unwrap(), - JsWord::from("./state") - ); + assert_eq!( + fix_path("src", "", "../state").unwrap(), + JsWord::from("./state") + ); + assert_eq!( + fix_path("a", "a", "./state").unwrap(), + JsWord::from("./state") + ); } pub fn generate_entries( - mut output: TransformOutput, - core_module: &JsWord, - explicit_extensions: bool, - root_dir: Option<&Path>, + mut output: TransformOutput, + core_module: &JsWord, + explicit_extensions: bool, + root_dir: Option<&Path>, ) -> Result { - let source_map = Lrc::new(SourceMap::default()); - let mut entries_map: BTreeMap<&str, Vec<&HookAnalysis>> = BTreeMap::new(); - let mut new_modules = Vec::with_capacity(output.modules.len()); - { - let hooks: Vec<&HookAnalysis> = output.modules.iter().flat_map(|m| &m.hook).collect(); - for hook in hooks { - if let Some(ref e) = hook.entry { - entries_map - .entry(e.as_ref()) - .or_insert_with(Vec::new) - .push(hook); - } - } + let source_map = Lrc::new(SourceMap::default()); + let mut entries_map: BTreeMap<&str, Vec<&HookAnalysis>> = BTreeMap::new(); + let mut new_modules = Vec::with_capacity(output.modules.len()); + { + let hooks: Vec<&HookAnalysis> = output.modules.iter().flat_map(|m| &m.hook).collect(); + for hook in hooks { + if let Some(ref e) = hook.entry { + entries_map + .entry(e.as_ref()) + .or_insert_with(Vec::new) + .push(hook); + } + } - for (entry, hooks) in &entries_map { - let module = new_entry_module(hooks, core_module, explicit_extensions); - let (code, map) = - emit_source_code(Lrc::clone(&source_map), None, &module, root_dir, false) - .context("Emitting source code")?; - new_modules.push(TransformModule { - path: [entry, ".js"].concat(), - code, - map, - is_entry: true, - hook: None, - order: 0, - }); - } - } - output.modules.append(&mut new_modules); + for (entry, hooks) in &entries_map { + let module = new_entry_module(hooks, core_module, explicit_extensions); + let (code, map) = + emit_source_code(Lrc::clone(&source_map), None, &module, root_dir, false) + .context("Emitting source code")?; + new_modules.push(TransformModule { + path: [entry, ".js"].concat(), + code, + map, + is_entry: true, + hook: None, + order: 0, + }); + } + } + output.modules.append(&mut new_modules); - Ok(output) + Ok(output) } fn new_entry_module( - hooks: &[&HookAnalysis], - core_module: &JsWord, - explicit_extensions: bool, + hooks: &[&HookAnalysis], + core_module: &JsWord, + explicit_extensions: bool, ) -> ast::Module { - let mut module = ast::Module { - span: DUMMY_SP, - body: Vec::with_capacity(hooks.len()), - shebang: None, - }; - let mut need_handle_watch = false; - for hook in hooks { - let mut src = ["./", &hook.canonical_filename].concat(); - if explicit_extensions { - src = src + "." + hook.extension.as_ref(); - } - if might_need_handle_watch(&hook.ctx_kind, &hook.ctx_name) { - need_handle_watch = true; - } - module - .body - .push(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportNamed( - ast::NamedExport { - span: DUMMY_SP, - type_only: false, - asserts: None, - src: Some(Box::new(ast::Str { - span: DUMMY_SP, - value: JsWord::from(src), - raw: None, - })), - specifiers: vec![ast::ExportSpecifier::Named(ast::ExportNamedSpecifier { - is_type_only: false, - span: DUMMY_SP, - orig: ast::ModuleExportName::Ident(ast::Ident::new( - hook.name.clone(), - DUMMY_SP, - )), - exported: None, - })], - }, - ))); - } - if need_handle_watch { - add_handle_watch(&mut module.body, core_module); - } - module + let mut module = ast::Module { + span: DUMMY_SP, + body: Vec::with_capacity(hooks.len()), + shebang: None, + }; + let mut need_handle_watch = false; + for hook in hooks { + let mut src = ["./", &hook.canonical_filename].concat(); + if explicit_extensions { + src = src + "." + hook.extension.as_ref(); + } + if might_need_handle_watch(&hook.ctx_kind, &hook.ctx_name) { + need_handle_watch = true; + } + module + .body + .push(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportNamed( + ast::NamedExport { + span: DUMMY_SP, + type_only: false, + asserts: None, + src: Some(Box::new(ast::Str { + span: DUMMY_SP, + value: JsWord::from(src), + raw: None, + })), + specifiers: vec![ast::ExportSpecifier::Named(ast::ExportNamedSpecifier { + is_type_only: false, + span: DUMMY_SP, + orig: ast::ModuleExportName::Ident(ast::Ident::new( + hook.name.clone(), + DUMMY_SP, + )), + exported: None, + })], + }, + ))); + } + if need_handle_watch { + add_handle_watch(&mut module.body, core_module); + } + module } pub fn transform_function_expr( - expr: ast::Expr, - use_lexical_scope: &Id, - scoped_idents: &[Id], + expr: ast::Expr, + use_lexical_scope: &Id, + scoped_idents: &[Id], ) -> ast::Expr { - match expr { - ast::Expr::Arrow(node) => { - ast::Expr::Arrow(transform_arrow_fn(node, use_lexical_scope, scoped_idents)) - } - ast::Expr::Fn(node) => ast::Expr::Fn(transform_fn(node, use_lexical_scope, scoped_idents)), - _ => expr, - } + match expr { + ast::Expr::Arrow(node) => { + ast::Expr::Arrow(transform_arrow_fn(node, use_lexical_scope, scoped_idents)) + } + ast::Expr::Fn(node) => ast::Expr::Fn(transform_fn(node, use_lexical_scope, scoped_idents)), + _ => expr, + } } fn transform_arrow_fn( - arrow: ast::ArrowExpr, - use_lexical_scope: &Id, - scoped_idents: &[Id], + arrow: ast::ArrowExpr, + use_lexical_scope: &Id, + scoped_idents: &[Id], ) -> ast::ArrowExpr { - match arrow.body { - box ast::BlockStmtOrExpr::BlockStmt(mut block) => { - let mut stmts = Vec::with_capacity(1 + block.stmts.len()); - stmts.push(create_use_lexical_scope(use_lexical_scope, scoped_idents)); - stmts.append(&mut block.stmts); - ast::ArrowExpr { - body: Box::new(ast::BlockStmtOrExpr::BlockStmt(ast::BlockStmt { - span: DUMMY_SP, - stmts, - })), - ..arrow - } - } - box ast::BlockStmtOrExpr::Expr(expr) => { - let mut stmts = Vec::with_capacity(2); - if !scoped_idents.is_empty() { - stmts.push(create_use_lexical_scope(use_lexical_scope, scoped_idents)); - } - stmts.push(create_return_stmt(expr)); - ast::ArrowExpr { - body: Box::new(ast::BlockStmtOrExpr::BlockStmt(ast::BlockStmt { - span: DUMMY_SP, - stmts, - })), - ..arrow - } - } - } + match arrow.body { + box ast::BlockStmtOrExpr::BlockStmt(mut block) => { + let mut stmts = Vec::with_capacity(1 + block.stmts.len()); + stmts.push(create_use_lexical_scope(use_lexical_scope, scoped_idents)); + stmts.append(&mut block.stmts); + ast::ArrowExpr { + body: Box::new(ast::BlockStmtOrExpr::BlockStmt(ast::BlockStmt { + span: DUMMY_SP, + stmts, + })), + ..arrow + } + } + box ast::BlockStmtOrExpr::Expr(expr) => { + let mut stmts = Vec::with_capacity(2); + if !scoped_idents.is_empty() { + stmts.push(create_use_lexical_scope(use_lexical_scope, scoped_idents)); + } + stmts.push(create_return_stmt(expr)); + ast::ArrowExpr { + body: Box::new(ast::BlockStmtOrExpr::BlockStmt(ast::BlockStmt { + span: DUMMY_SP, + stmts, + })), + ..arrow + } + } + } } fn transform_fn(node: ast::FnExpr, use_lexical_scope: &Id, scoped_idents: &[Id]) -> ast::FnExpr { - let mut stmts = Vec::with_capacity( - 1 + node - .function - .body - .as_ref() - .map_or(0, |body| body.stmts.len()), - ); - if !scoped_idents.is_empty() { - stmts.push(create_use_lexical_scope(use_lexical_scope, scoped_idents)); - } - if let Some(mut body) = node.function.body { - stmts.append(&mut body.stmts); - } - ast::FnExpr { - function: Box::new(ast::Function { - body: Some(ast::BlockStmt { - span: DUMMY_SP, - stmts, - }), - ..*node.function - }), - ..node - } + let mut stmts = Vec::with_capacity( + 1 + node + .function + .body + .as_ref() + .map_or(0, |body| body.stmts.len()), + ); + if !scoped_idents.is_empty() { + stmts.push(create_use_lexical_scope(use_lexical_scope, scoped_idents)); + } + if let Some(mut body) = node.function.body { + stmts.append(&mut body.stmts); + } + ast::FnExpr { + function: Box::new(ast::Function { + body: Some(ast::BlockStmt { + span: DUMMY_SP, + stmts, + }), + ..*node.function + }), + ..node + } } pub const fn create_return_stmt(expr: Box) -> ast::Stmt { - ast::Stmt::Return(ast::ReturnStmt { - arg: Some(expr), - span: DUMMY_SP, - }) + ast::Stmt::Return(ast::ReturnStmt { + arg: Some(expr), + span: DUMMY_SP, + }) } fn create_use_lexical_scope(use_lexical_scope: &Id, scoped_idents: &[Id]) -> ast::Stmt { - ast::Stmt::Decl(ast::Decl::Var(Box::new(ast::VarDecl { - span: DUMMY_SP, - declare: false, - kind: ast::VarDeclKind::Const, - decls: vec![ast::VarDeclarator { - definite: false, - span: DUMMY_SP, - init: Some(Box::new(ast::Expr::Call(ast::CallExpr { - callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id( - use_lexical_scope, - )))), - span: DUMMY_SP, - type_args: None, - args: vec![], - }))), - name: ast::Pat::Array(ast::ArrayPat { - span: DUMMY_SP, - optional: false, - type_ann: None, - elems: scoped_idents - .iter() - .map(|id| { - Some(ast::Pat::Ident(ast::BindingIdent::from(new_ident_from_id( - id, - )))) - }) - .collect(), - }), - }], - }))) + ast::Stmt::Decl(ast::Decl::Var(Box::new(ast::VarDecl { + span: DUMMY_SP, + declare: false, + kind: ast::VarDeclKind::Const, + decls: vec![ast::VarDeclarator { + definite: false, + span: DUMMY_SP, + init: Some(Box::new(ast::Expr::Call(ast::CallExpr { + callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id( + use_lexical_scope, + )))), + span: DUMMY_SP, + type_args: None, + args: vec![], + }))), + name: ast::Pat::Array(ast::ArrayPat { + span: DUMMY_SP, + optional: false, + type_ann: None, + elems: scoped_idents + .iter() + .map(|id| { + Some(ast::Pat::Ident(ast::BindingIdent::from(new_ident_from_id( + id, + )))) + }) + .collect(), + }), + }], + }))) } diff --git a/packages/qwik/src/optimizer/core/src/collector.rs b/packages/qwik/src/optimizer/core/src/collector.rs index a0274b0ac40..2197161ffe2 100644 --- a/packages/qwik/src/optimizer/core/src/collector.rs +++ b/packages/qwik/src/optimizer/core/src/collector.rs @@ -7,429 +7,429 @@ use swc_ecmascript::utils::private_ident; use swc_ecmascript::visit::{noop_visit_type, visit_expr, visit_stmt, Visit, VisitWith}; macro_rules! id { - ($ident: expr) => { - ($ident.sym.clone(), $ident.span.ctxt()) - }; + ($ident: expr) => { + ($ident.sym.clone(), $ident.span.ctxt()) + }; } pub type Id = (JsWord, SyntaxContext); pub fn new_ident_from_id(id: &Id) -> ast::Ident { - ast::Ident::new( - id.0.clone(), - Span { - lo: BytePos(0), - hi: BytePos(0), - ctxt: id.1, - }, - ) + ast::Ident::new( + id.0.clone(), + Span { + lo: BytePos(0), + hi: BytePos(0), + ctxt: id.1, + }, + ) } #[derive(Eq, PartialEq, Clone, Copy)] pub enum ImportKind { - Named, - All, - Default, + Named, + All, + Default, } #[derive(Clone)] pub struct Import { - pub source: JsWord, - pub specifier: JsWord, - pub kind: ImportKind, - pub synthetic: bool, - pub asserts: Option>, + pub source: JsWord, + pub specifier: JsWord, + pub kind: ImportKind, + pub synthetic: bool, + pub asserts: Option>, } pub struct GlobalCollect { - pub synthetic: Vec<(Id, Import)>, - pub imports: HashMap, - pub exports: HashMap>, - pub root: HashMap, + pub synthetic: Vec<(Id, Import)>, + pub imports: HashMap, + pub exports: HashMap>, + pub root: HashMap, - rev_imports: HashMap<(JsWord, JsWord), Id>, - in_export_decl: bool, + rev_imports: HashMap<(JsWord, JsWord), Id>, + in_export_decl: bool, } pub fn global_collect(module: &ast::Module) -> GlobalCollect { - let mut collect = GlobalCollect { - synthetic: vec![], - imports: HashMap::with_capacity(16), - exports: HashMap::with_capacity(16), - - root: HashMap::with_capacity(16), - rev_imports: HashMap::with_capacity(16), - - in_export_decl: false, - }; - module.visit_with(&mut collect); - collect + let mut collect = GlobalCollect { + synthetic: vec![], + imports: HashMap::with_capacity(16), + exports: HashMap::with_capacity(16), + + root: HashMap::with_capacity(16), + rev_imports: HashMap::with_capacity(16), + + in_export_decl: false, + }; + module.visit_with(&mut collect); + collect } impl GlobalCollect { - pub fn get_imported_local(&self, specifier: &JsWord, source: &JsWord) -> Option { - self.imports - .iter() - .find(|(_, import)| &import.specifier == specifier && &import.source == source) - .map(|s| s.0.clone()) - } - - pub fn is_global(&self, local: &Id) -> bool { - if self.imports.contains_key(local) { - return true; - } - if self.exports.contains_key(local) { - return true; - } - if self.root.contains_key(local) { - return true; - } - false - } - - pub fn import(&mut self, specifier: &JsWord, source: &JsWord) -> Id { - self.rev_imports - .get(&(specifier.clone(), source.clone())) - .cloned() - .map_or_else( - || { - let local = id!(private_ident!(specifier)); - self.add_import( - local.clone(), - Import { - source: source.clone(), - specifier: specifier.clone(), - kind: ImportKind::Named, - synthetic: true, - asserts: None, - }, - ); - local - }, - |local| local, - ) - } - - pub fn add_import(&mut self, local: Id, import: Import) { - if import.synthetic { - self.synthetic.push((local.clone(), import.clone())); - } - self.rev_imports.insert( - (import.specifier.clone(), import.source.clone()), - local.clone(), - ); - self.imports.insert(local, import); - } - - pub fn add_export(&mut self, local: Id, exported: Option) -> bool { - if let std::collections::hash_map::Entry::Vacant(e) = self.exports.entry(local) { - e.insert(exported); - true - } else { - false - } - } + pub fn get_imported_local(&self, specifier: &JsWord, source: &JsWord) -> Option { + self.imports + .iter() + .find(|(_, import)| &import.specifier == specifier && &import.source == source) + .map(|s| s.0.clone()) + } + + pub fn is_global(&self, local: &Id) -> bool { + if self.imports.contains_key(local) { + return true; + } + if self.exports.contains_key(local) { + return true; + } + if self.root.contains_key(local) { + return true; + } + false + } + + pub fn import(&mut self, specifier: &JsWord, source: &JsWord) -> Id { + self.rev_imports + .get(&(specifier.clone(), source.clone())) + .cloned() + .map_or_else( + || { + let local = id!(private_ident!(specifier)); + self.add_import( + local.clone(), + Import { + source: source.clone(), + specifier: specifier.clone(), + kind: ImportKind::Named, + synthetic: true, + asserts: None, + }, + ); + local + }, + |local| local, + ) + } + + pub fn add_import(&mut self, local: Id, import: Import) { + if import.synthetic { + self.synthetic.push((local.clone(), import.clone())); + } + self.rev_imports.insert( + (import.specifier.clone(), import.source.clone()), + local.clone(), + ); + self.imports.insert(local, import); + } + + pub fn add_export(&mut self, local: Id, exported: Option) -> bool { + if let std::collections::hash_map::Entry::Vacant(e) = self.exports.entry(local) { + e.insert(exported); + true + } else { + false + } + } } impl Visit for GlobalCollect { - noop_visit_type!(); - - fn visit_module_item(&mut self, node: &ast::ModuleItem) { - if let ast::ModuleItem::Stmt(ast::Stmt::Decl(decl)) = node { - match decl { - ast::Decl::Fn(function) => { - self.root.insert(id!(function.ident), function.ident.span); - } - ast::Decl::Class(class) => { - self.root.insert(id!(class.ident), class.ident.span); - } - ast::Decl::Var(var) => { - for decl in &var.decls { - let mut identifiers: Vec<(Id, Span)> = vec![]; - collect_from_pat(&decl.name, &mut identifiers); - self.root.extend(identifiers.into_iter()); - } - } - ast::Decl::TsEnum(enu) => { - self.root.insert(id!(enu.id), enu.id.span); - } - _ => {} - } - } else { - node.visit_children_with(self); - } - } - - fn visit_import_decl(&mut self, node: &ast::ImportDecl) { - for specifier in &node.specifiers { - match specifier { - ast::ImportSpecifier::Named(named) => { - let imported = match &named.imported { - Some(ast::ModuleExportName::Ident(ident)) => ident.sym.clone(), - _ => named.local.sym.clone(), - }; - self.add_import( - id!(named.local), - Import { - source: node.src.value.clone(), - specifier: imported, - kind: ImportKind::Named, - synthetic: false, - asserts: node.asserts.clone(), - }, - ); - } - ast::ImportSpecifier::Default(default) => { - self.add_import( - id!(default.local), - Import { - source: node.src.value.clone(), - specifier: js_word!("default"), - kind: ImportKind::Default, - synthetic: false, - asserts: node.asserts.clone(), - }, - ); - } - ast::ImportSpecifier::Namespace(namespace) => { - self.add_import( - id!(namespace.local), - Import { - source: node.src.value.clone(), - specifier: "*".into(), - kind: ImportKind::All, - synthetic: false, - asserts: node.asserts.clone(), - }, - ); - } - } - } - } - - fn visit_named_export(&mut self, node: &ast::NamedExport) { - if node.src.is_some() { - return; - } - - for specifier in &node.specifiers { - match specifier { - ast::ExportSpecifier::Named(named) => { - let local = match &named.orig { - ast::ModuleExportName::Ident(ident) => Some(id!(ident)), - _ => None, - }; - let exported = match &named.exported { - Some(ast::ModuleExportName::Ident(exported)) => Some(exported.sym.clone()), - _ => None, - }; - if let Some(local) = local { - self.add_export(local, exported); - } - } - ast::ExportSpecifier::Default(default) => { - self.exports - .entry(id!(default.exported)) - .or_insert(Some(js_word!("default"))); - } - ast::ExportSpecifier::Namespace(namespace) => { - if let ast::ModuleExportName::Ident(ident) = &namespace.name { - self.exports - .entry(id!(ident)) - .or_insert_with(|| Some("*".into())); - } - } - } - } - } - - fn visit_export_decl(&mut self, node: &ast::ExportDecl) { - match &node.decl { - ast::Decl::TsEnum(enu) => { - self.add_export(id!(enu.id), None); - } - ast::Decl::Class(class) => { - self.add_export(id!(class.ident), None); - } - ast::Decl::Fn(func) => { - self.add_export(id!(func.ident), None); - } - ast::Decl::Var(var) => { - for decl in &var.decls { - self.in_export_decl = true; - decl.name.visit_with(self); - self.in_export_decl = false; - - decl.init.visit_with(self); - } - } - _ => {} - } - } - - fn visit_export_default_decl(&mut self, node: &ast::ExportDefaultDecl) { - match &node.decl { - ast::DefaultDecl::Class(class) => { - if let Some(ident) = &class.ident { - self.add_export(id!(ident), Some(js_word!("default"))); - } - } - ast::DefaultDecl::Fn(func) => { - if let Some(ident) = &func.ident { - self.add_export(id!(ident), Some(js_word!("default"))); - } - } - _ => { - unreachable!("unsupported export default declaration"); - } - }; - } - - fn visit_binding_ident(&mut self, node: &ast::BindingIdent) { - if self.in_export_decl { - self.add_export(id!(node.id), None); - } - } - - fn visit_assign_pat_prop(&mut self, node: &ast::AssignPatProp) { - if self.in_export_decl { - self.add_export(id!(node.key), None); - } - } + noop_visit_type!(); + + fn visit_module_item(&mut self, node: &ast::ModuleItem) { + if let ast::ModuleItem::Stmt(ast::Stmt::Decl(decl)) = node { + match decl { + ast::Decl::Fn(function) => { + self.root.insert(id!(function.ident), function.ident.span); + } + ast::Decl::Class(class) => { + self.root.insert(id!(class.ident), class.ident.span); + } + ast::Decl::Var(var) => { + for decl in &var.decls { + let mut identifiers: Vec<(Id, Span)> = vec![]; + collect_from_pat(&decl.name, &mut identifiers); + self.root.extend(identifiers.into_iter()); + } + } + ast::Decl::TsEnum(enu) => { + self.root.insert(id!(enu.id), enu.id.span); + } + _ => {} + } + } else { + node.visit_children_with(self); + } + } + + fn visit_import_decl(&mut self, node: &ast::ImportDecl) { + for specifier in &node.specifiers { + match specifier { + ast::ImportSpecifier::Named(named) => { + let imported = match &named.imported { + Some(ast::ModuleExportName::Ident(ident)) => ident.sym.clone(), + _ => named.local.sym.clone(), + }; + self.add_import( + id!(named.local), + Import { + source: node.src.value.clone(), + specifier: imported, + kind: ImportKind::Named, + synthetic: false, + asserts: node.asserts.clone(), + }, + ); + } + ast::ImportSpecifier::Default(default) => { + self.add_import( + id!(default.local), + Import { + source: node.src.value.clone(), + specifier: js_word!("default"), + kind: ImportKind::Default, + synthetic: false, + asserts: node.asserts.clone(), + }, + ); + } + ast::ImportSpecifier::Namespace(namespace) => { + self.add_import( + id!(namespace.local), + Import { + source: node.src.value.clone(), + specifier: "*".into(), + kind: ImportKind::All, + synthetic: false, + asserts: node.asserts.clone(), + }, + ); + } + } + } + } + + fn visit_named_export(&mut self, node: &ast::NamedExport) { + if node.src.is_some() { + return; + } + + for specifier in &node.specifiers { + match specifier { + ast::ExportSpecifier::Named(named) => { + let local = match &named.orig { + ast::ModuleExportName::Ident(ident) => Some(id!(ident)), + _ => None, + }; + let exported = match &named.exported { + Some(ast::ModuleExportName::Ident(exported)) => Some(exported.sym.clone()), + _ => None, + }; + if let Some(local) = local { + self.add_export(local, exported); + } + } + ast::ExportSpecifier::Default(default) => { + self.exports + .entry(id!(default.exported)) + .or_insert(Some(js_word!("default"))); + } + ast::ExportSpecifier::Namespace(namespace) => { + if let ast::ModuleExportName::Ident(ident) = &namespace.name { + self.exports + .entry(id!(ident)) + .or_insert_with(|| Some("*".into())); + } + } + } + } + } + + fn visit_export_decl(&mut self, node: &ast::ExportDecl) { + match &node.decl { + ast::Decl::TsEnum(enu) => { + self.add_export(id!(enu.id), None); + } + ast::Decl::Class(class) => { + self.add_export(id!(class.ident), None); + } + ast::Decl::Fn(func) => { + self.add_export(id!(func.ident), None); + } + ast::Decl::Var(var) => { + for decl in &var.decls { + self.in_export_decl = true; + decl.name.visit_with(self); + self.in_export_decl = false; + + decl.init.visit_with(self); + } + } + _ => {} + } + } + + fn visit_export_default_decl(&mut self, node: &ast::ExportDefaultDecl) { + match &node.decl { + ast::DefaultDecl::Class(class) => { + if let Some(ident) = &class.ident { + self.add_export(id!(ident), Some(js_word!("default"))); + } + } + ast::DefaultDecl::Fn(func) => { + if let Some(ident) = &func.ident { + self.add_export(id!(ident), Some(js_word!("default"))); + } + } + _ => { + unreachable!("unsupported export default declaration"); + } + }; + } + + fn visit_binding_ident(&mut self, node: &ast::BindingIdent) { + if self.in_export_decl { + self.add_export(id!(node.id), None); + } + } + + fn visit_assign_pat_prop(&mut self, node: &ast::AssignPatProp) { + if self.in_export_decl { + self.add_export(id!(node.key), None); + } + } } #[derive(Debug)] enum ExprOrSkip { - Expr, - Skip, + Expr, + Skip, } #[derive(Debug)] pub struct IdentCollector { - pub local_idents: HashSet, - pub use_h: bool, - pub use_fragment: bool, + pub local_idents: HashSet, + pub use_h: bool, + pub use_fragment: bool, - expr_ctxt: Vec, + expr_ctxt: Vec, } impl IdentCollector { - pub fn new() -> Self { - Self { - local_idents: HashSet::new(), - expr_ctxt: Vec::with_capacity(32), - use_h: false, - use_fragment: false, - } - } - - pub fn get_words(self) -> Vec { - let mut local_idents: Vec = self.local_idents.into_iter().collect(); - local_idents.sort(); - local_idents - } + pub fn new() -> Self { + Self { + local_idents: HashSet::new(), + expr_ctxt: Vec::with_capacity(32), + use_h: false, + use_fragment: false, + } + } + + pub fn get_words(self) -> Vec { + let mut local_idents: Vec = self.local_idents.into_iter().collect(); + local_idents.sort(); + local_idents + } } impl Visit for IdentCollector { - noop_visit_type!(); - - fn visit_expr(&mut self, node: &ast::Expr) { - self.expr_ctxt.push(ExprOrSkip::Expr); - visit_expr(self, node); - self.expr_ctxt.pop(); - } - - fn visit_stmt(&mut self, node: &ast::Stmt) { - self.expr_ctxt.push(ExprOrSkip::Skip); - visit_stmt(self, node); - self.expr_ctxt.pop(); - } - - fn visit_jsx_element(&mut self, node: &ast::JSXElement) { - self.use_h = true; - node.visit_children_with(self); - } - - fn visit_jsx_fragment(&mut self, node: &ast::JSXFragment) { - self.use_h = true; - self.use_fragment = true; - node.visit_children_with(self); - } - - fn visit_jsx_element_name(&mut self, node: &ast::JSXElementName) { - if let ast::JSXElementName::Ident(ref ident) = node { - let ident_name = ident.sym.as_ref().chars().next(); - if let Some('A'..='Z') = ident_name { - } else { - return; - } - } - - node.visit_children_with(self); - } - fn visit_jsx_attr(&mut self, node: &ast::JSXAttr) { - self.expr_ctxt.push(ExprOrSkip::Skip); - node.visit_children_with(self); - self.expr_ctxt.pop(); - } - - fn visit_ident(&mut self, node: &ast::Ident) { - if matches!(self.expr_ctxt.last(), Some(ExprOrSkip::Expr)) - && node.span.ctxt() != SyntaxContext::empty() - { - self.local_idents.insert(id!(node)); - } - } - - fn visit_key_value_prop(&mut self, node: &ast::KeyValueProp) { - self.expr_ctxt.push(ExprOrSkip::Skip); - node.visit_children_with(self); - self.expr_ctxt.pop(); - } + noop_visit_type!(); + + fn visit_expr(&mut self, node: &ast::Expr) { + self.expr_ctxt.push(ExprOrSkip::Expr); + visit_expr(self, node); + self.expr_ctxt.pop(); + } + + fn visit_stmt(&mut self, node: &ast::Stmt) { + self.expr_ctxt.push(ExprOrSkip::Skip); + visit_stmt(self, node); + self.expr_ctxt.pop(); + } + + fn visit_jsx_element(&mut self, node: &ast::JSXElement) { + self.use_h = true; + node.visit_children_with(self); + } + + fn visit_jsx_fragment(&mut self, node: &ast::JSXFragment) { + self.use_h = true; + self.use_fragment = true; + node.visit_children_with(self); + } + + fn visit_jsx_element_name(&mut self, node: &ast::JSXElementName) { + if let ast::JSXElementName::Ident(ref ident) = node { + let ident_name = ident.sym.as_ref().chars().next(); + if let Some('A'..='Z') = ident_name { + } else { + return; + } + } + + node.visit_children_with(self); + } + fn visit_jsx_attr(&mut self, node: &ast::JSXAttr) { + self.expr_ctxt.push(ExprOrSkip::Skip); + node.visit_children_with(self); + self.expr_ctxt.pop(); + } + + fn visit_ident(&mut self, node: &ast::Ident) { + if matches!(self.expr_ctxt.last(), Some(ExprOrSkip::Expr)) + && node.span.ctxt() != SyntaxContext::empty() + { + self.local_idents.insert(id!(node)); + } + } + + fn visit_key_value_prop(&mut self, node: &ast::KeyValueProp) { + self.expr_ctxt.push(ExprOrSkip::Skip); + node.visit_children_with(self); + self.expr_ctxt.pop(); + } } pub fn collect_from_pat(pat: &ast::Pat, identifiers: &mut Vec<(Id, Span)>) -> bool { - match pat { - ast::Pat::Ident(ident) => { - identifiers.push((id!(ident.id), ident.id.span)); - true - } - ast::Pat::Array(array) => { - for el in array.elems.iter().flatten() { - collect_from_pat(el, identifiers); - } - false - } - ast::Pat::Rest(rest) => { - if let ast::Pat::Ident(ident) = rest.arg.as_ref() { - identifiers.push((id!(ident.id), ident.id.span)); - } - false - } - ast::Pat::Assign(expr) => { - if let ast::Pat::Ident(ident) = expr.left.as_ref() { - identifiers.push((id!(ident.id), ident.id.span)); - } - false - } - ast::Pat::Object(obj) => { - for prop in &obj.props { - match prop { - ast::ObjectPatProp::Assign(ref v) => { - identifiers.push((id!(v.key), v.key.span)); - } - ast::ObjectPatProp::KeyValue(ref v) => { - collect_from_pat(&v.value, identifiers); - } - ast::ObjectPatProp::Rest(ref v) => { - if let ast::Pat::Ident(ident) = v.arg.as_ref() { - identifiers.push((id!(ident.id), ident.id.span)); - } - } - } - } - false - } - _ => false, - } + match pat { + ast::Pat::Ident(ident) => { + identifiers.push((id!(ident.id), ident.id.span)); + true + } + ast::Pat::Array(array) => { + for el in array.elems.iter().flatten() { + collect_from_pat(el, identifiers); + } + false + } + ast::Pat::Rest(rest) => { + if let ast::Pat::Ident(ident) = rest.arg.as_ref() { + identifiers.push((id!(ident.id), ident.id.span)); + } + false + } + ast::Pat::Assign(expr) => { + if let ast::Pat::Ident(ident) = expr.left.as_ref() { + identifiers.push((id!(ident.id), ident.id.span)); + } + false + } + ast::Pat::Object(obj) => { + for prop in &obj.props { + match prop { + ast::ObjectPatProp::Assign(ref v) => { + identifiers.push((id!(v.key), v.key.span)); + } + ast::ObjectPatProp::KeyValue(ref v) => { + collect_from_pat(&v.value, identifiers); + } + ast::ObjectPatProp::Rest(ref v) => { + if let ast::Pat::Ident(ident) = v.arg.as_ref() { + identifiers.push((id!(ident.id), ident.id.span)); + } + } + } + } + false + } + _ => false, + } } diff --git a/packages/qwik/src/optimizer/core/src/const_replace.rs b/packages/qwik/src/optimizer/core/src/const_replace.rs index b8ab0391a5e..7a6299e327c 100644 --- a/packages/qwik/src/optimizer/core/src/const_replace.rs +++ b/packages/qwik/src/optimizer/core/src/const_replace.rs @@ -4,80 +4,80 @@ use swc_common::DUMMY_SP; use swc_ecmascript::ast; use swc_ecmascript::visit::{VisitMut, VisitMutWith}; pub struct ConstReplacerVisitor { - pub is_server: bool, - pub is_dev: bool, - pub is_server_ident: Option, - pub is_browser_ident: Option, - pub is_dev_ident: Option, + pub is_server: bool, + pub is_dev: bool, + pub is_server_ident: Option, + pub is_browser_ident: Option, + pub is_dev_ident: Option, } impl ConstReplacerVisitor { - pub fn new(is_server: bool, is_dev: bool, global_collector: &GlobalCollect) -> Self { - Self { - is_server, - is_dev, - is_server_ident: global_collector - .get_imported_local(&IS_SERVER, &BUILDER_IO_QWIK_BUILD), - is_browser_ident: global_collector - .get_imported_local(&IS_BROWSER, &BUILDER_IO_QWIK_BUILD), - is_dev_ident: global_collector.get_imported_local(&IS_DEV, &BUILDER_IO_QWIK_BUILD), - } - } + pub fn new(is_server: bool, is_dev: bool, global_collector: &GlobalCollect) -> Self { + Self { + is_server, + is_dev, + is_server_ident: global_collector + .get_imported_local(&IS_SERVER, &BUILDER_IO_QWIK_BUILD), + is_browser_ident: global_collector + .get_imported_local(&IS_BROWSER, &BUILDER_IO_QWIK_BUILD), + is_dev_ident: global_collector.get_imported_local(&IS_DEV, &BUILDER_IO_QWIK_BUILD), + } + } } macro_rules! id_eq { - ($ident: expr, $cid: expr) => { - if let Some(cid) = $cid { - cid.0 == $ident.sym && cid.1 == $ident.span.ctxt() - } else { - false - } - }; + ($ident: expr, $cid: expr) => { + if let Some(cid) = $cid { + cid.0 == $ident.sym && cid.1 == $ident.span.ctxt() + } else { + false + } + }; } enum ConstVariable { - IsServer, - IsBrowser, - IsDev, - None, + IsServer, + IsBrowser, + IsDev, + None, } impl VisitMut for ConstReplacerVisitor { - fn visit_mut_expr(&mut self, node: &mut ast::Expr) { - let mode = match node { - ast::Expr::Ident(ref ident) => { - if id_eq!(ident, &self.is_server_ident) { - ConstVariable::IsServer - } else if id_eq!(ident, &self.is_browser_ident) { - ConstVariable::IsBrowser - } else if id_eq!(ident, &self.is_dev_ident) { - ConstVariable::IsDev - } else { - ConstVariable::None - } - } - _ => ConstVariable::None, - }; - match mode { - ConstVariable::IsServer => { - *node = ast::Expr::Lit(ast::Lit::Bool(ast::Bool { - span: DUMMY_SP, - value: self.is_server, - })) - } - ConstVariable::IsBrowser => { - *node = ast::Expr::Lit(ast::Lit::Bool(ast::Bool { - span: DUMMY_SP, - value: !self.is_server, - })) - } - ConstVariable::IsDev => { - *node = ast::Expr::Lit(ast::Lit::Bool(ast::Bool { - span: DUMMY_SP, - value: self.is_dev, - })) - } - ConstVariable::None => { - node.visit_mut_children_with(self); - } - } - } + fn visit_mut_expr(&mut self, node: &mut ast::Expr) { + let mode = match node { + ast::Expr::Ident(ref ident) => { + if id_eq!(ident, &self.is_server_ident) { + ConstVariable::IsServer + } else if id_eq!(ident, &self.is_browser_ident) { + ConstVariable::IsBrowser + } else if id_eq!(ident, &self.is_dev_ident) { + ConstVariable::IsDev + } else { + ConstVariable::None + } + } + _ => ConstVariable::None, + }; + match mode { + ConstVariable::IsServer => { + *node = ast::Expr::Lit(ast::Lit::Bool(ast::Bool { + span: DUMMY_SP, + value: self.is_server, + })) + } + ConstVariable::IsBrowser => { + *node = ast::Expr::Lit(ast::Lit::Bool(ast::Bool { + span: DUMMY_SP, + value: !self.is_server, + })) + } + ConstVariable::IsDev => { + *node = ast::Expr::Lit(ast::Lit::Bool(ast::Bool { + span: DUMMY_SP, + value: self.is_dev, + })) + } + ConstVariable::None => { + node.visit_mut_children_with(self); + } + } + } } diff --git a/packages/qwik/src/optimizer/core/src/entry_strategy.rs b/packages/qwik/src/optimizer/core/src/entry_strategy.rs index fc63bbf45e6..674991390b2 100644 --- a/packages/qwik/src/optimizer/core/src/entry_strategy.rs +++ b/packages/qwik/src/optimizer/core/src/entry_strategy.rs @@ -8,185 +8,185 @@ use swc_atoms::JsWord; use lazy_static::lazy_static; lazy_static! { - static ref ENTRY_HOOKS: JsWord = JsWord::from("entry_hooks"); - static ref ENTRY_SERVER: JsWord = JsWord::from("entry_server"); + static ref ENTRY_HOOKS: JsWord = JsWord::from("entry_hooks"); + static ref ENTRY_SERVER: JsWord = JsWord::from("entry_server"); } // EntryStrategies #[derive(Debug, Serialize, Copy, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub enum EntryStrategy { - Inline, - Hoist, - Single, - Hook, - Component, - Smart, + Inline, + Hoist, + Single, + Hook, + Component, + Smart, } pub trait EntryPolicy: Send + Sync { - fn get_entry_for_sym( - &self, - hash: &str, - location: &PathData, - context: &[String], - hook_data: &HookData, - ) -> Option; + fn get_entry_for_sym( + &self, + hash: &str, + location: &PathData, + context: &[String], + hook_data: &HookData, + ) -> Option; } #[derive(Default, Clone)] pub struct InlineStrategy; impl EntryPolicy for InlineStrategy { - fn get_entry_for_sym( - &self, - _hash: &str, - _path: &PathData, - _context: &[String], - _hook_data: &HookData, - ) -> Option { - Some(ENTRY_HOOKS.clone()) - } + fn get_entry_for_sym( + &self, + _hash: &str, + _path: &PathData, + _context: &[String], + _hook_data: &HookData, + ) -> Option { + Some(ENTRY_HOOKS.clone()) + } } #[derive(Clone)] pub struct SingleStrategy { - map: Option>, + map: Option>, } impl SingleStrategy { - pub const fn new(map: Option>) -> Self { - Self { map } - } + pub const fn new(map: Option>) -> Self { + Self { map } + } } impl EntryPolicy for SingleStrategy { - fn get_entry_for_sym( - &self, - hash: &str, - _path: &PathData, - _context: &[String], - _hook_data: &HookData, - ) -> Option { - if let Some(map) = &self.map { - let entry = map.get(hash); - if let Some(entry) = entry { - return Some(entry.clone()); - } - } - Some(ENTRY_HOOKS.clone()) - } + fn get_entry_for_sym( + &self, + hash: &str, + _path: &PathData, + _context: &[String], + _hook_data: &HookData, + ) -> Option { + if let Some(map) = &self.map { + let entry = map.get(hash); + if let Some(entry) = entry { + return Some(entry.clone()); + } + } + Some(ENTRY_HOOKS.clone()) + } } #[derive(Clone)] pub struct PerHookStrategy { - map: Option>, + map: Option>, } impl PerHookStrategy { - pub const fn new(map: Option>) -> Self { - Self { map } - } + pub const fn new(map: Option>) -> Self { + Self { map } + } } impl EntryPolicy for PerHookStrategy { - fn get_entry_for_sym( - &self, - hash: &str, - _path: &PathData, - _context: &[String], - _hook_data: &HookData, - ) -> Option { - if let Some(map) = &self.map { - let entry = map.get(hash); - if let Some(entry) = entry { - return Some(entry.clone()); - } - } - None - } + fn get_entry_for_sym( + &self, + hash: &str, + _path: &PathData, + _context: &[String], + _hook_data: &HookData, + ) -> Option { + if let Some(map) = &self.map { + let entry = map.get(hash); + if let Some(entry) = entry { + return Some(entry.clone()); + } + } + None + } } #[derive(Clone)] pub struct PerComponentStrategy { - map: Option>, + map: Option>, } impl PerComponentStrategy { - pub const fn new(map: Option>) -> Self { - Self { map } - } + pub const fn new(map: Option>) -> Self { + Self { map } + } } impl EntryPolicy for PerComponentStrategy { - fn get_entry_for_sym( - &self, - hash: &str, - _path: &PathData, - context: &[String], - _hook_data: &HookData, - ) -> Option { - if let Some(map) = &self.map { - let entry = map.get(hash); - if let Some(entry) = entry { - return Some(entry.clone()); - } - } - context.first().map_or_else( - || Some(ENTRY_HOOKS.clone()), - |root| Some(JsWord::from(["entry_", root].concat())), - ) - } + fn get_entry_for_sym( + &self, + hash: &str, + _path: &PathData, + context: &[String], + _hook_data: &HookData, + ) -> Option { + if let Some(map) = &self.map { + let entry = map.get(hash); + if let Some(entry) = entry { + return Some(entry.clone()); + } + } + context.first().map_or_else( + || Some(ENTRY_HOOKS.clone()), + |root| Some(JsWord::from(["entry_", root].concat())), + ) + } } #[derive(Clone)] pub struct SmartStrategy { - map: Option>, + map: Option>, } impl SmartStrategy { - pub const fn new(map: Option>) -> Self { - Self { map } - } + pub const fn new(map: Option>) -> Self { + Self { map } + } } impl EntryPolicy for SmartStrategy { - fn get_entry_for_sym( - &self, - hash: &str, - _path: &PathData, - context: &[String], - hook_data: &HookData, - ) -> Option { - if hook_data.scoped_idents.is_empty() - && (hook_data.ctx_kind != HookKind::Function || &hook_data.ctx_name == "event$") - { - return None; - } - if hook_data.ctx_name == *USE_SERVER_MOUNT { - return Some(ENTRY_SERVER.clone()); - } - if let Some(map) = &self.map { - let entry = map.get(hash); - if let Some(entry) = entry { - return Some(entry.clone()); - } - } - Some(context.first().map_or_else( - || ENTRY_HOOKS.clone(), - |root| JsWord::from(["entry_", root].concat()), - )) - } + fn get_entry_for_sym( + &self, + hash: &str, + _path: &PathData, + context: &[String], + hook_data: &HookData, + ) -> Option { + if hook_data.scoped_idents.is_empty() + && (hook_data.ctx_kind != HookKind::Function || &hook_data.ctx_name == "event$") + { + return None; + } + if hook_data.ctx_name == *USE_SERVER_MOUNT { + return Some(ENTRY_SERVER.clone()); + } + if let Some(map) = &self.map { + let entry = map.get(hash); + if let Some(entry) = entry { + return Some(entry.clone()); + } + } + Some(context.first().map_or_else( + || ENTRY_HOOKS.clone(), + |root| JsWord::from(["entry_", root].concat()), + )) + } } pub fn parse_entry_strategy( - strategy: &EntryStrategy, - manual_chunks: Option>, + strategy: &EntryStrategy, + manual_chunks: Option>, ) -> Box { - match strategy { - EntryStrategy::Inline | EntryStrategy::Hoist => Box::::default(), - EntryStrategy::Hook => Box::new(PerHookStrategy::new(manual_chunks)), - EntryStrategy::Single => Box::new(SingleStrategy::new(manual_chunks)), - EntryStrategy::Component => Box::new(PerComponentStrategy::new(manual_chunks)), - EntryStrategy::Smart => Box::new(SmartStrategy::new(manual_chunks)), - } + match strategy { + EntryStrategy::Inline | EntryStrategy::Hoist => Box::::default(), + EntryStrategy::Hook => Box::new(PerHookStrategy::new(manual_chunks)), + EntryStrategy::Single => Box::new(SingleStrategy::new(manual_chunks)), + EntryStrategy::Component => Box::new(PerComponentStrategy::new(manual_chunks)), + EntryStrategy::Smart => Box::new(SmartStrategy::new(manual_chunks)), + } } diff --git a/packages/qwik/src/optimizer/core/src/errors.rs b/packages/qwik/src/optimizer/core/src/errors.rs index 65e18e8521f..2855a30a075 100644 --- a/packages/qwik/src/optimizer/core/src/errors.rs +++ b/packages/qwik/src/optimizer/core/src/errors.rs @@ -1,13 +1,13 @@ use swc_common::errors::DiagnosticId; pub enum Error { - FunctionReference = 2, - CanNotCapture, - DynamicImportInsideQhook, - MissingQrlImplementation, + FunctionReference = 2, + CanNotCapture, + DynamicImportInsideQhook, + MissingQrlImplementation, } pub fn get_diagnostic_id(err: Error) -> DiagnosticId { - let id = err as u32; - DiagnosticId::Error(format!("C{:02}", id)) + let id = err as u32; + DiagnosticId::Error(format!("C{:02}", id)) } diff --git a/packages/qwik/src/optimizer/core/src/filter_exports.rs b/packages/qwik/src/optimizer/core/src/filter_exports.rs index af53944029a..2ec8daf099c 100644 --- a/packages/qwik/src/optimizer/core/src/filter_exports.rs +++ b/packages/qwik/src/optimizer/core/src/filter_exports.rs @@ -4,47 +4,47 @@ use swc_ecmascript::ast; use swc_ecmascript::visit::VisitMut; pub struct StripExportsVisitor<'a> { - pub filter_symbols: &'a [JsWord], + pub filter_symbols: &'a [JsWord], } impl<'a> StripExportsVisitor<'a> { - pub const fn new(filter_symbols: &'a [JsWord]) -> Self { - Self { filter_symbols } - } + pub const fn new(filter_symbols: &'a [JsWord]) -> Self { + Self { filter_symbols } + } } impl<'a> VisitMut for StripExportsVisitor<'a> { - fn visit_mut_module(&mut self, node: &mut ast::Module) { - for item in &mut node.body { - if let ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDecl(decl)) = item { - match &decl.decl { - ast::Decl::Var(var) => { - if var.decls.len() == 1 { - if let Some(ast::VarDeclarator { - name: ast::Pat::Ident(ident), - .. - }) = var.decls.first() - { - if self.filter_symbols.contains(&ident.id.sym) { - *item = empty_module_item(ident.id.clone()); - } - } - } - } - ast::Decl::Fn(fn_decl) => { - if self.filter_symbols.contains(&fn_decl.ident.sym) { - *item = empty_module_item(fn_decl.ident.clone()); - } - } - _ => {} - } - } - } - } + fn visit_mut_module(&mut self, node: &mut ast::Module) { + for item in &mut node.body { + if let ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDecl(decl)) = item { + match &decl.decl { + ast::Decl::Var(var) => { + if var.decls.len() == 1 { + if let Some(ast::VarDeclarator { + name: ast::Pat::Ident(ident), + .. + }) = var.decls.first() + { + if self.filter_symbols.contains(&ident.id.sym) { + *item = empty_module_item(ident.id.clone()); + } + } + } + } + ast::Decl::Fn(fn_decl) => { + if self.filter_symbols.contains(&fn_decl.ident.sym) { + *item = empty_module_item(fn_decl.ident.clone()); + } + } + _ => {} + } + } + } + } } fn empty_module_item(ident: ast::Ident) -> ast::ModuleItem { - ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDecl(ast::ExportDecl { + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportDecl(ast::ExportDecl { span: DUMMY_SP, decl: ast::Decl::Var(Box::new(ast::VarDecl { span: DUMMY_SP, diff --git a/packages/qwik/src/optimizer/core/src/has_branches.rs b/packages/qwik/src/optimizer/core/src/has_branches.rs index 0e4b1ce535e..f39103448f6 100644 --- a/packages/qwik/src/optimizer/core/src/has_branches.rs +++ b/packages/qwik/src/optimizer/core/src/has_branches.rs @@ -5,139 +5,139 @@ use swc_ecmascript::ast; use swc_ecmascript::visit::{noop_visit_type, Visit, VisitWith}; macro_rules! id { - ($ident: expr) => { - ($ident.sym.clone(), $ident.span.ctxt()) - }; + ($ident: expr) => { + ($ident.sym.clone(), $ident.span.ctxt()) + }; } pub fn is_conditional_jsx( - expr: &ast::BlockStmtOrExpr, - jsx_functions: &HashSet, - immutable_function_cmp: &HashSet, + expr: &ast::BlockStmtOrExpr, + jsx_functions: &HashSet, + immutable_function_cmp: &HashSet, ) -> bool { - let mut collector = HasBranches::new(jsx_functions, immutable_function_cmp); - expr.visit_with(&mut collector); - collector.conditional + let mut collector = HasBranches::new(jsx_functions, immutable_function_cmp); + expr.visit_with(&mut collector); + collector.conditional } pub fn is_conditional_jsx_block( - expr: &ast::BlockStmt, - jsx_functions: &HashSet, - immutable_function_cmp: &HashSet, + expr: &ast::BlockStmt, + jsx_functions: &HashSet, + immutable_function_cmp: &HashSet, ) -> bool { - let mut collector = HasBranches::new(jsx_functions, immutable_function_cmp); - expr.visit_with(&mut collector); - collector.conditional + let mut collector = HasBranches::new(jsx_functions, immutable_function_cmp); + expr.visit_with(&mut collector); + collector.conditional } pub struct HasBranches<'a> { - under_conditional: i32, - jsx_functions: &'a HashSet, - immutable_function_cmp: &'a HashSet, - conditional: bool, - found_return: bool, + under_conditional: i32, + jsx_functions: &'a HashSet, + immutable_function_cmp: &'a HashSet, + conditional: bool, + found_return: bool, } impl<'a> HasBranches<'a> { - const fn new(jsx_functions: &'a HashSet, immutable_function_cmp: &'a HashSet) -> Self { - Self { - jsx_functions, - immutable_function_cmp, - under_conditional: 0, - conditional: false, - found_return: false, - } - } + const fn new(jsx_functions: &'a HashSet, immutable_function_cmp: &'a HashSet) -> Self { + Self { + jsx_functions, + immutable_function_cmp, + under_conditional: 0, + conditional: false, + found_return: false, + } + } } impl<'a> Visit for HasBranches<'a> { - noop_visit_type!(); - - fn visit_arrow_expr(&mut self, _: &ast::ArrowExpr) {} - fn visit_fn_expr(&mut self, _: &ast::FnExpr) {} - fn visit_fn_decl(&mut self, _: &ast::FnDecl) {} - - fn visit_return_stmt(&mut self, node: &ast::ReturnStmt) { - node.visit_children_with(self); - self.found_return = true; - } - - fn visit_for_in_stmt(&mut self, node: &ast::ForInStmt) { - self.under_conditional += 1; - node.visit_children_with(self); - self.under_conditional -= 1; - } - - fn visit_for_of_stmt(&mut self, node: &ast::ForOfStmt) { - self.under_conditional += 1; - node.visit_children_with(self); - self.under_conditional -= 1; - } - - fn visit_for_stmt(&mut self, node: &ast::ForStmt) { - self.under_conditional += 1; - node.visit_children_with(self); - self.under_conditional -= 1; - } - - fn visit_if_stmt(&mut self, node: &ast::IfStmt) { - self.under_conditional += 1; - node.visit_children_with(self); - self.under_conditional -= 1; - } - - fn visit_while_stmt(&mut self, node: &ast::WhileStmt) { - self.under_conditional += 1; - node.visit_children_with(self); - self.under_conditional -= 1; - } - - fn visit_do_while_stmt(&mut self, node: &ast::DoWhileStmt) { - self.under_conditional += 1; - node.visit_children_with(self); - self.under_conditional -= 1; - } - - fn visit_switch_stmt(&mut self, node: &ast::SwitchStmt) { - self.under_conditional += 1; - node.visit_children_with(self); - self.under_conditional -= 1; - } - - fn visit_cond_expr(&mut self, node: &ast::CondExpr) { - self.under_conditional += 1; - node.visit_children_with(self); - self.under_conditional -= 1; - } - - fn visit_bin_expr(&mut self, node: &ast::BinExpr) { - if matches!( - node.op, - ast::BinaryOp::LogicalAnd | ast::BinaryOp::LogicalOr | ast::BinaryOp::NullishCoalescing - ) { - self.under_conditional += 1; - node.visit_children_with(self); - self.under_conditional -= 1; - } else { - node.visit_children_with(self); - } - } - - fn visit_call_expr(&mut self, node: &ast::CallExpr) { - if self.under_conditional > 0 || self.found_return { - if let ast::Callee::Expr(box ast::Expr::Ident(ident)) = &node.callee { - if self.jsx_functions.contains(&id!(ident)) { - let first_arg = node.args.first(); - if let Some(name) = first_arg { - if let ast::Expr::Ident(jsx_id) = &*name.expr { - if !self.immutable_function_cmp.contains(&id!(jsx_id)) { - self.conditional = true; - } - } - } - } - } - } - node.visit_children_with(self); - } + noop_visit_type!(); + + fn visit_arrow_expr(&mut self, _: &ast::ArrowExpr) {} + fn visit_fn_expr(&mut self, _: &ast::FnExpr) {} + fn visit_fn_decl(&mut self, _: &ast::FnDecl) {} + + fn visit_return_stmt(&mut self, node: &ast::ReturnStmt) { + node.visit_children_with(self); + self.found_return = true; + } + + fn visit_for_in_stmt(&mut self, node: &ast::ForInStmt) { + self.under_conditional += 1; + node.visit_children_with(self); + self.under_conditional -= 1; + } + + fn visit_for_of_stmt(&mut self, node: &ast::ForOfStmt) { + self.under_conditional += 1; + node.visit_children_with(self); + self.under_conditional -= 1; + } + + fn visit_for_stmt(&mut self, node: &ast::ForStmt) { + self.under_conditional += 1; + node.visit_children_with(self); + self.under_conditional -= 1; + } + + fn visit_if_stmt(&mut self, node: &ast::IfStmt) { + self.under_conditional += 1; + node.visit_children_with(self); + self.under_conditional -= 1; + } + + fn visit_while_stmt(&mut self, node: &ast::WhileStmt) { + self.under_conditional += 1; + node.visit_children_with(self); + self.under_conditional -= 1; + } + + fn visit_do_while_stmt(&mut self, node: &ast::DoWhileStmt) { + self.under_conditional += 1; + node.visit_children_with(self); + self.under_conditional -= 1; + } + + fn visit_switch_stmt(&mut self, node: &ast::SwitchStmt) { + self.under_conditional += 1; + node.visit_children_with(self); + self.under_conditional -= 1; + } + + fn visit_cond_expr(&mut self, node: &ast::CondExpr) { + self.under_conditional += 1; + node.visit_children_with(self); + self.under_conditional -= 1; + } + + fn visit_bin_expr(&mut self, node: &ast::BinExpr) { + if matches!( + node.op, + ast::BinaryOp::LogicalAnd | ast::BinaryOp::LogicalOr | ast::BinaryOp::NullishCoalescing + ) { + self.under_conditional += 1; + node.visit_children_with(self); + self.under_conditional -= 1; + } else { + node.visit_children_with(self); + } + } + + fn visit_call_expr(&mut self, node: &ast::CallExpr) { + if self.under_conditional > 0 || self.found_return { + if let ast::Callee::Expr(box ast::Expr::Ident(ident)) = &node.callee { + if self.jsx_functions.contains(&id!(ident)) { + let first_arg = node.args.first(); + if let Some(name) = first_arg { + if let ast::Expr::Ident(jsx_id) = &*name.expr { + if !self.immutable_function_cmp.contains(&id!(jsx_id)) { + self.conditional = true; + } + } + } + } + } + } + node.visit_children_with(self); + } } diff --git a/packages/qwik/src/optimizer/core/src/inlined_fn.rs b/packages/qwik/src/optimizer/core/src/inlined_fn.rs index b8546147a65..971bf5ec9b6 100644 --- a/packages/qwik/src/optimizer/core/src/inlined_fn.rs +++ b/packages/qwik/src/optimizer/core/src/inlined_fn.rs @@ -9,202 +9,202 @@ use swc_ecmascript::codegen::text_writer::JsWriter; use swc_ecmascript::transforms::fixer; use swc_ecmascript::transforms::hygiene::hygiene_with_config; use swc_ecmascript::{ - utils::private_ident, - visit::{VisitMut, VisitMutWith}, + utils::private_ident, + visit::{VisitMut, VisitMutWith}, }; macro_rules! id { - ($ident: expr) => { - ($ident.sym.clone(), $ident.span.ctxt()) - }; + ($ident: expr) => { + ($ident.sym.clone(), $ident.span.ctxt()) + }; } pub fn convert_inlined_fn( - mut expr: ast::Expr, - scoped_idents: Vec, - qqhook: &Id, - accept_call_expr: bool, - serialize_fn: bool, + mut expr: ast::Expr, + scoped_idents: Vec, + qqhook: &Id, + accept_call_expr: bool, + serialize_fn: bool, ) -> (Option, bool) { - let mut identifiers = HashMap::new(); - let params: Vec = scoped_idents - .iter() - .enumerate() - .map(|(index, id)| { - let new_ident = private_ident!(format!("p{}", index)); - identifiers.insert(id.clone(), ast::Expr::Ident(new_ident.clone())); - ast::Pat::Ident(ast::BindingIdent { - id: new_ident, - type_ann: None, - }) - }) - .collect(); - - if matches!(expr, ast::Expr::Arrow(_)) { - return (None, false); - } - - // Replace identifier - let mut replace_identifiers = ReplaceIdentifiers::new(identifiers, accept_call_expr); - expr.visit_mut_with(&mut replace_identifiers); - - if replace_identifiers.abort { - return (None, false); - } - - let rendered_expr = render_expr(&expr); - if rendered_expr.len() > 150 { - return (None, false); - } - - if scoped_idents.is_empty() { - return (None, true); - } - - // Generate stringified version - let rendered_str = - ast::ExprOrSpread::from(ast::Expr::Lit(ast::Lit::Str(ast::Str::from(rendered_expr)))); - - // Wrap around arrow functions - let expr = ast::Expr::Arrow(ast::ArrowExpr { - body: Box::new(ast::BlockStmtOrExpr::Expr(Box::new(expr))), - is_async: false, - is_generator: false, - params, - return_type: None, - span: DUMMY_SP, - type_params: None, - }); - - let mut args = vec![ - ast::ExprOrSpread::from(expr), - ast::ExprOrSpread::from(ast::Expr::Array(ast::ArrayLit { - span: DUMMY_SP, - elems: scoped_idents - .iter() - .map(|id| { - Some(ast::ExprOrSpread::from(ast::Expr::Ident( - new_ident_from_id(id), - ))) - }) - .collect(), - })), - ]; - - if serialize_fn { - args.push(rendered_str) - } - - ( - Some(ast::Expr::Call(ast::CallExpr { - span: DUMMY_SP, - callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(qqhook)))), - type_args: None, - args, - })), - true, - ) + let mut identifiers = HashMap::new(); + let params: Vec = scoped_idents + .iter() + .enumerate() + .map(|(index, id)| { + let new_ident = private_ident!(format!("p{}", index)); + identifiers.insert(id.clone(), ast::Expr::Ident(new_ident.clone())); + ast::Pat::Ident(ast::BindingIdent { + id: new_ident, + type_ann: None, + }) + }) + .collect(); + + if matches!(expr, ast::Expr::Arrow(_)) { + return (None, false); + } + + // Replace identifier + let mut replace_identifiers = ReplaceIdentifiers::new(identifiers, accept_call_expr); + expr.visit_mut_with(&mut replace_identifiers); + + if replace_identifiers.abort { + return (None, false); + } + + let rendered_expr = render_expr(&expr); + if rendered_expr.len() > 150 { + return (None, false); + } + + if scoped_idents.is_empty() { + return (None, true); + } + + // Generate stringified version + let rendered_str = + ast::ExprOrSpread::from(ast::Expr::Lit(ast::Lit::Str(ast::Str::from(rendered_expr)))); + + // Wrap around arrow functions + let expr = ast::Expr::Arrow(ast::ArrowExpr { + body: Box::new(ast::BlockStmtOrExpr::Expr(Box::new(expr))), + is_async: false, + is_generator: false, + params, + return_type: None, + span: DUMMY_SP, + type_params: None, + }); + + let mut args = vec![ + ast::ExprOrSpread::from(expr), + ast::ExprOrSpread::from(ast::Expr::Array(ast::ArrayLit { + span: DUMMY_SP, + elems: scoped_idents + .iter() + .map(|id| { + Some(ast::ExprOrSpread::from(ast::Expr::Ident( + new_ident_from_id(id), + ))) + }) + .collect(), + })), + ]; + + if serialize_fn { + args.push(rendered_str) + } + + ( + Some(ast::Expr::Call(ast::CallExpr { + span: DUMMY_SP, + callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(qqhook)))), + type_args: None, + args, + })), + true, + ) } struct ReplaceIdentifiers { - pub identifiers: HashMap, - pub accept_call_expr: bool, - pub abort: bool, + pub identifiers: HashMap, + pub accept_call_expr: bool, + pub abort: bool, } impl ReplaceIdentifiers { - const fn new(identifiers: HashMap, accept_call_expr: bool) -> Self { - Self { - identifiers, - accept_call_expr, - abort: false, - } - } + const fn new(identifiers: HashMap, accept_call_expr: bool) -> Self { + Self { + identifiers, + accept_call_expr, + abort: false, + } + } } impl VisitMut for ReplaceIdentifiers { - fn visit_mut_expr(&mut self, node: &mut ast::Expr) { - match node { - ast::Expr::Ident(ident) => { - if let Some(expr) = self.identifiers.get(&id!(ident)) { - *node = expr.clone(); - } - } - _ => { - node.visit_mut_children_with(self); - } - } - } - - fn visit_mut_prop(&mut self, node: &mut ast::Prop) { - if let ast::Prop::Shorthand(short) = node { - if let Some(expr) = self.identifiers.get(&id!(short)) { - *node = ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Ident(short.clone()), - value: Box::new(expr.clone()), - }); - } - } - node.visit_mut_children_with(self); - } - - fn visit_mut_callee(&mut self, node: &mut ast::Callee) { - if !self.accept_call_expr || matches!(node, ast::Callee::Import(_)) { - self.abort = true; - } else { - node.visit_mut_children_with(self); - } - } - - fn visit_mut_arrow_expr(&mut self, _: &mut ast::ArrowExpr) { - self.abort = true; - } - - fn visit_mut_function(&mut self, _: &mut ast::Function) { - self.abort = true; - } - - fn visit_mut_class_expr(&mut self, _: &mut ast::ClassExpr) { - self.abort = true; - } - - fn visit_mut_decorator(&mut self, _: &mut ast::Decorator) { - self.abort = true; - } - - fn visit_mut_stmt(&mut self, _: &mut ast::Stmt) { - self.abort = true; - } + fn visit_mut_expr(&mut self, node: &mut ast::Expr) { + match node { + ast::Expr::Ident(ident) => { + if let Some(expr) = self.identifiers.get(&id!(ident)) { + *node = expr.clone(); + } + } + _ => { + node.visit_mut_children_with(self); + } + } + } + + fn visit_mut_prop(&mut self, node: &mut ast::Prop) { + if let ast::Prop::Shorthand(short) = node { + if let Some(expr) = self.identifiers.get(&id!(short)) { + *node = ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Ident(short.clone()), + value: Box::new(expr.clone()), + }); + } + } + node.visit_mut_children_with(self); + } + + fn visit_mut_callee(&mut self, node: &mut ast::Callee) { + if !self.accept_call_expr || matches!(node, ast::Callee::Import(_)) { + self.abort = true; + } else { + node.visit_mut_children_with(self); + } + } + + fn visit_mut_arrow_expr(&mut self, _: &mut ast::ArrowExpr) { + self.abort = true; + } + + fn visit_mut_function(&mut self, _: &mut ast::Function) { + self.abort = true; + } + + fn visit_mut_class_expr(&mut self, _: &mut ast::ClassExpr) { + self.abort = true; + } + + fn visit_mut_decorator(&mut self, _: &mut ast::Decorator) { + self.abort = true; + } + + fn visit_mut_stmt(&mut self, _: &mut ast::Stmt) { + self.abort = true; + } } pub fn render_expr(expr: &ast::Expr) -> String { - let mut expr = expr.clone(); - let mut buf = Vec::new(); - let source_map = Lrc::new(SourceMap::default()); - let writer = Box::new(JsWriter::new(Lrc::clone(&source_map), "\n", &mut buf, None)); - let config = swc_ecmascript::codegen::Config { - minify: true, - target: ast::EsVersion::latest(), - ascii_only: false, - omit_last_semi: false, - }; - let mut emitter = swc_ecmascript::codegen::Emitter { - cfg: config, - comments: None, - cm: Lrc::clone(&source_map), - wr: writer, - }; - expr.visit_mut_with(&mut hygiene_with_config(Default::default())); - expr.visit_mut_with(&mut fixer(None)); - emitter - .emit_module_item(&ast::ModuleItem::Stmt(ast::Stmt::Expr(ast::ExprStmt { - span: DUMMY_SP, - expr: Box::new(expr), - }))) - .expect("Should emit"); - - str::from_utf8(&buf) - .expect("should be utf8") - .trim_end_matches(';') - .to_string() + let mut expr = expr.clone(); + let mut buf = Vec::new(); + let source_map = Lrc::new(SourceMap::default()); + let writer = Box::new(JsWriter::new(Lrc::clone(&source_map), "\n", &mut buf, None)); + let config = swc_ecmascript::codegen::Config { + minify: true, + target: ast::EsVersion::latest(), + ascii_only: false, + omit_last_semi: false, + }; + let mut emitter = swc_ecmascript::codegen::Emitter { + cfg: config, + comments: None, + cm: Lrc::clone(&source_map), + wr: writer, + }; + expr.visit_mut_with(&mut hygiene_with_config(Default::default())); + expr.visit_mut_with(&mut fixer(None)); + emitter + .emit_module_item(&ast::ModuleItem::Stmt(ast::Stmt::Expr(ast::ExprStmt { + span: DUMMY_SP, + expr: Box::new(expr), + }))) + .expect("Should emit"); + + str::from_utf8(&buf) + .expect("should be utf8") + .trim_end_matches(';') + .to_string() } diff --git a/packages/qwik/src/optimizer/core/src/is_immutable.rs b/packages/qwik/src/optimizer/core/src/is_immutable.rs index 854e4ce4782..daaa9747fe8 100644 --- a/packages/qwik/src/optimizer/core/src/is_immutable.rs +++ b/packages/qwik/src/optimizer/core/src/is_immutable.rs @@ -4,36 +4,36 @@ use swc_ecmascript::ast; use swc_ecmascript::visit::{noop_visit_type, Visit}; macro_rules! id { - ($ident: expr) => { - ($ident.sym.clone(), $ident.span.ctxt()) - }; + ($ident: expr) => { + ($ident.sym.clone(), $ident.span.ctxt()) + }; } pub fn is_immutable_expr( - expr: &ast::Expr, - global: &GlobalCollect, - current_stack: Option<&Vec>, + expr: &ast::Expr, + global: &GlobalCollect, + current_stack: Option<&Vec>, ) -> bool { - let mut collector = ImmutableCollector::new(global, current_stack); - collector.visit_expr(expr); - collector.is_immutable + let mut collector = ImmutableCollector::new(global, current_stack); + collector.visit_expr(expr); + collector.is_immutable } pub struct ImmutableCollector<'a> { - global: &'a GlobalCollect, - immutable_idents: Option<&'a Vec>, + global: &'a GlobalCollect, + immutable_idents: Option<&'a Vec>, - pub is_immutable: bool, + pub is_immutable: bool, } impl<'a> ImmutableCollector<'a> { - const fn new(global: &'a GlobalCollect, immutable_idents: Option<&'a Vec>) -> Self { - Self { - global, - is_immutable: true, - immutable_idents, - } - } + const fn new(global: &'a GlobalCollect, immutable_idents: Option<&'a Vec>) -> Self { + Self { + global, + is_immutable: true, + immutable_idents, + } + } } // A prop is considered mutable if it: @@ -41,34 +41,34 @@ impl<'a> ImmutableCollector<'a> { // - accesses a member // - is a variable that is not an import, an export, or in the immutable stack impl<'a> Visit for ImmutableCollector<'a> { - noop_visit_type!(); + noop_visit_type!(); - fn visit_call_expr(&mut self, _: &ast::CallExpr) { - self.is_immutable = false; - } + fn visit_call_expr(&mut self, _: &ast::CallExpr) { + self.is_immutable = false; + } - fn visit_member_expr(&mut self, _: &ast::MemberExpr) { - self.is_immutable = false; - } + fn visit_member_expr(&mut self, _: &ast::MemberExpr) { + self.is_immutable = false; + } - fn visit_arrow_expr(&mut self, _: &ast::ArrowExpr) {} + fn visit_arrow_expr(&mut self, _: &ast::ArrowExpr) {} - fn visit_ident(&mut self, ident: &ast::Ident) { - let id = id!(ident); - if self.global.imports.contains_key(&id) { - return; - } - if self.global.exports.contains_key(&id) { - return; - } - if let Some(current_stack) = self.immutable_idents { - if current_stack - .iter() - .any(|item| item.1 == IdentType::Var(true) && item.0 == id) - { - return; - } - } - self.is_immutable = false; - } + fn visit_ident(&mut self, ident: &ast::Ident) { + let id = id!(ident); + if self.global.imports.contains_key(&id) { + return; + } + if self.global.exports.contains_key(&id) { + return; + } + if let Some(current_stack) = self.immutable_idents { + if current_stack + .iter() + .any(|item| item.1 == IdentType::Var(true) && item.0 == id) + { + return; + } + } + self.is_immutable = false; + } } diff --git a/packages/qwik/src/optimizer/core/src/lib.rs b/packages/qwik/src/optimizer/core/src/lib.rs index e4f4837f430..f6158c0afec 100644 --- a/packages/qwik/src/optimizer/core/src/lib.rs +++ b/packages/qwik/src/optimizer/core/src/lib.rs @@ -55,195 +55,195 @@ pub use crate::parse::{ErrorBuffer, HookAnalysis, MinifyMode, TransformModule, T #[derive(Serialize, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransformFsOptions { - pub src_dir: String, - pub root_dir: Option, - pub vendor_roots: Vec, - pub glob: Option, - pub minify: MinifyMode, - pub entry_strategy: EntryStrategy, - pub manual_chunks: Option>, - pub source_maps: bool, - pub transpile_ts: bool, - pub transpile_jsx: bool, - pub preserve_filenames: bool, - pub explicit_extensions: bool, - pub mode: EmitMode, - pub scope: Option, - - pub core_module: Option, - pub strip_exports: Option>, - pub strip_ctx_name: Option>, - pub strip_event_handlers: bool, - pub reg_ctx_name: Option>, - pub is_server: Option, + pub src_dir: String, + pub root_dir: Option, + pub vendor_roots: Vec, + pub glob: Option, + pub minify: MinifyMode, + pub entry_strategy: EntryStrategy, + pub manual_chunks: Option>, + pub source_maps: bool, + pub transpile_ts: bool, + pub transpile_jsx: bool, + pub preserve_filenames: bool, + pub explicit_extensions: bool, + pub mode: EmitMode, + pub scope: Option, + + pub core_module: Option, + pub strip_exports: Option>, + pub strip_ctx_name: Option>, + pub strip_event_handlers: bool, + pub reg_ctx_name: Option>, + pub is_server: Option, } #[derive(Serialize, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransformModuleInput { - pub path: String, - pub code: String, + pub path: String, + pub code: String, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransformModulesOptions { - pub src_dir: String, - pub root_dir: Option, - pub input: Vec, - pub source_maps: bool, - pub minify: MinifyMode, - pub transpile_ts: bool, - pub transpile_jsx: bool, - pub preserve_filenames: bool, - pub entry_strategy: EntryStrategy, - pub manual_chunks: Option>, - pub explicit_extensions: bool, - pub mode: EmitMode, - pub scope: Option, - - pub core_module: Option, - pub strip_exports: Option>, - pub strip_ctx_name: Option>, - pub strip_event_handlers: bool, - pub reg_ctx_name: Option>, - pub is_server: Option, + pub src_dir: String, + pub root_dir: Option, + pub input: Vec, + pub source_maps: bool, + pub minify: MinifyMode, + pub transpile_ts: bool, + pub transpile_jsx: bool, + pub preserve_filenames: bool, + pub entry_strategy: EntryStrategy, + pub manual_chunks: Option>, + pub explicit_extensions: bool, + pub mode: EmitMode, + pub scope: Option, + + pub core_module: Option, + pub strip_exports: Option>, + pub strip_ctx_name: Option>, + pub strip_event_handlers: bool, + pub reg_ctx_name: Option>, + pub is_server: Option, } #[cfg(feature = "fs")] pub fn transform_fs(config: TransformFsOptions) -> Result { - let core_module = config - .core_module - .map_or(BUILDER_IO_QWIK.clone(), |s| s.into()); - let src_dir = Path::new(&config.src_dir); - let root_dir = config.root_dir.as_ref().map(Path::new); - - let mut paths = vec![]; - let entry_policy = &*parse_entry_strategy(&config.entry_strategy, config.manual_chunks); - crate::package_json::find_modules(src_dir, config.vendor_roots, &mut paths)?; - - #[cfg(feature = "parallel")] - let iterator = paths.par_iter(); - - #[cfg(not(feature = "parallel"))] - let iterator = paths.iter(); - let mut final_output = iterator - .map(|path| -> Result { - let code = fs::read_to_string(path) - .with_context(|| format!("Opening {}", &path.to_string_lossy()))?; - - let relative_path = pathdiff::diff_paths(path, &config.src_dir).unwrap(); - transform_code(TransformCodeOptions { - src_dir, - root_dir, - relative_path: relative_path.to_str().unwrap(), - minify: config.minify, - code: &code, - explicit_extensions: config.explicit_extensions, - source_maps: config.source_maps, - transpile_jsx: config.transpile_jsx, - transpile_ts: config.transpile_ts, - preserve_filenames: config.preserve_filenames, - scope: config.scope.as_ref(), - entry_policy, - mode: config.mode, - core_module: core_module.clone(), - entry_strategy: config.entry_strategy, - reg_ctx_name: config.reg_ctx_name.as_deref(), - strip_exports: config.strip_exports.as_deref(), - strip_ctx_name: config.strip_ctx_name.as_deref(), - strip_event_handlers: config.strip_event_handlers, - is_server: config.is_server, - }) - }) - .reduce(|| Ok(TransformOutput::new()), |x, y| Ok(x?.append(&mut y?)))?; - - final_output.modules.sort_unstable_by_key(|key| key.order); - if !matches!( - config.entry_strategy, - EntryStrategy::Hook | EntryStrategy::Inline | EntryStrategy::Hoist - ) { - final_output = generate_entries( - final_output, - &core_module, - config.explicit_extensions, - root_dir, - )?; - } - // final_output = generate_entries( - // final_output, - // &core_module, - // config.explicit_extensions, - // root_dir, - // )?; - Ok(final_output) + let core_module = config + .core_module + .map_or(BUILDER_IO_QWIK.clone(), |s| s.into()); + let src_dir = Path::new(&config.src_dir); + let root_dir = config.root_dir.as_ref().map(Path::new); + + let mut paths = vec![]; + let entry_policy = &*parse_entry_strategy(&config.entry_strategy, config.manual_chunks); + crate::package_json::find_modules(src_dir, config.vendor_roots, &mut paths)?; + + #[cfg(feature = "parallel")] + let iterator = paths.par_iter(); + + #[cfg(not(feature = "parallel"))] + let iterator = paths.iter(); + let mut final_output = iterator + .map(|path| -> Result { + let code = fs::read_to_string(path) + .with_context(|| format!("Opening {}", &path.to_string_lossy()))?; + + let relative_path = pathdiff::diff_paths(path, &config.src_dir).unwrap(); + transform_code(TransformCodeOptions { + src_dir, + root_dir, + relative_path: relative_path.to_str().unwrap(), + minify: config.minify, + code: &code, + explicit_extensions: config.explicit_extensions, + source_maps: config.source_maps, + transpile_jsx: config.transpile_jsx, + transpile_ts: config.transpile_ts, + preserve_filenames: config.preserve_filenames, + scope: config.scope.as_ref(), + entry_policy, + mode: config.mode, + core_module: core_module.clone(), + entry_strategy: config.entry_strategy, + reg_ctx_name: config.reg_ctx_name.as_deref(), + strip_exports: config.strip_exports.as_deref(), + strip_ctx_name: config.strip_ctx_name.as_deref(), + strip_event_handlers: config.strip_event_handlers, + is_server: config.is_server, + }) + }) + .reduce(|| Ok(TransformOutput::new()), |x, y| Ok(x?.append(&mut y?)))?; + + final_output.modules.sort_unstable_by_key(|key| key.order); + if !matches!( + config.entry_strategy, + EntryStrategy::Hook | EntryStrategy::Inline | EntryStrategy::Hoist + ) { + final_output = generate_entries( + final_output, + &core_module, + config.explicit_extensions, + root_dir, + )?; + } + // final_output = generate_entries( + // final_output, + // &core_module, + // config.explicit_extensions, + // root_dir, + // )?; + Ok(final_output) } pub fn transform_modules(config: TransformModulesOptions) -> Result { - let core_module = config - .core_module - .map_or(BUILDER_IO_QWIK.clone(), |s| s.into()); - let src_dir = std::path::Path::new(&config.src_dir); - let root_dir = config.root_dir.as_ref().map(Path::new); - - let entry_policy = &*parse_entry_strategy(&config.entry_strategy, config.manual_chunks); - #[cfg(feature = "parallel")] - let iterator = config.input.par_iter(); - - #[cfg(not(feature = "parallel"))] - let iterator = config.input.iter(); - let iterator = iterator.map(|path| -> Result { - transform_code(TransformCodeOptions { - src_dir, - root_dir, - relative_path: &path.path, - code: &path.code, - minify: config.minify, - source_maps: config.source_maps, - transpile_ts: config.transpile_ts, - transpile_jsx: config.transpile_jsx, - preserve_filenames: config.preserve_filenames, - explicit_extensions: config.explicit_extensions, - entry_policy, - mode: config.mode, - scope: config.scope.as_ref(), - core_module: core_module.clone(), - entry_strategy: config.entry_strategy, - reg_ctx_name: config.reg_ctx_name.as_deref(), - strip_exports: config.strip_exports.as_deref(), - strip_ctx_name: config.strip_ctx_name.as_deref(), - strip_event_handlers: config.strip_event_handlers, - is_server: config.is_server, - }) - }); - - #[cfg(feature = "parallel")] - let final_output: Result = - iterator.reduce(|| Ok(TransformOutput::new()), |x, y| Ok(x?.append(&mut y?))); - - #[cfg(not(feature = "parallel"))] - let final_output: Result = - iterator.fold(Ok(TransformOutput::new()), |x, y| Ok(x?.append(&mut y?))); - - let mut final_output = final_output?; - final_output.modules.sort_unstable_by_key(|key| key.order); - if !matches!( - config.entry_strategy, - EntryStrategy::Hook | EntryStrategy::Inline | EntryStrategy::Hoist - ) { - final_output = generate_entries( - final_output, - &core_module, - config.explicit_extensions, - root_dir, - )?; - } - // final_output = generate_entries( - // final_output, - // &core_module, - // config.explicit_extensions, - // root_dir, - // )?; - - Ok(final_output) + let core_module = config + .core_module + .map_or(BUILDER_IO_QWIK.clone(), |s| s.into()); + let src_dir = std::path::Path::new(&config.src_dir); + let root_dir = config.root_dir.as_ref().map(Path::new); + + let entry_policy = &*parse_entry_strategy(&config.entry_strategy, config.manual_chunks); + #[cfg(feature = "parallel")] + let iterator = config.input.par_iter(); + + #[cfg(not(feature = "parallel"))] + let iterator = config.input.iter(); + let iterator = iterator.map(|path| -> Result { + transform_code(TransformCodeOptions { + src_dir, + root_dir, + relative_path: &path.path, + code: &path.code, + minify: config.minify, + source_maps: config.source_maps, + transpile_ts: config.transpile_ts, + transpile_jsx: config.transpile_jsx, + preserve_filenames: config.preserve_filenames, + explicit_extensions: config.explicit_extensions, + entry_policy, + mode: config.mode, + scope: config.scope.as_ref(), + core_module: core_module.clone(), + entry_strategy: config.entry_strategy, + reg_ctx_name: config.reg_ctx_name.as_deref(), + strip_exports: config.strip_exports.as_deref(), + strip_ctx_name: config.strip_ctx_name.as_deref(), + strip_event_handlers: config.strip_event_handlers, + is_server: config.is_server, + }) + }); + + #[cfg(feature = "parallel")] + let final_output: Result = + iterator.reduce(|| Ok(TransformOutput::new()), |x, y| Ok(x?.append(&mut y?))); + + #[cfg(not(feature = "parallel"))] + let final_output: Result = + iterator.fold(Ok(TransformOutput::new()), |x, y| Ok(x?.append(&mut y?))); + + let mut final_output = final_output?; + final_output.modules.sort_unstable_by_key(|key| key.order); + if !matches!( + config.entry_strategy, + EntryStrategy::Hook | EntryStrategy::Inline | EntryStrategy::Hoist + ) { + final_output = generate_entries( + final_output, + &core_module, + config.explicit_extensions, + root_dir, + )?; + } + // final_output = generate_entries( + // final_output, + // &core_module, + // config.explicit_extensions, + // root_dir, + // )?; + + Ok(final_output) } diff --git a/packages/qwik/src/optimizer/core/src/package_json.rs b/packages/qwik/src/optimizer/core/src/package_json.rs index 22845341fe6..8efc992f7f7 100644 --- a/packages/qwik/src/optimizer/core/src/package_json.rs +++ b/packages/qwik/src/optimizer/core/src/package_json.rs @@ -3,39 +3,39 @@ pub use crate::parse::{ErrorBuffer, HookAnalysis, MinifyMode, TransformModule, T #[cfg(feature = "fs")] pub fn find_modules( - src_dir: &std::path::Path, - vendor_dirs: Vec, - files: &mut Vec, + src_dir: &std::path::Path, + vendor_dirs: Vec, + files: &mut Vec, ) -> std::io::Result<()> { - for root in &vendor_dirs { - find_files(std::path::Path::new(root), files)?; - } - find_files(src_dir, files) + for root in &vendor_dirs { + find_files(std::path::Path::new(root), files)?; + } + find_files(src_dir, files) } #[cfg(feature = "fs")] fn find_files(dir: &std::path::Path, files: &mut Vec) -> std::io::Result<()> { - if dir.is_dir() { - for entry in std::fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - find_files(&path, files)?; - } else if should_capture_file(&path) { - files.push(path); - } - } - } else if should_capture_file(dir) { - files.push(dir.to_path_buf()); - } - Ok(()) + if dir.is_dir() { + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + find_files(&path, files)?; + } else if should_capture_file(&path) { + files.push(path); + } + } + } else if should_capture_file(dir) { + files.push(dir.to_path_buf()); + } + Ok(()) } #[cfg(feature = "fs")] fn should_capture_file(path: &std::path::Path) -> bool { - let ext = path.extension().and_then(|p| p.to_str()); - matches!( - ext, - Some("ts" | "tsx" | "js" | "jsx" | "mjs" | "mts" | "mtsx" | "mjsx") - ) + let ext = path.extension().and_then(|p| p.to_str()); + matches!( + ext, + Some("ts" | "tsx" | "js" | "jsx" | "mjs" | "mts" | "mtsx" | "mjsx") + ) } diff --git a/packages/qwik/src/optimizer/core/src/parse.rs b/packages/qwik/src/optimizer/core/src/parse.rs index 19be6135649..4f6eabd3a09 100644 --- a/packages/qwik/src/optimizer/core/src/parse.rs +++ b/packages/qwik/src/optimizer/core/src/parse.rs @@ -33,728 +33,728 @@ use swc_ecmascript::codegen::text_writer::JsWriter; use swc_ecmascript::parser::lexer::Lexer; use swc_ecmascript::parser::{EsConfig, PResult, Parser, StringInput, Syntax, TsConfig}; use swc_ecmascript::transforms::{ - fixer, hygiene::hygiene_with_config, optimization::simplify, react, resolver, typescript, + fixer, hygiene::hygiene_with_config, optimization::simplify, react, resolver, typescript, }; use swc_ecmascript::visit::{FoldWith, VisitMutWith}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct HookAnalysis { - pub origin: JsWord, - pub name: JsWord, - pub entry: Option, - pub display_name: JsWord, - pub hash: JsWord, - pub canonical_filename: JsWord, - pub extension: JsWord, - pub parent: Option, - pub ctx_kind: HookKind, - pub ctx_name: JsWord, - pub captures: bool, - pub loc: (u32, u32), + pub origin: JsWord, + pub name: JsWord, + pub entry: Option, + pub display_name: JsWord, + pub hash: JsWord, + pub canonical_filename: JsWord, + pub extension: JsWord, + pub parent: Option, + pub ctx_kind: HookKind, + pub ctx_name: JsWord, + pub captures: bool, + pub loc: (u32, u32), } #[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum MinifyMode { - Simplify, - None, + Simplify, + None, } #[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum EmitMode { - Prod, - Lib, - Dev, + Prod, + Lib, + Dev, } pub struct TransformCodeOptions<'a> { - pub relative_path: &'a str, - pub src_dir: &'a Path, - pub root_dir: Option<&'a Path>, - pub source_maps: bool, - pub minify: MinifyMode, - pub transpile_ts: bool, - pub transpile_jsx: bool, - pub preserve_filenames: bool, - pub explicit_extensions: bool, - pub code: &'a str, - pub entry_policy: &'a dyn EntryPolicy, - pub mode: EmitMode, - pub scope: Option<&'a String>, - pub entry_strategy: EntryStrategy, - pub core_module: JsWord, - - pub reg_ctx_name: Option<&'a [JsWord]>, - pub strip_exports: Option<&'a [JsWord]>, - pub strip_ctx_name: Option<&'a [JsWord]>, - pub strip_event_handlers: bool, - pub is_server: Option, + pub relative_path: &'a str, + pub src_dir: &'a Path, + pub root_dir: Option<&'a Path>, + pub source_maps: bool, + pub minify: MinifyMode, + pub transpile_ts: bool, + pub transpile_jsx: bool, + pub preserve_filenames: bool, + pub explicit_extensions: bool, + pub code: &'a str, + pub entry_policy: &'a dyn EntryPolicy, + pub mode: EmitMode, + pub scope: Option<&'a String>, + pub entry_strategy: EntryStrategy, + pub core_module: JsWord, + + pub reg_ctx_name: Option<&'a [JsWord]>, + pub strip_exports: Option<&'a [JsWord]>, + pub strip_ctx_name: Option<&'a [JsWord]>, + pub strip_event_handlers: bool, + pub is_server: Option, } #[derive(Debug, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct TransformOutput { - pub modules: Vec, - pub diagnostics: Vec, - pub is_type_script: bool, - pub is_jsx: bool, + pub modules: Vec, + pub diagnostics: Vec, + pub is_type_script: bool, + pub is_jsx: bool, } #[derive(Debug, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct QwikBundle { - pub size: usize, - pub symbols: Vec, + pub size: usize, + pub symbols: Vec, } #[derive(Debug, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct QwikManifest { - pub version: JsWord, - pub symbols: HashMap, - pub bundles: HashMap, - pub mapping: HashMap, + pub version: JsWord, + pub symbols: HashMap, + pub bundles: HashMap, + pub mapping: HashMap, } impl TransformOutput { - pub fn new() -> Self { - Self::default() - } - - pub fn append(mut self, output: &mut Self) -> Self { - self.modules.append(&mut output.modules); - self.diagnostics.append(&mut output.diagnostics); - self.is_type_script = self.is_type_script || output.is_type_script; - self.is_jsx = self.is_jsx || output.is_jsx; - self - } - - pub fn get_manifest(&self) -> QwikManifest { - let mut manifest = QwikManifest { - bundles: HashMap::new(), - symbols: HashMap::new(), - mapping: HashMap::new(), - version: "1".into(), - }; - for module in &self.modules { - if let Some(hook) = &module.hook { - let filename = - JsWord::from(format!("{}.{}", hook.canonical_filename, hook.extension)); - manifest.mapping.insert(hook.name.clone(), filename.clone()); - manifest.symbols.insert(hook.name.clone(), hook.clone()); - manifest.bundles.insert( - filename.clone(), - QwikBundle { - symbols: vec![hook.name.clone()], - size: module.code.len(), - }, - ); - } - } - manifest - } - - #[cfg(feature = "fs")] - pub fn write_to_fs( - &self, - destination: &Path, - manifest: Option, - ) -> Result { - for module in &self.modules { - let write_path = destination.join(&module.path); - fs::create_dir_all(write_path.parent().with_context(|| { - format!("Computing path parent of {}", write_path.to_string_lossy()) - })?)?; - fs::write(write_path, &module.code)?; - } - if let Some(manifest) = manifest { - let write_path = destination.join(manifest); - let manifest = self.get_manifest(); - let json = serde_json::to_string(&manifest)?; - fs::write(write_path, json)?; - } - Ok(self.modules.len()) - } + pub fn new() -> Self { + Self::default() + } + + pub fn append(mut self, output: &mut Self) -> Self { + self.modules.append(&mut output.modules); + self.diagnostics.append(&mut output.diagnostics); + self.is_type_script = self.is_type_script || output.is_type_script; + self.is_jsx = self.is_jsx || output.is_jsx; + self + } + + pub fn get_manifest(&self) -> QwikManifest { + let mut manifest = QwikManifest { + bundles: HashMap::new(), + symbols: HashMap::new(), + mapping: HashMap::new(), + version: "1".into(), + }; + for module in &self.modules { + if let Some(hook) = &module.hook { + let filename = + JsWord::from(format!("{}.{}", hook.canonical_filename, hook.extension)); + manifest.mapping.insert(hook.name.clone(), filename.clone()); + manifest.symbols.insert(hook.name.clone(), hook.clone()); + manifest.bundles.insert( + filename.clone(), + QwikBundle { + symbols: vec![hook.name.clone()], + size: module.code.len(), + }, + ); + } + } + manifest + } + + #[cfg(feature = "fs")] + pub fn write_to_fs( + &self, + destination: &Path, + manifest: Option, + ) -> Result { + for module in &self.modules { + let write_path = destination.join(&module.path); + fs::create_dir_all(write_path.parent().with_context(|| { + format!("Computing path parent of {}", write_path.to_string_lossy()) + })?)?; + fs::write(write_path, &module.code)?; + } + if let Some(manifest) = manifest { + let write_path = destination.join(manifest); + let manifest = self.get_manifest(); + let json = serde_json::to_string(&manifest)?; + fs::write(write_path, json)?; + } + Ok(self.modules.len()) + } } #[derive(Debug, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct TransformModule { - pub path: String, - pub code: String, + pub path: String, + pub code: String, - pub map: Option, + pub map: Option, - pub hook: Option, - pub is_entry: bool, + pub hook: Option, + pub is_entry: bool, - #[serde(skip_serializing)] - pub order: u64, + #[serde(skip_serializing)] + pub order: u64, } #[derive(Debug, Clone, Default)] pub struct ErrorBuffer(std::sync::Arc>>); impl Emitter for ErrorBuffer { - fn emit(&mut self, db: &DiagnosticBuilder) { - self.0.lock().unwrap().push((**db).clone()); - } + fn emit(&mut self, db: &DiagnosticBuilder) { + self.0.lock().unwrap().push((**db).clone()); + } } pub fn transform_code(config: TransformCodeOptions) -> Result { - let source_map = Lrc::new(SourceMap::default()); - let path_data = parse_path(config.relative_path, config.src_dir)?; - let module = parse( - config.code, - &path_data, - config.root_dir, - Lrc::clone(&source_map), - ); - // dbg!(&module); - let transpile_jsx = config.transpile_jsx; - let transpile_ts = config.transpile_ts; - - let origin: JsWord = path_data.rel_path.to_slash_lossy().into(); - - match module { - Ok((main_module, comments, is_type_script, is_jsx)) => { - let extension = match (transpile_ts, transpile_jsx, is_type_script, is_jsx) { - (true, true, _, _) => JsWord::from("js"), - (true, false, _, true) => JsWord::from("jsx"), - (true, false, _, false) => JsWord::from("js"), - (false, true, true, _) => JsWord::from("ts"), - (false, true, false, _) => JsWord::from("js"), - (false, false, _, _) => JsWord::from(path_data.extension.clone()), - }; - let error_buffer = ErrorBuffer::default(); - let handler = swc_common::errors::Handler::with_emitter( - true, - false, - Box::new(error_buffer.clone()), - ); - - swc_common::GLOBALS.set(&Globals::new(), || { - swc_common::errors::HANDLER.set(&handler, || { - let unresolved_mark = Mark::new(); - let top_level_mark = Mark::new(); - - let mut main_module = main_module; - - if let Some(strip_exports) = config.strip_exports { - let mut visitor = StripExportsVisitor::new(strip_exports); - main_module.visit_mut_with(&mut visitor); - } - - let mut did_transform = false; - - // Transpile JSX - if transpile_ts && is_type_script { - did_transform = true; - main_module = if is_jsx { - main_module.fold_with(&mut typescript::strip_with_jsx( - Lrc::clone(&source_map), - typescript::Config { - pragma: Some("h".to_string()), - pragma_frag: Some("Fragment".to_string()), - ..Default::default() - }, - Some(&comments), - top_level_mark, - )) - } else { - main_module.fold_with(&mut typescript::strip(top_level_mark)) - } - } - - // Transpile JSX - if transpile_jsx && is_jsx { - did_transform = true; - let mut react_options = react::Options::default(); - if is_jsx { - react_options.next = Some(true); - react_options.throw_if_namespace = Some(false); - react_options.runtime = Some(react::Runtime::Automatic); - react_options.import_source = Some("@builder.io/qwik".to_string()); - }; - main_module = main_module.fold_with(&mut react::react( - Lrc::clone(&source_map), - Some(&comments), - react_options, - top_level_mark, - unresolved_mark, - )); - } - - // Resolve with mark - main_module.visit_mut_with(&mut resolver( - unresolved_mark, - top_level_mark, - is_type_script && !transpile_ts, - )); - // Collect import/export metadata - let mut collect = global_collect(&main_module); - - transform_props_destructuring( - &mut main_module, - &mut collect, - &config.core_module, - ); - - // Replace const values - if let Some(is_server) = config.is_server { - if config.mode != EmitMode::Lib { - let is_dev = config.mode == EmitMode::Dev; - let mut const_replacer = - ConstReplacerVisitor::new(is_server, is_dev, &collect); - main_module.visit_mut_with(&mut const_replacer); - } - } - let mut qwik_transform = QwikTransform::new(QwikTransformOptions { - path_data: &path_data, - entry_policy: config.entry_policy, - explicit_extensions: config.explicit_extensions, - extension: extension.clone(), - comments: Some(&comments), - global_collect: collect, - scope: config.scope, - mode: config.mode, - core_module: config.core_module, - entry_strategy: config.entry_strategy, - reg_ctx_name: config.reg_ctx_name, - strip_ctx_name: config.strip_ctx_name, - strip_event_handlers: config.strip_event_handlers, - is_server: config.is_server, - cm: Lrc::clone(&source_map), - }); - - // Run main transform - main_module = main_module.fold_with(&mut qwik_transform); - - let mut treeshaker = Treeshaker::new(); - - if config.minify != MinifyMode::None { - main_module.visit_mut_with(&mut treeshaker.marker); - - main_module = main_module.fold_with(&mut simplify::simplifier( - unresolved_mark, - simplify::Config { - dce: simplify::dce::Config { - preserve_imports_with_side_effects: false, - ..Default::default() - }, - ..Default::default() - }, - )); - } - if matches!( - config.entry_strategy, - EntryStrategy::Inline | EntryStrategy::Hoist - ) { - main_module.visit_mut_with(&mut SideEffectVisitor::new( - &qwik_transform.options.global_collect, - &path_data, - config.src_dir, - )); - } else if config.minify != MinifyMode::None - && matches!(config.is_server, Some(false)) - { - main_module.visit_mut_with(&mut treeshaker.cleaner); - if treeshaker.cleaner.did_drop { - main_module = main_module.fold_with(&mut simplify::simplifier( - unresolved_mark, - simplify::Config { - dce: simplify::dce::Config { - preserve_imports_with_side_effects: false, - ..Default::default() - }, - ..Default::default() - }, - )); - } - } - main_module.visit_mut_with(&mut hygiene_with_config(Default::default())); - main_module.visit_mut_with(&mut fixer(None)); - - let hooks = qwik_transform.hooks; - let mut modules: Vec = Vec::with_capacity(hooks.len() + 10); - - let comments_maps = comments.clone().take_all(); - for h in hooks.into_iter() { - let is_entry = h.entry.is_none(); - let hook_path = [&h.canonical_filename, ".", &h.data.extension].concat(); - let need_handle_watch = - might_need_handle_watch(&h.data.ctx_kind, &h.data.ctx_name) && is_entry; - - let (mut hook_module, comments) = new_module(NewModuleCtx { - expr: h.expr, - path: &path_data, - name: &h.name, - local_idents: &h.data.local_idents, - scoped_idents: &h.data.scoped_idents, - need_transform: h.data.need_transform, - explicit_extensions: qwik_transform.options.explicit_extensions, - global: &qwik_transform.options.global_collect, - core_module: &qwik_transform.options.core_module, - need_handle_watch, - is_entry, - leading_comments: comments_maps.0.clone(), - trailing_comments: comments_maps.1.clone(), - })?; - if config.minify != MinifyMode::None { - hook_module = hook_module.fold_with(&mut simplify::simplifier( - unresolved_mark, - simplify::Config { - dce: simplify::dce::Config { - preserve_imports_with_side_effects: false, - ..Default::default() - }, - ..Default::default() - }, - )); - } - hook_module.visit_mut_with(&mut hygiene_with_config(Default::default())); - hook_module.visit_mut_with(&mut fixer(None)); - - let (code, map) = emit_source_code( - Lrc::clone(&source_map), - Some(comments), - &hook_module, - config.root_dir, - config.source_maps, - ) - .unwrap(); - - modules.push(TransformModule { - code, - map, - is_entry, - path: hook_path, - order: h.hash, - hook: Some(HookAnalysis { - origin: h.data.origin, - name: h.name, - entry: h.entry, - extension: h.data.extension, - canonical_filename: h.canonical_filename, - parent: h.data.parent_hook, - ctx_kind: h.data.ctx_kind, - ctx_name: h.data.ctx_name, - captures: !h.data.scoped_idents.is_empty(), - display_name: h.data.display_name, - hash: h.data.hash, - loc: (h.span.lo.0, h.span.hi.0), - }), - }); - } - - let (code, map) = emit_source_code( - Lrc::clone(&source_map), - Some(comments), - &main_module, - config.root_dir, - config.source_maps, - )?; - - let a = if did_transform && !config.preserve_filenames { - [&path_data.file_stem, ".", &extension].concat() - } else { - path_data.file_name - }; - let path = path_data.rel_dir.join(a).to_string_lossy().to_string(); - - let mut hasher = DefaultHasher::new(); - hasher.write(path.as_bytes()); - - modules.push(TransformModule { - is_entry: false, - path, - code, - map, - order: hasher.finish(), - hook: None, - }); - - let diagnostics = handle_error(&error_buffer, origin, &source_map); - Ok(TransformOutput { - modules, - diagnostics, - is_type_script, - is_jsx, - }) - }) - }) - } - Err(err) => { - let error_buffer = ErrorBuffer::default(); - let handler = Handler::with_emitter(true, false, Box::new(error_buffer.clone())); - err.into_diagnostic(&handler).emit(); - let diagnostics = handle_error(&error_buffer, origin, &source_map); - Ok(TransformOutput { - modules: vec![], - diagnostics, - is_type_script: false, - is_jsx: false, - }) - } - } + let source_map = Lrc::new(SourceMap::default()); + let path_data = parse_path(config.relative_path, config.src_dir)?; + let module = parse( + config.code, + &path_data, + config.root_dir, + Lrc::clone(&source_map), + ); + // dbg!(&module); + let transpile_jsx = config.transpile_jsx; + let transpile_ts = config.transpile_ts; + + let origin: JsWord = path_data.rel_path.to_slash_lossy().into(); + + match module { + Ok((main_module, comments, is_type_script, is_jsx)) => { + let extension = match (transpile_ts, transpile_jsx, is_type_script, is_jsx) { + (true, true, _, _) => JsWord::from("js"), + (true, false, _, true) => JsWord::from("jsx"), + (true, false, _, false) => JsWord::from("js"), + (false, true, true, _) => JsWord::from("ts"), + (false, true, false, _) => JsWord::from("js"), + (false, false, _, _) => JsWord::from(path_data.extension.clone()), + }; + let error_buffer = ErrorBuffer::default(); + let handler = swc_common::errors::Handler::with_emitter( + true, + false, + Box::new(error_buffer.clone()), + ); + + swc_common::GLOBALS.set(&Globals::new(), || { + swc_common::errors::HANDLER.set(&handler, || { + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + + let mut main_module = main_module; + + if let Some(strip_exports) = config.strip_exports { + let mut visitor = StripExportsVisitor::new(strip_exports); + main_module.visit_mut_with(&mut visitor); + } + + let mut did_transform = false; + + // Transpile JSX + if transpile_ts && is_type_script { + did_transform = true; + main_module = if is_jsx { + main_module.fold_with(&mut typescript::strip_with_jsx( + Lrc::clone(&source_map), + typescript::Config { + pragma: Some("h".to_string()), + pragma_frag: Some("Fragment".to_string()), + ..Default::default() + }, + Some(&comments), + top_level_mark, + )) + } else { + main_module.fold_with(&mut typescript::strip(top_level_mark)) + } + } + + // Transpile JSX + if transpile_jsx && is_jsx { + did_transform = true; + let mut react_options = react::Options::default(); + if is_jsx { + react_options.next = Some(true); + react_options.throw_if_namespace = Some(false); + react_options.runtime = Some(react::Runtime::Automatic); + react_options.import_source = Some("@builder.io/qwik".to_string()); + }; + main_module = main_module.fold_with(&mut react::react( + Lrc::clone(&source_map), + Some(&comments), + react_options, + top_level_mark, + unresolved_mark, + )); + } + + // Resolve with mark + main_module.visit_mut_with(&mut resolver( + unresolved_mark, + top_level_mark, + is_type_script && !transpile_ts, + )); + // Collect import/export metadata + let mut collect = global_collect(&main_module); + + transform_props_destructuring( + &mut main_module, + &mut collect, + &config.core_module, + ); + + // Replace const values + if let Some(is_server) = config.is_server { + if config.mode != EmitMode::Lib { + let is_dev = config.mode == EmitMode::Dev; + let mut const_replacer = + ConstReplacerVisitor::new(is_server, is_dev, &collect); + main_module.visit_mut_with(&mut const_replacer); + } + } + let mut qwik_transform = QwikTransform::new(QwikTransformOptions { + path_data: &path_data, + entry_policy: config.entry_policy, + explicit_extensions: config.explicit_extensions, + extension: extension.clone(), + comments: Some(&comments), + global_collect: collect, + scope: config.scope, + mode: config.mode, + core_module: config.core_module, + entry_strategy: config.entry_strategy, + reg_ctx_name: config.reg_ctx_name, + strip_ctx_name: config.strip_ctx_name, + strip_event_handlers: config.strip_event_handlers, + is_server: config.is_server, + cm: Lrc::clone(&source_map), + }); + + // Run main transform + main_module = main_module.fold_with(&mut qwik_transform); + + let mut treeshaker = Treeshaker::new(); + + if config.minify != MinifyMode::None { + main_module.visit_mut_with(&mut treeshaker.marker); + + main_module = main_module.fold_with(&mut simplify::simplifier( + unresolved_mark, + simplify::Config { + dce: simplify::dce::Config { + preserve_imports_with_side_effects: false, + ..Default::default() + }, + ..Default::default() + }, + )); + } + if matches!( + config.entry_strategy, + EntryStrategy::Inline | EntryStrategy::Hoist + ) { + main_module.visit_mut_with(&mut SideEffectVisitor::new( + &qwik_transform.options.global_collect, + &path_data, + config.src_dir, + )); + } else if config.minify != MinifyMode::None + && matches!(config.is_server, Some(false)) + { + main_module.visit_mut_with(&mut treeshaker.cleaner); + if treeshaker.cleaner.did_drop { + main_module = main_module.fold_with(&mut simplify::simplifier( + unresolved_mark, + simplify::Config { + dce: simplify::dce::Config { + preserve_imports_with_side_effects: false, + ..Default::default() + }, + ..Default::default() + }, + )); + } + } + main_module.visit_mut_with(&mut hygiene_with_config(Default::default())); + main_module.visit_mut_with(&mut fixer(None)); + + let hooks = qwik_transform.hooks; + let mut modules: Vec = Vec::with_capacity(hooks.len() + 10); + + let comments_maps = comments.clone().take_all(); + for h in hooks.into_iter() { + let is_entry = h.entry.is_none(); + let hook_path = [&h.canonical_filename, ".", &h.data.extension].concat(); + let need_handle_watch = + might_need_handle_watch(&h.data.ctx_kind, &h.data.ctx_name) && is_entry; + + let (mut hook_module, comments) = new_module(NewModuleCtx { + expr: h.expr, + path: &path_data, + name: &h.name, + local_idents: &h.data.local_idents, + scoped_idents: &h.data.scoped_idents, + need_transform: h.data.need_transform, + explicit_extensions: qwik_transform.options.explicit_extensions, + global: &qwik_transform.options.global_collect, + core_module: &qwik_transform.options.core_module, + need_handle_watch, + is_entry, + leading_comments: comments_maps.0.clone(), + trailing_comments: comments_maps.1.clone(), + })?; + if config.minify != MinifyMode::None { + hook_module = hook_module.fold_with(&mut simplify::simplifier( + unresolved_mark, + simplify::Config { + dce: simplify::dce::Config { + preserve_imports_with_side_effects: false, + ..Default::default() + }, + ..Default::default() + }, + )); + } + hook_module.visit_mut_with(&mut hygiene_with_config(Default::default())); + hook_module.visit_mut_with(&mut fixer(None)); + + let (code, map) = emit_source_code( + Lrc::clone(&source_map), + Some(comments), + &hook_module, + config.root_dir, + config.source_maps, + ) + .unwrap(); + + modules.push(TransformModule { + code, + map, + is_entry, + path: hook_path, + order: h.hash, + hook: Some(HookAnalysis { + origin: h.data.origin, + name: h.name, + entry: h.entry, + extension: h.data.extension, + canonical_filename: h.canonical_filename, + parent: h.data.parent_hook, + ctx_kind: h.data.ctx_kind, + ctx_name: h.data.ctx_name, + captures: !h.data.scoped_idents.is_empty(), + display_name: h.data.display_name, + hash: h.data.hash, + loc: (h.span.lo.0, h.span.hi.0), + }), + }); + } + + let (code, map) = emit_source_code( + Lrc::clone(&source_map), + Some(comments), + &main_module, + config.root_dir, + config.source_maps, + )?; + + let a = if did_transform && !config.preserve_filenames { + [&path_data.file_stem, ".", &extension].concat() + } else { + path_data.file_name + }; + let path = path_data.rel_dir.join(a).to_string_lossy().to_string(); + + let mut hasher = DefaultHasher::new(); + hasher.write(path.as_bytes()); + + modules.push(TransformModule { + is_entry: false, + path, + code, + map, + order: hasher.finish(), + hook: None, + }); + + let diagnostics = handle_error(&error_buffer, origin, &source_map); + Ok(TransformOutput { + modules, + diagnostics, + is_type_script, + is_jsx, + }) + }) + }) + } + Err(err) => { + let error_buffer = ErrorBuffer::default(); + let handler = Handler::with_emitter(true, false, Box::new(error_buffer.clone())); + err.into_diagnostic(&handler).emit(); + let diagnostics = handle_error(&error_buffer, origin, &source_map); + Ok(TransformOutput { + modules: vec![], + diagnostics, + is_type_script: false, + is_jsx: false, + }) + } + } } fn parse( - code: &str, - path_data: &PathData, - root_dir: Option<&Path>, - source_map: Lrc, + code: &str, + path_data: &PathData, + root_dir: Option<&Path>, + source_map: Lrc, ) -> PResult<(ast::Module, SingleThreadedComments, bool, bool)> { - let sm_path = if let Some(root_dir) = root_dir { - pathdiff::diff_paths(path_data.abs_path.clone(), root_dir).unwrap() - } else { - path_data.abs_path.clone() - }; - let source_file = source_map.new_source_file(FileName::Real(sm_path), code.into()); - - let comments = SingleThreadedComments::default(); - let (is_type_script, is_jsx) = parse_filename(path_data); - let syntax = if is_type_script { - Syntax::Typescript(TsConfig { - tsx: is_jsx, - decorators: true, - ..Default::default() - }) - } else { - Syntax::Es(EsConfig { - jsx: is_jsx, - export_default_from: true, - ..Default::default() - }) - }; - - let lexer = Lexer::new( - syntax, - Default::default(), - StringInput::from(&*source_file), - Some(&comments), - ); - - let mut parser = Parser::new_from(lexer); - match parser.parse_module() { - Err(err) => Err(err), - Ok(module) => Ok((module, comments, is_type_script, is_jsx)), - } + let sm_path = if let Some(root_dir) = root_dir { + pathdiff::diff_paths(path_data.abs_path.clone(), root_dir).unwrap() + } else { + path_data.abs_path.clone() + }; + let source_file = source_map.new_source_file(FileName::Real(sm_path), code.into()); + + let comments = SingleThreadedComments::default(); + let (is_type_script, is_jsx) = parse_filename(path_data); + let syntax = if is_type_script { + Syntax::Typescript(TsConfig { + tsx: is_jsx, + decorators: true, + ..Default::default() + }) + } else { + Syntax::Es(EsConfig { + jsx: is_jsx, + export_default_from: true, + ..Default::default() + }) + }; + + let lexer = Lexer::new( + syntax, + Default::default(), + StringInput::from(&*source_file), + Some(&comments), + ); + + let mut parser = Parser::new_from(lexer); + match parser.parse_module() { + Err(err) => Err(err), + Ok(module) => Ok((module, comments, is_type_script, is_jsx)), + } } fn parse_filename(path_data: &PathData) -> (bool, bool) { - match path_data.extension.as_str() { - "ts" => (true, false), - "mts" => (true, false), - "mtsx" => (true, true), - "js" => (false, false), - "mjs" => (false, false), - "cjs" => (false, false), - "jsx" => (false, true), - "mjsx" => (false, true), - "cjsx" => (false, true), - _ => (true, true), - } + match path_data.extension.as_str() { + "ts" => (true, false), + "mts" => (true, false), + "mtsx" => (true, true), + "js" => (false, false), + "mjs" => (false, false), + "cjs" => (false, false), + "jsx" => (false, true), + "mjsx" => (false, true), + "cjsx" => (false, true), + _ => (true, true), + } } pub fn emit_source_code( - source_map: Lrc, - comments: Option, - program: &ast::Module, - root_dir: Option<&Path>, - source_maps: bool, + source_map: Lrc, + comments: Option, + program: &ast::Module, + root_dir: Option<&Path>, + source_maps: bool, ) -> Result<(String, Option), Error> { - let mut src_map_buf = Vec::new(); - let mut buf = Vec::new(); - { - let writer = Box::new(JsWriter::new( - Lrc::clone(&source_map), - "\n", - &mut buf, - if source_maps { - Some(&mut src_map_buf) - } else { - None - }, - )); - let config = swc_ecmascript::codegen::Config { - minify: false, - target: ast::EsVersion::latest(), - ascii_only: false, - omit_last_semi: false, - }; - let mut emitter = swc_ecmascript::codegen::Emitter { - cfg: config, - comments: Some(&comments), - cm: Lrc::clone(&source_map), - wr: writer, - }; - emitter.emit_module(program)?; - } - - let mut map_buf = vec![]; - let emit_source_maps = if source_maps { - let mut s = source_map.build_source_map(&src_map_buf); - if let Some(root_dir) = root_dir { - s.set_source_root(Some(root_dir.to_str().unwrap())); - } - s.to_writer(&mut map_buf).is_ok() - } else { - false - }; - if emit_source_maps { - Ok(( - unsafe { str::from_utf8_unchecked(&buf).to_string() }, - unsafe { Some(str::from_utf8_unchecked(&map_buf).to_string()) }, - )) - } else { - Ok((unsafe { str::from_utf8_unchecked(&buf).to_string() }, None)) - } + let mut src_map_buf = Vec::new(); + let mut buf = Vec::new(); + { + let writer = Box::new(JsWriter::new( + Lrc::clone(&source_map), + "\n", + &mut buf, + if source_maps { + Some(&mut src_map_buf) + } else { + None + }, + )); + let config = swc_ecmascript::codegen::Config { + minify: false, + target: ast::EsVersion::latest(), + ascii_only: false, + omit_last_semi: false, + }; + let mut emitter = swc_ecmascript::codegen::Emitter { + cfg: config, + comments: Some(&comments), + cm: Lrc::clone(&source_map), + wr: writer, + }; + emitter.emit_module(program)?; + } + + let mut map_buf = vec![]; + let emit_source_maps = if source_maps { + let mut s = source_map.build_source_map(&src_map_buf); + if let Some(root_dir) = root_dir { + s.set_source_root(Some(root_dir.to_str().unwrap())); + } + s.to_writer(&mut map_buf).is_ok() + } else { + false + }; + if emit_source_maps { + Ok(( + unsafe { str::from_utf8_unchecked(&buf).to_string() }, + unsafe { Some(str::from_utf8_unchecked(&map_buf).to_string()) }, + )) + } else { + Ok((unsafe { str::from_utf8_unchecked(&buf).to_string() }, None)) + } } fn handle_error( - error_buffer: &ErrorBuffer, - origin: JsWord, - source_map: &Lrc, + error_buffer: &ErrorBuffer, + origin: JsWord, + source_map: &Lrc, ) -> Vec { - error_buffer - .0 - .lock() - .map(|diagnostics| diagnostics.clone()) - .ok() - .unwrap_or_default() - .iter() - .map(|diagnostic| { - let message = diagnostic.message(); - let code = diagnostic.get_code().and_then(|m| { - if let DiagnosticId::Error(s) = m { - Some(s) - } else { - None - } - }); - - let span = diagnostic.span.clone(); - let suggestions = diagnostic.suggestions.clone(); - - let span_labels = span.span_labels(); - let highlights = if span_labels.is_empty() { - None - } else { - Some( - span_labels - .into_iter() - .flat_map(|span_label| { - if span_label.span.hi == span_label.span.lo { - None - } else { - Some(SourceLocation::from(source_map, span_label.span)) - } - }) - .collect(), - ) - }; - - let suggestions = if suggestions.is_empty() { - None - } else { - Some( - suggestions - .into_iter() - .map(|suggestion| suggestion.msg) - .collect(), - ) - }; - - Diagnostic { - file: origin.clone(), - code, - message, - highlights, - suggestions, - category: DiagnosticCategory::Error, - scope: DiagnosticScope::Optimizer, - } - }) - .collect() + error_buffer + .0 + .lock() + .map(|diagnostics| diagnostics.clone()) + .ok() + .unwrap_or_default() + .iter() + .map(|diagnostic| { + let message = diagnostic.message(); + let code = diagnostic.get_code().and_then(|m| { + if let DiagnosticId::Error(s) = m { + Some(s) + } else { + None + } + }); + + let span = diagnostic.span.clone(); + let suggestions = diagnostic.suggestions.clone(); + + let span_labels = span.span_labels(); + let highlights = if span_labels.is_empty() { + None + } else { + Some( + span_labels + .into_iter() + .flat_map(|span_label| { + if span_label.span.hi == span_label.span.lo { + None + } else { + Some(SourceLocation::from(source_map, span_label.span)) + } + }) + .collect(), + ) + }; + + let suggestions = if suggestions.is_empty() { + None + } else { + Some( + suggestions + .into_iter() + .map(|suggestion| suggestion.msg) + .collect(), + ) + }; + + Diagnostic { + file: origin.clone(), + code, + message, + highlights, + suggestions, + category: DiagnosticCategory::Error, + scope: DiagnosticScope::Optimizer, + } + }) + .collect() } pub struct PathData { - pub abs_path: PathBuf, - pub rel_path: PathBuf, - pub base_dir: PathBuf, - pub abs_dir: PathBuf, - pub rel_dir: PathBuf, - pub file_stem: String, - pub extension: String, - pub file_name: String, - pub file_prefix: String, + pub abs_path: PathBuf, + pub rel_path: PathBuf, + pub base_dir: PathBuf, + pub abs_dir: PathBuf, + pub rel_dir: PathBuf, + pub file_stem: String, + pub extension: String, + pub file_name: String, + pub file_prefix: String, } pub fn parse_path(src: &str, base_dir: &Path) -> Result { - let path = Path::new(src); - let file_stem = path - .file_stem() - .and_then(OsStr::to_str) - .map(Into::into) - .with_context(|| format!("Computing file stem for {}", path.to_string_lossy()))?; - - let rel_dir = path.parent().unwrap().to_path_buf(); - let extension = path.extension().and_then(OsStr::to_str).unwrap(); - let file_name = path - .file_name() - .and_then(OsStr::to_str) - .with_context(|| format!("Computing filename for {}", path.to_string_lossy()))?; - let file_prefix = file_name - .rsplitn(2, '.') - .last() - .with_context(|| format!("Computing file_prefix for {}", path.to_string_lossy()))?; - - let abs_path = normalize_path(base_dir.join(path)); - let abs_dir = normalize_path(abs_path.parent().unwrap()); - - Ok(PathData { - abs_path, - base_dir: base_dir.to_path_buf(), - rel_path: path.into(), - abs_dir, - rel_dir, - extension: extension.into(), - file_name: file_name.into(), - file_prefix: file_prefix.into(), - file_stem, - }) + let path = Path::new(src); + let file_stem = path + .file_stem() + .and_then(OsStr::to_str) + .map(Into::into) + .with_context(|| format!("Computing file stem for {}", path.to_string_lossy()))?; + + let rel_dir = path.parent().unwrap().to_path_buf(); + let extension = path.extension().and_then(OsStr::to_str).unwrap(); + let file_name = path + .file_name() + .and_then(OsStr::to_str) + .with_context(|| format!("Computing filename for {}", path.to_string_lossy()))?; + let file_prefix = file_name + .rsplitn(2, '.') + .last() + .with_context(|| format!("Computing file_prefix for {}", path.to_string_lossy()))?; + + let abs_path = normalize_path(base_dir.join(path)); + let abs_dir = normalize_path(abs_path.parent().unwrap()); + + Ok(PathData { + abs_path, + base_dir: base_dir.to_path_buf(), + rel_path: path.into(), + abs_dir, + rel_dir, + extension: extension.into(), + file_name: file_name.into(), + file_prefix: file_prefix.into(), + file_stem, + }) } pub fn normalize_path>(path: P) -> PathBuf { - let ends_with_slash = path.as_ref().to_str().map_or(false, |s| s.ends_with('/')); - let mut normalized = PathBuf::new(); - for component in path.as_ref().components() { - match &component { - Component::ParentDir => { - if !normalized.pop() { - normalized.push(component); - } - } - _ => { - normalized.push(component); - } - } - } - if ends_with_slash { - normalized.push(""); - } - normalized + let ends_with_slash = path.as_ref().to_str().map_or(false, |s| s.ends_with('/')); + let mut normalized = PathBuf::new(); + for component in path.as_ref().components() { + match &component { + Component::ParentDir => { + if !normalized.pop() { + normalized.push(component); + } + } + _ => { + normalized.push(component); + } + } + } + if ends_with_slash { + normalized.push(""); + } + normalized } pub fn might_need_handle_watch(ctx_kind: &HookKind, ctx_name: &str) -> bool { - if !matches!(ctx_kind, HookKind::Function) { - return false; - } - matches!( - ctx_name, - "useTask$" | "useVisibleTask$" | "useBrowserVisibleTask$" | "useClientEffect$" | "$" - ) + if !matches!(ctx_kind, HookKind::Function) { + return false; + } + matches!( + ctx_name, + "useTask$" | "useVisibleTask$" | "useBrowserVisibleTask$" | "useClientEffect$" | "$" + ) } diff --git a/packages/qwik/src/optimizer/core/src/props_destructuring.rs b/packages/qwik/src/optimizer/core/src/props_destructuring.rs index f0833161bdc..7c62d3a7d48 100644 --- a/packages/qwik/src/optimizer/core/src/props_destructuring.rs +++ b/packages/qwik/src/optimizer/core/src/props_destructuring.rs @@ -11,437 +11,437 @@ use swc_ecmascript::utils::private_ident; use swc_ecmascript::visit::{VisitMut, VisitMutWith}; struct PropsDestructuring<'a> { - component_ident: Option, - pub identifiers: HashMap, - pub global_collect: &'a mut GlobalCollect, - pub core_module: &'a JsWord, + component_ident: Option, + pub identifiers: HashMap, + pub global_collect: &'a mut GlobalCollect, + pub core_module: &'a JsWord, } pub fn transform_props_destructuring( - main_module: &mut ast::Module, - global_collect: &mut GlobalCollect, - core_module: &JsWord, + main_module: &mut ast::Module, + global_collect: &mut GlobalCollect, + core_module: &JsWord, ) { - main_module.visit_mut_with(&mut PropsDestructuring { - component_ident: global_collect.get_imported_local(&COMPONENT, core_module), - identifiers: HashMap::new(), - global_collect, - core_module, - }); + main_module.visit_mut_with(&mut PropsDestructuring { + component_ident: global_collect.get_imported_local(&COMPONENT, core_module), + identifiers: HashMap::new(), + global_collect, + core_module, + }); } macro_rules! id { - ($ident: expr) => { - ($ident.sym.clone(), $ident.span.ctxt()) - }; + ($ident: expr) => { + ($ident.sym.clone(), $ident.span.ctxt()) + }; } macro_rules! id_eq { - ($ident: expr, $cid: expr) => { - if let Some(cid) = $cid { - cid.0 == $ident.sym && cid.1 == $ident.span.ctxt() - } else { - false - } - }; + ($ident: expr, $cid: expr) => { + if let Some(cid) = $cid { + cid.0 == $ident.sym && cid.1 == $ident.span.ctxt() + } else { + false + } + }; } enum TransformInit { - Keep, - Remove, - Replace(ast::Expr), + Keep, + Remove, + Replace(ast::Expr), } impl<'a> PropsDestructuring<'a> { - fn transform_component_props(&mut self, arrow: &mut ast::ArrowExpr) { - if let Some(ast::Pat::Object(obj)) = arrow.params.first() { - let new_ident = private_ident!("props"); - if let Some((rest_id, local)) = - transform_pat(ast::Expr::Ident(new_ident.clone()), obj, self) - { - if let Some(rest_id) = rest_id { - let omit_fn = self.global_collect.import(&_REST_PROPS, self.core_module); - let omit = local.iter().map(|(_, id, _)| id.clone()).collect(); - transform_rest( - arrow, - &omit_fn, - &rest_id, - ast::Expr::Ident(new_ident.clone()), - omit, - ); - } - for (id, _, expr) in local { - self.identifiers.insert(id, expr); - } - arrow.params[0] = ast::Pat::Ident(ast::BindingIdent::from(new_ident)); - } - } - if let ast::BlockStmtOrExpr::BlockStmt(body) = &mut *arrow.body { - self.transform_component_body(body); - } - } - fn transform_component_body(&mut self, body: &mut ast::BlockStmt) { - let mut inserts = vec![]; - for (index, stmt) in body.stmts.iter_mut().enumerate() { - if let ast::Stmt::Decl(ast::Decl::Var(var_decl)) = stmt { - if var_decl.kind == ast::VarDeclKind::Const { - for decl in var_decl.decls.iter_mut() { - let convert = match &decl.init { - Some(box ast::Expr::Lit(lit)) => { - let new_ident = private_ident!("_unused"); - Some(( - new_ident, - ast::Expr::Lit(lit.clone()), - TransformInit::Remove, - )) - } - Some(box ast::Expr::Member(member_expr)) => match &member_expr.obj { - box ast::Expr::Ident(ident) => { - let new_ident = private_ident!("_unused"); - let expr = self - .identifiers - .get(&id!(ident.clone())) - .cloned() - .unwrap_or_else(|| ast::Expr::Ident(ident.clone())); + fn transform_component_props(&mut self, arrow: &mut ast::ArrowExpr) { + if let Some(ast::Pat::Object(obj)) = arrow.params.first() { + let new_ident = private_ident!("props"); + if let Some((rest_id, local)) = + transform_pat(ast::Expr::Ident(new_ident.clone()), obj, self) + { + if let Some(rest_id) = rest_id { + let omit_fn = self.global_collect.import(&_REST_PROPS, self.core_module); + let omit = local.iter().map(|(_, id, _)| id.clone()).collect(); + transform_rest( + arrow, + &omit_fn, + &rest_id, + ast::Expr::Ident(new_ident.clone()), + omit, + ); + } + for (id, _, expr) in local { + self.identifiers.insert(id, expr); + } + arrow.params[0] = ast::Pat::Ident(ast::BindingIdent::from(new_ident)); + } + } + if let ast::BlockStmtOrExpr::BlockStmt(body) = &mut *arrow.body { + self.transform_component_body(body); + } + } + fn transform_component_body(&mut self, body: &mut ast::BlockStmt) { + let mut inserts = vec![]; + for (index, stmt) in body.stmts.iter_mut().enumerate() { + if let ast::Stmt::Decl(ast::Decl::Var(var_decl)) = stmt { + if var_decl.kind == ast::VarDeclKind::Const { + for decl in var_decl.decls.iter_mut() { + let convert = match &decl.init { + Some(box ast::Expr::Lit(lit)) => { + let new_ident = private_ident!("_unused"); + Some(( + new_ident, + ast::Expr::Lit(lit.clone()), + TransformInit::Remove, + )) + } + Some(box ast::Expr::Member(member_expr)) => match &member_expr.obj { + box ast::Expr::Ident(ident) => { + let new_ident = private_ident!("_unused"); + let expr = self + .identifiers + .get(&id!(ident.clone())) + .cloned() + .unwrap_or_else(|| ast::Expr::Ident(ident.clone())); - let mut cloned_prop = member_expr.prop.clone(); - cloned_prop.visit_mut_with(self); - let new_replace = ast::Expr::Member(ast::MemberExpr { - obj: Box::new(expr), - prop: cloned_prop, - span: member_expr.span, - }); - Some((new_ident, new_replace, TransformInit::Remove)) - } - box ast::Expr::Call(call_expr) => { - if let ast::Callee::Expr(box ast::Expr::Ident(ref ident)) = - &call_expr.callee - { - if ident.sym.starts_with("use") { - let new_ident = - private_ident!(ident.sym[3..].to_lowercase()); + let mut cloned_prop = member_expr.prop.clone(); + cloned_prop.visit_mut_with(self); + let new_replace = ast::Expr::Member(ast::MemberExpr { + obj: Box::new(expr), + prop: cloned_prop, + span: member_expr.span, + }); + Some((new_ident, new_replace, TransformInit::Remove)) + } + box ast::Expr::Call(call_expr) => { + if let ast::Callee::Expr(box ast::Expr::Ident(ref ident)) = + &call_expr.callee + { + if ident.sym.starts_with("use") { + let new_ident = + private_ident!(ident.sym[3..].to_lowercase()); - let mut cloned_prop = member_expr.prop.clone(); - cloned_prop.visit_mut_with(self); + let mut cloned_prop = member_expr.prop.clone(); + cloned_prop.visit_mut_with(self); - let new_replace = ast::Expr::Member(ast::MemberExpr { - obj: Box::new(ast::Expr::Ident(new_ident.clone())), - prop: cloned_prop, - span: DUMMY_SP, - }); - Some(( - new_ident, - new_replace, - TransformInit::Replace(ast::Expr::Call( - call_expr.clone(), - )), - )) - } else { - None - } - } else { - None - } - } - _ => None, - }, - Some(box ast::Expr::Ident(ref ident)) => { - let new_ident = private_ident!("_unused"); - let new_replace = self - .identifiers - .get(&id!(ident.clone())) - .cloned() - .unwrap_or_else(|| ast::Expr::Ident(ident.clone())); - Some((new_ident, new_replace, TransformInit::Remove)) - } - Some(box ast::Expr::Call(call_expr)) => { - if let ast::Callee::Expr(box ast::Expr::Ident(ref ident)) = - &call_expr.callee - { - if ident.sym.starts_with("use") { - let new_ident = - private_ident!(ident.sym[3..].to_lowercase()); - let new_replace = ast::Expr::Ident(new_ident.clone()); - Some((new_ident, new_replace, TransformInit::Keep)) - } else { - None - } - } else { - None - } - } - _ => None, - }; - if let Some((replace_pat, new_ref, init)) = convert { - let keep_ident = matches!(init, TransformInit::Keep); - let mut transform_init = init; - match &decl.name { - ast::Pat::Ident(ident) => { - if !keep_ident { - self.identifiers.insert(id!(ident.id.clone()), new_ref); - decl.name = - ast::Pat::Ident(ast::BindingIdent::from(replace_pat)); - } else { - transform_init = TransformInit::Keep; - } - } - ast::Pat::Object(obj_pat) => { - if let Some((rest_id, local)) = - transform_pat(new_ref.clone(), obj_pat, self) - { - if let Some(rest_id) = rest_id { - let omit_fn = self - .global_collect - .import(&_REST_PROPS, self.core_module); - let omit = - local.iter().map(|(_, id, _)| id.clone()).collect(); + let new_replace = ast::Expr::Member(ast::MemberExpr { + obj: Box::new(ast::Expr::Ident(new_ident.clone())), + prop: cloned_prop, + span: DUMMY_SP, + }); + Some(( + new_ident, + new_replace, + TransformInit::Replace(ast::Expr::Call( + call_expr.clone(), + )), + )) + } else { + None + } + } else { + None + } + } + _ => None, + }, + Some(box ast::Expr::Ident(ref ident)) => { + let new_ident = private_ident!("_unused"); + let new_replace = self + .identifiers + .get(&id!(ident.clone())) + .cloned() + .unwrap_or_else(|| ast::Expr::Ident(ident.clone())); + Some((new_ident, new_replace, TransformInit::Remove)) + } + Some(box ast::Expr::Call(call_expr)) => { + if let ast::Callee::Expr(box ast::Expr::Ident(ref ident)) = + &call_expr.callee + { + if ident.sym.starts_with("use") { + let new_ident = + private_ident!(ident.sym[3..].to_lowercase()); + let new_replace = ast::Expr::Ident(new_ident.clone()); + Some((new_ident, new_replace, TransformInit::Keep)) + } else { + None + } + } else { + None + } + } + _ => None, + }; + if let Some((replace_pat, new_ref, init)) = convert { + let keep_ident = matches!(init, TransformInit::Keep); + let mut transform_init = init; + match &decl.name { + ast::Pat::Ident(ident) => { + if !keep_ident { + self.identifiers.insert(id!(ident.id.clone()), new_ref); + decl.name = + ast::Pat::Ident(ast::BindingIdent::from(replace_pat)); + } else { + transform_init = TransformInit::Keep; + } + } + ast::Pat::Object(obj_pat) => { + if let Some((rest_id, local)) = + transform_pat(new_ref.clone(), obj_pat, self) + { + if let Some(rest_id) = rest_id { + let omit_fn = self + .global_collect + .import(&_REST_PROPS, self.core_module); + let omit = + local.iter().map(|(_, id, _)| id.clone()).collect(); - let element = create_omit_props( - &omit_fn, &rest_id, new_ref, omit, - ); - inserts.push((index + 1 + inserts.len(), element)); - } - for (id, _, expr) in local { - self.identifiers.insert(id, expr); - } - decl.name = - ast::Pat::Ident(ast::BindingIdent::from(replace_pat)); - } else { - transform_init = TransformInit::Keep; - } - } - _ => { - transform_init = TransformInit::Keep; - } - } - match transform_init { - TransformInit::Remove => { - decl.init = None; - } - TransformInit::Replace(expr) => { - decl.init = Some(Box::new(expr)); - } - TransformInit::Keep => {} - } - } - } - } else { - break; - } - } - } + let element = create_omit_props( + &omit_fn, &rest_id, new_ref, omit, + ); + inserts.push((index + 1 + inserts.len(), element)); + } + for (id, _, expr) in local { + self.identifiers.insert(id, expr); + } + decl.name = + ast::Pat::Ident(ast::BindingIdent::from(replace_pat)); + } else { + transform_init = TransformInit::Keep; + } + } + _ => { + transform_init = TransformInit::Keep; + } + } + match transform_init { + TransformInit::Remove => { + decl.init = None; + } + TransformInit::Replace(expr) => { + decl.init = Some(Box::new(expr)); + } + TransformInit::Keep => {} + } + } + } + } else { + break; + } + } + } - for (index, stmt) in inserts { - body.stmts.insert(index, stmt); - } - } + for (index, stmt) in inserts { + body.stmts.insert(index, stmt); + } + } } impl<'a> VisitMut for PropsDestructuring<'a> { - fn visit_mut_call_expr(&mut self, node: &mut ast::CallExpr) { - if let ast::Callee::Expr(box ast::Expr::Ident(ref ident)) = &node.callee { - if id_eq!(ident, &self.component_ident) { - if let Some(first_arg) = node.args.first_mut() { - if let ast::Expr::Arrow(arrow) = &mut *first_arg.expr { - self.transform_component_props(arrow); - } - } - } - } + fn visit_mut_call_expr(&mut self, node: &mut ast::CallExpr) { + if let ast::Callee::Expr(box ast::Expr::Ident(ref ident)) = &node.callee { + if id_eq!(ident, &self.component_ident) { + if let Some(first_arg) = node.args.first_mut() { + if let ast::Expr::Arrow(arrow) = &mut *first_arg.expr { + self.transform_component_props(arrow); + } + } + } + } - node.visit_mut_children_with(self); - } + node.visit_mut_children_with(self); + } - fn visit_mut_expr(&mut self, node: &mut ast::Expr) { - match node { - ast::Expr::Ident(ident) => { - if let Some(expr) = self.identifiers.get(&id!(ident)) { - *node = expr.clone(); - } - } - _ => { - node.visit_mut_children_with(self); - } - } - } + fn visit_mut_expr(&mut self, node: &mut ast::Expr) { + match node { + ast::Expr::Ident(ident) => { + if let Some(expr) = self.identifiers.get(&id!(ident)) { + *node = expr.clone(); + } + } + _ => { + node.visit_mut_children_with(self); + } + } + } - fn visit_mut_prop(&mut self, node: &mut ast::Prop) { - if let ast::Prop::Shorthand(short) = node { - if let Some(expr) = self.identifiers.get(&id!(short)) { - *node = ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Ident(short.clone()), - value: Box::new(expr.clone()), - }); - } - } - node.visit_mut_children_with(self); - } + fn visit_mut_prop(&mut self, node: &mut ast::Prop) { + if let ast::Prop::Shorthand(short) = node { + if let Some(expr) = self.identifiers.get(&id!(short)) { + *node = ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Ident(short.clone()), + value: Box::new(expr.clone()), + }); + } + } + node.visit_mut_children_with(self); + } } type TransformPatReturn = (Option, Vec<(Id, JsWord, ast::Expr)>); fn transform_pat( - new_ident: ast::Expr, - obj: &ast::ObjectPat, - props_transform: &mut PropsDestructuring, + new_ident: ast::Expr, + obj: &ast::ObjectPat, + props_transform: &mut PropsDestructuring, ) -> Option { - let mut local = vec![]; - let mut skip = false; - let mut rest_id = None; - for prop in &obj.props { - match prop { - ast::ObjectPatProp::Assign(ref v) => { - let access = ast::Expr::Member(ast::MemberExpr { - obj: Box::new(new_ident.clone()), - prop: ast::MemberProp::Ident(v.key.clone()), - span: DUMMY_SP, - }); - if let Some(value) = &v.value { - if is_immutable_expr(value.as_ref(), props_transform.global_collect, None) { - local.push(( - id!(v.key), - v.key.sym.clone(), - ast::Expr::Bin(ast::BinExpr { - span: DUMMY_SP, - op: ast::BinaryOp::NullishCoalescing, - left: Box::new(access), - right: value.clone(), - }), - )); - } else { - skip = true; - } - } else { - local.push((id!(v.key), v.key.sym.clone(), access)); - } - } - ast::ObjectPatProp::KeyValue(ref v) => { - if let ast::PropName::Ident(ref key) = v.key { - match &v.value { - box ast::Pat::Ident(ref ident) => { - let access = ast::Expr::Member(ast::MemberExpr { - obj: Box::new(new_ident.clone()), - prop: ast::MemberProp::Ident(key.clone()), - span: DUMMY_SP, - }); - local.push((id!(ident), key.sym.clone(), access)); - } - box ast::Pat::Assign(ast::AssignPat { - left: box ast::Pat::Ident(ident), - right: value, - .. - }) => { - if is_immutable_expr( - value.as_ref(), - props_transform.global_collect, - None, - ) { - let access = ast::Expr::Member(ast::MemberExpr { - obj: Box::new(new_ident.clone()), - prop: ast::MemberProp::Ident(key.clone()), - span: DUMMY_SP, - }); - local.push(( - id!(ident.id), - key.sym.clone(), - ast::Expr::Bin(ast::BinExpr { - span: DUMMY_SP, - op: ast::BinaryOp::NullishCoalescing, - left: Box::new(access), - right: value.clone(), - }), - )); - } else { - skip = true; - } - } - _ => { - skip = true; - } - } - } else { - skip = true; - } - } - ast::ObjectPatProp::Rest(ast::RestPat { box arg, .. }) => { - if let ast::Pat::Ident(ref ident) = arg { - rest_id = Some(id!(&ident.id)); - } else { - skip = true; - } - } - } - } - if skip || local.is_empty() { - return None; - } - Some((rest_id, local)) + let mut local = vec![]; + let mut skip = false; + let mut rest_id = None; + for prop in &obj.props { + match prop { + ast::ObjectPatProp::Assign(ref v) => { + let access = ast::Expr::Member(ast::MemberExpr { + obj: Box::new(new_ident.clone()), + prop: ast::MemberProp::Ident(v.key.clone()), + span: DUMMY_SP, + }); + if let Some(value) = &v.value { + if is_immutable_expr(value.as_ref(), props_transform.global_collect, None) { + local.push(( + id!(v.key), + v.key.sym.clone(), + ast::Expr::Bin(ast::BinExpr { + span: DUMMY_SP, + op: ast::BinaryOp::NullishCoalescing, + left: Box::new(access), + right: value.clone(), + }), + )); + } else { + skip = true; + } + } else { + local.push((id!(v.key), v.key.sym.clone(), access)); + } + } + ast::ObjectPatProp::KeyValue(ref v) => { + if let ast::PropName::Ident(ref key) = v.key { + match &v.value { + box ast::Pat::Ident(ref ident) => { + let access = ast::Expr::Member(ast::MemberExpr { + obj: Box::new(new_ident.clone()), + prop: ast::MemberProp::Ident(key.clone()), + span: DUMMY_SP, + }); + local.push((id!(ident), key.sym.clone(), access)); + } + box ast::Pat::Assign(ast::AssignPat { + left: box ast::Pat::Ident(ident), + right: value, + .. + }) => { + if is_immutable_expr( + value.as_ref(), + props_transform.global_collect, + None, + ) { + let access = ast::Expr::Member(ast::MemberExpr { + obj: Box::new(new_ident.clone()), + prop: ast::MemberProp::Ident(key.clone()), + span: DUMMY_SP, + }); + local.push(( + id!(ident.id), + key.sym.clone(), + ast::Expr::Bin(ast::BinExpr { + span: DUMMY_SP, + op: ast::BinaryOp::NullishCoalescing, + left: Box::new(access), + right: value.clone(), + }), + )); + } else { + skip = true; + } + } + _ => { + skip = true; + } + } + } else { + skip = true; + } + } + ast::ObjectPatProp::Rest(ast::RestPat { box arg, .. }) => { + if let ast::Pat::Ident(ref ident) = arg { + rest_id = Some(id!(&ident.id)); + } else { + skip = true; + } + } + } + } + if skip || local.is_empty() { + return None; + } + Some((rest_id, local)) } fn transform_rest( - arrow: &mut ast::ArrowExpr, - omit_fn: &Id, - rest_id: &Id, - props_expr: ast::Expr, - omit: Vec, + arrow: &mut ast::ArrowExpr, + omit_fn: &Id, + rest_id: &Id, + props_expr: ast::Expr, + omit: Vec, ) { - let new_stmt = create_omit_props(omit_fn, rest_id, props_expr, omit); - match &mut arrow.body { - box ast::BlockStmtOrExpr::BlockStmt(block) => { - block.stmts.insert(0, new_stmt); - } - box ast::BlockStmtOrExpr::Expr(ref expr) => { - arrow.body = Box::new(ast::BlockStmtOrExpr::BlockStmt(ast::BlockStmt { - span: DUMMY_SP, - stmts: vec![new_stmt, create_return_stmt(expr.clone())], - })); - } - } + let new_stmt = create_omit_props(omit_fn, rest_id, props_expr, omit); + match &mut arrow.body { + box ast::BlockStmtOrExpr::BlockStmt(block) => { + block.stmts.insert(0, new_stmt); + } + box ast::BlockStmtOrExpr::Expr(ref expr) => { + arrow.body = Box::new(ast::BlockStmtOrExpr::BlockStmt(ast::BlockStmt { + span: DUMMY_SP, + stmts: vec![new_stmt, create_return_stmt(expr.clone())], + })); + } + } } fn create_omit_props( - omit_fn: &Id, - rest_id: &Id, - props_expr: ast::Expr, - omit: Vec, + omit_fn: &Id, + rest_id: &Id, + props_expr: ast::Expr, + omit: Vec, ) -> ast::Stmt { - ast::Stmt::Decl(ast::Decl::Var(Box::new(ast::VarDecl { - span: DUMMY_SP, - declare: false, - kind: ast::VarDeclKind::Const, - decls: vec![ast::VarDeclarator { - definite: false, - span: DUMMY_SP, - init: Some(Box::new(ast::Expr::Call(ast::CallExpr { - callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(omit_fn)))), - span: DUMMY_SP, - type_args: None, - args: vec![ - ast::ExprOrSpread { - spread: None, - expr: Box::new(props_expr), - }, - ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Array(ast::ArrayLit { - span: DUMMY_SP, - elems: omit - .into_iter() - .map(|v| { - Some(ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - value: v, - raw: None, - }))), - }) - }) - .collect(), - })), - }, - ], - }))), - name: ast::Pat::Ident(ast::BindingIdent::from(new_ident_from_id(rest_id))), - }], - }))) + ast::Stmt::Decl(ast::Decl::Var(Box::new(ast::VarDecl { + span: DUMMY_SP, + declare: false, + kind: ast::VarDeclKind::Const, + decls: vec![ast::VarDeclarator { + definite: false, + span: DUMMY_SP, + init: Some(Box::new(ast::Expr::Call(ast::CallExpr { + callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(omit_fn)))), + span: DUMMY_SP, + type_args: None, + args: vec![ + ast::ExprOrSpread { + spread: None, + expr: Box::new(props_expr), + }, + ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Array(ast::ArrayLit { + span: DUMMY_SP, + elems: omit + .into_iter() + .map(|v| { + Some(ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: v, + raw: None, + }))), + }) + }) + .collect(), + })), + }, + ], + }))), + name: ast::Pat::Ident(ast::BindingIdent::from(new_ident_from_id(rest_id))), + }], + }))) } diff --git a/packages/qwik/src/optimizer/core/src/test.rs b/packages/qwik/src/optimizer/core/src/test.rs index 38ecc540c90..38d670a2900 100644 --- a/packages/qwik/src/optimizer/core/src/test.rs +++ b/packages/qwik/src/optimizer/core/src/test.rs @@ -4,91 +4,91 @@ use super::*; use serde_json::to_string_pretty; macro_rules! test_input { - ($input: expr) => { - let input = $input; - let strip_exports: Option> = input - .strip_exports - .map(|v| v.into_iter().map(|s| JsWord::from(s)).collect()); - - let reg_ctx_name: Option> = input - .reg_ctx_name - .map(|v| v.into_iter().map(|s| JsWord::from(s)).collect()); - - let strip_ctx_name: Option> = input - .strip_ctx_name - .map(|v| v.into_iter().map(|s| JsWord::from(s)).collect()); - - let res = transform_modules(TransformModulesOptions { - src_dir: input.src_dir, - root_dir: input.root_dir, - input: vec![TransformModuleInput { - code: input.code.clone(), - path: input.filename, - }], - source_maps: true, - minify: input.minify, - transpile_ts: input.transpile_ts, - transpile_jsx: input.transpile_jsx, - preserve_filenames: input.preserve_filenames, - explicit_extensions: input.explicit_extensions, - manual_chunks: input.manual_chunks, - entry_strategy: input.entry_strategy, - mode: input.mode, - scope: input.scope, - core_module: input.core_module, - strip_exports, - strip_ctx_name, - reg_ctx_name, - strip_event_handlers: input.strip_event_handlers, - is_server: input.is_server, - }); - if input.snapshot { - let input = input.code.to_string(); - let output = format!("==INPUT==\n\n{}", input); - snapshot_res!(&res, output); - } - drop(res) - }; + ($input: expr) => { + let input = $input; + let strip_exports: Option> = input + .strip_exports + .map(|v| v.into_iter().map(|s| JsWord::from(s)).collect()); + + let reg_ctx_name: Option> = input + .reg_ctx_name + .map(|v| v.into_iter().map(|s| JsWord::from(s)).collect()); + + let strip_ctx_name: Option> = input + .strip_ctx_name + .map(|v| v.into_iter().map(|s| JsWord::from(s)).collect()); + + let res = transform_modules(TransformModulesOptions { + src_dir: input.src_dir, + root_dir: input.root_dir, + input: vec![TransformModuleInput { + code: input.code.clone(), + path: input.filename, + }], + source_maps: true, + minify: input.minify, + transpile_ts: input.transpile_ts, + transpile_jsx: input.transpile_jsx, + preserve_filenames: input.preserve_filenames, + explicit_extensions: input.explicit_extensions, + manual_chunks: input.manual_chunks, + entry_strategy: input.entry_strategy, + mode: input.mode, + scope: input.scope, + core_module: input.core_module, + strip_exports, + strip_ctx_name, + reg_ctx_name, + strip_event_handlers: input.strip_event_handlers, + is_server: input.is_server, + }); + if input.snapshot { + let input = input.code.to_string(); + let output = format!("==INPUT==\n\n{}", input); + snapshot_res!(&res, output); + } + drop(res) + }; } macro_rules! snapshot_res { - ($res: expr, $prefix: expr) => { - match $res { - Ok(v) => { - let mut output: String = $prefix; - - for module in &v.modules { - let is_entry = if module.is_entry { "(ENTRY POINT)" } else { "" }; - output += format!( - "\n============================= {} {}==\n\n{}\n\n{:?}", - module.path, is_entry, module.code, module.map - ) - .as_str(); - if let Some(hook) = &module.hook { - let hook = to_string_pretty(&hook).unwrap(); - output += &format!("\n/*\n{}\n*/", hook); - } - // let map = if let Some(map) = s.map { map } else { "".to_string() }; - // output += format!("\n== MAP ==\n{}", map).as_str(); - } - output += format!( - "\n== DIAGNOSTICS ==\n\n{}", - to_string_pretty(&v.diagnostics).unwrap() - ) - .as_str(); - insta::assert_display_snapshot!(output); - } - Err(err) => { - insta::assert_display_snapshot!(err); - } - } - }; + ($res: expr, $prefix: expr) => { + match $res { + Ok(v) => { + let mut output: String = $prefix; + + for module in &v.modules { + let is_entry = if module.is_entry { "(ENTRY POINT)" } else { "" }; + output += format!( + "\n============================= {} {}==\n\n{}\n\n{:?}", + module.path, is_entry, module.code, module.map + ) + .as_str(); + if let Some(hook) = &module.hook { + let hook = to_string_pretty(&hook).unwrap(); + output += &format!("\n/*\n{}\n*/", hook); + } + // let map = if let Some(map) = s.map { map } else { "".to_string() }; + // output += format!("\n== MAP ==\n{}", map).as_str(); + } + output += format!( + "\n== DIAGNOSTICS ==\n\n{}", + to_string_pretty(&v.diagnostics).unwrap() + ) + .as_str(); + insta::assert_display_snapshot!(output); + } + Err(err) => { + insta::assert_display_snapshot!(err); + } + } + }; } #[test] fn example_1() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component, onRender } from '@builder.io/qwik'; export const renderHeader = $(() => { @@ -101,15 +101,15 @@ const renderHeader = component($(() => { return render; })); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_2() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const Header = component$(() => { console.log("mount"); @@ -118,15 +118,15 @@ export const Header = component$(() => { ); }); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_3() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const App = () => { const Header = component$(() => { @@ -138,15 +138,15 @@ export const App = () => { return Header; }); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_4() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export function App() { const Header = component$(() => { @@ -158,15 +158,15 @@ export function App() { return Header; } "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_5() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const Header = component$(() => { return ( @@ -177,27 +177,27 @@ export const Header = component$(() => { ); }); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_6() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const sym1 = $((ctx) => console.log("1")); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_7() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const Header = component$(() => { @@ -213,15 +213,15 @@ const App = component$(() => { ); }); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_8() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const Header = component$(() => { @@ -235,15 +235,15 @@ export const Header = component$(() => { }); }); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_9() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; const Header = $((decl1, {decl2}, [decl3]) => { const {decl4, key: decl5} = this; @@ -257,16 +257,16 @@ const Header = $((decl1, {decl2}, [decl3]) => { try{}catch({decl19}){} }); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_10() { - test_input!(TestInput { - filename: "project/test.tsx".to_string(), - code: r#" + test_input!(TestInput { + filename: "project/test.tsx".to_string(), + code: r#" import { $, component$ } from '@builder.io/qwik'; const Header = $((decl1, {decl2}, [decl3]) => { @@ -287,16 +287,16 @@ const Header = $((decl1, {decl2}, [decl3]) => { ) }); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_11() { - test_input!(TestInput { - filename: "project/test.tsx".to_string(), - code: r#" + test_input!(TestInput { + filename: "project/test.tsx".to_string(), + code: r#" import { $, component$ } from '@builder.io/qwik'; import {foo, bar as bbar} from "../state"; import * as dep2 from "dep2"; @@ -316,16 +316,16 @@ export const App = component$(() => { ); }); "# - .to_string(), - entry_strategy: EntryStrategy::Single, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Single, + ..TestInput::default() + }); } #[test] fn example_functional_component() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, useStore } from '@builder.io/qwik'; const Header = component$(() => { const thing = useStore(); @@ -336,16 +336,16 @@ const Header = component$(() => { ); }); "# - .to_string(), - minify: MinifyMode::None, - ..TestInput::default() - }); + .to_string(), + minify: MinifyMode::None, + ..TestInput::default() + }); } #[test] fn example_functional_component_2() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, useStore } from '@builder.io/qwik'; export const useCounter = () => { return useStore({count: 0}); @@ -375,17 +375,17 @@ export const App = component$((props) => { ); }) "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_functional_component_capture_props() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, useStore } from '@builder.io/qwik'; export const App = component$(({count, rest: [I2, {I3, v1: [I4], I5=v2, ...I6}, I7=v3, ...I8]}) => { @@ -402,17 +402,17 @@ export const App = component$(({count, rest: [I2, {I3, v1: [I4], I5=v2, ...I6}, }); }) "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_multi_capture() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const Foo = component$(({foo}) => { @@ -437,16 +437,16 @@ export const Bar = component$(({bar}) => { }); }) "# - .to_string(), - transpile_ts: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + ..TestInput::default() + }); } #[test] fn example_dead_code() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; import { deps } from 'deps'; @@ -461,16 +461,16 @@ export const Foo = component$(({foo}) => { ); }) "# - .to_string(), - minify: MinifyMode::Simplify, - ..TestInput::default() - }); + .to_string(), + minify: MinifyMode::Simplify, + ..TestInput::default() + }); } #[test] fn example_with_tagname() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const Foo = component$(() => { @@ -484,15 +484,15 @@ export const Foo = component$(() => { tagName: "my-foo", }); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_with_style() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, useStyles$ } from '@builder.io/qwik'; export const Foo = component$(() => { @@ -504,15 +504,15 @@ export const Foo = component$(() => { tagName: "my-foo", }); "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_props_optimization() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, useTask$ } from '@builder.io/qwik'; import { CONST } from 'const'; export const Works = component$(({ @@ -554,18 +554,18 @@ export const NoWorks3 = component$(({count, stuff = hola()}) => { ); }); "# - .to_string(), - transpile_jsx: true, - entry_strategy: EntryStrategy::Inline, - transpile_ts: true, - ..TestInput::default() - }); + .to_string(), + transpile_jsx: true, + entry_strategy: EntryStrategy::Inline, + transpile_ts: true, + ..TestInput::default() + }); } #[test] fn example_use_optimization() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, useTask$ } from '@builder.io/qwik'; import { CONST } from 'const'; export const Works = component$((props) => { @@ -581,19 +581,19 @@ export const Works = component$((props) => { ); }); "# - .to_string(), - transpile_jsx: false, - entry_strategy: EntryStrategy::Inline, - transpile_ts: true, - is_server: Some(false), - ..TestInput::default() - }); + .to_string(), + transpile_jsx: false, + entry_strategy: EntryStrategy::Inline, + transpile_ts: true, + is_server: Some(false), + ..TestInput::default() + }); } #[test] fn example_optimization_issue_3561() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; export const Issue3561 = component$(() => { @@ -615,19 +615,19 @@ export const Issue3561 = component$(() => { return

; }); "# - .to_string(), - transpile_jsx: false, - entry_strategy: EntryStrategy::Inline, - transpile_ts: true, - is_server: Some(false), - ..TestInput::default() - }); + .to_string(), + transpile_jsx: false, + entry_strategy: EntryStrategy::Inline, + transpile_ts: true, + is_server: Some(false), + ..TestInput::default() + }); } #[test] fn example_optimization_issue_4386() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; export const FOO_MAPPING = { @@ -643,18 +643,18 @@ export const FOO_MAPPING = { return <>{value}; }); "# - .to_string(), - transpile_jsx: false, - entry_strategy: EntryStrategy::Inline, - transpile_ts: true, - is_server: Some(false), - ..TestInput::default() - }); + .to_string(), + transpile_jsx: false, + entry_strategy: EntryStrategy::Inline, + transpile_ts: true, + is_server: Some(false), + ..TestInput::default() + }); } #[test] fn example_optimization_issue_3542() { - test_input!(TestInput { + test_input!(TestInput { code: r#" import { component$ } from '@builder.io/qwik'; @@ -682,8 +682,8 @@ export const AtomStatus = component$(({ctx,atom})=>{ #[test] fn example_optimization_issue_3795() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; export const Issue3795 = component$(() => { @@ -696,19 +696,19 @@ export const Issue3795 = component$(() => { ) }); "# - .to_string(), - entry_strategy: EntryStrategy::Inline, - transpile_ts: true, - transpile_jsx: true, - is_server: Some(false), - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Inline, + transpile_ts: true, + transpile_jsx: true, + is_server: Some(false), + ..TestInput::default() + }); } #[test] fn example_drop_side_effects() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; import { server$ } from '@builder.io/qwik-city'; import { clientSupabase } from 'supabase'; @@ -738,20 +738,20 @@ export default component$(() => { ) }); "# - .to_string(), - entry_strategy: EntryStrategy::Hook, - strip_ctx_name: Some(vec!["server".into()]), - transpile_ts: true, - transpile_jsx: true, - is_server: Some(false), - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Hook, + strip_ctx_name: Some(vec!["server".into()]), + transpile_ts: true, + transpile_jsx: true, + is_server: Some(false), + ..TestInput::default() + }); } #[test] fn example_reg_ctx_name_hooks() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, server$ } from '@builder.io/qwik'; import { foo } from './foo'; export const Works = component$((props) => { @@ -764,20 +764,20 @@ export const Works = component$((props) => { ); }); "# - .to_string(), - entry_strategy: EntryStrategy::Inline, - reg_ctx_name: Some(vec!["server".into()]), - strip_event_handlers: true, - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Inline, + reg_ctx_name: Some(vec!["server".into()]), + strip_event_handlers: true, + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_reg_ctx_name_hooks_inlined() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, server$ } from '@builder.io/qwik'; export const Works = component$((props) => { const text = 'hola'; @@ -786,19 +786,19 @@ export const Works = component$((props) => { ); }); "# - .to_string(), - entry_strategy: EntryStrategy::Inline, - reg_ctx_name: Some(vec!["server".into()]), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Inline, + reg_ctx_name: Some(vec!["server".into()]), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_reg_ctx_name_hooks_hoisted() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, server$, useStyle$ } from '@builder.io/qwik'; export const Works = component$((props) => { @@ -811,18 +811,18 @@ export const Works = component$((props) => { const STYLES = '.class {}'; "# - .to_string(), - entry_strategy: EntryStrategy::Hoist, - reg_ctx_name: Some(vec!["server".into()]), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Hoist, + reg_ctx_name: Some(vec!["server".into()]), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_lightweight_functional() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const Foo = component$(({color}) => { @@ -848,15 +848,15 @@ export const ButtonArrow = ({text, color}) => { ); } "# - .to_string(), - ..TestInput::default() - }); + .to_string(), + ..TestInput::default() + }); } #[test] fn example_invalid_references() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; const I1 = 12; @@ -874,17 +874,17 @@ export const App = component$(({count}) => { }); }) "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_invalid_hook_expr1() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, useStyles$ } from '@builder.io/qwik'; import css1 from './global.css'; import css2 from './style.css'; @@ -900,17 +900,17 @@ export const App = component$(() => { return $(render); }) "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_capture_imports() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStyles$ } from '@builder.io/qwik'; import css1 from './global.css'; import css2 from './style.css'; @@ -921,17 +921,17 @@ export const App = component$(() => { useStyles$(css3); }) "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_capturing_fn_class() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const App = component$(() => { @@ -950,17 +950,17 @@ export const App = component$(() => { }); }) "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_renamed_exports() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ as Component, $ as onRender, useStore } from '@builder.io/qwik'; export const App = Component((props) => { @@ -971,18 +971,18 @@ export const App = Component((props) => { )); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_exports() { - test_input!(TestInput { - filename: "project/test.tsx".to_string(), - code: r#" + test_input!(TestInput { + filename: "project/test.tsx".to_string(), + code: r#" import { $, component$ } from '@builder.io/qwik'; export const [a, {b, v1: [c], d=v2, ...e}, f=v3, ...g] = obj; @@ -1007,29 +1007,29 @@ export const Header = component$(() => { export const Footer = component$(); "# - .to_string(), - transpile_ts: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + ..TestInput::default() + }); } #[test] fn issue_117() { - test_input!(TestInput { - filename: "project/test.tsx".to_string(), - code: r#" + test_input!(TestInput { + filename: "project/test.tsx".to_string(), + code: r#" export const cache = patternCache[cacheKey] || (patternCache[cacheKey]={}); "# - .to_string(), - entry_strategy: EntryStrategy::Single, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Single, + ..TestInput::default() + }); } #[test] fn example_jsx() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, h, Fragment } from '@builder.io/qwik'; export const Lightweight = (props) => { @@ -1070,17 +1070,17 @@ export const Foo = component$((props) => { tagName: "my-foo", }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_jsx_listeners() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$ } from '@builder.io/qwik'; export const Foo = component$(() => { @@ -1113,17 +1113,17 @@ export const Foo = component$(() => { tagName: "my-foo", }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_qwik_conflict() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { $, component$, useStyles } from '@builder.io/qwik'; import { qrl } from '@builder.io/qwik/what'; @@ -1156,18 +1156,18 @@ export const Root = component$(() => { tagName: "my-foo", }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_fix_dynamic_import() { - test_input!(TestInput { - filename: "project/folder/test.tsx".to_string(), - code: r#" + test_input!(TestInput { + filename: "project/folder/test.tsx".to_string(), + code: r#" import { $, component$ } from '@builder.io/qwik'; import thing from "../state"; @@ -1184,16 +1184,16 @@ export const Header = component$(() => { ); }); "# - .to_string(), - entry_strategy: EntryStrategy::Single, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Single, + ..TestInput::default() + }); } #[test] fn example_custom_inlined_functions() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, $, useStore, wrap, useEffect } from '@builder.io/qwik'; export const useMemoQrl = (qrt) => { @@ -1218,16 +1218,16 @@ export const Lightweight = (props) => { }); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_missing_custom_inlined_functions() { - test_input!(TestInput { + test_input!(TestInput { code: r#" import { component$ as Component, $ as onRender, useStore, wrap, useEffect } from '@builder.io/qwik'; @@ -1255,8 +1255,8 @@ export const App = component$((props) => { #[test] fn example_skip_transform() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ as Component, $ as onRender } from '@builder.io/qwik'; export const handler = $(()=>console.log('hola')); @@ -1268,17 +1268,17 @@ export const App = component$((props) => { )); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_explicit_ext_transpile() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, $, useStyles$ } from '@builder.io/qwik'; export const App = component$((props) => { @@ -1288,18 +1288,18 @@ export const App = component$((props) => { )); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_explicit_ext_no_transpile() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, $, useStyles$ } from '@builder.io/qwik'; export const App = component$((props) => { @@ -1309,17 +1309,17 @@ export const App = component$((props) => { )); }); "# - .to_string(), - explicit_extensions: true, - entry_strategy: EntryStrategy::Single, - ..TestInput::default() - }); + .to_string(), + explicit_extensions: true, + entry_strategy: EntryStrategy::Single, + ..TestInput::default() + }); } #[test] fn example_jsx_import_source() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" /* @jsxImportSource react */ import { qwikify$ } from './qwikfy'; @@ -1332,18 +1332,18 @@ export const App2 = qwikify$(() => (
console.log('App2')}>
)); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_prod_node() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; export const Foo = component$(() => { @@ -1356,16 +1356,16 @@ export const Foo = component$(() => { ); }); "# - .to_string(), - mode: EmitMode::Prod, - ..TestInput::default() - }); + .to_string(), + mode: EmitMode::Prod, + ..TestInput::default() + }); } #[test] fn example_use_client_effect() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useBrowserVisibleTask$, useStore, useStyles$ } from '@builder.io/qwik'; export const Child = component$(() => { @@ -1391,17 +1391,17 @@ export const Child = component$(() => { }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_inlined_entry_strategy() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useBrowserVisibleTask$, useStore, useStyles$ } from '@builder.io/qwik'; import { thing } from './sibling'; import mongodb from 'mongodb'; @@ -1425,16 +1425,16 @@ export const Child = component$(() => { }); "# - .to_string(), - entry_strategy: EntryStrategy::Inline, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Inline, + ..TestInput::default() + }); } #[test] fn example_default_export() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; import { sibling } from './sibling'; @@ -1446,20 +1446,20 @@ export default component$(() => { }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - filename: "src/routes/_repl/[id]/[[...slug]].tsx".into(), - entry_strategy: EntryStrategy::Smart, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + filename: "src/routes/_repl/[id]/[[...slug]].tsx".into(), + entry_strategy: EntryStrategy::Smart, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_default_export_index() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; export default component$(() => { @@ -1470,17 +1470,17 @@ export default component$(() => { }); "# - .to_string(), - filename: "src/components/mongo/index.tsx".into(), - entry_strategy: EntryStrategy::Inline, - ..TestInput::default() - }); + .to_string(), + filename: "src/components/mongo/index.tsx".into(), + entry_strategy: EntryStrategy::Inline, + ..TestInput::default() + }); } #[test] fn example_default_export_invalid_ident() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; export default component$(() => { @@ -1491,16 +1491,16 @@ export default component$(() => { }); "# - .to_string(), - filename: "src/components/mongo/404.tsx".into(), - ..TestInput::default() - }); + .to_string(), + filename: "src/components/mongo/404.tsx".into(), + ..TestInput::default() + }); } #[test] fn example_parsed_inlined_qrls() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { componentQrl, inlinedQrl, useStore, jsxs, jsx, useLexicalScope } from '@builder.io/qwik'; export const App = /*#__PURE__*/ componentQrl(inlinedQrl(()=>{ @@ -1536,18 +1536,18 @@ export const App = /*#__PURE__*/ componentQrl(inlinedQrl(()=>{ export const STYLES = ".red { color: red; }"; "# - .to_string(), - entry_strategy: EntryStrategy::Inline, - mode: EmitMode::Prod, - transpile_ts: false, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Inline, + mode: EmitMode::Prod, + transpile_ts: false, + ..TestInput::default() + }); } #[test] fn example_use_server_mount() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useServerMount$, useStore, useStyles$ } from '@builder.io/qwik'; import mongo from 'mongodb'; import redis from 'redis'; @@ -1587,18 +1587,18 @@ export const Child = component$(() => { ); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - entry_strategy: EntryStrategy::Smart, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + entry_strategy: EntryStrategy::Smart, + ..TestInput::default() + }); } #[test] fn example_manual_chunks() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useTask$, useStore, useStyles$ } from '@builder.io/qwik'; import mongo from 'mongodb'; import redis from 'redis'; @@ -1635,25 +1635,25 @@ export const Child = component$(() => {
console.log('child')}> {state.text}
- ); -}); -"# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - manual_chunks: Some(HashMap::from_iter(vec![ - ("C5XE49Nqd3A".into(), "chunk_clicks".into()), - ("elliVSnAiOQ".into(), "chunk_clicks".into()), - ])), - entry_strategy: EntryStrategy::Smart, - ..TestInput::default() - }); + ); +}); +"# + .to_string(), + transpile_ts: true, + transpile_jsx: true, + manual_chunks: Some(HashMap::from_iter(vec![ + ("C5XE49Nqd3A".into(), "chunk_clicks".into()), + ("elliVSnAiOQ".into(), "chunk_clicks".into()), + ])), + entry_strategy: EntryStrategy::Smart, + ..TestInput::default() + }); } #[test] fn example_strip_exports_unused() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; import mongodb from 'mongodb'; @@ -1670,16 +1670,16 @@ export default component$(()=> { return
cmp
}); "# - .to_string(), - strip_exports: Some(vec!["onGet".into()]), - ..TestInput::default() - }); + .to_string(), + strip_exports: Some(vec!["onGet".into()]), + ..TestInput::default() + }); } #[test] fn example_strip_exports_used() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useResource$ } from '@builder.io/qwik'; import mongodb from 'mongodb'; @@ -1699,15 +1699,15 @@ export default component$(()=> { return
cmp
}); "# - .to_string(), - strip_exports: Some(vec!["onGet".into()]), - ..TestInput::default() - }); + .to_string(), + strip_exports: Some(vec!["onGet".into()]), + ..TestInput::default() + }); } #[test] fn example_strip_server_code() { - test_input!(TestInput { + test_input!(TestInput { code: r#" import { component$, useServerMount$, serverLoader$, serverStuff$, $, client$, useStore, useTask$ } from '@builder.io/qwik'; import mongo from 'mongodb'; @@ -1760,8 +1760,8 @@ export const Parent = component$(() => { #[test] fn example_server_auth() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import GitHub from '@auth/core/providers/github' import Facebook from 'next-auth/providers/facebook' import Google from 'next-auth/providers/google' @@ -1801,18 +1801,18 @@ export const { onRequest, logout, getSession, signup } = auth$({ ] }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - entry_strategy: EntryStrategy::Hook, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + entry_strategy: EntryStrategy::Hook, + ..TestInput::default() + }); } #[test] fn example_strip_client_code() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useClientMount$, useStore, useTask$ } from '@builder.io/qwik'; import mongo from 'mongodb'; import redis from 'redis'; @@ -1850,21 +1850,21 @@ export const Parent = component$(() => { ); }); "# - .to_string(), - filename: "components/component.tsx".into(), - transpile_ts: true, - transpile_jsx: true, - entry_strategy: EntryStrategy::Inline, - strip_ctx_name: Some(vec!["useClientMount$".into(),]), - strip_event_handlers: true, - ..TestInput::default() - }); + .to_string(), + filename: "components/component.tsx".into(), + transpile_ts: true, + transpile_jsx: true, + entry_strategy: EntryStrategy::Inline, + strip_ctx_name: Some(vec!["useClientMount$".into(),]), + strip_event_handlers: true, + ..TestInput::default() + }); } #[test] fn issue_150() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, $ } from '@builder.io/qwik'; import { hola } from 'sdfds'; @@ -1885,17 +1885,17 @@ export const Greeter = component$(() => { const d = $(()=>console.log('thing')); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_input_bind() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, $ } from '@builder.io/qwik'; export const Greeter = component$(() => { @@ -1914,19 +1914,19 @@ export const Greeter = component$(() => { ) }); "# - .to_string(), - entry_strategy: EntryStrategy::Inline, - transpile_ts: true, - transpile_jsx: true, - mode: EmitMode::Prod, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Inline, + transpile_ts: true, + transpile_jsx: true, + mode: EmitMode::Prod, + ..TestInput::default() + }); } #[test] fn example_import_assertion() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, $ } from '@builder.io/qwik'; import json from "./foo.json" assert { type: "json" }; @@ -1934,20 +1934,20 @@ export const Greeter = component$(() => { return json; }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[cfg(target_os = "windows")] #[test] fn issue_188() { - let res = test_input!({ - filename: r"components\apps\apps.tsx".to_string(), - src_dir: r"C:\users\apps".to_string(), - code: r#" + let res = test_input!({ + filename: r"components\apps\apps.tsx".to_string(), + src_dir: r"C:\users\apps".to_string(), + code: r#" import { component$, $ } from '@builder.io/qwik'; export const Greeter = component$(() => { @@ -1960,19 +1960,19 @@ export const Greeter = component$(() => { const d = $(()=>console.log('thing')); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - snapshot: false, - }) - .unwrap(); - let last_module = res.modules.last().unwrap(); - assert_eq!(last_module.path, r"C:/users/apps/components/apps/apps.tsx") + .to_string(), + transpile_ts: true, + transpile_jsx: true, + snapshot: false, + }) + .unwrap(); + let last_module = res.modules.last().unwrap(); + assert_eq!(last_module.path, r"C:/users/apps/components/apps/apps.tsx") } #[test] fn issue_476() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { Counter } from "./counter.tsx"; export const Root = () => { @@ -1989,17 +1989,17 @@ export const Root = () => { ); }; "# - .to_string(), - transpile_ts: false, - transpile_jsx: false, - ..TestInput::default() - }); + .to_string(), + transpile_ts: false, + transpile_jsx: false, + ..TestInput::default() + }); } #[test] fn issue_964() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; export const App = component$(() => { @@ -2010,17 +2010,17 @@ export const App = component$(() => { return

Hello Qwik

; }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_immutable_analysis() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore, $ } from '@builder.io/qwik'; import importedValue from 'v'; import styles from './styles.module.css'; @@ -2071,17 +2071,17 @@ export const App = component$((props) => { ); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_ts_enums_issue_1341() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; enum Thing { @@ -2098,17 +2098,17 @@ export const App = component$(() => { ); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_ts_enums_no_transpile() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export enum Thing { @@ -2125,18 +2125,18 @@ export const App = component$(() => { ); }); "# - .to_string(), - transpile_ts: false, - transpile_jsx: false, + .to_string(), + transpile_ts: false, + transpile_jsx: false, - ..TestInput::default() - }); + ..TestInput::default() + }); } #[test] fn example_ts_enums() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export enum Thing { @@ -2153,17 +2153,17 @@ export const App = component$(() => { ); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_dev_mode() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export const App = component$(() => { @@ -2174,18 +2174,18 @@ export const App = component$(() => { ); }); "# - .to_string(), - mode: EmitMode::Dev, - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + mode: EmitMode::Dev, + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_dev_mode_inlined() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export const App = component$(() => { @@ -2196,19 +2196,19 @@ export const App = component$(() => { ); }); "# - .to_string(), - mode: EmitMode::Dev, - entry_strategy: EntryStrategy::Inline, - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + mode: EmitMode::Dev, + entry_strategy: EntryStrategy::Inline, + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_transpile_jsx_only() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export const App = component$((props: Stuff) => { @@ -2219,18 +2219,18 @@ export const App = component$((props: Stuff) => { ); }); "# - .to_string(), - transpile_ts: false, - transpile_jsx: true, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: false, + transpile_jsx: true, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_spread_jsx() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; import { useDocumentHead, useLocation } from '@builder.io/qwik-city'; @@ -2263,17 +2263,17 @@ export const RouterHead = component$(() => { ); });"# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_export_issue() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; const App = component$(() => { @@ -2294,17 +2294,17 @@ export { Other as App }; export default App; "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_jsx_keyed() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export const App = component$((props: Stuff) => { @@ -2319,18 +2319,18 @@ export const App = component$((props: Stuff) => { ); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_jsx_keyed_dev() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export const App = component$((props: Stuff) => { @@ -2345,21 +2345,21 @@ export const App = component$((props: Stuff) => { ); }); "# - .to_string(), - filename: "project/index.tsx".into(), - src_dir: "/src/project".into(), - transpile_ts: true, - transpile_jsx: true, - mode: EmitMode::Dev, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + filename: "project/index.tsx".into(), + src_dir: "/src/project".into(), + transpile_ts: true, + transpile_jsx: true, + mode: EmitMode::Dev, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_mutable_children() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore, Slot, Fragment } from '@builder.io/qwik'; import Image from './image.jpg?jsx'; @@ -2455,19 +2455,19 @@ export const AppStatic = component$((props: Stuff) => { ); }); "# - .to_string(), - entry_strategy: EntryStrategy::Hoist, - transpile_ts: true, - transpile_jsx: true, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Hoist, + transpile_ts: true, + transpile_jsx: true, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_immutable_function_components() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore, Slot } from '@builder.io/qwik'; export const App = component$((props: Stuff) => { @@ -2478,18 +2478,18 @@ export const App = component$((props: Stuff) => { ); }); "# - .to_string(), - entry_strategy: EntryStrategy::Hoist, - transpile_ts: true, - transpile_jsx: true, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Hoist, + transpile_ts: true, + transpile_jsx: true, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_transpile_ts_only() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export const App = component$((props: Stuff) => { @@ -2500,19 +2500,19 @@ export const App = component$((props: Stuff) => { ); }); "# - .to_string(), - entry_strategy: EntryStrategy::Inline, - transpile_ts: true, - transpile_jsx: false, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Inline, + transpile_ts: true, + transpile_jsx: false, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_class_name() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$ } from '@builder.io/qwik'; export const App2 = component$(() => { @@ -2533,18 +2533,18 @@ export const App2 = component$(() => { ); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_preserve_filenames() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export const App = component$((props: Stuff) => { @@ -2555,20 +2555,20 @@ export const App = component$((props: Stuff) => { ); }); "# - .to_string(), - entry_strategy: EntryStrategy::Inline, - transpile_ts: false, - transpile_jsx: true, - preserve_filenames: true, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Inline, + transpile_ts: false, + transpile_jsx: true, + preserve_filenames: true, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_preserve_filenames_hooks() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export const App = component$((props: Stuff) => { @@ -2582,20 +2582,20 @@ export const App = component$((props: Stuff) => { export const foo = () => console.log('foo'); "# - .to_string(), - entry_strategy: EntryStrategy::Hook, - transpile_ts: true, - transpile_jsx: true, - preserve_filenames: true, - explicit_extensions: true, - ..TestInput::default() - }); + .to_string(), + entry_strategy: EntryStrategy::Hook, + transpile_ts: true, + transpile_jsx: true, + preserve_filenames: true, + explicit_extensions: true, + ..TestInput::default() + }); } #[test] fn example_build_server() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; import { isServer, isBrowser } from '@builder.io/qwik/build'; import { mongodb } from 'mondodb'; @@ -2628,17 +2628,17 @@ export const App = component$(() => { ); }); "# - .to_string(), - is_server: Some(true), - mode: EmitMode::Prod, - ..TestInput::default() - }); + .to_string(), + is_server: Some(true), + mode: EmitMode::Prod, + ..TestInput::default() + }); } #[test] fn example_derived_signals_div() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore, mutable } from '@builder.io/qwik'; import {dep} from './file'; @@ -2691,18 +2691,18 @@ export const App = component$((props) => { ); }); "# - .to_string(), - transpile_jsx: true, - transpile_ts: true, - entry_strategy: EntryStrategy::Hoist, - ..TestInput::default() - }); + .to_string(), + transpile_jsx: true, + transpile_ts: true, + entry_strategy: EntryStrategy::Hoist, + ..TestInput::default() + }); } #[test] fn example_issue_4438() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useSignal } from '@builder.io/qwik'; export const App = component$(() => { @@ -2715,18 +2715,18 @@ export const App = component$(() => { ); }); "# - .to_string(), - transpile_jsx: true, - transpile_ts: true, - entry_strategy: EntryStrategy::Hoist, - ..TestInput::default() - }); + .to_string(), + transpile_jsx: true, + transpile_ts: true, + entry_strategy: EntryStrategy::Hoist, + ..TestInput::default() + }); } #[test] fn example_derived_signals_children() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore, mutable } from '@builder.io/qwik'; import {dep} from './file'; @@ -2770,18 +2770,18 @@ export const App = component$(() => { ); }); "# - .to_string(), - transpile_jsx: true, - transpile_ts: true, - entry_strategy: EntryStrategy::Hoist, - ..TestInput::default() - }); + .to_string(), + transpile_jsx: true, + transpile_ts: true, + entry_strategy: EntryStrategy::Hoist, + ..TestInput::default() + }); } #[test] fn example_derived_signals_multiple_children() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore, mutable } from '@builder.io/qwik'; import {dep} from './file'; @@ -2816,18 +2816,18 @@ export const App = component$(() => { ); }); "# - .to_string(), - transpile_jsx: true, - transpile_ts: true, - entry_strategy: EntryStrategy::Hoist, - ..TestInput::default() - }); + .to_string(), + transpile_jsx: true, + transpile_ts: true, + entry_strategy: EntryStrategy::Hoist, + ..TestInput::default() + }); } #[test] fn example_derived_signals_complext_children() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore, mutable } from '@builder.io/qwik'; import {dep} from './file'; @@ -2848,18 +2848,18 @@ export const App = component$(() => { ); }); "# - .to_string(), - transpile_jsx: true, - transpile_ts: true, - entry_strategy: EntryStrategy::Hoist, - ..TestInput::default() - }); + .to_string(), + transpile_jsx: true, + transpile_ts: true, + entry_strategy: EntryStrategy::Hoist, + ..TestInput::default() + }); } #[test] fn example_derived_signals_cmp() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore, mutable } from '@builder.io/qwik'; import {dep} from './file'; @@ -2901,17 +2901,17 @@ export const App = component$(() => { ); }); "# - .to_string(), - transpile_jsx: true, - transpile_ts: true, - entry_strategy: EntryStrategy::Hoist, - ..TestInput::default() - }); + .to_string(), + transpile_jsx: true, + transpile_ts: true, + entry_strategy: EntryStrategy::Hoist, + ..TestInput::default() + }); } #[test] fn example_issue_33443() { - test_input!(TestInput { + test_input!(TestInput { code: r#" import { component$, useSignal } from '@builder.io/qwik'; @@ -2938,8 +2938,8 @@ export const Issue3742 = component$(({description = '', other}: any) => { } #[test] fn example_getter_generation() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from '@builder.io/qwik'; export const App = component$(() => { @@ -2973,16 +2973,16 @@ export const Cmp = component$((props) => { ); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_qwik_react() { - test_input!(TestInput { + test_input!(TestInput { code: r#" import { componentQrl, inlinedQrl, useLexicalScope, useHostElement, useStore, useTaskQrl, noSerialize, SkipRerender, implicit$FirstArg } from '@builder.io/qwik'; import { jsx, Fragment } from '@builder.io/qwik/jsx-runtime'; @@ -3085,7 +3085,7 @@ export { qwikify$, qwikifyQrl, renderToString }; #[test] fn example_qwik_react_inline() { - test_input!(TestInput { + test_input!(TestInput { code: r#" import { componentQrl, inlinedQrl, useLexicalScope, useHostElement, useStore, useTaskQrl, noSerialize, SkipRerender, implicit$FirstArg } from '@builder.io/qwik'; import { jsx, Fragment } from '@builder.io/qwik/jsx-runtime'; @@ -3188,19 +3188,19 @@ export { qwikify$, qwikifyQrl, renderToString }; #[test] fn example_qwik_sdk_inline() { - test_input!(TestInput { - code: include_str!("fixtures/index.qwik.mjs").to_string(), - filename: "../node_modules/@builder.io/qwik-city/index.qwik.mjs".to_string(), - entry_strategy: EntryStrategy::Smart, - explicit_extensions: true, - mode: EmitMode::Prod, - ..TestInput::default() - }); + test_input!(TestInput { + code: include_str!("fixtures/index.qwik.mjs").to_string(), + filename: "../node_modules/@builder.io/qwik-city/index.qwik.mjs".to_string(), + entry_strategy: EntryStrategy::Smart, + explicit_extensions: true, + mode: EmitMode::Prod, + ..TestInput::default() + }); } #[test] fn relative_paths() { - let dep = r#" + let dep = r#" import { componentQrl, inlinedQrl, useStore, useLexicalScope } from "@builder.io/qwik"; import { jsx, jsxs } from "@builder.io/qwik/jsx-runtime"; import { state } from './sibling'; @@ -3237,7 +3237,7 @@ export const App = /*#__PURE__*/ componentQrl(inlinedQrl(()=>{ }, "App_component_AkbU84a8zes")); "#; - let code = r#" + let code = r#" import { component$, $ } from '@builder.io/qwik'; import { state } from './sibling'; @@ -3247,41 +3247,41 @@ export const Local = component$(() => { ) }); "#; - let res = transform_modules(TransformModulesOptions { - src_dir: "/path/to/app/src/thing".into(), - root_dir: Some("/path/to/app/".into()), - input: vec![ - TransformModuleInput { - code: dep.into(), - path: "../../node_modules/dep/dist/lib.mjs".into(), - }, - TransformModuleInput { - code: code.into(), - path: "components/main.tsx".into(), - }, - ], - source_maps: true, - minify: MinifyMode::Simplify, - explicit_extensions: true, - mode: EmitMode::Lib, - manual_chunks: None, - entry_strategy: EntryStrategy::Hook, - transpile_ts: true, - transpile_jsx: true, - preserve_filenames: false, - core_module: None, - scope: None, - strip_exports: None, - strip_ctx_name: None, - strip_event_handlers: false, - reg_ctx_name: None, - is_server: None, - }); - snapshot_res!(&res, "".into()); + let res = transform_modules(TransformModulesOptions { + src_dir: "/path/to/app/src/thing".into(), + root_dir: Some("/path/to/app/".into()), + input: vec![ + TransformModuleInput { + code: dep.into(), + path: "../../node_modules/dep/dist/lib.mjs".into(), + }, + TransformModuleInput { + code: code.into(), + path: "components/main.tsx".into(), + }, + ], + source_maps: true, + minify: MinifyMode::Simplify, + explicit_extensions: true, + mode: EmitMode::Lib, + manual_chunks: None, + entry_strategy: EntryStrategy::Hook, + transpile_ts: true, + transpile_jsx: true, + preserve_filenames: false, + core_module: None, + scope: None, + strip_exports: None, + strip_ctx_name: None, + strip_event_handlers: false, + reg_ctx_name: None, + is_server: None, + }); + snapshot_res!(&res, "".into()); } #[test] fn consistent_hashes() { - let code = r#" + let code = r#" import { component$, $ } from '@builder.io/qwik'; import mongo from 'mongodb'; @@ -3300,130 +3300,130 @@ export const Greeter = component$(() => { }); "#; - let options = vec![ - (EmitMode::Lib, EntryStrategy::Hook, true), - (EmitMode::Lib, EntryStrategy::Single, true), - (EmitMode::Lib, EntryStrategy::Component, true), - // (EmitMode::Lib, EntryStrategy::Inline, true), - (EmitMode::Prod, EntryStrategy::Hook, true), - (EmitMode::Prod, EntryStrategy::Single, true), - (EmitMode::Prod, EntryStrategy::Component, true), - // (EmitMode::Prod, EntryStrategy::Inline, true), - (EmitMode::Dev, EntryStrategy::Hook, true), - (EmitMode::Dev, EntryStrategy::Single, true), - (EmitMode::Dev, EntryStrategy::Component, true), - // (EmitMode::Dev, EntryStrategy::Inline, true), - (EmitMode::Lib, EntryStrategy::Hook, false), - (EmitMode::Lib, EntryStrategy::Single, false), - (EmitMode::Lib, EntryStrategy::Component, false), - // (EmitMode::Lib, EntryStrategy::Inline, false), - (EmitMode::Prod, EntryStrategy::Hook, false), - (EmitMode::Prod, EntryStrategy::Single, false), - (EmitMode::Prod, EntryStrategy::Component, false), - // (EmitMode::Prod, EntryStrategy::Inline, false), - (EmitMode::Dev, EntryStrategy::Hook, false), - (EmitMode::Dev, EntryStrategy::Single, false), - (EmitMode::Dev, EntryStrategy::Component, false), - // (EmitMode::Dev, EntryStrategy::Inline, false), - ]; - - let res = transform_modules(TransformModulesOptions { - src_dir: "./thing".into(), - input: vec![ - TransformModuleInput { - code: code.into(), - path: "main.tsx".into(), - }, - TransformModuleInput { - code: code.into(), - path: "components/main.tsx".into(), - }, - ], - source_maps: true, - minify: MinifyMode::Simplify, - root_dir: None, - explicit_extensions: true, - mode: EmitMode::Lib, - manual_chunks: None, - entry_strategy: EntryStrategy::Hook, - transpile_ts: true, - transpile_jsx: true, - preserve_filenames: false, - scope: None, - core_module: None, - reg_ctx_name: None, - strip_exports: None, - strip_ctx_name: None, - strip_event_handlers: false, - is_server: None, - }); - let ref_hooks: Vec<_> = res - .unwrap() - .modules - .into_iter() - .flat_map(|module| module.hook) - .collect(); - - for (i, option) in options.into_iter().enumerate() { - let res = transform_modules(TransformModulesOptions { - src_dir: "./thing".into(), - input: vec![ - TransformModuleInput { - code: code.into(), - path: "main.tsx".into(), - }, - TransformModuleInput { - code: code.into(), - path: "components/main.tsx".into(), - }, - ], - root_dir: None, - source_maps: false, - minify: MinifyMode::Simplify, - explicit_extensions: true, - mode: option.0, - manual_chunks: None, - entry_strategy: option.1, - transpile_ts: option.2, - transpile_jsx: option.2, - preserve_filenames: false, - scope: None, - core_module: None, - strip_exports: None, - strip_ctx_name: None, - strip_event_handlers: false, - reg_ctx_name: None, - is_server: None, - }); - - let hooks: Vec<_> = res - .unwrap() - .modules - .into_iter() - .flat_map(|module| module.hook) - .collect(); - - assert_eq!(hooks.len(), ref_hooks.len()); - - for (a, b) in hooks.iter().zip(ref_hooks.iter()) { - assert_eq!( - get_hash(a.name.as_ref()), - get_hash(b.name.as_ref()), - "INDEX: {}\n\n{:#?}\n\n{:#?}\n\n{:#?}\n\n{:#?}", - i, - a, - b, - hooks, - ref_hooks - ); - } - } + let options = vec![ + (EmitMode::Lib, EntryStrategy::Hook, true), + (EmitMode::Lib, EntryStrategy::Single, true), + (EmitMode::Lib, EntryStrategy::Component, true), + // (EmitMode::Lib, EntryStrategy::Inline, true), + (EmitMode::Prod, EntryStrategy::Hook, true), + (EmitMode::Prod, EntryStrategy::Single, true), + (EmitMode::Prod, EntryStrategy::Component, true), + // (EmitMode::Prod, EntryStrategy::Inline, true), + (EmitMode::Dev, EntryStrategy::Hook, true), + (EmitMode::Dev, EntryStrategy::Single, true), + (EmitMode::Dev, EntryStrategy::Component, true), + // (EmitMode::Dev, EntryStrategy::Inline, true), + (EmitMode::Lib, EntryStrategy::Hook, false), + (EmitMode::Lib, EntryStrategy::Single, false), + (EmitMode::Lib, EntryStrategy::Component, false), + // (EmitMode::Lib, EntryStrategy::Inline, false), + (EmitMode::Prod, EntryStrategy::Hook, false), + (EmitMode::Prod, EntryStrategy::Single, false), + (EmitMode::Prod, EntryStrategy::Component, false), + // (EmitMode::Prod, EntryStrategy::Inline, false), + (EmitMode::Dev, EntryStrategy::Hook, false), + (EmitMode::Dev, EntryStrategy::Single, false), + (EmitMode::Dev, EntryStrategy::Component, false), + // (EmitMode::Dev, EntryStrategy::Inline, false), + ]; + + let res = transform_modules(TransformModulesOptions { + src_dir: "./thing".into(), + input: vec![ + TransformModuleInput { + code: code.into(), + path: "main.tsx".into(), + }, + TransformModuleInput { + code: code.into(), + path: "components/main.tsx".into(), + }, + ], + source_maps: true, + minify: MinifyMode::Simplify, + root_dir: None, + explicit_extensions: true, + mode: EmitMode::Lib, + manual_chunks: None, + entry_strategy: EntryStrategy::Hook, + transpile_ts: true, + transpile_jsx: true, + preserve_filenames: false, + scope: None, + core_module: None, + reg_ctx_name: None, + strip_exports: None, + strip_ctx_name: None, + strip_event_handlers: false, + is_server: None, + }); + let ref_hooks: Vec<_> = res + .unwrap() + .modules + .into_iter() + .flat_map(|module| module.hook) + .collect(); + + for (i, option) in options.into_iter().enumerate() { + let res = transform_modules(TransformModulesOptions { + src_dir: "./thing".into(), + input: vec![ + TransformModuleInput { + code: code.into(), + path: "main.tsx".into(), + }, + TransformModuleInput { + code: code.into(), + path: "components/main.tsx".into(), + }, + ], + root_dir: None, + source_maps: false, + minify: MinifyMode::Simplify, + explicit_extensions: true, + mode: option.0, + manual_chunks: None, + entry_strategy: option.1, + transpile_ts: option.2, + transpile_jsx: option.2, + preserve_filenames: false, + scope: None, + core_module: None, + strip_exports: None, + strip_ctx_name: None, + strip_event_handlers: false, + reg_ctx_name: None, + is_server: None, + }); + + let hooks: Vec<_> = res + .unwrap() + .modules + .into_iter() + .flat_map(|module| module.hook) + .collect(); + + assert_eq!(hooks.len(), ref_hooks.len()); + + for (a, b) in hooks.iter().zip(ref_hooks.iter()) { + assert_eq!( + get_hash(a.name.as_ref()), + get_hash(b.name.as_ref()), + "INDEX: {}\n\n{:#?}\n\n{:#?}\n\n{:#?}\n\n{:#?}", + i, + a, + b, + hooks, + ref_hooks + ); + } + } } #[test] fn issue_5008() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { component$, useStore } from "@builder.io/qwik"; export default component$(() => { @@ -3441,17 +3441,17 @@ fn issue_5008() { ); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } #[test] fn example_of_synchronous_qrl() { - test_input!(TestInput { - code: r#" + test_input!(TestInput { + code: r#" import { sync$, component$ } from "@builder.io/qwik"; export default component$(() => { @@ -3469,11 +3469,11 @@ fn example_of_synchronous_qrl() { ); }); "# - .to_string(), - transpile_ts: true, - transpile_jsx: true, - ..TestInput::default() - }); + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); } // TODO(misko): Make this test work by implementing strict serialization. @@ -3499,55 +3499,55 @@ fn example_of_synchronous_qrl() { // } fn get_hash(name: &str) -> String { - name.split('_').last().unwrap().into() + name.split('_').last().unwrap().into() } struct TestInput { - pub code: String, - pub filename: String, - pub src_dir: String, - pub root_dir: Option, - pub manual_chunks: Option>, - pub entry_strategy: EntryStrategy, - pub minify: MinifyMode, - pub transpile_ts: bool, - pub transpile_jsx: bool, - pub preserve_filenames: bool, - pub explicit_extensions: bool, - pub snapshot: bool, - pub mode: EmitMode, - pub core_module: Option, - pub scope: Option, - pub strip_exports: Option>, - pub reg_ctx_name: Option>, - pub strip_ctx_name: Option>, - pub strip_event_handlers: bool, - pub is_server: Option, + pub code: String, + pub filename: String, + pub src_dir: String, + pub root_dir: Option, + pub manual_chunks: Option>, + pub entry_strategy: EntryStrategy, + pub minify: MinifyMode, + pub transpile_ts: bool, + pub transpile_jsx: bool, + pub preserve_filenames: bool, + pub explicit_extensions: bool, + pub snapshot: bool, + pub mode: EmitMode, + pub core_module: Option, + pub scope: Option, + pub strip_exports: Option>, + pub reg_ctx_name: Option>, + pub strip_ctx_name: Option>, + pub strip_event_handlers: bool, + pub is_server: Option, } impl TestInput { - pub fn default() -> Self { - Self { - filename: "test.tsx".to_string(), - src_dir: "/user/qwik/src/".to_string(), - root_dir: None, - code: "/user/qwik/src/".to_string(), - manual_chunks: None, - entry_strategy: EntryStrategy::Hook, - minify: MinifyMode::Simplify, - transpile_ts: false, - transpile_jsx: false, - preserve_filenames: false, - explicit_extensions: false, - snapshot: true, - mode: EmitMode::Lib, - scope: None, - core_module: None, - reg_ctx_name: None, - strip_exports: None, - strip_ctx_name: None, - strip_event_handlers: false, - is_server: None, - } - } + pub fn default() -> Self { + Self { + filename: "test.tsx".to_string(), + src_dir: "/user/qwik/src/".to_string(), + root_dir: None, + code: "/user/qwik/src/".to_string(), + manual_chunks: None, + entry_strategy: EntryStrategy::Hook, + minify: MinifyMode::Simplify, + transpile_ts: false, + transpile_jsx: false, + preserve_filenames: false, + explicit_extensions: false, + snapshot: true, + mode: EmitMode::Lib, + scope: None, + core_module: None, + reg_ctx_name: None, + strip_exports: None, + strip_ctx_name: None, + strip_event_handlers: false, + is_server: None, + } + } } diff --git a/packages/qwik/src/optimizer/core/src/transform.rs b/packages/qwik/src/optimizer/core/src/transform.rs index 1c7b59fd5e6..ce0423b8cea 100644 --- a/packages/qwik/src/optimizer/core/src/transform.rs +++ b/packages/qwik/src/optimizer/core/src/transform.rs @@ -1,6 +1,6 @@ use crate::code_move::{fix_path, transform_function_expr}; use crate::collector::{ - collect_from_pat, new_ident_from_id, GlobalCollect, Id, IdentCollector, ImportKind, + collect_from_pat, new_ident_from_id, GlobalCollect, Id, IdentCollector, ImportKind, }; use crate::entry_strategy::EntryPolicy; use crate::has_branches::{is_conditional_jsx, is_conditional_jsx_block}; @@ -29,615 +29,615 @@ use swc_ecmascript::utils::{private_ident, quote_ident, ExprFactory}; use swc_ecmascript::visit::{noop_fold_type, Fold, FoldWith, VisitWith}; macro_rules! id { - ($ident: expr) => { - ($ident.sym.clone(), $ident.span.ctxt()) - }; + ($ident: expr) => { + ($ident.sym.clone(), $ident.span.ctxt()) + }; } macro_rules! id_eq { - ($ident: expr, $cid: expr) => { - if let Some(cid) = $cid { - cid.0 == $ident.sym && cid.1 == $ident.span.ctxt() - } else { - false - } - }; + ($ident: expr, $cid: expr) => { + if let Some(cid) = $cid { + cid.0 == $ident.sym && cid.1 == $ident.span.ctxt() + } else { + false + } + }; } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum HookKind { - Function, - EventHandler, - JSXProp, + Function, + EventHandler, + JSXProp, } #[derive(Debug, Clone)] pub struct Hook { - pub entry: Option, - pub canonical_filename: JsWord, - pub name: JsWord, - pub expr: Box, - pub data: HookData, - pub hash: u64, - pub span: Span, + pub entry: Option, + pub canonical_filename: JsWord, + pub name: JsWord, + pub expr: Box, + pub data: HookData, + pub hash: u64, + pub span: Span, } #[derive(Debug, Clone)] pub struct HookData { - pub extension: JsWord, - pub local_idents: Vec, - pub scoped_idents: Vec, - pub parent_hook: Option, - pub ctx_kind: HookKind, - pub ctx_name: JsWord, - pub origin: JsWord, - pub display_name: JsWord, - pub hash: JsWord, - pub need_transform: bool, + pub extension: JsWord, + pub local_idents: Vec, + pub scoped_idents: Vec, + pub parent_hook: Option, + pub ctx_kind: HookKind, + pub ctx_name: JsWord, + pub origin: JsWord, + pub display_name: JsWord, + pub hash: JsWord, + pub need_transform: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IdentType { - Var(bool), - Fn, - Class, + Var(bool), + Fn, + Class, } pub type IdPlusType = (Id, IdentType); #[allow(clippy::module_name_repetitions)] pub struct QwikTransform<'a> { - pub hooks: Vec, - pub options: QwikTransformOptions<'a>, - - hooks_names: HashMap, - // extra_top_items: BTreeMap, - extra_bottom_items: BTreeMap, - stack_ctxt: Vec, - decl_stack: Vec>, - in_component: bool, - marker_functions: HashMap, - jsx_functions: HashSet, - immutable_function_cmp: HashSet, - qcomponent_fn: Option, - qhook_fn: Option, - inlined_qrl_fn: Option, - sync_qrl_fn: Option, - h_fn: Option, - fragment_fn: Option, - - jsx_mutable: bool, - - hook_stack: Vec, - file_hash: u64, - jsx_key_counter: u32, - root_jsx_mode: bool, + pub hooks: Vec, + pub options: QwikTransformOptions<'a>, + + hooks_names: HashMap, + // extra_top_items: BTreeMap, + extra_bottom_items: BTreeMap, + stack_ctxt: Vec, + decl_stack: Vec>, + in_component: bool, + marker_functions: HashMap, + jsx_functions: HashSet, + immutable_function_cmp: HashSet, + qcomponent_fn: Option, + qhook_fn: Option, + inlined_qrl_fn: Option, + sync_qrl_fn: Option, + h_fn: Option, + fragment_fn: Option, + + jsx_mutable: bool, + + hook_stack: Vec, + file_hash: u64, + jsx_key_counter: u32, + root_jsx_mode: bool, } pub struct QwikTransformOptions<'a> { - pub path_data: &'a PathData, - pub entry_policy: &'a dyn EntryPolicy, - pub extension: JsWord, - pub core_module: JsWord, - pub explicit_extensions: bool, - pub comments: Option<&'a SingleThreadedComments>, - pub global_collect: GlobalCollect, - pub scope: Option<&'a String>, - pub mode: EmitMode, - pub entry_strategy: EntryStrategy, - pub reg_ctx_name: Option<&'a [JsWord]>, - pub strip_ctx_name: Option<&'a [JsWord]>, - pub strip_event_handlers: bool, - pub is_server: Option, - pub cm: Lrc, + pub path_data: &'a PathData, + pub entry_policy: &'a dyn EntryPolicy, + pub extension: JsWord, + pub core_module: JsWord, + pub explicit_extensions: bool, + pub comments: Option<&'a SingleThreadedComments>, + pub global_collect: GlobalCollect, + pub scope: Option<&'a String>, + pub mode: EmitMode, + pub entry_strategy: EntryStrategy, + pub reg_ctx_name: Option<&'a [JsWord]>, + pub strip_ctx_name: Option<&'a [JsWord]>, + pub strip_event_handlers: bool, + pub is_server: Option, + pub cm: Lrc, } fn convert_signal_word(id: &JsWord) -> Option { - let ident_name = id.as_ref(); - let has_signal = ident_name.ends_with(SIGNAL); - if has_signal { - let new_specifier = [&ident_name[0..ident_name.len() - 1], LONG_SUFFIX].concat(); - Some(JsWord::from(new_specifier)) - } else { - None - } + let ident_name = id.as_ref(); + let has_signal = ident_name.ends_with(SIGNAL); + if has_signal { + let new_specifier = [&ident_name[0..ident_name.len() - 1], LONG_SUFFIX].concat(); + Some(JsWord::from(new_specifier)) + } else { + None + } } impl<'a> QwikTransform<'a> { - pub fn new(options: QwikTransformOptions<'a>) -> Self { - let mut marker_functions = HashMap::new(); - for (id, import) in options.global_collect.imports.iter() { - if import.kind == ImportKind::Named && import.specifier.ends_with(SIGNAL) { - marker_functions.insert(id.clone(), import.specifier.clone()); - } - } - - for id in options.global_collect.exports.keys() { - if id.0.ends_with(SIGNAL) { - marker_functions.insert(id.clone(), id.0.clone()); - } - } - - let mut hasher = DefaultHasher::new(); - let local_file_name = options.path_data.rel_path.to_slash_lossy(); - if let Some(scope) = options.scope { - hasher.write(scope.as_bytes()); - } - hasher.write(local_file_name.as_bytes()); - - let jsx_functions = options - .global_collect - .imports - .iter() - .flat_map(|(id, import)| { - match ( - import.kind, - import.source.as_ref(), - import.specifier.as_ref(), - ) { - (ImportKind::Named, "@builder.io/qwik", "jsx") => Some(id.clone()), - (ImportKind::Named, "@builder.io/qwik", "jsxs") => Some(id.clone()), - (ImportKind::Named, "@builder.io/qwik", "jsxDEV") => Some(id.clone()), - (ImportKind::Named, "@builder.io/qwik/jsx-runtime", _) => Some(id.clone()), - (ImportKind::Named, "@builder.io/qwik/jsx-dev-runtime", _) => Some(id.clone()), - _ => None, - } - }) - .collect(); - - let immutable_function_cmp = options - .global_collect - .imports - .iter() - .flat_map(|(id, import)| { - match ( - import.kind, - import.source.as_ref(), - import.specifier.as_ref(), - ) { - ( - ImportKind::Named, - "@builder.io/qwik/jsx-runtime" | "@builder.io/qwik/jsx-dev-runtime", - "Fragment", - ) => Some(id.clone()), - ( - ImportKind::Named, - "@builder.io/qwik", - "Fragment" | "RenderOnce" | "HTMLFragment", - ) => Some(id.clone()), - (ImportKind::Named, "@builder.io/qwik-city", "Link") => Some(id.clone()), - (_, source, _) => { - if source.ends_with("?jsx") || source.ends_with(".md") { - Some(id.clone()) - } else { - None - } - } - } - }) - .collect(); - QwikTransform { - file_hash: hasher.finish(), - jsx_key_counter: 0, - stack_ctxt: Vec::with_capacity(16), - decl_stack: Vec::with_capacity(32), - in_component: false, - hooks: Vec::with_capacity(16), - hook_stack: Vec::with_capacity(16), - // extra_top_items: BTreeMap::new(), - extra_bottom_items: BTreeMap::new(), - - hooks_names: HashMap::new(), - qcomponent_fn: options - .global_collect - .get_imported_local(&QCOMPONENT, &options.core_module), - sync_qrl_fn: options - .global_collect - .get_imported_local(&Q_SYNC, &options.core_module), - qhook_fn: options - .global_collect - .get_imported_local(&QHOOK, &options.core_module), - inlined_qrl_fn: options - .global_collect - .get_imported_local(&_INLINED_QRL, &options.core_module), - h_fn: options - .global_collect - .get_imported_local(&H, &options.core_module), - fragment_fn: options - .global_collect - .get_imported_local(&FRAGMENT, &options.core_module), - marker_functions, - jsx_functions, - immutable_function_cmp, - root_jsx_mode: true, - jsx_mutable: false, - options, - } - } - - const fn is_inline(&self) -> bool { - matches!( - self.options.entry_strategy, - EntryStrategy::Inline | EntryStrategy::Hoist - ) - } - - fn is_inside_module(&self) -> bool { - self.hook_stack.is_empty() || self.is_inline() - } - - fn get_dev_location(&self, span: Span) -> ast::ExprOrSpread { - let loc = self.options.cm.lookup_char_pos(span.lo); - let file_name = self - .options - .path_data - .rel_path - .to_string_lossy() - .to_string(); - ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Object(ast::ObjectLit { - span: DUMMY_SP, - props: vec![ - ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Ident(quote_ident!("fileName")), - value: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - raw: None, - value: file_name.into(), - }))), - }))), - ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Ident(quote_ident!("lineNumber")), - value: loc.line.into(), - }))), - ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Ident(quote_ident!("columnNumber")), - value: (loc.col.0 + 1).into(), - }))), - ], - })), - } - } - - 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_"; - } - display_name = escape_sym(&display_name); - let first_char = display_name.chars().next(); - if first_char.map_or(false, |c| c.is_ascii_digit()) { - display_name = format!("_{}", display_name); - } - let index = match self.hooks_names.get_mut(&display_name) { - Some(count) => { - *count += 1; - *count - } - None => 0, - }; - if index == 0 { - self.hooks_names.insert(display_name.clone(), 0); - } else { - write!(display_name, "_{}", index).unwrap(); - } - let mut hasher = DefaultHasher::new(); - let local_file_name = self.options.path_data.rel_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(); - let hash64 = base64(hash); - - let symbol_name = if matches!(self.options.mode, EmitMode::Dev | EmitMode::Lib) { - format!("{}_{}", display_name, hash64) - } else { - format!("s_{}", hash64) - }; - ( - JsWord::from(symbol_name), - JsWord::from(display_name), - JsWord::from(hash64), - hash, - ) - } - - fn handle_inlined_qhook(&mut self, mut node: ast::CallExpr) -> ast::CallExpr { - node.args.reverse(); - - let last_stack = self - .stack_ctxt - .last() - .map_or_else(|| QHOOK.clone(), |last| JsWord::from(last.as_str())); - - let ctx_name = if last_stack.ends_with("Qrl") { - JsWord::from(format!("{}$", last_stack.trim_end_matches("Qrl"))) - } else { - last_stack - }; - let ctx_kind = if ctx_name.starts_with("on") { - HookKind::JSXProp - } else { - HookKind::Function - }; - let first_arg = node - .args - .pop() - .expect("inlinedQrl() should always have the first argument"); - - let second_arg = node - .args - .pop() - .expect("inlinedQrl() should always have the second argument"); - - let third_arg = node.args.pop(); - let span = first_arg.span(); - - let (symbol_name, display_name, hash) = { - let symbol_name = match *second_arg.expr { - ast::Expr::Lit(ast::Lit::Str(string)) => string.value, - _ => panic!("dfd"), - }; - parse_symbol_name( - symbol_name, - matches!(self.options.mode, EmitMode::Dev | EmitMode::Lib), - ) - }; - - self.hook_stack.push(symbol_name.clone()); - let folded = *first_arg.expr.fold_with(self); - self.hook_stack.pop(); - - let scoped_idents = { - third_arg.map_or_else(Vec::new, |scoped| { - let list: Vec = match &*scoped.expr { - ast::Expr::Array(array) => array - .elems - .iter() - .flat_map(|item| match &*item.as_ref().unwrap().expr { - ast::Expr::Ident(ident) => Some(id!(ident)), - _ => None, - }) - .collect(), - _ => vec![], - }; - list - }) - }; - let local_idents = self.get_local_idents(&folded); - let hook_data = HookData { - extension: self.options.extension.clone(), - local_idents, - scoped_idents, - parent_hook: self.hook_stack.last().cloned(), - ctx_kind, - ctx_name, - origin: self.options.path_data.rel_path.to_slash_lossy().into(), - display_name, - need_transform: false, - hash, - }; - let should_emit = self.should_emit_hook(&hook_data); - if should_emit { - for id in &hook_data.local_idents { - if !self.options.global_collect.exports.contains_key(id) - && self.options.global_collect.root.contains_key(id) - { - self.ensure_export(id); - } - } - } - if !should_emit { - self.create_noop_qrl(&symbol_name, hook_data) - } else if self.is_inline() { - let folded = if self.should_reg_hook(&hook_data.ctx_name) { - ast::Expr::Call(self.create_internal_call( - &_REG_SYMBOL, - vec![ - folded, - ast::Expr::Lit(ast::Lit::Str(ast::Str::from(hook_data.hash.clone()))), - ], - true, - )) - } else { - folded - }; - self.create_inline_qrl(hook_data, folded, symbol_name, span) - } else { - self.create_hook(hook_data, folded, symbol_name, span, 0) - } - } - - fn handle_qhook(&mut self, node: ast::CallExpr) -> ast::CallExpr { - let mut node = node; - node.args.reverse(); - - if let Some(ast::ExprOrSpread { - expr: first_arg, .. - }) = node.args.pop() - { - 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 - } - } - - fn handle_sync_qrl(&mut self, mut node: ast::CallExpr) -> ast::CallExpr { - if let Some(ast::ExprOrSpread { - expr: first_arg, .. - }) = node.args.pop() - { - match *first_arg { - ast::Expr::Arrow(..) | ast::Expr::Fn(..) => { - let serialize = render_expr(first_arg.as_ref()); - let new_callee = self.ensure_core_import(&_QRL_SYNC); - ast::CallExpr { - callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id( - &new_callee, - )))), - span: DUMMY_SP, - type_args: None, - args: vec![ - ast::ExprOrSpread { - spread: None, - expr: first_arg, - }, - // string serialized version of first argument - ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - value: serialize.into(), - raw: None, - }))), - }, - ], - } - } - _ => node, - } - } else { - node - } - } - - /** Converts inline expressions into QRLs. Returns (expr?, true) if succeeded. */ - fn create_synthetic_qqhook( - &mut self, - first_arg: ast::Expr, - accept_call_expr: bool, - ) -> (Option, bool) { - // Collect descendent idents - let descendent_idents = { - let mut collector = IdentCollector::new(); - first_arg.visit_with(&mut collector); - collector.get_words() - }; - - let (decl_collect, invalid_decl): (_, Vec<_>) = self - .decl_stack - .iter() - .flat_map(|v| v.iter()) - .cloned() - .partition(|(_, t)| matches!(t, IdentType::Var(true))); - - let folded = first_arg; - - let mut set: HashSet = HashSet::new(); - let mut contains_side_effect = false; - for ident in &descendent_idents { - if self.options.global_collect.is_global(ident) { - contains_side_effect = true; - } else if invalid_decl.iter().any(|entry| entry.0 == *ident) { - return (None, false); - } else if decl_collect.iter().any(|entry| entry.0 == *ident) { - set.insert(ident.clone()); - } else if ident.0.starts_with('$') { - // TODO: remove, this is a workaround for $localize to work - return (None, false); - } - } - let mut scoped_idents: Vec = set.into_iter().collect(); - - if contains_side_effect { - return (None, scoped_idents.is_empty()); - } - scoped_idents.sort(); - - let serialize_fn = matches!(self.options.is_server, None | Some(true)); - let (scoped_idents, _) = compute_scoped_idents(&descendent_idents, &decl_collect); - let inlined_fn = self.ensure_core_import(&_INLINED_FN); - convert_inlined_fn( - folded, - scoped_idents, - &inlined_fn, - accept_call_expr, - serialize_fn, - ) - } - - fn create_synthetic_qhook( - &mut self, - first_arg: ast::Expr, - ctx_kind: HookKind, - ctx_name: JsWord, - custom_symbol: Option, - ) -> ast::CallExpr { - self._create_synthetic_qhook(first_arg, ctx_kind, ctx_name, custom_symbol) - .0 - } - - fn _create_synthetic_qhook( - &mut self, - first_arg: ast::Expr, - ctx_kind: HookKind, - ctx_name: JsWord, - custom_symbol: Option, - ) -> (ast::CallExpr, bool) { - 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(custom_symbol); - - // Collect descendent idents - let descendent_idents = { - let mut collector = IdentCollector::new(); - first_arg.visit_with(&mut collector); - collector.get_words() - }; - - let (decl_collect, invalid_decl): (_, Vec<_>) = self - .decl_stack - .iter() - .flat_map(|v| v.iter()) - .cloned() - .partition(|(_, t)| matches!(t, IdentType::Var(_))); - - self.hook_stack.push(symbol_name.clone()); - let span = first_arg.span(); - let folded = first_arg.fold_with(self); - self.hook_stack.pop(); - - // Collect local idents - let local_idents = self.get_local_idents(&folded); - - let (mut scoped_idents, immutable) = - compute_scoped_idents(&descendent_idents, &decl_collect); - if !can_capture && !scoped_idents.is_empty() { - HANDLER.with(|handler| { + pub fn new(options: QwikTransformOptions<'a>) -> Self { + let mut marker_functions = HashMap::new(); + for (id, import) in options.global_collect.imports.iter() { + if import.kind == ImportKind::Named && import.specifier.ends_with(SIGNAL) { + marker_functions.insert(id.clone(), import.specifier.clone()); + } + } + + for id in options.global_collect.exports.keys() { + if id.0.ends_with(SIGNAL) { + marker_functions.insert(id.clone(), id.0.clone()); + } + } + + let mut hasher = DefaultHasher::new(); + let local_file_name = options.path_data.rel_path.to_slash_lossy(); + if let Some(scope) = options.scope { + hasher.write(scope.as_bytes()); + } + hasher.write(local_file_name.as_bytes()); + + let jsx_functions = options + .global_collect + .imports + .iter() + .flat_map(|(id, import)| { + match ( + import.kind, + import.source.as_ref(), + import.specifier.as_ref(), + ) { + (ImportKind::Named, "@builder.io/qwik", "jsx") => Some(id.clone()), + (ImportKind::Named, "@builder.io/qwik", "jsxs") => Some(id.clone()), + (ImportKind::Named, "@builder.io/qwik", "jsxDEV") => Some(id.clone()), + (ImportKind::Named, "@builder.io/qwik/jsx-runtime", _) => Some(id.clone()), + (ImportKind::Named, "@builder.io/qwik/jsx-dev-runtime", _) => Some(id.clone()), + _ => None, + } + }) + .collect(); + + let immutable_function_cmp = options + .global_collect + .imports + .iter() + .flat_map(|(id, import)| { + match ( + import.kind, + import.source.as_ref(), + import.specifier.as_ref(), + ) { + ( + ImportKind::Named, + "@builder.io/qwik/jsx-runtime" | "@builder.io/qwik/jsx-dev-runtime", + "Fragment", + ) => Some(id.clone()), + ( + ImportKind::Named, + "@builder.io/qwik", + "Fragment" | "RenderOnce" | "HTMLFragment", + ) => Some(id.clone()), + (ImportKind::Named, "@builder.io/qwik-city", "Link") => Some(id.clone()), + (_, source, _) => { + if source.ends_with("?jsx") || source.ends_with(".md") { + Some(id.clone()) + } else { + None + } + } + } + }) + .collect(); + QwikTransform { + file_hash: hasher.finish(), + jsx_key_counter: 0, + stack_ctxt: Vec::with_capacity(16), + decl_stack: Vec::with_capacity(32), + in_component: false, + hooks: Vec::with_capacity(16), + hook_stack: Vec::with_capacity(16), + // extra_top_items: BTreeMap::new(), + extra_bottom_items: BTreeMap::new(), + + hooks_names: HashMap::new(), + qcomponent_fn: options + .global_collect + .get_imported_local(&QCOMPONENT, &options.core_module), + sync_qrl_fn: options + .global_collect + .get_imported_local(&Q_SYNC, &options.core_module), + qhook_fn: options + .global_collect + .get_imported_local(&QHOOK, &options.core_module), + inlined_qrl_fn: options + .global_collect + .get_imported_local(&_INLINED_QRL, &options.core_module), + h_fn: options + .global_collect + .get_imported_local(&H, &options.core_module), + fragment_fn: options + .global_collect + .get_imported_local(&FRAGMENT, &options.core_module), + marker_functions, + jsx_functions, + immutable_function_cmp, + root_jsx_mode: true, + jsx_mutable: false, + options, + } + } + + const fn is_inline(&self) -> bool { + matches!( + self.options.entry_strategy, + EntryStrategy::Inline | EntryStrategy::Hoist + ) + } + + fn is_inside_module(&self) -> bool { + self.hook_stack.is_empty() || self.is_inline() + } + + fn get_dev_location(&self, span: Span) -> ast::ExprOrSpread { + let loc = self.options.cm.lookup_char_pos(span.lo); + let file_name = self + .options + .path_data + .rel_path + .to_string_lossy() + .to_string(); + ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Object(ast::ObjectLit { + span: DUMMY_SP, + props: vec![ + ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Ident(quote_ident!("fileName")), + value: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + raw: None, + value: file_name.into(), + }))), + }))), + ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Ident(quote_ident!("lineNumber")), + value: loc.line.into(), + }))), + ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Ident(quote_ident!("columnNumber")), + value: (loc.col.0 + 1).into(), + }))), + ], + })), + } + } + + 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_"; + } + display_name = escape_sym(&display_name); + let first_char = display_name.chars().next(); + if first_char.map_or(false, |c| c.is_ascii_digit()) { + display_name = format!("_{}", display_name); + } + let index = match self.hooks_names.get_mut(&display_name) { + Some(count) => { + *count += 1; + *count + } + None => 0, + }; + if index == 0 { + self.hooks_names.insert(display_name.clone(), 0); + } else { + write!(display_name, "_{}", index).unwrap(); + } + let mut hasher = DefaultHasher::new(); + let local_file_name = self.options.path_data.rel_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(); + let hash64 = base64(hash); + + let symbol_name = if matches!(self.options.mode, EmitMode::Dev | EmitMode::Lib) { + format!("{}_{}", display_name, hash64) + } else { + format!("s_{}", hash64) + }; + ( + JsWord::from(symbol_name), + JsWord::from(display_name), + JsWord::from(hash64), + hash, + ) + } + + fn handle_inlined_qhook(&mut self, mut node: ast::CallExpr) -> ast::CallExpr { + node.args.reverse(); + + let last_stack = self + .stack_ctxt + .last() + .map_or_else(|| QHOOK.clone(), |last| JsWord::from(last.as_str())); + + let ctx_name = if last_stack.ends_with("Qrl") { + JsWord::from(format!("{}$", last_stack.trim_end_matches("Qrl"))) + } else { + last_stack + }; + let ctx_kind = if ctx_name.starts_with("on") { + HookKind::JSXProp + } else { + HookKind::Function + }; + let first_arg = node + .args + .pop() + .expect("inlinedQrl() should always have the first argument"); + + let second_arg = node + .args + .pop() + .expect("inlinedQrl() should always have the second argument"); + + let third_arg = node.args.pop(); + let span = first_arg.span(); + + let (symbol_name, display_name, hash) = { + let symbol_name = match *second_arg.expr { + ast::Expr::Lit(ast::Lit::Str(string)) => string.value, + _ => panic!("dfd"), + }; + parse_symbol_name( + symbol_name, + matches!(self.options.mode, EmitMode::Dev | EmitMode::Lib), + ) + }; + + self.hook_stack.push(symbol_name.clone()); + let folded = *first_arg.expr.fold_with(self); + self.hook_stack.pop(); + + let scoped_idents = { + third_arg.map_or_else(Vec::new, |scoped| { + let list: Vec = match &*scoped.expr { + ast::Expr::Array(array) => array + .elems + .iter() + .flat_map(|item| match &*item.as_ref().unwrap().expr { + ast::Expr::Ident(ident) => Some(id!(ident)), + _ => None, + }) + .collect(), + _ => vec![], + }; + list + }) + }; + let local_idents = self.get_local_idents(&folded); + let hook_data = HookData { + extension: self.options.extension.clone(), + local_idents, + scoped_idents, + parent_hook: self.hook_stack.last().cloned(), + ctx_kind, + ctx_name, + origin: self.options.path_data.rel_path.to_slash_lossy().into(), + display_name, + need_transform: false, + hash, + }; + let should_emit = self.should_emit_hook(&hook_data); + if should_emit { + for id in &hook_data.local_idents { + if !self.options.global_collect.exports.contains_key(id) + && self.options.global_collect.root.contains_key(id) + { + self.ensure_export(id); + } + } + } + if !should_emit { + self.create_noop_qrl(&symbol_name, hook_data) + } else if self.is_inline() { + let folded = if self.should_reg_hook(&hook_data.ctx_name) { + ast::Expr::Call(self.create_internal_call( + &_REG_SYMBOL, + vec![ + folded, + ast::Expr::Lit(ast::Lit::Str(ast::Str::from(hook_data.hash.clone()))), + ], + true, + )) + } else { + folded + }; + self.create_inline_qrl(hook_data, folded, symbol_name, span) + } else { + self.create_hook(hook_data, folded, symbol_name, span, 0) + } + } + + fn handle_qhook(&mut self, node: ast::CallExpr) -> ast::CallExpr { + let mut node = node; + node.args.reverse(); + + if let Some(ast::ExprOrSpread { + expr: first_arg, .. + }) = node.args.pop() + { + 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 + } + } + + fn handle_sync_qrl(&mut self, mut node: ast::CallExpr) -> ast::CallExpr { + if let Some(ast::ExprOrSpread { + expr: first_arg, .. + }) = node.args.pop() + { + match *first_arg { + ast::Expr::Arrow(..) | ast::Expr::Fn(..) => { + let serialize = render_expr(first_arg.as_ref()); + let new_callee = self.ensure_core_import(&_QRL_SYNC); + ast::CallExpr { + callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id( + &new_callee, + )))), + span: DUMMY_SP, + type_args: None, + args: vec![ + ast::ExprOrSpread { + spread: None, + expr: first_arg, + }, + // string serialized version of first argument + ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: serialize.into(), + raw: None, + }))), + }, + ], + } + } + _ => node, + } + } else { + node + } + } + + /** Converts inline expressions into QRLs. Returns (expr?, true) if succeeded. */ + fn create_synthetic_qqhook( + &mut self, + first_arg: ast::Expr, + accept_call_expr: bool, + ) -> (Option, bool) { + // Collect descendent idents + let descendent_idents = { + let mut collector = IdentCollector::new(); + first_arg.visit_with(&mut collector); + collector.get_words() + }; + + let (decl_collect, invalid_decl): (_, Vec<_>) = self + .decl_stack + .iter() + .flat_map(|v| v.iter()) + .cloned() + .partition(|(_, t)| matches!(t, IdentType::Var(true))); + + let folded = first_arg; + + let mut set: HashSet = HashSet::new(); + let mut contains_side_effect = false; + for ident in &descendent_idents { + if self.options.global_collect.is_global(ident) { + contains_side_effect = true; + } else if invalid_decl.iter().any(|entry| entry.0 == *ident) { + return (None, false); + } else if decl_collect.iter().any(|entry| entry.0 == *ident) { + set.insert(ident.clone()); + } else if ident.0.starts_with('$') { + // TODO: remove, this is a workaround for $localize to work + return (None, false); + } + } + let mut scoped_idents: Vec = set.into_iter().collect(); + + if contains_side_effect { + return (None, scoped_idents.is_empty()); + } + scoped_idents.sort(); + + let serialize_fn = matches!(self.options.is_server, None | Some(true)); + let (scoped_idents, _) = compute_scoped_idents(&descendent_idents, &decl_collect); + let inlined_fn = self.ensure_core_import(&_INLINED_FN); + convert_inlined_fn( + folded, + scoped_idents, + &inlined_fn, + accept_call_expr, + serialize_fn, + ) + } + + fn create_synthetic_qhook( + &mut self, + first_arg: ast::Expr, + ctx_kind: HookKind, + ctx_name: JsWord, + custom_symbol: Option, + ) -> ast::CallExpr { + self._create_synthetic_qhook(first_arg, ctx_kind, ctx_name, custom_symbol) + .0 + } + + fn _create_synthetic_qhook( + &mut self, + first_arg: ast::Expr, + ctx_kind: HookKind, + ctx_name: JsWord, + custom_symbol: Option, + ) -> (ast::CallExpr, bool) { + 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(custom_symbol); + + // Collect descendent idents + let descendent_idents = { + let mut collector = IdentCollector::new(); + first_arg.visit_with(&mut collector); + collector.get_words() + }; + + let (decl_collect, invalid_decl): (_, Vec<_>) = self + .decl_stack + .iter() + .flat_map(|v| v.iter()) + .cloned() + .partition(|(_, t)| matches!(t, IdentType::Var(_))); + + self.hook_stack.push(symbol_name.clone()); + let span = first_arg.span(); + let folded = first_arg.fold_with(self); + self.hook_stack.pop(); + + // Collect local idents + let local_idents = self.get_local_idents(&folded); + + let (mut scoped_idents, immutable) = + compute_scoped_idents(&descendent_idents, &decl_collect); + if !can_capture && !scoped_idents.is_empty() { + HANDLER.with(|handler| { let ids: Vec<_> = scoped_idents.iter().map(|id| id.0.as_ref()).collect(); handler .struct_span_err_with_code( @@ -647,29 +647,29 @@ impl<'a> QwikTransform<'a> { ) .emit(); }); - scoped_idents = vec![]; - } - let hook_data = HookData { - extension: self.options.extension.clone(), - local_idents, - scoped_idents, - parent_hook: self.hook_stack.last().cloned(), - ctx_kind, - ctx_name, - origin: self.options.path_data.rel_path.to_slash_lossy().into(), - display_name, - need_transform: true, - hash, - }; - let should_emit = self.should_emit_hook(&hook_data); - if should_emit { - for id in &hook_data.local_idents { - if !self.options.global_collect.exports.contains_key(id) { - if self.options.global_collect.root.contains_key(id) { - self.ensure_export(id); - } - if invalid_decl.iter().any(|entry| entry.0 == *id) { - HANDLER.with(|handler| { + scoped_idents = vec![]; + } + let hook_data = HookData { + extension: self.options.extension.clone(), + local_idents, + scoped_idents, + parent_hook: self.hook_stack.last().cloned(), + ctx_kind, + ctx_name, + origin: self.options.path_data.rel_path.to_slash_lossy().into(), + display_name, + need_transform: true, + hash, + }; + let should_emit = self.should_emit_hook(&hook_data); + if should_emit { + for id in &hook_data.local_idents { + if !self.options.global_collect.exports.contains_key(id) { + if self.options.global_collect.root.contains_key(id) { + self.ensure_export(id); + } + if invalid_decl.iter().any(|entry| entry.0 == *id) { + HANDLER.with(|handler| { handler .struct_err_with_code( &format!( @@ -680,444 +680,444 @@ impl<'a> QwikTransform<'a> { ) .emit(); }); - } - } - } - } - if !should_emit { - (self.create_noop_qrl(&symbol_name, hook_data), immutable) - } else if self.is_inline() { - let folded = if !hook_data.scoped_idents.is_empty() { - let new_local = self.ensure_core_import(&USE_LEXICAL_SCOPE); - transform_function_expr(folded, &new_local, &hook_data.scoped_idents) - } else { - folded - }; - let folded = if self.should_reg_hook(&hook_data.ctx_name) { - ast::Expr::Call(self.create_internal_call( - &_REG_SYMBOL, - vec![ - folded, - ast::Expr::Lit(ast::Lit::Str(ast::Str::from(hook_data.hash.clone()))), - ], - true, - )) - } else { - folded - }; - ( - self.create_inline_qrl(hook_data, folded, symbol_name, span), - immutable, - ) - } else { - ( - self.create_hook(hook_data, folded, symbol_name, span, hook_hash), - immutable, - ) - } - } - - fn get_local_idents(&self, expr: &ast::Expr) -> Vec { - let mut collector = IdentCollector::new(); - expr.visit_with(&mut collector); - - let use_h = collector.use_h; - let use_fragment = collector.use_fragment; - - let mut idents = collector.get_words(); - if use_h { - if let Some(id) = &self.h_fn { - idents.push(id.clone()); - } - } - if use_fragment { - if let Some(id) = &self.fragment_fn { - idents.push(id.clone()); - } - } - idents - } - - fn create_hook( - &mut self, - hook_data: HookData, - expr: ast::Expr, - symbol_name: JsWord, - span: Span, - hook_hash: u64, - ) -> ast::CallExpr { - let canonical_filename = get_canonical_filename(&symbol_name); - - let entry = self - .options - .entry_policy - .get_entry_for_sym( - &hook_data.hash, - self.options.path_data, - &self.stack_ctxt, - &hook_data, - ) - .map(|entry| JsWord::from(escape_sym(entry.as_ref()))); - - let mut filename = format!( - "./{}", - entry - .as_ref() - .map(|e| e.as_ref()) - .unwrap_or(&canonical_filename) - ); - if self.options.explicit_extensions { - filename.push('.'); - filename.push_str(&self.options.extension); - } - let inside_hook = !self.hook_stack.is_empty(); - let import_path = if inside_hook { - fix_path("a", "a", &filename) - } else { - fix_path( - &self.options.path_data.base_dir, - &self.options.path_data.abs_dir, - &filename, - ) - } - .unwrap(); - - let o = self.create_qrl(import_path, &symbol_name, &hook_data, &span); - self.hooks.push(Hook { - entry, - span, - canonical_filename, - name: symbol_name, - data: hook_data, - expr: Box::new(expr), - hash: hook_hash, - }); - o - } - - fn handle_jsx(&mut self, mut node: ast::CallExpr) -> ast::CallExpr { - let node_type = node.args.remove(0); - let node_props = node.args.remove(0); - let (name_token, is_fn, is_text_only) = match &*node_type.expr { - ast::Expr::Lit(ast::Lit::Str(str)) => { - self.stack_ctxt.push(str.value.to_string()); - (true, false, is_text_only(&str.value)) - } - ast::Expr::Ident(ident) => { - self.stack_ctxt.push(ident.sym.to_string()); - if !self.immutable_function_cmp.contains(&id!(ident)) { - self.jsx_mutable = true; - } - (true, true, false) - } - _ => { - self.jsx_mutable = true; - (false, true, false) - } - }; - let should_emit_key = is_fn || self.root_jsx_mode; - self.root_jsx_mode = false; - - let (dynamic_props, mutable_props, immutable_props, children, flags) = - self.handle_jsx_props_obj(node_props, is_fn, is_text_only); - - let key = if node.args.len() == 1 { - node.args.remove(0) - } else if should_emit_key { - let new_key = format!("{}_{}", &base64(self.file_hash)[0..2], self.jsx_key_counter); - self.jsx_key_counter += 1; - ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - value: new_key.into(), - raw: None, - }))), - } - } else { - get_null_arg() - }; - - let (jsx_func, mut args) = if is_fn { - ( - self.ensure_core_import(&_JSX_C), - vec![node_type, mutable_props, flags, key], - ) - } else if dynamic_props { - ( - self.ensure_core_import(&_JSX_S), - vec![node_type, mutable_props, immutable_props, flags, key], - ) - } else { - ( - self.ensure_core_import(&_JSX_Q), - vec![ - node_type, - mutable_props, - immutable_props, - children, - flags, - key, - ], - ) - }; - if self.options.mode == EmitMode::Dev { - args.push(self.get_dev_location(node.span)); - } - - if name_token { - self.stack_ctxt.pop(); - } - ast::CallExpr { - callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(&jsx_func)))), - args, - ..node - } - } - - fn handle_jsx_value( - &mut self, - ctx_name: JsWord, - value: Option, - ) -> Option { - if let Some(ast::JSXAttrValue::JSXExprContainer(container)) = value { - if let ast::JSXExpr::Expr(expr) = container.expr { - let is_fn = matches!(*expr, ast::Expr::Arrow(_) | ast::Expr::Fn(_)); - if is_fn { - Some(ast::JSXAttrValue::JSXExprContainer(ast::JSXExprContainer { - span: DUMMY_SP, - expr: ast::JSXExpr::Expr(Box::new(ast::Expr::Call( - self.create_synthetic_qhook(*expr, HookKind::JSXProp, ctx_name, None), - ))), - })) - } else { - Some(ast::JSXAttrValue::JSXExprContainer(ast::JSXExprContainer { - span: DUMMY_SP, - expr: ast::JSXExpr::Expr(expr), - })) - } - } else { - Some(ast::JSXAttrValue::JSXExprContainer(container)) - } - } else { - value - } - } - - pub fn ensure_import(&mut self, new_specifier: &JsWord, source: &JsWord) -> Id { - self.options.global_collect.import(new_specifier, source) - } - - pub fn ensure_core_import(&mut self, new_specifier: &JsWord) -> Id { - self.options - .global_collect - .import(new_specifier, &self.options.core_module) - } - - fn ensure_export(&mut self, id: &Id) { - let exported_name: Option = Some(format!("_auto_{}", id.0).into()); - if self - .options - .global_collect - .add_export(id.clone(), exported_name.clone()) - { - self.extra_bottom_items - .insert(id.clone(), create_synthetic_named_export(id, exported_name)); - } - } - - fn create_qrl( - &mut self, - url: JsWord, - symbol: &str, - hook_data: &HookData, - span: &Span, - ) -> ast::CallExpr { - let mut args = vec![ - ast::Expr::Arrow(ast::ArrowExpr { - is_async: false, - is_generator: false, - span: DUMMY_SP, - params: vec![], - return_type: None, - type_params: None, - body: Box::new(ast::BlockStmtOrExpr::Expr(Box::new(ast::Expr::Call( - ast::CallExpr { - callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(ast::Ident::new( - js_word!("import"), - DUMMY_SP, - )))), - span: DUMMY_SP, - type_args: None, - args: vec![ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - value: url, - raw: None, - }))), - }], - }, - )))), - }), - ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - value: symbol.into(), - raw: None, - })), - ]; - let fn_callee = if self.options.mode == EmitMode::Dev { - args.push(get_qrl_dev_obj( - &self.options.path_data.abs_path, - hook_data, - span, - )); - _QRL_DEV.clone() - } else { - _QRL.clone() - }; - - // Injects state - if !hook_data.scoped_idents.is_empty() { - args.push(ast::Expr::Array(ast::ArrayLit { - span: DUMMY_SP, - elems: hook_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) - } - - fn create_inline_qrl( - &mut self, - hook_data: HookData, - expr: ast::Expr, - symbol_name: JsWord, - span: Span, - ) -> ast::CallExpr { - let should_inline = matches!(self.options.entry_strategy, EntryStrategy::Inline) - || matches!(expr, ast::Expr::Ident(_)); - let inlined_expr = if should_inline { - expr - } else { - let new_ident = private_ident!(symbol_name.clone()); - self.hooks.push(Hook { - entry: None, - span, - canonical_filename: get_canonical_filename(&symbol_name), - name: symbol_name.clone(), - data: hook_data.clone(), - expr: Box::new(expr), - hash: new_ident.span.ctxt().as_u32() as u64, - }); - ast::Expr::Ident(new_ident) - }; - - let mut args = vec![ - inlined_expr, - ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - value: symbol_name, - raw: None, - })), - ]; - - let fn_callee = if self.options.mode == EmitMode::Dev { - args.push(get_qrl_dev_obj( - &self.options.path_data.abs_path, - &hook_data, - &span, - )); - _INLINED_QRL_DEV.clone() - } else { - _INLINED_QRL.clone() - }; - - // Injects state - if !hook_data.scoped_idents.is_empty() { - args.push(ast::Expr::Array(ast::ArrayLit { - span: DUMMY_SP, - elems: hook_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) - } - - pub fn create_internal_call( - &mut self, - fn_name: &JsWord, - exprs: Vec, - pure: bool, - ) -> ast::CallExpr { - let local = self.ensure_core_import(fn_name); - let span = if pure { - if let Some(comments) = self.options.comments { - let span = Span::dummy_with_cmt(); - comments.add_pure_comment(span.lo); - span - } else { - DUMMY_SP - } - } else { - DUMMY_SP - }; - ast::CallExpr { - callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(&local)))), - span, - type_args: None, - args: exprs - .into_iter() - .map(|expr| ast::ExprOrSpread { - spread: None, - expr: Box::new(expr), - }) - .collect(), - } - } - - fn fix_dynamic_import(&self, node: ast::CallExpr) -> ast::CallExpr { - if let Some(expr_spread) = node.args.get(0) { - if let ast::Expr::Lit(ast::Lit::Str(string)) = &*expr_spread.expr { - let new_value = fix_path( - &self.options.path_data.abs_dir, - &self.options.path_data.base_dir, - string.value.as_ref(), - ) - .unwrap(); - - return ast::CallExpr { - args: vec![ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { - value: new_value, - raw: None, - span: string.span, - }))), - }], - ..node - }; - } - } - HANDLER.with(|handler| { + } + } + } + } + if !should_emit { + (self.create_noop_qrl(&symbol_name, hook_data), immutable) + } else if self.is_inline() { + let folded = if !hook_data.scoped_idents.is_empty() { + let new_local = self.ensure_core_import(&USE_LEXICAL_SCOPE); + transform_function_expr(folded, &new_local, &hook_data.scoped_idents) + } else { + folded + }; + let folded = if self.should_reg_hook(&hook_data.ctx_name) { + ast::Expr::Call(self.create_internal_call( + &_REG_SYMBOL, + vec![ + folded, + ast::Expr::Lit(ast::Lit::Str(ast::Str::from(hook_data.hash.clone()))), + ], + true, + )) + } else { + folded + }; + ( + self.create_inline_qrl(hook_data, folded, symbol_name, span), + immutable, + ) + } else { + ( + self.create_hook(hook_data, folded, symbol_name, span, hook_hash), + immutable, + ) + } + } + + fn get_local_idents(&self, expr: &ast::Expr) -> Vec { + let mut collector = IdentCollector::new(); + expr.visit_with(&mut collector); + + let use_h = collector.use_h; + let use_fragment = collector.use_fragment; + + let mut idents = collector.get_words(); + if use_h { + if let Some(id) = &self.h_fn { + idents.push(id.clone()); + } + } + if use_fragment { + if let Some(id) = &self.fragment_fn { + idents.push(id.clone()); + } + } + idents + } + + fn create_hook( + &mut self, + hook_data: HookData, + expr: ast::Expr, + symbol_name: JsWord, + span: Span, + hook_hash: u64, + ) -> ast::CallExpr { + let canonical_filename = get_canonical_filename(&symbol_name); + + let entry = self + .options + .entry_policy + .get_entry_for_sym( + &hook_data.hash, + self.options.path_data, + &self.stack_ctxt, + &hook_data, + ) + .map(|entry| JsWord::from(escape_sym(entry.as_ref()))); + + let mut filename = format!( + "./{}", + entry + .as_ref() + .map(|e| e.as_ref()) + .unwrap_or(&canonical_filename) + ); + if self.options.explicit_extensions { + filename.push('.'); + filename.push_str(&self.options.extension); + } + let inside_hook = !self.hook_stack.is_empty(); + let import_path = if inside_hook { + fix_path("a", "a", &filename) + } else { + fix_path( + &self.options.path_data.base_dir, + &self.options.path_data.abs_dir, + &filename, + ) + } + .unwrap(); + + let o = self.create_qrl(import_path, &symbol_name, &hook_data, &span); + self.hooks.push(Hook { + entry, + span, + canonical_filename, + name: symbol_name, + data: hook_data, + expr: Box::new(expr), + hash: hook_hash, + }); + o + } + + fn handle_jsx(&mut self, mut node: ast::CallExpr) -> ast::CallExpr { + let node_type = node.args.remove(0); + let node_props = node.args.remove(0); + let (name_token, is_fn, is_text_only) = match &*node_type.expr { + ast::Expr::Lit(ast::Lit::Str(str)) => { + self.stack_ctxt.push(str.value.to_string()); + (true, false, is_text_only(&str.value)) + } + ast::Expr::Ident(ident) => { + self.stack_ctxt.push(ident.sym.to_string()); + if !self.immutable_function_cmp.contains(&id!(ident)) { + self.jsx_mutable = true; + } + (true, true, false) + } + _ => { + self.jsx_mutable = true; + (false, true, false) + } + }; + let should_emit_key = is_fn || self.root_jsx_mode; + self.root_jsx_mode = false; + + let (dynamic_props, mutable_props, immutable_props, children, flags) = + self.handle_jsx_props_obj(node_props, is_fn, is_text_only); + + let key = if node.args.len() == 1 { + node.args.remove(0) + } else if should_emit_key { + let new_key = format!("{}_{}", &base64(self.file_hash)[0..2], self.jsx_key_counter); + self.jsx_key_counter += 1; + ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: new_key.into(), + raw: None, + }))), + } + } else { + get_null_arg() + }; + + let (jsx_func, mut args) = if is_fn { + ( + self.ensure_core_import(&_JSX_C), + vec![node_type, mutable_props, flags, key], + ) + } else if dynamic_props { + ( + self.ensure_core_import(&_JSX_S), + vec![node_type, mutable_props, immutable_props, flags, key], + ) + } else { + ( + self.ensure_core_import(&_JSX_Q), + vec![ + node_type, + mutable_props, + immutable_props, + children, + flags, + key, + ], + ) + }; + if self.options.mode == EmitMode::Dev { + args.push(self.get_dev_location(node.span)); + } + + if name_token { + self.stack_ctxt.pop(); + } + ast::CallExpr { + callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(&jsx_func)))), + args, + ..node + } + } + + fn handle_jsx_value( + &mut self, + ctx_name: JsWord, + value: Option, + ) -> Option { + if let Some(ast::JSXAttrValue::JSXExprContainer(container)) = value { + if let ast::JSXExpr::Expr(expr) = container.expr { + let is_fn = matches!(*expr, ast::Expr::Arrow(_) | ast::Expr::Fn(_)); + if is_fn { + Some(ast::JSXAttrValue::JSXExprContainer(ast::JSXExprContainer { + span: DUMMY_SP, + expr: ast::JSXExpr::Expr(Box::new(ast::Expr::Call( + self.create_synthetic_qhook(*expr, HookKind::JSXProp, ctx_name, None), + ))), + })) + } else { + Some(ast::JSXAttrValue::JSXExprContainer(ast::JSXExprContainer { + span: DUMMY_SP, + expr: ast::JSXExpr::Expr(expr), + })) + } + } else { + Some(ast::JSXAttrValue::JSXExprContainer(container)) + } + } else { + value + } + } + + pub fn ensure_import(&mut self, new_specifier: &JsWord, source: &JsWord) -> Id { + self.options.global_collect.import(new_specifier, source) + } + + pub fn ensure_core_import(&mut self, new_specifier: &JsWord) -> Id { + self.options + .global_collect + .import(new_specifier, &self.options.core_module) + } + + fn ensure_export(&mut self, id: &Id) { + let exported_name: Option = Some(format!("_auto_{}", id.0).into()); + if self + .options + .global_collect + .add_export(id.clone(), exported_name.clone()) + { + self.extra_bottom_items + .insert(id.clone(), create_synthetic_named_export(id, exported_name)); + } + } + + fn create_qrl( + &mut self, + url: JsWord, + symbol: &str, + hook_data: &HookData, + span: &Span, + ) -> ast::CallExpr { + let mut args = vec![ + ast::Expr::Arrow(ast::ArrowExpr { + is_async: false, + is_generator: false, + span: DUMMY_SP, + params: vec![], + return_type: None, + type_params: None, + body: Box::new(ast::BlockStmtOrExpr::Expr(Box::new(ast::Expr::Call( + ast::CallExpr { + callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(ast::Ident::new( + js_word!("import"), + DUMMY_SP, + )))), + span: DUMMY_SP, + type_args: None, + args: vec![ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: url, + raw: None, + }))), + }], + }, + )))), + }), + ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: symbol.into(), + raw: None, + })), + ]; + let fn_callee = if self.options.mode == EmitMode::Dev { + args.push(get_qrl_dev_obj( + &self.options.path_data.abs_path, + hook_data, + span, + )); + _QRL_DEV.clone() + } else { + _QRL.clone() + }; + + // Injects state + if !hook_data.scoped_idents.is_empty() { + args.push(ast::Expr::Array(ast::ArrayLit { + span: DUMMY_SP, + elems: hook_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) + } + + fn create_inline_qrl( + &mut self, + hook_data: HookData, + expr: ast::Expr, + symbol_name: JsWord, + span: Span, + ) -> ast::CallExpr { + let should_inline = matches!(self.options.entry_strategy, EntryStrategy::Inline) + || matches!(expr, ast::Expr::Ident(_)); + let inlined_expr = if should_inline { + expr + } else { + let new_ident = private_ident!(symbol_name.clone()); + self.hooks.push(Hook { + entry: None, + span, + canonical_filename: get_canonical_filename(&symbol_name), + name: symbol_name.clone(), + data: hook_data.clone(), + expr: Box::new(expr), + hash: new_ident.span.ctxt().as_u32() as u64, + }); + ast::Expr::Ident(new_ident) + }; + + let mut args = vec![ + inlined_expr, + ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: symbol_name, + raw: None, + })), + ]; + + let fn_callee = if self.options.mode == EmitMode::Dev { + args.push(get_qrl_dev_obj( + &self.options.path_data.abs_path, + &hook_data, + &span, + )); + _INLINED_QRL_DEV.clone() + } else { + _INLINED_QRL.clone() + }; + + // Injects state + if !hook_data.scoped_idents.is_empty() { + args.push(ast::Expr::Array(ast::ArrayLit { + span: DUMMY_SP, + elems: hook_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) + } + + pub fn create_internal_call( + &mut self, + fn_name: &JsWord, + exprs: Vec, + pure: bool, + ) -> ast::CallExpr { + let local = self.ensure_core_import(fn_name); + let span = if pure { + if let Some(comments) = self.options.comments { + let span = Span::dummy_with_cmt(); + comments.add_pure_comment(span.lo); + span + } else { + DUMMY_SP + } + } else { + DUMMY_SP + }; + ast::CallExpr { + callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(&local)))), + span, + type_args: None, + args: exprs + .into_iter() + .map(|expr| ast::ExprOrSpread { + spread: None, + expr: Box::new(expr), + }) + .collect(), + } + } + + fn fix_dynamic_import(&self, node: ast::CallExpr) -> ast::CallExpr { + if let Some(expr_spread) = node.args.get(0) { + if let ast::Expr::Lit(ast::Lit::Str(string)) = &*expr_spread.expr { + let new_value = fix_path( + &self.options.path_data.abs_dir, + &self.options.path_data.base_dir, + string.value.as_ref(), + ) + .unwrap(); + + return ast::CallExpr { + args: vec![ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { + value: new_value, + raw: None, + span: string.span, + }))), + }], + ..node + }; + } + } + HANDLER.with(|handler| { handler .struct_span_err_with_code( node.span, @@ -1126,1035 +1126,1035 @@ impl<'a> QwikTransform<'a> { ) .emit(); }); - node - } - - fn handle_jsx_props_obj( - &mut self, - expr: ast::ExprOrSpread, - is_fn: bool, - is_text_only: bool, - ) -> ( - bool, - ast::ExprOrSpread, - ast::ExprOrSpread, - ast::ExprOrSpread, - ast::ExprOrSpread, - ) { - let (dynamic_props, mut mutable_props, mut immutable_props, children, flags) = - self.internal_handle_jsx_props_obj(expr, is_fn, is_text_only); - - // For functions, put the immutable props under the "_IMMUTABLE" prop - if is_fn && !immutable_props.is_empty() { - mutable_props.push(ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue( - ast::KeyValueProp { - key: ast::PropName::Computed(ast::ComputedPropName { - span: DUMMY_SP, - expr: Box::new(ast::Expr::Ident(new_ident_from_id( - &self.ensure_core_import(&_IMMUTABLE), - ))), - }), - value: Box::new(ast::Expr::Object(ast::ObjectLit { - props: immutable_props.drain(..).collect(), - span: DUMMY_SP, - })), - }, - )))) - } - - let mutable = if mutable_props.is_empty() { - get_null_arg() - } else { - self.jsx_mutable = true; - ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Object(ast::ObjectLit { - props: mutable_props, - span: DUMMY_SP, - })), - } - }; - let immutable_props = if immutable_props.is_empty() { - get_null_arg() - } else { - ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Object(ast::ObjectLit { - props: immutable_props, - span: DUMMY_SP, - })), - } - }; - - let children = if let Some(children) = children { - ast::ExprOrSpread { - spread: None, - expr: children, - } - } else { - get_null_arg() - }; - - let flags = ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Lit(ast::Lit::Num(ast::Number { - value: flags as f64, - span: DUMMY_SP, - raw: None, - }))), - }; - (dynamic_props, mutable, immutable_props, children, flags) - } - - #[allow(clippy::cognitive_complexity)] - fn internal_handle_jsx_props_obj( - &mut self, - expr: ast::ExprOrSpread, - is_fn: bool, - is_text_only: bool, - ) -> ( - bool, - Vec, - Vec, - Option>, - u32, - ) { - match expr { - ast::ExprOrSpread { - expr: box ast::Expr::Object(object), - .. - } => { - let mut mutable_props = vec![]; - let mut immutable_props = vec![]; - let mut children = None; - let mut static_listeners = true; - let mut static_subtree = true; - let mut event_handlers = vec![]; - let immutable_idents: Vec<_> = self - .decl_stack - .iter() - .flat_map(|v| v.iter()) - .filter(|(_, t)| matches!(t, IdentType::Var(true))) - .cloned() - .collect(); - - let dynamic_props = object - .props - .iter() - .any(|prop| !matches!(prop, ast::PropOrSpread::Prop(_))); - - for prop in object.props { - let mut name_token = false; - match prop { - ast::PropOrSpread::Prop(box ast::Prop::KeyValue(ref node)) => { - let key_word = match node.key { - ast::PropName::Ident(ref ident) => Some(ident.sym.clone()), - ast::PropName::Str(ref s) => Some(s.value.clone()), - _ => None, - }; - if let Some(key_word) = key_word { - let is_children = key_word == *CHILDREN; - if !is_children { - self.stack_ctxt.push(key_word.to_string()); - name_token = true; - } - if is_children { - let prev = self.jsx_mutable; - self.jsx_mutable = false; - let folded = node.value.clone(); - let transformed_children = if let Some(new_children) = - self.convert_children(&folded, &immutable_idents) - { - if is_text_only { - self.jsx_mutable = true; - folded.fold_with(self) - } else { - Box::new(new_children.fold_with(self)) - } - } else { - folded.fold_with(self) - }; - if self.jsx_mutable { - static_subtree = false; - } else { - self.jsx_mutable = prev; - } - if is_fn || dynamic_props { - // self.jsx_mutable = true; - // static_subtree = false; - mutable_props.push(ast::PropOrSpread::Prop(Box::new( - ast::Prop::KeyValue(ast::KeyValueProp { - key: node.key.clone(), - value: transformed_children, - }), - ))); - } else { - children = Some(transformed_children); - } - } else if !is_fn && key_word.starts_with("bind:") { - let folded = node.value.clone().fold_with(self); - let prop_name: JsWord = key_word[5..].into(); - immutable_props.push(ast::PropOrSpread::Prop(Box::new( - ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Str(ast::Str { - span: DUMMY_SP, - value: prop_name.clone(), - raw: None, - }), - value: folded.clone(), - }), - ))); - let elm = private_ident!("elm"); - let arrow_fn = ast::Expr::Arrow(ast::ArrowExpr { - span: DUMMY_SP, - params: vec![ - ast::Pat::Ident(ast::BindingIdent::from( - ast::Ident::new("_".into(), DUMMY_SP), - )), - ast::Pat::Ident(ast::BindingIdent::from(elm.clone())), - ], - body: Box::new(ast::BlockStmtOrExpr::Expr(Box::new( - ast::Expr::Assign(ast::AssignExpr { - left: ast::PatOrExpr::Expr(Box::new( - ast::Expr::Member(ast::MemberExpr { - obj: folded.clone(), - prop: ast::MemberProp::Ident( - ast::Ident::new( - "value".into(), - DUMMY_SP, - ), - ), - span: DUMMY_SP, - }), - )), - op: ast::AssignOp::Assign, - right: Box::new(ast::Expr::Member( - ast::MemberExpr { - obj: Box::new(ast::Expr::Ident(elm)), - prop: ast::MemberProp::Ident( - ast::Ident::new(prop_name, DUMMY_SP), - ), - span: DUMMY_SP, - }, - )), - span: DUMMY_SP, - }), - ))), - is_async: false, - is_generator: false, - type_params: None, - return_type: None, - }); - let event_handler = JsWord::from(match key_word.as_ref() { - "bind:value" => "onInput$", - "bind:checked" => "onInput$", - _ => "onChange$", - }); - let (converted_expr, immutable) = self._create_synthetic_qhook( - arrow_fn, - HookKind::EventHandler, - event_handler.clone(), - None, - ); - if !immutable { - static_listeners = false; - } - let converted_prop = ast::PropOrSpread::Prop(Box::new( - ast::Prop::KeyValue(ast::KeyValueProp { - value: Box::new(ast::Expr::Call(converted_expr)), - key: ast::PropName::Str(ast::Str { - span: DUMMY_SP, - value: event_handler, - raw: None, - }), - }), - )); - event_handlers.push(converted_prop); - } else if !is_fn && (key_word == *REF || key_word == *QSLOT) { - // skip - mutable_props.push(prop.fold_with(self)); - } else if convert_signal_word(&key_word).is_some() { - if matches!(*node.value, ast::Expr::Arrow(_) | ast::Expr::Fn(_)) - { - let (converted_expr, immutable) = self - ._create_synthetic_qhook( - *node.value.clone(), - if is_fn { - HookKind::JSXProp - } else { - HookKind::EventHandler - }, - key_word.clone(), - None, - ); - - let converted_prop = ast::PropOrSpread::Prop(Box::new( - ast::Prop::KeyValue(ast::KeyValueProp { - value: Box::new(ast::Expr::Call(converted_expr)), - key: node.key.clone(), - }), - )); - if is_fn { - if immutable { - immutable_props.push(ast::PropOrSpread::Prop( - Box::new(ast::Prop::KeyValue( - ast::KeyValueProp { - key: node.key.clone(), - value: Box::new(ast::Expr::Ident( - new_ident_from_id( - &self.ensure_core_import( - &_IMMUTABLE, - ), - ), - )), - }, - )), - )); - } - mutable_props.push(converted_prop.fold_with(self)); - } else { - if !immutable { - static_listeners = false; - } - event_handlers.push(converted_prop.fold_with(self)); - } - } else { - let immutable_prop = is_immutable_expr( - &node.value, - &self.options.global_collect, - Some(&immutable_idents), - ); - if !immutable_prop { - static_listeners = false; - } - - if is_fn { - if immutable_prop { - immutable_props.push(ast::PropOrSpread::Prop( - Box::new(ast::Prop::KeyValue( - ast::KeyValueProp { - key: node.key.clone(), - value: Box::new(ast::Expr::Ident( - new_ident_from_id( - &self.ensure_core_import( - &_IMMUTABLE, - ), - ), - )), - }, - )), - )); - } - mutable_props.push(prop.fold_with(self)); - } else { - event_handlers.push(prop.fold_with(self)); - } - } - } else if is_immutable_expr( - &node.value, - &self.options.global_collect, - Some(&immutable_idents), - ) { - if is_fn || dynamic_props { - immutable_props.push(ast::PropOrSpread::Prop(Box::new( - ast::Prop::KeyValue(ast::KeyValueProp { - key: node.key.clone(), - value: Box::new(ast::Expr::Ident( - new_ident_from_id( - &self.ensure_core_import(&_IMMUTABLE), - ), - )), - }), - ))); - mutable_props.push(prop.fold_with(self)); - } else { - immutable_props.push(prop.fold_with(self)); - } - } else if let Some((getter, is_immutable)) = - self.convert_to_getter(&node.value, is_fn) - { - let key = node.key.clone(); - if is_fn || dynamic_props { - mutable_props.push(ast::PropOrSpread::Prop(Box::new( - ast::Prop::Getter(ast::GetterProp { - span: DUMMY_SP, - type_ann: None, - key: key.clone(), - body: Some(ast::BlockStmt { - span: DUMMY_SP, - stmts: vec![ast::Stmt::Return( - ast::ReturnStmt { - span: DUMMY_SP, - arg: Some(node.value.clone()), - }, - )], - }), - }), - ))); - } - let entry = ast::PropOrSpread::Prop(Box::new( - ast::Prop::KeyValue(ast::KeyValueProp { - key, - value: Box::new(getter), - }), - )); - if is_fn || is_immutable { - immutable_props.push(entry); - } else { - mutable_props.push(entry); - } - } else { - mutable_props.push(prop.fold_with(self)); - } - } else { - mutable_props.push(prop.fold_with(self)); - } - } - prop => { - static_listeners = false; - static_subtree = false; - mutable_props.push(prop.fold_with(self)); - } - }; - if name_token { - self.stack_ctxt.pop(); - } - } - let mut flags = 0; - if static_listeners { - flags |= 1 << 0; - immutable_props.extend(event_handlers.into_iter()); - } else { - mutable_props.extend(event_handlers.into_iter()); - } - - if static_subtree { - flags |= 1 << 1; - } - ( - dynamic_props, - mutable_props, - immutable_props, - children, - flags, - ) - } - _ => (true, vec![], vec![], None, 0), - } - } - - fn convert_children( - &mut self, - expr: &ast::Expr, - immutable_idents: &Vec, - ) -> Option { - match expr { - ast::Expr::Call(call_expr) => { - match &call_expr.callee { - ast::Callee::Expr(box ast::Expr::Ident(ident)) => { - if !self.jsx_functions.contains(&id!(ident)) { - self.jsx_mutable = true; - } - } - _ => { - self.jsx_mutable = true; - } - }; - None - } - ast::Expr::Array(array) => Some(ast::Expr::Array(ast::ArrayLit { - span: array.span, - elems: array - .elems - .iter() - .map(|e| { - if let Some(e) = e { - if let Some(new) = - self.convert_to_signal_item(&e.expr, immutable_idents) - { - Some(ast::ExprOrSpread { - spread: e.spread, - expr: Box::new(new), - }) - } else { - Some(e.clone()) - } - } else { - None - } - }) - .collect(), - })), - expr => self.convert_to_signal_item(expr, immutable_idents), - } - } - - /* Convert an expression to a QRL or a getter. Returns (expr, isImmutable) */ - fn convert_to_getter(&mut self, expr: &ast::Expr, is_fn: bool) -> Option<(ast::Expr, bool)> { - let inlined = self.create_synthetic_qqhook(expr.clone(), true); - if let Some(expr) = inlined.0 { - return Some((expr, inlined.1)); - } - if inlined.1 { - return if is_fn { - Some(( - ast::Expr::Ident(new_ident_from_id(&self.ensure_core_import(&_IMMUTABLE))), - true, - )) - } else { - Some((expr.clone(), true)) - }; - } - if let ast::Expr::Member(member) = expr { - let prop_sym = prop_to_string(&member.prop); - if let Some(prop_sym) = prop_sym { - let id = if is_fn { - self.ensure_core_import(&_WRAP_PROP) - } else { - self.ensure_core_import(&_WRAP_SIGNAL) - }; - return Some((make_wrap(&id, member.obj.clone(), prop_sym), false)); - } - } - None - } - - fn convert_to_signal_item( - &mut self, - expr: &ast::Expr, - immutable_idents: &Vec, - ) -> Option { - if let ast::Expr::Call(call_expr) = expr { - match &call_expr.callee { - ast::Callee::Expr(box ast::Expr::Ident(ident)) => { - if !self.jsx_functions.contains(&id!(ident)) { - self.jsx_mutable = true; - } - } - _ => { - self.jsx_mutable = true; - } - }; - return None; - } - if is_immutable_expr(expr, &self.options.global_collect, Some(immutable_idents)) { - return None; - } - let (inlined_expr, immutable) = self.create_synthetic_qqhook(expr.clone(), false); - if !immutable { - self.jsx_mutable = true; - } - if inlined_expr.is_some() { - return inlined_expr; - } else if immutable { - return None; - } - if let ast::Expr::Member(member) = expr { - let prop_sym = prop_to_string(&member.prop); - if let Some(prop_sym) = prop_sym { - let id = self.ensure_core_import(&_WRAP_SIGNAL); - return Some(make_wrap(&id, member.obj.clone(), prop_sym)); - } - } - // let inlined = self.create_synthetic_qqhook(expr.clone(), false); - // if let Some((expr, _)) = inlined { - // return Some(expr); - // } - None - } - - fn should_reg_hook(&self, ctx_name: &str) -> bool { - if let Some(strip_ctx_name) = self.options.reg_ctx_name { - if strip_ctx_name - .iter() - .any(|v| ctx_name.starts_with(v.as_ref())) - { - return true; - } - } - false - } - - fn should_emit_hook(&self, hook_data: &HookData) -> bool { - if let Some(strip_ctx_name) = self.options.strip_ctx_name { - if strip_ctx_name - .iter() - .any(|v| hook_data.ctx_name.starts_with(v.as_ref())) - { - return false; - } - } - if self.options.strip_event_handlers && hook_data.ctx_kind == HookKind::EventHandler { - return false; - } - true - } - - fn create_noop_qrl(&mut self, symbol_name: &JsWord, hook_data: HookData) -> ast::CallExpr { - let mut args = vec![ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - value: symbol_name.into(), - raw: None, - }))]; - // Injects state - if !hook_data.scoped_idents.is_empty() { - args.push(ast::Expr::Array(ast::ArrayLit { - span: DUMMY_SP, - elems: hook_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(&_NOOP_QRL, args, true) - } + node + } + + fn handle_jsx_props_obj( + &mut self, + expr: ast::ExprOrSpread, + is_fn: bool, + is_text_only: bool, + ) -> ( + bool, + ast::ExprOrSpread, + ast::ExprOrSpread, + ast::ExprOrSpread, + ast::ExprOrSpread, + ) { + let (dynamic_props, mut mutable_props, mut immutable_props, children, flags) = + self.internal_handle_jsx_props_obj(expr, is_fn, is_text_only); + + // For functions, put the immutable props under the "_IMMUTABLE" prop + if is_fn && !immutable_props.is_empty() { + mutable_props.push(ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue( + ast::KeyValueProp { + key: ast::PropName::Computed(ast::ComputedPropName { + span: DUMMY_SP, + expr: Box::new(ast::Expr::Ident(new_ident_from_id( + &self.ensure_core_import(&_IMMUTABLE), + ))), + }), + value: Box::new(ast::Expr::Object(ast::ObjectLit { + props: immutable_props.drain(..).collect(), + span: DUMMY_SP, + })), + }, + )))) + } + + let mutable = if mutable_props.is_empty() { + get_null_arg() + } else { + self.jsx_mutable = true; + ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Object(ast::ObjectLit { + props: mutable_props, + span: DUMMY_SP, + })), + } + }; + let immutable_props = if immutable_props.is_empty() { + get_null_arg() + } else { + ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Object(ast::ObjectLit { + props: immutable_props, + span: DUMMY_SP, + })), + } + }; + + let children = if let Some(children) = children { + ast::ExprOrSpread { + spread: None, + expr: children, + } + } else { + get_null_arg() + }; + + let flags = ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Lit(ast::Lit::Num(ast::Number { + value: flags as f64, + span: DUMMY_SP, + raw: None, + }))), + }; + (dynamic_props, mutable, immutable_props, children, flags) + } + + #[allow(clippy::cognitive_complexity)] + fn internal_handle_jsx_props_obj( + &mut self, + expr: ast::ExprOrSpread, + is_fn: bool, + is_text_only: bool, + ) -> ( + bool, + Vec, + Vec, + Option>, + u32, + ) { + match expr { + ast::ExprOrSpread { + expr: box ast::Expr::Object(object), + .. + } => { + let mut mutable_props = vec![]; + let mut immutable_props = vec![]; + let mut children = None; + let mut static_listeners = true; + let mut static_subtree = true; + let mut event_handlers = vec![]; + let immutable_idents: Vec<_> = self + .decl_stack + .iter() + .flat_map(|v| v.iter()) + .filter(|(_, t)| matches!(t, IdentType::Var(true))) + .cloned() + .collect(); + + let dynamic_props = object + .props + .iter() + .any(|prop| !matches!(prop, ast::PropOrSpread::Prop(_))); + + for prop in object.props { + let mut name_token = false; + match prop { + ast::PropOrSpread::Prop(box ast::Prop::KeyValue(ref node)) => { + let key_word = match node.key { + ast::PropName::Ident(ref ident) => Some(ident.sym.clone()), + ast::PropName::Str(ref s) => Some(s.value.clone()), + _ => None, + }; + if let Some(key_word) = key_word { + let is_children = key_word == *CHILDREN; + if !is_children { + self.stack_ctxt.push(key_word.to_string()); + name_token = true; + } + if is_children { + let prev = self.jsx_mutable; + self.jsx_mutable = false; + let folded = node.value.clone(); + let transformed_children = if let Some(new_children) = + self.convert_children(&folded, &immutable_idents) + { + if is_text_only { + self.jsx_mutable = true; + folded.fold_with(self) + } else { + Box::new(new_children.fold_with(self)) + } + } else { + folded.fold_with(self) + }; + if self.jsx_mutable { + static_subtree = false; + } else { + self.jsx_mutable = prev; + } + if is_fn || dynamic_props { + // self.jsx_mutable = true; + // static_subtree = false; + mutable_props.push(ast::PropOrSpread::Prop(Box::new( + ast::Prop::KeyValue(ast::KeyValueProp { + key: node.key.clone(), + value: transformed_children, + }), + ))); + } else { + children = Some(transformed_children); + } + } else if !is_fn && key_word.starts_with("bind:") { + let folded = node.value.clone().fold_with(self); + let prop_name: JsWord = key_word[5..].into(); + immutable_props.push(ast::PropOrSpread::Prop(Box::new( + ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Str(ast::Str { + span: DUMMY_SP, + value: prop_name.clone(), + raw: None, + }), + value: folded.clone(), + }), + ))); + let elm = private_ident!("elm"); + let arrow_fn = ast::Expr::Arrow(ast::ArrowExpr { + span: DUMMY_SP, + params: vec![ + ast::Pat::Ident(ast::BindingIdent::from( + ast::Ident::new("_".into(), DUMMY_SP), + )), + ast::Pat::Ident(ast::BindingIdent::from(elm.clone())), + ], + body: Box::new(ast::BlockStmtOrExpr::Expr(Box::new( + ast::Expr::Assign(ast::AssignExpr { + left: ast::PatOrExpr::Expr(Box::new( + ast::Expr::Member(ast::MemberExpr { + obj: folded.clone(), + prop: ast::MemberProp::Ident( + ast::Ident::new( + "value".into(), + DUMMY_SP, + ), + ), + span: DUMMY_SP, + }), + )), + op: ast::AssignOp::Assign, + right: Box::new(ast::Expr::Member( + ast::MemberExpr { + obj: Box::new(ast::Expr::Ident(elm)), + prop: ast::MemberProp::Ident( + ast::Ident::new(prop_name, DUMMY_SP), + ), + span: DUMMY_SP, + }, + )), + span: DUMMY_SP, + }), + ))), + is_async: false, + is_generator: false, + type_params: None, + return_type: None, + }); + let event_handler = JsWord::from(match key_word.as_ref() { + "bind:value" => "onInput$", + "bind:checked" => "onInput$", + _ => "onChange$", + }); + let (converted_expr, immutable) = self._create_synthetic_qhook( + arrow_fn, + HookKind::EventHandler, + event_handler.clone(), + None, + ); + if !immutable { + static_listeners = false; + } + let converted_prop = ast::PropOrSpread::Prop(Box::new( + ast::Prop::KeyValue(ast::KeyValueProp { + value: Box::new(ast::Expr::Call(converted_expr)), + key: ast::PropName::Str(ast::Str { + span: DUMMY_SP, + value: event_handler, + raw: None, + }), + }), + )); + event_handlers.push(converted_prop); + } else if !is_fn && (key_word == *REF || key_word == *QSLOT) { + // skip + mutable_props.push(prop.fold_with(self)); + } else if convert_signal_word(&key_word).is_some() { + if matches!(*node.value, ast::Expr::Arrow(_) | ast::Expr::Fn(_)) + { + let (converted_expr, immutable) = self + ._create_synthetic_qhook( + *node.value.clone(), + if is_fn { + HookKind::JSXProp + } else { + HookKind::EventHandler + }, + key_word.clone(), + None, + ); + + let converted_prop = ast::PropOrSpread::Prop(Box::new( + ast::Prop::KeyValue(ast::KeyValueProp { + value: Box::new(ast::Expr::Call(converted_expr)), + key: node.key.clone(), + }), + )); + if is_fn { + if immutable { + immutable_props.push(ast::PropOrSpread::Prop( + Box::new(ast::Prop::KeyValue( + ast::KeyValueProp { + key: node.key.clone(), + value: Box::new(ast::Expr::Ident( + new_ident_from_id( + &self.ensure_core_import( + &_IMMUTABLE, + ), + ), + )), + }, + )), + )); + } + mutable_props.push(converted_prop.fold_with(self)); + } else { + if !immutable { + static_listeners = false; + } + event_handlers.push(converted_prop.fold_with(self)); + } + } else { + let immutable_prop = is_immutable_expr( + &node.value, + &self.options.global_collect, + Some(&immutable_idents), + ); + if !immutable_prop { + static_listeners = false; + } + + if is_fn { + if immutable_prop { + immutable_props.push(ast::PropOrSpread::Prop( + Box::new(ast::Prop::KeyValue( + ast::KeyValueProp { + key: node.key.clone(), + value: Box::new(ast::Expr::Ident( + new_ident_from_id( + &self.ensure_core_import( + &_IMMUTABLE, + ), + ), + )), + }, + )), + )); + } + mutable_props.push(prop.fold_with(self)); + } else { + event_handlers.push(prop.fold_with(self)); + } + } + } else if is_immutable_expr( + &node.value, + &self.options.global_collect, + Some(&immutable_idents), + ) { + if is_fn || dynamic_props { + immutable_props.push(ast::PropOrSpread::Prop(Box::new( + ast::Prop::KeyValue(ast::KeyValueProp { + key: node.key.clone(), + value: Box::new(ast::Expr::Ident( + new_ident_from_id( + &self.ensure_core_import(&_IMMUTABLE), + ), + )), + }), + ))); + mutable_props.push(prop.fold_with(self)); + } else { + immutable_props.push(prop.fold_with(self)); + } + } else if let Some((getter, is_immutable)) = + self.convert_to_getter(&node.value, is_fn) + { + let key = node.key.clone(); + if is_fn || dynamic_props { + mutable_props.push(ast::PropOrSpread::Prop(Box::new( + ast::Prop::Getter(ast::GetterProp { + span: DUMMY_SP, + type_ann: None, + key: key.clone(), + body: Some(ast::BlockStmt { + span: DUMMY_SP, + stmts: vec![ast::Stmt::Return( + ast::ReturnStmt { + span: DUMMY_SP, + arg: Some(node.value.clone()), + }, + )], + }), + }), + ))); + } + let entry = ast::PropOrSpread::Prop(Box::new( + ast::Prop::KeyValue(ast::KeyValueProp { + key, + value: Box::new(getter), + }), + )); + if is_fn || is_immutable { + immutable_props.push(entry); + } else { + mutable_props.push(entry); + } + } else { + mutable_props.push(prop.fold_with(self)); + } + } else { + mutable_props.push(prop.fold_with(self)); + } + } + prop => { + static_listeners = false; + static_subtree = false; + mutable_props.push(prop.fold_with(self)); + } + }; + if name_token { + self.stack_ctxt.pop(); + } + } + let mut flags = 0; + if static_listeners { + flags |= 1 << 0; + immutable_props.extend(event_handlers.into_iter()); + } else { + mutable_props.extend(event_handlers.into_iter()); + } + + if static_subtree { + flags |= 1 << 1; + } + ( + dynamic_props, + mutable_props, + immutable_props, + children, + flags, + ) + } + _ => (true, vec![], vec![], None, 0), + } + } + + fn convert_children( + &mut self, + expr: &ast::Expr, + immutable_idents: &Vec, + ) -> Option { + match expr { + ast::Expr::Call(call_expr) => { + match &call_expr.callee { + ast::Callee::Expr(box ast::Expr::Ident(ident)) => { + if !self.jsx_functions.contains(&id!(ident)) { + self.jsx_mutable = true; + } + } + _ => { + self.jsx_mutable = true; + } + }; + None + } + ast::Expr::Array(array) => Some(ast::Expr::Array(ast::ArrayLit { + span: array.span, + elems: array + .elems + .iter() + .map(|e| { + if let Some(e) = e { + if let Some(new) = + self.convert_to_signal_item(&e.expr, immutable_idents) + { + Some(ast::ExprOrSpread { + spread: e.spread, + expr: Box::new(new), + }) + } else { + Some(e.clone()) + } + } else { + None + } + }) + .collect(), + })), + expr => self.convert_to_signal_item(expr, immutable_idents), + } + } + + /* Convert an expression to a QRL or a getter. Returns (expr, isImmutable) */ + fn convert_to_getter(&mut self, expr: &ast::Expr, is_fn: bool) -> Option<(ast::Expr, bool)> { + let inlined = self.create_synthetic_qqhook(expr.clone(), true); + if let Some(expr) = inlined.0 { + return Some((expr, inlined.1)); + } + if inlined.1 { + return if is_fn { + Some(( + ast::Expr::Ident(new_ident_from_id(&self.ensure_core_import(&_IMMUTABLE))), + true, + )) + } else { + Some((expr.clone(), true)) + }; + } + if let ast::Expr::Member(member) = expr { + let prop_sym = prop_to_string(&member.prop); + if let Some(prop_sym) = prop_sym { + let id = if is_fn { + self.ensure_core_import(&_WRAP_PROP) + } else { + self.ensure_core_import(&_WRAP_SIGNAL) + }; + return Some((make_wrap(&id, member.obj.clone(), prop_sym), false)); + } + } + None + } + + fn convert_to_signal_item( + &mut self, + expr: &ast::Expr, + immutable_idents: &Vec, + ) -> Option { + if let ast::Expr::Call(call_expr) = expr { + match &call_expr.callee { + ast::Callee::Expr(box ast::Expr::Ident(ident)) => { + if !self.jsx_functions.contains(&id!(ident)) { + self.jsx_mutable = true; + } + } + _ => { + self.jsx_mutable = true; + } + }; + return None; + } + if is_immutable_expr(expr, &self.options.global_collect, Some(immutable_idents)) { + return None; + } + let (inlined_expr, immutable) = self.create_synthetic_qqhook(expr.clone(), false); + if !immutable { + self.jsx_mutable = true; + } + if inlined_expr.is_some() { + return inlined_expr; + } else if immutable { + return None; + } + if let ast::Expr::Member(member) = expr { + let prop_sym = prop_to_string(&member.prop); + if let Some(prop_sym) = prop_sym { + let id = self.ensure_core_import(&_WRAP_SIGNAL); + return Some(make_wrap(&id, member.obj.clone(), prop_sym)); + } + } + // let inlined = self.create_synthetic_qqhook(expr.clone(), false); + // if let Some((expr, _)) = inlined { + // return Some(expr); + // } + None + } + + fn should_reg_hook(&self, ctx_name: &str) -> bool { + if let Some(strip_ctx_name) = self.options.reg_ctx_name { + if strip_ctx_name + .iter() + .any(|v| ctx_name.starts_with(v.as_ref())) + { + return true; + } + } + false + } + + fn should_emit_hook(&self, hook_data: &HookData) -> bool { + if let Some(strip_ctx_name) = self.options.strip_ctx_name { + if strip_ctx_name + .iter() + .any(|v| hook_data.ctx_name.starts_with(v.as_ref())) + { + return false; + } + } + if self.options.strip_event_handlers && hook_data.ctx_kind == HookKind::EventHandler { + return false; + } + true + } + + fn create_noop_qrl(&mut self, symbol_name: &JsWord, hook_data: HookData) -> ast::CallExpr { + let mut args = vec![ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: symbol_name.into(), + raw: None, + }))]; + // Injects state + if !hook_data.scoped_idents.is_empty() { + args.push(ast::Expr::Array(ast::ArrayLit { + span: DUMMY_SP, + elems: hook_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(&_NOOP_QRL, args, true) + } } impl<'a> Fold for QwikTransform<'a> { - noop_fold_type!(); - - fn fold_module(&mut self, node: ast::Module) -> ast::Module { - let mut body = Vec::with_capacity(node.body.len() + 10); - let mut module_body = node - .body - .into_iter() - .flat_map(|i| { - let module_item = i.fold_with(self); - let output: Vec<_> = if matches!(self.options.entry_strategy, EntryStrategy::Hoist) - { - self.hooks - .drain(..) - .map(|hook| { - let id = (hook.name.clone(), SyntaxContext::from_u32(hook.hash as u32)); - ast::ModuleItem::Stmt(ast::Stmt::Decl(ast::Decl::Var(Box::new( - ast::VarDecl { - kind: ast::VarDeclKind::Const, - decls: vec![ast::VarDeclarator { - name: ast::Pat::Ident(ast::BindingIdent::from( - new_ident_from_id(&id), - )), - init: Some(hook.expr), - definite: false, - span: DUMMY_SP, - }], - declare: false, - span: DUMMY_SP, - }, - )))) - }) - .chain(iter::once(module_item)) - .collect() - } else { - vec![module_item] - }; - output - }) - .collect(); - - body.extend( - self.options - .global_collect - .synthetic - .iter() - .map(|(new_local, import)| { - create_synthetic_named_import(new_local, &import.source) - }), - ); - // body.extend(self.extra_top_items.values().cloned()); - body.append(&mut module_body); - body.extend(self.extra_bottom_items.values().cloned()); - - ast::Module { body, ..node } - } - - // Variable tracking - fn fold_var_decl(&mut self, node: ast::VarDecl) -> ast::VarDecl { - if let Some(current_scope) = self.decl_stack.last_mut() { - for decl in &node.decls { - let mut identifiers = Vec::with_capacity(node.decls.len() + 2); - collect_from_pat(&decl.name, &mut identifiers); - let ident_type = if node.kind == ast::VarDeclKind::Const - && matches!(decl.name, ast::Pat::Ident(_)) - && is_return_static(&decl.init) - { - IdentType::Var(true) - } else { - IdentType::Var(false) - }; - current_scope.extend(identifiers.into_iter().map(|(id, _)| (id, ident_type))); - } - } - node.fold_children_with(self) - } - - fn fold_var_declarator(&mut self, node: ast::VarDeclarator) -> ast::VarDeclarator { - let mut stacked = false; - if let ast::Pat::Ident(ref ident) = node.name { - self.stack_ctxt.push(ident.id.sym.to_string()); - stacked = true; - } - let o = node.fold_children_with(self); - if stacked { - self.stack_ctxt.pop(); - } - o - } - - fn fold_fn_decl(&mut self, node: ast::FnDecl) -> ast::FnDecl { - if let Some(current_scope) = self.decl_stack.last_mut() { - current_scope.push((id!(node.ident), IdentType::Fn)); - } - self.stack_ctxt.push(node.ident.sym.to_string()); - - let o = node.fold_children_with(self); - self.stack_ctxt.pop(); - o - } - - fn fold_function(&mut self, node: ast::Function) -> ast::Function { - self.decl_stack.push(vec![]); - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - - let prev_jsx_mutable = self.jsx_mutable; - self.jsx_mutable = false; - - let is_component = self.in_component; - self.in_component = false; - let is_condition = is_conditional_jsx_block( - node.body.as_ref().unwrap(), - &self.jsx_functions, - &self.immutable_function_cmp, - ); - let current_scope = self - .decl_stack - .last_mut() - .expect("Declaration stack empty!"); - - for param in &node.params { - let mut identifiers = vec![]; - collect_from_pat(¶m.pat, &mut identifiers); - let is_constant = is_component && matches!(param.pat, ast::Pat::Ident(_)); - current_scope.extend( - identifiers - .into_iter() - .map(|(id, _)| (id, IdentType::Var(is_constant))), - ); - } - let mut o = node.fold_children_with(self); - if is_condition { - if let Some(body) = &mut o.body { - body.stmts.insert( - 0, - ast::Stmt::Expr(ast::ExprStmt { - span: DUMMY_SP, - expr: Box::new(ast::Expr::Call(self.create_internal_call( - &_JSX_BRANCH, - vec![], - false, - ))), - }), - ); - } - } - self.root_jsx_mode = prev; - self.jsx_mutable = prev_jsx_mutable; - self.decl_stack.pop(); - - o - } - - fn fold_arrow_expr(&mut self, node: ast::ArrowExpr) -> ast::ArrowExpr { - self.decl_stack.push(vec![]); - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - - let prev_jsx_mutable = self.jsx_mutable; - self.jsx_mutable = false; - - let is_component = self.in_component; - self.in_component = false; - let is_condition = is_conditional_jsx( - &node.body, - &self.jsx_functions, - &self.immutable_function_cmp, - ); - let current_scope = self - .decl_stack - .last_mut() - .expect("Declaration stack empty!"); - for param in &node.params { - let mut identifiers = vec![]; - collect_from_pat(param, &mut identifiers); - let is_constant = is_component && matches!(param, ast::Pat::Ident(_)); - current_scope.extend( - identifiers - .into_iter() - .map(|(id, _)| (id, IdentType::Var(is_constant))), - ); - } - - let mut o = node.fold_children_with(self); - if is_condition { - match &mut o.body { - box ast::BlockStmtOrExpr::BlockStmt(block) => { - block.stmts.insert( - 0, - ast::Stmt::Expr(ast::ExprStmt { - span: DUMMY_SP, - expr: Box::new(ast::Expr::Call(self.create_internal_call( - &_JSX_BRANCH, - vec![], - false, - ))), - }), - ); - } - box ast::BlockStmtOrExpr::Expr(expr) => { - *expr = Box::new(ast::Expr::Call(self.create_internal_call( - &_JSX_BRANCH, - vec![*expr.to_owned()], - true, - ))); - } - } - } - self.root_jsx_mode = prev; - self.jsx_mutable = prev_jsx_mutable; - self.decl_stack.pop(); - - o - } - - fn fold_for_stmt(&mut self, node: ast::ForStmt) -> ast::ForStmt { - self.decl_stack.push(vec![]); - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - self.in_component = false; - let o = node.fold_children_with(self); - self.root_jsx_mode = prev; - self.decl_stack.pop(); - - o - } - - fn fold_for_in_stmt(&mut self, node: ast::ForInStmt) -> ast::ForInStmt { - self.decl_stack.push(vec![]); - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - self.in_component = false; - let o = node.fold_children_with(self); - self.root_jsx_mode = prev; - self.decl_stack.pop(); - - o - } - - fn fold_for_of_stmt(&mut self, node: ast::ForOfStmt) -> ast::ForOfStmt { - self.decl_stack.push(vec![]); - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - let o = node.fold_children_with(self); - self.root_jsx_mode = prev; - self.decl_stack.pop(); - - o - } - - fn fold_bin_expr(&mut self, node: ast::BinExpr) -> ast::BinExpr { - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - let o = node.fold_children_with(self); - self.root_jsx_mode = prev; - o - } - - fn fold_cond_expr(&mut self, node: ast::CondExpr) -> ast::CondExpr { - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - let o = node.fold_children_with(self); - self.root_jsx_mode = prev; - o - } - - fn fold_if_stmt(&mut self, node: ast::IfStmt) -> ast::IfStmt { - self.decl_stack.push(vec![]); - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - self.in_component = false; - let o = node.fold_children_with(self); - self.root_jsx_mode = prev; - self.decl_stack.pop(); - - o - } - - fn fold_block_stmt(&mut self, node: ast::BlockStmt) -> ast::BlockStmt { - self.decl_stack.push(vec![]); - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - let o = node.fold_children_with(self); - self.root_jsx_mode = prev; - self.decl_stack.pop(); - - o - } - - fn fold_while_stmt(&mut self, node: ast::WhileStmt) -> ast::WhileStmt { - self.decl_stack.push(vec![]); - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - self.in_component = false; - - let o = node.fold_children_with(self); - self.root_jsx_mode = prev; - self.decl_stack.pop(); - - o - } - - fn fold_class_decl(&mut self, node: ast::ClassDecl) -> ast::ClassDecl { - if let Some(current_scope) = self.decl_stack.last_mut() { - current_scope.push((id!(node.ident), IdentType::Class)); - } - - self.stack_ctxt.push(node.ident.sym.to_string()); - self.decl_stack.push(vec![]); - let prev = self.root_jsx_mode; - self.root_jsx_mode = true; - self.in_component = false; - let o = node.fold_children_with(self); - self.root_jsx_mode = prev; - self.stack_ctxt.pop(); - self.decl_stack.pop(); - - o - } - - fn fold_jsx_element(&mut self, node: ast::JSXElement) -> ast::JSXElement { - let mut stacked = false; - - if let ast::JSXElementName::Ident(ref ident) = node.opening.name { - self.stack_ctxt.push(ident.sym.to_string()); - stacked = true; - } - let o = node.fold_children_with(self); - if stacked { - self.stack_ctxt.pop(); - } - o - } - - fn fold_export_default_expr(&mut self, node: ast::ExportDefaultExpr) -> ast::ExportDefaultExpr { - let mut filename = self.options.path_data.file_stem.clone(); - if filename == "index" { - if let Some(foldername) = self - .options - .path_data - .rel_dir - .file_name() - .and_then(|s| s.to_str()) - { - filename = foldername.to_string(); - } - } - self.stack_ctxt.push(filename); - let o = node.fold_children_with(self); - self.stack_ctxt.pop(); - - o - } - - fn fold_jsx_attr(&mut self, node: ast::JSXAttr) -> ast::JSXAttr { - let node = match node.name { - ast::JSXAttrName::Ident(ref ident) => { - let new_word = convert_signal_word(&ident.sym); - self.stack_ctxt.push(ident.sym.to_string()); - - if new_word.is_some() { - ast::JSXAttr { - value: self.handle_jsx_value(ident.sym.clone(), node.value), - ..node - } - } else { - node - } - } - ast::JSXAttrName::JSXNamespacedName(ref namespaced) => { - let new_word = convert_signal_word(&namespaced.name.sym); - let ident_name = [ - namespaced.ns.sym.as_ref(), - "-", - namespaced.name.sym.as_ref(), - ] - .concat(); - self.stack_ctxt.push(ident_name.clone()); - if new_word.is_some() { - ast::JSXAttr { - value: self.handle_jsx_value(JsWord::from(ident_name), node.value), - ..node - } - } else { - node - } - } - }; - - let o = node.fold_children_with(self); - self.stack_ctxt.pop(); - o - } - - fn fold_call_expr(&mut self, node: ast::CallExpr) -> ast::CallExpr { - let mut name_token = false; - let mut replace_callee = None; - let mut ctx_name: JsWord = QHOOK.clone(); - - match &node.callee { - ast::Callee::Import(_) => { - if !self.is_inside_module() { - return self.fix_dynamic_import(node); - } - } - ast::Callee::Expr(box ast::Expr::Ident(ident)) => { - if id_eq!(ident, &self.sync_qrl_fn) { - return self.handle_sync_qrl(node); - } else if id_eq!(ident, &self.qhook_fn) { - if let Some(comments) = self.options.comments { - comments.add_pure_comment(ident.span.lo); - } - return self.handle_qhook(node); - } else if self.jsx_functions.contains(&id!(ident)) { - return self.handle_jsx(node); - } else if id_eq!(ident, &self.inlined_qrl_fn) { - return self.handle_inlined_qhook(node); - } else if let Some(specifier) = self.marker_functions.get(&id!(ident)) { - self.stack_ctxt.push(ident.sym.to_string()); - ctx_name = specifier.clone(); - name_token = true; - - if id_eq!(ident, &self.qcomponent_fn) { - self.in_component = true; - if let Some(comments) = self.options.comments { - comments.add_pure_comment(node.span.lo); - } - } - let global_collect = &mut self.options.global_collect; - if let Some(import) = global_collect.imports.get(&id!(ident)).cloned() { - let new_specifier = - convert_signal_word(&import.specifier).expect("Specifier ends with $"); - let new_local = self.ensure_import(&new_specifier, &import.source); - replace_callee = Some(new_ident_from_id(&new_local).as_callee()); - } else { - let new_specifier = - convert_signal_word(&ident.sym).expect("Specifier ends with $"); - global_collect + noop_fold_type!(); + + fn fold_module(&mut self, node: ast::Module) -> ast::Module { + let mut body = Vec::with_capacity(node.body.len() + 10); + let mut module_body = node + .body + .into_iter() + .flat_map(|i| { + let module_item = i.fold_with(self); + let output: Vec<_> = if matches!(self.options.entry_strategy, EntryStrategy::Hoist) + { + self.hooks + .drain(..) + .map(|hook| { + let id = (hook.name.clone(), SyntaxContext::from_u32(hook.hash as u32)); + ast::ModuleItem::Stmt(ast::Stmt::Decl(ast::Decl::Var(Box::new( + ast::VarDecl { + kind: ast::VarDeclKind::Const, + decls: vec![ast::VarDeclarator { + name: ast::Pat::Ident(ast::BindingIdent::from( + new_ident_from_id(&id), + )), + init: Some(hook.expr), + definite: false, + span: DUMMY_SP, + }], + declare: false, + span: DUMMY_SP, + }, + )))) + }) + .chain(iter::once(module_item)) + .collect() + } else { + vec![module_item] + }; + output + }) + .collect(); + + body.extend( + self.options + .global_collect + .synthetic + .iter() + .map(|(new_local, import)| { + create_synthetic_named_import(new_local, &import.source) + }), + ); + // body.extend(self.extra_top_items.values().cloned()); + body.append(&mut module_body); + body.extend(self.extra_bottom_items.values().cloned()); + + ast::Module { body, ..node } + } + + // Variable tracking + fn fold_var_decl(&mut self, node: ast::VarDecl) -> ast::VarDecl { + if let Some(current_scope) = self.decl_stack.last_mut() { + for decl in &node.decls { + let mut identifiers = Vec::with_capacity(node.decls.len() + 2); + collect_from_pat(&decl.name, &mut identifiers); + let ident_type = if node.kind == ast::VarDeclKind::Const + && matches!(decl.name, ast::Pat::Ident(_)) + && is_return_static(&decl.init) + { + IdentType::Var(true) + } else { + IdentType::Var(false) + }; + current_scope.extend(identifiers.into_iter().map(|(id, _)| (id, ident_type))); + } + } + node.fold_children_with(self) + } + + fn fold_var_declarator(&mut self, node: ast::VarDeclarator) -> ast::VarDeclarator { + let mut stacked = false; + if let ast::Pat::Ident(ref ident) = node.name { + self.stack_ctxt.push(ident.id.sym.to_string()); + stacked = true; + } + let o = node.fold_children_with(self); + if stacked { + self.stack_ctxt.pop(); + } + o + } + + fn fold_fn_decl(&mut self, node: ast::FnDecl) -> ast::FnDecl { + if let Some(current_scope) = self.decl_stack.last_mut() { + current_scope.push((id!(node.ident), IdentType::Fn)); + } + self.stack_ctxt.push(node.ident.sym.to_string()); + + let o = node.fold_children_with(self); + self.stack_ctxt.pop(); + o + } + + fn fold_function(&mut self, node: ast::Function) -> ast::Function { + self.decl_stack.push(vec![]); + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + + let prev_jsx_mutable = self.jsx_mutable; + self.jsx_mutable = false; + + let is_component = self.in_component; + self.in_component = false; + let is_condition = is_conditional_jsx_block( + node.body.as_ref().unwrap(), + &self.jsx_functions, + &self.immutable_function_cmp, + ); + let current_scope = self + .decl_stack + .last_mut() + .expect("Declaration stack empty!"); + + for param in &node.params { + let mut identifiers = vec![]; + collect_from_pat(¶m.pat, &mut identifiers); + let is_constant = is_component && matches!(param.pat, ast::Pat::Ident(_)); + current_scope.extend( + identifiers + .into_iter() + .map(|(id, _)| (id, IdentType::Var(is_constant))), + ); + } + let mut o = node.fold_children_with(self); + if is_condition { + if let Some(body) = &mut o.body { + body.stmts.insert( + 0, + ast::Stmt::Expr(ast::ExprStmt { + span: DUMMY_SP, + expr: Box::new(ast::Expr::Call(self.create_internal_call( + &_JSX_BRANCH, + vec![], + false, + ))), + }), + ); + } + } + self.root_jsx_mode = prev; + self.jsx_mutable = prev_jsx_mutable; + self.decl_stack.pop(); + + o + } + + fn fold_arrow_expr(&mut self, node: ast::ArrowExpr) -> ast::ArrowExpr { + self.decl_stack.push(vec![]); + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + + let prev_jsx_mutable = self.jsx_mutable; + self.jsx_mutable = false; + + let is_component = self.in_component; + self.in_component = false; + let is_condition = is_conditional_jsx( + &node.body, + &self.jsx_functions, + &self.immutable_function_cmp, + ); + let current_scope = self + .decl_stack + .last_mut() + .expect("Declaration stack empty!"); + for param in &node.params { + let mut identifiers = vec![]; + collect_from_pat(param, &mut identifiers); + let is_constant = is_component && matches!(param, ast::Pat::Ident(_)); + current_scope.extend( + identifiers + .into_iter() + .map(|(id, _)| (id, IdentType::Var(is_constant))), + ); + } + + let mut o = node.fold_children_with(self); + if is_condition { + match &mut o.body { + box ast::BlockStmtOrExpr::BlockStmt(block) => { + block.stmts.insert( + 0, + ast::Stmt::Expr(ast::ExprStmt { + span: DUMMY_SP, + expr: Box::new(ast::Expr::Call(self.create_internal_call( + &_JSX_BRANCH, + vec![], + false, + ))), + }), + ); + } + box ast::BlockStmtOrExpr::Expr(expr) => { + *expr = Box::new(ast::Expr::Call(self.create_internal_call( + &_JSX_BRANCH, + vec![*expr.to_owned()], + true, + ))); + } + } + } + self.root_jsx_mode = prev; + self.jsx_mutable = prev_jsx_mutable; + self.decl_stack.pop(); + + o + } + + fn fold_for_stmt(&mut self, node: ast::ForStmt) -> ast::ForStmt { + self.decl_stack.push(vec![]); + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + self.in_component = false; + let o = node.fold_children_with(self); + self.root_jsx_mode = prev; + self.decl_stack.pop(); + + o + } + + fn fold_for_in_stmt(&mut self, node: ast::ForInStmt) -> ast::ForInStmt { + self.decl_stack.push(vec![]); + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + self.in_component = false; + let o = node.fold_children_with(self); + self.root_jsx_mode = prev; + self.decl_stack.pop(); + + o + } + + fn fold_for_of_stmt(&mut self, node: ast::ForOfStmt) -> ast::ForOfStmt { + self.decl_stack.push(vec![]); + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + let o = node.fold_children_with(self); + self.root_jsx_mode = prev; + self.decl_stack.pop(); + + o + } + + fn fold_bin_expr(&mut self, node: ast::BinExpr) -> ast::BinExpr { + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + let o = node.fold_children_with(self); + self.root_jsx_mode = prev; + o + } + + fn fold_cond_expr(&mut self, node: ast::CondExpr) -> ast::CondExpr { + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + let o = node.fold_children_with(self); + self.root_jsx_mode = prev; + o + } + + fn fold_if_stmt(&mut self, node: ast::IfStmt) -> ast::IfStmt { + self.decl_stack.push(vec![]); + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + self.in_component = false; + let o = node.fold_children_with(self); + self.root_jsx_mode = prev; + self.decl_stack.pop(); + + o + } + + fn fold_block_stmt(&mut self, node: ast::BlockStmt) -> ast::BlockStmt { + self.decl_stack.push(vec![]); + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + let o = node.fold_children_with(self); + self.root_jsx_mode = prev; + self.decl_stack.pop(); + + o + } + + fn fold_while_stmt(&mut self, node: ast::WhileStmt) -> ast::WhileStmt { + self.decl_stack.push(vec![]); + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + self.in_component = false; + + let o = node.fold_children_with(self); + self.root_jsx_mode = prev; + self.decl_stack.pop(); + + o + } + + fn fold_class_decl(&mut self, node: ast::ClassDecl) -> ast::ClassDecl { + if let Some(current_scope) = self.decl_stack.last_mut() { + current_scope.push((id!(node.ident), IdentType::Class)); + } + + self.stack_ctxt.push(node.ident.sym.to_string()); + self.decl_stack.push(vec![]); + let prev = self.root_jsx_mode; + self.root_jsx_mode = true; + self.in_component = false; + let o = node.fold_children_with(self); + self.root_jsx_mode = prev; + self.stack_ctxt.pop(); + self.decl_stack.pop(); + + o + } + + fn fold_jsx_element(&mut self, node: ast::JSXElement) -> ast::JSXElement { + let mut stacked = false; + + if let ast::JSXElementName::Ident(ref ident) = node.opening.name { + self.stack_ctxt.push(ident.sym.to_string()); + stacked = true; + } + let o = node.fold_children_with(self); + if stacked { + self.stack_ctxt.pop(); + } + o + } + + fn fold_export_default_expr(&mut self, node: ast::ExportDefaultExpr) -> ast::ExportDefaultExpr { + let mut filename = self.options.path_data.file_stem.clone(); + if filename == "index" { + if let Some(foldername) = self + .options + .path_data + .rel_dir + .file_name() + .and_then(|s| s.to_str()) + { + filename = foldername.to_string(); + } + } + self.stack_ctxt.push(filename); + let o = node.fold_children_with(self); + self.stack_ctxt.pop(); + + o + } + + fn fold_jsx_attr(&mut self, node: ast::JSXAttr) -> ast::JSXAttr { + let node = match node.name { + ast::JSXAttrName::Ident(ref ident) => { + let new_word = convert_signal_word(&ident.sym); + self.stack_ctxt.push(ident.sym.to_string()); + + if new_word.is_some() { + ast::JSXAttr { + value: self.handle_jsx_value(ident.sym.clone(), node.value), + ..node + } + } else { + node + } + } + ast::JSXAttrName::JSXNamespacedName(ref namespaced) => { + let new_word = convert_signal_word(&namespaced.name.sym); + let ident_name = [ + namespaced.ns.sym.as_ref(), + "-", + namespaced.name.sym.as_ref(), + ] + .concat(); + self.stack_ctxt.push(ident_name.clone()); + if new_word.is_some() { + ast::JSXAttr { + value: self.handle_jsx_value(JsWord::from(ident_name), node.value), + ..node + } + } else { + node + } + } + }; + + let o = node.fold_children_with(self); + self.stack_ctxt.pop(); + o + } + + fn fold_call_expr(&mut self, node: ast::CallExpr) -> ast::CallExpr { + let mut name_token = false; + let mut replace_callee = None; + let mut ctx_name: JsWord = QHOOK.clone(); + + match &node.callee { + ast::Callee::Import(_) => { + if !self.is_inside_module() { + return self.fix_dynamic_import(node); + } + } + ast::Callee::Expr(box ast::Expr::Ident(ident)) => { + if id_eq!(ident, &self.sync_qrl_fn) { + return self.handle_sync_qrl(node); + } else if id_eq!(ident, &self.qhook_fn) { + if let Some(comments) = self.options.comments { + comments.add_pure_comment(ident.span.lo); + } + return self.handle_qhook(node); + } else if self.jsx_functions.contains(&id!(ident)) { + return self.handle_jsx(node); + } else if id_eq!(ident, &self.inlined_qrl_fn) { + return self.handle_inlined_qhook(node); + } else if let Some(specifier) = self.marker_functions.get(&id!(ident)) { + self.stack_ctxt.push(ident.sym.to_string()); + ctx_name = specifier.clone(); + name_token = true; + + if id_eq!(ident, &self.qcomponent_fn) { + self.in_component = true; + if let Some(comments) = self.options.comments { + comments.add_pure_comment(node.span.lo); + } + } + let global_collect = &mut self.options.global_collect; + if let Some(import) = global_collect.imports.get(&id!(ident)).cloned() { + let new_specifier = + convert_signal_word(&import.specifier).expect("Specifier ends with $"); + let new_local = self.ensure_import(&new_specifier, &import.source); + replace_callee = Some(new_ident_from_id(&new_local).as_callee()); + } else { + let new_specifier = + convert_signal_word(&ident.sym).expect("Specifier ends with $"); + global_collect .exports .keys() .find(|id| id.0 == new_specifier) @@ -2174,255 +2174,253 @@ impl<'a> Fold for QwikTransform<'a> { replace_callee = Some(new_ident_from_id(new_local).as_callee()); }, ); - } - } else { - self.stack_ctxt.push(ident.sym.to_string()); - name_token = true; - } - } - _ => {} - } - - let convert_qrl = replace_callee.is_some(); - let callee = if let Some(callee) = replace_callee { - callee - } else { - node.callee - }; - let callee = callee.fold_with(self); - let args: Vec = node - .args - .into_iter() - .enumerate() - .map(|(i, arg)| { - if convert_qrl && i == 0 { - ast::ExprOrSpread { - expr: Box::new(ast::Expr::Call(self.create_synthetic_qhook( - *arg.expr, - HookKind::Function, - ctx_name.clone(), - None, - ))) - .fold_with(self), - ..arg - } - } else { - arg.fold_with(self) - } - }) - .collect(); - - if name_token { - self.stack_ctxt.pop(); - } - self.in_component = false; - ast::CallExpr { - callee, - args, - ..node - } - } + } + } else { + self.stack_ctxt.push(ident.sym.to_string()); + name_token = true; + } + } + _ => {} + } + + let convert_qrl = replace_callee.is_some(); + let callee = if let Some(callee) = replace_callee { + callee + } else { + node.callee + }; + let callee = callee.fold_with(self); + let args: Vec = node + .args + .into_iter() + .enumerate() + .map(|(i, arg)| { + if convert_qrl && i == 0 { + ast::ExprOrSpread { + expr: Box::new(ast::Expr::Call(self.create_synthetic_qhook( + *arg.expr, + HookKind::Function, + ctx_name.clone(), + None, + ))) + .fold_with(self), + ..arg + } + } else { + arg.fold_with(self) + } + }) + .collect(); + + if name_token { + self.stack_ctxt.pop(); + } + self.in_component = false; + ast::CallExpr { + callee, + args, + ..node + } + } } pub fn add_handle_watch(body: &mut Vec, core_module: &JsWord) { - body.push(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportNamed( - ast::NamedExport { - src: Some(Box::new(ast::Str { - span: DUMMY_SP, - value: core_module.clone(), - raw: None, - })), - span: DUMMY_SP, - asserts: None, - type_only: false, - specifiers: vec![ast::ExportSpecifier::Named(ast::ExportNamedSpecifier { - orig: ast::ModuleExportName::Ident(ast::Ident::new(HANDLE_WATCH.clone(), DUMMY_SP)), - exported: None, - is_type_only: false, - span: DUMMY_SP, - })], - }, - ))); + body.push(ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportNamed( + ast::NamedExport { + src: Some(Box::new(ast::Str { + span: DUMMY_SP, + value: core_module.clone(), + raw: None, + })), + span: DUMMY_SP, + asserts: None, + type_only: false, + specifiers: vec![ast::ExportSpecifier::Named(ast::ExportNamedSpecifier { + orig: ast::ModuleExportName::Ident(ast::Ident::new(HANDLE_WATCH.clone(), DUMMY_SP)), + exported: None, + is_type_only: false, + span: DUMMY_SP, + })], + }, + ))); } pub fn create_synthetic_named_export(local: &Id, exported: Option) -> ast::ModuleItem { - ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportNamed(ast::NamedExport { - span: DUMMY_SP, - type_only: false, - asserts: None, - specifiers: vec![ast::ExportSpecifier::Named(ast::ExportNamedSpecifier { - span: DUMMY_SP, - is_type_only: false, - orig: ast::ModuleExportName::Ident(new_ident_from_id(local)), - exported: exported - .map(|name| ast::ModuleExportName::Ident(ast::Ident::new(name, DUMMY_SP))), - })], - src: None, - })) + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::ExportNamed(ast::NamedExport { + span: DUMMY_SP, + type_only: false, + asserts: None, + specifiers: vec![ast::ExportSpecifier::Named(ast::ExportNamedSpecifier { + span: DUMMY_SP, + is_type_only: false, + orig: ast::ModuleExportName::Ident(new_ident_from_id(local)), + exported: exported + .map(|name| ast::ModuleExportName::Ident(ast::Ident::new(name, DUMMY_SP))), + })], + src: None, + })) } pub fn create_synthetic_named_import(local: &Id, src: &JsWord) -> ast::ModuleItem { - ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import(ast::ImportDecl { - span: DUMMY_SP, - src: Box::new(ast::Str { - span: DUMMY_SP, - value: src.clone(), - raw: None, - }), - asserts: None, - type_only: false, - specifiers: vec![ast::ImportSpecifier::Named(ast::ImportNamedSpecifier { - is_type_only: false, - span: DUMMY_SP, - local: new_ident_from_id(local), - imported: None, - })], - })) + ast::ModuleItem::ModuleDecl(ast::ModuleDecl::Import(ast::ImportDecl { + span: DUMMY_SP, + src: Box::new(ast::Str { + span: DUMMY_SP, + value: src.clone(), + raw: None, + }), + asserts: None, + type_only: false, + specifiers: vec![ast::ImportSpecifier::Named(ast::ImportNamedSpecifier { + is_type_only: false, + span: DUMMY_SP, + local: new_ident_from_id(local), + imported: None, + })], + })) } fn escape_sym(str: &str) -> String { - str.chars() - .flat_map(|x| match x { - 'A'..='Z' | 'a'..='z' | '0'..='9' | '_' => Some(x), - '$' => None, - _ => Some('_'), - }) - .collect() + str.chars() + .flat_map(|x| match x { + 'A'..='Z' | 'a'..='z' | '0'..='9' | '_' => Some(x), + '$' => None, + _ => Some('_'), + }) + .collect() } const fn can_capture_scope(expr: &ast::Expr) -> bool { - matches!(expr, &ast::Expr::Fn(_) | &ast::Expr::Arrow(_)) + matches!(expr, &ast::Expr::Fn(_) | &ast::Expr::Arrow(_)) } fn base64(nu: u64) -> String { - base64::engine::general_purpose::URL_SAFE_NO_PAD - .encode(nu.to_le_bytes()) - .replace(['-', '_'], "0") + base64::engine::general_purpose::URL_SAFE_NO_PAD + .encode(nu.to_le_bytes()) + .replace(['-', '_'], "0") } fn compute_scoped_idents(all_idents: &[Id], all_decl: &[IdPlusType]) -> (Vec, bool) { - let mut set: HashSet = HashSet::new(); - let mut immutable = true; - for ident in all_idents { - if let Some(item) = all_decl.iter().find(|item| item.0 == *ident) { - set.insert(ident.clone()); - if !matches!(item.1, IdentType::Var(true)) { - immutable = false; - } - } - } - let mut output: Vec = set.into_iter().collect(); - output.sort(); - (output, immutable) + let mut set: HashSet = HashSet::new(); + let mut immutable = true; + for ident in all_idents { + if let Some(item) = all_decl.iter().find(|item| item.0 == *ident) { + set.insert(ident.clone()); + if !matches!(item.1, IdentType::Var(true)) { + immutable = false; + } + } + } + let mut output: Vec = set.into_iter().collect(); + output.sort(); + (output, immutable) } fn get_canonical_filename(symbol_name: &JsWord) -> JsWord { - JsWord::from(symbol_name.as_ref().to_ascii_lowercase()) + JsWord::from(symbol_name.as_ref().to_ascii_lowercase()) } fn parse_symbol_name(symbol_name: JsWord, dev: bool) -> (JsWord, JsWord, JsWord) { - let mut splitter = symbol_name.rsplitn(2, '_'); - let hash = splitter - .next() - .expect("symbol_name always need to have a segment"); - let display_name = splitter.next().unwrap_or(hash); - - let s_n = if dev { - symbol_name.clone() - } else { - JsWord::from(format!("s_{}", hash)) - }; - (s_n, display_name.into(), hash.into()) + let mut splitter = symbol_name.rsplitn(2, '_'); + let hash = splitter + .next() + .expect("symbol_name always need to have a segment"); + let display_name = splitter.next().unwrap_or(hash); + + let s_n = if dev { + symbol_name.clone() + } else { + JsWord::from(format!("s_{}", hash)) + }; + (s_n, display_name.into(), hash.into()) } fn get_qrl_dev_obj(asb_path: &Path, hook: &HookData, span: &Span) -> ast::Expr { - ast::Expr::Object(ast::ObjectLit { - span: DUMMY_SP, - props: vec![ - ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Ident(ast::Ident::new(js_word!("file"), DUMMY_SP)), - value: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - value: asb_path.to_str().unwrap().into(), - raw: None, - }))), - }))), - ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Ident(ast::Ident::new(JsWord::from("lo"), DUMMY_SP)), - value: Box::new(ast::Expr::Lit(ast::Lit::Num(ast::Number { - span: DUMMY_SP, - value: span.lo().0 as f64, - raw: None, - }))), - }))), - ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Ident(ast::Ident::new(JsWord::from("hi"), DUMMY_SP)), - value: Box::new(ast::Expr::Lit(ast::Lit::Num(ast::Number { - span: DUMMY_SP, - value: span.hi().0 as f64, - raw: None, - }))), - }))), - ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { - key: ast::PropName::Ident(ast::Ident::new(JsWord::from("displayName"), DUMMY_SP)), - value: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { - span: DUMMY_SP, - value: hook.display_name.clone(), - raw: None, - }))), - }))), - ], - }) + ast::Expr::Object(ast::ObjectLit { + span: DUMMY_SP, + props: vec![ + ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Ident(ast::Ident::new(js_word!("file"), DUMMY_SP)), + value: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: asb_path.to_str().unwrap().into(), + raw: None, + }))), + }))), + ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Ident(ast::Ident::new(JsWord::from("lo"), DUMMY_SP)), + value: Box::new(ast::Expr::Lit(ast::Lit::Num(ast::Number { + span: DUMMY_SP, + value: span.lo().0 as f64, + raw: None, + }))), + }))), + ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Ident(ast::Ident::new(JsWord::from("hi"), DUMMY_SP)), + value: Box::new(ast::Expr::Lit(ast::Lit::Num(ast::Number { + span: DUMMY_SP, + value: span.hi().0 as f64, + raw: None, + }))), + }))), + ast::PropOrSpread::Prop(Box::new(ast::Prop::KeyValue(ast::KeyValueProp { + key: ast::PropName::Ident(ast::Ident::new(JsWord::from("displayName"), DUMMY_SP)), + value: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: hook.display_name.clone(), + raw: None, + }))), + }))), + ], + }) } fn prop_to_string(prop: &ast::MemberProp) -> Option { - match prop { - ast::MemberProp::Ident(ident) => Some(ident.sym.clone()), - ast::MemberProp::Computed(ast::ComputedPropName { - expr: box ast::Expr::Lit(ast::Lit::Str(str)), - .. - }) => Some(str.value.clone()), - _ => None, - } + match prop { + ast::MemberProp::Ident(ident) => Some(ident.sym.clone()), + ast::MemberProp::Computed(ast::ComputedPropName { + expr: box ast::Expr::Lit(ast::Lit::Str(str)), + .. + }) => Some(str.value.clone()), + _ => None, + } } fn is_return_static(expr: &Option>) -> bool { - match expr { - Some(box ast::Expr::Call(ast::CallExpr { - callee: ast::Callee::Expr(box ast::Expr::Ident(ident)), - .. - })) => { - ident.sym.ends_with('$') || ident.sym.ends_with("Qrl") || ident.sym.starts_with("use") - } - Some(_) => false, - None => true, - } + match expr { + Some(box ast::Expr::Call(ast::CallExpr { + callee: ast::Callee::Expr(box ast::Expr::Ident(ident)), + .. + })) => ident.sym.ends_with('$') || ident.sym.ends_with("Qrl") || ident.sym.starts_with("use"), + Some(_) => false, + None => true, + } } fn make_wrap(method: &Id, obj: Box, prop: JsWord) -> ast::Expr { - ast::Expr::Call(ast::CallExpr { - callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(method)))), - args: vec![ - ast::ExprOrSpread::from(obj), - ast::ExprOrSpread::from(Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str::from( - prop, - ))))), - ], - span: DUMMY_SP, - type_args: None, - }) + ast::Expr::Call(ast::CallExpr { + callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(new_ident_from_id(method)))), + args: vec![ + ast::ExprOrSpread::from(obj), + ast::ExprOrSpread::from(Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str::from( + prop, + ))))), + ], + span: DUMMY_SP, + type_args: None, + }) } fn get_null_arg() -> ast::ExprOrSpread { - ast::ExprOrSpread { - spread: None, - expr: Box::new(ast::Expr::Lit(ast::Lit::Null(ast::Null { span: DUMMY_SP }))), - } + ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Lit(ast::Lit::Null(ast::Null { span: DUMMY_SP }))), + } } fn is_text_only(node: &str) -> bool { - matches!( - node, - "text" | "textarea" | "title" | "option" | "script" | "style" | "noscript" - ) + matches!( + node, + "text" | "textarea" | "title" | "option" | "script" | "style" | "noscript" + ) } diff --git a/packages/qwik/src/optimizer/core/src/utils.rs b/packages/qwik/src/optimizer/core/src/utils.rs index cd8d96542c9..4fe9f1b3807 100644 --- a/packages/qwik/src/optimizer/core/src/utils.rs +++ b/packages/qwik/src/optimizer/core/src/utils.rs @@ -5,74 +5,74 @@ use swc_atoms::JsWord; #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SourceLocation { - lo: usize, - hi: usize, - start_line: usize, - start_col: usize, - end_line: usize, - end_col: usize, + lo: usize, + hi: usize, + start_line: usize, + start_col: usize, + end_line: usize, + end_col: usize, } impl SourceLocation { - pub fn from(source_map: &swc_common::SourceMap, span: swc_common::Span) -> Self { - let start = source_map.lookup_char_pos(span.lo); - let end = source_map.lookup_char_pos(span.hi); - // - SWC's columns are exclusive, ours are inclusive (column - 1) - // - SWC has 0-based columns, ours are 1-based (column + 1) - // = +-0 + pub fn from(source_map: &swc_common::SourceMap, span: swc_common::Span) -> Self { + let start = source_map.lookup_char_pos(span.lo); + let end = source_map.lookup_char_pos(span.hi); + // - SWC's columns are exclusive, ours are inclusive (column - 1) + // - SWC has 0-based columns, ours are 1-based (column + 1) + // = +-0 - Self { - lo: span.lo.0 as usize, - hi: span.hi.0 as usize, - start_line: start.line, - start_col: start.col_display + 1, - end_line: end.line, - end_col: end.col_display, - } - } + Self { + lo: span.lo.0 as usize, + hi: span.hi.0 as usize, + start_line: start.line, + start_col: start.col_display + 1, + end_line: end.line, + end_col: end.col_display, + } + } } impl PartialOrd for SourceLocation { - fn partial_cmp(&self, other: &Self) -> Option { - match self.start_line.cmp(&other.start_line) { - Ordering::Equal => self.start_col.partial_cmp(&other.start_col), - o => Some(o), - } - } + fn partial_cmp(&self, other: &Self) -> Option { + match self.start_line.cmp(&other.start_line) { + Ordering::Equal => self.start_col.partial_cmp(&other.start_col), + o => Some(o), + } + } } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Diagnostic { - pub category: DiagnosticCategory, - pub code: Option, - pub file: JsWord, - pub message: String, - pub highlights: Option>, - pub suggestions: Option>, - pub scope: DiagnosticScope, + pub category: DiagnosticCategory, + pub code: Option, + pub file: JsWord, + pub message: String, + pub highlights: Option>, + pub suggestions: Option>, + pub scope: DiagnosticScope, } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub enum DiagnosticCategory { - /// Fails the build with an error. - Error, - /// Logs a warning, but the build does not fail. - Warning, - /// An error if this is source code in the project, or a warning if in node_modules. - SourceError, + /// Fails the build with an error. + Error, + /// Logs a warning, but the build does not fail. + Warning, + /// An error if this is source code in the project, or a warning if in node_modules. + SourceError, } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub enum DiagnosticScope { - Optimizer, + Optimizer, } #[derive(Serialize, Debug, Deserialize, Eq, PartialEq, Clone, Copy)] #[serde(rename_all = "camelCase")] pub enum SourceType { - Script, - Module, + Script, + Module, } diff --git a/packages/qwik/src/optimizer/core/src/words.rs b/packages/qwik/src/optimizer/core/src/words.rs index 96b581be17d..7107de140cc 100644 --- a/packages/qwik/src/optimizer/core/src/words.rs +++ b/packages/qwik/src/optimizer/core/src/words.rs @@ -5,44 +5,44 @@ pub const SIGNAL: char = '$'; pub const LONG_SUFFIX: &str = "Qrl"; lazy_static! { - pub static ref REF: JsWord = JsWord::from("ref"); - pub static ref QSLOT: JsWord = JsWord::from("q:slot"); - pub static ref CHILDREN: JsWord = JsWord::from("children"); - pub static ref HANDLE_WATCH: JsWord = JsWord::from("_hW"); - pub static ref _QRL: JsWord = JsWord::from("qrl"); - pub static ref _QRL_DEV: JsWord = JsWord::from("qrlDEV"); - pub static ref _INLINED_QRL: JsWord = JsWord::from("inlinedQrl"); - pub static ref _INLINED_QRL_DEV: JsWord = JsWord::from("inlinedQrlDEV"); - pub static ref _NOOP_QRL: JsWord = JsWord::from("_noopQrl"); - pub static ref _REST_PROPS: JsWord = JsWord::from("_restProps"); - pub static ref QHOOK: JsWord = JsWord::from("$"); - pub static ref Q_SYNC: JsWord = JsWord::from("sync$"); - 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_BUILD: JsWord = JsWord::from("@builder.io/qwik/build"); - pub static ref BUILDER_IO_QWIK_JSX: JsWord = JsWord::from("@builder.io/qwik/jsx-runtime"); - pub static ref BUILDER_IO_QWIK_JSX_DEV: JsWord = - JsWord::from("@builder.io/qwik/jsx-dev-runtime"); - pub static ref QCOMPONENT: JsWord = JsWord::from("component$"); - 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"); - pub static ref _IMMUTABLE: JsWord = JsWord::from("_IMMUTABLE"); - pub static ref _INLINED_FN: JsWord = JsWord::from("_fnSignal"); - pub static ref IS_SERVER: JsWord = JsWord::from("isServer"); - pub static ref IS_BROWSER: JsWord = JsWord::from("isBrowser"); - pub static ref IS_DEV: JsWord = JsWord::from("isDev"); - pub static ref COMPONENT: JsWord = JsWord::from("component$"); - pub static ref _REG_SYMBOL: JsWord = JsWord::from("_regSymbol"); - pub static ref _JSX_BRANCH: JsWord = JsWord::from("_jsxBranch"); - pub static ref _QRL_SYNC: JsWord = JsWord::from("_qrlSync"); - pub static ref _WRAP_PROP: JsWord = JsWord::from("_wrapProp"); - pub static ref _WRAP_SIGNAL: JsWord = JsWord::from("_wrapSignal"); - pub static ref _JSX_Q: JsWord = JsWord::from("_jsxQ"); - pub static ref _JSX_S: JsWord = JsWord::from("_jsxS"); - pub static ref _JSX_C: JsWord = JsWord::from("_jsxC"); - pub static ref JSX: JsWord = JsWord::from("jsx"); - pub static ref JSXS: JsWord = JsWord::from("jsxs"); - pub static ref JSX_DEV: JsWord = JsWord::from("jsxDEV"); + pub static ref REF: JsWord = JsWord::from("ref"); + pub static ref QSLOT: JsWord = JsWord::from("q:slot"); + pub static ref CHILDREN: JsWord = JsWord::from("children"); + pub static ref HANDLE_WATCH: JsWord = JsWord::from("_hW"); + pub static ref _QRL: JsWord = JsWord::from("qrl"); + pub static ref _QRL_DEV: JsWord = JsWord::from("qrlDEV"); + pub static ref _INLINED_QRL: JsWord = JsWord::from("inlinedQrl"); + pub static ref _INLINED_QRL_DEV: JsWord = JsWord::from("inlinedQrlDEV"); + pub static ref _NOOP_QRL: JsWord = JsWord::from("_noopQrl"); + pub static ref _REST_PROPS: JsWord = JsWord::from("_restProps"); + pub static ref QHOOK: JsWord = JsWord::from("$"); + pub static ref Q_SYNC: JsWord = JsWord::from("sync$"); + 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_BUILD: JsWord = JsWord::from("@builder.io/qwik/build"); + pub static ref BUILDER_IO_QWIK_JSX: JsWord = JsWord::from("@builder.io/qwik/jsx-runtime"); + pub static ref BUILDER_IO_QWIK_JSX_DEV: JsWord = + JsWord::from("@builder.io/qwik/jsx-dev-runtime"); + pub static ref QCOMPONENT: JsWord = JsWord::from("component$"); + 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"); + pub static ref _IMMUTABLE: JsWord = JsWord::from("_IMMUTABLE"); + pub static ref _INLINED_FN: JsWord = JsWord::from("_fnSignal"); + pub static ref IS_SERVER: JsWord = JsWord::from("isServer"); + pub static ref IS_BROWSER: JsWord = JsWord::from("isBrowser"); + pub static ref IS_DEV: JsWord = JsWord::from("isDev"); + pub static ref COMPONENT: JsWord = JsWord::from("component$"); + pub static ref _REG_SYMBOL: JsWord = JsWord::from("_regSymbol"); + pub static ref _JSX_BRANCH: JsWord = JsWord::from("_jsxBranch"); + pub static ref _QRL_SYNC: JsWord = JsWord::from("_qrlSync"); + pub static ref _WRAP_PROP: JsWord = JsWord::from("_wrapProp"); + pub static ref _WRAP_SIGNAL: JsWord = JsWord::from("_wrapSignal"); + pub static ref _JSX_Q: JsWord = JsWord::from("_jsxQ"); + pub static ref _JSX_S: JsWord = JsWord::from("_jsxS"); + pub static ref _JSX_C: JsWord = JsWord::from("_jsxC"); + pub static ref JSX: JsWord = JsWord::from("jsx"); + pub static ref JSXS: JsWord = JsWord::from("jsxs"); + pub static ref JSX_DEV: JsWord = JsWord::from("jsxDEV"); } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000000..218e203215e --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/starters/adapters/cloudflare-pages/.node-version b/starters/adapters/cloudflare-pages/.node-version deleted file mode 100644 index b6a7d89c68e..00000000000 --- a/starters/adapters/cloudflare-pages/.node-version +++ /dev/null @@ -1 +0,0 @@ -16