diff --git a/optimizer/src/component/component.rs b/optimizer/src/component/component.rs index d9ebebf..9690335 100644 --- a/optimizer/src/component/component.rs +++ b/optimizer/src/component/component.rs @@ -1,8 +1,7 @@ -use crate::component::Language; use crate::component::*; use crate::segment::Segment; use crate::transform::TransformOptions; -use crate::transpiler::Transpiler; +use crate::{component::Language, import_clean_up::ImportCleanUp}; use oxc_allocator::{Allocator, Box as OxcBox, CloneIn, IntoIn, Vec as OxcVec}; use oxc_ast::ast::*; use oxc_ast::*; @@ -118,9 +117,7 @@ impl QrlComponent { body, ); - if options.transpile_jsx { - Transpiler::transpile(allocator, &mut new_pgm, source_info); - } + ImportCleanUp::clean_up(&mut new_pgm, allocator); let codegen = Codegen::new(); let codegen_options = CodegenOptions { diff --git a/optimizer/src/component/shared.rs b/optimizer/src/component/shared.rs index ab8540c..6b26c0a 100644 --- a/optimizer/src/component/shared.rs +++ b/optimizer/src/component/shared.rs @@ -9,12 +9,14 @@ use std::convert::Into; use std::path::PathBuf; pub const QWIK_CORE_SOURCE: &str = "@qwik.dev/core"; +pub const JSX_SORTED_NAME: &str = "_jsxSorted"; +pub const JSX_SPLIT_NAME: &str = "_jsxSplit"; pub const MARKER_SUFFIX: &str = "$"; pub const QRL: &str = "qrl"; pub const QRL_SUFFIX: &str = "Qrl"; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) enum ImportId { +pub enum ImportId { Named(String), NamedWithAlias(String, String), Default(String), diff --git a/optimizer/src/import_clean_up.rs b/optimizer/src/import_clean_up.rs index 0e60d74..d369c86 100644 --- a/optimizer/src/import_clean_up.rs +++ b/optimizer/src/import_clean_up.rs @@ -1,33 +1,44 @@ -use crate::component::{Import, QWIK_CORE_SOURCE}; +use crate::component::{Import, ImportId, QWIK_CORE_SOURCE}; use oxc_allocator::Allocator; use oxc_ast::ast::{ImportDeclaration, ImportOrExportKind, Program, Statement}; use oxc_semantic::{SemanticBuilder, SemanticBuilderReturn}; use oxc_traverse::{traverse_mut, Traverse, TraverseCtx}; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; /// This struct is used to clean up unused imports in the AST. -pub(crate) struct ImportCleanUp; +pub(crate) struct ImportCleanUp<'a> { + imports: BTreeMap<&'a str, BTreeSet>, +} -impl ImportCleanUp { +impl ImportCleanUp<'_> { pub fn new() -> Self { - ImportCleanUp + ImportCleanUp { + imports: BTreeMap::new(), + } } pub fn clean_up<'a>(program: &mut Program<'a>, allocator: &'a Allocator) { let SemanticBuilderReturn { semantic, errors: semantic_errors, - } = SemanticBuilder::new() - .with_check_syntax_error(true) // Enable extra syntax error checking - .with_build_jsdoc(true) // Enable JSDoc parsing - .with_cfg(true) // Build a Control Flow Graph - .build(program); + } = SemanticBuilder::new().build(program); let scoping = semantic.into_scoping(); - let transform = &mut ImportCleanUp::new(); + let mut transform = ImportCleanUp::new(); + + traverse_mut(&mut transform, allocator, program, scoping); - traverse_mut(transform, allocator, program, scoping); + transform + .imports + .into_iter() + .rev() + .for_each(|(module, names)| { + program.body.insert( + 0, + Import::new(names.into_iter().collect(), module).into_statement(allocator), + ); + }) } /// This function renames the Qwik imports to the new qwik.dev imports. @@ -64,7 +75,7 @@ struct Key(String); impl From<&ImportDeclaration<'_>> for Key { fn from(import: &ImportDeclaration) -> Self { let mut key = String::new(); - for specifiers in &import.specifiers { + if let Some(specifiers) = &import.specifiers { for specifier in specifiers { let local = specifier.local(); let local_name = local.name; @@ -88,35 +99,34 @@ impl From<&ImportDeclaration<'_>> for Key { } } -impl<'a> Traverse<'a> for ImportCleanUp { - fn enter_statements( +impl<'a> Traverse<'a> for ImportCleanUp<'a> { + fn exit_statements( &mut self, node: &mut oxc_allocator::Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>, ) { - let mut imports: BTreeSet = BTreeSet::new(); - node.retain_mut(|node| match node { Statement::ImportDeclaration(import) => { let source = import.source.clone(); - let specifiers = &mut import.specifiers; - if let Some(specifiers) = specifiers { + if let Some(specifiers) = &import.specifiers { for specifier in specifiers { if ctx.scoping().symbol_is_used(specifier.local().symbol_id()) { - imports.insert(Import::from_import_declaration_specifier( - specifier, &source, - )); + if let Some(existing_set) = self.imports.get_mut(source.value.into()) { + existing_set.insert(specifier.into()); + } else { + let mut set: BTreeSet = BTreeSet::new(); + set.insert(specifier.into()); + self.imports.insert(source.value.into(), set); + } } } + false + } else { + true } - false } _ => true, }); - - imports.iter().for_each(|import| { - node.insert(0, import.into_statement(ctx.ast.allocator)); - }) } } @@ -153,6 +163,31 @@ mod tests { assert_eq!(lines[1], r#"b.foo();"#); } + #[test] + fn test_merge_imports() { + let allocator = Allocator::new(); + let source = r#" + import { a as A } from '@qwik.dev/core'; + import { b } from '@qwik.dev/core'; + import { c } from '@qwik.dev/router'; + + A.foo(b, c); + "#; + + let parse_return = Parser::new(&allocator, source, SourceType::tsx()).parse(); + let mut program = parse_return.program; + ImportCleanUp::clean_up(&mut program, &allocator); + + let codegen = Codegen::default(); + let raw = codegen.build(&program).code; + let lines: Vec<&str> = raw.lines().collect(); + assert_eq!(program.body.len(), 3); + assert_eq!(lines.len(), 3); + assert_eq!(lines[0], r#"import { b, a as A } from "@qwik.dev/core";"#); + assert_eq!(lines[1], r#"import { c } from "@qwik.dev/router";"#); + assert_eq!(lines[2], r#"A.foo(b, c);"#); + } + #[test] fn test_rename_qwik_imports() { let source = "@builder.io/qwik-city/foo"; diff --git a/optimizer/src/js_lib_interface.rs b/optimizer/src/js_lib_interface.rs index 00e2898..ef52a4d 100644 --- a/optimizer/src/js_lib_interface.rs +++ b/optimizer/src/js_lib_interface.rs @@ -246,13 +246,18 @@ pub fn transform_modules(config: TransformModulesOptions) -> Result true, MinifyMode::None => false, }, target: config.mode, + transpile_ts: config.transpile_ts, transpile_jsx: config.transpile_jsx, }, )?; diff --git a/optimizer/src/lib.rs b/optimizer/src/lib.rs index 8fbcf04..324c955 100644 --- a/optimizer/src/lib.rs +++ b/optimizer/src/lib.rs @@ -17,4 +17,3 @@ mod processing_failure; mod ref_counter; mod segment; pub mod transform; -mod transpiler; diff --git a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap index 9c0de31..c1d0f39 100644 --- a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap +++ b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap @@ -1,95 +1,75 @@ --- -source: src/js_lib_interface.rs +source: optimizer/src/js_lib_interface.rs expression: result --- modules: - - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\nexport const component_3DYNZg0ikgU = () => {\n\treturn /* @__PURE__ */ _jsxs(_Fragment, { children: [/* @__PURE__ */ _jsx(\"h1\", { children: \"Hi 👋\" }), /* @__PURE__ */ _jsxs(\"div\", { children: [\n\t\t\"Can't wait to see what you build with qwik!\",\n\t\t/* @__PURE__ */ _jsx(\"br\", {}),\n\t\t\"Happy coding.\"\n\t] })] });\n};\n" - map: ~ - segment: - origin: "./src/test_input/test_project_1/src/routes/index.tsx" - name: component_3DYNZg0ikgU - entry: ~ - displayName: index.tsx.tsx_component - hash: 3DYNZg0ikgU - canonicalFilename: "././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU" - path: "./src/test_input/test_project_1/src/routes/index.tsx" - extension: tsx - parent: ~ - ctxKind: function - ctxName: component_3DYNZg0ikgU - captures: false - loc: - - 0 - - 0 - isEntry: true - path: "./src/test_input/test_project_1/src/entry.preview.tsx" code: "import render from \"./entry.ssr\";\nimport qwikCityPlan from \"@qwik-city-plan\";\nimport { createQwikCity } from \"@qwik.dev/router/middleware/node\";\n/**\n* The default export is the QwikCity adapter used by Vite preview.\n*/\nexport default createQwikCity({\n\trender,\n\tqwikCityPlan\n});\n" map: ~ segment: ~ isEntry: false - - path: "./src/test_input/test_project_1/src/root.tsx" - code: "import { QwikCityProvider } from \"@qwik.dev/router\";\nimport { RouterHead } from \"./components/router-head/router-head\";\nimport { RouterOutlet } from \"@qwik.dev/router\";\nimport { ServiceWorkerRegister } from \"@qwik.dev/router\";\nimport { isDev } from \"@qwik.dev/core\";\nimport { jsx as _jsx, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const component_cQDlEL2LM0Q = () => {\n\treturn /* @__PURE__ */ _jsxs(QwikCityProvider, { children: [/* @__PURE__ */ _jsxs(\"head\", { children: [\n\t\t/* @__PURE__ */ _jsx(\"meta\", { charset: \"utf-8\" }),\n\t\t!isDev && /* @__PURE__ */ _jsx(\"link\", {\n\t\t\trel: \"manifest\",\n\t\t\thref: `${import.meta.env.BASE_URL}manifest.json`\n\t\t}),\n\t\t/* @__PURE__ */ _jsx(RouterHead, {})\n\t] }), /* @__PURE__ */ _jsxs(\"body\", {\n\t\tlang: \"en\",\n\t\tchildren: [/* @__PURE__ */ _jsx(RouterOutlet, {}), !isDev && /* @__PURE__ */ _jsx(ServiceWorkerRegister, {})]\n\t})] });\n};\n" + - path: "./src/test_input/test_project_1/src/routes/layout.tsx" + code: "import { Slot, _jsxSorted } from \"@qwik.dev/core\";\nexport const component_e0ZOSHqXHEo = () => {\n\treturn _jsxSorted(Slot, {}, {}, [], 1, null);\n};\n" map: ~ segment: - origin: "./src/test_input/test_project_1/src/root.tsx" - name: component_cQDlEL2LM0Q + origin: "./src/test_input/test_project_1/src/routes/layout.tsx" + name: component_e0ZOSHqXHEo entry: ~ - displayName: root.tsx.tsx_component - hash: cQDlEL2LM0Q - canonicalFilename: "././src/test_input/test_project_1/src/root.tsx.tsx_component_cQDlEL2LM0Q" - path: "./src/test_input/test_project_1/src/root.tsx" + displayName: layout.tsx_component + hash: e0ZOSHqXHEo + canonicalFilename: "././src/test_input/test_project_1/src/routes/layout.tsx_component_e0ZOSHqXHEo" + path: "./src/test_input/test_project_1/src/routes/layout.tsx" extension: tsx parent: ~ ctxKind: function - ctxName: component_cQDlEL2LM0Q + ctxName: component_e0ZOSHqXHEo captures: false loc: - 0 - 0 isEntry: true - path: "./src/test_input/test_project_1/src/entry.ssr.tsx" - code: "import Root from \"./root\";\nimport { renderToStream } from \"@qwik.dev/core/server\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default function(opts) {\n\treturn renderToStream(/* @__PURE__ */ _jsx(Root, {}), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" + code: "import Root from \"./root\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { renderToStream } from \"@qwik.dev/core/server\";\nexport default function(opts) {\n\treturn renderToStream(_jsxSorted(Root, {}, {}, [], 1, null), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/root.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/root.tsx.tsx_component_cQDlEL2LM0Q\"), \"component_cQDlEL2LM0Q\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nimport \"./global.css\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/root.tsx_component_fZ4L0pYApnM\"), \"component_fZ4L0pYApnM\"));\n" map: ~ segment: ~ isEntry: false - - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { Slot } from \"@qwik.dev/core\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport const component_a1SxCR8N64g = () => {\n\treturn /* @__PURE__ */ _jsx(Slot, {});\n};\n" + - path: "./src/test_input/test_project_1/src/root.tsx" + code: "import { RouterHead } from \"./components/router-head/router-head\";\nimport { _jsxSorted, isDev } from \"@qwik.dev/core\";\nimport { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from \"@qwik.dev/router\";\nexport const component_fZ4L0pYApnM = () => {\n\treturn _jsxSorted(QwikCityProvider, {}, {}, [/* @__PURE__ */ _jsxSorted(\"head\", {}, {}, [\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, { charset: \"utf-8\" }, [], 1, null),\n\t\t!isDev && /* @__PURE__ */ _jsxSorted(\"link\", { href: `${import.meta.env.BASE_URL}manifest.json` }, { rel: \"manifest\" }, [], 1, null),\n\t\t_jsxSorted(RouterHead, {}, {}, [], 1, null)\n\t], 1, null), /* @__PURE__ */ _jsxSorted(\"body\", {}, { lang: \"en\" }, [_jsxSorted(RouterOutlet, {}, {}, [], 1, null), !isDev && _jsxSorted(ServiceWorkerRegister, {}, {}, [], 1, null)], 1, null)], 1, null);\n};\n" map: ~ segment: - origin: "./src/test_input/test_project_1/src/routes/layout.tsx" - name: component_a1SxCR8N64g + origin: "./src/test_input/test_project_1/src/root.tsx" + name: component_fZ4L0pYApnM entry: ~ - displayName: layout.tsx.tsx_component - hash: a1SxCR8N64g - canonicalFilename: "././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g" - path: "./src/test_input/test_project_1/src/routes/layout.tsx" + displayName: root.tsx_component + hash: fZ4L0pYApnM + canonicalFilename: "././src/test_input/test_project_1/src/root.tsx_component_fZ4L0pYApnM" + path: "./src/test_input/test_project_1/src/root.tsx" extension: tsx parent: ~ ctxKind: function - ctxName: component_a1SxCR8N64g + ctxName: component_fZ4L0pYApnM captures: false loc: - 0 - 0 isEntry: true - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\nexport const head = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx_component_4HLI2RMDcP8\"), \"component_4HLI2RMDcP8\"));\nexport const head = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx_component_4HLI2RMDcP8\"), \"component_4HLI2RMDcP8\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const onGet = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g\"), \"component_a1SxCR8N64g\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const onGet = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx_component_e0ZOSHqXHEo\"), \"component_e0ZOSHqXHEo\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/entry.dev.tsx" - code: "import Root from \"./root\";\nimport { render } from \"@qwik.dev/core\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default function(opts) {\n\treturn render(document, /* @__PURE__ */ _jsx(Root, {}), opts);\n}\n" + code: "import Root from \"./root\";\nimport { _jsxSorted, render } from \"@qwik.dev/core\";\nexport default function(opts) {\n\treturn render(document, _jsxSorted(Root, {}, {}, [], 1, null), opts);\n}\n" map: ~ segment: ~ isEntry: false @@ -99,25 +79,45 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\n/**\n* The RouterHead component is placed inside of the document `` element.\n*/\nexport const RouterHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/components/router-head/router-head.tsx.tsx_RouterHead_component_9vp071E6SfU\"), \"RouterHead_component_9vp071E6SfU\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\n/**\n* The RouterHead component is placed inside of the document `` element.\n*/\nexport const RouterHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/components/router-head/router-head.tsx_RouterHead_component_VtXR96RQWfE\"), \"RouterHead_component_VtXR96RQWfE\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - code: "import { useDocumentHead } from \"@qwik.dev/router\";\nimport { useLocation } from \"@qwik.dev/router\";\nimport { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const RouterHead_component_9vp071E6SfU = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn /* @__PURE__ */ _jsxs(_Fragment, { children: [\n\t\t/* @__PURE__ */ _jsx(\"title\", { children: head.title }),\n\t\t/* @__PURE__ */ _jsx(\"link\", {\n\t\t\trel: \"canonical\",\n\t\t\thref: loc.url.href\n\t\t}),\n\t\t/* @__PURE__ */ _jsx(\"meta\", {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}),\n\t\t/* @__PURE__ */ _jsx(\"link\", {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}),\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsx(\"meta\", { ...m }, m.key)),\n\t\thead.links.map((l) => /* @__PURE__ */ _jsx(\"link\", { ...l }, l.key)),\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsx(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, s.key)),\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsx(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, s.key))\n\t] });\n};\n" + code: "import { _jsxSorted, _jsxSplit } from \"@qwik.dev/core\";\nimport { useDocumentHead, useLocation } from \"@qwik.dev/router\";\nexport const RouterHead_component_VtXR96RQWfE = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn [\n\t\t/* @__PURE__ */ _jsxSorted(\"title\", {}, {}, [head.title], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", { href: loc.url.href }, { rel: \"canonical\" }, [], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}, [], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", {}, {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}, [], 1, null),\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsxSplit(\"meta\", { ...m }, {}, [], 0, m.key)),\n\t\thead.links.map((l) => /* @__PURE__ */ _jsxSplit(\"link\", { ...l }, {}, [], 0, l.key)),\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsxSplit(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, {}, [], 0, s.key)),\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsxSplit(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, {}, [], 0, s.key))\n\t];\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - name: RouterHead_component_9vp071E6SfU + name: RouterHead_component_VtXR96RQWfE entry: ~ - displayName: router-head.tsx.tsx_RouterHead_component - hash: 9vp071E6SfU - canonicalFilename: "././src/test_input/test_project_1/src/components/router-head/router-head.tsx.tsx_RouterHead_component_9vp071E6SfU" + displayName: router-head.tsx_RouterHead_component + hash: VtXR96RQWfE + canonicalFilename: "././src/test_input/test_project_1/src/components/router-head/router-head.tsx_RouterHead_component_VtXR96RQWfE" path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" extension: tsx parent: ~ ctxKind: function - ctxName: RouterHead_component_9vp071E6SfU + ctxName: RouterHead_component_VtXR96RQWfE + captures: false + loc: + - 0 + - 0 + isEntry: true + - path: "./src/test_input/test_project_1/src/routes/index.tsx" + code: "import { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_4HLI2RMDcP8 = () => {\n\treturn [/* @__PURE__ */ _jsxSorted(\"h1\", {}, {}, [\"Hi 👋\"], 1, null), /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"Can't wait to see what you build with qwik!\",\n\t\t/* @__PURE__ */ _jsxSorted(\"br\", {}, {}, [], 1, null),\n\t\t\"Happy coding.\"\n\t], 1, null)];\n};\n" + map: ~ + segment: + origin: "./src/test_input/test_project_1/src/routes/index.tsx" + name: component_4HLI2RMDcP8 + entry: ~ + displayName: index.tsx_component + hash: 4HLI2RMDcP8 + canonicalFilename: "././src/test_input/test_project_1/src/routes/index.tsx_component_4HLI2RMDcP8" + path: "./src/test_input/test_project_1/src/routes/index.tsx" + extension: tsx + parent: ~ + ctxKind: function + ctxName: component_4HLI2RMDcP8 captures: false loc: - 0 diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_App_component_ckEPmXZlub0.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_App_component_ckEPmXZlub0.snap index f7e2d3e..51603c5 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_App_component_ckEPmXZlub0.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_App_component_ckEPmXZlub0.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: comp.code -snapshot_kind: text --- -"import { Header } from \"./test\";\nimport { foo } from \"../state\";\nexport const App_component_ckEPmXZlub0 = () => {\n\treturn
{foo()}
;\n};\n" +"import { foo } from \"../state\";\nimport { Header } from \"./test\";\nexport const App_component_ckEPmXZlub0 = () => {\n\treturn
{foo()}
;\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_Header_component_J4uyIhaBNR4.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_Header_component_J4uyIhaBNR4.snap index 1f7d2f8..132a47e 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_Header_component_J4uyIhaBNR4.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_Header_component_J4uyIhaBNR4.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: comp.code -snapshot_kind: text --- -"import { Header } from \"./test\";\nimport { qrl } from \"@qwik.dev/core\";\nimport { bar as bbar } from \"../state\";\nimport * as dep2 from \"dep2\";\nexport const Header_component_J4uyIhaBNR4 = () => {\n\treturn
import(\"./test.tsx_Header_component_Header_onClick_oNOlojcAk6Q\"), \"Header_component_Header_onClick_oNOlojcAk6Q\")}>\n {dep2.stuff()}{bbar()}\n
;\n};\n" +"import { bar as bbar } from \"../state\";\nimport { Header } from \"./test\";\nimport { qrl } from \"@qwik.dev/core\";\nimport * as dep2 from \"dep2\";\nexport const Header_component_J4uyIhaBNR4 = () => {\n\treturn
import(\"./test.tsx_Header_component_Header_onClick_oNOlojcAk6Q\"), \"Header_component_Header_onClick_oNOlojcAk6Q\")}>\n {dep2.stuff()}{bbar()}\n
;\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_body.snap index 8fac317..fb57cb6 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\nexport const App = componentQrl(qrl(() => import(\"./test.tsx_App_component_ckEPmXZlub0\"), \"App_component_ckEPmXZlub0\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\nexport const App = componentQrl(qrl(() => import(\"./test.tsx_App_component_ckEPmXZlub0\"), \"App_component_ckEPmXZlub0\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_1_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_1_body.snap index 54c93f2..eeab627 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_1_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_1_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { component } from \"@qwik.dev/core\";\nexport const renderHeader = qrl(() => import(\"./test.tsx_renderHeader_zBbHWn4e8Cg\"), \"renderHeader_zBbHWn4e8Cg\");\nconst renderHeader = component(qrl(() => import(\"./test.tsx_renderHeader_component_U6Kkv07sbpQ\"), \"renderHeader_component_U6Kkv07sbpQ\"));\n" +"import { component, qrl } from \"@qwik.dev/core\";\nexport const renderHeader = qrl(() => import(\"./test.tsx_renderHeader_zBbHWn4e8Cg\"), \"renderHeader_zBbHWn4e8Cg\");\nconst renderHeader = component(qrl(() => import(\"./test.tsx_renderHeader_component_U6Kkv07sbpQ\"), \"renderHeader_component_U6Kkv07sbpQ\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_2_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_2_body.snap index 54c93f2..eeab627 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_2_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_2_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { component } from \"@qwik.dev/core\";\nexport const renderHeader = qrl(() => import(\"./test.tsx_renderHeader_zBbHWn4e8Cg\"), \"renderHeader_zBbHWn4e8Cg\");\nconst renderHeader = component(qrl(() => import(\"./test.tsx_renderHeader_component_U6Kkv07sbpQ\"), \"renderHeader_component_U6Kkv07sbpQ\"));\n" +"import { component, qrl } from \"@qwik.dev/core\";\nexport const renderHeader = qrl(() => import(\"./test.tsx_renderHeader_zBbHWn4e8Cg\"), \"renderHeader_zBbHWn4e8Cg\");\nconst renderHeader = component(qrl(() => import(\"./test.tsx_renderHeader_component_U6Kkv07sbpQ\"), \"renderHeader_component_U6Kkv07sbpQ\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_3_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_3_body.snap index 50f1d52..a7a1077 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_3_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_3_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const App = () => {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n};\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const App = () => {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_4_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_4_body.snap index 46b2941..5eb0eaa 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_4_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_4_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport function App() {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n}\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport function App() {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n}\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_5_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_5_body.snap index 104dc9f..1879937 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_5_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_5_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_7_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_7_body.snap index cf6b85d..6c2d0e5 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_7_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_7_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\ncomponentQrl(qrl(() => import(\"./test.tsx_App_component_ckEPmXZlub0\"), \"App_component_ckEPmXZlub0\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\ncomponentQrl(qrl(() => import(\"./test.tsx_App_component_ckEPmXZlub0\"), \"App_component_ckEPmXZlub0\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_8_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_8_body.snap index 104dc9f..1879937 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_8_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_8_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_App_component_MB7xrsoro5g.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_App_component_MB7xrsoro5g.snap index 20228cb..1194300 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_App_component_MB7xrsoro5g.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_App_component_MB7xrsoro5g.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: comp.code -snapshot_kind: text --- -"import { useStylesQrl, qrl } from \"@qwik.dev/core\";\nexport const App_component_MB7xrsoro5g = () => {\n\tuseStylesQrl(qrl(() => import(\"./test.jsx_App_component_useStyles_yj4sK3KtjRE\"), \"App_component_useStyles_yj4sK3KtjRE\"));\n\tuseStylesQrl(qrl(() => import(\"./test.jsx_App_component_useStyles_1_QaDDTipqmN4\"), \"App_component_useStyles_1_QaDDTipqmN4\"));\n};\n" +"import { qrl, useStylesQrl } from \"@qwik.dev/core\";\nexport const App_component_MB7xrsoro5g = () => {\n\tuseStylesQrl(qrl(() => import(\"./test.jsx_App_component_useStyles_yj4sK3KtjRE\"), \"App_component_useStyles_yj4sK3KtjRE\"));\n\tuseStylesQrl(qrl(() => import(\"./test.jsx_App_component_useStyles_1_QaDDTipqmN4\"), \"App_component_useStyles_1_QaDDTipqmN4\"));\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_body.snap index 45b6b39..f7812f3 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const App = componentQrl(qrl(() => import(\"./test.jsx_App_component_MB7xrsoro5g\"), \"App_component_MB7xrsoro5g\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const App = componentQrl(qrl(() => import(\"./test.jsx_App_component_MB7xrsoro5g\"), \"App_component_MB7xrsoro5g\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capturing_fn_class_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capturing_fn_class_body.snap index 45b6b39..f7812f3 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capturing_fn_class_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capturing_fn_class_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const App = componentQrl(qrl(() => import(\"./test.jsx_App_component_MB7xrsoro5g\"), \"App_component_MB7xrsoro5g\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const App = componentQrl(qrl(() => import(\"./test.jsx_App_component_MB7xrsoro5g\"), \"App_component_MB7xrsoro5g\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap index 13c5b6a..b91a334 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap @@ -1,5 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: comp.code --- -"import { Lightweight } from \"./test.jsx\";\nimport { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const Foo_component_1_dBtHyQ8Btps = () => {\n\treturn /* @__PURE__ */ _jsxs(\"div\", { children: [\n\t\t/* @__PURE__ */ _jsxs(_Fragment, { children: [\n\t\t\t/* @__PURE__ */ _jsx(\"div\", { class: \"class\" }),\n\t\t\t/* @__PURE__ */ _jsx(\"div\", { class: \"class\" }),\n\t\t\t/* @__PURE__ */ _jsx(\"div\", {\n\t\t\t\tclass: \"class\",\n\t\t\t\tchildren: \"12\"\n\t\t\t})\n\t\t] }),\n\t\t/* @__PURE__ */ _jsx(\"div\", {\n\t\t\tclass: \"class\",\n\t\t\tchildren: /* @__PURE__ */ _jsx(Lightweight, { ...props })\n\t\t}),\n\t\t/* @__PURE__ */ _jsxs(\"div\", {\n\t\t\tclass: \"class\",\n\t\t\tchildren: [\n\t\t\t\t/* @__PURE__ */ _jsx(\"div\", {}),\n\t\t\t\t/* @__PURE__ */ _jsx(\"div\", {}),\n\t\t\t\t/* @__PURE__ */ _jsx(\"div\", {})\n\t\t\t]\n\t\t}),\n\t\t/* @__PURE__ */ _jsx(\"div\", {\n\t\t\tclass: \"class\",\n\t\t\tchildren\n\t\t})\n\t] });\n};\n" +"import { Lightweight } from \"./test.jsx\";\nimport { _jsxSorted, _jsxSplit } from \"@qwik.dev/core\";\nexport const Foo_component_1_dBtHyQ8Btps = () => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t[\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\"12\"], 1, null)\n\t\t],\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [_jsxSplit(Lightweight, { ...props }, {}, [], 0, null)], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null)\n\t\t], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [children], 1, null)\n\t], 1, null);\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap index 814939a..f12cf29 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap @@ -1,5 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nimport { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsx(\"div\", { children: /* @__PURE__ */ _jsxs(_Fragment, { children: [/* @__PURE__ */ _jsx(\"div\", {}), /* @__PURE__ */ _jsx(\"button\", { ...props })] }) });\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" +"import { _jsxSorted, _jsxSplit, componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [[/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null), /* @__PURE__ */ _jsxSplit(\"button\", { ...props }, {}, [], 0, null)]], 1, null);\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_B9F3YeqcO1w.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_B9F3YeqcO1w.snap new file mode 100644 index 0000000..2c3975a --- /dev/null +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_B9F3YeqcO1w.snap @@ -0,0 +1,5 @@ +--- +source: optimizer/src/transform.rs +expression: comp.code +--- +"import { qrl } from \"@qwik.dev/core\";\nexport const App_Header_component_B9F3YeqcO1w = () => {\n\tconsole.log(\"mount\");\n\treturn
import(\"./test.tsx_App_Header_component_div_onClick_aO7uI7Iw6oQ\"), \"App_Header_component_div_onClick_aO7uI7Iw6oQ\")} />;\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_div_onClick_aO7uI7Iw6oQ.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_div_onClick_aO7uI7Iw6oQ.snap new file mode 100644 index 0000000..be1b93c --- /dev/null +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_div_onClick_aO7uI7Iw6oQ.snap @@ -0,0 +1,5 @@ +--- +source: optimizer/src/transform.rs +expression: comp.code +--- +"export const App_Header_component_div_onClick_aO7uI7Iw6oQ = (ctx) => console.log(ctx);\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_body.snap new file mode 100644 index 0000000..a7a1077 --- /dev/null +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_body.snap @@ -0,0 +1,5 @@ +--- +source: optimizer/src/transform.rs +expression: result.body +--- +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const App = () => {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n};\n" diff --git a/optimizer/src/test_input/test_example_ts.tsx b/optimizer/src/test_input/test_example_ts.tsx new file mode 100644 index 0000000..940e973 --- /dev/null +++ b/optimizer/src/test_input/test_example_ts.tsx @@ -0,0 +1,11 @@ +import { $, component$, type Component } from '@builder.io/qwik'; + +export const App = () => { + const Header: Component = component$(() => { + console.log("mount"); + return ( +
console.log(ctx))}/> + ); + }); + return Header; +}; diff --git a/optimizer/src/transform.rs b/optimizer/src/transform.rs index 9d61231..943237f 100644 --- a/optimizer/src/transform.rs +++ b/optimizer/src/transform.rs @@ -16,6 +16,7 @@ use oxc_ast::{match_member_expression, AstBuilder, AstType, Comment, CommentKind use oxc_ast_visit::{Visit, VisitMut}; use oxc_codegen::{Codegen, CodegenOptions, Context, Gen}; use oxc_index::Idx; +use oxc_transformer::JsxOptions; use std::borrow::{Borrow, Cow}; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -29,6 +30,7 @@ use oxc_semantic::{ SymbolId, }; use oxc_span::*; +use oxc_transformer::{TransformOptions as OxcTransformOptions, Transformer, TypeScriptOptions}; use oxc_traverse::{traverse_mut, Ancestor, Traverse, TraverseCtx}; use serde::{Deserialize, Serialize}; use std::cell::{Cell, RefCell}; @@ -50,7 +52,6 @@ pub struct OptimizedApp { use crate::ext::*; use crate::illegal_code::{IllegalCode, IllegalCodeType}; use crate::processing_failure::ProcessingFailure; -use crate::transpiler::Transpiler; impl OptimizedApp { fn get_component(&self, name: String) -> Option<&QrlComponent> { @@ -100,6 +101,19 @@ impl OptimizationResult { } } +struct JsxState<'gen> { + is_fn: bool, + is_text_only: bool, + is_segment: bool, + should_runtime_sort: bool, + static_listeners: bool, + static_subtree: bool, + key_prop: Option>, + var_props: OxcVec<'gen, ObjectPropertyKind<'gen>>, + const_props: OxcVec<'gen, ObjectPropertyKind<'gen>>, + children: OxcVec<'gen, ArrayExpressionElement<'gen>>, +} + pub struct TransformGenerator<'gen> { pub options: TransformOptions, @@ -109,6 +123,8 @@ pub struct TransformGenerator<'gen> { pub errors: Vec, + builder: AstBuilder<'gen>, + depth: usize, segment_stack: Vec, @@ -123,6 +139,8 @@ pub struct TransformGenerator<'gen> { import_stack: Vec>, + const_stack: Vec>, + import_by_symbol: HashMap, removed: HashMap, @@ -130,6 +148,22 @@ pub struct TransformGenerator<'gen> { source_info: &'gen SourceInfo, scope: Option, + + jsx_stack: Vec>, + + jsx_key_counter: u32, + + /// Marks whether each JSX attribute in the stack is var (false) or const (true). + /// An attribute is considered var if it: + /// - calls a function + /// - accesses a member + /// - is a variable that is not an import, an export, or in the const stack + expr_is_const_stack: Vec, + + /// Used to replace the current expression in the AST. Should be set when exiting a specific + /// type of expression (e.g., `exit_jsx_element`); this will be picked up in `exit_expression`, + /// which will replace the entire expression with the contents of this field. + replace_expr: Option>, } impl<'gen> TransformGenerator<'gen> { @@ -137,12 +171,16 @@ impl<'gen> TransformGenerator<'gen> { source_info: &'gen SourceInfo, options: TransformOptions, scope: Option, + allocator: &'gen Allocator, ) -> Self { + let qwik_core_import_path = PathBuf::from("@qwik/core"); + let builder = AstBuilder::new(allocator); Self { options, components: Vec::new(), app: OptimizedApp::default(), errors: Vec::new(), + builder, depth: 0, segment_stack: Vec::new(), segment_builder: SegmentBuilder::new(), @@ -150,10 +188,15 @@ impl<'gen> TransformGenerator<'gen> { component_stack: Vec::new(), qrl_stack: Vec::new(), import_stack: vec![BTreeSet::new()], - import_by_symbol: Default::default(), + const_stack: vec![BTreeSet::new()], + import_by_symbol: HashMap::default(), removed: HashMap::new(), source_info, scope, + jsx_stack: Vec::new(), + jsx_key_counter: 0, + expr_is_const_stack: Vec::new(), + replace_expr: None, } } @@ -208,11 +251,24 @@ impl<'gen> TransformGenerator<'gen> { } } +fn move_expression<'gen>( + builder: &AstBuilder<'gen>, + expr: &mut Expression<'gen>, +) -> Expression<'gen> { + let span = expr.span().clone(); + std::mem::replace(expr, builder.expression_null_literal(span)) +} + const DEBUG: bool = true; const DUMP_FINAL_AST: bool = false; impl<'a> Traverse<'a> for TransformGenerator<'a> { + fn enter_program(&mut self, node: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + println!("ENTERING PROGRAM {}", self.source_info.file_name); + } + fn exit_program(&mut self, node: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + println!("EXITING PROGRAM {}", self.source_info.file_name); if let Some(tree) = self.import_stack.pop() { tree.iter().for_each(|import| { node.body.insert(0, import.into_in(ctx.ast.allocator)); @@ -221,10 +277,6 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { ImportCleanUp::clean_up(node, ctx.ast.allocator); - if self.options.transpile_jsx { - Transpiler::transpile(ctx.ast.allocator, node, self.source_info); - } - let codegen_options = CodegenOptions { annotation_comments: true, minify: self.options.minify, @@ -251,6 +303,10 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { self.ascend(); self.debug(format!("ENTER: CallExpression, {:?}", node), ctx); + if let Some(mut is_const) = self.expr_is_const_stack.last_mut() { + *is_const = false; + } + let name = node.callee_name().unwrap_or_default().to_string(); if (name.ends_with(MARKER_SUFFIX)) { self.import_stack.push(BTreeSet::new()); @@ -312,6 +368,16 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { self.segment_stack.pop(); } + fn enter_member_expression( + &mut self, + node: &mut MemberExpression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if let Some(mut is_const) = self.expr_is_const_stack.last_mut() { + *is_const = false; + } + } + fn enter_function(&mut self, node: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { let segment: Segment = node .name() @@ -360,6 +426,12 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { let s: Segment = self.new_segment(segment_name); self.segment_stack.push(s); + if (self.options.transpile_jsx) { + self.expr_is_const_stack.push(match node.kind { + VariableDeclarationKind::Const => true, + _ => false, + }); + } if let Some(name) = id.get_identifier_name() { /// Adds symbol and import information in the case this declaration ends up being referenced in @@ -398,10 +470,35 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } } + // If this definition is constant, mark it as constant within the current scope + if self.options.transpile_jsx && self.expr_is_const_stack.pop().unwrap_or_default() { + if let Some(consts) = self.const_stack.last_mut() { + let symbol_id = node + .id + .get_binding_identifier() + .and_then(|b| b.symbol_id.get()); + if let Some(symbol_id) = symbol_id { + consts.insert(symbol_id); + } + } + } + let popped = self.segment_stack.pop(); println!("pop segment: {popped:?}"); } + fn enter_block_statement(&mut self, node: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) { + if (self.options.transpile_jsx) { + self.const_stack.push(BTreeSet::new()); + } + } + + fn exit_block_statement(&mut self, node: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) { + if (self.options.transpile_jsx) { + self.const_stack.pop(); + } + } + fn enter_expression_statement( &mut self, node: &mut ExpressionStatement<'a>, @@ -420,9 +517,39 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { self.descend(); } + fn exit_expression(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(expr) = self.replace_expr.take() { + println!("Replacing expression on exit"); + *node = expr; + } + } + fn enter_jsx_element(&mut self, node: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) { - if let Some(name) = node.opening_element.name.get_identifier_name() { - let segment: Segment = self.new_segment(name); + let (segment, is_fn, is_text_only) = + if let Some(id) = node.opening_element.name.get_identifier() { + (Some(self.new_segment(id.name)), true, false) + } else if let Some(name) = node.opening_element.name.get_identifier_name() { + ( + Some(self.new_segment(name)), + false, + is_text_only(name.into()), + ) + } else { + (None, true, false) + }; + self.jsx_stack.push(JsxState { + is_fn, + is_text_only, + is_segment: segment.is_some(), + should_runtime_sort: false, + static_listeners: true, + static_subtree: true, + key_prop: None, + var_props: OxcVec::new_in(self.builder.allocator), + const_props: OxcVec::new_in(self.builder.allocator), + children: OxcVec::new_in(self.builder.allocator), + }); + if let Some(segment) = segment { self.debug(format!("ENTER: JSXElementName {segment}"), ctx); println!("push segment: {segment}"); self.segment_stack.push(segment); @@ -430,15 +557,217 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } fn exit_jsx_element(&mut self, node: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) { - // JSX Elements should be treated as part of the segment scope. - if let Some(name) = node.opening_element.name.get_identifier_name() { - let popped = self.segment_stack.pop(); + if let Some(mut jsx) = self.jsx_stack.pop() { + if (self.options.transpile_jsx) { + if (!jsx.should_runtime_sort) { + jsx.var_props.sort_by_key(|prop| match prop { + ObjectPropertyKind::ObjectProperty(b) => match &(*b).key { + PropertyKey::StringLiteral(b) => (*b).to_string(), + _ => "".to_string(), + }, + _ => "".to_string(), + }); + } + let name = &node.opening_element.name; + let (jsx_type, pure) = match name { + JSXElementName::Identifier(b) => ( + self.builder.expression_string_literal( + (*b).span, + (*b).name, + Some((*b).name), + ), + true, + ), + JSXElementName::IdentifierReference(b) => ( + self.builder.expression_identifier((*b).span, (*b).name), + false, + ), + JSXElementName::NamespacedName(b) => { + panic!("namespaced names in JSX not implemented") + } + JSXElementName::MemberExpression(b) => { + fn process_member_expr<'b>( + builder: &AstBuilder<'b>, + expr: &JSXMemberExpressionObject<'b>, + ) -> Expression<'b> { + match expr { + JSXMemberExpressionObject::ThisExpression(b) => { + builder.expression_this((*b).span) + } + JSXMemberExpressionObject::IdentifierReference(b) => { + builder.expression_identifier((*b).span, (*b).name) + } + JSXMemberExpressionObject::MemberExpression(b) => builder + .member_expression_static( + (*b).span, + process_member_expr(builder, &(*b).object), + builder.identifier_name( + (*b).property.span(), + (*b).property.name, + ), + false, + ) + .into(), + } + } + ( + self.builder + .member_expression_static( + (*b).span(), + process_member_expr(&self.builder, &((*b).object)), + self.builder + .identifier_name((*b).property.span(), (*b).property.name), + false, + ) + .into(), + false, + ) + } + JSXElementName::ThisExpression(b) => { + (self.builder.expression_this((*b).span), false) + } + }; + let args: OxcVec> = OxcVec::from_array_in( + [ + // type + jsx_type.into(), + // varProps + self.builder + .expression_object(node.span(), jsx.var_props, None) + .into(), + // constProps + self.builder + .expression_object(node.span(), jsx.const_props, None) + .into(), + // children + self.builder + .expression_array(node.span(), jsx.children, None) + .into(), + // flags + self.builder + .expression_numeric_literal( + node.span(), + ((if jsx.static_subtree { 0b1 } else { 0 }) + | (if jsx.static_listeners { 0b01 } else { 0 })) + .into(), + None, + NumberBase::Binary, + ) + .into(), + // key + jsx.key_prop + .unwrap_or_else(|| -> Expression<'a> { + // TODO: Figure out how to replicate root_jsx_mode from old optimizer + // (this conditional should be is_fn || root_jsx_mode) + if jsx.is_fn { + if let Some(cmp) = self.component_stack.last() { + let new_key = format!( + "{}_{}", + cmp.id.hash.chars().take(2).collect::(), + self.jsx_key_counter + ); + self.jsx_key_counter += 1; + return self.builder.expression_string_literal( + Span::default(), + new_key, + None, + ); + } + } + self.builder.expression_null_literal(Span::default()) + }) + .into(), + ], + self.builder.allocator, + ); + let callee = if (jsx.should_runtime_sort) { + JSX_SPLIT_NAME + } else { + JSX_SORTED_NAME + }; + self.replace_expr = Some(self.builder.expression_call_with_pure( + node.span, + self.builder.expression_identifier(name.span(), callee), + None::>>, + args, + false, + pure, + )); + if let Some(imports) = self.import_stack.last_mut() { + imports.insert(Import::new(vec![callee.into()], QWIK_CORE_SOURCE)); + } + } + if jsx.is_segment { + let popped = self.segment_stack.pop(); + } } self.debug("EXIT: JSXElementName", ctx); self.descend(); } + fn enter_jsx_fragment(&mut self, node: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) { + self.jsx_stack.push(JsxState { + is_fn: false, + is_text_only: false, + is_segment: false, + should_runtime_sort: false, + static_listeners: true, + static_subtree: true, + key_prop: None, + var_props: OxcVec::new_in(self.builder.allocator), + const_props: OxcVec::new_in(self.builder.allocator), + children: OxcVec::new_in(self.builder.allocator), + }); + self.debug("ENTER: JSXFragment", ctx); + } + + fn exit_jsx_fragment(&mut self, node: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(mut jsx) = self.jsx_stack.pop() { + if (self.options.transpile_jsx) { + self.replace_expr = Some(self.builder.expression_array( + node.span(), + jsx.children, + None, + )); + } + } + self.debug("EXIT: JSXFragment", ctx); + } + + fn exit_jsx_spread_attribute( + &mut self, + node: &mut JSXSpreadAttribute<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if (!self.options.transpile_jsx) { + return; + } + // Reference: qwik build/v2 internal_handle_jsx_props_obj + // If we have spread props, all props that come before it are variable even if they're static + if let Some(jsx) = self.jsx_stack.last_mut() { + let range = 0..jsx.const_props.len(); + jsx.const_props + .drain(range) + .for_each(|p| jsx.var_props.push(p)); + jsx.should_runtime_sort = true; + jsx.static_subtree = false; + jsx.static_listeners = false; + jsx.var_props + .push(self.builder.object_property_kind_spread_property( + node.span(), + move_expression(&self.builder, &mut node.argument).into(), + )) + } + } + fn enter_jsx_attribute(&mut self, node: &mut JSXAttribute<'a>, ctx: &mut TraverseCtx<'a>) { + if (self.options.transpile_jsx) { + self.expr_is_const_stack.push( + self.jsx_stack + .last() + .map_or(false, |jsx| !jsx.should_runtime_sort), + ); + } self.ascend(); self.debug("ENTER: JSXAttribute", ctx); // JSX Attributes should be treated as part of the segment scope. @@ -447,8 +776,53 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } fn exit_jsx_attribute(&mut self, node: &mut JSXAttribute<'a>, ctx: &mut TraverseCtx<'a>) { + if (self.options.transpile_jsx) { + if let Some(jsx) = self.jsx_stack.last_mut() { + let expr: Expression<'a> = { + let v = &mut node.value; + match v { + None => self.builder.expression_boolean_literal(node.span, true), + Some(JSXAttributeValue::Element(_)) => { + println!("Replacing JSX attribute element on exit"); + self.replace_expr.take().unwrap() + } + Some(JSXAttributeValue::Fragment(_)) => { + println!("Replacing JSX attribute fragment on exit"); + self.replace_expr.take().unwrap() + } + Some(JSXAttributeValue::StringLiteral(b)) => self + .builder + .expression_string_literal((*b).span, (*b).value, Some((*b).value)), + Some(JSXAttributeValue::ExpressionContainer(b)) => { + move_expression(&self.builder, (*b).expression.to_expression_mut()) + } + } + }; + let is_const = self.expr_is_const_stack.pop().unwrap_or_default(); + if node.is_key() { + jsx.key_prop = Some(expr); + } else { + let props = if is_const { + &mut jsx.const_props + } else { + &mut jsx.var_props + }; + props.push(self.builder.object_property_kind_object_property( + node.span, + PropertyKind::Init, + self.builder.property_key_static_identifier( + node.name.span(), + node.name.get_identifier().name, + ), + expr, + false, + false, + false, + )); + } + } + } let popped = self.segment_stack.pop(); - println!("pop segment: {popped:?}"); self.debug("EXIT: JSXAttribute", ctx); self.descend(); } @@ -471,6 +845,52 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } } + fn exit_jsx_child(&mut self, node: &mut JSXChild<'a>, ctx: &mut TraverseCtx<'a>) { + if (!self.options.transpile_jsx) { + return; + } + self.debug("EXIT: JSX child", ctx); + if let Some(jsx) = self.jsx_stack.last_mut() { + let maybe_child = match node { + JSXChild::Text(b) => { + let text: &'a str = self.builder.allocator.alloc_str(b.value.trim()); + if (text.is_empty()) { + None + } else { + Some( + self.builder + .expression_string_literal((*b).span, text, Some(text.into())) + .into(), + ) + } + } + JSXChild::Element(_) => { + println!("Replacing JSX child element on exit"); + Some(self.replace_expr.take().unwrap().into()) + } + JSXChild::Fragment(_) => { + println!("Replacing JSX child fragment on exit"); + Some(self.replace_expr.take().unwrap().into()) + } + JSXChild::ExpressionContainer(b) => { + jsx.static_subtree = false; + Some(move_expression(&self.builder, (*b).expression.to_expression_mut()).into()) + } + JSXChild::Spread(b) => { + jsx.static_subtree = false; + let span = (*b).span.clone(); + Some(self.builder.array_expression_element_spread_element( + span, + move_expression(&self.builder, &mut (*b).expression), + )) + } + }; + if let Some(child) = maybe_child { + jsx.children.push(child); + } + } + } + fn exit_return_statement(&mut self, node: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(expr) = &node.argument { if expr.is_qrl_replaceable() { @@ -658,13 +1078,26 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } } +fn is_text_only(node: &str) -> bool { + matches!( + node, + "text" | "textarea" | "title" | "option" | "script" | "style" | "noscript" + ) +} + pub struct TransformOptions { pub minify: bool, pub target: Target, + pub transpile_ts: bool, pub transpile_jsx: bool, } impl TransformOptions { + pub fn with_transpile_ts(mut self, transpile_ts: bool) -> Self { + self.transpile_ts = transpile_ts; + self + } + pub fn with_transpile_jsx(mut self, transpile_jsx: bool) -> Self { self.transpile_jsx = transpile_jsx; self @@ -676,6 +1109,7 @@ impl Default for TransformOptions { TransformOptions { minify: false, target: Target::Dev, + transpile_ts: false, transpile_jsx: false, } } @@ -694,6 +1128,24 @@ pub fn transform(script_source: Source, options: TransformOptions) -> Result Result( - allocator: &'a Allocator, - pgm: &mut Program<'a>, - source_info: &SourceInfo, - ) { - // TODO Correctly handle errors. - let ret = SemanticBuilder::new().with_excess_capacity(2.0).build(&pgm); - let scoping = ret.semantic.into_scoping(); - let path = &source_info.rel_path; - // TODO Correctly handle errors. - let ret = Transformer::new(allocator, path, &TransformOptions::default()) - .build_with_scoping(scoping, pgm); - } -}