diff --git a/.REFACTOR_NOTES.md b/.REFACTOR_NOTES.md new file mode 100644 index 00000000000..94fe1b685c5 --- /dev/null +++ b/.REFACTOR_NOTES.md @@ -0,0 +1,44 @@ +1 << 0 | 001 | static listeners +1 << 1 | 002 | static subtree + +## Slots + +```typescript +const Parent = component$(() => { + return ( + + Projection Content + Secondary Content + Other Content + + }; +}); + +const Child = component$(() => { + return ( +
+ Default Primary + Default Secondary +
+ ); +}); +``` + +```html + + +
+ + Projected Content + + + + Secondary Content + +
+
+ +
+``` diff --git a/.changeset/afraid-bags-jam.md b/.changeset/afraid-bags-jam.md new file mode 100644 index 00000000000..fc00f500181 --- /dev/null +++ b/.changeset/afraid-bags-jam.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: Resource without onPending callback diff --git a/.changeset/afraid-garlics-greet.md b/.changeset/afraid-garlics-greet.md new file mode 100644 index 00000000000..bc9283e30da --- /dev/null +++ b/.changeset/afraid-garlics-greet.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: don't escape value attribute diff --git a/.changeset/all-cloths-hammer.md b/.changeset/all-cloths-hammer.md new file mode 100644 index 00000000000..4a2db064fba --- /dev/null +++ b/.changeset/all-cloths-hammer.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +FEAT: the QRL segment mapping during Vite dev mode now happens in core and does not require providing a separate `symbolMapper` function any more. diff --git a/.changeset/angry-boats-lose.md b/.changeset/angry-boats-lose.md new file mode 100644 index 00000000000..22add4b4ade --- /dev/null +++ b/.changeset/angry-boats-lose.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': patch +--- + +Bugfix - rename the view transition type in CSS to prevent default view transition on SPA navigation diff --git a/.changeset/angry-grapes-itch.md b/.changeset/angry-grapes-itch.md new file mode 100644 index 00000000000..a4af4b3c5d7 --- /dev/null +++ b/.changeset/angry-grapes-itch.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: rendering attribute value from array of classes from spread props diff --git a/.changeset/better-shrimps-sin.md b/.changeset/better-shrimps-sin.md new file mode 100644 index 00000000000..a9dc57aad3c --- /dev/null +++ b/.changeset/better-shrimps-sin.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': patch +--- + +fix: adding popstate and scroll event for SPA navigation diff --git a/.changeset/blue-beans-happen.md b/.changeset/blue-beans-happen.md new file mode 100644 index 00000000000..2767fb83652 --- /dev/null +++ b/.changeset/blue-beans-happen.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': patch +--- + +fix: using routeLoader$ result as component prop diff --git a/.changeset/brave-files-grin.md b/.changeset/brave-files-grin.md new file mode 100644 index 00000000000..7b1e6c2bb05 --- /dev/null +++ b/.changeset/brave-files-grin.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: reduced number of errors "Cannot serialize function" during serialization diff --git a/.changeset/bright-cows-sell.md b/.changeset/bright-cows-sell.md new file mode 100644 index 00000000000..6cad08879b7 --- /dev/null +++ b/.changeset/bright-cows-sell.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: resuming a component using styles and a text node diff --git a/.changeset/brown-ravens-behave.md b/.changeset/brown-ravens-behave.md new file mode 100644 index 00000000000..2753f83f5e3 --- /dev/null +++ b/.changeset/brown-ravens-behave.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: updating signal-based props diff --git a/.changeset/calm-cycles-know.md b/.changeset/calm-cycles-know.md new file mode 100644 index 00000000000..1b0fc3ab1a1 --- /dev/null +++ b/.changeset/calm-cycles-know.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: serialization of an array of refs diff --git a/.changeset/chilled-spoons-wonder.md b/.changeset/chilled-spoons-wonder.md new file mode 100644 index 00000000000..262ae2d7438 --- /dev/null +++ b/.changeset/chilled-spoons-wonder.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: prevent multiple store deserialization diff --git a/.changeset/chilly-fans-shave.md b/.changeset/chilly-fans-shave.md new file mode 100644 index 00000000000..dba9d9d3c20 --- /dev/null +++ b/.changeset/chilly-fans-shave.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: don't wrap template literals with a function call inside them in a signal diff --git a/.changeset/clever-flowers-drum.md b/.changeset/clever-flowers-drum.md new file mode 100644 index 00000000000..f9d34076769 --- /dev/null +++ b/.changeset/clever-flowers-drum.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: using ref inside useContext diff --git a/.changeset/cold-moons-follow.md b/.changeset/cold-moons-follow.md new file mode 100644 index 00000000000..3a97ce6fcc6 --- /dev/null +++ b/.changeset/cold-moons-follow.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: infinity loop while tracking element ref diff --git a/.changeset/cold-rice-slide.md b/.changeset/cold-rice-slide.md new file mode 100644 index 00000000000..6f15856a353 --- /dev/null +++ b/.changeset/cold-rice-slide.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: proper empty props diffing diff --git a/.changeset/config.json b/.changeset/config.json index e45f374add7..9522e2b7f2f 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -2,19 +2,14 @@ "$schema": "https://unpkg.com/@changesets/config@3.0.1/schema.json", "changelog": ["./changelog-github-custom.cjs", { "repo": "QwikDev/qwik" }], "commit": false, - "fixed": [["@builder.io/qwik", "@builder.io/qwik-city", "eslint-plugin-qwik", "create-qwik"]], + "fixed": [ + ["@qwik.dev/core", "@qwik.dev/router", "eslint-plugin-qwik", "create-qwik", "@qwik.dev/react"] + ], "linked": [], "access": "public", - "baseBranch": "origin/upcoming", + "baseBranch": "origin/build/v2", "updateInternalDependencies": "minor", - "ignore": [ - "qwik-docs", - "@builder.io/qwik-labs", - "insights", - "@builder.io/qwik-react", - "@builder.io/qwik-worker", - "qwik-cli-e2e" - ], + "ignore": ["qwik-docs", "insights", "qwik-cli-e2e"], "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { "onlyUpdatePeerDependentsWhenOutOfRange": true } diff --git a/.changeset/cyan-bottles-speak.md b/.changeset/cyan-bottles-speak.md new file mode 100644 index 00000000000..fcdcf0536a7 --- /dev/null +++ b/.changeset/cyan-bottles-speak.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: creating error overlay diff --git a/.changeset/cyan-walls-sing.md b/.changeset/cyan-walls-sing.md new file mode 100644 index 00000000000..a87c32aba38 --- /dev/null +++ b/.changeset/cyan-walls-sing.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: reuse the same props instance when props are changing diff --git a/.changeset/dirty-dolls-heal.md b/.changeset/dirty-dolls-heal.md deleted file mode 100644 index dd5e6a307ba..00000000000 --- a/.changeset/dirty-dolls-heal.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@builder.io/qwik-city': minor ---- - -FEAT: Added rewrite() to the RequestEvent object. It works like redirect but does not change the URL, - think of it as an internal redirect. - -Example usage: -```ts -export const onRequest: RequestHandler = async ({ url, rewrite }) => { - if (url.pathname.includes("/articles/the-best-article-in-the-world")) { - const artistId = db.getArticleByName("the-best-article-in-the-world"); - - // Url will remain /articles/the-best-article-in-the-world, but under the hood, - // will render /articles/${artistId} - throw rewrite(`/articles/${artistId}`); - } -}; -``` \ No newline at end of file diff --git a/.changeset/dirty-lemons-shop.md b/.changeset/dirty-lemons-shop.md new file mode 100644 index 00000000000..5d8e229f1a1 --- /dev/null +++ b/.changeset/dirty-lemons-shop.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: reexecute component with null key diff --git a/.changeset/dirty-lines-march.md b/.changeset/dirty-lines-march.md new file mode 100644 index 00000000000..55aa90a1bc8 --- /dev/null +++ b/.changeset/dirty-lines-march.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +feat: implement new SerializationWeakRef class for values that can be not serialized diff --git a/.changeset/eighty-ligers-wink.md b/.changeset/eighty-ligers-wink.md new file mode 100644 index 00000000000..0e5e73b1ac3 --- /dev/null +++ b/.changeset/eighty-ligers-wink.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: correctly handle initial resource state diff --git a/.changeset/eighty-points-argue.md b/.changeset/eighty-points-argue.md new file mode 100644 index 00000000000..21f11f3f004 --- /dev/null +++ b/.changeset/eighty-points-argue.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: async computed signal promise rejection diff --git a/.changeset/empty-sites-tie.md b/.changeset/empty-sites-tie.md deleted file mode 100644 index fd16386ebf5..00000000000 --- a/.changeset/empty-sites-tie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@builder.io/qwik': patch ---- - -:zap: the qwikloader is no longer embedded in the SSR results. Instead, the same techniques are used as for the preloader to ensure that the qwikloader is active as soon as possible, loaded from a separate bundle. This reduces SSR page size by several kB end ensures that subsequent qwikloader loads are nearly instant. diff --git a/.changeset/fair-cameras-boil.md b/.changeset/fair-cameras-boil.md new file mode 100644 index 00000000000..da52dcd02a7 --- /dev/null +++ b/.changeset/fair-cameras-boil.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: change client side generated ID to start with build base and add convert first character to letter if it is starting from number diff --git a/.changeset/famous-numbers-kneel.md b/.changeset/famous-numbers-kneel.md new file mode 100644 index 00000000000..bc08f538218 --- /dev/null +++ b/.changeset/famous-numbers-kneel.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +feat: expose invalidate method for computed-like signals diff --git a/.changeset/fancy-nights-chew.md b/.changeset/fancy-nights-chew.md deleted file mode 100644 index a3935274142..00000000000 --- a/.changeset/fancy-nights-chew.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@builder.io/qwik': patch ---- - -Removed backdrop-filter of vite-error-overlay to prevent perf issues with multiple errors diff --git a/.changeset/fast-baboons-itch.md b/.changeset/fast-baboons-itch.md new file mode 100644 index 00000000000..c143d2a6e8a --- /dev/null +++ b/.changeset/fast-baboons-itch.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': major +--- + +BREAKING: remove HTML-related types. Use PropsOf instead. diff --git a/.changeset/few-mugs-accept.md b/.changeset/few-mugs-accept.md new file mode 100644 index 00000000000..6f9ca38be04 --- /dev/null +++ b/.changeset/few-mugs-accept.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: null or undefined as ref attribute value diff --git a/.changeset/five-kangaroos-matter.md b/.changeset/five-kangaroos-matter.md new file mode 100644 index 00000000000..caa63e24785 --- /dev/null +++ b/.changeset/five-kangaroos-matter.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': major +--- + +💥**BREAKING**: `useComputed` no longer allows Promise returns. (meaning it is strictly sync) Instead, use `useSignal` and `useTask` together to perform async signal updates diff --git a/.changeset/five-shoes-deny.md b/.changeset/five-shoes-deny.md new file mode 100644 index 00000000000..83e3fb8cfa1 --- /dev/null +++ b/.changeset/five-shoes-deny.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: prevent infinity loop by inserting the same projection before itself diff --git a/.changeset/fluffy-poets-raise.md b/.changeset/fluffy-poets-raise.md new file mode 100644 index 00000000000..55c0e6bf676 --- /dev/null +++ b/.changeset/fluffy-poets-raise.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: custom event names and DOMContentLoaded handling diff --git a/.changeset/forty-garlics-train.md b/.changeset/forty-garlics-train.md new file mode 100644 index 00000000000..b45123a890d --- /dev/null +++ b/.changeset/forty-garlics-train.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: store effects cleanup diff --git a/.changeset/fresh-rocks-exercise.md b/.changeset/fresh-rocks-exercise.md new file mode 100644 index 00000000000..28ff44c88d0 --- /dev/null +++ b/.changeset/fresh-rocks-exercise.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +feat: new integration tests that are running with the optimizer diff --git a/.changeset/friendly-beers-heal.md b/.changeset/friendly-beers-heal.md new file mode 100644 index 00000000000..489f8b4107e --- /dev/null +++ b/.changeset/friendly-beers-heal.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: attribute diffing was not working correctly in some edge cases diff --git a/.changeset/friendly-gorillas-walk.md b/.changeset/friendly-gorillas-walk.md new file mode 100644 index 00000000000..803223e3e70 --- /dev/null +++ b/.changeset/friendly-gorillas-walk.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +FIX: types error when migrating to V2 with `moduleResulution: "node"` diff --git a/.changeset/friendly-sloths-return.md b/.changeset/friendly-sloths-return.md new file mode 100644 index 00000000000..f3cda8ab3a2 --- /dev/null +++ b/.changeset/friendly-sloths-return.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: projection siblings serialization diff --git a/.changeset/funny-apricots-learn.md b/.changeset/funny-apricots-learn.md new file mode 100644 index 00000000000..4fb9c305ffb --- /dev/null +++ b/.changeset/funny-apricots-learn.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: moving existing virtual node during vnode diffing diff --git a/.changeset/fuzzy-files-kiss.md b/.changeset/fuzzy-files-kiss.md new file mode 100644 index 00000000000..e247367a082 --- /dev/null +++ b/.changeset/fuzzy-files-kiss.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: don't wrap function calls in signal diff --git a/.changeset/fuzzy-jobs-compare.md b/.changeset/fuzzy-jobs-compare.md new file mode 100644 index 00000000000..5b55fe46fe0 --- /dev/null +++ b/.changeset/fuzzy-jobs-compare.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: component props as var props diff --git a/.changeset/gentle-melons-pretend.md b/.changeset/gentle-melons-pretend.md new file mode 100644 index 00000000000..9d490c6017c --- /dev/null +++ b/.changeset/gentle-melons-pretend.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: convert destructured string prop to props variable diff --git a/.changeset/gold-colts-change.md b/.changeset/gold-colts-change.md deleted file mode 100644 index 1bbdbe5ea5b..00000000000 --- a/.changeset/gold-colts-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@builder.io/qwik': patch ---- - -FIX: assetsDir and debug:true will no longer break your application. diff --git a/.changeset/good-mammals-grab.md b/.changeset/good-mammals-grab.md deleted file mode 100644 index 8887dc944cf..00000000000 --- a/.changeset/good-mammals-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@builder.io/qwik': minor ---- - -FIX: the preloader bundle graph file is now built as an asset. This is cleaner and avoids i18n translation of the file. diff --git a/.changeset/green-drinks-strive.md b/.changeset/green-drinks-strive.md new file mode 100644 index 00000000000..b124d7097ea --- /dev/null +++ b/.changeset/green-drinks-strive.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': minor +--- + +FEAT: qwikRouter middleware no longer needs qwikRouterConfig, it handles it internally diff --git a/.changeset/heavy-kids-wave.md b/.changeset/heavy-kids-wave.md new file mode 100644 index 00000000000..30a2e8ac3b2 --- /dev/null +++ b/.changeset/heavy-kids-wave.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: rendering markdown file with Qwik component diff --git a/.changeset/heavy-maps-wash.md b/.changeset/heavy-maps-wash.md new file mode 100644 index 00000000000..376c6956e80 --- /dev/null +++ b/.changeset/heavy-maps-wash.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': patch +--- + +fix: getting invoke context for loaders in production diff --git a/.changeset/heavy-radios-dream.md b/.changeset/heavy-radios-dream.md new file mode 100644 index 00000000000..8db7e56e562 --- /dev/null +++ b/.changeset/heavy-radios-dream.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +FIX: QRLs are now scheduled instead of directly executed by qwik-loader, so that they are executed in the right order. diff --git a/.changeset/heavy-seas-carry.md b/.changeset/heavy-seas-carry.md new file mode 100644 index 00000000000..d5d1582094b --- /dev/null +++ b/.changeset/heavy-seas-carry.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: encode the `q:subs` property diff --git a/.changeset/hip-hornets-cheer.md b/.changeset/hip-hornets-cheer.md new file mode 100644 index 00000000000..80a614bb89c --- /dev/null +++ b/.changeset/hip-hornets-cheer.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +feat: new simpler signals implementation with lazy useComputed$ execution, only when is needed diff --git a/.changeset/hip-points-kick.md b/.changeset/hip-points-kick.md new file mode 100644 index 00000000000..338fb9beef0 --- /dev/null +++ b/.changeset/hip-points-kick.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: the @qwik-handlers aren't properly handled in dev mode for library projects diff --git a/.changeset/honest-pears-sniff.md b/.changeset/honest-pears-sniff.md new file mode 100644 index 00000000000..2d9d57a57e2 --- /dev/null +++ b/.changeset/honest-pears-sniff.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: serialize less vnode data diff --git a/.changeset/itchy-comics-develop.md b/.changeset/itchy-comics-develop.md new file mode 100644 index 00000000000..c37c1332be0 --- /dev/null +++ b/.changeset/itchy-comics-develop.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: different component rendering with the same key diff --git a/.changeset/kind-toes-glow.md b/.changeset/kind-toes-glow.md new file mode 100644 index 00000000000..3478fd452d0 --- /dev/null +++ b/.changeset/kind-toes-glow.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: export SVG type from qwik/core diff --git a/.changeset/large-houses-watch.md b/.changeset/large-houses-watch.md new file mode 100644 index 00000000000..3471c878d4a --- /dev/null +++ b/.changeset/large-houses-watch.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +FIX: the `srcInput` option to `qwikVite` is deprecated because it's unused. diff --git a/.changeset/lemon-tools-bathe.md b/.changeset/lemon-tools-bathe.md new file mode 100644 index 00000000000..3b097d7d35a --- /dev/null +++ b/.changeset/lemon-tools-bathe.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +FIX: optimizer is now better at recognizing constProp diff --git a/.changeset/little-ways-deny.md b/.changeset/little-ways-deny.md new file mode 100644 index 00000000000..53f6ec85f3f --- /dev/null +++ b/.changeset/little-ways-deny.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +feat: new hook - useAsyncComputed$ in replacement of useComputed$ with async operations diff --git a/.changeset/long-shirts-thank.md b/.changeset/long-shirts-thank.md new file mode 100644 index 00000000000..5204b0320bb --- /dev/null +++ b/.changeset/long-shirts-thank.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: create svg nested children with correct namespace diff --git a/.changeset/loud-dolphins-relate.md b/.changeset/loud-dolphins-relate.md new file mode 100644 index 00000000000..28521f3f36a --- /dev/null +++ b/.changeset/loud-dolphins-relate.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: finding context parent and sorting projections in the scheduler diff --git a/.changeset/many-tips-win.md b/.changeset/many-tips-win.md new file mode 100644 index 00000000000..e52f53a2c14 --- /dev/null +++ b/.changeset/many-tips-win.md @@ -0,0 +1,6 @@ +--- +'@qwik.dev/core': minor +'@qwik.dev/router': patch +--- + +FEAT: Server output chunk files are now under their own build/ subdir, like the client build. This makes it easier to override the chunk filenames. This is possible because the Router metadata files are now an earlier part of the build process. diff --git a/.changeset/mean-dingos-hug.md b/.changeset/mean-dingos-hug.md new file mode 100644 index 00000000000..705ba076cb2 --- /dev/null +++ b/.changeset/mean-dingos-hug.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +feat: move signal invalidation to the scheduler diff --git a/.changeset/mean-parents-buy.md b/.changeset/mean-parents-buy.md new file mode 100644 index 00000000000..ed4f50182e8 --- /dev/null +++ b/.changeset/mean-parents-buy.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: replace inline component with component$ with the same key diff --git a/.changeset/nervous-terms-explode.md b/.changeset/nervous-terms-explode.md new file mode 100644 index 00000000000..3ac190db6c4 --- /dev/null +++ b/.changeset/nervous-terms-explode.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +feat: better node attributes serialization diff --git a/.changeset/nine-otters-repeat.md b/.changeset/nine-otters-repeat.md new file mode 100644 index 00000000000..807574de712 --- /dev/null +++ b/.changeset/nine-otters-repeat.md @@ -0,0 +1,35 @@ +--- +'@qwik.dev/core': major +--- + +`qwik-labs` package has been removed in favor of experimental features. +So the "Insights" vite plugin and components have been moved to core as an experimental feature. + +In order to use it, you need to - + +**1)** add `insights` to the experimental array in `vite.config.ts`: + +```ts +qwikVite({ + experimental: ['insights'] +}), +``` + +**2)** Import and use the `qwikInsights` vite plugin from `@qwik.dev/core/insights/vite`: + +```ts +import { qwikInsights } from '@qwik.dev/core/insights/vite'; +``` + +**3)** import the `` component from `@qwik.dev/core/insights` and use it in your `root.tsx` file: : + +```tsx title="root.tsx" +import { Insights } from '@qwik.dev/core/insights'; + +// ...rest of root.tsx file + +return ( + + /* ...qwik app */ +); +``` diff --git a/.changeset/ninety-crabs-lay.md b/.changeset/ninety-crabs-lay.md new file mode 100644 index 00000000000..5665a8a037b --- /dev/null +++ b/.changeset/ninety-crabs-lay.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: preserve innerHTML after component rerender diff --git a/.changeset/old-guests-stare.md b/.changeset/old-guests-stare.md new file mode 100644 index 00000000000..d08ad40d8cf --- /dev/null +++ b/.changeset/old-guests-stare.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: subscribe to signals on computed signal recomputation diff --git a/.changeset/old-mangos-return.md b/.changeset/old-mangos-return.md new file mode 100644 index 00000000000..2869373840c --- /dev/null +++ b/.changeset/old-mangos-return.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: maximum component rerender retries diff --git a/.changeset/old-mayflies-fetch.md b/.changeset/old-mayflies-fetch.md new file mode 100644 index 00000000000..1b47c91d512 --- /dev/null +++ b/.changeset/old-mayflies-fetch.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: the use hook didn't work when type is Slot. diff --git a/.changeset/olive-cameras-collect.md b/.changeset/olive-cameras-collect.md new file mode 100644 index 00000000000..4a5cdb5c658 --- /dev/null +++ b/.changeset/olive-cameras-collect.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +CHORE: replace the `_hW` export in segments with a shared export `_task` in core. This opens up using QRLs from core. diff --git a/.changeset/olive-yaks-prove.md b/.changeset/olive-yaks-prove.md new file mode 100644 index 00000000000..f2e8ec6239c --- /dev/null +++ b/.changeset/olive-yaks-prove.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: don't wrap static objects with signal diff --git a/.changeset/open-beds-grab.md b/.changeset/open-beds-grab.md new file mode 100644 index 00000000000..7ee62465733 --- /dev/null +++ b/.changeset/open-beds-grab.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: render SVG attributes with correct namespace diff --git a/.changeset/orange-otters-attend.md b/.changeset/orange-otters-attend.md new file mode 100644 index 00000000000..8f1d2f204fe --- /dev/null +++ b/.changeset/orange-otters-attend.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +feat: emit "qrender" event after every render diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000000..ea978822403 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,125 @@ +{ + "mode": "pre", + "tag": "beta", + "initialVersions": { + "create-qwik": "2.0.0-0", + "qwik-docs": "0.0.1", + "eslint-plugin-qwik": "2.0.0-0", + "@qwik.dev/core": "2.0.0-0", + "@qwik.dev/router": "2.0.0-0", + "insights": "0.1.0", + "@qwik.dev/dom": "2.1.19", + "@qwik.dev/react": "2.0.0-0", + "supabase-auth-helpers-qwik": "0.0.3", + "qwik-cli-e2e": "0.0.0" + }, + "branch": "build/v2", + "changesets": [ + "afraid-bags-jam", + "afraid-garlics-greet", + "angry-grapes-itch", + "better-shrimps-sin", + "blue-beans-happen", + "brave-files-grin", + "bright-cows-sell", + "brown-ravens-behave", + "calm-cycles-know", + "chilled-spoons-wonder", + "chilly-fans-shave", + "clever-flowers-drum", + "cold-moons-follow", + "cold-rice-slide", + "cyan-bottles-speak", + "cyan-walls-sing", + "dirty-lemons-shop", + "dirty-lines-march", + "eighty-ligers-wink", + "eighty-points-argue", + "fair-cameras-boil", + "famous-numbers-kneel", + "fast-baboons-itch", + "few-mugs-accept", + "five-kangaroos-matter", + "five-shoes-deny", + "fluffy-poets-raise", + "forty-garlics-train", + "fresh-rocks-exercise", + "friendly-beers-heal", + "friendly-gorillas-walk", + "friendly-sloths-return", + "funny-apricots-learn", + "fuzzy-files-kiss", + "fuzzy-jobs-compare", + "gentle-melons-pretend", + "heavy-kids-wave", + "heavy-radios-dream", + "heavy-seas-carry", + "hip-hornets-cheer", + "hip-points-kick", + "honest-pears-sniff", + "itchy-comics-develop", + "kind-toes-glow", + "lemon-tools-bathe", + "little-ways-deny", + "long-shirts-thank", + "loud-dolphins-relate", + "mean-dingos-hug", + "mean-parents-buy", + "nervous-terms-explode", + "nine-otters-repeat", + "old-guests-stare", + "old-mangos-return", + "old-mayflies-fetch", + "olive-cameras-collect", + "olive-yaks-prove", + "orange-otters-attend", + "pretty-trees-check", + "proud-houses-fix", + "proud-kangaroos-boil", + "proud-pillows-try", + "purple-melons-invent", + "quick-moons-show", + "rare-candies-join", + "red-trains-deny", + "rich-shirts-thank", + "rich-wasps-tease", + "rotten-penguins-cough", + "rotten-weeks-tickle", + "seven-pumpkins-argue", + "shaggy-poems-appear", + "shaggy-poems-return", + "sharp-apples-relate", + "shy-walls-shake", + "six-games-float", + "sixty-grapes-beam", + "slimy-weeks-hope", + "smooth-cups-press", + "soft-insects-see", + "some-birds-juggle", + "sour-zebras-tell", + "strange-bottles-sleep", + "strong-rules-rescue", + "sweet-bees-punch", + "sweet-hairs-remember", + "sweet-socks-whisper", + "swift-flowers-juggle", + "tall-rivers-appear", + "tame-glasses-explain", + "tasty-penguins-ring", + "thirty-carrots-stand", + "thirty-ravens-draw", + "tiny-berries-bow", + "tricky-meals-heal", + "tricky-peaches-buy", + "twenty-goats-flow", + "unlucky-dodos-grab", + "unlucky-olives-knock", + "warm-camels-remain", + "wet-bobcats-decide", + "wicked-pets-chew", + "wide-boats-pump", + "wild-cooks-pay", + "witty-balloons-thank", + "young-cameras-hang" + ] +} diff --git a/.changeset/pretty-trees-check.md b/.changeset/pretty-trees-check.md new file mode 100644 index 00000000000..3ed456a8c1d --- /dev/null +++ b/.changeset/pretty-trees-check.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: ensure components are only rendered when necessary diff --git a/.changeset/proud-houses-fix.md b/.changeset/proud-houses-fix.md new file mode 100644 index 00000000000..39a63045930 --- /dev/null +++ b/.changeset/proud-houses-fix.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +feat: expose option to never or always serialize computed-like signal value diff --git a/.changeset/proud-kangaroos-boil.md b/.changeset/proud-kangaroos-boil.md new file mode 100644 index 00000000000..9c44e3d9d53 --- /dev/null +++ b/.changeset/proud-kangaroos-boil.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: finding vnodes on interaction diff --git a/.changeset/proud-pillows-try.md b/.changeset/proud-pillows-try.md new file mode 100644 index 00000000000..52b8001173c --- /dev/null +++ b/.changeset/proud-pillows-try.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +chore: more descriptive HTML streaming error message diff --git a/.changeset/purple-melons-invent.md b/.changeset/purple-melons-invent.md new file mode 100644 index 00000000000..be13abfef7b --- /dev/null +++ b/.changeset/purple-melons-invent.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: find correct context after rendering empty array diff --git a/.changeset/quick-moons-show.md b/.changeset/quick-moons-show.md new file mode 100644 index 00000000000..ca950218c53 --- /dev/null +++ b/.changeset/quick-moons-show.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: correctly serialize vnode props in production mode diff --git a/.changeset/rare-candies-join.md b/.changeset/rare-candies-join.md new file mode 100644 index 00000000000..a483644fc06 --- /dev/null +++ b/.changeset/rare-candies-join.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: skip serialize functions wrapped with the `noSerialize` diff --git a/.changeset/real-cities-fold.md b/.changeset/real-cities-fold.md deleted file mode 100644 index 1f1609b2aab..00000000000 --- a/.changeset/real-cities-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@builder.io/qwik': patch ---- - -FEAT: q-manifest.json now also includes the generated assets diff --git a/.changeset/red-trains-deny.md b/.changeset/red-trains-deny.md new file mode 100644 index 00000000000..41418c4a044 --- /dev/null +++ b/.changeset/red-trains-deny.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: textarea with null value diff --git a/.changeset/rich-shirts-thank.md b/.changeset/rich-shirts-thank.md new file mode 100644 index 00000000000..51f3cd948e1 --- /dev/null +++ b/.changeset/rich-shirts-thank.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +FIX: add HTMLElementAttrs and SVGProps types to exports diff --git a/.changeset/rich-wasps-tease.md b/.changeset/rich-wasps-tease.md new file mode 100644 index 00000000000..c0b3baffcaf --- /dev/null +++ b/.changeset/rich-wasps-tease.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': major +--- + +BREAKING: the Typescript exports were trimmed down to the bare minimum. If there are types you are missing, open an issue. diff --git a/.changeset/rotten-penguins-cough.md b/.changeset/rotten-penguins-cough.md new file mode 100644 index 00000000000..2f7854361e4 --- /dev/null +++ b/.changeset/rotten-penguins-cough.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: undefined or null as projection child diff --git a/.changeset/rotten-weeks-tickle.md b/.changeset/rotten-weeks-tickle.md new file mode 100644 index 00000000000..267de3a2bbf --- /dev/null +++ b/.changeset/rotten-weeks-tickle.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: do not trigger effects if computed value is not changed diff --git a/.changeset/seven-pumpkins-argue.md b/.changeset/seven-pumpkins-argue.md new file mode 100644 index 00000000000..d41780171d0 --- /dev/null +++ b/.changeset/seven-pumpkins-argue.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: infinity serialization loop diff --git a/.changeset/shaggy-poems-appear.md b/.changeset/shaggy-poems-appear.md new file mode 100644 index 00000000000..4899a30fc83 --- /dev/null +++ b/.changeset/shaggy-poems-appear.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +feat: log a warning instead of throwing an error for server host mismatch error diff --git a/.changeset/shaggy-poems-return.md b/.changeset/shaggy-poems-return.md new file mode 100644 index 00000000000..1d869f2f7ac --- /dev/null +++ b/.changeset/shaggy-poems-return.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: event handlers in loops diff --git a/.changeset/sharp-apples-relate.md b/.changeset/sharp-apples-relate.md new file mode 100644 index 00000000000..7278588ecfb --- /dev/null +++ b/.changeset/sharp-apples-relate.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: vNode serialization error on server$ diff --git a/.changeset/shiny-lies-sip.md b/.changeset/shiny-lies-sip.md deleted file mode 100644 index af900f4590d..00000000000 --- a/.changeset/shiny-lies-sip.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@builder.io/qwik': patch ---- - -fix(ssr): support q-manifest resolution under Bun runtime (#7565) diff --git a/.changeset/short-suits-bet.md b/.changeset/short-suits-bet.md new file mode 100644 index 00000000000..62d98fa4ec1 --- /dev/null +++ b/.changeset/short-suits-bet.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +FIX: `qwikVite` has better vite config handling around input files, and no longer writes the q-manifest file to a temp dir. diff --git a/.changeset/shy-walls-shake.md b/.changeset/shy-walls-shake.md new file mode 100644 index 00000000000..a7ba9087047 --- /dev/null +++ b/.changeset/shy-walls-shake.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: reactivity for type-asserted variables in templates diff --git a/.changeset/six-games-float.md b/.changeset/six-games-float.md new file mode 100644 index 00000000000..e5346ede392 --- /dev/null +++ b/.changeset/six-games-float.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: don't wrap and serialize functions that are attribute values diff --git a/.changeset/sixty-grapes-beam.md b/.changeset/sixty-grapes-beam.md new file mode 100644 index 00000000000..5469f057e85 --- /dev/null +++ b/.changeset/sixty-grapes-beam.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: serializer symbol value recalculate without update function diff --git a/.changeset/slimy-weeks-hope.md b/.changeset/slimy-weeks-hope.md new file mode 100644 index 00000000000..1d4087cacd6 --- /dev/null +++ b/.changeset/slimy-weeks-hope.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: retry vnode diffing on promise throw diff --git a/.changeset/smooth-cups-press.md b/.changeset/smooth-cups-press.md new file mode 100644 index 00000000000..cd09530de0b --- /dev/null +++ b/.changeset/smooth-cups-press.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': patch +--- + +fix: nested not serialized loaders diff --git a/.changeset/soft-insects-see.md b/.changeset/soft-insects-see.md new file mode 100644 index 00000000000..54368b08250 --- /dev/null +++ b/.changeset/soft-insects-see.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +:zap: QRL segments now memoize imports, removing some Promises during render diff --git a/.changeset/some-birds-juggle.md b/.changeset/some-birds-juggle.md new file mode 100644 index 00000000000..197792ac52d --- /dev/null +++ b/.changeset/some-birds-juggle.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: reactivity for logical expressions in templates diff --git a/.changeset/sour-zebras-tell.md b/.changeset/sour-zebras-tell.md new file mode 100644 index 00000000000..684e9e5d075 --- /dev/null +++ b/.changeset/sour-zebras-tell.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +feat: added the scheduler to sort chores execution and have more predictable behavior diff --git a/.changeset/strange-bottles-sleep.md b/.changeset/strange-bottles-sleep.md new file mode 100644 index 00000000000..0b06f61c07c --- /dev/null +++ b/.changeset/strange-bottles-sleep.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: don't execute QRLs for elements marked as deleted diff --git a/.changeset/strong-rules-rescue.md b/.changeset/strong-rules-rescue.md new file mode 100644 index 00000000000..6e0e87fd653 --- /dev/null +++ b/.changeset/strong-rules-rescue.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +FEAT: When an error occurs during SSR due to using the browser APIs, show an explanation. diff --git a/.changeset/sweet-bees-punch.md b/.changeset/sweet-bees-punch.md new file mode 100644 index 00000000000..272642dbaaa --- /dev/null +++ b/.changeset/sweet-bees-punch.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': patch +--- + +Implement View Transition on SPA navigation diff --git a/.changeset/sweet-hairs-remember.md b/.changeset/sweet-hairs-remember.md new file mode 100644 index 00000000000..a74444f9d41 --- /dev/null +++ b/.changeset/sweet-hairs-remember.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +FIX: Introduce retry logic for QRL resolution to handle potential promise retries, ensuring robustness in asynchronous operations. diff --git a/.changeset/sweet-socks-whisper.md b/.changeset/sweet-socks-whisper.md new file mode 100644 index 00000000000..714ff27f10b --- /dev/null +++ b/.changeset/sweet-socks-whisper.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +feat: new faster serialization system diff --git a/.changeset/swift-flowers-juggle.md b/.changeset/swift-flowers-juggle.md new file mode 100644 index 00000000000..e13e80886f7 --- /dev/null +++ b/.changeset/swift-flowers-juggle.md @@ -0,0 +1,33 @@ +--- +'@qwik.dev/core': patch +--- + +feat: updated scoped styles prefix to ⚡️ + +# Scoped styles prefix update + +We've updated the `ComponentStylesPrefixContent` from the star symbol (⭐️) to the lightning bolt symbol (⚡️). This prefix is used internally to generate unique CSS class names for components, helping to prevent style collisions. + +**Potential Compatibility Issue (Rare):** + +While this change is expected to be seamless for the vast majority of users, there's a _very small_ possibility of a conflict if your application _directly relies_ on the star symbol (⭐️) for CSS overriding. Specifically, if you're using CSS selectors that include the _literal_ star character (⭐️) as part of a class name (e.g., `.⭐️ComponentName { ... }`), your styles require need to be changed manually to work as expected after this update. + +## How to check if you're affected + +**Search your codebase:** Look for any instances where the star symbol (⭐️) is used as part of a CSS class name or selector. + +## How to fix it if you're affected + +If you find that you are indeed relying on the star symbol (⭐️), you'll need to update your CSS selectors to use the new lightning bolt symbol (⚡️). For example, change `.⭐️ComponentName { ... }` to `.⚡️ComponentName { ... }`. + +```css +/* Example of old, potentially problematic CSS */ +.⭐️MyComponent { + /* ... old styles ... */ +} + +/* Example of updated, correct CSS */ +.⚡️MyComponent { + /* ... updated styles ... */ +} +``` diff --git a/.changeset/tall-rivers-appear.md b/.changeset/tall-rivers-appear.md new file mode 100644 index 00000000000..0bb352eca06 --- /dev/null +++ b/.changeset/tall-rivers-appear.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': minor +--- + +feat: implement route loaders serialization RFC with the correct "data shaken" diff --git a/.changeset/tame-glasses-explain.md b/.changeset/tame-glasses-explain.md new file mode 100644 index 00000000000..071ac0504cb --- /dev/null +++ b/.changeset/tame-glasses-explain.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: inserting new node edge case diff --git a/.changeset/tasty-penguins-ring.md b/.changeset/tasty-penguins-ring.md new file mode 100644 index 00000000000..934b4d63136 --- /dev/null +++ b/.changeset/tasty-penguins-ring.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: prevent reusing projection if is marked as deleted diff --git a/.changeset/thirty-carrots-stand.md b/.changeset/thirty-carrots-stand.md new file mode 100644 index 00000000000..b4b10e451bc --- /dev/null +++ b/.changeset/thirty-carrots-stand.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: allow special characters in key attribute diff --git a/.changeset/thirty-ravens-draw.md b/.changeset/thirty-ravens-draw.md new file mode 100644 index 00000000000..770c934d65f --- /dev/null +++ b/.changeset/thirty-ravens-draw.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: destructured props for inline components diff --git a/.changeset/tiny-berries-bow.md b/.changeset/tiny-berries-bow.md new file mode 100644 index 00000000000..46a4ba2ad84 --- /dev/null +++ b/.changeset/tiny-berries-bow.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: serialize virtual props for DOM elements diff --git a/.changeset/tiny-cows-pick.md b/.changeset/tiny-cows-pick.md new file mode 100644 index 00000000000..fb695bbf761 --- /dev/null +++ b/.changeset/tiny-cows-pick.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: using useOn and useVisibleTask$ in component with primitive value only diff --git a/.changeset/tricky-meals-heal.md b/.changeset/tricky-meals-heal.md new file mode 100644 index 00000000000..28de7b4c58f --- /dev/null +++ b/.changeset/tricky-meals-heal.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': major +--- + +Renamed "Qwik City" to "Qwik Router" and package to "@qwik.dev/router" diff --git a/.changeset/tricky-peaches-buy.md b/.changeset/tricky-peaches-buy.md new file mode 100644 index 00000000000..2e9fb9218b8 --- /dev/null +++ b/.changeset/tricky-peaches-buy.md @@ -0,0 +1,9 @@ +--- +'@qwik.dev/core': minor +--- + +FEAT: `useSerializer$`, `createSerializer$`: Create a Signal holding a custom serializable value. See {@link useSerializer$} for more details. + +`NoSerializeSymbol`: objects that have this symbol will not be serialized. + +`SerializerSymbol`: When defined on an object, this function will get called with the object and is expected to returned a serializable object literal representing this object. Use this to remove data cached data, consolidate things, integrate with other libraries, etc. diff --git a/.changeset/twenty-goats-flow.md b/.changeset/twenty-goats-flow.md new file mode 100644 index 00000000000..64cbd9ca066 --- /dev/null +++ b/.changeset/twenty-goats-flow.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: replacing projection content with null or undefined diff --git a/.changeset/unlucky-dodos-grab.md b/.changeset/unlucky-dodos-grab.md new file mode 100644 index 00000000000..b40439266a8 --- /dev/null +++ b/.changeset/unlucky-dodos-grab.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: convert destructured array's props to signal diff --git a/.changeset/unlucky-olives-knock.md b/.changeset/unlucky-olives-knock.md new file mode 100644 index 00000000000..b0f0a694b06 --- /dev/null +++ b/.changeset/unlucky-olives-knock.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: signal wrapper should not rerender causing missing child error diff --git a/.changeset/warm-camels-remain.md b/.changeset/warm-camels-remain.md new file mode 100644 index 00000000000..051fd35cedd --- /dev/null +++ b/.changeset/warm-camels-remain.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +Expose missing types into `public.d.ts` and fix types uri for internal export inside `package.json` diff --git a/.changeset/wet-bobcats-decide.md b/.changeset/wet-bobcats-decide.md new file mode 100644 index 00000000000..cc756187a94 --- /dev/null +++ b/.changeset/wet-bobcats-decide.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: inflating text nodes from single shared text node diff --git a/.changeset/wicked-pets-chew.md b/.changeset/wicked-pets-chew.md new file mode 100644 index 00000000000..ae5cfc11a97 --- /dev/null +++ b/.changeset/wicked-pets-chew.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: removing text node from shared text node diff --git a/.changeset/wide-boats-pump.md b/.changeset/wide-boats-pump.md new file mode 100644 index 00000000000..73e844b19f4 --- /dev/null +++ b/.changeset/wide-boats-pump.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: async computed correctly handle returning falsy value diff --git a/.changeset/wild-cooks-pay.md b/.changeset/wild-cooks-pay.md new file mode 100644 index 00000000000..f786009b3bb --- /dev/null +++ b/.changeset/wild-cooks-pay.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +feat: new CSR and SSR rendering written from scratch to speed up performance, improve code readability, and make the code easier to understand for new contributors diff --git a/.changeset/witty-balloons-thank.md b/.changeset/witty-balloons-thank.md new file mode 100644 index 00000000000..66b28520792 --- /dev/null +++ b/.changeset/witty-balloons-thank.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: serialize var prop diff --git a/.changeset/young-cameras-hang.md b/.changeset/young-cameras-hang.md new file mode 100644 index 00000000000..3b5a05ff62f --- /dev/null +++ b/.changeset/young-cameras-hang.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: tracking whole store diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0528ae65053..23a60937a3c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -33,5 +33,6 @@ "label": "Serve", "onAutoForward": "openPreview" } - } + }, + "postCreateCommand": "./.devcontainer/post-create.sh" } diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 00000000000..c1cabb69e5d --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +git config --global alias.co checkout +git config --global alias.br branch +git config --global alias.ci commit +git config --global alias.st status +git config --global alias.lg "log --oneline" diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 75678124cb4..6907a92f78d 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -17,7 +17,7 @@ body: - Qwik Rollup / Vite plugin - Qwik Optimizer (rust) - Qwik React - - Qwik City (routing) + - Qwik Router - Starters / CLI - Qwik Playground validations: @@ -53,7 +53,7 @@ body: id: system-info attributes: label: System Info - description: Output of `npx envinfo --system --npmPackages '{vite,undici,typescript,@builder.io/*}' --binaries --browsers` + description: Output of `npx envinfo --system --npmPackages '{vite,typescript,@builder.io/*,@qwik.dev/*}' --binaries --browsers` render: shell placeholder: System, Binaries, Browsers validations: diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml new file mode 100644 index 00000000000..c848f75abe4 --- /dev/null +++ b/.github/workflows/cancel.yml @@ -0,0 +1,15 @@ +# Workaround to cancel workflow runs from forked repositories +name: Cancel +on: + workflow_run: + workflows: ['ci'] + types: + - requested +jobs: + cancel: + runs-on: ubuntu-latest + steps: + - uses: styfle/cancel-workflow-action@0.12.1 + if: github.event_name == 'pull_request' + with: + workflow_id: ${{ github.event.workflow.id }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5642afafcd9..f6dd9ff2e82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,13 +23,12 @@ on: - main - upcoming - next - - qwik-labs - vercelserverless - 'build/**' workflow_dispatch: inputs: disttag: - description: 'Publish "@builder.io/qwik" to NPM using this dist-tag, push the git-tag to the repo and create a GitHub release. The "latest" and "next" dist-tags will use the version number already committed in package.json.' + description: 'Publish "@qwik.dev/core" to NPM using this dist-tag, push the git-tag to the repo and create a GitHub release. The "latest" and "next" dist-tags will use the version number already committed in package.json.' required: true type: choice default: 'dev' @@ -79,7 +78,7 @@ jobs: - name: Set Dist Tag id: set_dist_tag if: | - github.ref == 'refs/heads/upcoming' && ( + github.ref == 'refs/heads/build/v2' && ( github.event_name == 'push' || github.event_name == 'workflow_dispatch' ) @@ -152,14 +151,12 @@ jobs: with: lookup-only: true path: | - packages/qwik-city/lib - packages/qwik-labs/lib - packages/qwik-labs/vite + packages/qwik-router/lib packages/qwik-react/lib packages/eslint-plugin-qwik/dist packages/create-qwik/dist # note that all inputs need to be listed here, including qwik, for correct cache invalidation - key: ${{ hashfiles('qwik-key.txt', 'rust-key.txt', 'packages/qwik-city/**/*', 'packages/qwik-labs/**/*', 'packages/qwik-react/**/*', 'packages/eslint-plugin-qwik/**/*', 'packages/create-qwik/**/*', 'starters/apps/**/*', 'starters/features/**/*', 'starters/adapters/**/*', '!**/*.unit.*') }} + key: ${{ hashfiles('qwik-key.txt', 'rust-key.txt', 'packages/qwik-router/**/*', 'packages/qwik-react/**/*', 'packages/eslint-plugin-qwik/**/*', 'packages/create-qwik/**/*', 'starters/apps/**/*', 'starters/features/**/*', 'starters/adapters/**/*', '!**/*.unit.*') }} - run: 'echo ${{ steps.cache-others.outputs.cache-primary-key }} > others-key.txt' - name: 'check cache: docs' id: cache-docs @@ -224,7 +221,7 @@ jobs: pnpm install --frozen-lockfile - name: 'build: qwik' - run: pnpm build --qwik --set-dist-tag="${{ needs.changes.outputs.disttag }}" + run: pnpm build --qwik --insights --set-dist-tag="${{ needs.changes.outputs.disttag }}" - name: Print Qwik Dist Build continue-on-error: true @@ -275,6 +272,8 @@ jobs: - name: Install Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: clippy,rustfmt - uses: pnpm/action-setup@v4 - name: Setup Node @@ -410,58 +409,40 @@ jobs: if: needs.changes.outputs.build-others == 'true' run: pnpm install - - name: 'build: qwik-city & others' + - name: 'build: qwik-router & others' if: needs.changes.outputs.build-others == 'true' - run: pnpm build --tsc --api --qwikcity --cli --qwiklabs --qwikreact --eslint --set-dist-tag="${{ needs.changes.outputs.disttag }}" + run: pnpm build --tsc --api --qwikrouter --cli --qwikreact --eslint --set-dist-tag="${{ needs.changes.outputs.disttag }}" - name: Save others cache if: needs.changes.outputs.build-others == 'true' uses: actions/cache/save@v4 with: key: ${{ needs.changes.outputs.hash-others }} path: | - packages/qwik-city/lib - packages/qwik-labs/lib - packages/qwik-labs/vite + packages/qwik-router/lib packages/qwik-react/lib packages/eslint-plugin-qwik/dist packages/create-qwik/dist - - name: 'restore: qwik-city & others' + - name: 'restore: qwik-router & others' if: needs.changes.outputs.build-others != 'true' uses: actions/cache/restore@v4 with: path: | - packages/qwik-city/lib - packages/qwik-labs/lib - packages/qwik-labs/vite + packages/qwik-router/lib packages/qwik-react/lib packages/eslint-plugin-qwik/dist packages/create-qwik/dist key: ${{ needs.changes.outputs.hash-others }} - - name: Print QwikCity Lib Build - run: tree -a packages/qwik-city/lib/ - - - name: Upload QwikCity Build Artifacts - uses: actions/upload-artifact@v4 - with: - name: artifact-qwikcity - include-hidden-files: true - path: packages/qwik-city/lib/ - if-no-files-found: error - - - name: Print QwikLabs Lib Build - run: tree -a packages/qwik-labs/lib/ packages/qwik-labs/vite/ + - name: Print QwikRouter Lib Build + run: tree -a packages/qwik-router/lib/ - - name: Upload QwikLabs+React Build Artifacts + - name: Upload QwikRouter Build Artifacts uses: actions/upload-artifact@v4 with: - name: artifact-qwiklabs + name: artifact-qwikrouter include-hidden-files: true - path: | - packages/qwik-labs/lib/ - packages/qwik-labs/vite/ - packages/qwik-labs/package.json + path: packages/qwik-router/lib/ if-no-files-found: error - name: Print qwik-react Lib Build @@ -522,14 +503,12 @@ jobs: - name: Move Distribution Artifacts run: | mv artifact-qwik/* packages/qwik/ - mkdir -p packages/qwik-city/lib/ - mv artifact-qwikcity/* packages/qwik-city/lib/ + mkdir -p packages/qwik-router/lib/ + mv artifact-qwikrouter/* packages/qwik-router/lib/ mkdir -p packages/create-qwik/dist/ mv artifact-create-qwik/* packages/create-qwik/dist/ mkdir -p packages/eslint-plugin-qwik/dist/ mv artifact-eslint-plugin-qwik/* packages/eslint-plugin-qwik/dist/ - mv artifact-qwiklabs/lib packages/qwik-labs/lib - mv artifact-qwiklabs/vite packages/qwik-labs/vite - uses: pnpm/action-setup@v4 - name: Setup Node @@ -582,19 +561,17 @@ jobs: - name: Move Distribution Artifacts run: | mv artifact-qwik/* packages/qwik/ - mkdir -p packages/qwik-city/lib/ - mv artifact-qwikcity/* packages/qwik-city/lib/ + mkdir -p packages/qwik-router/lib/ + mv artifact-qwikrouter/* packages/qwik-router/lib/ mkdir -p packages/create-qwik/dist/ mv artifact-create-qwik/* packages/create-qwik/dist/ mkdir -p packages/eslint-plugin-qwik/dist/ mv artifact-eslint-plugin-qwik/* packages/eslint-plugin-qwik/dist/ - mv artifact-qwiklabs/lib packages/qwik-labs/lib - mv artifact-qwiklabs/vite packages/qwik-labs/vite mv artifact-qwikreact/lib packages/qwik-react/lib - run: pnpm install --frozen-lockfile - name: Build Qwik Docs - run: pnpm run build.packages.docs && echo ok > docs-build-completed.txt + run: pnpm build --tsc-docs && pnpm run build.packages.docs && echo ok > docs-build-completed.txt - name: Save Docs Artifacts uses: actions/upload-artifact@v4 @@ -642,8 +619,8 @@ jobs: - name: Move Distribution Artifacts run: | mv artifact-qwik/* packages/qwik/ - mkdir -p packages/qwik-city/lib/ - mv artifact-qwikcity/* packages/qwik-city/lib/ + mkdir -p packages/qwik-router/lib/ + mv artifact-qwikrouter/* packages/qwik-router/lib/ mkdir -p packages/create-qwik/dist/ mv artifact-create-qwik/* packages/create-qwik/dist/ mkdir -p packages/eslint-plugin-qwik/dist/ @@ -705,8 +682,8 @@ jobs: - name: Move Distribution Artifacts run: | mv artifact-qwik/* packages/qwik/ - mkdir -p packages/qwik-city/lib/ - mv artifact-qwikcity/* packages/qwik-city/lib/ + mkdir -p packages/qwik-router/lib/ + mv artifact-qwikrouter/* packages/qwik-router/lib/ mkdir -p packages/create-qwik/dist/ mv artifact-create-qwik/* packages/create-qwik/dist/ mkdir -p packages/eslint-plugin-qwik/dist/ @@ -723,9 +700,10 @@ jobs: - name: Playwright E2E Integration Tests run: pnpm run test.e2e.integrations.${{ matrix.settings.browser }} --timeout 60000 --retries 7 --workers 1 - - name: Validate Create Qwik Cli - if: matrix.settings.host != 'windows-latest' - run: pnpm cli.validate + # RE-ENABBLE THIS AFTER qwik.dev/ packages are published + # - name: Validate Create Qwik Cli + # if: matrix.settings.host != 'windows-latest' + # run: pnpm cli.validate ############ E2E CLI TEST ############ test-cli-e2e: @@ -764,8 +742,8 @@ jobs: - name: Move Distribution Artifacts run: | mv artifact-qwik/* packages/qwik/ - mkdir -p packages/qwik-city/lib/ - mv artifact-qwikcity/* packages/qwik-city/lib/ + mkdir -p packages/qwik-router/lib/ + mv artifact-qwikrouter/* packages/qwik-router/lib/ mkdir -p packages/create-qwik/dist/ mv artifact-create-qwik/* packages/create-qwik/dist/ mkdir -p packages/eslint-plugin-qwik/dist/ @@ -826,7 +804,7 @@ jobs: if: | always() && github.repository == 'QwikDev/qwik' && ( - github.ref == 'refs/heads/upcoming' || + github.ref == 'refs/heads/build/v2' || needs.test-unit.result == 'success' ) @@ -878,14 +856,12 @@ jobs: - name: Move Distribution Artifacts run: | mv artifact-qwik/* packages/qwik/ - mkdir -p packages/qwik-city/lib/ - mv artifact-qwikcity/* packages/qwik-city/lib/ + mkdir -p packages/qwik-router/lib/ + mv artifact-qwikrouter/* packages/qwik-router/lib/ mkdir -p packages/create-qwik/dist/ mv artifact-create-qwik/* packages/create-qwik/dist/ mkdir -p packages/eslint-plugin-qwik/dist/ mv artifact-eslint-plugin-qwik/* packages/eslint-plugin-qwik/dist/ - mv artifact-qwiklabs/lib packages/qwik-labs/lib - mv artifact-qwiklabs/vite packages/qwik-labs/vite mv artifact-qwikreact/lib packages/qwik-react/lib rm -rf artifact-* @@ -894,16 +870,30 @@ jobs: # Do this before other release steps to avoid # publishing temporary files - name: Create Release Pull Request or Publish to npm - if: github.ref == 'refs/heads/upcoming' + if: github.ref == 'refs/heads/build/v2' id: changesets uses: changesets/action@v1 with: publish: pnpm release + title: V2 Version Packages + branch: build/v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # Delete this after V2 is released + - name: Tag with latest + if: steps.changesets.outputs.published == 'true' + run: | + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > $GITHUB_WORKSPACE/.npmrc + npm dist-tag add @qwik.dev/core@${{ fromJSON(steps.changesets.outputs.publishedPackages)[0].version }} latest + npm dist-tag add @qwik.dev/router@${{ fromJSON(steps.changesets.outputs.publishedPackages)[0].version }} latest + npm dist-tag add @qwik.dev/react@${{ fromJSON(steps.changesets.outputs.publishedPackages)[0].version }} latest + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_USERCONFIG: ${{ github.workspace }}/.npmrc + - name: Fixup package.json files run: pnpm run release.fixup-package-json @@ -915,12 +905,13 @@ jobs: - name: Publish packages for testing if: github.event_name != 'workflow_dispatch' + # TODO: bring back --compact in the package.json release.pkg-pr-new script after first npm public of V2 alpha run: pnpm release.pkg-pr-new env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ############ TRIGGER QWIKCITY E2E TEST ############ - trigger-qwikcity-e2e: + trigger-qwikrouter-e2e: name: Trigger Qwik City E2E runs-on: ubuntu-latest if: github.ref == 'refs/heads/upcoming' @@ -934,7 +925,7 @@ jobs: uses: peter-evans/repository-dispatch@v2 with: token: ${{ secrets.QWIK_API_TOKEN_GITHUB }} - repository: builderIO/qwik-city-e2e + repository: QwikDev/qwik-city-e2e event-type: main-updated ############ Everything is fine ############ diff --git a/.github/workflows/labeling-issues.yml b/.github/workflows/labeling-issues.yml index ca318abdc19..d347912420d 100644 --- a/.github/workflows/labeling-issues.yml +++ b/.github/workflows/labeling-issues.yml @@ -1,4 +1,4 @@ -name: Labling Issues +name: Labeling Issues on: issues: diff --git a/.gitignore b/.gitignore index b9e7fa07098..010cbe2eb15 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,7 @@ test-results sandbox # We need to ignore this because "changesets" will try to replace it -/CHANGELOG.md \ No newline at end of file +/CHANGELOG.md + +# Vitest coverage +packages/coverage/* \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index a3597ecbd10..2bd5a0a98a3 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.11 +22 diff --git a/.prettierignore b/.prettierignore index 4ae0a4de10b..74bb951369d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,4 @@ -**/**.api.md +**/*.api.md **/*.log **/.DS_Store *. @@ -36,8 +36,6 @@ packages/insights/drizzle packages/insights/.netlify packages/insights/scripts packages/insights/**/*.gen.d.ts -packages/qwik-labs/lib-types -packages/qwik-labs/vite # insights cache files **/q-insights.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f8bda961746..6fe6446c816 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,7 +6,6 @@ "ms-playwright.playwright", "rust-lang.rust-analyzer", "ms-azuretools.vscode-docker", - "manucorporat.vermoji", "vadimcn.vscode-lldb", "streetsidesoftware.code-spell-checker", "vitest.explorer" diff --git a/.vscode/launch.json b/.vscode/launch.json index e81b9434684..361b353c847 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -70,7 +70,7 @@ "internalConsoleOptions": "neverOpen", "program": "${workspaceFolder}/./node_modules/vitest/vitest.mjs", "cwd": "${workspaceFolder}", - "args": ["${file}"] + "args": ["--test-timeout", "999999", "--minWorkers", "1", "--maxWorkers", "1", "${file}"] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 093ff9abfc9..3a20cde19ff 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,8 +7,10 @@ "**/cypress/**", "**/.{idea,git,cache,output,temp}/**" ], + "cSpell.words": ["bucketize", "Stringifiable"], "javascript.preferences.autoImportFileExcludePatterns": ["node:test"], "typescript.preferences.preferTypeOnlyAutoImports": true, "typescript.tsdk": "./node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true + "typescript.enablePromptUseWorkspaceTsdk": true, + "makefile.configureOnOpen": false } diff --git a/CONTINUOUS_BUILD.md b/CONTINUOUS_BUILD.md index 605628e2468..5081b175046 100644 --- a/CONTINUOUS_BUILD.md +++ b/CONTINUOUS_BUILD.md @@ -4,9 +4,8 @@ This repo contains build artifacts that are generated as part of the continues b Currently supported artifacts: -- [`@builder.io/qwik`](https://github.com/QwikDev/qwik-build) -- [`@builder.io/qwik-city`](https://github.com/QwikDev/qwik-city-build) -- [`@builder.io/qwik-labs`](https://github.com/QwikDev/qwik-labs-build) +- [`@qwik.dev/core`](https://github.com/QwikDev/qwik-build) +- [`@qwik.dev/router`](https://github.com/QwikDev/qwik-city-build) The build artifact is created if: @@ -27,9 +26,8 @@ To install a specific build artifact change you `package.json` like so (not all ```json { "dependencies": { - "@builder.io/qwik": "github:QwikDev/qwik-build#SHA", - "@builder.io/qwik-city": "github:QwikDev/qwik-city-build#SHA", - "@builder.io/qwik-labs": "github:QwikDev/qwik-labs-build#SHA" + "@qwik.dev/core": "github:QwikDev/qwik-build#SHA", + "@qwik.dev/router": "github:QwikDev/qwik-city-build#SHA" } } ``` @@ -37,13 +35,11 @@ To install a specific build artifact change you `package.json` like so (not all Where `#SHA` is one of the following: - `#SHA` - Install a specific build SHA. You can get the SHA from: - - [`@builder.io/qwik`](https://github.com/QwikDev/qwik-build/commits/) commits - - [`@builder.io/qwik-city`](https://github.com/QwikDev/qwik-city-build/commits/) commits - - [`@builder.io/qwik-labs`](https://github.com/QwikDev/qwik-labs-build/commits/) commits + - [`@qwik.dev/core`](https://github.com/QwikDev/qwik-build/commits/) commits + - [`@qwik.dev/router`](https://github.com/QwikDev/qwik-city-build/commits/) commits - `#build/name` (or `#main`) - Install a specific `build/*` (or `#main`) branch: - - [`@builder.io/qwik`](https://github.com/QwikDev/qwik-build/branches/) branches - - [`@builder.io/qwik-city`](https://github.com/QwikDev/qwik-city-build/branches/) branches - - [`@builder.io/qwik-labs`](https://github.com/QwikDev/qwik-labs-build/branches/) branches + - [`@qwik.dev/core`](https://github.com/QwikDev/qwik-build/branches/) branches + - [`@qwik.dev/router`](https://github.com/QwikDev/qwik-city-build/branches/) branches > NOTE: Package managers will treat any SHA in the lock file which is on the branch as valid, and so they will not auto upgrade to the latest. For this reason this is not recommended. ## Bisect for regression diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2bca58538b3..b7867e26a48 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -166,7 +166,7 @@ If you want to work on the Rust code, use `build.full` instead of `build.local`. ### Fast build -This will build only Qwik and Qwik City and their types. This is not enough to run the docs. +This will build only Qwik and Qwik Router and their types. This is not enough to run the docs. ```shell pnpm build.core @@ -174,10 +174,10 @@ pnpm build.core ### Custom build -Once you have done a full build, the types are built, and you can build just the code you're working on. For qwik and qwik-city, you can do very fast rebuilds with +Once you have done a full build, the types are built, and you can build just the code you're working on. For qwik and qwik-router, you can do very fast rebuilds with ```shell -pnpm build --dev --qwik --qwikcity +pnpm build --dev --qwik --qwikrouter ``` The `--dev` flag skips type checking and generating. @@ -187,9 +187,8 @@ You can run `pnpm build` without parameters to see which flags are available. No - `--tsc`: build types - `--api`: build API docs and type bundles. Requires `--tsc` to have run. - `--build`: Qwik (you'll probably also need `--dev`) -- `--qwikcity`: Qwik City (you'll probably also need `--dev`) +- `--qwikrouter`: Qwik Router (you'll probably also need `--dev`) - `--qwikreact`: Qwik React -- `--qwiklabs`: Qwik Labs - `--eslint`: Eslint plugin E.g. to build only the React integration, you'd run `pnpm build --qwikreact`. @@ -218,7 +217,7 @@ It will build **everything**, including Rust packages and WASM. pnpm build.full ``` -The build output will be written to `packages/qwik/dist`, which will be the directory that is published to [@builder.io/qwik](https://www.npmjs.com/package/@builder.io/qwik). +The build output will be written to `packages/qwik/dist`, which will be the directory that is published to [@qwik.dev/core](https://www.npmjs.com/package/@qwik.dev/core). To update the Rust test snapshots after you've made changes to the Rust code, run `pnpm test.rust.update`. @@ -231,12 +230,12 @@ Assuming qwik is in `../qwik`, run this inside the root of your app: ```shell pnpm link ../qwik/packages/qwik -pnpm link ../qwik/packages/qwik-city +pnpm link ../qwik/packages/qwik-router ``` -Other package managers probably need to first be told about the packages. For example, with `bun` you need to `cd ../qwik/packages/qwik` and `bun link`, repeat for `qwik-city`. Then in your app run `bun link @builder.io/qwik @builder.io/qwik-city`. +Other package managers probably need to first be told about the packages. For example, with `bun` you need to `cd ../qwik/packages/qwik` and `bun link`, repeat for `qwik-router`. Then in your app run `bun link @qwik.dev/core @qwik.dev/router`. -If you can't use package linking, just copy the contents of `packages/qwik` into your projects' `node_modules/@builder.io/qwik` folder, and/or the contents of `packages/qwik-city` into your projects' `node_modules/@builder.io/qwik-city` folder. +If you can't use package linking, just copy the contents of `packages/qwik` into your projects' `node_modules/@qwik.dev/core` folder, and/or the contents of `packages/qwik-router` into your projects' `node_modules/@qwik.dev/router` folder. ### Working on the docs site @@ -318,7 +317,7 @@ For larger PRs, it would really help if you follow these guidelines. - Keep your commits focused and atomic. Each commit should represent a single, coherent change. - If you have commits like `wip lol` or `fixup`, squash them. Use `git rebase -i`. - Commits must follow the format: `type(scope): description` - For example: `feat(qwik-city): confetti animations` or `chore: pnpm api.update` + For example: `feat(qwik-router): confetti animations` or `chore: pnpm api.update` Common types include: diff --git a/Cargo.lock b/Cargo.lock index feddb1ec0a7..51d6bd69ae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -26,37 +41,26 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "ast_node" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94741d66bdda032fcbf33e621b4e3a888d7d11bd3ac4446d82c5593a136936ff" +checksum = "91fb5864e2f5bf9fd9797b94b2dfd1554d4c3092b535008b27d7e15c86675a2f" dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.89", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", + "syn 2.0.90", ] [[package]] @@ -65,6 +69,21 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + [[package]] name = "base64" version = "0.21.7" @@ -95,12 +114,6 @@ dependencies = [ "scoped-tls", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" @@ -139,9 +152,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "shlex", ] @@ -158,30 +171,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex", - "indexmap 1.9.3", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "console" version = "0.15.8" @@ -264,7 +253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -325,7 +314,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -369,7 +358,7 @@ checksum = "8d7ccf961415e7aa17ef93dcb6c2441faaa8e768abe09e659b908089546f74c5" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -402,10 +391,10 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "gimli" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hashbrown" @@ -429,15 +418,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.9" @@ -573,7 +553,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -605,19 +585,9 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" [[package]] name = "indexmap" -version = "1.9.3" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -644,7 +614,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -655,10 +625,11 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -670,15 +641,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.166" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if 1.0.0", "windows-targets", @@ -743,19 +714,29 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "napi" version = "2.16.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "214f07a80874bb96a8433b3cdfc84980d56c7b02e1a0d7ba4ba0db5cef785e2b" dependencies = [ - "bitflags 2.6.0", + "bitflags", "ctor", "napi-derive", "napi-sys", "once_cell", "serde", "serde_json", + "tokio", ] [[package]] @@ -766,23 +747,23 @@ checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" [[package]] name = "napi-derive" -version = "2.16.12" +version = "2.16.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17435f7a00bfdab20b0c27d9c56f58f6499e418252253081bfff448099da31d1" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" dependencies = [ "cfg-if 1.0.0", "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "napi-derive-backend" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "967c485e00f0bf3b1bdbe510a38a4606919cf1d34d9a37ad41f25a81aa077abe" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" dependencies = [ "convert_case", "once_cell", @@ -790,7 +771,7 @@ dependencies = [ "quote", "regex", "semver 1.0.23", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -843,21 +824,24 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] [[package]] -name = "once_cell" -version = "1.20.2" +name = "object" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] [[package]] -name = "os_str_bytes" -version = "6.6.1" +name = "once_cell" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "outref" @@ -878,24 +862,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "path-absolutize" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5" -dependencies = [ - "path-dedot", -] - -[[package]] -name = "path-dedot" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397" -dependencies = [ - "once_cell", -] - [[package]] name = "path-slash" version = "0.2.1" @@ -921,7 +887,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap", ] [[package]] @@ -954,7 +920,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -992,22 +958,22 @@ dependencies = [ [[package]] name = "ptr_meta" -version = "0.1.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" dependencies = [ "ptr_meta_derive", ] [[package]] name = "ptr_meta_derive" -version = "0.1.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -1019,15 +985,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "qwik" -version = "0.1.0" -dependencies = [ - "clap", - "path-absolutize", - "qwik-core", -] - [[package]] name = "qwik-core" version = "0.2.0" @@ -1074,6 +1031,7 @@ dependencies = [ "napi-build", "napi-derive", "qwik-core", + "tokio", ] [[package]] @@ -1123,7 +1081,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.6.0", + "bitflags", ] [[package]] @@ -1161,6 +1119,12 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1264,7 +1228,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1393,20 +1357,14 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.89", + "syn 2.0.90", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "swc_allocator" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cacc28f0ada8e4e31a720dd849ff06864b10e6ab0a1aaa99c06456cfe046af" +checksum = "117d5d3289663f53022ebf157df8a42b3872d7ac759e63abf96b5987b85d4af3" dependencies = [ "bumpalo", "hashbrown 0.14.5", @@ -1417,9 +1375,9 @@ dependencies = [ [[package]] name = "swc_atoms" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7211e5c57ea972f32b8a104d7006c4a68d094ec30c6a73bcd20d4d6c473c7c" +checksum = "151a6feb82b989a087433baca7f6a6eb4fcf83f828c479eecd039c9312d60e10" dependencies = [ "hstr", "once_cell", @@ -1443,9 +1401,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f87a21612a324493fd065e9c6fea960b4031088a213db782e2ca71d2fabb3ec" +checksum = "a521e8120dc0401580864a643b5bffa035c29fc3fc41697c972743d4f008ed22" dependencies = [ "ast_node", "better_scoped_tls", @@ -1475,7 +1433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aa30931f9b26af8edcb4cce605909d15dcfd7577220b22c50a2988f2a53c4c1" dependencies = [ "anyhow", - "indexmap 2.6.0", + "indexmap", "serde", "serde_json", "swc_cached", @@ -1491,16 +1449,16 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "swc_ecma_ast" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bdab7759509c1b37ec77bd9fc231f525b888d9609c2963ce71995da1b27357c" +checksum = "94cf86f17358b93fcfe2876a9f0f7a7ebbff94cd6eaab4c809c7a0da1f4b892e" dependencies = [ - "bitflags 2.6.0", + "bitflags", "is-macro", "num-bigint", "phf", @@ -1514,9 +1472,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "4.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e474f6c2671524dbb179b44a36425cb1a58928f0f7211c45043f0951a1842c5d" +checksum = "fb17e77270860f2a975c546c4609e9fa7ae8dbcf85260497e31af19890645800" dependencies = [ "memchr", "num-bigint", @@ -1541,14 +1499,14 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "swc_ecma_parser" -version = "5.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c5ab8bd4cc4a4956514699c84d1a25cdb5a33f5ec760ec64ce712e973019c9" +checksum = "c2c361b4153905dc088a6bacfaa944b582305cf94fbfcaa9b3aa61a7dd3adbf9" dependencies = [ "either", "new_debug_unreachable", @@ -1568,9 +1526,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3570a4bbd8596ee82ad6cb35e25b3ba57deda503191c104af98cf9994922fdb" +checksum = "85be851a12e79e29bb4a60175a57de46219d71e662b13b67d5fa97d41a000116" dependencies = [ "swc_atoms", "swc_common", @@ -1585,13 +1543,13 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "5.0.1" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb4000822f02b54af0be4f668649fa1e5555f1e3392479d17a277eb81a841f0" +checksum = "2409f9c896f99481d9f609de89c7786ccd0dba008650a4116f1aef7a58926422" dependencies = [ "better_scoped_tls", - "bitflags 2.6.0", - "indexmap 2.6.0", + "bitflags", + "indexmap", "once_cell", "phf", "rustc-hash", @@ -1615,17 +1573,17 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "swc_ecma_transforms_optimization" -version = "5.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63d691ccea03a8eb25f37c7498e7609ad76ca3dc2070b630596e49f0b8fd1f4" +checksum = "350a4965abfada7d5b23b3140896652acc11e110ac042a160bcea5bf8b08d367" dependencies = [ "dashmap", - "indexmap 2.6.0", + "indexmap", "once_cell", "petgraph", "rustc-hash", @@ -1644,13 +1602,13 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "5.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90002fdbe17f10c84cb29a102154a30ee5ad3e7165f0610d18ba8aa3a592924c" +checksum = "4cabf9375cfb71fc0e3d98e07e6fca39a18daa23d4878d8d2daa4c2b6c07b379" dependencies = [ "base64 0.21.7", "dashmap", - "indexmap 2.6.0", + "indexmap", "once_cell", "serde", "sha1", @@ -1669,9 +1627,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "5.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67d5ff2ec723d075db340ac155877fea9607186f179e41ef2116aeef960a2cf" +checksum = "77346c37397fb238f991d6dccc027881caca539628e9a6c629299c7b94bdb08a" dependencies = [ "ryu-js", "serde", @@ -1686,11 +1644,11 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb9a28511d17d1e6c5dfcf209368a1da4a542270c450fba7f27faf22c34df22" +checksum = "527fad9bdb16883782d55291fd3330925b3572f512ef89b3d92a29e2f713fe4f" dependencies = [ - "indexmap 2.6.0", + "indexmap", "num_cpus", "once_cell", "rustc-hash", @@ -1705,9 +1663,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5af5332117aa0424e418556f74e9cee335dc47eb7ae35dddbd9fd65fc01452c" +checksum = "b04c06c1805bda18c27165560f1617a57453feb9fb0638d90839053641af42d4" dependencies = [ "new_debug_unreachable", "num-bigint", @@ -1720,9 +1678,9 @@ dependencies = [ [[package]] name = "swc_ecmascript" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abce4f9d0b85f46cee2867501f8e8b0dc4585ec270027b242f75fc7e9c7381eb" +checksum = "b2e47bc421453ebe3c316c03ef1cbd5e4b563ae9055e6288e0cec3b669f5d3e1" dependencies = [ "swc_ecma_ast", "swc_ecma_codegen", @@ -1740,16 +1698,16 @@ checksum = "e96e15288bf385ab85eb83cff7f9e2d834348da58d0a31b33bdb572e66ee413e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "swc_fast_graph" -version = "5.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f65856acf41991a43d47d19ca947ee34f1152fccc42f048063c64eaf45a8e26" +checksum = "c22e0a0478b1b06610453a97c8371cafa742e371a79aff860ccfbabe1ab160a7" dependencies = [ - "indexmap 2.6.0", + "indexmap", "petgraph", "rustc-hash", "swc_common", @@ -1763,7 +1721,7 @@ checksum = "a509f56fca05b39ba6c15f3e58636c3924c78347d63853632ed2ffcb6f5a0ac7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1789,9 +1747,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1806,7 +1764,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1815,21 +1773,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" - [[package]] name = "tinystr" version = "0.7.6" @@ -1840,6 +1783,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "pin-project-lite", +] + [[package]] name = "tracing" version = "0.1.41" @@ -1859,7 +1812,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1986,7 +1939,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -2008,7 +1961,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2050,15 +2003,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2188,7 +2132,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -2209,7 +2153,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2229,7 +2173,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -2252,5 +2196,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] diff --git a/Cargo.toml b/Cargo.toml index f92a0a02909..cb01148c172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ members = [ "packages/qwik/src/napi", "packages/qwik/src/wasm", - "packages/qwik/src/optimizer/cli", "packages/qwik/src/optimizer/core", ] exclude = ["packages/qwik/src/wasm"] diff --git a/Makefile b/Makefile index 8597758b38b..84f3eab9160 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,9 @@ lint: test: cargo test --manifest-path packages/qwik/src/optimizer/core/Cargo.toml +benchmark: + cargo bench --manifest-path packages/qwik/src/optimizer/core/Cargo.toml + test-update: if ! cargo test --manifest-path packages/qwik/src/optimizer/core/Cargo.toml; then \ cd packages/qwik/src/optimizer/core/src/snapshots/; \ diff --git a/cspell.json b/cspell.json index b5fe53abe90..f7a61b211b7 100644 --- a/cspell.json +++ b/cspell.json @@ -187,8 +187,8 @@ "quicktime", "qvisible", "qwik", - "qwikauth", "qwikcity", + "qwikrouter", "qwikdeps", "qwikdom", "qwikevents", diff --git a/e2e/adapters-e2e/adapters/express/vite.config.ts b/e2e/adapters-e2e/adapters/express/vite.config.ts index bb9ee132041..5c0ac664b36 100644 --- a/e2e/adapters-e2e/adapters/express/vite.config.ts +++ b/e2e/adapters-e2e/adapters/express/vite.config.ts @@ -1,5 +1,5 @@ -import { nodeServerAdapter } from '@builder.io/qwik-city/adapters/node-server/vite'; -import { extendConfig } from '@builder.io/qwik-city/vite'; +import { nodeServerAdapter } from '@qwik.dev/router/adapters/node-server/vite'; +import { extendConfig } from '@qwik.dev/router/vite'; import baseConfig from '../../vite.config'; export default extendConfig(baseConfig, () => { @@ -7,7 +7,7 @@ export default extendConfig(baseConfig, () => { build: { ssr: true, rollupOptions: { - input: ['src/entry.express.tsx', '@qwik-city-plan'], + input: ['src/entry.express.tsx'], }, }, plugins: [nodeServerAdapter({ name: 'express' })], diff --git a/e2e/adapters-e2e/src/components/click-me/click-me.tsx b/e2e/adapters-e2e/src/components/click-me/click-me.tsx index 1141e7d5e9c..229aaaf1049 100644 --- a/e2e/adapters-e2e/src/components/click-me/click-me.tsx +++ b/e2e/adapters-e2e/src/components/click-me/click-me.tsx @@ -1,4 +1,4 @@ -import { component$, useSignal } from '@builder.io/qwik'; +import { component$, useSignal } from '@qwik.dev/core'; // We need to extract the component to see the bug on 1.5.7 export default component$(() => { diff --git a/e2e/adapters-e2e/src/components/router-head/router-head.tsx b/e2e/adapters-e2e/src/components/router-head/router-head.tsx index 44ee2fd5408..849545cf345 100644 --- a/e2e/adapters-e2e/src/components/router-head/router-head.tsx +++ b/e2e/adapters-e2e/src/components/router-head/router-head.tsx @@ -1,5 +1,5 @@ -import { component$ } from '@builder.io/qwik'; -import { useDocumentHead, useLocation } from '@builder.io/qwik-city'; +import { component$ } from '@qwik.dev/core'; +import { useDocumentHead, useLocation } from '@qwik.dev/router'; export const RouterHead = component$(() => { const head = useDocumentHead(); diff --git a/e2e/adapters-e2e/src/entry.dev.tsx b/e2e/adapters-e2e/src/entry.dev.tsx index 4f8f58fa5c3..6efcd8709c6 100644 --- a/e2e/adapters-e2e/src/entry.dev.tsx +++ b/e2e/adapters-e2e/src/entry.dev.tsx @@ -9,7 +9,7 @@ * - More code is transferred to the browser than in SSR mode. * - Optimizer/Serialization/Deserialization code is not exercised! */ -import { render, type RenderOptions } from '@builder.io/qwik'; +import { render, type RenderOptions } from '@qwik.dev/core'; import Root from './root'; export default function (opts: RenderOptions) { diff --git a/e2e/adapters-e2e/src/entry.express.tsx b/e2e/adapters-e2e/src/entry.express.tsx index 9edba3480c8..3cbf0571c3f 100644 --- a/e2e/adapters-e2e/src/entry.express.tsx +++ b/e2e/adapters-e2e/src/entry.express.tsx @@ -7,19 +7,13 @@ * - https://qwik.dev/docs/deployments/node/ * */ -import { createQwikCity, type PlatformNode } from '@builder.io/qwik-city/middleware/node'; +import { createQwikRouter } from '@qwik.dev/router/middleware/node'; import 'dotenv/config'; -import qwikCityPlan from '@qwik-city-plan'; -import { manifest } from '@qwik-client-manifest'; import render from './entry.ssr'; import express from 'express'; import { fileURLToPath } from 'node:url'; import { join } from 'node:path'; -declare global { - interface QwikCityPlatform extends PlatformNode {} -} - // Directories where the static assets are located const distDir = join(fileURLToPath(import.meta.url), '..', '..', 'dist'); const buildDir = join(distDir, 'build'); @@ -28,10 +22,8 @@ const buildDir = join(distDir, 'build'); const PORT = process.env.PORT ?? 3000; // Create the Qwik City Node middleware -const { router, notFound } = createQwikCity({ +const { router, notFound } = createQwikRouter({ render, - qwikCityPlan, - manifest, // getOrigin(req) { // // If deploying under a proxy, you may need to build the origin from the request headers // // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto diff --git a/e2e/adapters-e2e/src/entry.preview.tsx b/e2e/adapters-e2e/src/entry.preview.tsx index 3882063a3f8..35a2b15c5da 100644 --- a/e2e/adapters-e2e/src/entry.preview.tsx +++ b/e2e/adapters-e2e/src/entry.preview.tsx @@ -10,10 +10,9 @@ * - https://vitejs.dev/config/preview-options.html#preview-options * */ -import { createQwikCity } from '@builder.io/qwik-city/middleware/node'; -import qwikCityPlan from '@qwik-city-plan'; +import { createQwikRouter } from '@qwik.dev/router/middleware/node'; // make sure qwikCityPlan is imported before entry import render from './entry.ssr'; /** The default export is the QwikCity adapter used by Vite preview. */ -export default createQwikCity({ render, qwikCityPlan }); +export default createQwikRouter({ render }); diff --git a/e2e/adapters-e2e/src/entry.ssr.tsx b/e2e/adapters-e2e/src/entry.ssr.tsx index cdd6fbe6e59..2b563785e7b 100644 --- a/e2e/adapters-e2e/src/entry.ssr.tsx +++ b/e2e/adapters-e2e/src/entry.ssr.tsx @@ -9,13 +9,11 @@ * - Npm run preview * - Npm run build */ -import { renderToStream, type RenderToStreamOptions } from '@builder.io/qwik/server'; -import { manifest } from '@qwik-client-manifest'; +import { renderToStream, type RenderToStreamOptions } from '@qwik.dev/core/server'; import Root from './root'; export default function (opts: RenderToStreamOptions) { return renderToStream(, { - manifest, ...opts, // Use container attributes to set attributes on the html tag. containerAttributes: { diff --git a/e2e/adapters-e2e/src/root.tsx b/e2e/adapters-e2e/src/root.tsx index 5ac35517c3f..505af2e5942 100644 --- a/e2e/adapters-e2e/src/root.tsx +++ b/e2e/adapters-e2e/src/root.tsx @@ -1,5 +1,5 @@ -import { component$ } from '@builder.io/qwik'; -import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city'; +import { component$ } from '@qwik.dev/core'; +import { QwikRouterProvider, RouterOutlet } from '@qwik.dev/router'; import { RouterHead } from './components/router-head/router-head'; export default component$(() => { @@ -11,17 +11,14 @@ export default component$(() => { */ return ( - + - + - {/* - */} - - + ); }); diff --git a/e2e/adapters-e2e/src/routes/index.tsx b/e2e/adapters-e2e/src/routes/index.tsx index d3585ff0a69..0b0d900960a 100644 --- a/e2e/adapters-e2e/src/routes/index.tsx +++ b/e2e/adapters-e2e/src/routes/index.tsx @@ -1,5 +1,5 @@ -import { component$ } from '@builder.io/qwik'; -import { type DocumentHead } from '@builder.io/qwik-city'; +import { component$ } from '@qwik.dev/core'; +import { type DocumentHead } from '@qwik.dev/router'; import ClickMe from '~/components/click-me/click-me'; export default component$(() => { diff --git a/e2e/adapters-e2e/src/routes/layout.tsx b/e2e/adapters-e2e/src/routes/layout.tsx index d0e76e6d853..b34adeb0db8 100644 --- a/e2e/adapters-e2e/src/routes/layout.tsx +++ b/e2e/adapters-e2e/src/routes/layout.tsx @@ -1,4 +1,4 @@ -import { component$, Slot } from '@builder.io/qwik'; +import { component$, Slot } from '@qwik.dev/core'; export default component$(() => { return ; diff --git a/e2e/adapters-e2e/src/routes/loaders/index.tsx b/e2e/adapters-e2e/src/routes/loaders/index.tsx new file mode 100644 index 00000000000..7aee59171fc --- /dev/null +++ b/e2e/adapters-e2e/src/routes/loaders/index.tsx @@ -0,0 +1,12 @@ +import { component$ } from '@qwik.dev/core'; +import { Link } from '@qwik.dev/router'; + +export default component$(() => { + return ( + <> + + Sub page + + + ); +}); diff --git a/e2e/adapters-e2e/src/routes/loaders/subpage/index.tsx b/e2e/adapters-e2e/src/routes/loaders/subpage/index.tsx new file mode 100644 index 00000000000..41f5c090655 --- /dev/null +++ b/e2e/adapters-e2e/src/routes/loaders/subpage/index.tsx @@ -0,0 +1,18 @@ +import { component$ } from '@qwik.dev/core'; +import { routeLoader$ } from '@qwik.dev/router'; + +export const useTest = routeLoader$(async () => { + return 42; +}); + +export default component$(() => { + const loaded = useTest(); + + return ( + <> +

Sub page

+

This is the sub page.

+

{loaded.value}

+ + ); +}); diff --git a/e2e/adapters-e2e/src/routes/profile/index.tsx b/e2e/adapters-e2e/src/routes/profile/index.tsx index 1f6f835a4b2..e210d13b7c8 100644 --- a/e2e/adapters-e2e/src/routes/profile/index.tsx +++ b/e2e/adapters-e2e/src/routes/profile/index.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export default component$(() => { return ( diff --git a/e2e/adapters-e2e/src/routes/service-worker.ts b/e2e/adapters-e2e/src/routes/service-worker.ts index fb1bb3fd75e..e69de29bb2d 100644 --- a/e2e/adapters-e2e/src/routes/service-worker.ts +++ b/e2e/adapters-e2e/src/routes/service-worker.ts @@ -1,18 +0,0 @@ -/* - * WHAT IS THIS FILE? - * - * The service-worker.ts file is used to have state of the art prefetching. - * https://qwik.dev/qwikcity/prefetching/overview/ - * - * Qwik uses a service worker to speed up your site and reduce latency, ie, not used in the traditional way of offline. - * You can also use this file to add more functionality that runs in the service worker. - */ -import { setupServiceWorker } from '@builder.io/qwik-city/service-worker'; - -setupServiceWorker(); - -addEventListener('install', () => self.skipWaiting()); - -addEventListener('activate', () => self.clients.claim()); - -declare const self: ServiceWorkerGlobalScope; diff --git a/e2e/adapters-e2e/tests/express.spec.ts b/e2e/adapters-e2e/tests/express.spec.ts index 77e1e7ec5cc..90dead2f81e 100644 --- a/e2e/adapters-e2e/tests/express.spec.ts +++ b/e2e/adapters-e2e/tests/express.spec.ts @@ -1,5 +1,12 @@ import { expect, test } from '@playwright/test'; +test.beforeEach(async ({ page }) => { + page.on('console', (msg) => { + // eslint-disable-next-line no-console + console.log(`[browser ${msg.type()}] ${msg.text()}`); + }); +}); + test.describe('Verifying Express Adapter', () => { test('should ignore unknown qdata', async ({ page, request }) => { page.goto('/'); @@ -25,4 +32,15 @@ test.describe('Verifying Express Adapter', () => { await expect(page.getByRole('heading', { name: 'Profile page' })).toBeVisible(); }); + + test('should load loaders context in minified prod mode', async ({ page }) => { + page.goto('/loaders'); + const subpageLink = page.locator('#subpage-link'); + await expect(subpageLink).toBeVisible(); + + await subpageLink.click(); + + await expect(page.getByRole('heading', { name: 'Sub page' })).toBeVisible(); + await expect(page.locator('#subpage-loader-value')).toHaveText('42'); + }); }); diff --git a/e2e/adapters-e2e/tsconfig.json b/e2e/adapters-e2e/tsconfig.json index 634e8d388b9..97ae6fad669 100644 --- a/e2e/adapters-e2e/tsconfig.json +++ b/e2e/adapters-e2e/tsconfig.json @@ -6,7 +6,7 @@ "module": "ES2022", "lib": ["es2022", "DOM", "WebWorker", "DOM.Iterable"], "jsx": "react-jsx", - "jsxImportSource": "@builder.io/qwik", + "jsxImportSource": "@qwik.dev/core", "strict": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, diff --git a/e2e/adapters-e2e/vite.config.ts b/e2e/adapters-e2e/vite.config.ts index d50e689ddd6..3cee74fea64 100644 --- a/e2e/adapters-e2e/vite.config.ts +++ b/e2e/adapters-e2e/vite.config.ts @@ -2,8 +2,8 @@ * This is the base config for vite. When building, the adapter config is used which loads this file * and extends it. */ -import { qwikCity } from '@builder.io/qwik-city/vite'; -import { qwikVite } from '@builder.io/qwik/optimizer'; +import { qwikRouter } from '@qwik.dev/router/vite'; +import { qwikVite } from '@qwik.dev/core/optimizer'; import { defineConfig, type UserConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; import pkg from './package.json'; @@ -17,12 +17,12 @@ const { dependencies = {}, devDependencies = {} } = pkg as any as { errorOnDuplicatesPkgDeps(devDependencies, dependencies); /** - * Note that Vite normally starts from `index.html` but the qwikCity plugin makes start at + * Note that Vite normally starts from `index.html` but the qwikRouter plugin makes start at * `src/entry.ssr.tsx` instead. */ export default defineConfig((): UserConfig => { return { - plugins: [qwikCity(), qwikVite(), tsconfigPaths({ root: '.' })], + plugins: [qwikRouter(), qwikVite(), tsconfigPaths({ root: '.' })], // This tells Vite which dependencies to pre-build in dev mode. optimizeDeps: { // Put problematic deps that break bundling here, mostly those with binaries. diff --git a/e2e/qwik-cli-e2e/README.md b/e2e/qwik-cli-e2e/README.md index 3320cf62cd1..2b360645c50 100644 --- a/e2e/qwik-cli-e2e/README.md +++ b/e2e/qwik-cli-e2e/README.md @@ -10,7 +10,7 @@ Tests can be invoked by running `pnpm run test.e2e.cli`. E2E project does the following internally: -0. Vitest is configured to run a setup function once **PRIOR TO ALL** tests. During the setup `@builder.io/qwik`, `@builder.io/qwik-city` and `eslint-plugin-qwik` packages will be packed with `pnpm pack` Those will be used at a step 2 for every test. Tarballs are located in `temp/tarballs` folder within this repo. It is assumed that packages are built before E2E is executed. +0. Vitest is configured to run a setup function once **PRIOR TO ALL** tests. During the setup `@qwik.dev/core`, `@qwik.dev/router` and `eslint-plugin-qwik` packages will be packed with `pnpm pack` Those will be used at a step 2 for every test. Tarballs are located in `temp/tarballs` folder within this repo. It is assumed that packages are built before E2E is executed. 1. Simulates `npm create qwik` locally using direct command `node packages/create-qwik/create-qwik.cjs playground {outputDir}` @@ -21,7 +21,7 @@ E2E project does the following internally: Note that provided folder should exist. If custom path is used, generated application will not be removed after the test completes, which is helpful for debugging. -2. Uses packed `@builder.io/qwik`, `@builder.io/qwik-city` and `eslint-plugin-qwik` packages to update package.json file of the generated application with `file:path-to-package.tgz`. +2. Uses packed `@qwik.dev/core`, `@qwik.dev/router` and `eslint-plugin-qwik` packages to update package.json file of the generated application with `file:path-to-package.tgz`. 3. Runs actual tests. Please pay attention at the `beforeAll` hook in the spec file @@ -48,4 +48,4 @@ Both `config.cleanupFn();` and `killAllRegisteredProcesses` there are extremely ## Adding new tests -Right now we have only one test file within this project. This means only one test application will be created and used, which is good from the execution time standpoint. If more files are added, it shouldn't potentially be a problem as we have `fileParallelism: false` set in the `vite.config.ts`, which means only one test will be executed at a time. This obviously slows down the execution time, but is safer, because we're working with a real file system. +Right now we have only one test file within this project. This means only one test application will be created and used, which is good from the execution time standpoint. If more files are added, it shouldn't potentially be a problem as we have `fileParallelism: false` set in the `vite.config.mts`, which means only one test will be executed at a time. This obviously slows down the execution time, but is safer, because we're working with a real file system. diff --git a/e2e/qwik-cli-e2e/package.json b/e2e/qwik-cli-e2e/package.json index db74902f146..b0a98646444 100644 --- a/e2e/qwik-cli-e2e/package.json +++ b/e2e/qwik-cli-e2e/package.json @@ -6,7 +6,7 @@ }, "private": true, "scripts": { - "e2e": "vitest run --config=vite.config.ts", - "e2e.watch": "vitest watch --config=vite.config.ts" + "e2e": "vitest run --config=vite.config.mts", + "e2e.watch": "vitest watch --config=vite.config.mts" } } diff --git a/e2e/qwik-cli-e2e/tests/serve.spec.ts b/e2e/qwik-cli-e2e/tests/serve.spec.ts index dce1518d60a..796f914ebfd 100644 --- a/e2e/qwik-cli-e2e/tests/serve.spec.ts +++ b/e2e/qwik-cli-e2e/tests/serve.spec.ts @@ -22,7 +22,6 @@ beforeEach(() => { for (const type of ['empty', 'playground'] as QwikProjectType[]) { describe(`template: ${type}`, () => { beforeAll(() => { - console.log('================================================ scaffolding', type); const config = scaffoldQwikProject(type); global.tmpDir = config.tmpDir; diff --git a/e2e/qwik-cli-e2e/utils/setup.ts b/e2e/qwik-cli-e2e/utils/setup.ts index 05e2fe18a85..84e8975c2fe 100644 --- a/e2e/qwik-cli-e2e/utils/setup.ts +++ b/e2e/qwik-cli-e2e/utils/setup.ts @@ -1,16 +1,16 @@ import { execSync } from 'child_process'; +import { existsSync, writeFileSync } from 'fs'; import { join } from 'path'; import { workspaceRoot } from '.'; -import { existsSync, writeFileSync } from 'fs'; const packageCfg = { - '@builder.io/qwik': { + '@qwik.dev/core': { packagePath: 'packages/qwik', distPath: 'packages/qwik/dist', }, - '@builder.io/qwik-city': { - packagePath: 'packages/qwik-city', - distPath: 'packages/qwik-city/lib', + '@qwik.dev/router': { + packagePath: 'packages/qwik-router', + distPath: 'packages/qwik-router/lib', }, 'eslint-plugin-qwik': { packagePath: 'packages/eslint-plugin-qwik', diff --git a/e2e/qwik-cli-e2e/vite.config.ts b/e2e/qwik-cli-e2e/vite.config.mts similarity index 100% rename from e2e/qwik-cli-e2e/vite.config.ts rename to e2e/qwik-cli-e2e/vite.config.mts diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 0ff0bdcfac2..00000000000 --- a/eslint.config.js +++ /dev/null @@ -1,112 +0,0 @@ -import globals from 'globals'; -import js from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import noOnlyTests from 'eslint-plugin-no-only-tests'; -import { globalIgnores } from 'eslint/config'; -// import { qwikEslint9Plugin } from 'eslint-plugin-qwik'; - -const ignores = [ - '**/.history', - '**/.vscode', - '**/dist', - '**/dist-dev', - '**/lib', - '**/node_modules', - '**/tsc-out', - '**/external', - '**/*.', - '**/*.log', - '**/etc', - '**/target', - '**/temp', - '**/tsdoc-metadata.json', - '**/.DS_Store', - '**/*.mp4', - 'scripts', - '**/server/**/*.js', - '**/*.tsbuildinfo', - 'packages/docs/api', - 'packages/docs/public/repl/repl-sw.js*', - 'packages/docs/src/routes/examples/apps', - 'packages/docs/src/routes/playground/app', - 'packages/docs/src/routes/tutorial', - 'packages/qwik-labs/lib', - 'packages/qwik-labs/lib-types', - 'packages/qwik-labs/vite', - 'packages/insights/drizzle.config.ts', - 'packages/insights/panda.config.ts', - 'starters/apps/base', - 'starters/apps/library', - 'starters/templates', - '**/vite.config.ts', - // packages with eslint.config.mjs - 'packages/qwik-labs', - 'packages/insights', - 'starters', - // eslint.config.* - '**/eslint.config.mjs', - '**/eslint.config.js', -]; - -export default tseslint.config( - globalIgnores(ignores), - js.configs.recommended, - tseslint.configs.recommended, - // qwikEslint9Plugin.configs.recommended, - { - languageOptions: { - globals: { - ...globals.browser, - ...globals.node, - ...globals.es2021, - }, - parserOptions: { - // Needed when using the qwik plugin - // projectService: true, - // tsconfigRootDir: import.meta.dirname, - }, - }, - }, - { - plugins: { - 'no-only-tests': noOnlyTests, - }, - rules: { - 'no-only-tests/no-only-tests': 'error', - }, - name: 'no-only-tests', - }, - { - rules: { - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-inferrable-types': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-empty-interface': 'off', - '@typescript-eslint/no-namespace': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-this-alias': 'off', - '@typescript-eslint/ban-types': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - 'prefer-spread': 'off', - 'no-case-declarations': 'off', - 'no-console': ['error', { allow: ['warn', 'error'] }], - 'no-only-tests/no-only-tests': 'error', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-var-requires': 'off', - curly: 'error', - 'no-new-func': 'error', - '@typescript-eslint/no-empty-object-type': 'off', - '@typescript-eslint/no-unused-expressions': 'off', - '@typescript-eslint/no-unsafe-function-type': 'off', - '@typescript-eslint/no-require-imports': 'off', - '@typescript-eslint/no-wrapper-object-types': 'off', - }, - }, - { - files: ['packages/docs/**/*.{ts,tsx}'], - rules: { - 'no-console': 'off', - }, - } -); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000000..dcc4bcb7897 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,161 @@ +import globals from 'globals'; +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import noOnlyTests from 'eslint-plugin-no-only-tests'; +import { globalIgnores } from 'eslint/config'; +// import { qwikEslint9Plugin } from 'eslint-plugin-qwik'; + +const ignores = [ + '**/.history', + '**/.vscode', + '**/dist', + '**/dist-dev', + '**/lib', + '**/node_modules', + '**/tsc-out', + '**/external', + '**/*.', + '**/*.log', + '**/etc', + '**/target', + '**/temp', + '**/tsdoc-metadata.json', + '**/.DS_Store', + '**/*.mp4', + 'scripts', + '**/server/**/*.js', + '**/*.tsbuildinfo', + 'packages/docs/api', + 'packages/docs/public/repl/repl-sw.js*', + 'packages/docs/src/routes/examples/apps', + 'packages/docs/src/routes/playground/app', + 'packages/docs/src/routes/tutorial', + 'packages/qwik/src/optimizer/core/src/fixtures', + 'packages/qwik/bindings', + 'packages/qwik-labs/lib', + 'packages/qwik-labs/lib-types', + 'packages/qwik-labs/vite', + 'packages/insights/drizzle.config.ts', + 'packages/insights/panda.config.ts', + 'packages/qwik/src/napi', + 'starters/apps/base', + 'starters/apps/library', + 'starters/templates', + '**/vite.config.ts', + // packages with eslint.config.mjs + 'packages/qwik-labs', + 'packages/insights', + 'starters', + // eslint.config.* + '**/eslint.config.mjs', + '**/eslint.config.js', + '.changeset', + 'packages/docs/public/builder', +]; + +export default tseslint.config( + globalIgnores(ignores), + js.configs.recommended, + tseslint.configs.recommended, + // qwikEslint9Plugin.configs.recommended, + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + ...globals.es2021, + }, + parserOptions: { + // Needed when using the qwik plugin + // projectService: true, + // tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + plugins: { + 'no-only-tests': noOnlyTests, + }, + rules: { + 'no-only-tests/no-only-tests': 'error', + }, + name: 'no-only-tests', + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + 'prefer-spread': 'off', + 'no-case-declarations': 'off', + 'no-console': ['error', { allow: ['warn', 'error'] }], + 'no-only-tests/no-only-tests': 'error', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-var-requires': 'off', + curly: 'error', + 'no-new-func': 'error', + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-wrapper-object-types': 'off', + }, + }, + { + files: ['packages/docs/**/*.{ts,tsx}'], + rules: { + 'no-console': 'off', + }, + }, + { + files: ['packages/qwik/src/server/**/*.ts'], + ignores: ['packages/qwik/src/server/qwik-copy.ts'], + rules: { + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['packages/*'], + message: 'Absolute imports are not allowed.', + }, + { + group: ['../**'], + message: 'Relative imports are not allowed.', + }, + ], + }, + ], + 'no-duplicate-imports': 'error', + }, + }, + { + files: ['packages/qwik/src/server/qwik-types.ts'], + rules: { + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['packages/*'], + message: 'Absolute imports are not allowed.', + allowTypeImports: true, + }, + { + group: ['../**'], + message: 'Relative imports are not allowed.', + allowTypeImports: true, + }, + ], + }, + ], + }, + } +); diff --git a/flake.nix b/flake.nix index e3c401a67d8..0c09246dc85 100644 --- a/flake.nix +++ b/flake.nix @@ -30,8 +30,8 @@ bashInteractive gitMinimal - nodejs_20 - corepack_20 + nodejs_22 + corepack_22 # Playwright for the end-to-end tests playwright-driver.browsers diff --git a/package.json b/package.json index dd1a52b305d..3dfb90e7613 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "qwik-monorepo", "version": "0.0.0-read-qwik-package-json", + "author": "Qwik Team", "comments": { - "01": "devDependencies includes reference to @builder.io/qwik: workspace: *. This is needed or e2e tests will fail", + "01": "devDependencies includes reference to @qwik.dev/core: workspace:*. This is needed or e2e tests will fail", "02": " It would be nice to be able to remove this dependency and fix the test.", - "03": "devDependencies can't include reference to @builder.io/qwik-city or e2e test will fail." + "03": "devDependencies can't include reference to @qwik.dev/router or e2e test will fail." }, "config": { "syncpack": { @@ -18,7 +19,7 @@ "dependencies": [ "vite" ], - "pinVersion": "^5" + "pinVersion": ">=5 <8" }, { "label": "use workspace protocol for local packages and allow patch versions (used in e.g. qwik-react)", @@ -39,7 +40,7 @@ "dependencyTypes": [ "dev" ], - "pinVersion": "workspace:^" + "pinVersion": "workspace:*" }, { "label": "Separate prod deps from dev deps", @@ -60,13 +61,6 @@ } ], "semverGroups": [ - { - "label": "Undici should always be * until we remove it", - "dependencies": [ - "undici" - ], - "range": "*" - }, { "label": "use exact version numbers for devDependencies", "dependencyTypes": [ @@ -77,30 +71,11 @@ ] } }, - "contributors": [ - { - "name": "Miško Hevery", - "email": "misko@hevery.com", - "url": "https://twitter.com/mhevery" - }, - { - "name": "Adam Bradley", - "email": "adam@builder.io", - "url": "https://twitter.com/adamdbradley" - }, - { - "name": "Manu Mtz.-Almeida", - "email": "manu@builder.io", - "url": "https://twitter.com/manucorporat" - } - ], "dependencies": { "esbuild-plugin-raw": "^0.1.8" }, "devDependencies": { - "@builder.io/qwik": "workspace:^", - "@builder.io/qwik-city": "workspace:^", - "@changesets/cli": "2.28.1", + "@changesets/cli": "2.29.3", "@changesets/get-github-info": "0.6.0", "@changesets/types": "6.1.0", "@clack/prompts": "0.7.0", @@ -113,12 +88,14 @@ "@node-rs/helper": "1.6.0", "@octokit/action": "6.1.0", "@playwright/test": "1.50.1", + "@qwik.dev/core": "workspace:*", "@qwik.dev/partytown": "0.11.1", + "@qwik.dev/router": "workspace:*", "@types/brotli": "1.3.4", "@types/bun": "1.1.6", "@types/cross-spawn": "6.0.6", "@types/express": "4.17.21", - "@types/node": "20.14.11", + "@types/node": "24.0.4", "@types/path-browserify": "1.0.2", "@types/prompts": "2.4.9", "@types/react": "18.3.3", @@ -126,16 +103,18 @@ "@types/tmp": "0.2.6", "@types/which-pm-runs": "1.0.2", "@vitejs/plugin-basic-ssl": "2.0.0", + "@vitest/coverage-v8": "3.2.4", "all-contributors-cli": "6.26.1", "brotli": "1.3.3", - "create-qwik": "workspace:^", + "create-qwik": "workspace:*", "cross-spawn": "7.0.3", "csstype": "3.1.3", "dotenv": "16.4.5", - "esbuild": "0.25.4", + "esbuild": "0.25.5", "eslint": "9.25.1", + "eslint-plugin-import": "2.29.1", "eslint-plugin-no-only-tests": "3.3.0", - "eslint-plugin-qwik": "workspace:^", + "eslint-plugin-qwik": "workspace:*", "execa": "8.0.1", "express": "4.20.0", "globals": "16.0.0", @@ -144,36 +123,35 @@ "monaco-editor": "0.45.0", "mri": "1.2.0", "path-browserify": "1.0.1", - "prettier": "3.3.3", - "prettier-plugin-jsdoc": "1.3.0", + "prettier": "3.5.3", + "prettier-plugin-jsdoc": "1.3.2", "pretty-quick": "4.0.0", "prompts": "2.4.2", - "rollup": "4.39.0", + "rollup": "4.44.0", "semver": "7.6.3", "simple-git-hooks": "2.11.1", "snoop": "1.0.4", "source-map": "0.7.4", "svgo": "3.3.2", "syncpack": "12.3.3", - "terser": "5.31.3", + "terser": "5.43.1", "tmp": "0.2.3", "tree-kill": "1.2.2", - "tsx": "4.19.1", - "typescript": "5.4.5", + "tsx": "4.19.2", + "typescript": "5.8.3", "typescript-eslint": "8.26.1", - "undici": "*", "vfile": "6.0.2", - "vite": "5.3.5", - "vite-imagetools": "7.0.4", - "vite-plugin-dts": "3.9.1", - "vite-tsconfig-paths": "4.3.2", - "vitest": "2.0.5", + "vite": "7.0.0", + "vite-imagetools": "7.1.0", + "vite-plugin-dts": "4.5.4", + "vite-tsconfig-paths": "5.1.4", + "vitest": "3.2.4", "watchlist": "0.3.1", "which-pm-runs": "1.1.0", "zod": "3.22.4" }, "engines": { - "node": ">=16.8.0 <18.0.0 || >=18.11", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", "npm": "please-use-pnpm", "yarn": "please-use-pnpm", "pnpm": ">=9.0.5" @@ -181,11 +159,13 @@ "packageManager": "pnpm@9.15.5", "pnpm": { "overrides": { - "typescript": "5.4.5", + "prettier": "3.5.3", + "typescript": "5.8.3", "vfile": "6.0.2" }, "patchedDependencies": { - "density-clustering@1.3.0": "patches/density-clustering@1.3.0.patch" + "density-clustering@1.3.0": "patches/density-clustering@1.3.0.patch", + "@auth/qwik": "patches/@auth__qwik.patch" } }, "private": true, @@ -196,20 +176,21 @@ "build.clean": "tsx ./scripts/build-clean.ts", "build.cli": "tsx --require ./scripts/runBefore.ts scripts/index.ts --cli --dev", "build.cli.prod": "tsx --require ./scripts/runBefore.ts scripts/index.ts --cli", - "build.core": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --build --qwikcity --api --platform-binding", + "build.core": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --qwik --insights --qwikrouter --api --platform-binding", + "build.router": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --qwikrouter --api", "build.eslint": "tsx --require ./scripts/runBefore.ts scripts/index.ts --eslint", - "build.full": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --tsc-docs --build --supabaseauthhelpers --api --eslint --qwikcity --qwikworker --qwiklabs --qwikreact --qwikauth --cli --platform-binding --wasm", - "build.local": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --tsc-docs --build --supabaseauthhelpers --api --eslint --qwikcity --qwikworker --qwiklabs --qwikreact --qwikauth --cli --platform-binding-wasm-copy", - "build.only_javascript": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --build --api", + "build.full": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --tsc-docs --qwik --insights --supabaseauthhelpers --api --eslint --qwikrouter --qwikworker --qwikreact --cli --platform-binding --wasm", + "build.local": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --tsc-docs --qwik --insights --supabaseauthhelpers --api --eslint --qwikrouter --qwikworker --qwikreact --cli --platform-binding-wasm-copy", + "build.only_javascript": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --qwik --api", "build.packages.docs": "pnpm -C ./packages/docs/ run build", "build.packages.insights": "pnpm -C ./packages/insights/ run build", "build.platform": "tsx --require ./scripts/runBefore.ts scripts/index.ts --platform-binding", "build.platform.copy": "tsx --require ./scripts/runBefore.ts scripts/index.ts --platform-binding-wasm-copy", - "build.qwik-city": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --qwikcity", - "build.validate": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --build --api --eslint --qwikcity --platform-binding --wasm --validate", - "build.vite": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --build --api --qwikcity --eslint --platform-binding-wasm-copy", + "build.qwik-router": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --qwikrouter", + "build.validate": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --qwik --api --eslint --qwikrouter --platform-binding --wasm --validate", + "build.vite": "tsx --require ./scripts/runBefore.ts scripts/index.ts --tsc --qwik --insights --api --qwikrouter --eslint --platform-binding-wasm-copy", "build.wasm": "tsx --require ./scripts/runBefore.ts scripts/index.ts --wasm", - "build.watch": "tsx --require ./scripts/runBefore.ts scripts/index.ts --build --qwikcity --watch --dev --platform-binding", + "build.watch": "tsx --require ./scripts/runBefore.ts scripts/index.ts --qwik --qwikrouter --watch --dev --platform-binding", "change": "changeset", "cli": "pnpm build.cli && node packages/create-qwik/create-qwik.cjs && tsx --require ./scripts/runBefore.ts scripts/validate-cli.ts --copy-local-qwik-dist", "cli.qwik": "pnpm build.cli && node packages/qwik/qwik-cli.cjs", @@ -221,9 +202,9 @@ "eslint.update": "tsx --require ./scripts/runBefore.ts scripts/eslint-docs.ts", "fmt": "pnpm prettier.fix && pnpm syncpack format", "fmt.staged": "pretty-quick --staged", - "link.dist": "cd packages/qwik && pnpm link --global && cd ../qwik-city && pnpm link --global && cd ../eslint-plugin-qwik && pnpm link --global && cd ../qwik-react && pnpm link --global", - "link.dist.npm": "cd packages/qwik && npm link && cd ../qwik-city && npm link && cd ../eslint-plugin-qwik && npm link && cd ../qwik-react && npm link", - "link.dist.yarn": "cd packages/qwik && yarn link && cd ../qwik-city && yarn link && cd ../eslint-plugin-qwik && yarn link && cd ../qwik-react && yarn link", + "link.dist": "cd packages/qwik && pnpm link --global && cd ../qwik-router && pnpm link --global && cd ../eslint-plugin-qwik && pnpm link --global && cd ../qwik-react && pnpm link --global", + "link.dist.npm": "cd packages/qwik && npm link && cd ../qwik-router && npm link && cd ../eslint-plugin-qwik && npm link && cd ../qwik-react && npm link", + "link.dist.yarn": "cd packages/qwik && yarn link && cd ../qwik-router && yarn link && cd ../eslint-plugin-qwik && yarn link && cd ../qwik-react && yarn link", "lint": "pnpm lint.eslint && pnpm lint.prettier && pnpm lint.rust", "lint.eslint": "eslint --cache \"**/*.ts*\" && pnpm -r --parallel lint", "lint.fix": "eslint --fix \"**/*.ts*\" && pnpm -r --parallel lint.fix && pnpm prettier.fix", @@ -236,7 +217,7 @@ "qwik-push-build-repos": "tsx --require ./scripts/runBefore.ts ./scripts/qwik-push-build-repos.ts", "release": "changeset publish", "release.fixup-package-json": "syncpack fix-mismatches --config syncpack-release-conf.json", - "release.pkg-pr-new": "pnpm dlx pkg-pr-new@^0.0.9 publish --compact --pnpm ./packages/qwik ./packages/qwik-city ./packages/eslint-plugin-qwik ./packages/create-qwik", + "release.pkg-pr-new": "pnpm dlx pkg-pr-new@^0.0.9 publish --pnpm ./packages/qwik ./packages/qwik-router ./packages/eslint-plugin-qwik ./packages/create-qwik", "release.prepare": "pnpm build --prepare-release", "serve": "tsx --require ./scripts/runBefore.ts --inspect --conditions=development starters/dev-server.ts 3300", "serve.debug": "tsx --require ./scripts/runBefore.ts --inspect-brk --conditions=development starters/dev-server.ts 3300", @@ -245,17 +226,19 @@ "test.e2e": "pnpm test.e2e.chromium && pnpm test.e2e.webkit && test.e2e.integrations", "test.e2e.chromium": "playwright test starters --browser=chromium --config starters/playwright.config.ts", "test.e2e.chromium.debug": "PWDEBUG=1 playwright test starters --browser=chromium --config starters/playwright.config.ts", - "test.e2e.city": "playwright test starters/e2e/qwikcity --browser=chromium --config starters/playwright.config.ts", "test.e2e.cli": "pnpm --filter qwik-cli-e2e e2e", "test.e2e.firefox": "playwright test starters --browser=firefox --config starters/playwright.config.ts", "test.e2e.integrations.chromium": "playwright test e2e/adapters-e2e/tests --project=chromium --config e2e/adapters-e2e/playwright.config.ts", "test.e2e.integrations.webkit": "playwright test e2e/adapters-e2e/tests --project=webkit --config e2e/adapters-e2e/playwright.config.ts", + "test.e2e.router": "playwright test starters/e2e/qwikrouter --browser=chromium --config starters/playwright.config.ts", + "test.e2e.run": "tsm scripts/e2e-cli.ts", "test.e2e.webkit": "playwright test starters --browser=webkit --config starters/playwright.config.ts", "test.rust": "make test", + "test.rust.bench": "make benchmark", "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", + "test.vite": "playwright test starters/e2e/qwikrouter --browser=chromium --config starters/playwright.config.ts", "tsc.check": "tsc --noEmit", "tsc.trace": "tsc -p tsconfig.json --traceResolution > tsc.log", "tsc.watch": "tsc --noEmit --watch --preserveWatchOutput", diff --git a/packages/create-qwik/CHANGELOG.md b/packages/create-qwik/CHANGELOG.md index a5a5bdaec59..21fb69c8609 100644 --- a/packages/create-qwik/CHANGELOG.md +++ b/packages/create-qwik/CHANGELOG.md @@ -1,5 +1,37 @@ # create-qwik +## 2.0.0-beta.5 + +## 2.0.0-beta.4 + +## 2.0.0-beta.3 + +## 2.0.0-beta.2 + +## 2.0.0-beta.1 + +## 2.0.0-alpha.10 + +## 2.0.0-alpha.9 + +## 2.0.0-alpha.8 + +## 2.0.0-alpha.7 + +## 2.0.0-alpha.6 + +## 2.0.0-alpha.5 + +## 2.0.0-alpha.4 + +## 2.0.0-alpha.3 + +## 2.0.0-alpha.2 + +## 2.0.0-alpha.1 + +## 2.0.0-alpha.0 + ## 1.14.1 ### Patch Changes @@ -58,4 +90,4 @@ - - built files are now under dist/ or lib/. All tools that respect package export maps should just work. (by [@wmertens](https://github.com/wmertens) in [#6715](https://github.com/QwikDev/qwik/pull/6715)) If you have trouble with Typescript, ensure that you use `moduleResolution: "Bundler"` in your `tsconfig.json`. - - `@builder.io/qwik` no longer depends on `undici` + - `@qwik.dev/core` no longer depends on `undici` diff --git a/packages/create-qwik/package.json b/packages/create-qwik/package.json index 19db8932c3c..e2d317cdef9 100644 --- a/packages/create-qwik/package.json +++ b/packages/create-qwik/package.json @@ -1,8 +1,8 @@ { "name": "create-qwik", "description": "Interactive CLI for create Qwik projects and adding features.", - "version": "1.14.1", - "author": "Builder.io Team", + "version": "2.0.0-beta.5", + "author": "Qwik Team", "bin": "./create-qwik.cjs", "bugs": "https://github.com/QwikDev/qwik/issues", "devDependencies": { @@ -25,7 +25,6 @@ ], "homepage": "https://qwik.dev/", "keywords": [ - "builder.io", "generator", "qwik", "starters", diff --git a/packages/docs/adapters/cloudflare-pages/vite.config.mts b/packages/docs/adapters/cloudflare-pages/vite.config.mts deleted file mode 100644 index 7629ebc667c..00000000000 --- a/packages/docs/adapters/cloudflare-pages/vite.config.mts +++ /dev/null @@ -1,27 +0,0 @@ -import { cloudflarePagesAdapter } from '@builder.io/qwik-city/adapters/cloudflare-pages/vite'; -import { extendConfig } from '@builder.io/qwik-city/vite'; -// @ts-ignore -import baseConfig from '../../vite.config.mts'; - -export default extendConfig(baseConfig, () => { - return { - build: { - ssr: true, - rollupOptions: { - input: ['src/entry.cloudflare-pages.tsx', '@qwik-city-plan'], - }, - minify: false, - }, - plugins: [ - cloudflarePagesAdapter({ - ssg: { - include: ['/', '/*'], - exclude: ['/demo/*', '/shop/*'], - origin: - (process.env.CF_PAGES_BRANCH !== 'main' ? process.env.CF_PAGES_URL : null) ?? - 'https://qwik.builder.io', - }, - }), - ], - }; -}); diff --git a/packages/docs/adapters/cloudflare-pages/vite.config.ts b/packages/docs/adapters/cloudflare-pages/vite.config.ts new file mode 100644 index 00000000000..09178a42746 --- /dev/null +++ b/packages/docs/adapters/cloudflare-pages/vite.config.ts @@ -0,0 +1,26 @@ +import { cloudflarePagesAdapter } from '@qwik.dev/router/adapters/cloudflare-pages/vite'; +import { extendConfig } from '@qwik.dev/router/vite'; +import baseConfig from '../../vite.config'; + +export default extendConfig(baseConfig, () => { + return { + build: { + ssr: true, + rollupOptions: { + input: ['src/entry.cloudflare-pages.tsx'], + }, + minify: false, + }, + plugins: [ + cloudflarePagesAdapter({ + ssg: { + include: ['/', '/*'], + exclude: ['/demo/*', '/shop/*'], + origin: + (process.env.CF_PAGES_BRANCH !== 'main' ? process.env.CF_PAGES_URL : null) ?? + 'https://qwik.dev', + }, + }), + ], + }; +}); diff --git a/packages/docs/check-qwik-build.ts b/packages/docs/check-qwik-build.ts index 69ec5771c56..42c4ffa153d 100644 --- a/packages/docs/check-qwik-build.ts +++ b/packages/docs/check-qwik-build.ts @@ -5,8 +5,9 @@ import fs from 'node:fs'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'url'; -let __dirname = path.dirname(new URL(import.meta.url).pathname); +let __dirname = path.dirname(fileURLToPath(import.meta.url)); const isWindows = process.platform === 'win32'; if (isWindows && __dirname.startsWith('/')) { // in Windows __dirname starts with a / causing errors @@ -18,7 +19,7 @@ if (isWindows && __dirname.startsWith('/')) { } const qwikPkgDir = path.join(__dirname, '..', 'qwik', 'dist'); -if (!fs.existsSync(path.join(qwikPkgDir, 'core.d.ts'))) { +if (!fs.existsSync(path.join(qwikPkgDir, 'core-internal.d.ts'))) { console.warn( `\n\n=== Running 'pnpm run build.local' to generate missing imports for the docs ===\n` ); diff --git a/packages/docs/contributors.ts b/packages/docs/contributors.ts index 6b91e2726ae..4932f381804 100644 --- a/packages/docs/contributors.ts +++ b/packages/docs/contributors.ts @@ -1,4 +1,3 @@ -import { fetch } from 'undici'; import fs from 'node:fs'; import path from 'node:path'; import url from 'node:url'; diff --git a/packages/docs/package.json b/packages/docs/package.json index 414162aec5b..4a4d38d5bd8 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -2,15 +2,12 @@ "name": "qwik-docs", "description": "Qwik Docs Site", "version": "0.0.1", - "author": "Builder.io Team", + "author": "Qwik Team", "bugs": "https://github.com/QwikDev/qwik", "devDependencies": { "@algolia/autocomplete-core": "1.7.4", "@algolia/client-search": "4.14.3", - "@builder.io/qwik": "workspace:^", - "@builder.io/qwik-city": "workspace:^", - "@builder.io/qwik-labs": "workspace:^", - "@builder.io/qwik-react": "workspace:^", + "@builder.io/qwik": "1.14.1", "@emotion/react": "11.13.0", "@emotion/styled": "11.13.0", "@modular-forms/qwik": "0.23.1", @@ -18,7 +15,10 @@ "@mui/system": "5.16.4", "@mui/x-data-grid": "6.20.4", "@qwik-ui/headless": "0.6.7", + "@qwik.dev/core": "workspace:*", "@qwik.dev/partytown": "0.11.1", + "@qwik.dev/react": "workspace:*", + "@qwik.dev/router": "workspace:*", "@shikijs/colorized-brackets": "3.1.0", "@shikijs/rehype": "3.1.0", "@shikijs/transformers": "3.1.0", @@ -37,28 +37,28 @@ "leaflet": "1.9.4", "magic-string": "0.30.11", "openai": "3.3.0", - "prettier": "3.3.3", + "prettier": "3.5.3", "prism-themes": "1.9.0", "prismjs": "1.29.0", "puppeteer": "22.13.1", - "qwik-image": "0.0.10", + "qwik-image": "0.0.16", "react": "18.3.1", "react-dom": "18.3.1", "shiki": "3.1.0", "snarkdown": "2.0.0", "tailwindcss": "4.0.12", - "terser": "5.31.3", + "terser": "5.43.1", "tsm": "2.3.0", - "typescript": "5.4.5", + "typescript": "5.8.3", "undici": "*", "valibot": "0.33.3", - "vite": "5.3.5", + "vite": "7.0.0", "vite-plugin-inspect": "0.8.5", - "vite-tsconfig-paths": "4.3.2", + "vite-tsconfig-paths": "5.1.4", "wrangler": "3.65.1" }, "engines": { - "node": ">=18.11", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", "npm": "please-use-pnpm", "yarn": "please-use-pnpm", "pnpm": ">=8.6.12" @@ -67,11 +67,11 @@ "license": "MIT", "private": true, "scripts": { - "build": "qwik build", + "build": "pnpm build.repl-sw && qwik build", "build.client": "vite build", "build.preview": "NODE_OPTIONS=--max-old-space-size=8192 vite build --ssr src/entry.preview.tsx", - "build.repl-sw": "vite --config vite.config-repl-sw.mts build", - "build.server": "NODE_OPTIONS=--max-old-space-size=8192 vite build -c adapters/cloudflare-pages/vite.config.mts", + "build.repl-sw": "vite --config vite.config-repl-sw build", + "build.server": "NODE_OPTIONS=--max-old-space-size=8192 vite build -c adapters/cloudflare-pages/vite.config", "build.showcase": "pnpm node scripts/showcase.js", "codesandbox.sync": "tsx codesandbox.sync.ts", "contributors": "tsx contributors.ts", @@ -83,5 +83,6 @@ "preview.only": "NODE_DEBUG=net,http node --inspect-brk ../../node_modules/vite/bin/vite.js preview", "preview.wrangler": "wrangler pages dev ./dist --compatibility-flags=nodejs_als", "start": "pnpm dev" - } + }, + "type": "module" } diff --git a/packages/docs/public/_redirects b/packages/docs/public/_redirects index ddc89bbb39f..8c9ff564bc0 100644 --- a/packages/docs/public/_redirects +++ b/packages/docs/public/_redirects @@ -31,6 +31,7 @@ /qwikcity/routing/route-parameters/ /docs/routing/ 308 /qwikcity/routing/error-responses/ /docs/advanced/routing/ 308 /qwikcity/loader/ /docs/route-loader/ 308 +/qwikcity/adaptors/ /docs/deployments/ 308 /qwikcity/layout/overview/ /docs/layout/ 308 /qwikcity/layout/nested/ /docs/advanced/routing/ 308 /qwikcity/layout/grouped/ /docs/advanced/routing/ 308 @@ -62,10 +63,14 @@ /docs/components/resource/ /docs/components/state/ 308 /docs/cookbook/re-exporting-loaders/ /docs/re-exporting-loaders/ 308 +/qwikcity/adaptors/* /docs/deployments/:splat 308 /qwikcity/* /docs/:splat 308 +/qwikrouter/* /docs/:splat 308 /integrations/* /docs/integrations/:splat 308 /deployments/* /docs/deployments/:splat 308 /docs/advanced/i18n/ /docs/integrations/i18n/ 308 /docs/components/inline-components/ /docs/components/overview/ 308 /docs/think-qwik/ /docs/concepts/think-qwik/ 308 +/docs/qwikcity/ /docs/qwikrouter/ 308 +/docs/qwikcity-deprecated-features/ /docs/qwikrouter-deprecated-features/ 308 /docs/env-variables/ /docs/guides/env-variables/ 308 diff --git a/packages/docs/public/_routes.json b/packages/docs/public/_routes.json index f93db4d2d78..bf9f46f8f49 100644 --- a/packages/docs/public/_routes.json +++ b/packages/docs/public/_routes.json @@ -1,23 +1,12 @@ { "version": 1, - "include": ["/*"], + "include": ["/playground/*", "/tutorial/*", "/demo/*"], "exclude": [ - "/chat", - "/examples", - "/guide", - "/tutorial", - "/tutorials", - "/tutorial/hooks/use-client-effect/", - "/docs/overview/", - "/docs/cheat/qwik-react/", - "/docs/cheat/best-practices/", - "/docs/cheat/serialization/", - "/docs/components/lifecycle/", - "/docs/components/projection/", - "/docs/components/resource/", - "/qwikcity/*", - "/integrations/*", - "/deployments/*", - "/repl/*" + "/assets/*", + "/build/*", + "/favicon.ico", + "/manifest.json", + "/robots.txt", + "/service-worker.js" ] } diff --git a/packages/docs/public/docs/qwikcity/README.md b/packages/docs/public/docs/qwikcity/README.md deleted file mode 100644 index 551c630592f..00000000000 --- a/packages/docs/public/docs/qwikcity/README.md +++ /dev/null @@ -1 +0,0 @@ -The images in this folder were created: https://docs.google.com/presentation/d/1HjlWpOpnPAmjdxEV7F2uttjvkWf1FOLvXaWMcxGLs38/ diff --git a/packages/docs/public/icons/qwikCity_and_routing.svg b/packages/docs/public/icons/qwikRouter_and_routing.svg similarity index 100% rename from packages/docs/public/icons/qwikCity_and_routing.svg rename to packages/docs/public/icons/qwikRouter_and_routing.svg diff --git a/packages/docs/scripts/showcase.js b/packages/docs/scripts/showcase.js index cb6f24ec73f..d5c5528f4a5 100644 --- a/packages/docs/scripts/showcase.js +++ b/packages/docs/scripts/showcase.js @@ -145,7 +145,6 @@ async function captureMultipleScreenshots() { } async function getPagespeedData(url) { - const { fetch } = await import('undici'); const requestURL = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent( url )}&key=AIzaSyApBC9gblaCzWrtEBgHnZkd_B37OF49BfM&category=PERFORMANCE&strategy=MOBILE`; diff --git a/packages/docs/src/components/code-block/code-block.tsx b/packages/docs/src/components/code-block/code-block.tsx index 8743318e556..ff6fbaf7600 100644 --- a/packages/docs/src/components/code-block/code-block.tsx +++ b/packages/docs/src/components/code-block/code-block.tsx @@ -1,4 +1,4 @@ -import { component$, useStyles$, type QRL, useVisibleTask$, useSignal } from '@builder.io/qwik'; +import { component$, useStyles$, type QRL, useVisibleTask$, useSignal } from '@qwik.dev/core'; import prismjs from 'prismjs'; // Set to global so that prism language plugins can find it. const _global = @@ -15,7 +15,7 @@ import styles from './code-block.css?inline'; import { CopyCode } from '../copy-code/copy-code-block'; interface CodeBlockProps { path?: string; - language?: 'markup' | 'css' | 'javascript' | 'json' | 'jsx' | 'tsx'; + language?: 'markup' | 'css' | 'javascript' | 'json' | 'jsx' | 'tsx' | 'clike'; code: string; pathInView$?: QRL<(name: string) => void>; observerRootId?: string; diff --git a/packages/docs/src/components/code-sandbox/index.tsx b/packages/docs/src/components/code-sandbox/index.tsx index 23f4f82ccb6..1f13741b1a5 100644 --- a/packages/docs/src/components/code-sandbox/index.tsx +++ b/packages/docs/src/components/code-sandbox/index.tsx @@ -1,7 +1,7 @@ -import { component$, useContext, useStylesScoped$, Slot, useSignal } from '@builder.io/qwik'; -import CSS from './index.css?inline'; +import { component$, Slot, useContext, useSignal, useStylesScoped$ } from '@qwik.dev/core'; import { GlobalStore } from '../../context'; import { EditIcon } from '../svgs/edit-icon'; +import CSS from './index.css?inline'; export default component$<{ src?: string; @@ -96,7 +96,7 @@ function examplePath( } = typeof opts === 'string' ? ({ path: opts } as any) : opts; const newPath = path .replace('/(qwik)/', '/') - .replace('/(qwikcity)/', '/') + .replace('/(qwikrouter)/', '/') .replace('/src/routes/demo', '/demo') .replace(/\/[\w\d]+\.tsx?$/, '/'); diff --git a/packages/docs/src/components/content-nav/content-nav.tsx b/packages/docs/src/components/content-nav/content-nav.tsx index a8ba45639ee..87f1c6ac5bd 100644 --- a/packages/docs/src/components/content-nav/content-nav.tsx +++ b/packages/docs/src/components/content-nav/content-nav.tsx @@ -1,5 +1,5 @@ -import { type ContentMenu, useContent, useLocation } from '@builder.io/qwik-city'; -import { component$, useStyles$ } from '@builder.io/qwik'; +import { component$, useStyles$ } from '@qwik.dev/core'; +import { type ContentMenu, useContent, useLocation } from '@qwik.dev/router'; import styles from './content-nav.css?inline'; export const ContentNav = component$(() => { diff --git a/packages/docs/src/components/contributors/index.tsx b/packages/docs/src/components/contributors/index.tsx index b204a76ac93..1c8d3fc7ad2 100644 --- a/packages/docs/src/components/contributors/index.tsx +++ b/packages/docs/src/components/contributors/index.tsx @@ -1,6 +1,6 @@ -import { component$, useStylesScoped$ } from '@builder.io/qwik'; +import { component$, useStylesScoped$ } from '@qwik.dev/core'; +import { useDocumentHead } from '@qwik.dev/router'; import styles from './contributors.css?inline'; -import { useDocumentHead } from '@builder.io/qwik-city'; export default component$(() => { useStylesScoped$(styles); diff --git a/packages/docs/src/components/copy-code/copy-code-block.tsx b/packages/docs/src/components/copy-code/copy-code-block.tsx index 905f3f27f94..c3f9231c157 100644 --- a/packages/docs/src/components/copy-code/copy-code-block.tsx +++ b/packages/docs/src/components/copy-code/copy-code-block.tsx @@ -1,4 +1,4 @@ -import { component$, useSignal, useStyles$ } from '@builder.io/qwik'; +import { component$, useSignal, useStyles$ } from '@qwik.dev/core'; import { CopyCode as CopyCodeIcon } from '../svgs/copy-code-icon'; import styles from './copy-code.css?inline'; diff --git a/packages/docs/src/components/docsearch/algolia-logo.tsx b/packages/docs/src/components/docsearch/algolia-logo.tsx index 01ad2eec853..3fb4f9012c4 100644 --- a/packages/docs/src/components/docsearch/algolia-logo.tsx +++ b/packages/docs/src/components/docsearch/algolia-logo.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; type AlgoliaLogoTranslations = Partial<{ searchByText: string; diff --git a/packages/docs/src/components/docsearch/context.ts b/packages/docs/src/components/docsearch/context.ts index 06d9e061cbd..9aebd4792ba 100644 --- a/packages/docs/src/components/docsearch/context.ts +++ b/packages/docs/src/components/docsearch/context.ts @@ -1,3 +1,3 @@ -import { createContextId } from '@builder.io/qwik'; +import { createContextId } from '@qwik.dev/core'; export const SearchContext = createContextId('docsearch'); diff --git a/packages/docs/src/components/docsearch/doc-search-button.tsx b/packages/docs/src/components/docsearch/doc-search-button.tsx index 0126a623f98..56be8267506 100644 --- a/packages/docs/src/components/docsearch/doc-search-button.tsx +++ b/packages/docs/src/components/docsearch/doc-search-button.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import { SearchIcon } from './icons/SearchIcon'; export function isAppleDevice() { diff --git a/packages/docs/src/components/docsearch/doc-search-modal.tsx b/packages/docs/src/components/docsearch/doc-search-modal.tsx index 76d36501919..e0889bb7af8 100644 --- a/packages/docs/src/components/docsearch/doc-search-modal.tsx +++ b/packages/docs/src/components/docsearch/doc-search-modal.tsx @@ -5,7 +5,7 @@ import { useContextProvider, useTask$, type Signal, -} from '@builder.io/qwik'; +} from '@qwik.dev/core'; import { MAX_QUERY_SIZE } from './constants'; import { SearchContext } from './context'; import type { DocSearchProps, DocSearchState } from './doc-search'; @@ -19,7 +19,7 @@ import type { DocSearchHit } from './types'; import { identity } from './utils'; import { clearStalled, setStalled } from './utils/stalledControl'; import { AIButton } from './result'; -import { isBrowser } from '@builder.io/qwik'; +import { isBrowser } from '@qwik.dev/core'; export type ModalTranslations = Partial<{ searchBox: SearchBoxTranslations; diff --git a/packages/docs/src/components/docsearch/doc-search.css b/packages/docs/src/components/docsearch/doc-search.css index 8bcc5e72ffd..8b63bd7390e 100644 --- a/packages/docs/src/components/docsearch/doc-search.css +++ b/packages/docs/src/components/docsearch/doc-search.css @@ -22,10 +22,10 @@ --docsearch-hit-background: #fff; --docsearch-hit-shadow: 0 1px 3px 0 #d4d9e1; --docsearch-key-gradient: linear-gradient(-225deg, #d5dbe4, #f8f8f8); - --docsearch-key-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff, - 0 1px 2px 1px rgba(30, 35, 90, 0.4); - --docsearch-key-pressed-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff, - 0 1px 1px 0 rgba(30, 35, 90, 0.4); + --docsearch-key-shadow: + inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff, 0 1px 2px 1px rgba(30, 35, 90, 0.4); + --docsearch-key-pressed-shadow: + inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff, 0 1px 1px 0 rgba(30, 35, 90, 0.4); --docsearch-footer-height: 44px; --docsearch-footer-background: #fff; --docsearch-footer-shadow: 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgba(69, 98, 155, 0.12); @@ -41,12 +41,13 @@ html[data-theme='dark'] { --docsearch-hit-shadow: none; --docsearch-hit-background: #090a11; --docsearch-key-gradient: linear-gradient(-26.5deg, #565872, #31355b); - --docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, - 0 2px 2px 0 rgba(3, 4, 9, 0.3); - --docsearch-key-pressed-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, - 0 1px 1px 0 #0304094d; + --docsearch-key-shadow: + inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgba(3, 4, 9, 0.3); + --docsearch-key-pressed-shadow: + inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 1px 1px 0 #0304094d; --docsearch-footer-background: #1e2136; - --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5), 0 -4px 8px 0 rgba(0, 0, 0, 0.2); + --docsearch-footer-shadow: + inset 0 1px 0 0 rgba(73, 76, 106, 0.5), 0 -4px 8px 0 rgba(0, 0, 0, 0.2); --docsearch-logo-color: #fff; --docsearch-muted-color: #7f8497; } diff --git a/packages/docs/src/components/docsearch/doc-search.tsx b/packages/docs/src/components/docsearch/doc-search.tsx index f76e9a6f536..b269502fee2 100644 --- a/packages/docs/src/components/docsearch/doc-search.tsx +++ b/packages/docs/src/components/docsearch/doc-search.tsx @@ -9,7 +9,7 @@ import { type Signal, $, sync$, -} from '@builder.io/qwik'; +} from '@qwik.dev/core'; import { Modal } from '@qwik-ui/headless'; import type { DocSearchHit, InternalDocSearchHit } from './types'; import { type ButtonTranslations } from './doc-search-button'; diff --git a/packages/docs/src/components/docsearch/error-screen.tsx b/packages/docs/src/components/docsearch/error-screen.tsx index 2cfe0d9213d..a1d58f9e1b6 100644 --- a/packages/docs/src/components/docsearch/error-screen.tsx +++ b/packages/docs/src/components/docsearch/error-screen.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import { ErrorIcon } from './icons/ErrorIcon'; export type ErrorScreenTranslations = Partial<{ diff --git a/packages/docs/src/components/docsearch/hit.tsx b/packages/docs/src/components/docsearch/hit.tsx index e66325f0e53..1aa97dd3507 100644 --- a/packages/docs/src/components/docsearch/hit.tsx +++ b/packages/docs/src/components/docsearch/hit.tsx @@ -1,4 +1,4 @@ -import { component$, Slot } from '@builder.io/qwik'; +import { component$, Slot } from '@qwik.dev/core'; import type { InternalDocSearchHit, StoredDocSearchHit } from './types'; interface HitProps { diff --git a/packages/docs/src/components/docsearch/icons/ControlKeyIcon.tsx b/packages/docs/src/components/docsearch/icons/ControlKeyIcon.tsx index 1ebde32bd05..71f8b207f34 100644 --- a/packages/docs/src/components/docsearch/icons/ControlKeyIcon.tsx +++ b/packages/docs/src/components/docsearch/icons/ControlKeyIcon.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const ControlKeyIcon = component$(() => { return ( diff --git a/packages/docs/src/components/docsearch/icons/ErrorIcon.tsx b/packages/docs/src/components/docsearch/icons/ErrorIcon.tsx index 5b2d48a5eda..c91e2808e04 100644 --- a/packages/docs/src/components/docsearch/icons/ErrorIcon.tsx +++ b/packages/docs/src/components/docsearch/icons/ErrorIcon.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const ErrorIcon = component$(() => { return ( diff --git a/packages/docs/src/components/docsearch/icons/NoResultsIcon.tsx b/packages/docs/src/components/docsearch/icons/NoResultsIcon.tsx index 8bfa33f346e..343efc4032e 100644 --- a/packages/docs/src/components/docsearch/icons/NoResultsIcon.tsx +++ b/packages/docs/src/components/docsearch/icons/NoResultsIcon.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const NoResultsIcon = component$(() => { return ( diff --git a/packages/docs/src/components/docsearch/icons/RecentIcon.tsx b/packages/docs/src/components/docsearch/icons/RecentIcon.tsx index d4333fa1b15..caabeb698ec 100644 --- a/packages/docs/src/components/docsearch/icons/RecentIcon.tsx +++ b/packages/docs/src/components/docsearch/icons/RecentIcon.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const RecentIcon = component$(() => { return ( diff --git a/packages/docs/src/components/docsearch/icons/SelectIcon.tsx b/packages/docs/src/components/docsearch/icons/SelectIcon.tsx index c8eb4022ab9..68f188a6f1d 100644 --- a/packages/docs/src/components/docsearch/icons/SelectIcon.tsx +++ b/packages/docs/src/components/docsearch/icons/SelectIcon.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const SelectIcon = component$(() => { return ( diff --git a/packages/docs/src/components/docsearch/icons/SourceIcon.tsx b/packages/docs/src/components/docsearch/icons/SourceIcon.tsx index ba742b00734..96b56766b0e 100644 --- a/packages/docs/src/components/docsearch/icons/SourceIcon.tsx +++ b/packages/docs/src/components/docsearch/icons/SourceIcon.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const LvlIcon = () => { return ( diff --git a/packages/docs/src/components/docsearch/icons/StarIcon.tsx b/packages/docs/src/components/docsearch/icons/StarIcon.tsx index 76d13168d0c..11bceacef8c 100644 --- a/packages/docs/src/components/docsearch/icons/StarIcon.tsx +++ b/packages/docs/src/components/docsearch/icons/StarIcon.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const StarIcon = component$(() => { return ( diff --git a/packages/docs/src/components/docsearch/no-results-screen.tsx b/packages/docs/src/components/docsearch/no-results-screen.tsx index e1bb75de025..124bc9f1e59 100644 --- a/packages/docs/src/components/docsearch/no-results-screen.tsx +++ b/packages/docs/src/components/docsearch/no-results-screen.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import type { DocSearchState } from './doc-search'; import { NoResultsIcon } from './icons/NoResultsIcon'; diff --git a/packages/docs/src/components/docsearch/result.tsx b/packages/docs/src/components/docsearch/result.tsx index 82d253cdfce..798e630d45f 100644 --- a/packages/docs/src/components/docsearch/result.tsx +++ b/packages/docs/src/components/docsearch/result.tsx @@ -1,10 +1,10 @@ -import { Slot, component$, useContext, useSignal, useStore, useTask$ } from '@builder.io/qwik'; +import { Slot, component$, useContext, useSignal, useStore, useTask$ } from '@qwik.dev/core'; // import { QwikGPT } from '../qwik-gpt'; +import { Link } from '@qwik.dev/router'; import { SearchContext } from './context'; import { AiResultOpenContext, type DocSearchState } from './doc-search'; import { Snippet } from './snippet'; import type { InternalDocSearchHit } from './types'; -import { Link } from '@builder.io/qwik-city'; export const Result = component$( ({ state, item }: { state: DocSearchState; item: InternalDocSearchHit }) => { diff --git a/packages/docs/src/components/docsearch/results-screen.tsx b/packages/docs/src/components/docsearch/results-screen.tsx index a2ea120f1ee..ac08075cd8d 100644 --- a/packages/docs/src/components/docsearch/results-screen.tsx +++ b/packages/docs/src/components/docsearch/results-screen.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import { Result } from './result'; import { removeHighlightTags } from './utils'; import { SelectIcon } from './icons/SelectIcon'; diff --git a/packages/docs/src/components/docsearch/screen-state.tsx b/packages/docs/src/components/docsearch/screen-state.tsx index 4f1c7846c61..d876cac4fea 100644 --- a/packages/docs/src/components/docsearch/screen-state.tsx +++ b/packages/docs/src/components/docsearch/screen-state.tsx @@ -1,4 +1,4 @@ -import { component$, type Signal } from '@builder.io/qwik'; +import { component$, type Signal } from '@qwik.dev/core'; import type { DocSearchState } from './doc-search'; import type { ErrorScreenTranslations } from './error-screen'; import { ErrorScreen } from './error-screen'; diff --git a/packages/docs/src/components/docsearch/search-box.tsx b/packages/docs/src/components/docsearch/search-box.tsx index 89951874a3b..26ebbf63362 100644 --- a/packages/docs/src/components/docsearch/search-box.tsx +++ b/packages/docs/src/components/docsearch/search-box.tsx @@ -1,4 +1,4 @@ -import { component$, useVisibleTask$, useContext, type Signal } from '@builder.io/qwik'; +import { component$, useVisibleTask$, useContext, type Signal } from '@qwik.dev/core'; import { MAX_QUERY_SIZE } from './constants'; import { SearchContext } from './context'; diff --git a/packages/docs/src/components/docsearch/snippet.tsx b/packages/docs/src/components/docsearch/snippet.tsx index 894248a8491..b8e53828ee9 100644 --- a/packages/docs/src/components/docsearch/snippet.tsx +++ b/packages/docs/src/components/docsearch/snippet.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export function getPropertyByPath(object: Record, path: string): any { const parts = path.split('.'); diff --git a/packages/docs/src/components/docsearch/start-screen.tsx b/packages/docs/src/components/docsearch/start-screen.tsx index cef8e0d677a..f6ca1b0611c 100644 --- a/packages/docs/src/components/docsearch/start-screen.tsx +++ b/packages/docs/src/components/docsearch/start-screen.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const StartScreen = component$(() => { return null; diff --git a/packages/docs/src/components/docsearch/stored-searches.ts b/packages/docs/src/components/docsearch/stored-searches.ts index 57026b5937a..e7d2693e87b 100644 --- a/packages/docs/src/components/docsearch/stored-searches.ts +++ b/packages/docs/src/components/docsearch/stored-searches.ts @@ -1,4 +1,4 @@ -import { noSerialize } from '@builder.io/qwik'; +import { noSerialize } from '@qwik.dev/core'; import type { DocSearchHit, StoredDocSearchHit } from './types'; function isLocalStorageSupported() { diff --git a/packages/docs/src/components/docsearch/useTouchEvents.ts b/packages/docs/src/components/docsearch/useTouchEvents.ts index 8f82e8dce6b..2832dee6fe8 100644 --- a/packages/docs/src/components/docsearch/useTouchEvents.ts +++ b/packages/docs/src/components/docsearch/useTouchEvents.ts @@ -1,5 +1,5 @@ import type { AutocompleteApi } from '@algolia/autocomplete-core'; -import { useTask$ } from '@builder.io/qwik'; +import { useTask$ } from '@qwik.dev/core'; interface UseTouchEventsProps { getEnvironmentProps: AutocompleteApi['getEnvironmentProps']; diff --git a/packages/docs/src/components/docsearch/useTrapFocus.ts b/packages/docs/src/components/docsearch/useTrapFocus.ts index 6324e8f1e35..b62d835f3bd 100644 --- a/packages/docs/src/components/docsearch/useTrapFocus.ts +++ b/packages/docs/src/components/docsearch/useTrapFocus.ts @@ -1,4 +1,4 @@ -import { useTask$, type Signal } from '@builder.io/qwik'; +import { useTask$, type Signal } from '@qwik.dev/core'; interface UseTrapFocusProps { containerRef: Signal; diff --git a/packages/docs/src/components/footer/footer.tsx b/packages/docs/src/components/footer/footer.tsx index cc57d010cb1..8dbe9fadd83 100644 --- a/packages/docs/src/components/footer/footer.tsx +++ b/packages/docs/src/components/footer/footer.tsx @@ -1,7 +1,7 @@ -import { component$ } from '@builder.io/qwik'; -import { QwikLogo } from '~/components/svgs/qwik-logo'; +import { component$ } from '@qwik.dev/core'; import { DiscordLogo } from '~/components/svgs/discord-logo'; import { GithubLogo } from '~/components/svgs/github-logo'; +import { QwikLogo } from '~/components/svgs/qwik-logo'; import { TwitterLogo } from '~/components/svgs/twitter-logo'; import { BlueskyLogo } from '~/components/svgs/bluesky-logo'; @@ -9,7 +9,7 @@ const baseUrl = 'https://qwik.dev'; const linkColumns = [ [ { title: 'Docs', href: `${baseUrl}/docs/` }, - { title: 'Qwik City', href: `${baseUrl}/docs/qwikcity/` }, + { title: 'Qwik Router', href: `${baseUrl}/docs/qwikrouter/` }, { title: 'Ecosystem', href: `${baseUrl}/ecosystem/` }, { title: 'Playground', href: `${baseUrl}/playground/` }, ], diff --git a/packages/docs/src/components/header/header.tsx b/packages/docs/src/components/header/header.tsx index 76afad99b0d..80339a95747 100644 --- a/packages/docs/src/components/header/header.tsx +++ b/packages/docs/src/components/header/header.tsx @@ -1,28 +1,28 @@ -import { useLocation } from '@builder.io/qwik-city'; import { component$, - useStyles$, useContext, + useSignal, + useStyles$, useVisibleTask$, type PropsOf, - useSignal, -} from '@builder.io/qwik'; +} from '@qwik.dev/core'; +import { useLocation } from '@qwik.dev/router'; +import { GlobalStore } from '../../context'; import { DocSearch } from '../docsearch/doc-search'; +import { SearchIcon } from '../docsearch/icons/SearchIcon'; import { CloseIcon } from '../svgs/close-icon'; import { DiscordLogo } from '../svgs/discord-logo'; import { GithubLogo } from '../svgs/github-logo'; import { MoreIcon } from '../svgs/more-icon'; import { QwikLogo } from '../svgs/qwik-logo'; import { TwitterLogo } from '../svgs/twitter-logo'; -import styles from './header.css?inline'; -import { GlobalStore } from '../../context'; import { colorSchemeChangeListener, getColorPreference, setPreference, ThemeToggle, } from '../theme-toggle/theme-toggle'; -import { SearchIcon } from '../docsearch/icons/SearchIcon'; +import styles from './header.css?inline'; export const SearchButton = component$>(({ ...props }) => { return ( diff --git a/packages/docs/src/components/on-this-page/on-this-page-more.tsx b/packages/docs/src/components/on-this-page/on-this-page-more.tsx new file mode 100644 index 00000000000..9bee4ade3bc --- /dev/null +++ b/packages/docs/src/components/on-this-page/on-this-page-more.tsx @@ -0,0 +1,65 @@ +import { component$ } from '@qwik.dev/core'; +import { EditIcon } from '../svgs/edit-icon'; +import { AlertIcon } from '../svgs/alert-icon'; +import { ChatIcon } from '../svgs/chat-icon'; +import { GithubLogo } from '../svgs/github-logo'; +import { TwitterLogo } from '../svgs/twitter-logo'; + +type OnThisPageMoreProps = { + theme: 'light' | 'dark' | 'auto'; + editUrl: string; +}; + +export const OnThisPageMore = component$(({ theme, editUrl }) => { + const OnThisPageMore = [ + { + href: editUrl, + text: 'Edit this Page', + icon: EditIcon, + }, + { + href: 'https://github.com/QwikDev/qwik/issues/new/choose', + text: 'Create an issue', + icon: AlertIcon, + }, + { + href: 'https://qwik.dev/chat', + text: 'Join our community', + icon: ChatIcon, + }, + { + href: 'https://github.com/QwikDev/qwik', + text: 'GitHub', + icon: GithubLogo, + }, + { + href: 'https://twitter.com/QwikDev', + text: '@QwikDev', + icon: TwitterLogo, + }, + ]; + return ( + <> +
More
+ + + ); +}); diff --git a/packages/docs/src/components/on-this-page/on-this-page.tsx b/packages/docs/src/components/on-this-page/on-this-page.tsx index 35d7923cc44..dec9022ed66 100644 --- a/packages/docs/src/components/on-this-page/on-this-page.tsx +++ b/packages/docs/src/components/on-this-page/on-this-page.tsx @@ -1,12 +1,8 @@ -import { $, component$, useContext, useOnDocument, useSignal, useStyles$ } from '@builder.io/qwik'; -import { useContent, useLocation } from '@builder.io/qwik-city'; +import { $, component$, useContext, useOnDocument, useSignal, useStyles$ } from '@qwik.dev/core'; +import { useContent, useLocation } from '@qwik.dev/router'; import { GlobalStore } from '../../context'; -import { AlertIcon } from '../svgs/alert-icon'; -import { ChatIcon } from '../svgs/chat-icon'; -import { EditIcon } from '../svgs/edit-icon'; -import { GithubLogo } from '../svgs/github-logo'; -import { TwitterLogo } from '../svgs/twitter-logo'; import styles from './on-this-page.css?inline'; +import { OnThisPageMore } from './on-this-page-more'; const QWIK_GROUP = [ 'components', @@ -30,7 +26,7 @@ const QWIK_ADVANCED_GROUP = [ 'vite', ]; -const QWIKCITY_GROUP = [ +const QWIKROUTER_GROUP = [ 'action', 'api', 'caching', @@ -41,7 +37,7 @@ const QWIKCITY_GROUP = [ 'middleware', 'pages', 'project-structure', - 'qwikcity', + 'qwikrouter', 're-exporting-loaders', 'route-loader', 'routing', @@ -49,7 +45,7 @@ const QWIKCITY_GROUP = [ 'validator', ]; -const QWIKCITY_ADVANCED_GROUP = [ +const QWIKROUTER_ADVANCED_GROUP = [ 'complex-forms', 'content-security-policy', 'menu', @@ -76,13 +72,13 @@ const makeEditPageUrl = (url: string): string => { if (segments[1] == 'advanced') { if (QWIK_ADVANCED_GROUP.includes(segments[2])) { group = '(qwik)'; - } else if (QWIKCITY_ADVANCED_GROUP.includes(segments[2])) { - group = '(qwikcity)'; + } else if (QWIKROUTER_ADVANCED_GROUP.includes(segments[2])) { + group = '(qwikrouter)'; } } else if (QWIK_GROUP.includes(segments[1])) { group = '(qwik)'; - } else if (QWIKCITY_GROUP.includes(segments[1])) { - group = '(qwikcity)'; + } else if (QWIKROUTER_GROUP.includes(segments[1])) { + group = '(qwikrouter)'; } if (group) { @@ -117,39 +113,9 @@ export const OnThisPage = component$(() => { const contentHeadings = headings?.filter((h) => h.level <= 3) || []; const { url } = useLocation(); - const githubEditRoute = makeEditPageUrl(url.pathname); - const editUrl = `https://github.com/QwikDev/qwik/edit/main/packages/docs/src/routes/${githubEditRoute}/index.mdx`; - const OnThisPageMore = [ - { - href: editUrl, - text: 'Edit this Page', - icon: EditIcon, - }, - { - href: 'https://github.com/QwikDev/qwik/issues/new/choose', - text: 'Create an issue', - icon: AlertIcon, - }, - { - href: 'https://qwik.dev/chat', - text: 'Join our community', - icon: ChatIcon, - }, - { - href: 'https://github.com/QwikDev/qwik', - text: 'GitHub', - icon: GithubLogo, - }, - { - href: 'https://twitter.com/QwikDev', - text: '@QwikDev', - icon: TwitterLogo, - }, - ]; - const useActiveItem = (itemIds: string[]) => { const activeId = useSignal(null); useOnDocument( @@ -214,29 +180,9 @@ export const OnThisPage = component$(() => { ))} + ) : null} - -
More
-
    - {OnThisPageMore.map((el, index) => { - return ( -
  • - - - {el.text} - -
  • - ); - })} -
); }); diff --git a/packages/docs/src/components/package-manager-tabs/index.tsx b/packages/docs/src/components/package-manager-tabs/index.tsx index 50f0f6caca2..25c1d7830ce 100644 --- a/packages/docs/src/components/package-manager-tabs/index.tsx +++ b/packages/docs/src/components/package-manager-tabs/index.tsx @@ -1,5 +1,5 @@ -import { Slot, component$, useContext, useSignal, $, type PropsOf } from '@builder.io/qwik'; import { Tabs } from '@qwik-ui/headless'; +import { $, Slot, component$, useContext, useSignal, type PropsOf } from '@qwik.dev/core'; import { GlobalStore } from '~/context'; const pkgManagers = ['pnpm', 'npm', 'yarn', 'bun'] as const; diff --git a/packages/docs/src/components/panel-toggle/panel-toggle.tsx b/packages/docs/src/components/panel-toggle/panel-toggle.tsx index 1e1b4f04336..85b3157de86 100644 --- a/packages/docs/src/components/panel-toggle/panel-toggle.tsx +++ b/packages/docs/src/components/panel-toggle/panel-toggle.tsx @@ -1,4 +1,4 @@ -import { component$, useStyles$ } from '@builder.io/qwik'; +import { component$, useStyles$ } from '@qwik.dev/core'; import styles from './panel-toggle.css?inline'; export interface PanelToggleProps { diff --git a/packages/docs/src/components/qwik-gpt/gpt.md b/packages/docs/src/components/qwik-gpt/gpt.md index 8c507016c64..c061c8cd268 100644 --- a/packages/docs/src/components/qwik-gpt/gpt.md +++ b/packages/docs/src/components/qwik-gpt/gpt.md @@ -10,7 +10,7 @@ - Content projection is done by the `` component. Slots can be named, and can be projected into using the `q:slot` attribute. ```tsx -import { component$, $, useSignal, useVisibleTask$ } from '@builder.io/qwik'; +import { component$, $, useSignal, useVisibleTask$ } from '@qwik.dev/core'; import US_PRESIDENTS from './us-presidents.json'; import { MyOtherComponent } from './my-other-component'; @@ -87,8 +87,8 @@ Qwik comes with a file-based router, which is similar to Next.js. The router is To link to other routes, you can use the `Link` component, it is like `` but allows for SPA navigation. ```tsx title="src/routes/user/[userID]/index.tsx" -import { component$ } from '@builder.io/qwik'; -import { routeLoader$, useLocation, Link } from '@builder.io/qwik-city'; +import { component$ } from '@qwik.dev/core'; +import { routeLoader$, useLocation, Link } from '@qwik.dev/router'; export const useUserData = routeLoader$(async (requestEvent) => { const { userID } = requestEvent.params; diff --git a/packages/docs/src/components/qwik-gpt/index.tsx b/packages/docs/src/components/qwik-gpt/index.tsx index be0a8a590d9..14904d8c3a9 100644 --- a/packages/docs/src/components/qwik-gpt/index.tsx +++ b/packages/docs/src/components/qwik-gpt/index.tsx @@ -1,7 +1,7 @@ -import { component$, useComputed$, useSignal } from '@builder.io/qwik'; +import { component$, useComputed$, useSignal } from '@qwik.dev/core'; // import { qwikGPT, rateResponse } from './search'; import { CodeBlock } from '../code-block/code-block'; -// import { isBrowser } from '@builder.io/qwik'; +// import { isBrowser } from '@qwik.dev/core'; import snarkdown from 'snarkdown'; const snarkdownEnhanced = (md: string) => { diff --git a/packages/docs/src/components/qwik-gpt/search.tsx b/packages/docs/src/components/qwik-gpt/search.tsx index 416e1325e97..3a1187ce3a8 100644 --- a/packages/docs/src/components/qwik-gpt/search.tsx +++ b/packages/docs/src/components/qwik-gpt/search.tsx @@ -1,4 +1,4 @@ -// import { server$ } from '@builder.io/qwik-city'; +// import { server$ } from '@qwik.dev/router'; // import { createClient } from '@supabase/supabase-js'; import gpt from './gpt.md?raw'; // import { chatCompletion } from './streaming-gpt'; diff --git a/packages/docs/src/components/router-head/router-head.tsx b/packages/docs/src/components/router-head/router-head.tsx index b9b512370d3..a84d5c871ed 100644 --- a/packages/docs/src/components/router-head/router-head.tsx +++ b/packages/docs/src/components/router-head/router-head.tsx @@ -1,8 +1,8 @@ -import { component$ } from '@builder.io/qwik'; -import { useDocumentHead, useLocation } from '@builder.io/qwik-city'; +import { component$ } from '@qwik.dev/core'; +import { useDocumentHead, useLocation } from '@qwik.dev/router'; import { Social } from './social'; -import { Vendor } from './vendor'; import { ThemeScript } from './theme-script'; +import { Vendor } from './vendor'; export const RouterHead = component$(() => { const { url } = useLocation(); diff --git a/packages/docs/src/components/sidebar/sidebar.tsx b/packages/docs/src/components/sidebar/sidebar.tsx index 7b78551beab..97ad33e82be 100644 --- a/packages/docs/src/components/sidebar/sidebar.tsx +++ b/packages/docs/src/components/sidebar/sidebar.tsx @@ -1,5 +1,5 @@ -import { component$, sync$, useContext, useOnDocument, useStyles$ } from '@builder.io/qwik'; -import { type ContentMenu, useContent, useLocation, routeLoader$ } from '@builder.io/qwik-city'; +import { component$, sync$, useContext, useOnDocument, useStyles$ } from '@qwik.dev/core'; +import { routeLoader$, useContent, useLocation, type ContentMenu } from '@qwik.dev/router'; import { GlobalStore } from '../../context'; import { CloseIcon } from '../svgs/close-icon'; import styles from './sidebar.css?inline'; @@ -11,7 +11,7 @@ export const useMarkdownItems = routeLoader$(async () => { return [ k .replace('../../routes', '') - .replace('(qwikcity)/', '') + .replace('(qwikrouter)/', '') .replace('(qwik)/', '') .replaceAll(/([()])/g, '') .replace('index.mdx', '') @@ -65,7 +65,7 @@ export const SideBar = component$((props: { allOpen?: boolean }) => { const { menu } = useContent(); const { url } = useLocation(); const markdownItems = useMarkdownItems(); - const allOpen = url.pathname.startsWith('/qwikcity/') || props.allOpen; + const allOpen = url.pathname.startsWith('/qwikrouter/') || props.allOpen; useOnDocument( 'DOMContentLoaded', diff --git a/packages/docs/src/components/sponsors/sponsors.tsx b/packages/docs/src/components/sponsors/sponsors.tsx index 271ad3014c3..e785a887ad8 100644 --- a/packages/docs/src/components/sponsors/sponsors.tsx +++ b/packages/docs/src/components/sponsors/sponsors.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import { BuilderLogo } from '../svgs/builder-logo'; export const Sponsors = component$(() => { diff --git a/packages/docs/src/components/svgs/bluesky-logo.tsx b/packages/docs/src/components/svgs/bluesky-logo.tsx index a7ee106ba9a..45bae9ac630 100644 --- a/packages/docs/src/components/svgs/bluesky-logo.tsx +++ b/packages/docs/src/components/svgs/bluesky-logo.tsx @@ -1,9 +1,11 @@ +import { component$ } from '@qwik.dev/core'; + interface Props { width: number; height: number; } -export const BlueskyLogo = ({ width, height }: Props) => ( +export const BlueskyLogo = component$(({ width, height }: Props) => ( ( d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805zm36.254 0C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745z" /> -); +)); diff --git a/packages/docs/src/components/svgs/discord-logo.tsx b/packages/docs/src/components/svgs/discord-logo.tsx index c038b6dc69a..e5ecf5ab144 100644 --- a/packages/docs/src/components/svgs/discord-logo.tsx +++ b/packages/docs/src/components/svgs/discord-logo.tsx @@ -1,11 +1,12 @@ -import type { PropsOf } from '@builder.io/qwik'; +import type { PropsOf } from '@qwik.dev/core'; +import { component$ } from '@qwik.dev/core'; type DiscordLogoProps = { width: number; height: number; } & PropsOf<'svg'>; -export const DiscordLogo = ({ width, height }: DiscordLogoProps) => ( +export const DiscordLogo = component$(({ width, height }: DiscordLogoProps) => ( ( -); +)); diff --git a/packages/docs/src/components/svgs/github-logo.tsx b/packages/docs/src/components/svgs/github-logo.tsx index b1b22df0519..6b50def1c8a 100644 --- a/packages/docs/src/components/svgs/github-logo.tsx +++ b/packages/docs/src/components/svgs/github-logo.tsx @@ -1,9 +1,11 @@ +import { component$ } from '@qwik.dev/core'; + interface GithubLogoProps { width: number; height: number; } -export const GithubLogo = ({ width, height }: GithubLogoProps) => ( +export const GithubLogo = component$(({ width, height }: GithubLogoProps) => ( ( > -); +)); diff --git a/packages/docs/src/components/svgs/qwik-logo.tsx b/packages/docs/src/components/svgs/qwik-logo.tsx index 5dc3bafd85b..164bf872876 100644 --- a/packages/docs/src/components/svgs/qwik-logo.tsx +++ b/packages/docs/src/components/svgs/qwik-logo.tsx @@ -1,4 +1,4 @@ -import { component$, type PropsOf } from '@builder.io/qwik'; +import { component$, type PropsOf } from '@qwik.dev/core'; import QwikUwu from '~/media/images/qwik-uwu.webp?url'; type QwikLogoProps = { width: number; diff --git a/packages/docs/src/components/svgs/twitter-logo.tsx b/packages/docs/src/components/svgs/twitter-logo.tsx index 3cfd2ef1635..a1fbad26459 100644 --- a/packages/docs/src/components/svgs/twitter-logo.tsx +++ b/packages/docs/src/components/svgs/twitter-logo.tsx @@ -1,9 +1,11 @@ +import { component$ } from '@qwik.dev/core'; + interface TwitterLogoProps { width: number; height: number; } -export const TwitterLogo = ({ width, height }: TwitterLogoProps) => ( +export const TwitterLogo = component$(({ width, height }: TwitterLogoProps) => ( ( > -); +)); diff --git a/packages/docs/src/components/theme-toggle/sun-and-moon.tsx b/packages/docs/src/components/theme-toggle/sun-and-moon.tsx index 6aeb969543e..bf78e12a871 100644 --- a/packages/docs/src/components/theme-toggle/sun-and-moon.tsx +++ b/packages/docs/src/components/theme-toggle/sun-and-moon.tsx @@ -1,4 +1,4 @@ -import { component$, useStyles$ } from '@builder.io/qwik'; +import { component$, useStyles$ } from '@qwik.dev/core'; import sunAndMoonStyles from './sun-and-moon.css?inline'; export const SunAndMoon = component$(() => { diff --git a/packages/docs/src/components/theme-toggle/theme-toggle.tsx b/packages/docs/src/components/theme-toggle/theme-toggle.tsx index 2b8433c5f75..ebda4cf742c 100644 --- a/packages/docs/src/components/theme-toggle/theme-toggle.tsx +++ b/packages/docs/src/components/theme-toggle/theme-toggle.tsx @@ -1,4 +1,4 @@ -import { component$, event$, useContext, useStyles$ } from '@builder.io/qwik'; +import { component$, event$, useContext, useStyles$ } from '@qwik.dev/core'; import { SunAndMoon } from './sun-and-moon'; import { themeStorageKey } from '../router-head/theme-script'; import themeToggle from './theme-toggle.css?inline'; diff --git a/packages/docs/src/context.ts b/packages/docs/src/context.ts index 13eb9adca70..85f515cd45c 100644 --- a/packages/docs/src/context.ts +++ b/packages/docs/src/context.ts @@ -1,4 +1,4 @@ -import { createContextId } from '@builder.io/qwik'; +import { createContextId } from '@qwik.dev/core'; import type { ThemePreference } from './components/theme-toggle/theme-toggle'; export interface SiteStore { diff --git a/packages/docs/src/entry.cloudflare-pages.tsx b/packages/docs/src/entry.cloudflare-pages.tsx index 260810ae96f..f5fa7250f77 100644 --- a/packages/docs/src/entry.cloudflare-pages.tsx +++ b/packages/docs/src/entry.cloudflare-pages.tsx @@ -1,7 +1,6 @@ -import { createQwikCity } from '@builder.io/qwik-city/middleware/cloudflare-pages'; -import qwikCityPlan from '@qwik-city-plan'; +import { createQwikRouter } from '@qwik.dev/router/middleware/cloudflare-pages'; import render from './entry.ssr'; -const fetch = createQwikCity({ render, qwikCityPlan }); +const fetch = createQwikRouter({ render }); export { fetch }; diff --git a/packages/docs/src/entry.dev.tsx b/packages/docs/src/entry.dev.tsx index 70bf4d77393..4c2c7c0b8d7 100644 --- a/packages/docs/src/entry.dev.tsx +++ b/packages/docs/src/entry.dev.tsx @@ -1,4 +1,4 @@ -import { render, type RenderOptions } from '@builder.io/qwik'; +import { render, type RenderOptions } from '@qwik.dev/core'; import Root from './root'; /** diff --git a/packages/docs/src/entry.preview.tsx b/packages/docs/src/entry.preview.tsx index 7c4a0028bf2..3c175fcf955 100644 --- a/packages/docs/src/entry.preview.tsx +++ b/packages/docs/src/entry.preview.tsx @@ -1,6 +1,5 @@ -import { createQwikCity } from '@builder.io/qwik-city/middleware/node'; -import qwikCityPlan from '@qwik-city-plan'; +import { createQwikRouter } from '@qwik.dev/router/middleware/node'; import render from './entry.ssr'; -/** The default export is the QwikCity adapter used by Vite preview. */ -export default createQwikCity({ render, qwikCityPlan }); +/** The default export is the QwikRouter adapter used by Vite preview. */ +export default createQwikRouter({ render }); diff --git a/packages/docs/src/entry.ssr.tsx b/packages/docs/src/entry.ssr.tsx index 0c9922c47d2..7cc6b404eb0 100644 --- a/packages/docs/src/entry.ssr.tsx +++ b/packages/docs/src/entry.ssr.tsx @@ -1,5 +1,5 @@ -import type { PreloaderOptions, RenderToStreamOptions } from '@builder.io/qwik/server'; -import { renderToStream } from '@builder.io/qwik/server'; +import type { PreloaderOptions, RenderToStreamOptions } from '@qwik.dev/core/server'; +import { renderToStream } from '@qwik.dev/core/server'; import Root from './root'; // You can pass these as query parameters, as well as `preloadDebug` diff --git a/packages/docs/src/global.css b/packages/docs/src/global.css index 3864df2d0a4..7d5a6917763 100644 --- a/packages/docs/src/global.css +++ b/packages/docs/src/global.css @@ -11,8 +11,9 @@ font-weight: 400; font-display: optional; src: url(./fonts/poppins-400.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, - U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, + U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* latin */ @@ -22,8 +23,9 @@ font-weight: 500; font-display: optional; src: url(./fonts/poppins-500.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, - U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, + U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* latin */ @@ -33,8 +35,9 @@ font-weight: 700; font-display: optional; src: url(./fonts/poppins-700.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, - U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, + U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } .docs article pre code[data-language='tsx'] { diff --git a/packages/docs/src/media/docs/qwikcity/routing-files.png b/packages/docs/src/media/docs/qwikrouter/routing-files.png similarity index 100% rename from packages/docs/src/media/docs/qwikcity/routing-files.png rename to packages/docs/src/media/docs/qwikrouter/routing-files.png diff --git a/packages/docs/src/media/docs/qwikcity/routing.png b/packages/docs/src/media/docs/qwikrouter/routing.png similarity index 100% rename from packages/docs/src/media/docs/qwikcity/routing.png rename to packages/docs/src/media/docs/qwikrouter/routing.png diff --git a/packages/docs/src/media/icons/qwikCity_and_routing.svg b/packages/docs/src/media/icons/qwikRouter_and_routing.svg similarity index 100% rename from packages/docs/src/media/icons/qwikCity_and_routing.svg rename to packages/docs/src/media/icons/qwikRouter_and_routing.svg diff --git a/packages/docs/src/repl/bundled.tsx b/packages/docs/src/repl/bundled.tsx index bf56057f5c6..47ada122d40 100644 --- a/packages/docs/src/repl/bundled.tsx +++ b/packages/docs/src/repl/bundled.tsx @@ -1,4 +1,4 @@ -import { version as qwikVersion } from '@builder.io/qwik'; +import { version as qwikVersion } from '@qwik.dev/core'; import type { PkgUrls } from './types'; import prettierPkgJson from 'prettier/package.json'; @@ -8,21 +8,24 @@ import prettierStandaloneJs from '../../node_modules/prettier/standalone.js?raw- import terserPkgJson from 'terser/package.json'; import terserJs from '../../node_modules/terser/dist/bundle.min.js?raw-source'; -import qBuild from '../../node_modules/@builder.io/qwik/dist/build/index.d.ts?raw-source'; -import qCoreCjs from '../../node_modules/@builder.io/qwik/dist/core.cjs?raw-source'; -import qCoreDts from '../../node_modules/@builder.io/qwik/dist/core.d.ts?raw-source'; -import qCoreMinMjs from '../../node_modules/@builder.io/qwik/dist/core.min.mjs?raw-source'; -import qCoreMjs from '../../node_modules/@builder.io/qwik/dist/core.mjs?raw-source'; -import qOptimizerCjs from '../../node_modules/@builder.io/qwik/dist/optimizer.cjs?raw-source'; -import qPreloaderMjs from '../../node_modules/@builder.io/qwik/dist/preloader.mjs?raw-source'; -// we use the debug version for the repl so it's understandable -import qQwikLoaderJs from '../../node_modules/@builder.io/qwik/dist/qwikloader.debug.js?raw-source'; -import qServerCjs from '../../node_modules/@builder.io/qwik/dist/server.cjs?raw-source'; -import qServerDts from '../../node_modules/@builder.io/qwik/dist/server.d.ts?raw-source'; -import qWasmCjs from '../../node_modules/@builder.io/qwik/bindings/qwik.wasm.cjs?raw-source'; -import qWasmBinUrl from '../../node_modules/@builder.io/qwik/bindings/qwik_wasm_bg.wasm?raw-source'; +import qWasmCjs from '../../node_modules/@qwik.dev/core/bindings/qwik.wasm.cjs?raw-source'; +import qWasmBinUrl from '../../node_modules/@qwik.dev/core/bindings/qwik_wasm_bg.wasm?raw-source'; +import qBuild from '../../node_modules/@qwik.dev/core/dist/build/index.d.ts?raw-source'; +import qCoreCjs from '../../node_modules/@qwik.dev/core/dist/core.cjs?raw-source'; +import qPublicDts from '../../node_modules/@qwik.dev/core/public.d.ts?raw-source'; +import qCoreInternalDts from '../../node_modules/@qwik.dev/core/dist/core-internal.d.ts?raw-source'; +import qCoreMinMjs from '../../node_modules/@qwik.dev/core/dist/core.min.mjs?raw-source'; +import qPreloaderMjs from '../../node_modules/@qwik.dev/core/dist/preloader.mjs?raw-source'; +import qHandlersMjs from '../../node_modules/@qwik.dev/core/handlers.mjs?raw-source'; +import qQwikLoaderJs from '../../node_modules/@qwik.dev/core/dist/qwikloader.js?raw-source'; +import qQwikLoaderDebugJs from '../../node_modules/@qwik.dev/core/dist/qwikloader.debug.js?raw-source'; +import qCoreMjs from '../../node_modules/@qwik.dev/core/dist/core.mjs?raw-source'; +import qOptimizerCjs from '../../node_modules/@qwik.dev/core/dist/optimizer.cjs?raw-source'; +import qServerCjs from '../../node_modules/@qwik.dev/core/dist/server.cjs?raw-source'; +import qServerDts from '../../node_modules/@qwik.dev/core/dist/server.d.ts?raw-source'; -export const QWIK_PKG_NAME = '@builder.io/qwik'; +export const QWIK_PKG_NAME = '@qwik.dev/core'; +export const QWIK_PKG_NAME_V1 = '@builder.io/qwik'; const ROLLUP_VERSION = '2.75.6'; export const getNpmCdnUrl = ( @@ -44,6 +47,11 @@ export const getNpmCdnUrl = ( pkgVersion = pkgName === QWIK_PKG_NAME ? qwikVersion.split('-dev')[0] : ''; } } + if (pkgName === QWIK_PKG_NAME) { + if (pkgVersion < '2') { + pkgName = QWIK_PKG_NAME_V1; + } + } return `https://cdn.jsdelivr.net/npm/${pkgName}${pkgVersion ? '@' + pkgVersion : ''}${pkgPath}`; }; @@ -52,7 +60,8 @@ export const bundled: PkgUrls = { version: qwikVersion, '/dist/build/index.d.ts': qBuild, '/dist/core.cjs': qCoreCjs, - '/dist/core.d.ts': qCoreDts, + '/public.d.ts': qPublicDts, + '/dist/core-internal.d.ts': qCoreInternalDts, '/dist/core.min.mjs': qCoreMinMjs, '/dist/core.mjs': qCoreMjs, '/dist/optimizer.cjs': qOptimizerCjs, @@ -60,6 +69,8 @@ export const bundled: PkgUrls = { '/dist/server.d.ts': qServerDts, '/dist/preloader.mjs': qPreloaderMjs, '/dist/qwikloader.js': qQwikLoaderJs, + '/dist/qwikloader.debug.js': qQwikLoaderDebugJs, + '/handlers.mjs': qHandlersMjs, '/bindings/qwik.wasm.cjs': qWasmCjs, '/bindings/qwik_wasm_bg.wasm': qWasmBinUrl, }, diff --git a/packages/docs/src/repl/editor.tsx b/packages/docs/src/repl/editor.tsx index cd2d81c86d4..2975216c0e6 100644 --- a/packages/docs/src/repl/editor.tsx +++ b/packages/docs/src/repl/editor.tsx @@ -7,7 +7,7 @@ import { useSignal, useStore, useTask$, -} from '@builder.io/qwik'; +} from '@qwik.dev/core'; import type { IStandaloneCodeEditor } from './monaco'; import { addQwikLibs, diff --git a/packages/docs/src/repl/monaco.tsx b/packages/docs/src/repl/monaco.tsx index d4422c2889d..a6d82629d4c 100644 --- a/packages/docs/src/repl/monaco.tsx +++ b/packages/docs/src/repl/monaco.tsx @@ -1,11 +1,10 @@ -import { noSerialize } from '@builder.io/qwik'; -import type { Diagnostic } from '@builder.io/qwik/optimizer'; +import { isServer, noSerialize } from '@qwik.dev/core'; +import type { Diagnostic } from '@qwik.dev/core/optimizer'; import type MonacoTypes from 'monaco-editor'; +import { getColorPreference } from '../components/theme-toggle/theme-toggle'; +import { QWIK_PKG_NAME, QWIK_PKG_NAME_V1, bundled, getNpmCdnUrl } from './bundled'; import type { EditorProps, EditorStore } from './editor'; import type { ReplStore } from './types'; -import { getColorPreference } from '../components/theme-toggle/theme-toggle'; -import { bundled, getNpmCdnUrl } from './bundled'; -import { isServer } from '@builder.io/qwik'; // We cannot use this, it causes the repl to use imports // import { QWIK_REPL_DEPS_CACHE } from './worker/repl-constants'; const QWIK_REPL_DEPS_CACHE = 'QwikReplDeps'; @@ -26,7 +25,7 @@ export const initMonacoEditor = async ( esModuleInterop: true, isolatedModules: true, jsx: ts.JsxEmit.ReactJSX, - jsxImportSource: '@builder.io/qwik', + jsxImportSource: '@qwik.dev/core', moduleResolution: ts.ModuleResolutionKind.NodeJs, noEmit: true, skipLibCheck: true, @@ -148,7 +147,11 @@ const checkDiagnostics = async ( ) => { if (!monacoCtx.tsWorker) { const getTsWorker = await monaco.languages.typescript.getTypeScriptWorker(); - monacoCtx.tsWorker = await getTsWorker(editor.getModel()!.uri); + const uri = editor.getModel()?.uri; + if (!uri) { + return; + } + monacoCtx.tsWorker = await getTsWorker(uri); } const tsWorker = monacoCtx.tsWorker; @@ -211,41 +214,48 @@ export const addQwikLibs = async (version: string) => { } }); typescriptDefaults.addExtraLib( - `declare module '@builder.io/qwik/jsx-runtime' { export * from '@builder.io/qwik' }`, - '/node_modules/@builder.io/qwik/dist/jsx-runtime.d.ts' + `declare module '@qwik.dev/core/jsx-runtime' { export * from '@qwik.dev/core' }`, + '/node_modules/@qwik.dev/core/dist/jsx-runtime.d.ts' ); typescriptDefaults.addExtraLib(CLIENT_LIB); }; const loadDeps = async (qwikVersion: string) => { const [M, m, p] = qwikVersion.split('-')[0].split('.').map(Number); + const isV1 = M < 2; const prefix = qwikVersion === 'bundled' || M > 1 || (M == 1 && (m > 7 || (m == 7 && p >= 2))) ? '/dist/' : '/'; const deps: NodeModuleDep[] = [ // qwik - { - pkgName: '@builder.io/qwik', + !isV1 && { + pkgName: QWIK_PKG_NAME, pkgVersion: qwikVersion, - pkgPath: `${prefix}core.d.ts`, + pkgPath: `/public.d.ts`, import: '', }, + { + pkgName: isV1 ? QWIK_PKG_NAME_V1 : QWIK_PKG_NAME, + pkgVersion: qwikVersion, + pkgPath: `${prefix}core-internal.d.ts`, + import: isV1 ? '' : '/internal', + }, // server API { - pkgName: '@builder.io/qwik', + pkgName: isV1 ? QWIK_PKG_NAME_V1 : QWIK_PKG_NAME, pkgVersion: qwikVersion, pkgPath: `${prefix}server.d.ts`, import: '/server', }, // build constants { - pkgName: '@builder.io/qwik', + pkgName: isV1 ? QWIK_PKG_NAME_V1 : QWIK_PKG_NAME, pkgVersion: qwikVersion, pkgPath: `${prefix}build/index.d.ts`, import: '/build', }, - ]; + ].filter(Boolean) as NodeModuleDep[]; const cache = await caches.open(QWIK_REPL_DEPS_CACHE); diff --git a/packages/docs/src/repl/repl-console.tsx b/packages/docs/src/repl/repl-console.tsx index a61d5e44ac4..6890ede20a2 100644 --- a/packages/docs/src/repl/repl-console.tsx +++ b/packages/docs/src/repl/repl-console.tsx @@ -1,4 +1,4 @@ -import { component$, jsx } from '@builder.io/qwik'; +import { component$, jsx } from '@qwik.dev/core'; import type { ReplEvent, ReplStore } from './types'; export interface ReplConsoleProps { @@ -7,9 +7,10 @@ export interface ReplConsoleProps { export const ReplConsole = component$(({ store }: ReplConsoleProps) => { return (
- {store.events.filter(Boolean).map((ev) => ( - - ))} + { + // for some reason ev can be null, just skip it + store.events.map((ev) => ev && ) + }
); }); diff --git a/packages/docs/src/repl/repl-input-panel.tsx b/packages/docs/src/repl/repl-input-panel.tsx index dab4e48331e..fb8c8ff0921 100644 --- a/packages/docs/src/repl/repl-input-panel.tsx +++ b/packages/docs/src/repl/repl-input-panel.tsx @@ -1,4 +1,4 @@ -import type { QRL } from '@builder.io/qwik'; +import type { QRL } from '@qwik.dev/core'; import { Editor } from './editor'; import { ReplCommands } from './repl-commands'; import { ReplTabButton } from './repl-tab-button'; diff --git a/packages/docs/src/repl/repl-options.tsx b/packages/docs/src/repl/repl-options.tsx index 5e952bee7fb..c20ffb81390 100644 --- a/packages/docs/src/repl/repl-options.tsx +++ b/packages/docs/src/repl/repl-options.tsx @@ -22,6 +22,7 @@ export const ReplOptions = ({ input, versions, qwikVersion }: ReplOptionsProps) /> + ); }; diff --git a/packages/docs/src/repl/repl-output-modules.tsx b/packages/docs/src/repl/repl-output-modules.tsx index ea23c62949d..bb1159160db 100644 --- a/packages/docs/src/repl/repl-output-modules.tsx +++ b/packages/docs/src/repl/repl-output-modules.tsx @@ -1,4 +1,4 @@ -import { $, component$, createSignal, useSignal } from '@builder.io/qwik'; +import { $, component$, createSignal, useSignal } from '@qwik.dev/core'; import { CodeBlock } from '../components/code-block/code-block'; import type { ReplModuleOutput } from './types'; const FILE_MODULE_DIV_ID = 'file-modules-client-modules'; diff --git a/packages/docs/src/repl/repl-output-panel.tsx b/packages/docs/src/repl/repl-output-panel.tsx index 06385eb677f..2e1984e2ea1 100644 --- a/packages/docs/src/repl/repl-output-panel.tsx +++ b/packages/docs/src/repl/repl-output-panel.tsx @@ -1,13 +1,67 @@ -import { component$ } from '@builder.io/qwik'; +import { component$, useComputed$ } from '@qwik.dev/core'; import { CodeBlock } from '../components/code-block/code-block'; import { ReplOutputModules } from './repl-output-modules'; -import { ReplOutputSymbols } from './repl-output-symbols'; +import { ReplOutputSegments } from './repl-output-segments'; import { ReplTabButton } from './repl-tab-button'; import { ReplTabButtons } from './repl-tab-buttons'; import type { ReplAppInput, ReplStore } from './types'; +import { _deserialize, _getDomContainer } from '@qwik.dev/core/internal'; +import { _dumpState, _preprocessState, _vnode_toString } from '@qwik.dev/core/internal'; export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProps) => { - const diagnosticsLen = store.diagnostics.length + store.monacoDiagnostics.length; + const diagnosticsLen = useComputed$( + () => store.diagnostics.length + store.monacoDiagnostics.length + ); + + const domContainerFromResultHtml = useComputed$(() => { + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(store.htmlResult.rawHtml, 'text/html'); + return _getDomContainer(doc.documentElement); + } catch (err) { + console.error(err); + return null; + } + }); + + const parsedState = useComputed$(() => { + try { + const container = domContainerFromResultHtml.value; + const doc = container!.element; + const qwikStates = doc.querySelectorAll('script[type="qwik/state"]'); + if (qwikStates.length !== 0) { + const data = qwikStates[qwikStates.length - 1]; + const origState = JSON.parse(data?.textContent || '[]'); + _preprocessState(origState, container as any); + return origState + ? _dumpState(origState, false, '', null) + //remove first new line + .replace(/\n/, '') + : 'No state found'; + } + return 'No state found'; + } catch (err) { + console.error(err); + return null; + } + }); + + const vdomTree = useComputed$(() => { + try { + const container = domContainerFromResultHtml.value; + return _vnode_toString.call( + container!.rootVNode as any, + Number.MAX_SAFE_INTEGER, + '', + true, + false, + false + ); + } catch (err) { + console.error(err); + return null; + } + }); return (
@@ -32,10 +86,10 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp {store.enableClientOutput ? ( { - store.selectedOutputPanel = 'symbols'; + store.selectedOutputPanel = 'segments'; }} /> ) : null} @@ -61,8 +115,8 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp ) : null} 0 ? ` (${diagnosticsLen})` : ``}`} - cssClass={{ 'repl-tab-diagnostics': true, 'has-errors': diagnosticsLen > 0 }} + text={`Diagnostics${diagnosticsLen.value > 0 ? ` (${diagnosticsLen.value})` : ``}`} + cssClass={{ 'repl-tab-diagnostics': true, 'has-errors': diagnosticsLen.value > 0 }} isActive={store.selectedOutputPanel === 'diagnostics'} onClick$={async () => { store.selectedOutputPanel = 'diagnostics'; @@ -101,13 +155,31 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp
{store.selectedOutputPanel === 'html' ? ( -
- +
+
+ HTML + +
+ {parsedState.value ? ( +
+ Parsed State + +
+ ) : null} + {vdomTree.value ? ( +
+ VNode Tree + +
+ ) : null}
) : null} - {store.selectedOutputPanel === 'symbols' ? ( - + {store.selectedOutputPanel === 'segments' ? ( + ) : null} {store.selectedOutputPanel === 'clientBundles' ? ( @@ -120,7 +192,7 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp {store.selectedOutputPanel === 'diagnostics' ? (
- {diagnosticsLen === 0 ? ( + {diagnosticsLen.value === 0 ? (

- No Reported Diagnostics -

) : ( [...store.diagnostics, ...store.monacoDiagnostics].map((d, key) => ( diff --git a/packages/docs/src/repl/repl-output-segments.tsx b/packages/docs/src/repl/repl-output-segments.tsx new file mode 100644 index 00000000000..544cf5c109a --- /dev/null +++ b/packages/docs/src/repl/repl-output-segments.tsx @@ -0,0 +1,73 @@ +import { $, component$, useSignal } from '@qwik.dev/core'; +import type { TransformModule } from '@qwik.dev/core/optimizer'; +import { CodeBlock } from '../components/code-block/code-block'; +const FILE_MODULE_DIV_ID = 'file-modules-symbol'; + +export const ReplOutputSegments = component$(({ outputs }: ReplOutputSegmentsProps) => { + const selectedPath = useSignal(outputs.length ? outputs[0].path : ''); + const pathInView$ = $((path: string) => { + selectedPath.value = path; + }); + + return ( +
+ +
+ {outputs + .filter((o) => !!o.segment) + .map((o, i) => ( +
+
+ {o.segment!.canonicalFilename} + {o.segment!.paramNames && ( +
+ Params: {o.segment!.paramNames.join(', ')} +
+ )} + {o.segment!.captureNames && ( +
+ Captures: {o.segment!.captureNames.join(', ')} +
+ )} +
+
+ +
+
+ ))} +
+
+ ); +}); + +interface ReplOutputSegmentsProps { + outputs: TransformModule[]; +} diff --git a/packages/docs/src/repl/repl-output-symbols.tsx b/packages/docs/src/repl/repl-output-symbols.tsx deleted file mode 100644 index 47846d85e6e..00000000000 --- a/packages/docs/src/repl/repl-output-symbols.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import type { TransformModule } from '@builder.io/qwik/optimizer'; -import { CodeBlock } from '../components/code-block/code-block'; -import { $, component$, useSignal } from '@builder.io/qwik'; -const FILE_MODULE_DIV_ID = 'file-modules-symbol'; - -export const ReplOutputSymbols = component$(({ outputs }: ReplOutputSymbolsProps) => { - const selectedPath = useSignal(outputs.length ? outputs[0].path : ''); - const pathInView$ = $((path: string) => { - selectedPath.value = path; - }); - - return ( - - ); -}); - -interface ReplOutputSymbolsProps { - outputs: TransformModule[]; -} diff --git a/packages/docs/src/repl/repl-output-update.ts b/packages/docs/src/repl/repl-output-update.ts index 1fe666a0dc6..ebbd5a08acf 100644 --- a/packages/docs/src/repl/repl-output-update.ts +++ b/packages/docs/src/repl/repl-output-update.ts @@ -1,38 +1,47 @@ import type { ReplResult, ReplStore } from './types'; // TODO fix useStore to recursively notify subscribers -const deepUpdate = (prev: any, next: any) => { +const deepUpdate = (prev: any, next: any, matcher?: (a: any, b: any) => boolean) => { for (const key in next) { if (prev[key] && typeof next[key] === 'object' && typeof prev[key] === 'object') { deepUpdate(prev[key], next[key]); } else { - prev[key] = next[key]; + if (prev[key] !== next[key]) { + prev[key] = next[key]; + } } } - for (const key in prev) { - if (!(key in next)) { - delete prev[key]; + if (Array.isArray(prev)) { + for (const item of prev) { + if (!next.some((nextItem: any) => (matcher ? matcher(nextItem, item) : nextItem === item))) { + prev.splice(prev.indexOf(item), 1); + } + } + } else { + for (const key in prev) { + if (!(key in next)) { + delete prev[key]; + } } } }; -export const updateReplOutput = async (store: ReplStore, result: ReplResult) => { - store.diagnostics = result.diagnostics; +const matchByPath = (a: any, b: any) => a.path === b.path; - if (store.diagnostics.length === 0) { - store.html = result.html; - deepUpdate(store.transformedModules, result.transformedModules); - deepUpdate(store.clientBundles, result.clientBundles); - deepUpdate(store.ssrModules, result.ssrModules); - if ( - result.events.length !== store.events.length || - result.events.some((ev, i) => ev?.start !== store.events[i]?.start) - ) { - store.events = result.events; - } +export const updateReplOutput = async (store: ReplStore, result: ReplResult) => { + deepUpdate(store.diagnostics, result.diagnostics); + deepUpdate(store.htmlResult, result.htmlResult); + deepUpdate(store.transformedModules, result.transformedModules, matchByPath); + deepUpdate(store.clientBundles, result.clientBundles, matchByPath); + deepUpdate(store.ssrModules, result.ssrModules, matchByPath); + if ( + result.events.length !== store.events.length || + result.events.some((ev, i) => ev?.start !== store.events[i]?.start) + ) { + store.events = result.events; + } - if (store.selectedOutputPanel === 'diagnostics' && store.monacoDiagnostics.length === 0) { - store.selectedOutputPanel = 'app'; - } + if (store.selectedOutputPanel === 'diagnostics' && store.monacoDiagnostics.length === 0) { + store.selectedOutputPanel = 'app'; } }; diff --git a/packages/docs/src/repl/repl-share-url.ts b/packages/docs/src/repl/repl-share-url.ts index 6de7babc17e..781912b5d83 100644 --- a/packages/docs/src/repl/repl-share-url.ts +++ b/packages/docs/src/repl/repl-share-url.ts @@ -7,6 +7,7 @@ const dataDefaults: PlaygroundShareUrl = { buildMode: 'development', entryStrategy: 'segment', files: [], + preloader: false, }; export const parsePlaygroundShareUrl = (shareable: string) => { if (typeof shareable === 'string' && shareable.length > 0) { @@ -27,6 +28,10 @@ export const parsePlaygroundShareUrl = (shareable: string) => { if (ENTRY_STRATEGY_OPTIONS.includes(entryStrategy)) { data.entryStrategy = entryStrategy; } + const preloader = params.get('preloader')!; + if (preloader !== null) { + data.preloader = true; + } if (params.has('files')) { // Old URLs that didn't compress @@ -85,6 +90,10 @@ export const strToFiles = (str: string) => { // You can add new entries to the beginning though. export const dictionary = strToU8( filesToStr([ + { + path: '/app.tsx', + code: `import { component$ } from '@qwik.dev/core';\n\nexport default component$(() => {\n return (\n
\n

Hello from Qwik!

\n
\n );\n`, + }, { path: '', // Extra words to help with compression @@ -98,7 +107,7 @@ export const dictionary = strToU8( // You need to add a new section like this before this section instead code: `
props: class return ( story component$( store string state export const span type href={ page strong count useSignal< useStore< qwik import { } from searchInput console.log( searchResults builder useTask$( stories style={ news export default data track onClick$= new nav map link debounced controller user useStyles$( useStylesScoped$( url title timeoutId time_ago second response Date.now() minute main item interface hour disabled aria any State update transform the target suggestion setTimeout selectedValue rotate render people number list label https:// header deg debouncedGetPeople debounce component comments_count comments clock background await new Promise args SuggestionsListComponent IStory IState IComment GrandChild Clock Child AutoComplete 360 yellow with view useVisibleTask$( true tmrId timer then swapi styles signal section search results resolve rel prev points parsedResponse null noreferrer name more length json job items isServer index github getPeople function fetch example domain dev delay css container com click clearTimeout async api _blank Star Wars API This The StoryPreview Stories ReturnType Qwik App Page Nav HackerNewsCSS AbortController server$( routeAction$( routeLoader$( useContent( useDocumentHead( useLocation( useNavigate( validator$( zod$( noSerialize( useComputed$( useOnDocument( useOnWindow( useResource$( useContext( useContextProvider( createContextId<`, }, - // The default hello world app + supporting files + // The old default hello world app + supporting files { path: '/app.tsx', code: `import { component$ } from '@builder.io/qwik';\n\nexport default component$(() => {\n return

Hello Qwik

;\n});\n`, @@ -125,6 +134,13 @@ export const createPlaygroundShareUrl = (data: PlaygroundShareUrl, pathname = '/ if (data.entryStrategy !== dataDefaults.entryStrategy) { params.set('entryStrategy', data.entryStrategy); } + if (data.preloader !== dataDefaults.preloader) { + if (data.preloader) { + params.set('preloader', ''); + } else { + params.delete('preloader'); + } + } params.set('f', compressFiles(data.files)); @@ -180,4 +196,5 @@ interface PlaygroundShareUrl { buildMode: any; entryStrategy: any; files: any[]; + preloader?: boolean; } diff --git a/packages/docs/src/repl/repl-share-url.unit.ts b/packages/docs/src/repl/repl-share-url.unit.ts index 6d1dec380bc..0a04205690d 100644 --- a/packages/docs/src/repl/repl-share-url.unit.ts +++ b/packages/docs/src/repl/repl-share-url.unit.ts @@ -1,13 +1,14 @@ -import { assert, test } from 'vitest'; +import { strFromU8 } from 'fflate'; +import { assert, expect, test } from 'vitest'; import { - filesToStr, - strToFiles, - createPlaygroundShareUrl, compressFiles, - parseCompressedFiles, + createPlaygroundShareUrl, dictionary, + filesToStr, + parseCompressedFiles, + parsePlaygroundShareUrl, + strToFiles, } from './repl-share-url'; -import { strFromU8 } from 'fflate'; const data = { version: '1.2.3', @@ -23,6 +24,7 @@ const data = { code: 'console.log("bar");', }, ], + preloader: false, }; test('filesToStr', () => { assert.equal( @@ -64,8 +66,85 @@ test('createPlaygroundShareUrl 2', () => { }); test('dictionary is unchanged', () => { - assert.equal( - strFromU8(dictionary), - "0||1448|
props: class return ( story component$( store string state export const span type href={ page strong count useSignal< useStore< qwik import { } from searchInput console.log( searchResults builder useTask$( stories style={ news export default data track onClick$= new nav map link debounced controller user useStyles$( useStylesScoped$( url title timeoutId time_ago second response Date.now() minute main item interface hour disabled aria any State update transform the target suggestion setTimeout selectedValue rotate render people number list label https:// header deg debouncedGetPeople debounce component comments_count comments clock background await new Promise args SuggestionsListComponent IStory IState IComment GrandChild Clock Child AutoComplete 360 yellow with view useVisibleTask$( true tmrId timer then swapi styles signal section search results resolve rel prev points parsedResponse null noreferrer name more length json job items isServer index github getPeople function fetch example domain dev delay css container com click clearTimeout async api _blank Star Wars API This The StoryPreview Stories ReturnType Qwik App Page Nav HackerNewsCSS AbortController server$( routeAction$( routeLoader$( useContent( useDocumentHead( useLocation( useNavigate( validator$( zod$( noSerialize( useComputed$( useOnDocument( useOnWindow( useResource$( useContext( useContextProvider( createContextId<|8|/app.tsx|114|import { component$ } from '@builder.io/qwik';\n\nexport default component$(() => {\n return

Hello Qwik

;\n});\n|17|/entry.server.tsx|201|import { renderToString, type RenderOptions } from '@builder.io/qwik/server';\nimport { Root } from './root';\n\nexport default function (opts: RenderOptions) {\n return renderToString(, opts);\n}\n|9|/root.tsx|192|import App from './app';\n\nexport const Root = () => {\n return (\n <>\n \n Hello Qwik\n \n \n \n \n \n );\n};\n" + const dictionaryAsString = strFromU8(dictionary); + expect(dictionaryAsString).toMatchInlineSnapshot(` + "8|/app.tsx|149|import { component$ } from '@qwik.dev/core'; + + export default component$(() => { + return ( +
+

Hello from Qwik!

+
+ ); + |0||1448|
props: class return ( story component$( store string state export const span type href={ page strong count useSignal< useStore< qwik import { } from searchInput console.log( searchResults builder useTask$( stories style={ news export default data track onClick$= new nav map link debounced controller user useStyles$( useStylesScoped$( url title timeoutId time_ago second response Date.now() minute main item interface hour disabled aria any State update transform the target suggestion setTimeout selectedValue rotate render people number list label https:// header deg debouncedGetPeople debounce component comments_count comments clock background await new Promise args SuggestionsListComponent IStory IState IComment GrandChild Clock Child AutoComplete 360 yellow with view useVisibleTask$( true tmrId timer then swapi styles signal section search results resolve rel prev points parsedResponse null noreferrer name more length json job items isServer index github getPeople function fetch example domain dev delay css container com click clearTimeout async api _blank Star Wars API This The StoryPreview Stories ReturnType Qwik App Page Nav HackerNewsCSS AbortController server$( routeAction$( routeLoader$( useContent( useDocumentHead( useLocation( useNavigate( validator$( zod$( noSerialize( useComputed$( useOnDocument( useOnWindow( useResource$( useContext( useContextProvider( createContextId<|8|/app.tsx|114|import { component$ } from '@builder.io/qwik'; + + export default component$(() => { + return

Hello Qwik

; + }); + |17|/entry.server.tsx|201|import { renderToString, type RenderOptions } from '@builder.io/qwik/server'; + import { Root } from './root'; + + export default function (opts: RenderOptions) { + return renderToString(, opts); + } + |9|/root.tsx|192|import App from './app'; + + export const Root = () => { + return ( + <> + + Hello Qwik + + + + + + ); + }; + " + `); +}); + +test('previous URLs still work', () => { + expect(parsePlaygroundShareUrl('f=G000o4mG5EQDAA')).toHaveProperty( + 'files', + // DO NOT UPDATE THIS TEST - all these URLs must work forever + expect.arrayContaining([ + expect.objectContaining({ + path: '/app.tsx', + code: "import { component$ } from '@builder.io/qwik';\n\nexport default component$(() => {\n return

Hello Qwik

;\n});\n", + }), + ]) + ); + expect( + parsePlaygroundShareUrl( + 'f=Q0o0xgaW2BKNDrDkqNCB15QUpyFIgKTl51uBeGA%2BKO%2BBIwaW0W1A6SI%2FDWQzyKm1wKBDVwyU0lAqUNJRqE4GFc3AqLNSCnENDlGq1QTpAGJ43a5RDa6oa0FOgBsDbxkAXQIMCqAWMIktXqqBSvRgNoNMRg7C0XQ%2FJNM9AA' + ) + ).toHaveProperty( + 'files', + // DO NOT UPDATE THIS TEST - all these URLs must work forever + expect.arrayContaining([ + expect.objectContaining({ + path: '/app.tsx', + code: `import { component$, jsx, useTask$ } from '@builder.io/qwik'; + +export default component$(() => { + const foo:{ + contents: ReturnType + } = { + contents: jsx("p", {children:"TEST"}) + } + useTask$(({track}) =>{ + console.log(foo); + }); + return ( + <> + {foo.contents} + + ); +}); +`, + }), + ]) ); }); diff --git a/packages/docs/src/repl/repl-tab-button.tsx b/packages/docs/src/repl/repl-tab-button.tsx index 16274c8cac8..824514e2bb1 100644 --- a/packages/docs/src/repl/repl-tab-button.tsx +++ b/packages/docs/src/repl/repl-tab-button.tsx @@ -1,4 +1,4 @@ -import type { PropsOf, Component } from '@builder.io/qwik'; +import type { PropsOf, Component } from '@qwik.dev/core'; import { CloseIcon } from '../components/svgs/close-icon'; export const ReplTabButton: Component = (props) => { diff --git a/packages/docs/src/repl/repl-version.ts b/packages/docs/src/repl/repl-version.ts index 40608bbac0b..0a659d970c7 100644 --- a/packages/docs/src/repl/repl-version.ts +++ b/packages/docs/src/repl/repl-version.ts @@ -3,7 +3,7 @@ import { QWIK_PKG_NAME, bundled } from './bundled'; const bundledVersion = bundled[QWIK_PKG_NAME].version; // The golden oldies -const keepList = new Set('1.0.0,1.1.5,1.2.13,1.4.5'.split(',')); +const keepList = new Set('1.0.0,1.1.5,1.2.13,1.4.5,1.5.7,1.6.0,1.7.3,1.8.0,1.9.0'.split(',')); // The bad apples - add versions that break the REPL here const blockList = new Set( @@ -97,7 +97,7 @@ export const getReplVersion = async (version: string | undefined, offline: boole }); versions.unshift('bundled'); - if (!hasVersion || !version) { + if ((!offline && !hasVersion) || !version) { version = 'bundled'; } @@ -113,11 +113,11 @@ const isExpiredNpmData = (npmData: NpmData | null) => { return true; }; -const QWIK_NPM_DATA = `https://data.jsdelivr.com/v1/package/npm/@builder.io/qwik`; +const QWIK_NPM_DATA = `https://data.jsdelivr.com/v1/package/npm/@qwik.dev/core`; const NPM_STORAGE_KEY = `qwikNpmData`; -// https://data.jsdelivr.com/v1/package/npm/@builder.io/qwik +// https://data.jsdelivr.com/v1/package/npm/@qwik.dev/core interface NpmData { tags: { latest: string; next: string }; versions: string[]; diff --git a/packages/docs/src/repl/repl.css b/packages/docs/src/repl/repl.css index f5e8e437b4b..46517a129aa 100644 --- a/packages/docs/src/repl/repl.css +++ b/packages/docs/src/repl/repl.css @@ -199,6 +199,15 @@ overflow: auto; } +.output-html .code-block-info { + display: block; + font-weight: bold; + background-color: rgb(33 104 170 / 15%); + padding: 5px 10px; + overflow: hidden; + text-overflow: ellipsis; +} + .output-html pre { height: 100%; } @@ -230,7 +239,7 @@ .output-modules .file-tree { padding: 0 15px 15px 15px; grid-area: repl-file-tree; - overflow-y: auto; + overflow: auto; } .output-modules .file-tree-header { @@ -242,8 +251,6 @@ display: block; margin: 4px 0px 2px 9px; white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; } .output-modules .file-tree-items a:hover, @@ -270,8 +277,7 @@ margin-bottom: 15px; background-color: rgb(33 104 170 / 15%); padding: 5px 10px; - overflow: hidden; - text-overflow: ellipsis; + word-break: break-word; } .output-modules .file-size { diff --git a/packages/docs/src/repl/repl.tsx b/packages/docs/src/repl/repl.tsx index 5213ec856eb..e513c558af3 100644 --- a/packages/docs/src/repl/repl.tsx +++ b/packages/docs/src/repl/repl.tsx @@ -1,21 +1,21 @@ import { + $, component$, + isServer, noSerialize, - useStyles$, useStore, + useStyles$, useTask$, useVisibleTask$, - $, -} from '@builder.io/qwik'; +} from '@qwik.dev/core'; +import { QWIK_PKG_NAME, QWIK_PKG_NAME_V1, bundled, getNpmCdnUrl } from './bundled'; +import { ReplDetailPanel } from './repl-detail-panel'; import { ReplInputPanel } from './repl-input-panel'; import { ReplOutputPanel } from './repl-output-panel'; -import styles from './repl.css?inline'; -import type { ReplStore, ReplUpdateMessage, ReplMessage, ReplAppInput } from './types'; -import { ReplDetailPanel } from './repl-detail-panel'; -import { getReplVersion } from './repl-version'; import { updateReplOutput } from './repl-output-update'; -import { QWIK_PKG_NAME, bundled, getNpmCdnUrl } from './bundled'; -import { isServer } from '@builder.io/qwik'; +import { getReplVersion } from './repl-version'; +import styles from './repl.css?inline'; +import type { ReplAppInput, ReplMessage, ReplStore, ReplUpdateMessage } from './types'; export const Repl = component$((props: ReplProps) => { useStyles$(styles); @@ -27,7 +27,10 @@ export const Repl = component$((props: ReplProps) => { clientId: Math.round(Math.random() * Number.MAX_SAFE_INTEGER) .toString(36) .toLowerCase(), - html: '', + htmlResult: { + rawHtml: '', + prettyHtml: '', + }, transformedModules: [], clientBundles: [], ssrModules: [], @@ -79,10 +82,6 @@ export const Repl = component$((props: ReplProps) => { useVisibleTask$( async () => { - if (isServer) { - return; - } - // only run on the client // Get the version asap, most likely it will be cached. const v = await getReplVersion(input.version, true); store.versions = v.versions; @@ -109,6 +108,7 @@ export const Repl = component$((props: ReplProps) => { track(() => input.files); track(() => input.version); track(() => input.debug); + track(() => input.preloader); track(() => store.serverWindow); sendUserUpdateToReplServer(input, store); @@ -169,9 +169,14 @@ const getDependencies = (input: ReplAppInput) => { if (input.version !== 'bundled') { const [M, m, p] = input.version.split('-')[0].split('.').map(Number); const prefix = M > 1 || (M == 1 && (m > 7 || (m == 7 && p >= 2))) ? '/dist/' : '/'; - out[QWIK_PKG_NAME] = { - version: input.version, - }; + // we must always provide the qwik.dev package so the worker can find the version + out[QWIK_PKG_NAME] = { version: input.version }; + let bundles; + if (M < 2) { + bundles = out[QWIK_PKG_NAME_V1] = { version: input.version }; + } else { + bundles = out[QWIK_PKG_NAME]; + } for (const p of [ `${prefix}core.cjs`, `${prefix}core.mjs`, @@ -181,7 +186,7 @@ const getDependencies = (input: ReplAppInput) => { `/bindings/qwik.wasm.cjs`, `/bindings/qwik_wasm_bg.wasm`, ]) { - out[QWIK_PKG_NAME][p] = getNpmCdnUrl(bundled, QWIK_PKG_NAME, input.version, p); + bundles[p] = getNpmCdnUrl(bundled, QWIK_PKG_NAME, input.version, p); } } return out; @@ -206,6 +211,7 @@ export const sendUserUpdateToReplServer = (input: ReplAppInput, store: ReplStore version: input.version, serverUrl: store.serverUrl, deps: getDependencies(input), + preloader: !!input.preloader, }, }; diff --git a/packages/docs/src/repl/types.ts b/packages/docs/src/repl/types.ts index 11768f1cc4a..b720ec7e978 100644 --- a/packages/docs/src/repl/types.ts +++ b/packages/docs/src/repl/types.ts @@ -1,10 +1,10 @@ -import type { NoSerialize, Signal } from '@builder.io/qwik'; +import type { NoSerialize, Signal } from '@qwik.dev/core'; import type { Diagnostic, QwikManifest, QwikRollupPluginOptions, TransformModule, -} from '@builder.io/qwik/optimizer'; +} from '@qwik.dev/core/optimizer'; export interface ReplAppInput { buildId: number; @@ -13,6 +13,7 @@ export interface ReplAppInput { buildMode: 'development' | 'production'; entryStrategy: string; debug?: boolean; + preloader?: boolean; } export type PkgUrls = { [pkgName: string]: { [path: string]: string; version: string } }; @@ -23,11 +24,12 @@ export interface ReplInputOptions extends Omit { @@ -44,7 +44,7 @@ export const appBundleSsr = async (options: ReplInputOptions, result: ReplResult const loc = warning.loc; if (loc && loc.file) { diagnostic.file = loc.file; - diagnostic.highlights.push({ + diagnostic.highlights!.push({ startCol: loc.column, endCol: loc.column + 1, startLine: loc.line, diff --git a/packages/docs/src/repl/worker/app-ssr-html.ts b/packages/docs/src/repl/worker/app-ssr-html.ts index 71cfb3df0f7..8e29aa2405f 100644 --- a/packages/docs/src/repl/worker/app-ssr-html.ts +++ b/packages/docs/src/repl/worker/app-ssr-html.ts @@ -1,4 +1,4 @@ -import type { RenderOptions, RenderToStringResult } from '@builder.io/qwik/server'; +import type { RenderOptions, RenderToStringResult } from '@qwik.dev/core/server'; import type { ReplInputOptions, ReplResult } from '../types'; import type { QwikWorkerGlobal } from './repl-service-worker'; @@ -65,11 +65,13 @@ export const appSsrHtml = async (options: ReplInputOptions, cache: Cache, result }; const appUrl = `/repl/` + result.clientId + `/`; + (globalThis as any).BASE_URL = appUrl; const baseUrl = appUrl + `build/`; const ssrResult = await render({ base: baseUrl, manifest: result.manifest, prefetchStrategy: null as any, + preloader: options.preloader ? { debug: true } : false, }).catch((e) => { console.error('SSR failed', e); return { @@ -82,7 +84,7 @@ export const appSsrHtml = async (options: ReplInputOptions, cache: Cache, result console.error = error; console.debug = debug; - result.html = ssrResult.html; + result.htmlResult.rawHtml = ssrResult.html; result.events.push({ kind: 'pause', @@ -94,12 +96,12 @@ export const appSsrHtml = async (options: ReplInputOptions, cache: Cache, result if (options.buildMode !== 'production') { try { - const html = await self.prettier?.format(result.html, { + const html = await self.prettier?.format(result.htmlResult.rawHtml, { parser: 'html', plugins: self.prettierPlugins, }); if (html) { - result.html = html; + result.htmlResult.prettyHtml = html; } } catch (e) { console.error(e); diff --git a/packages/docs/src/repl/worker/app-update.ts b/packages/docs/src/repl/worker/app-update.ts index e9f22157d67..b93ec29804a 100644 --- a/packages/docs/src/repl/worker/app-update.ts +++ b/packages/docs/src/repl/worker/app-update.ts @@ -15,7 +15,10 @@ export const appUpdate = async ( type: 'result', clientId, buildId: options.buildId, - html: '', + htmlResult: { + rawHtml: '', + prettyHtml: '', + }, transformedModules: [], clientBundles: [], manifest: undefined, diff --git a/packages/docs/src/repl/worker/repl-constants.ts b/packages/docs/src/repl/worker/repl-constants.ts index 4cf1c064d3a..6bc35d8f9fd 100644 --- a/packages/docs/src/repl/worker/repl-constants.ts +++ b/packages/docs/src/repl/worker/repl-constants.ts @@ -1,4 +1,5 @@ -export const QWIK_PKG_NAME = '@builder.io/qwik'; +export const QWIK_PKG_NAME = '@qwik.dev/core'; +export const QWIK_PKG_NAME_V1 = '@builder.io/qwik'; export const QWIK_REPL_DEPS_CACHE = 'QwikReplDeps'; export const QWIK_REPL_RESULT_CACHE = 'QwikReplResults'; diff --git a/packages/docs/src/repl/worker/repl-dependencies.ts b/packages/docs/src/repl/worker/repl-dependencies.ts index ad2bec490f9..1c42daddb3d 100644 --- a/packages/docs/src/repl/worker/repl-dependencies.ts +++ b/packages/docs/src/repl/worker/repl-dependencies.ts @@ -1,19 +1,26 @@ import type { ReplInputOptions } from '../types'; -import { QWIK_PKG_NAME, QWIK_REPL_DEPS_CACHE } from './repl-constants'; +import { QWIK_PKG_NAME, QWIK_PKG_NAME_V1, QWIK_REPL_DEPS_CACHE } from './repl-constants'; import type { QwikWorkerGlobal } from './repl-service-worker'; let options: ReplInputOptions; let cache: Cache; export const depResponse = async (pkgName: string, pkgPath: string) => { - if (pkgName === QWIK_PKG_NAME && !pkgPath.startsWith('/bindings')) { - const version = options.deps[pkgName].version; + let dep = options.deps[pkgName]; + if (pkgName === QWIK_PKG_NAME) { + const version = dep.version; const [M, m, p] = version.split('-')[0].split('.').map(Number); - if (M > 1 || (M == 1 && (m > 7 || (m == 7 && p >= 2)))) { - pkgPath = `/dist${pkgPath}`; + if (!pkgPath.startsWith('/bindings') && !pkgPath.startsWith('/handlers.mjs')) { + if (M > 1 || (M == 1 && (m > 7 || (m == 7 && p >= 2)))) { + pkgPath = `/dist${pkgPath}`; + } + } + if (version < '2') { + pkgName = QWIK_PKG_NAME_V1; + dep = options.deps[pkgName]; } } - const url = options.deps[pkgName][pkgPath]; + const url = dep[pkgPath]; if (!url) { throw new Error(`No URL given for dep: ${pkgName}${pkgPath}`); } @@ -51,7 +58,7 @@ const _loadDependencies = async (replOptions: ReplInputOptions) => { isServer: true, isBrowser: false, isDev: false, - } as typeof import('@builder.io/qwik/build') as any; + } as typeof self.qwikBuild; const cachedCjsCode = `qwikWasmCjs${realQwikVersion}`; const cachedWasmRsp = `qwikWasmRsp${realQwikVersion}`; @@ -72,27 +79,29 @@ const _loadDependencies = async (replOptions: ReplInputOptions) => { if (!isSameQwikVersion(self.qwikCore?.version)) { await exec(QWIK_PKG_NAME, '/core.cjs'); if (self.qwikCore) { - console.debug(`Loaded @builder.io/qwik: ${self.qwikCore.version}`); + console.debug(`Loaded ${QWIK_PKG_NAME}: ${self.qwikCore.version}`); } else { - throw new Error(`Unable to load @builder.io/qwik ${qwikVersion}`); + throw new Error(`Unable to load ${QWIK_PKG_NAME} ${qwikVersion}`); } } if (!isSameQwikVersion(self.qwikOptimizer?.versions.qwik)) { await exec(QWIK_PKG_NAME, '/optimizer.cjs'); if (self.qwikOptimizer) { - console.debug(`Loaded @builder.io/qwik/optimizer: ${self.qwikOptimizer.versions.qwik}`); + console.debug(`Loaded ${QWIK_PKG_NAME}/optimizer: ${self.qwikOptimizer.versions.qwik}`); } else { - throw new Error(`Unable to load @builder.io/qwik/optimizer ${qwikVersion}`); + throw new Error(`Unable to load ${QWIK_PKG_NAME}/optimizer ${qwikVersion}`); } } if (!isSameQwikVersion(self.qwikServer?.versions.qwik)) { + // clear out the require shim that is version specific + (globalThis as any).require = undefined; await exec(QWIK_PKG_NAME, '/server.cjs'); if (self.qwikServer) { - console.debug(`Loaded @builder.io/qwik/server: ${self.qwikServer.versions.qwik}`); + console.debug(`Loaded ${QWIK_PKG_NAME}/server: ${self.qwikServer.versions.qwik}`); } else { - throw new Error(`Unable to load @builder.io/qwik/server ${qwikVersion}`); + throw new Error(`Unable to load ${QWIK_PKG_NAME}/server ${qwikVersion}`); } } diff --git a/packages/docs/src/repl/worker/repl-messenger.ts b/packages/docs/src/repl/worker/repl-messenger.ts index 98a1f450709..d7c65d658bf 100644 --- a/packages/docs/src/repl/worker/repl-messenger.ts +++ b/packages/docs/src/repl/worker/repl-messenger.ts @@ -3,9 +3,13 @@ import { appUpdate } from './app-update'; export const receiveMessageFromMain = (ev: MessageEvent) => { if (ev.data) { - const msg: ReplMessage = JSON.parse(ev.data); - if (msg.type === 'update') { - appUpdate(ev.source as any as WindowClient, msg.clientId, msg.options); + try { + const msg: ReplMessage = JSON.parse(ev.data); + if (msg.type === 'update') { + appUpdate(ev.source as any as WindowClient, msg.clientId, msg.options); + } + } catch { + // ignore, probably some extension sending non-JSON data } } }; diff --git a/packages/docs/src/repl/worker/repl-plugins.ts b/packages/docs/src/repl/worker/repl-plugins.ts index 50f82b9fa01..f9fab0892cb 100644 --- a/packages/docs/src/repl/worker/repl-plugins.ts +++ b/packages/docs/src/repl/worker/repl-plugins.ts @@ -1,9 +1,18 @@ +import type { QwikRollupPluginOptions } from '@qwik.dev/core/optimizer'; import type { Plugin } from 'rollup'; -import type { QwikRollupPluginOptions } from '@builder.io/qwik/optimizer'; -import type { QwikWorkerGlobal } from './repl-service-worker'; import type { MinifyOptions } from 'terser'; import type { ReplInputOptions } from '../types'; import { depResponse } from './repl-dependencies'; +import type { QwikWorkerGlobal } from './repl-service-worker'; + +/** + * Use paths that look like the paths ones from node modules. The plugin uses the paths to recognize + * the Qwik packages. + */ +const corePath = '/@qwik.dev/core/dist/core.mjs'; +const handlersPath = '/@qwik.dev/core/handlers.mjs'; +const serverPath = '/@qwik.dev/core/dist/server.mjs'; +const preloaderPath = '/@qwik.dev/core/dist/preloader.mjs'; export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 'ssr'): Plugin => { const srcInputs = options.srcInputs; @@ -19,18 +28,22 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's if (!importer) { return id; } - if ( - id === '@builder.io/qwik' || - id === '@builder.io/qwik/jsx-runtime' || - id === '@builder.io/qwik/jsx-dev-runtime' - ) { - return '\0qwikCore'; - } - if (id === '@builder.io/qwik/server') { - return '\0qwikServer'; - } - if (id === '@builder.io/qwik/preloader') { - return '\0qwikPreloader'; + const match = id.match(/(@builder\.io\/qwik|@qwik\.dev\/core)(.*)/); + if (match) { + const pkgPath = match[2]; + if (pkgPath === '/server') { + return serverPath; + } + if (pkgPath === '/preloader') { + return preloaderPath; + } + if (pkgPath === '/handlers.mjs') { + return handlersPath; + } + if (/^(|\/jsx(-dev)?-runtime|\/internal)$/.test(pkgPath)) { + return corePath; + } + console.error(`Unknown package ${id}`, match); } // Simple relative file resolution if (id.startsWith('./')) { @@ -51,36 +64,45 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's return input.code; } if (buildMode === 'ssr') { - if (id === '\0qwikCore') { + if (id === corePath || id === handlersPath) { return getRuntimeBundle('qwikCore'); } - if (id === '\0qwikServer') { + if (id === serverPath) { return getRuntimeBundle('qwikServer'); } } - if (id === '\0qwikCore') { + if (id === corePath) { if (options.buildMode === 'production') { - const rsp = await depResponse('@builder.io/qwik', '/core.min.mjs'); + const rsp = await depResponse('@qwik.dev/core', '/core.min.mjs'); if (rsp) { return rsp.text(); } } - const rsp = await depResponse('@builder.io/qwik', '/core.mjs'); + const rsp = await depResponse('@qwik.dev/core', '/core.mjs'); if (rsp) { return rsp.text(); } throw new Error(`Unable to load Qwik core`); } - if (id === '\0qwikPreloader') { - const rsp = await depResponse('@builder.io/qwik', '/preloader.mjs'); + if (id === preloaderPath) { + const rsp = await depResponse('@qwik.dev/core', '/preloader.mjs'); + if (rsp) { + return rsp.text(); + } + } + if (id === handlersPath) { + const rsp = await depResponse('@qwik.dev/core', '/handlers.mjs'); if (rsp) { return rsp.text(); } } // this id is unchanged because it's an entry point - if (id === '@builder.io/qwik/qwikloader.js') { - const rsp = await depResponse('@builder.io/qwik', '/qwikloader.js'); + if (id === '@qwik.dev/core/qwikloader.js') { + const rsp = await depResponse( + '@qwik.dev/core', + `/qwikloader${options.debug ? '.debug' : ''}.js` + ); if (rsp) { return rsp.text(); } diff --git a/packages/docs/src/repl/worker/repl-request-handler.ts b/packages/docs/src/repl/worker/repl-request-handler.ts index 02bfd97300f..b9a9bad51ad 100644 --- a/packages/docs/src/repl/worker/repl-request-handler.ts +++ b/packages/docs/src/repl/worker/repl-request-handler.ts @@ -30,20 +30,6 @@ export const requestHandler = (ev: FetchEvent) => { return htmlRsp; } - // app client modules - // const replEvent: ReplEventMessage = { - // type: 'event', - // clientId, - // event: { - // kind: 'client-module', - // scope: 'network', - // message: [reqUrl.pathname + reqUrl.search], - // start: performance.now(), - // }, - // }; - - // sendMessageToReplServer(replEvent); - return rsp; } @@ -169,5 +155,9 @@ const injectDevHtml = (clientId: string, html?: string) => { }, true); })();`; - return `${html || ''}`; + if (!html) { + return ''; + } + + return html.replace(/<\/body>/i, ``); }; diff --git a/packages/docs/src/repl/worker/repl-server.ts b/packages/docs/src/repl/worker/repl-server.ts index ff447157bb9..cd050c79d96 100644 --- a/packages/docs/src/repl/worker/repl-server.ts +++ b/packages/docs/src/repl/worker/repl-server.ts @@ -81,9 +81,13 @@ export const initReplServer = (win: Window, doc: Document, nav: Navigator) => { return; } if (ev.data) { - const msg: ReplMessage = JSON.parse(ev.data); - if (msg?.type === 'event') { - sendMessageToMain(msg); + try { + const msg: ReplMessage = JSON.parse(ev.data); + if (msg?.type === 'event') { + sendMessageToMain(msg); + } + } catch { + // ignore, probably some extension sending non-JSON data } } }; diff --git a/packages/docs/src/repl/worker/repl-service-worker.ts b/packages/docs/src/repl/worker/repl-service-worker.ts index 051bfae6083..8345bcecabc 100644 --- a/packages/docs/src/repl/worker/repl-service-worker.ts +++ b/packages/docs/src/repl/worker/repl-service-worker.ts @@ -16,10 +16,10 @@ self.oninstall = (ev) => { self.onactivate = () => self.clients.claim(); export interface ReplGlobalApi { - qwikBuild?: typeof import('@builder.io/qwik'); - qwikCore?: typeof import('@builder.io/qwik'); - qwikOptimizer?: typeof import('@builder.io/qwik/optimizer'); - qwikServer?: typeof import('@builder.io/qwik/server'); + qwikBuild?: typeof import('@qwik.dev/core/build'); + qwikCore?: typeof import('@qwik.dev/core'); + qwikOptimizer?: typeof import('@qwik.dev/core/optimizer'); + qwikServer?: typeof import('@qwik.dev/core/server'); prettier?: typeof import('prettier'); prettierPlugins?: any; rollup?: typeof import('rollup'); diff --git a/packages/docs/src/root.tsx b/packages/docs/src/root.tsx index d8b10f73263..2cde026e0e1 100644 --- a/packages/docs/src/root.tsx +++ b/packages/docs/src/root.tsx @@ -1,6 +1,6 @@ -import { component$, useContextProvider, useStore } from '@builder.io/qwik'; -import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city'; -import { Insights } from '@builder.io/qwik-labs'; +import { component$, useContextProvider, useStore } from '@qwik.dev/core'; +import { Insights } from '@qwik.dev/core/insights'; +import { QwikRouterProvider, RouterOutlet, ServiceWorkerRegister } from '@qwik.dev/router'; import RealMetricsOptimization from './components/real-metrics-optimization/real-metrics-optimization'; import { RouterHead } from './components/router-head/router-head'; import { BUILDER_PUBLIC_API_KEY } from './constants'; @@ -50,7 +50,7 @@ export default component$(() => { useContextProvider(GlobalStore, store); return ( - + ` - ); -}; - -export const onPost: RequestHandler = async ({ parseBody, json }) => { - json(200, { body: await parseBody() }); -}; diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/platform/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/platform/index.tsx deleted file mode 100644 index 91f5768e39e..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/platform/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { type RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async ({ platform, json }) => { - json(200, Object.keys(platform)); -}; diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/proxy/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/proxy/index.tsx deleted file mode 100644 index 0335da5e547..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/proxy/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async ({ send, url }) => { - const response = await fetch( - new URL('/demo/qwikcity/middleware/json/', url) - ); - send(response.status, await response.text()); -}; diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/query/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/query/index.tsx deleted file mode 100644 index d2605fe562b..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/query/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { type RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async ({ query, json }) => { - const obj: Record = {}; - query.forEach((v, k) => (obj[k] = v)); - json(200, obj); -}; diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/redirect/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/redirect/index.tsx deleted file mode 100644 index efeb4f5f415..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/redirect/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { type RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async ({ redirect, url }) => { - throw redirect( - 308, - new URL('/demo/qwikcity/middleware/status/', url).toString() - ); -}; diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/request/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/request/index.tsx deleted file mode 100644 index 73136127718..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/request/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { type RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async ({ json, request }) => { - const obj: Record = {}; - request.headers.forEach((v, k) => (obj[k] = v)); - json(200, { headers: obj }); -}; diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/send/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/send/index.tsx deleted file mode 100644 index 8de1b0ab5dd..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/send/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import type { RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async (requestEvent) => { - const response = new Response('Hello World', { - status: 200, - headers: { - 'Content-Type': 'text/plain', - }, - }); - requestEvent.send(response); -}; diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/sharedMap/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/sharedMap/index.tsx deleted file mode 100644 index 08a53d96536..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/sharedMap/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { component$ } from '@builder.io/qwik'; -import { - routeLoader$, - type RequestHandler, - type Cookie, -} from '@builder.io/qwik-city'; - -interface User { - username: string; - email: string; -} - -export const onRequest: RequestHandler = async ({ - sharedMap, - cookie, - send, -}) => { - const user = loadUserFromCookie(cookie); - if (user) { - sharedMap.set('user', user); - } else { - throw send(401, 'NOT_AUTHORIZED'); - } -}; - -function loadUserFromCookie(cookie: Cookie): User | null { - // this is where you would check cookie for user. - if (cookie) { - // just return mock user for this demo. - return { - username: `Mock User`, - email: `mock@users.com`, - }; - } else { - return null; - } -} - -export const useUser = routeLoader$(({ sharedMap }) => { - return sharedMap.get('user') as User; -}); - -export default component$(() => { - const log = useUser(); - return ( -
- {log.value.username} ({log.value.email}) -
- ); -}); diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/status/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/status/index.tsx deleted file mode 100644 index 51683374142..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/status/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { type RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async ({ status, getWritableStream }) => { - status(200); - const stream = getWritableStream(); - const writer = stream.getWriter(); - writer.write(new TextEncoder().encode('Hello World!')); - writer.close(); -}; diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/text/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/text/index.tsx deleted file mode 100644 index e3f0599615c..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/text/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { type RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async ({ text }) => { - text(200, 'Text based response.'); -}; diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/throw/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/throw/index.tsx deleted file mode 100644 index 7003894b9ba..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/throw/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { type RequestHandler } from '@builder.io/qwik-city'; - -export const onRequest: RequestHandler = async ({ next, sharedMap, json }) => { - const log: string[] = []; - sharedMap.set('log', log); - - log.push('onRequest'); - if (isLoggedIn()) { - // normal behavior call next middleware - await next(); - } else { - // If not logged in throw to prevent implicit call to the next middleware. - throw json(404, log); - } -}; - -export const onGet: RequestHandler = async ({ sharedMap }) => { - const log = sharedMap.get('log') as string[]; - log.push('onGET'); -}; - -function isLoggedIn() { - return false; // always return false as mock example -} diff --git a/packages/docs/src/routes/demo/qwikcity/middleware/url/index.tsx b/packages/docs/src/routes/demo/qwikcity/middleware/url/index.tsx deleted file mode 100644 index 348719931b1..00000000000 --- a/packages/docs/src/routes/demo/qwikcity/middleware/url/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { type RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async ({ url, json }) => { - json(200, { url: url.toString() }); -}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/basePathname/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/basePathname/index.tsx new file mode 100644 index 00000000000..a4ec92faeb8 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/basePathname/index.tsx @@ -0,0 +1,5 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ basePathname, json }) => { + json(200, { basePathname }); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/cacheControl/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/cacheControl/index.tsx new file mode 100644 index 00000000000..6e39c73b58b --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/cacheControl/index.tsx @@ -0,0 +1,12 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ + cacheControl, + headers, + json, +}) => { + cacheControl({ maxAge: 42, public: true }); + const obj: Record = {}; + headers.forEach((value, key) => (obj[key] = value)); + json(200, obj); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/component/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/component/index.tsx new file mode 100644 index 00000000000..8edc31800e4 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/component/index.tsx @@ -0,0 +1,16 @@ +import { type RequestHandler } from '@qwik.dev/router'; +import { component$ } from '@qwik.dev/core'; + +export const onRequest: RequestHandler = async ({ redirect }) => { + if (!isLoggedIn()) { + throw redirect(308, '/login'); + } +}; + +export default component$(() => { + return
You are logged in.
; +}); + +function isLoggedIn() { + return true; // Mock login as true +} diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/cookie/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/cookie/index.tsx new file mode 100644 index 00000000000..0a105bf174e --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/cookie/index.tsx @@ -0,0 +1,8 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ cookie, json }) => { + let count = cookie.get('Qwik.demo.count')?.number() || 0; + count++; + cookie.set('Qwik.demo.count', count); + json(200, { count }); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/env/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/env/index.tsx new file mode 100644 index 00000000000..99ef9228f8c --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/env/index.tsx @@ -0,0 +1,10 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ env, json }) => { + json(200, { + USER: env.get('USER'), + MODE_ENV: env.get('MODE_ENV'), + PATH: env.get('PATH'), + SHELL: env.get('SHELL'), + }); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/error/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/error/index.tsx new file mode 100644 index 00000000000..e20f8a1dd67 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/error/index.tsx @@ -0,0 +1,5 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ error }) => { + throw error(500, 'ERROR: Demonstration of an error response.'); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/exit/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/exit/index.tsx new file mode 100644 index 00000000000..ecf407e58b7 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/exit/index.tsx @@ -0,0 +1,5 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ exit }) => { + throw exit(); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/getWritableStream/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/getWritableStream/index.tsx new file mode 100644 index 00000000000..af1ee620c5f --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/getWritableStream/index.tsx @@ -0,0 +1,18 @@ +import type { RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async (requestEvent) => { + const writableStream = requestEvent.getWritableStream(); + const writer = writableStream.getWriter(); + const encoder = new TextEncoder(); + + writer.write(encoder.encode('Hello World\n')); + await wait(100); + writer.write(encoder.encode('After 100ms\n')); + await wait(100); + writer.write(encoder.encode('After 200ms\n')); + await wait(100); + writer.write(encoder.encode('END')); + writer.close(); +}; + +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/headerSent/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/headerSent/index.tsx new file mode 100644 index 00000000000..2dec941ffd7 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/headerSent/index.tsx @@ -0,0 +1,11 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ headersSent, json }) => { + if (!headersSent) { + json(200, { response: 'default response' }); + } +}; + +export const onRequest: RequestHandler = async ({ status }) => { + status(200); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/headers/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/headers/index.tsx new file mode 100644 index 00000000000..904ae0484ef --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/headers/index.tsx @@ -0,0 +1,8 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ headers, json }) => { + headers.set('X-SRF-TOKEN', Math.random().toString(36).replace('0.', '')); + const obj: Record = {}; + headers.forEach((value, key) => (obj[key] = value)); + json(200, obj); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/html/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/html/index.tsx new file mode 100644 index 00000000000..13d84d6861f --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/html/index.tsx @@ -0,0 +1,13 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ html }) => { + html( + 200, + ` + + +

HTML response

+ + ` + ); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/json/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/json/index.tsx new file mode 100644 index 00000000000..82cf1e77444 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/json/index.tsx @@ -0,0 +1,5 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ json }) => { + json(200, { hello: 'world' }); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/layout.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/layout.tsx new file mode 100644 index 00000000000..d097368e807 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/layout.tsx @@ -0,0 +1,20 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onRequest: RequestHandler = async ({ next }) => { + try { + await next(); + } catch (error: any) { + if (error?.message === 'ERROR: Demonstration of an error response.') { + return await next(); + } else if ( + error && + typeof error === 'object' && + 'message' in error && + typeof error.message === 'string' + ) { + // ignore this error + return; + } + throw error; + } +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/locale/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/locale/index.tsx new file mode 100644 index 00000000000..574cdd5a1ad --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/locale/index.tsx @@ -0,0 +1,12 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onRequest: RequestHandler = async ({ locale, request }) => { + const acceptLanguage = request.headers.get('accept-language'); + const [languages] = acceptLanguage?.split(';') || ['?', '?']; + const [preferredLanguage] = languages.split(','); + locale(preferredLanguage); +}; + +export const onGet: RequestHandler = async ({ locale, json }) => { + json(200, { locale: locale() }); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/method/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/method/index.tsx new file mode 100644 index 00000000000..b4a09f6b0f5 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/method/index.tsx @@ -0,0 +1,5 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onRequest: RequestHandler = async ({ method, json }) => { + json(200, { method }); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/next/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/next/index.tsx new file mode 100644 index 00000000000..5d5256d5725 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/next/index.tsx @@ -0,0 +1,24 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +// Generic function `onRequest` is executed first +export const onRequest: RequestHandler = async ({ next, sharedMap, json }) => { + const log: string[] = []; + sharedMap.set('log', log); + + log.push('onRequest start'); + await next(); // Execute next middleware function (onGet) + log.push('onRequest end'); + + json(200, log); +}; + +// Specific functions such as `onGet` are executed next +export const onGet: RequestHandler = async ({ next, sharedMap }) => { + const log = sharedMap.get('log') as string[]; + + log.push('onGET start'); + // execute next middleware function + // (in our case, there are no more middleware functions nor components.) + await next(); + log.push('onGET end'); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/params/[myId]/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/params/[myId]/index.tsx new file mode 100644 index 00000000000..ae3f4d0f05d --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/params/[myId]/index.tsx @@ -0,0 +1,5 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ params, json }) => { + json(200, { params }); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/parseBody/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/parseBody/index.tsx new file mode 100644 index 00000000000..5f8b46ec518 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/parseBody/index.tsx @@ -0,0 +1,17 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ html }) => { + html( + 200, + ` +
+ + +
+ ` + ); +}; + +export const onPost: RequestHandler = async ({ parseBody, json }) => { + json(200, { body: await parseBody() }); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/platform/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/platform/index.tsx new file mode 100644 index 00000000000..6a48f0031df --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/platform/index.tsx @@ -0,0 +1,5 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ platform, json }) => { + json(200, Object.keys(platform)); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/proxy/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/proxy/index.tsx new file mode 100644 index 00000000000..d970131f804 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/proxy/index.tsx @@ -0,0 +1,8 @@ +import type { RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ send, url }) => { + const response = await fetch( + new URL('/demo/qwikrouter/middleware/json/', url) + ); + send(response.status, await response.text()); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/query/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/query/index.tsx new file mode 100644 index 00000000000..dfdade351bf --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/query/index.tsx @@ -0,0 +1,7 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ query, json }) => { + const obj: Record = {}; + query.forEach((v, k) => (obj[k] = v)); + json(200, obj); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/redirect/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/redirect/index.tsx new file mode 100644 index 00000000000..1e3027e40c0 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/redirect/index.tsx @@ -0,0 +1,8 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ redirect, url }) => { + throw redirect( + 308, + new URL('/demo/qwikrouter/middleware/status/', url).toString() + ); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/request/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/request/index.tsx new file mode 100644 index 00000000000..abad06db8c2 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/request/index.tsx @@ -0,0 +1,7 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ json, request }) => { + const obj: Record = {}; + request.headers.forEach((v, k) => (obj[k] = v)); + json(200, { headers: obj }); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/send/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/send/index.tsx new file mode 100644 index 00000000000..4079702dcf1 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/send/index.tsx @@ -0,0 +1,11 @@ +import type { RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async (requestEvent) => { + const response = new Response('Hello World', { + status: 200, + headers: { + 'Content-Type': 'text/plain', + }, + }); + requestEvent.send(response); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/sharedMap/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/sharedMap/index.tsx new file mode 100644 index 00000000000..e820fe05648 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/sharedMap/index.tsx @@ -0,0 +1,50 @@ +import { + routeLoader$, + type Cookie, + type RequestHandler, +} from '@qwik.dev/router'; +import { component$ } from '@qwik.dev/core'; + +interface User { + username: string; + email: string; +} + +export const onRequest: RequestHandler = async ({ + sharedMap, + cookie, + send, +}) => { + const user = loadUserFromCookie(cookie); + if (user) { + sharedMap.set('user', user); + } else { + throw send(401, 'NOT_AUTHORIZED'); + } +}; + +function loadUserFromCookie(cookie: Cookie): User | null { + // this is where you would check cookie for user. + if (cookie) { + // just return mock user for this demo. + return { + username: `Mock User`, + email: `mock@users.com`, + }; + } else { + return null; + } +} + +export const useUser = routeLoader$(({ sharedMap }) => { + return sharedMap.get('user') as User; +}); + +export default component$(() => { + const log = useUser(); + return ( +
+ {log.value.username} ({log.value.email}) +
+ ); +}); diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/status/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/status/index.tsx new file mode 100644 index 00000000000..b44adf3edae --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/status/index.tsx @@ -0,0 +1,9 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ status, getWritableStream }) => { + status(200); + const stream = getWritableStream(); + const writer = stream.getWriter(); + writer.write(new TextEncoder().encode('Hello World!')); + writer.close(); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/text/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/text/index.tsx new file mode 100644 index 00000000000..ae387d835c5 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/text/index.tsx @@ -0,0 +1,5 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ text }) => { + text(200, 'Text based response.'); +}; diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/throw/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/throw/index.tsx new file mode 100644 index 00000000000..9a85cd6f174 --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/throw/index.tsx @@ -0,0 +1,24 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onRequest: RequestHandler = async ({ next, sharedMap, json }) => { + const log: string[] = []; + sharedMap.set('log', log); + + log.push('onRequest'); + if (isLoggedIn()) { + // normal behavior call next middleware + await next(); + } else { + // If not logged in throw to prevent implicit call to the next middleware. + throw json(404, log); + } +}; + +export const onGet: RequestHandler = async ({ sharedMap }) => { + const log = sharedMap.get('log') as string[]; + log.push('onGET'); +}; + +function isLoggedIn() { + return false; // always return false as mock example +} diff --git a/packages/docs/src/routes/demo/qwikrouter/middleware/url/index.tsx b/packages/docs/src/routes/demo/qwikrouter/middleware/url/index.tsx new file mode 100644 index 00000000000..c34581121ab --- /dev/null +++ b/packages/docs/src/routes/demo/qwikrouter/middleware/url/index.tsx @@ -0,0 +1,5 @@ +import { type RequestHandler } from '@qwik.dev/router'; + +export const onGet: RequestHandler = async ({ url, json }) => { + json(200, { url: url.toString() }); +}; diff --git a/packages/docs/src/routes/demo/react/children/index.tsx b/packages/docs/src/routes/demo/react/children/index.tsx index 2aaaa205fcc..67ff173fe75 100644 --- a/packages/docs/src/routes/demo/react/children/index.tsx +++ b/packages/docs/src/routes/demo/react/children/index.tsx @@ -1,4 +1,4 @@ -import { component$, useSignal } from '@builder.io/qwik'; +import { component$, useSignal } from '@qwik.dev/core'; import { QFrame } from './react'; export default component$(() => { diff --git a/packages/docs/src/routes/demo/react/children/react.tsx b/packages/docs/src/routes/demo/react/children/react.tsx index 40597267175..4cf1d252040 100644 --- a/packages/docs/src/routes/demo/react/children/react.tsx +++ b/packages/docs/src/routes/demo/react/children/react.tsx @@ -1,7 +1,7 @@ /** @jsxImportSource react */ import { type ReactNode } from 'react'; -import { qwikify$ } from '@builder.io/qwik-react'; +import { qwikify$ } from '@qwik.dev/react'; function Frame({ children }: { children?: ReactNode[] }) { console.log('React Render'); diff --git a/packages/docs/src/routes/demo/react/counter-simple-hover/index.tsx b/packages/docs/src/routes/demo/react/counter-simple-hover/index.tsx index 317e0e00913..75fb77414ab 100644 --- a/packages/docs/src/routes/demo/react/counter-simple-hover/index.tsx +++ b/packages/docs/src/routes/demo/react/counter-simple-hover/index.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import { QCounter } from './react'; export default component$(() => { diff --git a/packages/docs/src/routes/demo/react/counter-simple-hover/react.tsx b/packages/docs/src/routes/demo/react/counter-simple-hover/react.tsx index a8b03e8ac8d..a2fbc058710 100644 --- a/packages/docs/src/routes/demo/react/counter-simple-hover/react.tsx +++ b/packages/docs/src/routes/demo/react/counter-simple-hover/react.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource react */ -import { qwikify$ } from '@builder.io/qwik-react'; +import { qwikify$ } from '@qwik.dev/react'; import { useState } from 'react'; // Create React component standard way diff --git a/packages/docs/src/routes/demo/react/counter-simple/index.tsx b/packages/docs/src/routes/demo/react/counter-simple/index.tsx index c408137febf..af560d041a4 100644 --- a/packages/docs/src/routes/demo/react/counter-simple/index.tsx +++ b/packages/docs/src/routes/demo/react/counter-simple/index.tsx @@ -1,4 +1,4 @@ -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import { QCounter } from './react'; export default component$(() => { diff --git a/packages/docs/src/routes/demo/react/counter-simple/react.tsx b/packages/docs/src/routes/demo/react/counter-simple/react.tsx index 7b13adec884..c152882c431 100644 --- a/packages/docs/src/routes/demo/react/counter-simple/react.tsx +++ b/packages/docs/src/routes/demo/react/counter-simple/react.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource react */ -import { qwikify$ } from '@builder.io/qwik-react'; +import { qwikify$ } from '@qwik.dev/react'; import { useState } from 'react'; // Create React component standard way diff --git a/packages/docs/src/routes/demo/react/counter-two-islands-host/index.tsx b/packages/docs/src/routes/demo/react/counter-two-islands-host/index.tsx index 397e250f017..6232245f16d 100644 --- a/packages/docs/src/routes/demo/react/counter-two-islands-host/index.tsx +++ b/packages/docs/src/routes/demo/react/counter-two-islands-host/index.tsx @@ -1,4 +1,4 @@ -import { component$, useSignal } from '@builder.io/qwik'; +import { component$, useSignal } from '@qwik.dev/core'; import { QButton, QDisplay } from './react'; export default component$(() => { diff --git a/packages/docs/src/routes/demo/react/counter-two-islands-host/react.tsx b/packages/docs/src/routes/demo/react/counter-two-islands-host/react.tsx index 28c161bc411..ba19893db46 100644 --- a/packages/docs/src/routes/demo/react/counter-two-islands-host/react.tsx +++ b/packages/docs/src/routes/demo/react/counter-two-islands-host/react.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource react */ -import { qwikify$ } from '@builder.io/qwik-react'; +import { qwikify$ } from '@qwik.dev/react'; import { type ReactNode } from 'react'; function Button({ children }: { children?: ReactNode[] }) { diff --git a/packages/docs/src/routes/demo/react/counter-two-islands/index.tsx b/packages/docs/src/routes/demo/react/counter-two-islands/index.tsx index 9b711bcb22f..8995495d4b0 100644 --- a/packages/docs/src/routes/demo/react/counter-two-islands/index.tsx +++ b/packages/docs/src/routes/demo/react/counter-two-islands/index.tsx @@ -1,4 +1,4 @@ -import { component$, useSignal } from '@builder.io/qwik'; +import { component$, useSignal } from '@qwik.dev/core'; import { QButton, QDisplay } from './react'; export default component$(() => { diff --git a/packages/docs/src/routes/demo/react/counter-two-islands/react.tsx b/packages/docs/src/routes/demo/react/counter-two-islands/react.tsx index de18d5bfbf6..87923bfeba1 100644 --- a/packages/docs/src/routes/demo/react/counter-two-islands/react.tsx +++ b/packages/docs/src/routes/demo/react/counter-two-islands/react.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource react */ -import { qwikify$ } from '@builder.io/qwik-react'; +import { qwikify$ } from '@qwik.dev/react'; function Button({ onClick }: { onClick: () => void }) { console.log('React
+ + + + +
+ serializer-signal-usage + Ensure signals used in update() are also used in deserialize() +
+
+ + ✅ + + @@ -383,7 +411,7 @@ export const Counter = (() => {
```tsx {1,4} /print/#a /$((msg: string)/#b) -import { component$, useTask$, $ } from '@builder.io/qwik'; +import { component$, useTask$, $ } from '@qwik.dev/core'; export const HelloWorld = component$(() => { const print = $((msg: string) => { @@ -402,7 +430,7 @@ export const HelloWorld = component$(() => {
```tsx {1,4} /print/#a /(msg: string)/#b) -import { component$, useTask$ } from '@builder.io/qwik'; +import { component$, useTask$ } from '@qwik.dev/core'; export const HelloWorld = component$(() => { const print = (msg: string) => { @@ -424,7 +452,7 @@ export const HelloWorld = component$(() => {
```tsx {1} /click/#a -import { component$, $ } from '@builder.io/qwik'; +import { component$, $ } from '@qwik.dev/core'; export const HelloWorld = component$(() => { const click = $(() => console.log()); @@ -438,7 +466,7 @@ export const HelloWorld = component$(() => {
```tsx {1} /click/#a -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const HelloWorld = component$(() => { const click = () => console.log(); @@ -455,7 +483,7 @@ export const HelloWorld = component$(() => {
```tsx {4} /person/#a -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const HelloWorld = component$(() => { const person = { name: 'Bob' }; @@ -474,7 +502,7 @@ export const HelloWorld = component$(() => {
```tsx {4} /personName/#a -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const HelloWorld = component$(() => { let personName = 'Bob'; @@ -504,7 +532,7 @@ export const HelloWorld = component$(() => {
```tsx {3} /routeLoader$/#a title="src/routes/product/[productId]/index.tsx" -import { routeLoader$ } from '@builder.io/qwik-city'; +import { routeLoader$ } from '@qwik.dev/router'; export const useProductDetails = routeLoader$(async (requestEvent) => { const res = await fetch(`https://.../products/${requestEvent.params.productId}`); @@ -517,7 +545,7 @@ export const useProductDetails = routeLoader$(async (requestEvent) => {
```tsx {3} /routeLoader$/#a title="src/components/product/product.tsx" -import { routeLoader$ } from '@builder.io/qwik-city'; +import { routeLoader$ } from '@qwik.dev/router'; export const useProductDetails = routeLoader$(async (requestEvent) => { const res = await fetch(`https://.../products/${requestEvent.params.productId}`); @@ -533,7 +561,7 @@ export const useProductDetails = routeLoader$(async (requestEvent) => {
```tsx {3} /export/#a -import { routeLoader$ } from '@builder.io/qwik-city'; +import { routeLoader$ } from '@qwik.dev/router'; export const useProductDetails = routeLoader$(async (requestEvent) => { const res = await fetch(`https://.../products/${requestEvent.params.productId}`); @@ -546,7 +574,7 @@ export const useProductDetails = routeLoader$(async (requestEvent) => {
```tsx {3} -import { routeLoader$ } from '@builder.io/qwik-city'; +import { routeLoader$ } from '@qwik.dev/router'; const useProductDetails = routeLoader$(async (requestEvent) => { const res = await fetch(`https://.../products/${requestEvent.params.productId}`); @@ -562,7 +590,7 @@ const useProductDetails = routeLoader$(async (requestEvent) => {
```tsx {3} /use/#a -import { routeLoader$ } from '@builder.io/qwik-city'; +import { routeLoader$ } from '@qwik.dev/router'; export const useProductDetails = routeLoader$(async (requestEvent) => { const res = await fetch(`https://.../products/${requestEvent.params.productId}`); @@ -575,7 +603,7 @@ export const useProductDetails = routeLoader$(async (requestEvent) => {
```tsx {3} /get/#a -import { routeLoader$ } from '@builder.io/qwik-city'; +import { routeLoader$ } from '@qwik.dev/router'; export const getProductDetails = routeLoader$(async (requestEvent) => { const res = await fetch(`https://.../products/${requestEvent.params.productId}`); @@ -591,7 +619,7 @@ export const getProductDetails = routeLoader$(async (requestEvent) => {
```tsx {3} /=>/#a -import { routeLoader$ } from '@builder.io/qwik-city'; +import { routeLoader$ } from '@qwik.dev/router'; export const useProductDetails = routeLoader$(async (requestEvent) => { const res = await fetch(`https://.../products/${requestEvent.params.productId}`); @@ -604,7 +632,7 @@ export const useProductDetails = routeLoader$(async (requestEvent) => {
```tsx {9} /fetcher/#a -import { routeLoader$ } from '@builder.io/qwik-city'; +import { routeLoader$ } from '@qwik.dev/router'; async function fetcher() { const res = await fetch(`https://.../products/${requestEvent.params.productId}`); @@ -655,7 +683,7 @@ export const useProductDetails = routeLoader$(fetcher);
```tsx {7-10,15,16} /class/#a -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import styles from './MyComponent.module.css'; export default component$((props) => { @@ -679,7 +707,7 @@ export default component$((props) => {
```tsx {2,7-13} /classnames/#a -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import classnames from 'classnames'; import styles from './MyComponent.module.css'; @@ -735,7 +763,7 @@ export default component$((props) => {
```tsx {13} /key=/#a -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const Person = component$(() => { const person = { @@ -758,7 +786,7 @@ export const Person = component$(() => {
```tsx {13} -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const Person = component$(() => { const person = { @@ -784,7 +812,7 @@ export const Person = component$(() => {
```tsx {14} /Fragment/#a -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import Card from './Card'; import Summary from './Summary'; @@ -810,7 +838,7 @@ export const Person = component$(() => {
```tsx {14} -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; import Card from './Card'; import Summary from './Summary'; @@ -839,7 +867,7 @@ export const Person = component$(() => {
```tsx {9} /key=/#a -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const ColorList = component$(() => { const colors = ['red', 'green', 'blue']; @@ -858,7 +886,7 @@ export const ColorList = component$(() => {
```tsx {9} -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const ColorList = component$(() => { const colors = ['red', 'green', 'blue']; @@ -880,7 +908,7 @@ export const ColorList = component$(() => {
```tsx {8,11} /Fragment/#a -import { component$, Fragment } from '@builder.io/qwik'; +import { component$, Fragment } from '@qwik.dev/core'; export const ColorList = component$(() => { const colors = ['red', 'green', 'blue']; @@ -900,7 +928,7 @@ export const ColorList = component$(() => {
```tsx {8,11} -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const ColorList = component$(() => { const colors = ['red', 'green', 'blue']; @@ -923,7 +951,7 @@ export const ColorList = component$(() => {
```tsx {9} /key=/#a -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const ColorList = component$(() => { const colors = ['red', 'green', 'blue']; @@ -942,7 +970,7 @@ export const ColorList = component$(() => {
```tsx {9} /key=/#a /not-a-good-idea/#b -import { component$ } from '@builder.io/qwik'; +import { component$ } from '@qwik.dev/core'; export const ColorList = component$(() => { const colors = ['red', 'green', 'blue']; @@ -972,8 +1000,8 @@ export const ColorList = component$(() => {
```tsx {4,12} /serverGreeter/#a -import { $, component$ } from '@builder.io/qwik'; -import { server$ } from '@builder.io/qwik-city'; +import { component$ } from '@qwik.dev/core'; +import { server$ } from '@qwik.dev/router'; const serverGreeter = server$((firstName: string, lastName: string) => { const greeting = `Hello ${firstName} ${lastName}`; @@ -982,10 +1010,10 @@ const serverGreeter = server$((firstName: string, lastName: string) => { export default component$(() => ( @@ -997,8 +1025,8 @@ export default component$(() => (
```tsx {4,12} /serverGreeter/#a -import { component$ } from '@builder.io/qwik'; -import { server$ } from '@builder.io/qwik-city'; +import { component$ } from '@qwik.dev/core'; +import { server$ } from '@qwik.dev/router'; const serverGreeter = server$((firstName: string, lastName: string) => { const greeting = `Hello ${firstName} ${lastName}`; @@ -1086,5 +1114,61 @@ import Image from '~/media/image.png';
+ +
+

serializer-signal-usage

+ Ensure signals used in update() are also used in deserialize() + +

serializerSignalMismatch

+

Examples of correct code for this rule:

+
+ +```tsx /countSignal/#a +import { useSignal, useSerializer$ } from '@qwik.dev/core'; +import MyClass from './my-class'; + +export default component$(() => { + const countSignal = useSignal(0); + + useSerializer$(() => ({ + deserialize: () => new MyClass(countSignal.value), + update: (myClass) => { + myClass.count = countSignal.value; + return myClass; + } + })); + + return
{myClass.count}
; +}); +``` +
+

Examples of incorrect code for this rule:

+
+ +```tsx /countSignal/#a +import { useSignal, useSerializer$ } from '@qwik.dev/core'; +import MyClass from './my-class'; + +export default component$(() => { + const countSignal = useSignal(0); + + useSerializer$(() => ({ + deserialize: (count) => new MyClass(count), + initial: 2, + update: (myClass) => { + myClass.count = countSignal.value; + return myClass; + } + })); + + return
{myClass.count}
; +}); +``` +

The countSignal is used in update() but not in deserialize()

+
+ + +
+
\ No newline at end of file diff --git a/packages/docs/src/routes/docs/(qwik)/advanced/library/index.mdx b/packages/docs/src/routes/docs/(qwik)/advanced/library/index.mdx index dcfa74a078d..87a5ae2f0f1 100644 --- a/packages/docs/src/routes/docs/(qwik)/advanced/library/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/advanced/library/index.mdx @@ -57,10 +57,10 @@ This will create a new folder called `my-library` with the following structure: │   ├── index.ts │   └── root.tsx ├── tsconfig.json -└── vite.config.ts +└── vite.config.mts ``` -The most important files of a library are a properly configured `package.json` and `vite.config.ts`. +The most important files of a library are a properly configured `package.json` and `vite.config.mts`. ## package.json @@ -91,11 +91,11 @@ Notice the `qwik` field, this is the entry point for the Qwik Optimizer, it will > The file must be called with the `.qwik.mjs` extension, otherwise the Qwik Optimizer will not recognize it. -## vite.config.ts +## vite.config.mts ```ts {8-12} import { defineConfig } from 'vite'; -import { qwikVite } from '@builder.io/qwik/optimizer'; +import { qwikVite } from '@qwik.dev/core/optimizer'; export default defineConfig(() => { return { @@ -126,7 +126,7 @@ export { Counter } from './components/counter/counter'; ## Libraries are also Apps -The library starter is also a standalone Qwik app (without routing, nor Qwik City), this is the reason why you will find `entry.dev.tsx`, `entry.ssr.tsx` and `root.tsx` files. +The library starter is also a standalone Qwik app (without routing, nor Qwik Router), this is the reason why you will find `entry.dev.tsx`, `entry.ssr.tsx` and `root.tsx` files. Do not worry about them, they won't be part of the final library, but they are useful during development and testing, so you can test your components in a real Qwik app. diff --git a/packages/docs/src/routes/docs/(qwik)/advanced/qrl/index.mdx b/packages/docs/src/routes/docs/(qwik)/advanced/qrl/index.mdx index 7f51e271578..7865e3736e5 100644 --- a/packages/docs/src/routes/docs/(qwik)/advanced/qrl/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/advanced/qrl/index.mdx @@ -127,7 +127,7 @@ The main thing to note is the `on:click` attribute. This attribute gets read by }; ``` 6. At this point, the execution is handed off from Qwikloader to the lazy-loaded chunk. This is done so that the Qwikloader can be as small as possible as it is inlined into the HTML. -7. `useLexicalScope` is imported from `@builder.io/qwik` and is responsible for retrieving the `count` and `props`. +7. `useLexicalScope` is imported from `@qwik.dev/core` and is responsible for retrieving the `count` and `props`. `const [count, props] = useLexicalScope();` 8. Parse the `` JSON and distribute the deserialized objects per `q:obj` attribute. In our case - `
` gets object with id `123`. This will be the `count` created in the `Counter_onMount` function. diff --git a/packages/docs/src/routes/docs/(qwik)/advanced/vite/index.mdx b/packages/docs/src/routes/docs/(qwik)/advanced/vite/index.mdx index 30dd1917f1c..979b01615e1 100644 --- a/packages/docs/src/routes/docs/(qwik)/advanced/vite/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/advanced/vite/index.mdx @@ -27,17 +27,17 @@ After scaffolding a new Qwik project, you'll find a `vite.config.js` file in the ## Vite Plugins -Qwik comes with two plugins that make it easy to use Vite with Qwik and Qwik City. +Qwik comes with two plugins that make it easy to use Vite with Qwik and Qwik Router. -```ts title="vite.config.ts" +```ts title="vite.config.mts" import { defineConfig } from 'vite'; -import { qwikCity } from '@builder.io/qwik-city/vite'; -import { qwikVite } from '@builder.io/qwik/optimizer'; +import { qwikRouter } from '@qwik.dev/router/vite'; +import { qwikVite } from '@qwik.dev/core/optimizer'; import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig(() => { return { - plugins: [qwikCity(), qwikVite(), tsconfigPaths({ root: '.' })], + plugins: [qwikRouter(), qwikVite(), tsconfigPaths({ root: '.' })], }; }); ``` @@ -199,9 +199,9 @@ devTools?: { }; ``` -### `qwikCity()` +### `qwikRouter()` -To modify the configuration, you can pass an object to the `qwikCity` function. Possible options are: +To modify the configuration, you can pass an object to the `qwikRouter` function. Possible options are: #### `routesDir` @@ -246,7 +246,7 @@ trailingSlash?: boolean; ```js /** - * Enable or disable MDX plugins included by default in qwik-city. + * Enable or disable MDX plugins included by default in qwik-router. */ mdxPlugins?: MdxPlugins; ``` diff --git a/packages/docs/src/routes/docs/(qwik)/components/context/index.mdx b/packages/docs/src/routes/docs/(qwik)/components/context/index.mdx index da50d4ca22d..21b9d29c1d8 100644 --- a/packages/docs/src/routes/docs/(qwik)/components/context/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/components/context/index.mdx @@ -24,7 +24,7 @@ import CodeSandbox from '../../../../../components/code-sandbox/index.tsx'; Qwik provides a context API, which solves the problem of props drilling and it is very similar to React's functional `useContext()`. In fact, Qwik's context API is the most efficient way to pass down data to different components, reducing overhead, generating less code, and allowing Qwik to more effectively [treeshake](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking) unused data. -Qwik's context API is made of 3 methods, importable from `@builder.io/qwik`: +Qwik's context API is made of 3 methods, importable from `@qwik.dev/core`: - [`createContextId(contextName: string): ContextId`](#createcontextid) - [`useContextProvider(ctx: ContextId, value: VALUE): void`](#usecontextprovider) @@ -32,12 +32,12 @@ Qwik's context API is made of 3 methods, importable from `@builder.io/qwik`: ```tsx /createContextId/#a /useContext/#b /useContextProvider/#c -import { type Signal, component$, useSignal } from '@builder.io/qwik'; +import { type Signal, component$, useSignal } from '@qwik.dev/core'; import { useContext, useContextProvider, createContextId, -} from '@builder.io/qwik'; +} from '@qwik.dev/core'; export const ThemeContext = createContextId>( 'docs.theme-context' @@ -79,31 +79,31 @@ export interface GenericType { ... } -export const QwikCityContext = createContextId(name: string): ContextId; +export const QwikRouterContext = createContextId(name: string): ContextId; ``` ### Parameters -- `name`: is a unique string given to `createContextId` as an identifier of the context. This will avoid conflicts when there are multiple contexts. It is advised to use a naming convention like `io.builder.qwik.city`. +- `name`: is a unique string given to `createContextId` as an identifier of the context. This will avoid conflicts when there are multiple contexts. It is advised to use a naming convention like `dev.qwik.router`. ### Returns -Notice that the value returned by `createContextId()` does not hold any state, it is an immutable ID object i.e. `{ id: 'io.builder.qwik.city' }`. It's only used to describe the name and type of the context, like an address or an identifier. Since it doesn't hold any state, it can be initialized as a singleton and exported from a shared module. +Notice that the value returned by `createContextId()` does not hold any state, it is an immutable ID object i.e. `{ id: 'dev.qwik.router' }`. It's only used to describe the name and type of the context, like an address or an identifier. Since it doesn't hold any state, it can be initialized as a singleton and exported from a shared module. ## `useContextProvider()` This method is used to create a Context for a specific component and its descendants, using the `ContextId` as the key identifier of the context. -```tsx {9, 10, 11} /QwikCityContext/#a /PlainArrayContext/#b /AppNameContext/#c title="src/components/Parent.tsx" -import { component$, useStore, useContextProvider } from '@builder.io/qwik'; +```tsx {9, 10, 11} /QwikRouterContext/#a /PlainArrayContext/#b /AppNameContext/#c title="src/components/Parent.tsx" +import { component$, useStore, useContextProvider } from '@qwik.dev/core'; export const Parent = component$(() => { - const qwikCityObject = useStore({ + const qwikRouterObject = useStore({ ... }); - useContextProvider(QwikCityContext, qwikCityObject); + useContextProvider(QwikRouterContext, qwikRouterObject); useContextProvider(PlainArrayContext, [1, 2, 3]) useContextProvider(AppNameContext, "My Qwik App") @@ -128,11 +128,11 @@ export const Parent = component$(() => { This method is used to get the value of `Context` which is **provided** by Parent Component. -```tsx {4,5,6} /useContext/ /QwikCityContext/#a /PlainArrayContext/#b /AppNameContext/#c title="src/components/Children.tsx" -import { component$, useContext } from '@builder.io/qwik'; +```tsx {4,5,6} /useContext/ /QwikRouterContext/#a /PlainArrayContext/#b /AppNameContext/#c title="src/components/Children.tsx" +import { component$, useContext } from '@qwik.dev/core'; export const Children = component$(() => { - const qwikCityObject = useContext(QwikCityContext); + const qwikRouterObject = useContext(QwikRouterContext); const plainArray = useContext(PlainArrayContext); const appName = useContext(AppNameContext); diff --git a/packages/docs/src/routes/docs/(qwik)/components/events/index.mdx b/packages/docs/src/routes/docs/(qwik)/components/events/index.mdx index 7c0cfaf4f29..9d8a0f8aa5b 100644 --- a/packages/docs/src/routes/docs/(qwik)/components/events/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/components/events/index.mdx @@ -43,7 +43,7 @@ In the following example, the `onClick$` attribute of the `
@@ -88,3 +88,6 @@ Qwik is a new kind of web framework that can deliver instant loading web applica

Qwik delivers sub-second full page loads, even on mobile, by serving pure HTML and executing JavaScript when your users opt-in.

+ + + diff --git a/packages/docs/src/routes/docs/(qwikcity)/action/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/action/index.mdx deleted file mode 100644 index a03601c6e0c..00000000000 --- a/packages/docs/src/routes/docs/(qwikcity)/action/index.mdx +++ /dev/null @@ -1,469 +0,0 @@ ---- -title: RouteAction$ | QwikCity -description: Learn about actions in QwikCity, allowing form submissions, and performing side effects such as writing to a database or sending an email. -contributors: - - manucorporat - - cunzaizhuyi - - forresst - - keuller - - hamatoyogi - - AnthonyPAlicea - - the-r3aper7 - - thejackshelton - - adnanebrahimi - - mhevery - - ulic75 - - CoralWombat - - tzdesign - - igorbabko - - gioboa - - mrhoodz - - VinuB-Dev - - aivarsliepa - - wtlin1228 - - adamdbradley - - gioboa - - jemsco - - tzdesign - - shairez -updated_at: '2023-12-15T11:00:00Z' -created_at: '2023-03-20T23:45:13Z' ---- - -# `routeAction$()` - -`routeAction$()` is used to define functions called actions that execute exclusively on the server, and only when explicitly called. Actions can have side effects such as writing to a database or sending an email, that cannot happen during client-side rendering. This makes them ideal for handling form submissions, performing operations with side effects, and then returning data back to the client/browser where it can be used to update the UI. - -Actions can be declared using `routeAction$()` or `globalAction$()` exported from `@builder.io/qwik-city`. - -```tsx {4,16} /useAddUser/ /firstName/#a /lastName/#b /email/#c title="src/routes/layout.tsx" -import { component$ } from '@builder.io/qwik'; -import { routeAction$, Form } from '@builder.io/qwik-city'; - -export const useAddUser = routeAction$(async (data, requestEvent) => { - // This will only run on the server when the user submits the form (or when the action is called programmatically) - const userID = await db.users.add({ - firstName: data.firstName, - lastName: data.lastName, - }); - return { - success: true, - userID, - }; -}); - -export default component$(() => { - const action = useAddUser(); - - return ( - <> - - - - - - {action.value?.success && ( - // When the action is done successfully, the `action.value` property will contain the return value of the action -

User {action.value.userID} added successfully

- )} - - ); -}); -``` - -> Since actions are not executed during rendering, they can have side effects such as writing to a database, or sending an email. An action only runs when called explicitly. - - -## Using actions with `
` - -The best way to call an action is using the `` component exported in `@builder.io/qwik-city`. - -```tsx title="src/routes/index.tsx" -import { component$ } from '@builder.io/qwik'; -import { routeAction$, Form } from '@builder.io/qwik-city'; - -export const useAddUser = routeAction$(async (user) => { - const userID = await db.users.add(user); - return { - success: true, - userID, - }; -}); - -export default component$(() => { - const action = useAddUser(); - return ( - - - - {action.value?.success &&

User added successfully

} -
- ); -}); -``` - -Under the hood, the `
` component uses a native HTML `` element, so it will work without JavaScript. - -When JS is enabled, the `` component will intercept the form submission and trigger the action in SPA mode. Allowing for a full SPA experience. - -> This is to clarify that the server re-renders the whole page and re-executes everything, so if you have any [routeLoader$](/docs/route-loader/) they will be executed too. - -[Complex forms](/docs/advanced/complex-forms) can be created using dot notation. - -## Using actions programmatically - -Actions can also be triggered programmatically using the `action.submit()` method (i.e. you don't need a `` component). However, you can trigger the action from a button click or any other event, just like you would do with a function. - -```tsx {18} title="src/routes/index.tsx" -import { component$ } from '@builder.io/qwik'; -import { routeAction$ } from '@builder.io/qwik-city'; - -export const useAddUser = routeAction$(async (user) => { - const userID = await db.users.add(user); - return { - success: true, - userID, - }; -}); - -export default component$(() => { - const action = useAddUser(); - return ( -
- - {action.value?.success &&

User added successfully

} -
- ); -}); -``` - -In the example above, the `addUser` action is triggered when the user clicks the button. The `action.submit()` method returns a `Promise` that resolves when the action is done. - -### Uploading files - -When using `` with a file input, the submission will be sent as a `multipart/form-data` request. - -But when using actions programmatically, you can still upload files by passing a `File` object to the `action.submit()` method by creating a `FormData` object and appending the file to it. - -```tsx title="src/routes/index.tsx" - -import { component$ } from '@builder.io/qwik'; -import { routeAction$ } from '@builder.io/qwik-city'; - -export const useUploadFile = routeAction$(async ({file}) => { - // save the file somewhere... - return { - success: true, - }; -}); - -export default component$(() => { - const action = useUploadFile(); - const fileUploadRef = useSignal(); - return ( -
- - - -
- ); -}); - -``` - - - -## Actions with Event Handlers - -The `onSubmitCompleted$` event handler can be used after an action is successfully executed and returns some data. This is useful for performing tasks, such as resetting UI elements or updating the application state, once an action has been completed. - -Here's an example of the `onSubmitCompleted$` handler used to edit an item in a EditForm component of a todo app. - -```tsx title="src/components/EditForm.tsx" -import { component$, type Signal, useSignal } from '@builder.io/qwik'; -import { Form } from '@builder.io/qwik-city'; -import { type ListItem, useEditFromListAction } from '../../routes/index'; - -export interface EditFormProps { - item: listItem; - editingIdSignal: Signal; -} - -const EditForm = component$( - ({ item, editingIdSignal }: EditFormProps) => { - const editAction = useEditFromListAction(); - - return ( -
- { - editingIdSignal.value = ''; - }} - spaReset - > - - {/* Sends item.id with form data on submission. */} - - - - -
- -
-
- ); - } -); - -export default EditForm; -``` - -In this example, `onSubmitCompleted$` is used to reset the editingIdSignal value to an empty string once the form submission is completed successfully. This allows the application to update its state and return to the default view. - -## Validation and type safety - -Qwik comes with built-in support for [Zod](https://zod.dev/), a TypeScript-first schema validation that can be used directly with actions, using the `zod$()` function. - -Actions + [Zod](https://zod.dev/) allows to create type safe forms, where the data is validated server side before the action is executed. - -```tsx {16-20} /firstName/#a /lastName/#b title="src/routes/index.tsx" -import { component$ } from '@builder.io/qwik'; -import { routeAction$, zod$, z, Form } from '@builder.io/qwik-city'; - -export const useAddUser = routeAction$( - async (user) => { - // The "user" is strongly typed: { firstName: string, lastName: string } - const userID = await db.users.add({ - firstName: user.firstName, - lastName: user.lastName, - }); - return { - success: true, - userID, - }; - }, - // Zod schema is used to validate that the FormData includes "firstName" and "lastName" - zod$({ - firstName: z.string(), - lastName: z.string(), - }) -); - -export default component$(() => { - const action = useAddUser(); - return ( - <> -
- - - - {action.value?.failed &&

{action.value.fieldErrors?.firstName}

} - -
- {action.value?.success && ( -

User {action.value.userID} added successfully

- )} - - ); -}); -``` - -When submitting data to a `routeAction()`, the data is validated against the Zod schema. If the data is invalid, the action will put the validation error in the `routeAction.value` property. - -Please refer to the [Zod documentation](https://zod.dev/) for more information on how to use Zod schemas. - -### Advanced event based validation - -The constructor of ```zod$``` can also take a function, as the first argument is zod itself, so you can use this directly to build the schema. -The second parameter is the RequestEvent to construct an event-based zod schema. -Especially in combination with ```refine``` and ```superRefine``` in zod, the only limit is your imagination. - - -```tsx {5-5} /ev/#a title="Advanced event based validation" -export const useAddUser = routeAction$( - async (user) => { - // The "user" is still strongly typed, but firstname - // is now optional: { firstName?: string | undefined, lastName: string } - const userID = await db.users.add({ - firstName: user.firstName, - lastName: user.lastName, - }); - return { - success: true, - userID, - }; - }, - // Zod schema is used to validate that the FormData includes "firstName" and "lastName" - zod$((z, ev) => { - // The first name is optional if the url contains the query parameter "firstname=optional" - const firstName = - ev.url.searchParams.get("firstname") === "optional" - ? z.string().optional() - : z.string().nonempty(); - - return z.object({ - firstName, - lastName: z.string(), - }); - }) -); -``` - -## HTTP request and response - -`routeAction$` and `globalAction$` have access to the `RequestEvent` object which includes information about the current HTTP request and response. - -This allows actions to access the request headers, cookies, url and environment variables within the `routeAction$` function. - -```tsx /requestEvent/ title="src/routes/product/[user]/index.tsx" -import { routeAction$ } from '@builder.io/qwik-city'; - -// The second argument of the action is the `RequestEvent` object -export const useProductRecommendations = routeAction$( - async (_data, requestEvent) => { - console.log('Request headers:', requestEvent.request.headers); - console.log('Request cookies:', requestEvent.cookie); - console.log('Request url:', requestEvent.url); - console.log('Request params:', requestEvent.params); - console.log('MY_ENV_VAR:', requestEvent.env.get('MY_ENV_VAR')); - } -); - -``` - -## Action Failures - -In order to return non-success values, the action must use the `fail()` method. - -```tsx /fail/ -import { routeAction$, zod$, z } from '@builder.io/qwik-city'; - -export const useAddUser = routeAction$( - async (user, { fail }) => { - // `user` is typed { name: string } - const userID = await db.users.add(user); - if (!userID) { - return fail(500, { - message: 'User could not be added', - }); - } - return { - userID, - }; - }, - zod$({ - name: z.string(), - }) -); -``` - -Failures are stored in the `action.value` property, just like the success value. However, the `action.value.failed` property is set to `true` when the action fails. Futhermore, failure messages can be found in the `fieldErrors` object according to properties defined in your Zod schema. - -The `fieldErrors` become a dot notation object. See [Complex forms](/docs/advanced/complex-forms) for more information. - -```tsx -import { component$ } from '@builder.io/qwik'; -import { Form } from '@builder.io/qwik-city'; - -export default component$(() => { - const action = useAddUser(); - return ( -
- - - {action.value?.failed &&

{action.value.fieldErrors.name}

} - {action.value?.userID &&

User added successfully

} -
- ); -}); -``` - -Thanks to Typescript type discrimination, you can use the `action.value.failed` property to discriminate between success and failure. - -## Previous form state - -When an action is triggered, the previous state is stored in the `action.formData` property. This is useful to display a loading state while the action is running. - -```tsx {12} /action.formData/ -import { component$ } from '@builder.io/qwik'; -import { routeAction$, Form, zod$, z } from '@builder.io/qwik-city'; - -export const useAddUser = routeAction$(async (user) => { - // handle action... -}); - -export default component$(() => { - const action = useAddUser(); - return ( -
- - -
- ); -}); -``` - -The `action.formData` is especially useful for retaining user-filled form data even after a page refresh. This enables a seamless SPA experience, even with JS disabled. - -## Route vs Global actions - -Actions can be declared using the `routeAction$()` or `globalAction$()` exported from `@builder.io/qwik-city`, the only difference between the two is that `routeAction$()` is scoped to a route, while `globalAction$()` is globally available across the whole app. - -It's recommended to start with `routeAction$()`. Use `globalAction$()` only when sharing an action across multiple routes, or if you wish to use the action in a component that is not a route. - -### `routeAction$()` - -`routeAction$()` can only be declared inside the `src/routes` folder, in a `layout.tsx` or `index.tsx` file, and they MUST be exported, just like a `routeLoader$()`. Since `routeAction$()`s are only accessible within the route it's declared, they are recommended when the action needs to access some user data, or it's a protected route. Think about it like a "private" action. - -> If you want to manage common reusable routeAction$() it is essential that this function is re-exported from within 'layout.tsx' or 'index.tsx file of the existing route otherwise it will not run or throw exception. For more information [check this section](/docs/(qwikcity)/re-exporting-loaders/index.mdx). - -```tsx title="src/routes/form/index.tsx" -import { routeAction$ } from '@builder.io/qwik-city'; - -export const useChangePassword = routeAction$((data) => { - // ... -}); -``` - -### `globalAction$()` - -`globalAction$()` can be declared anywhere in the `src` folder. Since `globalAction$()` are globally available, they are recommended when the action needs to be shared across multiple routes, or when the action doesn't need to access any user data. For example, a `useLogin` action that logs in a user. Think about it like a "public" action. - -```tsx title="src/components/login/login.tsx" -import { globalAction$ } from '@builder.io/qwik-city'; - -export const useLogin = globalAction$((data) => { - // ... -}); -``` diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/content-security-policy/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/content-security-policy/index.mdx deleted file mode 100644 index 7f61da2335b..00000000000 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/content-security-policy/index.mdx +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: Content Security Policy | Advanced -description: Learn how to create a content security policy (CSP) to keep your Qwik app safe. -contributors: - - tzdesign - - jordanw66 - - mrhoodz - - the-zimmermann - - hamatoyogi -updated_at: '2023-06-25T19:43:33Z' -created_at: '2023-06-15T11:45:23Z' ---- - -# Content Security Policy - -## What is CSP? - -[Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft, to site defacement, to malware distribution. - -## Steps to integrate - -These steps only work for SSR approach. If you are using the Server Side Generation approach, you don't have an up and running server, so this code will not run during the page request. - -### Add a route plugin - -To add the middleware plugin to your application, simply add a file named ```plugin@your-plugin-name.ts``` to the routes folder. -This file will be loaded with every request, allowing you to add headers, modify the response, and more. - -```bash {3} /plugin@csp\.ts/ title="Add the plugin" -src/ -└── routes/ - ├── plugin@csp.ts # The plugin which runs on every request (route middleware) - ├── contact/ - │ └── index.mdx # https://example.com/contact - ├── about/ - │ └── index.md # https://example.com/about - ├── index.mdx # https://example.com/ - │ - └── layout.tsx # This layout is used for all pages -``` - -#### Example - -> This template provides very permissive backward-compatible defaults. -> It is highly recommended that you customize it to better suit your specific use case. -> As this is an advanced topic, you should take a closer look at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) or [web.dev](https://web.dev/csp/) to get a better understanding of CSP. -> Please note that in dev mode the Vite scripts have no nonce and will report. For this reason, the example will not add csp in dev mode. - -```typescript title="src/routes/plugin@csp.ts" -import type { RequestHandler } from "@builder.io/qwik-city"; -import { isDev } from "@builder.io/qwik"; - -export const onRequest: RequestHandler = event => { - if (isDev) return; // Will not return CSP headers in dev mode - const nonce = Date.now().toString(36); // Your custom nonce logic here - event.sharedMap.set("@nonce", nonce); - const csp = [ - `default-src 'self' 'unsafe-inline'`, - `font-src 'self'`, - `img-src 'self' 'unsafe-inline' data:`, - `script-src 'self' 'unsafe-inline' https: 'nonce-${nonce}' 'strict-dynamic'`, - `style-src 'self' 'unsafe-inline'`, - `frame-src 'self' 'nonce-${nonce}'`, - `object-src 'none'`, - `base-uri 'self'`, - ]; - - event.headers.set("Content-Security-Policy", csp.join("; ")); -}; -``` - -### Custom scripts - -If you have custom script tags that you need to add the nonce to, you can use the `useServerData` hook to get the nonce from the server and add it to your script tags. - -```tsx {2,5} /nonce/ title="src/components/some-component.tsx" -export default component$(() => { - const nonce = useServerData("nonce"); - return ( -
- -
- ); -}); -``` - - -## Validate your CSP - -There is a great tool to validate your CSP: [https://csp-evaluator.withgoogle.com/](https://csp-evaluator.withgoogle.com/) diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/menu/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/menu/index.mdx deleted file mode 100644 index e54811fc887..00000000000 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/menu/index.mdx +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: Menu | Advanced -description: How to use menus to define site navigation. -contributors: - - manucorporat - - adamdbradley - - the-r3aper7 - - Oyemade - - mhevery - - nnelgxorz - - jakovljevic-mladen - - cunzaizhuyi - - AnthonyPAlicea - - mrhoodz - - hamatoyogi - - dejurin - - gioboa - - jemsco -updated_at: '2023-06-25T19:43:33Z' -created_at: '2023-03-20T23:45:13Z' ---- - -# Menu - -Menus allow you to describe the site navigation structure in a simple declarative way. Menus come in two steps: - -1. Defining a `menu.md` file that contains the menu structure for the directory it's in. -2. Using the [`useContent()`](/docs/(qwikcity)/api/index.mdx#usecontent) function to retrieve the menu structure in a template for rendering. [Read more here](/docs/(qwikcity)/api/index.mdx#usecontent) - -## File Structure - -First layout files as follows: - -```bash -src/ -└── routes/ - └── some/ - ├── menu.md - ├── layout.tsx - └── path/ - └── index.tsx # https://example.com/some/path -``` - -Navigating to `https://example.com/some/path` activates: - -- `src/routes/some/path/index.tsx`: This component will be used for rendering the page content. -- `src/routes/some/layout.tsx`: This layout will be used to provide content around the `src/routes/some/path/index.tsx`. Internally the layout can use `src/routes/some/menu.md` to render the menus. -- `src/routes/some/menu.md`: This file will be used to declare the menu structure which will be rendered by `src/routes/some/layout.tsx`. - -## Declaring Menu Structure - -Use `menu.md` to declare the menu structure. - -- Use the headings (`#`, `##`, etc..) to define menu depth -- Use the bulleted list (`-`) to define menu items. - -```markdown title="src/route/some/menu.md" -# Docs - -## Getting Started - -- [Introduction](introduction/index.md) - -## Components - -- [Basics](/docs/(qwik)/components/basics/index.mdx) -``` - -## Retrieving Menu Structure - -At runtime, any component can retrieve the menu with [`useContent()`](/docs/(qwikcity)/api/index.mdx#usecontent) hook. The type returned is `ContentMenu`. - -The example above will return: - -```javascript -{ - text: "Docs", - items: [ - { - text: "Getting Started", - items: [ - { - text: "Introduction", - href: "/docs/introduction" - } - ], - }, - { - text: "Components", - items: [ - { - text: "Basics", - href: "/docs/(qwik)/components/basics" - } - ], - }, - ], -} -``` - -## Using `ContentMenu` in a layout - -While [`useContent()`](/docs/(qwikcity)/api/index.mdx#usecontent) can be invoked from any component that is part of the current route, it is typically used in a layout component (or a component used by layout) to render the menu. An example usage is shown here: - -```tsx -import { component$ } from '@builder.io/qwik'; -import { useLocation, useContent } from '@builder.io/qwik-city'; -export default component$(() => { - const { menu } = useContent(); - const { url } = useLocation(); - - return ( -
- ); -}); -``` diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/plugins/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/plugins/index.mdx deleted file mode 100644 index cbd9c0cb352..00000000000 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/plugins/index.mdx +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Qwik Plugins | Qwik City -description: Learn about advanced routing in Qwik City, including 404 page handling, grouped layouts, named layouts, nested layouts, and plugin.ts files. -contributors: - - patrickjs -updated_at: '2024-05-05T16:20:00Z' -created_at: '2024-05-05T16:20:00Z' ---- - -# Qwik Plugins -Qwik plugins, named as `plugin.ts` or `plugin@.ts`, handle incoming requests prior to root layout execution and are located in the `src/routes` directory. Request handlers like `onRequest`, `onGet`, `onPost` in these plugins are called before `server$` functions. For multiple plugins, `plugin.ts` handlers execute first, followed by `plugin@.ts` handlers in alphabetical order. Middleware functions should be defined in `plugin.ts` to ensure execution for all requests. - -### The order of execution of `plugin.ts` files - - If `plugin.ts` file exists and if it has exported request handlers, then they are executed first. - -Then exported request handlers from `plugin@.ts` files are executed in alphabetical order of the file names. For example, `onGet` from `plugin@auth.ts` is executed before `onGet` from `plugin@security.ts` because `auth` is alphabetically before `security`. - -Finally, if a `server$` function exists, it's executed last. - -## Middleware and `server$` - -When using `server$`, it's important to understand how [middleware functions](/docs/middleware/#middleware-function) are executed. Middleware functions defined in `layout` files do not run for `server$` requests. This can lead to confusion, especially when developers expect certain middleware to be executed for both page requests and `server$` requests. - -To ensure that a middleware function runs for both types of requests, it should be defined in the `plugin.ts` file. This ensures that the middleware is executed consistently for all incoming requests, regardless of whether they are normal page requests or `server$` requests. - diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/request-handling/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/request-handling/index.mdx deleted file mode 100644 index 668037865db..00000000000 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/request-handling/index.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Request Handling | Advanced -description: How to handle requests in Qwik City, including REST endpoints, and middlewares. -contributors: - - adamdbradley - - manucorporat - - mhevery - - tzdesign - - mrhoodz - - hamatoyogi - - jemsco -updated_at: '2023-07-01T18:35:24Z' -created_at: '2023-03-20T23:45:13Z' ---- - -# Request Handling - -Each `layout.ts` and `index.ts` file inside the `src/routes` directory has the ability to access the current HTTP request, response, and URL. This allows you to retrieve and modify data, and even respond with custom content. - -Qwik City implements a middleware system based on the hierarchy of the `src/routes` directory. The middleware system is used to handle HTTP requests and responses and is available to pages, layouts, and [endpoints](/docs/(qwikcity)/endpoints/index.mdx). - -Each route can add HTTP request and response handlers, allowing developers to retrieve and modify data. The handlers can also be used by [endpoints](/docs/(qwikcity)/endpoints/index.mdx), which only respond with data rather than a page's HTML. - -This feature enables you to handle any request event, have side effects on the request pipeline, just before you render the component and respond with custom content. It is available to pages, layouts and endpoint routes, but not on regular components. - -## Request and Response Handlers - -On pages, layouts, and [endpoints](/docs/(qwikcity)/endpoints/index.mdx), we can access request data by implementing request handler functions such as `onGet`, `onPost`, `onPut`, etc. These functions are executed according to the [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) used for this route. - -Additionally, an `onRequest` function can be used to handle any request method, rather than a specific one, in the form of a [middleware](/docs/(qwikcity)/middleware/index.mdx). For example, if both `onGet` and `onRequest` is provided, for a `GET` request, the `onGet` will be called. However, in this scenario, if a `POST` request method came in, then the `onRequest` handler would be called since an `onPost` was not provided. -The `onRequest` is available as a catch-all to any request methods that do not have a specific method. - -```tsx -import type { RequestHandler } from '@builder.io/qwik-city'; - -export const onGet: RequestHandler = async ({ params }) => { - // put your DB access here (hard coding data for simplicity) - return { - skuId: params.skuId, - price: 123.45, - description: `Description for ${params.skuId}`, - }; -}; -``` - -## Request Event - -The request handler functions receive a `RequestEvent` argument which has the following properties: - -| Field | Description | -| ---------- | ----------------------------------------------------------------------------- | -| `request` | The request object | -| `response` | The response object, which can be used to set response `headers` and `status` | -| `url` | URL which includes `pathname`, `hostname`, etc. | -| `next` | Next middleware function | -| `abort` | Request abort function | -| `params` | Custom user params found within the URL | -| `cookie` | Get and set cookies. | -| `platform` | Platform data object (useful for Cloudflare, Netlify, etc) | - -### Cookie - -```tsx -interface Cookie { - get: (key: string) => CookieValue | null; - set: (key: string, value: string | number | Record, options?: CookieOptions) => void; - delete: (key: string) => void; - has: (key: string) => boolean; -} -``` - -**get** -Takes a string with the cookie name and returns the `CookieValue`, if present and null if not. - -```tsx -interface CookieValue { - value: string; - json: () => T; - number: () => number; -} -``` - -A cookie value is a simple record with three fields: - -1. `value`: Contains the cookie value as a string -2. `json()`: Runs `JSON.parse()` on the value and returns the result -3. `number()`: Runs `Number()` on the value and returns the result - -**getAll** -Returns an object with all cookies, if any. _This is sometimes required if the names of cookies are unknown and must be parsed through._ - -**set** -Takes a key and value and creates a header that will be appended to the response. Value can be a `string | number | Record` - -As a third argument, you can optionally provide a `CookieOptions` record for setting additional fields. - -```tsx -export interface CookieOptions { - domain?: string; - expires?: Date | string; - httpOnly?: boolean; - maxAge?: number | [number, 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks']; - path?: string; - sameSite?: 'lax' | 'none' | 'strict'; - secure?: boolean; -} -``` - -For more information on these attributes and their values, please refer to [the MDN article on the Set-Cookie header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes). - -**delete** -Appends a header with the provided key to the cookie. The new header will have an expired date in the `expires` field, telling the browsers to remove it. - -```tsx -cookie.delete('my-cookie'); -// equivalent to -cookie.set('my-cookie', 'deleted', new Date(0)); -// or -cookie.set('my-cookie', ''); -``` - -Optionally, you can set the path, sameSite and/or domain when deleting the cookie. If your cookie was created with a path/domain, you must set these fields for the deletion to take effect. - -```tsx -cookie.delete('my-cookie', { domain: 'https://qwik.dev', path: '/docs/' }); -``` - -**has** -A convenience method which returns true or false based on the presence of the provided key in cookie. - -```tsx -cookie.has('my-cookie'); -``` diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/routing/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/routing/index.mdx deleted file mode 100644 index 365669a3baf..00000000000 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/routing/index.mdx +++ /dev/null @@ -1,265 +0,0 @@ ---- -title: Advanced Routing | Qwik City -description: Learn about advanced routing in Qwik City, including 404 page handling, grouped layouts, named layouts, nested layouts, and plugin.ts files. -contributors: - - manucorporat - - adamdbradley - - cunzaizhuyi - - the-r3aper7 - - mhevery - - jakovljevic-mladen - - vfshera - - thejackshelton - - wtlin1228 - - hamatoyogi - - jemsco - - patrickjs -updated_at: '2023-10-05T21:23:14Z' -created_at: '2023-03-20T23:45:13Z' ---- - -# Advanced Routing - -## 404 Page Handling - -It's possible for any user to visit a URL that doesn't exist on your site. For example, if a user visits `https://example.com/does-not-exist`, then the server should respond with a [404 HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404), and the page should have at least some sort of explanation, rather than just a blank page. - -For any given route, Qwik City can choose how to best handle the 404 response for the user, whether it's with the default 404 page, a custom 404 page, or a dynamically generated 404 page. - -### Default 404 Page - -Rather than showing a blank page, by default Qwik City provides a generic 404 page for any route that isn't handled. The default 404 page is rendered as a fallback when a custom 404 page was not found. We recommend providing a custom 404 page for a better user experience. Including the common header and navigation in a custom 404 page would help the user find the page they're looking for. - -### Custom 404 Page - -Instead of showing the generic (boring) 404 response, it's possible to provide a custom 404 page using the same familiar layouts as the rest of your site. - -To create a custom 404 page, add a `404.tsx` file to the root `src/routes` directory. - -```bash -src/ -└── routes/ - ├── 404.tsx # Custom 404 - ├── layout.tsx # Default layout - └── index.tsx # https://example.com/ -``` - -In the example above, the `404.tsx` page will also use the `layout.tsx` layout, since it is a sibling of the layout in the same directory. - -Additionally, using Qwik City's directory-based routing allows for custom 404 pages to be created at different paths. For example, if `src/routes/account/404.tsx` was also added to the structure, then the custom account 404 page would only be applied to the `/account/*` routes, while all other 404s would use the root `404.tsx` page. - -> Note: During development and in preview mode, the custom 404 pages will not be rendered, but instead the default Qwik City 404 page will show. However, when building the app for production, the custom 404 page will be statically generated as a static `404.html` file. - -```bash -src/ -└── routes/ - ├── account/ - │ └── 404.tsx # Custom Account 404 - │ └── index.tsx # https://example.com/account/ - ├── 404.tsx # Custom 404 - ├── layout.tsx # Default layout - └── index.tsx # https://example.com/ -``` - -It's worth noting that custom 404 pages are statically generated at build time, resulting in a static 404.html file, rather than individually server-side rendered pages. This strategy reduces the load on your HTTP server, avoiding server-side rendering of 404 pages, and thus preserving resources. - -### Dynamic 404 Page - -When rendering a page, the default is to always respond with a [200 HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200), which tells the browser all is good and the route exists. However, it's also possible to handle rendering the page, but manually setting the response status code to something other than 200, such as 404. - -For example, let's say we have a product page with a URL such as `https://example.com/product/abc`. The product page would be handled using `src/routes/product/[id]/index.tsx` directory-based route, and `[id]` is a dynamic param in the URL. - -In this example, `id` is used as a key to load the product data from the database. When the product data is found, great, we'll correctly render data. However, if the product data is not found in the database, we can still handle rendering the page, but instead respond with a 404 HTTP status code. - -```tsx -import { component$ } from '@builder.io/qwik'; -import { routeLoader$ } from '@builder.io/qwik-city'; - -export const useProductLoader = routeLoader$(async ({ params, status }) => { - // Example database call using the id param - // The database could return null if the product is not found - const data = await productDatabase.get(params.id); - - if (!data) { - // Product data was not found - // Set the status code to 404 - status(404); - } - - // return the data (which may be null) - return data; -}); - -export default component$(() => { - // get the product data from the loader - const product = useProductLoader(); - - if (!product.value) { - // no product data found - // so render our own custom product 404 - return

Sorry, looks like we don't have this product.

; - } - - // product data was found, so let's render it - return ( -
-

{product.value.name}

-

{product.value.price}

-

{product.value.description}

-
- ); -}); -``` - -## Grouped Layouts - -Common purpose routes are often placed into directories so they can share layouts, and so related source files are logically grouped next to each other. However, it may be desirable that the directory, which was used to group similar files and share layouts, is excluded from the public-facing URL. This is where "grouped" layouts come in (also referred to as a "pathless" layout route). - -By surrounding any directory name with parentheses, such as `(name)`, then the directory name itself will not be included in the URL pathname. - -For example, let's say an app placed all _account_ routes together in a directory. `/account/` could be dropped from the URL for cleaner, shorter URLs. In the example below, notice that the paths are within the `src/routes/(account)/` directory, but the URL paths exclude `(account)/`. - -```bash -src/ -└── routes/ - └── (account)/ # Notice the parentheses - ├── layout.tsx # Shared account layout - └── profile/ - └── index.tsx # https://example.com/profile - └── settings/ - └── index.tsx # https://example.com/settings -``` - -## Named Layout - -At times related routes need to have drastically different layouts from their siblings. It is possible to define multiple layouts for different sibling routes by using a single default layout and any number of named layouts. The child route can then request a specific named-layout. - -Qwik City defines the convention that layouts are within `src/routes` and the filename starts with `layout`. That's why the default layout is named `layout.tsx`. A named layout also starts with `layout` followed by a dash `-` and a unique name, such as `layout-narrow.tsx`. - -To reference a named layout, the route's `index.tsx` file must be suffixed with `@`. For example, `index@narrow.tsx` would use the `layout-narrow.tsx` layout. - -```bash -src/ -└── routes/ - ├── contact/ - │ └── index@narrow.tsx # https://example.com/contact (Layout: layout-narrow.tsx) - ├── layout.tsx # Default layout - ├── layout-narrow.tsx # Named layout - └── index.tsx # https://example.com/ (Layout: layout.tsx) -``` - -- `https://example.com/` - ``` - ┌──────────────────────────────────────────────────┐ - │ src/routes/layout.tsx │ - │ ┌────────────────────────────────────────────┐ │ - │ │ src/routes/index.tsx │ │ - │ │ │ │ - │ └────────────────────────────────────────────┘ │ - │ │ - └──────────────────────────────────────────────────┘ - ``` -- `https://example.com/contact` - ``` - ┌──────────────────────────────────────────────────┐ - │ src/routes/layout-narrow.tsx │ - │ ┌────────────────────────────────────────────┐ │ - │ │ src/routes/contact/index@narrow.tsx │ │ - │ │ │ │ - │ └────────────────────────────────────────────┘ │ - │ │ - └──────────────────────────────────────────────────┘ - ``` - -## Nested Layout - -Most times it is desirable to nest layouts within each other. A page's content can be nested in numerous wrapping layouts, which is determined by the directory structure. - -```bash -src/ -└── routes/ - ├── layout.tsx # Parent layout - └── about/ - ├── layout.tsx # Child layout - └── index.tsx # https://example.com/about -``` - -In the above example, there are two layouts that apply themselves around the `/about` page component. - -1. `src/routes/layout.tsx` -2. `src/routes/about/layout.tsx` - -In this case, the layouts will be nested within each other on the page. - -``` -┌────────────────────────────────────────────────┐ -│ src/routes/layout.tsx │ -│ ┌──────────────────────────────────────────┐ │ -│ │ src/routes/about/layout.tsx │ │ -│ │ ┌────────────────────────────────────┐ │ │ -│ │ │ src/routes/about/index.tsx │ │ │ -│ │ │ │ │ │ -│ │ └────────────────────────────────────┘ │ │ -│ │ │ │ -│ └──────────────────────────────────────────┘ │ -│ │ -└────────────────────────────────────────────────┘ -``` - -```tsx title="src/routes/layout.tsx" -import { component$, Slot } from '@builder.io/qwik'; - -export default component$(() => { - return ( -
- {/* <== Child layout/route inserted here */} -
- ); -}); -``` - -```tsx title="src/routes/about/layout.tsx" -import { component$, Slot } from '@builder.io/qwik'; - -export default component$(() => { - return ( -
- {/* <== Child layout/route inserted here */} -
- ); -}); -``` - -```tsx title="src/routes/about/index.tsx" -import { component$ } from '@builder.io/qwik'; - -export default component$(() => { - return

About

; -}); -``` - -The above example would render the html: - -```html -
-
-

About

-
-
-``` - -## Plugin with `plugin@.ts` - -`plugin.ts` and `plugin@.ts` files can be created in the root of the `src/routes` directory to handle any incoming request before even the root layout executes. - -You can have multiple `plugin.ts` files, each with a different name. For example, `plugin@auth.ts` and `plugin@security.ts`. The `@` is optional and it's only used for developers to help identify the plugin. - -Requests handlers like `onRequest`, `onGet`, `onPost`, etc. are called before `server$` functions are executed. - -### The order of execution of `plugin.ts` files - - If `plugin.ts` file exists and if it has exported request handlers, then they are executed first. - -Then exported request handlers from `plugin@.ts` files are executed in alphabetical order of the file names. For example, `onGet` from `plugin@auth.ts` is executed before `onGet` from `plugin@security.ts` because `auth` is alphabetically before `security`. - -Finally, if a `server$` function exists, it's executed last. diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/sitemaps/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/sitemaps/index.mdx deleted file mode 100644 index 099a11bb0ab..00000000000 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/sitemaps/index.mdx +++ /dev/null @@ -1,140 +0,0 @@ ---- -title: Generating Sitemaps | Advanced -description: Learn how to generate a sitemap for your site in Qwik City. -contributors: - - adamdbradley - - hbendev - - mrhoodz - - thejackshelton - - hamatoyogi - - devweb13 -updated_at: '2023-07-07T09:31:48Z' -created_at: '2023-04-24T19:40:27Z' ---- - -# Generating Sitemaps - -## Generating Sitemaps in SSG - -By default, when Static Site Generated (SSG) pages are built, a [sitemap](https://en.wikipedia.org/wiki/Sitemaps) is generated for the site. The `sitemap.xml` is automatically generated based on the pages that were built. This means that if you have a page that is not built, it will not be included in the sitemap. - -### Configuration - -The sitemap can be configured using the adapter's vite config file. The example below is configuring the Cloudflare adapter. The default sitemap file path is `sitemap.xml`, but you can use the `sitemapOutFile` option to change the file path. - -```ts - plugins: [ - cloudflarePagesAdapter({ - ssg: { - include: ['/*'], - origin: 'https://qwik.dev', - sitemapOutFile: 'sitemap.xml', - }, - }), - ] -``` - -The `include` option is used to specify which pages should be built, which also adds them to the sitemap. Any pages added to the `exclude` option will also exclude them from the sitemap. - -The `origin` option is used to specify the origin of the site and is used to generate the absolute URLs for the sitemap. - -### robots.txt - -Depending on your site setup, you'll probably want to add a [robots.txt](https://en.wikipedia.org/wiki/Robots.txt) file to your site. This can be done by adding a `robots.txt` file to the `public` directory. Any file in the `public` directory is treated as a static file and deploy alongside the build. The following is an example of a `public/robots.txt` file: - -```bash -User-agent: * -Allow: / - -Sitemap: https:///sitemap.xml -``` - -Note the added `Sitemap` directive to the `robots.txt` file which tells search engines where to find the sitemap for your site. Be sure to replace `` with the hostname of your site. - -## Generating Dynamic Sitemaps in SSR - -In addition to generating sitemaps for Static Site Generation (SSG), you can also generate sitemaps dynamically with Server-Side Rendering (SSR). This is useful if your site has content that is not known at build time or is frequently updated. - -To generate a dynamic sitemap in SSR, you can create a route that serves a `dynamic-sitemap.xml` file based on your site’s routes and other dynamic content. Below is an example of how to set this up. - -### Create a Sitemap Function - -First, create a function that generates the sitemap XML based on the routes you want to include. You can create this function in a file such as `src/routes/dynamic-sitemap.xml/create-sitemap.ts`: - -```typescript -// src/routes/dynamic-sitemap.xml/create-sitemap.ts - -export interface SitemapEntry { - loc: string; - priority: number; -} - -export function createSitemap(entries: SitemapEntry[]) { - const baseUrl = "https://"; - - return ` - -${entries.map( - (entry) => ` - - ${baseUrl}${entry.loc.startsWith("/") ? "" : "/"}${entry.loc} - ${entry.priority} - `, -)} -`.trim(); -} -``` - -### Set Up a Route for the Dynamic Sitemap - -Next, set up a route that will use the sitemap function to generate the sitemap dynamically. Create a file like src/routes/dynamic-sitemap.xml/index.tsx: - -```tsx -// src/routes/dynamic-sitemap.xml/index.tsx - -import type { RequestHandler } from "@builder.io/qwik-city"; -import { routes } from "@qwik-city-plan"; -import { createSitemap } from "./create-sitemap"; - -export const onGet: RequestHandler = (ev) => { - const siteRoutes = routes - .map(([route]) => route as string) - .filter(route => route !== "/"); // Exclude the '/' route - - const sitemap = createSitemap([ - { loc: "/", priority: 1 }, // Manually include the root route - ...siteRoutes.map((route) => ({ - loc: route, - priority: 0.9, // Default priority, adjust as needed - })), - ]); - - const response = new Response(sitemap, { - status: 200, - headers: { "Content-Type": "text/xml" }, - }); - - ev.send(response); -}; -``` - -This route dynamically creates the sitemap XML based on the routes in your Qwik City application. - -### robots.txt - -To ensure that search engines know where to find your dynamic sitemap, you should also add or update your robots.txt file. Add the following line to your robots.txt file, which is typically located in your public directory: - - -```bash -User-agent: * -Allow: / - -# Uncomment the following line and replace with the actual folder name you want to disallow -# Disallow: // - -Sitemap: https:///dynamic-sitemap.xml -``` - -Be sure to replace `` with your actual site URL. - -This setup will dynamically generate and serve a dynamic.sitemap.xml whenever it is requested, keeping it up to date with the latest routes and changes to your site. diff --git a/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx deleted file mode 100644 index ae99b7d8f02..00000000000 --- a/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx +++ /dev/null @@ -1,492 +0,0 @@ ---- -title: API Reference | Qwik City -description: Qwik City API reference. -contributors: - - manucorporat - - adamdbradley - - the-r3aper7 - - nnelgxorz - - cunzaizhuyi - - jakovljevic-mladen - - barbosajlm - - Eucer - - eltociear - - literalpie - - Mhmdrza - - ulic75 - - mhevery - - jordanw66 - - igorbabko - - mrhoodz - - VinuB-Dev - - billykwok - - julianobrasil - - hamatoyogi - - srapport -updated_at: '2023-10-03T18:53:23Z' -created_at: '2023-03-20T23:45:13Z' ---- - -# API reference - -## `useContent()` - -The [`useContent()`](/docs/(qwikcity)/api/index.mdx#usecontent) function retrieves the nearest content information for the current route. The returned object includes: - -```ts -headings: ContentHeading[] | undefined; -menu: ContentMenu | undefined; -``` - -The `headings` array includes data about a markdown file's `

` to `

` [html heading elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements). - -Menus are contextual data declared with `menu.md` files. See [menus file definition](/docs/(qwikcity)/advanced/menu/index.mdx) for more information on the file format and location. - -## `useDocumentHead()` - -Use the `useDocumentHead()` function to read the document [head metadata](/docs/(qwikcity)/pages/index.mdx#head-export). - -`useDocumentHead()` retrieves a readonly `DocumentHead` object that includes: - -```ts -export interface DocumentHead { - /** - * Represents the `` element of the document. - */ - readonly title?: string; - /** - * Used to manually set meta tags in the head. Additionally, the `data` - * property could be used to set arbitrary data which the `<head>` component - * could later use to generate `<meta>` tags. - */ - readonly meta?: readonly DocumentMeta[]; - /** - * Used to manually append `<link>` elements to the `<head>`. - */ - readonly links?: readonly DocumentLink[]; - /** - * Used to manually append `<style>` elements to the `<head>`. - */ - readonly styles?: readonly DocumentStyle[]; - /** - * Arbitrary object containing custom data. When the document head is created from - * markdown files, the frontmatter attributes that are not recognized as a well-known - * meta names (such as title, description, author, etc...), are stored in this property. - */ - readonly frontmatter?: Readonly<Record<string, any>>; -} -``` - -All starters include a `<RouterHead>` component that is responsible for generating the `<head>` element of the document. It uses the `useDocumentHead()` function to retrieve the current head metadata and render the appropriate `<meta>`, `<link>`, `<style>` and `<title>` elements. - -```tsx title="src/components/router-head/router-head.tsx" -import { component$ } from '@builder.io/qwik'; -import { useDocumentHead } from '@builder.io/qwik-city'; - -/** - * The RouterHead component is placed inside of the document `<head>` element. - */ -export const RouterHead = component$(() => { - const head = useDocumentHead(); - - return ( - <> - <title>{head.title} - - - - - {head.meta.map((m) => ( - - ))} - - {head.links.map((l) => ( - - ))} - - {head.styles.map((s) => ( - - - Hello World - - ` - ); - }); - - test('should render Counter and accept events', async () => { - const { screen, render, userEvent } = await createDOM(); - - await render(); - await expectDOM( - screen, - ` - - - - - 15 - - - - ` - ); - await userEvent('button.decrement', 'click'); - await expectDOM( - screen, - ` - - - - - 10 - - - -` - ); - }); - - test('should render a collection of todo items', async () => { - const { screen, render } = await createDOM(); - - const items = { - items: [ - { - done: true, - title: 'Task 1', - }, - { - done: false, - title: 'Task 2', - }, - ], - }; - await render(); - await delay(0); - await expectDOM( - screen, - ` - - - - - - - Task 1 - - - - - - Task 2 - - - Total: 2 - - - - ` - ); - }); - - test('types work as expected', () => () => { - // Let's keep one of these old type exports around for now. - const Input1 = component$>((props) => { - return ; - }); - - const Input2 = component$((props: PropsOf<'input'>) => { - return ; - }); - - type Input3Props = { - type: 'text' | 'number'; - } & Partial>; - - const Input3 = component$(({ type, ...props }) => { - return ; - }); - - type Input4Props = { - type: 'text' | 'number'; - } & QwikIntrinsicElements['input']; - - const Input4 = component$(({ type, ...props }) => { - return ( -
- -
- ); - }); - - component$(() => { - return ( - <> - - - - - - ); - }); - }); - - test('custom function types should work', () => () => { - type TestProps = PropsOf<'h1'> & { - qrl$?: QRL<() => void>; - }; - const Test1 = component$(({ qrl$, ...props }) => { - return ( - <> -

- Hi 👋 -

- - ); - }); - expectTypeOf().toMatchTypeOf[0]['qrl$']>(); - expectTypeOf[0]['qrl$']>().toEqualTypeOf< - (() => void) | QRL<() => void> | undefined - >(); - - const Test2 = component$(({ qrl$, ...props }: TestProps) => { - return ( - <> -

- Hi 👋 -

- - ); - }); - expectTypeOf().toMatchTypeOf[0]['qrl$']>(); - expectTypeOf[0]['qrl$']>().toEqualTypeOf< - (() => void) | QRL<() => void> | undefined - >(); - component$(() => { - return ( - <> - - - - ); - }); - }); - - test('PropFunction should work', () => () => { - type TestProps = PropsOf<'h1'> & { - test$?: PropFunction<() => void>; - }; - const Test1 = component$(({ test$, ...props }) => { - return ( - <> -

- Hi 👋 -

- - ); - }); - expectTypeOf().toMatchTypeOf[0]['test$']>(); - expectTypeOf void>>().toMatchTypeOf[0]['test$']>(); - }); - - test('Inline Components should be able to use Component', () => () => { - const InlineComponent: Component> = (props) => { - expectTypeOf(props).not.toBeAny(); - return
; - }; - // Passing a plain function should not error - return {}} />; - }); -}); - -///////////////////////////////////////////////////////////////////////////// -export const HelloWorld = component$(() => { - useStylesQrl(inlinedQrl(`{}`, 'named-style')); - return Hello World; -}); - -///////////////////////////////////////////////////////////////////////////// -// - -export const Greeter = component$((props: { salutation?: string; name?: string }) => { - const state = useStore({ count: 0 }); - return ( -
- {' '} - {props.salutation} {props.name} ({state.count}){' '} -
- ); -}); - -////////////////////////////////////////////// -// import { QComponent, component, qView, qHandler, getState, markDirty } from '@builder.io/qwik'; - -// Component view may need additional handlers describing the component's behavior. -export const MyCounter_update = () => { - const [props, state, args] = - useLexicalScope<[PropsOf, { count: number }, { dir: number }]>(); - state.count += args.dir * (props.step || 1); -}; - -// Finally tie it all together into a component. -export const MyCounter = component$((props: { step?: number; value?: number }) => { - const state = useStore({ count: props.value || 0 }); - return ( - - - {state.count} - - - ); -}); - -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// - -interface ItemObj { - title: string; - done: boolean; -} - -interface ItemsObj { - items: ItemObj[]; -} - -///////////////////////////////////////////////////////////////////////////// - -export const ItemDetail = component$((props: { itemObj: ItemObj }) => { - // const state = useStore({ editing: false }); - return ( - - - {props.itemObj.title || 'loading...'} - - ); -}); - -///////////////////////////////////////////////////////////////////////////// - -export const Items = component$((props: { items: ItemsObj }) => { - // const state = useStore({ editing: false }); - return ( - - {props.items.items.map((item) => ( - - ))} - Total: {props.items.items.length} - - ); -}); - -function delay(milliseconds: number): Promise { - return new Promise((res) => setTimeout(res, milliseconds)); -} diff --git a/packages/qwik/src/core/container/container.ts b/packages/qwik/src/core/container/container.ts deleted file mode 100644 index d1a403f25e4..00000000000 --- a/packages/qwik/src/core/container/container.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { qError, QError_invalidRefValue } from '../error/error'; -import type { ResourceReturnInternal, SubscriberEffect } from '../use/use-task'; -import { seal } from '../util/qdev'; -import { isFunction } from '../util/types'; -import type { QRL } from '../qrl/qrl.public'; -import { fromKebabToCamelCase } from '../util/case'; -import { QContainerAttr } from '../util/markers'; -import { isElement } from '../util/element'; -import { - createSubscriptionManager, - type SubscriberSignal, - type SubscriptionManager, -} from '../state/common'; -import { isSignal, type Signal, type SignalImpl } from '../state/signal'; -import { directGetAttribute } from '../render/fast-calls'; -import type { QContext } from '../state/context'; -import { isServerPlatform } from '../platform/platform'; - -export type GetObject = (id: string) => any; -export type GetObjID = (obj: any) => string | null; -export type MustGetObjID = (obj: any) => string; - -/** @public */ -export interface SnapshotMetaValue { - w?: string; // q:watches - s?: string; // q:seq - h?: string; // q:host - c?: string; // q:context -} - -/** @public */ -export type SnapshotMeta = Record; - -/** @public */ -export interface SnapshotState { - ctx: SnapshotMeta; - refs: Record; - objs: any[]; - subs: any[]; -} - -/** @public */ -export interface SnapshotListener { - key: string; - qrl: QRL; - el: Element; -} - -/** @public */ -export interface SnapshotResult { - state: SnapshotState; - funcs: string[]; - qrls: QRL[]; - objs: any[]; - resources: ResourceReturnInternal[]; - mode: 'render' | 'listeners' | 'static'; -} - -export type ObjToProxyMap = WeakMap; - -/** @public */ -export interface PauseContext { - getObject: GetObject; - meta: SnapshotMeta; - refs: Record; -} - -/** @public */ -export interface ContainerState { - readonly $containerEl$: Element; - - readonly $proxyMap$: ObjToProxyMap; - $subsManager$: SubscriptionManager; - - readonly $taskNext$: Set; - readonly $taskStaging$: Set; - - readonly $opsNext$: Set; - - readonly $hostsNext$: Set; - readonly $hostsStaging$: Set; - readonly $base$: string; - - $hostsRendering$: Set | undefined; - $renderPromise$: Promise | undefined; - - $serverData$: Record; - $elementIndex$: number; - - $pauseCtx$: PauseContext | undefined; - $styleMoved$: boolean; - readonly $styleIds$: Set; - readonly $events$: Set; - readonly $inlineFns$: Map; -} - -const CONTAINER_STATE = Symbol('ContainerState'); - -/** @internal */ -export const _getContainerState = (containerEl: Element): ContainerState => { - let state = (containerEl as any)[CONTAINER_STATE] as ContainerState; - if (!state) { - (containerEl as any)[CONTAINER_STATE] = state = createContainerState( - containerEl, - directGetAttribute(containerEl, 'q:base') ?? '/' - ); - } - return state; -}; - -export const createContainerState = (containerEl: Element, base: string) => { - const containerAttributes: Record = {}; - if (containerEl) { - const attrs = containerEl.attributes; - if (attrs) { - for (let index = 0; index < attrs.length; index++) { - const attr = attrs[index]; - containerAttributes[attr.name] = attr.value; - } - } - } - const containerState: ContainerState = { - $containerEl$: containerEl, - - $elementIndex$: 0, - $styleMoved$: false, - - $proxyMap$: new WeakMap(), - - $opsNext$: new Set(), - - $taskNext$: new Set(), - $taskStaging$: new Set(), - - $hostsNext$: new Set(), - $hostsStaging$: new Set(), - - $styleIds$: new Set(), - $events$: new Set(), - - $serverData$: { containerAttributes }, - $base$: base, - $renderPromise$: undefined, - $hostsRendering$: undefined, - $pauseCtx$: undefined, - $subsManager$: null as any, - $inlineFns$: new Map(), - }; - seal(containerState); - containerState.$subsManager$ = createSubscriptionManager(containerState); - return containerState; -}; - -export const removeContainerState = (containerEl: Element) => { - delete (containerEl as any)[CONTAINER_STATE]; -}; - -export const setRef = (value: any, elm: Element) => { - if (isFunction(value)) { - return value(elm); - } else if (isSignal(value)) { - if (isServerPlatform()) { - // During SSR, assigning a ref should not cause reactivity because - // the expectation is that the ref is filled in on the client - return ((value as SignalImpl).untrackedValue = elm); - } else { - return ((value as Signal).value = elm); - } - } - throw qError(QError_invalidRefValue, value); -}; - -export const SHOW_ELEMENT = 1; -export const SHOW_COMMENT = 128; -export const FILTER_ACCEPT = 1; -export const FILTER_REJECT = 2; -export const FILTER_SKIP = 3; - -export const isContainer = (el: Node) => { - return isElement(el) && el.hasAttribute(QContainerAttr); -}; - -export const intToStr = (nu: number) => { - return nu.toString(36); -}; - -export const strToInt = (nu: string) => { - return parseInt(nu, 36); -}; - -export const getEventName = (attribute: string) => { - const colonPos = attribute.indexOf(':'); - if (attribute) { - return fromKebabToCamelCase(attribute.slice(colonPos + 1)); - } else { - return attribute; - } -}; - -export interface QContainerElement extends Element { - _qwikjson_?: any; -} diff --git a/packages/qwik/src/core/container/index.html b/packages/qwik/src/core/container/index.html deleted file mode 100644 index 6f355e38dad..00000000000 --- a/packages/qwik/src/core/container/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - Document - - -
-
-
-
-
-
-
-
-
-
- - diff --git a/packages/qwik/src/core/container/pause.ts b/packages/qwik/src/core/container/pause.ts deleted file mode 100644 index e7165fbd4dd..00000000000 --- a/packages/qwik/src/core/container/pause.ts +++ /dev/null @@ -1,995 +0,0 @@ -import { assertDefined, assertElement, assertEqual } from '../error/assert'; -import { getDocument } from '../util/dom'; -import { - isComment, - isDocument, - isElement, - isNode, - isQwikElement, - isText, - isVirtualElement, -} from '../util/element'; -import { logWarn } from '../util/log'; -import { ELEMENT_ID, ELEMENT_ID_PREFIX, QContainerAttr, QScopedStyle } from '../util/markers'; -import { qDev } from '../util/qdev'; - -import { - QError_containerAlreadyPaused, - QError_missingObjectId, - QError_verifySerializable, - qError, -} from '../error/error'; -import { serializeQRLs } from '../qrl/qrl'; -import type { QRL } from '../qrl/qrl.public'; -import { - processVirtualNodes, - type QwikElement, - type VirtualElement, -} from '../render/dom/virtual-element'; -import { directGetAttribute, directSetAttribute } from '../render/fast-calls'; -import { - LocalSubscriptionManager, - fastSkipSerialize, - fastWeakSerialize, - getProxyFlags, - getProxyTarget, - getSubscriptionManager, - isConnected, - serializeSubscription, - type Subscriptions, - type SubscriberSignal, -} from '../state/common'; -import { QObjectImmutable, QObjectRecursive } from '../state/constants'; -import { HOST_FLAG_DYNAMIC, tryGetContext, type QContext } from '../state/context'; -import { groupListeners } from '../state/listeners'; -import { SignalImpl } from '../state/signal'; -import { serializeSStyle } from '../style/qrl-styles'; -import { - TaskFlagsIsDirty, - destroyTask, - isResourceTask, - type ResourceReturnInternal, -} from '../use/use-task'; -import { isNotNullable, isPromise } from '../util/promises'; -import { isArray, isObject, isSerializableObject } from '../util/types'; -import { - FILTER_REJECT, - FILTER_SKIP, - SHOW_COMMENT, - SHOW_ELEMENT, - _getContainerState, - intToStr, - type ContainerState, - type GetObjID, - type SnapshotMeta, - type SnapshotMetaValue, - type SnapshotResult, - createContainerState, -} from './container'; -import { UNDEFINED_PREFIX, collectDeps, serializeValue } from './serializers'; -import { isQrl } from '../qrl/qrl-class'; - -/** @internal */ -export const _serializeData = async (data: any, pureQRL?: boolean) => { - const containerState = createContainerState(null!, null!); - const collector = createCollector(containerState); - collectValue(data, collector, false); - - // Wait for remaining promises - let promises: Promise[]; - while ((promises = collector.$promises$).length > 0) { - collector.$promises$ = []; - const results = await Promise.allSettled(promises); - for (const result of results) { - if (result.status === 'rejected') { - console.error(result.reason); - } - } - } - - const objs = Array.from(collector.$objSet$.keys()); - let count = 0; - - const objToId = new Map(); - for (const obj of objs) { - objToId.set(obj, intToStr(count)); - count++; - } - if (collector.$noSerialize$.length > 0) { - const undefinedID = objToId.get(undefined); - assertDefined(undefinedID, 'undefined ID must be defined'); - for (const obj of collector.$noSerialize$) { - objToId.set(obj, undefinedID); - } - } - - const mustGetObjId = (obj: any): string => { - let suffix = ''; - if (isPromise(obj)) { - const promiseValue = getPromiseValue(obj); - if (!promiseValue) { - throw qError(QError_missingObjectId, obj); - } - obj = promiseValue.value; - if (promiseValue.resolved) { - suffix += '~'; - } else { - suffix += '_'; - } - } - if (isObject(obj)) { - const target = getProxyTarget(obj); - if (target) { - suffix += '!'; - obj = target; - } - } - const key = objToId.get(obj); - if (key === undefined) { - throw qError(QError_missingObjectId, obj); - } - return key + suffix; - }; - - const convertedObjs = serializeObjects(objs, mustGetObjId, null, collector, containerState); - - return JSON.stringify({ - _entry: mustGetObjId(data), - _objs: convertedObjs, - }); -}; - -// -// !!DO NOT EDIT THIS COMMENT DIRECTLY!!! -// (edit ../readme.md#pauseContainer instead) -// -/** This pauses a running container in the browser. It is not used for SSR */ -// TODO(mhevery): this is a remnant when you could have paused on client. Should be deleted. -export const pauseContainer = async ( - elmOrDoc: Element | Document, - defaultParentJSON?: Element -): Promise => { - const doc = getDocument(elmOrDoc); - const documentElement = doc.documentElement; - const containerEl = isDocument(elmOrDoc) ? documentElement : elmOrDoc; - if (directGetAttribute(containerEl, QContainerAttr) === 'paused') { - throw qError(QError_containerAlreadyPaused); - } - const parentJSON = - defaultParentJSON ?? (containerEl === doc.documentElement ? doc.body : containerEl); - - const containerState = _getContainerState(containerEl); - const contexts = getNodesInScope(containerEl, hasContext); - - // Set container to paused - directSetAttribute(containerEl, QContainerAttr, 'paused'); - - // Update elements with context - for (const elCtx of contexts) { - const elm = elCtx.$element$; - const listeners = elCtx.li; - if (elCtx.$scopeIds$) { - const value = serializeSStyle(elCtx.$scopeIds$); - if (value) { - elm.setAttribute(QScopedStyle, value); - } - } - if (elCtx.$id$) { - elm.setAttribute(ELEMENT_ID, elCtx.$id$); - } - if (isElement(elm) && listeners.length > 0) { - const groups = groupListeners(listeners); - for (const listener of groups) { - elm.setAttribute(listener[0], serializeQRLs(listener[1], containerState, elCtx)); - } - } - } - - // Serialize data - const data = await _pauseFromContexts(contexts, containerState, (el) => { - if (isNode(el) && isText(el)) { - return getTextID(el, containerState); - } - return null; - }); - - // Emit Qwik JSON - const qwikJson = doc.createElement('script'); - directSetAttribute(qwikJson, 'type', 'qwik/json'); - qwikJson.textContent = escapeText(JSON.stringify(data.state, undefined, qDev ? ' ' : undefined)); - parentJSON.appendChild(qwikJson); - - // Emit event registration - const extraListeners = Array.from(containerState.$events$, (s) => JSON.stringify(s)); - const eventsScript = doc.createElement('script'); - eventsScript.textContent = `(window.qwikevents||=[]).push(${extraListeners.join(', ')})`; - parentJSON.appendChild(eventsScript); - - return data; -}; - -/** - * Grab all state needed to resume the container later. - * - * @internal - */ -export const _pauseFromContexts = async ( - allContexts: QContext[], - containerState: ContainerState, - fallbackGetObjId?: GetObjID, - textNodes?: Map -): Promise => { - const collector = createCollector(containerState); - textNodes?.forEach((_, key) => { - collector.$seen$.add(key); - }); - let hasListeners = false; - - // Collect resources - // TODO: optimize - for (const ctx of allContexts) { - if (ctx.$tasks$) { - for (const task of ctx.$tasks$) { - if (qDev) { - if (task.$flags$ & TaskFlagsIsDirty) { - logWarn( - `Serializing dirty task. Looks like an internal error. -Task Symbol: ${task.$qrl$.$symbol$} -` - ); - } - if (!isConnected(task)) { - logWarn('Serializing disconnected task. Looks like an internal error.'); - } - } - if (isResourceTask(task)) { - collector.$resources$.push(task.$state$!); - } - destroyTask(task); - } - } - } - - // Find all listeners. They are the "entries" for resuming the container. - // Any lexical scope they reference must be serialized. - for (const ctx of allContexts) { - const el = ctx.$element$; - const ctxListeners = ctx.li; - for (const listener of ctxListeners) { - if (isElement(el)) { - const qrl = listener[1]; - const captured = qrl.$captureRef$; - if (captured) { - for (const obj of captured) { - /** - * Collect the lexical scope used by the listener. This also collects all the - * subscribers of any reactive state in scope, since the listener might change that - * state - */ - collectValue(obj, collector, true); - } - } - collector.$qrls$.push(qrl); - hasListeners = true; - } - } - } - - // No listeners implies static page - if (!hasListeners) { - return { - state: { - refs: {}, - ctx: {}, - objs: [], - subs: [], - }, - objs: [], - funcs: [], - qrls: [], - resources: collector.$resources$, - mode: 'static', - }; - } - - // Wait for remaining promises - let promises: Promise[]; - while ((promises = collector.$promises$).length > 0) { - collector.$promises$ = []; - await Promise.all(promises); - } - - // If at this point any component can render, we need to capture Context and Props - const canRender = collector.$elements$.length > 0; - if (canRender) { - for (const elCtx of collector.$deferElements$) { - collectElementData(elCtx, collector, elCtx.$element$); - } - - for (const ctx of allContexts) { - collectProps(ctx, collector); - } - } - - // Wait for remaining promises - while ((promises = collector.$promises$).length > 0) { - collector.$promises$ = []; - await Promise.all(promises); - } - - // Convert objSet to array - const elementToIndex = new Map(); - const objs = Array.from(collector.$objSet$.keys()); - const objToId = new Map(); - - const getElementID = (el: QwikElement): string | null => { - let id = elementToIndex.get(el); - if (id === undefined) { - id = getQId(el); - if (!id) { - console.warn('Missing ID', el); - } - elementToIndex.set(el, id); - } - return id; - }; - - const getObjId: GetObjID = (obj) => { - let suffix = ''; - if (isPromise(obj)) { - const promiseValue = getPromiseValue(obj); - if (!promiseValue) { - return null; - } - obj = promiseValue.value; - if (promiseValue.resolved) { - suffix += '~'; - } else { - suffix += '_'; - } - } - - if (isObject(obj)) { - const target = getProxyTarget(obj); - if (target) { - suffix += '!'; - obj = target; - } else if (isQwikElement(obj)) { - const elID = getElementID(obj); - if (elID) { - return ELEMENT_ID_PREFIX + elID + suffix; - } - return null; - } - } - const id = objToId.get(obj); - if (id) { - return id + suffix; - } - const textId = textNodes?.get(obj); - if (textId) { - return '*' + textId; - } - if (fallbackGetObjId) { - return fallbackGetObjId(obj); - } - return null; - }; - - const mustGetObjId = (obj: any): string => { - const key = getObjId(obj); - if (key === null) { - // TODO(mhevery): this is a hack as we should never get here. - // This as a workaround for https://github.com/QwikDev/qwik/issues/4979 - if (isQrl(obj)) { - const id = intToStr(objToId.size); - objToId.set(obj, id); - return id; - } else { - throw qError(QError_missingObjectId, obj); - } - } - return key; - }; - - // Compute subscriptions - const subsMap = new Map(); - for (const obj of objs) { - const subs = getManager(obj, containerState)?.$subs$; - if (!subs) { - continue; - } - const flags = getProxyFlags(obj) ?? 0; - const converted: (Subscriptions | number)[] = []; - if (flags & QObjectRecursive) { - converted.push(flags); - } - for (const sub of subs) { - const host = sub[1]; - if (sub[0] === 0 && isNode(host) && isVirtualElement(host)) { - if (!collector.$elements$.includes(tryGetContext(host)!)) { - continue; - } - } - converted.push(sub); - } - if (converted.length > 0) { - subsMap.set(obj, converted); - } - } - - // Sort objects: the ones with subscriptions go first - objs.sort((a, b) => { - const isProxyA = subsMap.has(a) ? 0 : 1; - const isProxyB = subsMap.has(b) ? 0 : 1; - return isProxyA - isProxyB; - }); - - // Generate object ID by using a monotonic counter - let count = 0; - for (const obj of objs) { - objToId.set(obj, intToStr(count)); - count++; - } - if (collector.$noSerialize$.length > 0) { - const undefinedID = objToId.get(undefined); - assertDefined(undefinedID, 'undefined ID must be defined'); - for (const obj of collector.$noSerialize$) { - objToId.set(obj, undefinedID); - } - } - - // Serialize object subscriptions - const subs: string[][] = []; - for (const obj of objs) { - const value = subsMap.get(obj); - if (value == null) { - break; - } - subs.push( - value - .map((s) => { - if (typeof s === 'number') { - return `_${s}`; - } - return serializeSubscription(s, getObjId); - }) - .filter(isNotNullable) - ); - } - assertEqual(subs.length, subsMap.size, 'missing subscriptions to serialize', subs, subsMap); - - const convertedObjs = serializeObjects(objs, mustGetObjId, getObjId, collector, containerState); - - const meta: SnapshotMeta = {}; - const refs: Record = {}; - - // Write back to the dom - for (const ctx of allContexts) { - const node = ctx.$element$; - const elementID = ctx.$id$; - const ref = ctx.$refMap$; - const props = ctx.$props$; - const contexts = ctx.$contexts$; - const tasks = ctx.$tasks$; - const renderQrl = ctx.$componentQrl$; - const seq = ctx.$seq$; - const metaValue: SnapshotMetaValue = {}; - const elementCaptured = isVirtualElement(node) && collector.$elements$.includes(ctx); - assertDefined(elementID, `pause: can not generate ID for dom node`, node); - - if (ref.length > 0) { - assertElement(node); - const value = mapJoin(ref, mustGetObjId, ' '); - if (value) { - refs[elementID] = value; - } - } else if (canRender) { - let add = false; - if (elementCaptured) { - assertDefined(renderQrl, 'renderQrl must be defined'); - const propsId = getObjId(props); - metaValue.h = mustGetObjId(renderQrl) + (propsId ? ' ' + propsId : ''); - add = true; - } else { - const propsId = getObjId(props); - if (propsId) { - metaValue.h = ' ' + propsId; - add = true; - } - } - - if (tasks && tasks.length > 0) { - const value = mapJoin(tasks, getObjId, ' '); - if (value) { - metaValue.w = value; - add = true; - } - } - - if (elementCaptured && seq && seq.length > 0) { - const value = mapJoin(seq, mustGetObjId, ' '); - metaValue.s = value; - add = true; - } - - if (contexts) { - const serializedContexts: string[] = []; - contexts.forEach((value, key) => { - const id = getObjId(value); - if (id) { - serializedContexts.push(`${key}=${id}`); - } - }); - const value = serializedContexts.join(' '); - if (value) { - metaValue.c = value; - add = true; - } - } - if (add) { - meta[elementID] = metaValue; - } - } - } - - // Sanity check of serialized element - if (qDev) { - elementToIndex.forEach((value, el) => { - if (!value) { - logWarn('unconnected element', el.nodeName, '\n'); - } - }); - } - - return { - state: { - refs, - ctx: meta, - objs: convertedObjs, - subs, - }, - objs, - funcs: collector.$inlinedFunctions$, - resources: collector.$resources$, - qrls: collector.$qrls$, - mode: canRender ? 'render' : 'listeners', - }; -}; - -export const mapJoin = (objects: any[], getObjectId: GetObjID, sep: string): string => { - let output = ''; - for (const obj of objects) { - const id = getObjectId(obj); - if (id !== null) { - if (output !== '') { - output += sep; - } - output += id; - } - } - return output; -}; - -export const getNodesInScope = ( - parent: Element, - predicate: (el: Node) => T | undefined -): T[] => { - const results: T[] = []; - const v = predicate(parent); - if (v !== undefined) { - results.push(v); - } - const walker = parent.ownerDocument.createTreeWalker(parent, SHOW_ELEMENT | SHOW_COMMENT, { - acceptNode(node) { - if (isContainer(node)) { - return FILTER_REJECT; - } - const v = predicate(node); - if (v !== undefined) { - results.push(v); - } - return FILTER_SKIP; - }, - }); - while (walker.nextNode()) { - // do nothing - } - - return results; -}; - -export interface Collector { - $seen$: Set; - $objSet$: Set; - $noSerialize$: any[]; - $elements$: QContext[]; - $qrls$: QRL[]; - $inlinedFunctions$: string[]; - $resources$: ResourceReturnInternal[]; - $prefetch$: number; - $deferElements$: QContext[]; - $containerState$: ContainerState; - $promises$: Promise[]; -} - -// Collect props proxy objects -const collectProps = (elCtx: QContext, collector: Collector) => { - const parentCtx = elCtx.$realParentCtx$ || elCtx.$parentCtx$; - const props = elCtx.$props$; - // Collect only if the parent (which changes the props) is part of the listener graph - if (parentCtx && props && !isEmptyObj(props) && collector.$elements$.includes(parentCtx)) { - const subs = getSubscriptionManager(props)?.$subs$; - const el = elCtx.$element$ as VirtualElement; - if (subs) { - for (const [type, host] of subs) { - if (type === 0) { - if (host !== el) { - collectSubscriptions(getSubscriptionManager(props)!, collector, false); - } - if (isNode(host)) { - collectElement(host, collector); - } else { - collectValue(host, collector, true); - } - } else { - collectValue(props, collector, false); - collectSubscriptions(getSubscriptionManager(props)!, collector, false); - } - } - } - } -}; - -const createCollector = (containerState: ContainerState): Collector => { - const inlinedFunctions: string[] = []; - containerState.$inlineFns$.forEach((id, fnStr) => { - while (inlinedFunctions.length <= id) { - inlinedFunctions.push(''); - } - inlinedFunctions[id] = fnStr; - }); - return { - $containerState$: containerState, - $seen$: new Set(), - $objSet$: new Set(), - $prefetch$: 0, - $noSerialize$: [], - $inlinedFunctions$: inlinedFunctions, - $resources$: [], - $elements$: [], - $qrls$: [], - $deferElements$: [], - $promises$: [], - }; -}; - -const collectDeferElement = (el: VirtualElement, collector: Collector) => { - const ctx = tryGetContext(el)!; - if (collector.$elements$.includes(ctx)) { - return; - } - collector.$elements$.push(ctx); - if (ctx.$flags$ & HOST_FLAG_DYNAMIC) { - collector.$prefetch$++; - collectElementData(ctx, collector, true); - collector.$prefetch$--; - } else { - collector.$deferElements$.push(ctx); - } -}; - -const collectElement = (el: QwikElement, collector: Collector) => { - const ctx = tryGetContext(el); - if (ctx) { - if (collector.$elements$.includes(ctx)) { - return; - } - collector.$elements$.push(ctx); - collectElementData(ctx, collector, el); - } -}; - -export const collectElementData = ( - elCtx: QContext, - collector: Collector, - dynamicCtx: QwikElement | boolean -) => { - if (elCtx.$props$ && !isEmptyObj(elCtx.$props$)) { - collectValue(elCtx.$props$, collector, dynamicCtx); - collectSubscriptions(getSubscriptionManager(elCtx.$props$)!, collector, dynamicCtx); - } - if (elCtx.$componentQrl$) { - collectValue(elCtx.$componentQrl$, collector, dynamicCtx); - } - if (elCtx.$seq$) { - for (const obj of elCtx.$seq$) { - collectValue(obj, collector, dynamicCtx); - } - } - if (elCtx.$tasks$) { - const map = collector.$containerState$.$subsManager$.$groupToManagers$; - for (const obj of elCtx.$tasks$) { - if (map.has(obj)) { - collectValue(obj, collector, dynamicCtx); - } - } - } - - if (dynamicCtx === true) { - collectContext(elCtx, collector); - if (elCtx.$dynamicSlots$) { - for (const slotCtx of elCtx.$dynamicSlots$) { - collectContext(slotCtx, collector); - } - } - } -}; - -const collectContext = (elCtx: QContext | null | undefined, collector: Collector) => { - while (elCtx) { - if (elCtx.$contexts$) { - for (const obj of elCtx.$contexts$.values()) { - collectValue(obj, collector, true); - } - } - elCtx = elCtx.$parentCtx$; - } -}; - -export const escapeText = (str: string) => { - return str.replace(/<(\/?script)/gi, '\\x3C$1'); -}; - -// Collect all the subscribers of this manager -export const collectSubscriptions = ( - manager: LocalSubscriptionManager, - collector: Collector, - leaks: boolean | QwikElement -) => { - // if (!leaks) { - // return; - // } - if (collector.$seen$.has(manager)) { - return; - } - collector.$seen$.add(manager); - - const subs = manager.$subs$; - assertDefined(subs, 'subs must be defined'); - for (const sub of subs) { - const type = sub[0]; - if (type > 0) { - collectValue((sub as SubscriberSignal)[2], collector, leaks); - } - if (leaks === true) { - const host = sub[1]; - if (isNode(host) && isVirtualElement(host)) { - if (sub[0] === 0) { - collectDeferElement(host, collector); - } - } else { - collectValue(host, collector, true); - } - } - } -}; - -const PROMISE_VALUE = Symbol(); - -interface PromiseValue { - resolved: boolean; - value: any; -} -const resolvePromise = (promise: Promise) => { - return promise.then( - (value) => { - const v: PromiseValue = { - resolved: true, - value, - }; - (promise as any)[PROMISE_VALUE] = v; - return value; - }, - (value) => { - const v: PromiseValue = { - resolved: false, - value, - }; - (promise as any)[PROMISE_VALUE] = v; - return value; - } - ); -}; - -const getPromiseValue = (promise: Promise): PromiseValue | undefined => { - return (promise as any)[PROMISE_VALUE]; -}; - -export const collectValue = (obj: unknown, collector: Collector, leaks: boolean | QwikElement) => { - if (obj != null) { - const objType = typeof obj; - switch (objType) { - case 'function': - case 'object': { - if (collector.$seen$.has(obj)) { - return; - } - collector.$seen$.add(obj); - if (fastSkipSerialize(obj)) { - collector.$objSet$.add(undefined); - collector.$noSerialize$.push(obj); - return; - } - - /** The possibly proxied `obj` */ - const input = obj; - const target = getProxyTarget(obj); - if (target) { - // `obj` is now the non-proxied object - obj = target; - // NOTE: You may be tempted to add the `target` to the `seen` set, - // but that would not work as it is possible for the `target` object - // to already be in `seen` set if it was passed in directly, so - // we can't short circuit and need to do the work. - // Issue: https://github.com/QwikDev/qwik/issues/5001 - const mutable = (getProxyFlags(obj)! & QObjectImmutable) === 0; - if (leaks && mutable) { - collectSubscriptions(getSubscriptionManager(input)!, collector, leaks); - } - if (fastWeakSerialize(input)) { - collector.$objSet$.add(obj); - return; - } - } - const collected = collectDeps(obj, collector, leaks); - if (collected) { - collector.$objSet$.add(obj); - return; - } - - if (isPromise(obj)) { - collector.$promises$.push( - resolvePromise(obj).then((value) => { - collectValue(value, collector, leaks); - }) - ); - return; - } - - if (objType === 'object') { - if (isNode(obj)) { - return; - } - if (isArray(obj)) { - for (let i = 0; i < obj.length; i++) { - collectValue((input as typeof obj)[i], collector, leaks); - } - } else if (isSerializableObject(obj)) { - for (const key in obj) { - collectValue((input as typeof obj)[key], collector, leaks); - } - } - } - break; - } - } - } - collector.$objSet$.add(obj); -}; - -export const isContainer = (el: Node) => { - return isElement(el) && el.hasAttribute(QContainerAttr); -}; - -const hasContext = (el: Node) => { - const node = processVirtualNodes(el); - if (isQwikElement(node)) { - const ctx = tryGetContext(node); - if (ctx && ctx.$id$) { - return ctx; - } - } - return undefined; -}; - -const getManager = (obj: any, containerState: ContainerState) => { - if (!isObject(obj)) { - return undefined; - } - if (obj instanceof SignalImpl) { - return getSubscriptionManager(obj); - } - const proxy = containerState.$proxyMap$.get(obj); - if (proxy) { - return getSubscriptionManager(proxy); - } - return undefined; -}; - -const getQId = (el: QwikElement): string | null => { - const ctx = tryGetContext(el); - if (ctx) { - return ctx.$id$; - } - return null; -}; - -const getTextID = (node: Text, containerState: ContainerState) => { - const prev = node.previousSibling; - if (prev && isComment(prev)) { - if (prev.data.startsWith('t=')) { - return ELEMENT_ID_PREFIX + prev.data.slice(2); - } - } - const doc = node.ownerDocument; - const id = intToStr(containerState.$elementIndex$++); - const open = doc.createComment(`t=${id}`); - const close = doc.createComment(''); - const parent = node.parentElement!; - parent.insertBefore(open, node); - parent.insertBefore(close, node.nextSibling); - return ELEMENT_ID_PREFIX + id; -}; - -const isEmptyObj = (obj: Record) => { - return Object.keys(obj).length === 0; -}; -function serializeObjects( - objs: any[], - mustGetObjId: (obj: any) => string, - getObjId: GetObjID | null, - collector: Collector, - containerState: any -) { - return objs.map((obj) => { - if (obj === null) { - return null; - } - const typeObj = typeof obj; - switch (typeObj) { - case 'undefined': - return UNDEFINED_PREFIX; - case 'number': - if (!Number.isFinite(obj)) { - break; - } - return obj; - case 'string': - if ((obj as string).charCodeAt(0) < 32 /* space */) { - // if strings starts with a special character let the string serializer handle it - // to deal with escape sequences. - break; - } else { - // Fast path of just serializing the string. - return obj; - } - case 'boolean': - return obj; - } - const value = serializeValue(obj, mustGetObjId, collector, containerState); - if (value !== undefined) { - return value; - } - if (typeObj === 'object') { - if (isArray(obj)) { - return obj.map(mustGetObjId); - } - if (isSerializableObject(obj)) { - const output: Record = {}; - for (const key in obj) { - if (getObjId) { - const id = getObjId(obj[key]); - if (id !== null) { - output[key] = id; - } - } else { - output[key] = mustGetObjId(obj[key]); - } - } - return output; - } - } - throw qError(QError_verifySerializable, obj); - }); -} diff --git a/packages/qwik/src/core/container/pause.unit.tsx b/packages/qwik/src/core/container/pause.unit.tsx deleted file mode 100644 index 865de528373..00000000000 --- a/packages/qwik/src/core/container/pause.unit.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import type { SymbolMapper, SymbolMapperFn } from '../../optimizer/src/types'; -import { componentQrl } from '../component/component.public'; -import { useComputedQrl } from '../use/use-task'; -import { useSignal } from '../use/use-signal'; -import { renderToString } from '../../server/render'; -import { assert, test } from 'vitest'; -import { inlinedQrl } from '../qrl/qrl'; - -const symbolMapper: SymbolMapperFn = (symbolName: string, mapper: SymbolMapper | undefined) => { - return [symbolName, `q-${symbolName}.js`]; -}; - -test('issue-4979', async () => { - await renderToString(, { - containerTagName: 'div', - symbolMapper: symbolMapper, - manifest: {} as any, - }); - - assert(true, 'Serialized successfully'); -}); - -export const Issue4979Inner = componentQrl<{ value: number }>( - inlinedQrl( - (props) => { - const foo = useComputedQrl( - inlinedQrl( - () => { - return Object.entries(props); - }, - 's_foo', - [props] - ) - ); - - return ; - }, - 's_issue4979Inner', - [] - ) -); - -export const Issue4979 = componentQrl( - inlinedQrl( - () => { - const pageSig = useSignal(1); - - const currentDataSig = useComputedQrl( - inlinedQrl( - () => { - return [pageSig.value]; - }, - 's_currentData', - [pageSig] - ) - ); - - return ( - <> - - {currentDataSig.value.map((o) => ( - - ))} - - ); - }, - 's_issue4979', - [] - ) -); diff --git a/packages/qwik/src/core/container/render.unit.tsx b/packages/qwik/src/core/container/render.unit.tsx deleted file mode 100644 index ac0fd4de2aa..00000000000 --- a/packages/qwik/src/core/container/render.unit.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { assert, suite, test } from 'vitest'; -import { renderToString } from '../../server/render'; -import { createDocument } from '../../testing/document'; -import { createDOM } from '../../testing/library'; -import { component$ } from '../component/component.public'; -import { _fnSignal } from '../internal'; -import { useSignal } from '../use/use-signal'; -import type { JSXOutput } from '../render/jsx/types/jsx-node'; - -suite('jsx signals', () => { - const RenderJSX = component$(() => { - const jsx = useSignal(SSR); - return ( - <> - - - ); -}; - -export const LexicalScope = component$(() => { - const state = useStore({ - count: 0, - }); - const signal = useSignal(0); - const signalFromFn = useSignal(() => 'ok'); - const nu = 1; - const str = 'hola'; - const obj = { - a: { thing: 12 }, - b: 'hola', - c: 123, - d: false, - e: true, - f: null, - g: undefined, - h: [1, 'string', false, { hola: 1 }, ['hello']], - i: LexicalScope, - }; - const noserialize = noSerialize({ text: 'not included', window: () => {} }); - const undef = undefined; - const nulll = null; - const array = [1, 2, 'hola', {}]; - const boolTrue = true; - const boolFalse = false; - const qrl = $(() => logDebug('qrl')); - const thing = inlinedQrl(LexicalScope_render, 'LexicalScope_render', [ - nu, - str, - obj, - undef, - nulll, - array, - boolTrue, - boolFalse, - state, - noserialize, - qrl, - signal, - signalFromFn, - ]); - return
{signal as any}
; -}); diff --git a/packages/qwik/src/core/debug.ts b/packages/qwik/src/core/debug.ts new file mode 100644 index 00000000000..920bf007381 --- /dev/null +++ b/packages/qwik/src/core/debug.ts @@ -0,0 +1,94 @@ +import { isSignal } from './reactive-primitives/utils'; +// ^ keep this first to avoid circular dependency breaking class extend +import { vnode_getProp, vnode_isVNode } from './client/vnode'; +import { ComputedSignalImpl } from './reactive-primitives/impl/computed-signal-impl'; +import { isStore } from './reactive-primitives/impl/store'; +import { WrappedSignalImpl } from './reactive-primitives/impl/wrapped-signal-impl'; +import { isJSXNode } from './shared/jsx/jsx-runtime'; +import { isQrl } from './shared/qrl/qrl-utils'; +import { DEBUG_TYPE } from './shared/types'; +import { isTask } from './use/use-task'; + +const stringifyPath: any[] = []; +export function qwikDebugToString(value: any): any { + if (value === null) { + return 'null'; + } else if (value === undefined) { + return 'undefined'; + } else if (typeof value === 'string') { + return '"' + value + '"'; + } else if (typeof value === 'number' || typeof value === 'boolean') { + return String(value); + } else if (isTask(value)) { + return `Task(${qwikDebugToString(value.$qrl$)})`; + } else if (isQrl(value)) { + return `Qrl(${value.$symbol$})`; + } else if (typeof value === 'object' || typeof value === 'function') { + if (stringifyPath.includes(value)) { + return '*'; + } + if (stringifyPath.length > 10) { + // debugger; + } + try { + stringifyPath.push(value); + if (Array.isArray(value)) { + if (vnode_isVNode(value)) { + return '(' + vnode_getProp(value, DEBUG_TYPE, null) + ')'; + } else { + return value.map(qwikDebugToString); + } + } else if (isSignal(value)) { + if (value instanceof WrappedSignalImpl) { + return 'WrappedSignal'; + } else if (value instanceof ComputedSignalImpl) { + return 'ComputedSignal'; + } else { + return 'Signal'; + } + } else if (isStore(value)) { + return 'Store'; + } else if (isJSXNode(value)) { + return jsxToString(value); + } + } finally { + stringifyPath.pop(); + } + } + return value; +} + +export const pad = (text: string, prefix: string) => { + return String(text) + .split('\n') + .map((line, idx) => (idx ? prefix : '') + line) + .join('\n'); +}; + +export const jsxToString = (value: any): string => { + if (isJSXNode(value)) { + let str = '<' + value.type; + if (value.props) { + for (const [key, val] of Object.entries(value.props)) { + str += ' ' + key + '=' + qwikDebugToString(val); + } + const children = value.children; + if (children != null) { + str += '>'; + if (Array.isArray(children)) { + children.forEach((child) => { + str += jsxToString(child); + }); + } else { + str += jsxToString(children); + } + str += ''; + } else { + str += '/>'; + } + } + return str; + } else { + return String(value); + } +}; diff --git a/packages/qwik/src/core/error/assert.ts b/packages/qwik/src/core/error/assert.ts deleted file mode 100644 index 50554f49459..00000000000 --- a/packages/qwik/src/core/error/assert.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { QwikElement, VirtualElement } from '../render/dom/virtual-element'; -import { isElement, isQwikElement } from '../util/element'; -import { throwErrorAndStop } from '../util/log'; -import { qDev } from '../util/qdev'; - -const ASSERT_DISCLAIMER = 'Internal assert, this is likely caused by a bug in Qwik: '; - -export function assertDefined( - value: T, - text: string, - ...parts: any[] -): asserts value is NonNullable { - if (qDev) { - if (value != null) { - return; - } - throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts); - } -} - -export function assertEqual( - value1: any, - value2: any, - text: string, - ...parts: any[] -): asserts value1 is typeof value2 { - if (qDev) { - if (value1 === value2) { - return; - } - throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts); - } -} - -export function assertFail(text: string, ...parts: any[]): never; -export function assertFail(text: string, ...parts: any[]) { - if (qDev) { - throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts); - } -} - -export function assertTrue(value1: any, text: string, ...parts: any[]): asserts value1 is true { - if (qDev) { - if (value1 === true) { - return; - } - throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts); - } -} - -export function assertNumber(value1: any, text: string, ...parts: any[]): asserts value1 is number { - if (qDev) { - if (typeof value1 === 'number') { - return; - } - throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts); - } -} - -export function assertString(value1: any, text: string, ...parts: any[]): asserts value1 is string { - if (qDev) { - if (typeof value1 === 'string') { - return; - } - throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts); - } -} - -export function assertQwikElement(el: any): asserts el is QwikElement { - if (qDev) { - if (!isQwikElement(el)) { - console.error('Not a Qwik Element, got', el); - throwErrorAndStop(ASSERT_DISCLAIMER + 'Not a Qwik Element'); - } - } -} - -export function assertElement(el: Node | VirtualElement): asserts el is Element { - if (qDev) { - if (!isElement(el)) { - console.error('Not a Element, got', el); - throwErrorAndStop(ASSERT_DISCLAIMER + 'Not an Element'); - } - } -} diff --git a/packages/qwik/src/core/error/error.ts b/packages/qwik/src/core/error/error.ts deleted file mode 100644 index 53482935148..00000000000 --- a/packages/qwik/src/core/error/error.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { logErrorAndStop } from '../util/log'; -import { qDev } from '../util/qdev'; - -export const codeToText = (code: number, ...parts: any[]): string => { - if (qDev) { - // Keep one error, one line to make it easier to search for the error message. - const MAP = [ - 'Error while serializing class or style attributes', // 0 - 'Can not serialize a HTML Node that is not an Element', // 1 - 'Runtime but no instance found on element.', // 2 - 'Only primitive and object literals can be serialized', // 3 - 'Crash while rendering', // 4 - 'You can render over a existing q:container. Skipping render().', // 5 - 'Set property {{0}}', // 6 - "Only function's and 'string's are supported.", // 7 - "Only objects can be wrapped in 'QObject'", // 8 - `Only objects literals can be wrapped in 'QObject'`, // 9 - 'QRL is not a function', // 10 - 'Dynamic import not found', // 11 - 'Unknown type argument', // 12 - `Actual value for useContext({{0}}) can not be found, make sure some ancestor component has set a value using useContextProvider(). In the browser make sure that the context was used during SSR so its state was serialized.`, // 13 - "Invoking 'use*()' method outside of invocation context.", // 14 - 'Cant access renderCtx for existing context', // 15 - 'Cant access document for existing context', // 16 - 'props are immutable', // 17 - '
component can only be used at the root of a Qwik component$()', // 18 - 'Props are immutable by default.', // 19 - `Calling a 'use*()' method outside 'component$(() => { HERE })' is not allowed. 'use*()' methods provide hooks to the 'component$' state and lifecycle, ie 'use' hooks can only be called synchronously within the 'component$' function or another 'use' method.\nSee https://qwik.dev/docs/components/tasks/#use-method-rules`, // 20 - 'Container is already paused. Skipping', // 21 - '', // 22 -- unused - 'When rendering directly on top of Document, the root node must be a ', // 23 - 'A node must have 2 children. The first one and the second one a ', // 24 - 'Invalid JSXNode type "{{0}}". It must be either a function or a string. Found:', // 25 - 'Tracking value changes can only be done to useStore() objects and component props', // 26 - 'Missing Object ID for captured object', // 27 - 'The provided Context reference "{{0}}" is not a valid context created by createContextId()', // 28 - ' is the root container, it can not be rendered inside a component', // 29 - 'QRLs can not be resolved because it does not have an attached container. This means that the QRL does not know where it belongs inside the DOM, so it cant dynamically import() from a relative path.', // 30 - 'QRLs can not be dynamically resolved, because it does not have a chunk path', // 31 - 'The JSX ref attribute must be a Signal', // 32 - ]; - let text = MAP[code] ?? ''; - if (parts.length) { - text = text.replaceAll(/{{(\d+)}}/g, (_, index) => { - let v = parts[index]; - if (v && typeof v === 'object' && v.constructor === Object) { - v = JSON.stringify(v).slice(0, 50); - } - return v; - }); - } - return `Code(${code}): ${text}`; - } else { - // cute little hack to give roughly the correct line number. Update the line number if it shifts. - return `Code(${code}) https://github.com/QwikDev/qwik/blob/main/packages/qwik/src/core/error/error.ts#L${8 + code}`; - } -}; - -export const QError_stringifyClassOrStyle = 0; -export const QError_cannotSerializeNode = 1; -export const QError_runtimeQrlNoElement = 2; -export const QError_verifySerializable = 3; -export const QError_errorWhileRendering = 4; -export const QError_cannotRenderOverExistingContainer = 5; -export const QError_setProperty = 6; -export const QError_qrlOrError = 7; -export const QError_onlyObjectWrapped = 8; -export const QError_onlyLiteralWrapped = 9; -export const QError_qrlIsNotFunction = 10; -export const QError_dynamicImportFailed = 11; -export const QError_unknownTypeArgument = 12; -export const QError_notFoundContext = 13; -export const QError_useMethodOutsideContext = 14; -export const QError_missingRenderCtx = 15; -export const QError_missingDoc = 16; -export const QError_immutableProps = 17; -export const QError_hostCanOnlyBeAtRoot = 18; -export const QError_immutableJsxProps = 19; -export const QError_useInvokeContext = 20; -export const QError_containerAlreadyPaused = 21; -export const QError_unused_please_reuse = 22; -export const QError_rootNodeMustBeHTML = 23; -export const QError_strictHTMLChildren = 24; -export const QError_invalidJsxNodeType = 25; -export const QError_trackUseStore = 26; -export const QError_missingObjectId = 27; -export const QError_invalidContext = 28; -export const QError_canNotRenderHTML = 29; -export const QError_qrlMissingContainer = 30; -export const QError_qrlMissingChunk = 31; -export const QError_invalidRefValue = 32; -export const qError = (code: number, ...parts: any[]): Error => { - const text = codeToText(code, ...parts); - return logErrorAndStop(text, ...parts); -}; diff --git a/packages/qwik/src/core/examples.tsx b/packages/qwik/src/core/examples.tsx index 80f1fa67600..0f2559129bf 100644 --- a/packages/qwik/src/core/examples.tsx +++ b/packages/qwik/src/core/examples.tsx @@ -6,14 +6,15 @@ // it to the desired comment location // -import { component$ } from './component/component.public'; -import { qrl } from './qrl/qrl'; -import { $, type QRL } from './qrl/qrl.public'; +import { component$ } from './shared/component.public'; +import { qrl } from './shared/qrl/qrl'; +import { $, type QRL } from './shared/qrl/qrl.public'; import { useOn, useOnDocument, useOnWindow } from './use/use-on'; import { useStore } from './use/use-store.public'; import { useStyles$, useStylesScoped$ } from './use/use-styles'; -import { useVisibleTask$, useTask$ } from './use/use-task'; -import { implicit$FirstArg } from './util/implicit_dollar'; +import { useTask$ } from './use/use-task-dollar'; +import { useVisibleTask$ } from './use/use-visible-task-dollar'; +import { implicit$FirstArg } from './shared/qrl/implicit_dollar'; import { isServer, isBrowser } from '../build'; ////////////////////////////////////////////////////////// @@ -474,7 +475,8 @@ function doExtraStuff() { // import { createContextId, useContext, useContextProvider } from './use/use-context'; -import { Resource, useResource$ } from './use/use-resource'; +import { Resource } from './use/use-resource'; +import { useResource$ } from './use/use-resource-dollar'; import { useSignal } from './use/use-signal'; export const greet = () => console.log('greet'); diff --git a/packages/qwik/src/core/index.ts b/packages/qwik/src/core/index.ts index e40ad6854d0..77068bd48b6 100644 --- a/packages/qwik/src/core/index.ts +++ b/packages/qwik/src/core/index.ts @@ -1,19 +1,11 @@ ////////////////////////////////////////////////////////////////////////////////////////// // Developer Core API ////////////////////////////////////////////////////////////////////////////////////////// -export { componentQrl, component$ } from './component/component.public'; +export { componentQrl, component$ } from './shared/component.public'; -export type { - PropsOf, - OnRenderFn, - Component, - PublicProps, - PropFunctionProps, - _AllowPlainQrl, - _Only$, -} from './component/component.public'; +export type { PropsOf, OnRenderFn, Component, PublicProps } from './shared/component.public'; -export { isBrowser, isDev, isServer } from '@builder.io/qwik/build'; +export { isBrowser, isDev, isServer } from '@qwik.dev/core/build'; ////////////////////////////////////////////////////////////////////////////////////////// // Developer Event API @@ -24,40 +16,49 @@ export type { SnapshotMeta, SnapshotMetaValue, SnapshotListener, -} from './container/container'; + ISsrComponentFrame, +} from './ssr/ssr-types'; ////////////////////////////////////////////////////////////////////////////////////////// // Internal Runtime ////////////////////////////////////////////////////////////////////////////////////////// -export { $, sync$, _qrlSync, type SyncQRL } from './qrl/qrl.public'; -export { event$, eventQrl } from './qrl/qrl.public'; +export { $, sync$, _qrlSync, type SyncQRL } from './shared/qrl/qrl.public'; +export { eventQrl } from './shared/qrl/qrl.public'; +export { event$ } from './shared/qrl/qrl.public.dollar'; -export { qrl, inlinedQrl, inlinedQrlDEV, qrlDEV } from './qrl/qrl'; -export type { QRL, PropFunction, PropFnInterface } from './qrl/qrl.public'; -export { implicit$FirstArg } from './util/implicit_dollar'; +export { qrl, inlinedQrl, inlinedQrlDEV, qrlDEV } from './shared/qrl/qrl'; +export type { QRL, PropFunction } from './shared/qrl/qrl.public'; +export { implicit$FirstArg } from './shared/qrl/implicit_dollar'; ////////////////////////////////////////////////////////////////////////////////////////// // PLATFORM ////////////////////////////////////////////////////////////////////////////////////////// -export { getPlatform, setPlatform } from './platform/platform'; -export type { CorePlatform } from './platform/types'; +export { getPlatform, setPlatform } from './shared/platform/platform'; +export type { CorePlatform } from './shared/platform/types'; +export type { ClientContainer } from './client/types'; +export type { DomContainer } from './client/dom-container'; ////////////////////////////////////////////////////////////////////////////////////////// // JSX Runtime ////////////////////////////////////////////////////////////////////////////////////////// -export { h, h as createElement } from './render/jsx/factory'; export { SSRStreamBlock, SSRRaw, SSRStream, SSRComment, - SSRHint, SkipRender, -} from './render/jsx/utils.public'; -export type { SSRStreamProps, SSRHintProps } from './render/jsx/utils.public'; -export { Slot } from './render/jsx/slot.public'; -export { Fragment, HTMLFragment, RenderOnce, jsx, jsxDEV, jsxs } from './render/jsx/jsx-runtime'; -export type * from './render/jsx/types/jsx-generated'; +} from './shared/jsx/utils.public'; +export type { SSRStreamProps, SSRHintProps, SSRStreamChildren } from './shared/jsx/utils.public'; +export { Slot } from './shared/jsx/slot.public'; +export { + Fragment, + RenderOnce, + jsx, + jsxDEV, + jsxs, + h, + h as createElement, +} from './shared/jsx/jsx-runtime'; export type { DOMAttributes, QwikAttributes, @@ -68,66 +69,101 @@ export type { CorrectedToggleEvent, EventHandler, QRLEventHandlerMulti, -} from './render/jsx/types/jsx-qwik-attributes'; -export type { JSXOutput, FunctionComponent, JSXNode, DevJSX } from './render/jsx/types/jsx-node'; -export type { QwikDOMAttributes, QwikJSX, QwikJSX as JSX } from './render/jsx/types/jsx-qwik'; +} from './shared/jsx/types/jsx-qwik-attributes'; +export type { + JSXOutput, + FunctionComponent, + JSXNode, + JSXNodeInternal, + DevJSX, +} from './shared/jsx/types/jsx-node'; +export type { QwikDOMAttributes, QwikJSX, QwikJSX as JSX } from './shared/jsx/types/jsx-qwik'; -export type { QwikIntrinsicElements } from './render/jsx/types/jsx-qwik-elements'; -export type { QwikHTMLElements, QwikSVGElements } from './render/jsx/types/jsx-generated'; -export { render } from './render/dom/render.public'; -export type { RenderSSROptions, StreamWriter } from './render/ssr/render-ssr'; -export type { RenderOptions, RenderResult } from './render/dom/render.public'; +export type { QwikIntrinsicElements } from './shared/jsx/types/jsx-qwik-elements'; +export type { + CSSProperties, + QwikHTMLElements, + QwikSVGElements, + SVGAttributes, +} from './shared/jsx/types/jsx-generated'; +export { render } from './client/dom-render'; +export { getDomContainer, _getQContainerElement } from './client/dom-container'; +export type { StreamWriter, RenderSSROptions } from './ssr/ssr-types'; +export type { RenderOptions, RenderResult } from './client/types'; +export type { SerializationStrategy } from './shared/types'; ////////////////////////////////////////////////////////////////////////////////////////// // use API ////////////////////////////////////////////////////////////////////////////////////////// export { useLexicalScope } from './use/use-lexical-scope.public'; -export { useStore } from './use/use-store.public'; +export { useStore, unwrapStore } from './use/use-store.public'; export { untrack } from './use/use-core'; export { useId } from './use/use-id'; export { useContext, useContextProvider, createContextId } from './use/use-context'; export { useServerData } from './use/use-env-data'; export { useStylesQrl, useStyles$, useStylesScopedQrl, useStylesScoped$ } from './use/use-styles'; export { useOn, useOnDocument, useOnWindow } from './use/use-on'; -export { useSignal, useConstant, createSignal } from './use/use-signal'; +export { useSignal, useConstant } from './use/use-signal'; export { withLocale, getLocale } from './use/use-locale'; export type { UseStylesScoped } from './use/use-styles'; export type { UseSignal } from './use/use-signal'; export type { ContextId } from './use/use-context'; export type { UseStoreOptions } from './use/use-store.public'; +export type { ComputedFn, ComputedReturnType } from './use/use-computed'; +export { useComputedQrl } from './use/use-computed'; +export { useSerializerQrl, useSerializer$ } from './use/use-serializer'; +export type { OnVisibleTaskOptions, VisibleTaskStrategy } from './use/use-visible-task'; +export { useVisibleTaskQrl } from './use/use-visible-task'; +export type { TaskCtx, TaskFn, Tracker } from './use/use-task'; export type { - ComputedFn, - EagernessOptions, - OnVisibleTaskOptions, + ResourceProps, + ResourceOptions, ResourceCtx, ResourceFn, ResourcePending, ResourceRejected, ResourceResolved, ResourceReturn, - TaskCtx, - TaskFn, - Tracker, - UseTaskOptions, - VisibleTaskStrategy, -} from './use/use-task'; -export type { ResourceProps, ResourceOptions } from './use/use-resource'; -export { useResource$, useResourceQrl, Resource } from './use/use-resource'; -export { useTask$, useTaskQrl } from './use/use-task'; -export { useVisibleTask$, useVisibleTaskQrl } from './use/use-task'; -export { useComputed$, useComputedQrl, createComputed$, createComputedQrl } from './use/use-task'; +} from './use/use-resource'; +export { useResourceQrl, Resource } from './use/use-resource'; +export { useResource$ } from './use/use-resource-dollar'; +export { useTaskQrl } from './use/use-task'; +export { useTask$ } from './use/use-task-dollar'; +export { useVisibleTask$ } from './use/use-visible-task-dollar'; +export { useComputed$ } from './use/use-computed'; +export type { AsyncComputedFn, AsyncComputedReturnType } from './use/use-async-computed'; +export { useAsyncComputedQrl, useAsyncComputed$ } from './use/use-async-computed'; export { useErrorBoundary } from './use/use-error-boundary'; -export type { ErrorBoundaryStore } from './render/error-handling'; +export type { ErrorBoundaryStore } from './shared/error/error-handling'; +export { + type ReadonlySignal, + type AsyncComputedReadonlySignal, + type Signal, + type ComputedSignal, +} from './reactive-primitives/signal.public'; +export { + isSignal, + createSignal, + createComputedQrl, + createComputed$, + createSerializerQrl, + createSerializer$, + createAsyncComputedQrl, + createAsyncComputed$, +} from './reactive-primitives/signal.public'; +export type { ComputedOptions } from './reactive-primitives/types'; ////////////////////////////////////////////////////////////////////////////////////////// // Developer Low-Level API ////////////////////////////////////////////////////////////////////////////////////////// -export type { ValueOrPromise } from './util/types'; -export type { Signal, ReadonlySignal } from './state/signal'; -export type { NoSerialize } from './state/common'; -export { noSerialize, unwrapProxy as unwrapStore } from './state/common'; -export { isSignal } from './state/signal'; +export type { ValueOrPromise } from './shared/utils/types'; +export { + NoSerializeSymbol, + SerializerSymbol, + type NoSerialize, +} from './shared/utils/serialize-utils'; +export { noSerialize } from './shared/utils/serialize-utils'; export { version } from './version'; ////////////////////////////////////////////////////////////////////////////////////////// @@ -167,12 +203,12 @@ export type { QwikTouchEvent, QwikUIEvent, QwikWheelEvent, -} from './render/jsx/types/jsx-qwik-events'; +} from './shared/jsx/types/jsx-qwik-events'; ////////////////////////////////////////////////////////////////////////////////////////// // Components ////////////////////////////////////////////////////////////////////////////////////////// -export { PrefetchServiceWorker, PrefetchGraph } from './components/prefetch'; +export { PrefetchServiceWorker, PrefetchGraph } from './shared/prefetch-service-worker/prefetch'; ////////////////////////////////////////////////////////////////////////////////////////// // INTERNAL diff --git a/packages/qwik/src/core/internal.ts b/packages/qwik/src/core/internal.ts index cad738b5834..bf5efa057ae 100644 --- a/packages/qwik/src/core/internal.ts +++ b/packages/qwik/src/core/internal.ts @@ -1,18 +1,56 @@ -export { _pauseFromContexts, _serializeData } from './container/pause'; -export { _noopQrl, _noopQrlDEV, _regSymbol } from './qrl/qrl'; -export { _renderSSR } from './render/ssr/render-ssr'; -export { _hW } from './render/dom/notify-render'; -export { _wrapSignal, _wrapProp } from './state/signal'; -export { _restProps } from './state/store'; -export { _IMMUTABLE } from './state/constants'; -export { _weakSerialize } from './state/common'; -export { _deserializeData } from './container/resume'; -export { verifySerializable as _verifySerializable } from './state/common'; +export { _noopQrl, _noopQrlDEV, _regSymbol } from './shared/qrl/qrl'; +// ^ keep this above to avoid circular dependency issues + +export { + DomContainer as _DomContainer, + getDomContainer as _getDomContainer, +} from './client/dom-container'; +export { queueQRL as _run } from './client/queue-qrl'; +export type { + ContainerElement as _ContainerElement, + ElementVNode as _ElementVNode, + QDocument as _QDocument, + TextVNode as _TextVNode, + VirtualVNode as _VirtualVNode, + VNode as _VNode, + VNodeFlags as _VNodeFlags, +} from './client/types'; +export { vnode_toString as _vnode_toString } from './client/vnode'; +export { _wrapProp, _wrapSignal } from './reactive-primitives/internal-api'; +export { SubscriptionData as _SubscriptionData } from './reactive-primitives/subscription-data'; +export { _EFFECT_BACK_REF } from './reactive-primitives/types'; +export { + isStringifiable as _isStringifiable, + type Stringifiable as _Stringifiable, +} from './shared-types'; +export { + isJSXNode as _isJSXNode, + _jsxC, + _jsxQ, + _jsxS, + _jsxSorted, + _jsxSplit, +} from './shared/jsx/jsx-runtime'; +export { _fnSignal } from './shared/qrl/inlined-fn'; +export { _SharedContainer } from './shared/shared-container'; +export { + _deserialize, + dumpState as _dumpState, + preprocessState as _preprocessState, + _serializationWeakRef, + _serialize, +} from './shared/shared-serialization'; +export { _CONST_PROPS, _IMMUTABLE, _VAR_PROPS, _UNINITIALIZED } from './shared/utils/constants'; +export { EMPTY_ARRAY as _EMPTY_ARRAY } from './shared/utils/flyweight'; +export { _restProps } from './shared/utils/prop'; +export { verifySerializable as _verifySerializable } from './shared/utils/serialize-utils'; +export { _walkJSX } from './ssr/ssr-render-jsx'; export { _getContextElement, _getContextEvent, + _getContextContainer, _jsxBranch, _waitUntilRendered, } from './use/use-core'; -export { _jsxQ, _jsxC, _jsxS } from './render/jsx/jsx-runtime'; -export { _fnSignal } from './qrl/inlined-fn'; +export { scheduleTask as _task } from './use/use-task'; +export { _resolveContextWithoutSequentialScope } from './use/use-context'; diff --git a/packages/qwik/src/core/platform/types.ts b/packages/qwik/src/core/platform/types.ts deleted file mode 100644 index 2dd7fecec54..00000000000 --- a/packages/qwik/src/core/platform/types.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type { ValueOrPromise } from '../util/types'; - -// -// !!DO NOT EDIT THIS COMMENT DIRECTLY!!! -// (edit ./readme.md#CorePlatform instead) -/** - * Low-level API for platform abstraction. - * - * Different platforms (browser, node, service workers) may have different ways of handling things - * such as `requestAnimationFrame` and imports. To make Qwik platform-independent Qwik uses the - * `CorePlatform` API to access the platform API. - * - * `CorePlatform` also is responsible for importing symbols. The import map is different on the - * client (browser) then on the server. For this reason, the server has a manifest that is used to - * map symbols to javascript chunks. The manifest is encapsulated in `CorePlatform`, for this - * reason, the `CorePlatform` can't be global as there may be multiple applications running at - * server concurrently. - * - * This is a low-level API and there should not be a need for you to access this. - * - * @public - */ -// -export interface CorePlatform { - // - // !!DO NOT EDIT THIS COMMENT DIRECTLY!!! - // (edit ./readme.md#CorePlatform.isServer instead) - /** - * True of running on the server platform. - * - * @returns True if we are running on the server (not the browser.) - */ - // - isServer: boolean; - // - // !!DO NOT EDIT THIS COMMENT DIRECTLY!!! - // (edit ./readme.md#CorePlatform.importSymbol instead) - /** - * Retrieve a symbol value from QRL. - * - * Qwik needs to lazy load data and closures. For this Qwik uses QRLs that are serializable - * references of resources that are needed. The QRLs contain all the information necessary to - * retrieve the reference using `importSymbol`. - * - * Why not use `import()`? Because `import()` is relative to the current file, and the current - * file is always the Qwik framework. So QRLs have additional information that allows them to - * serialize imports relative to application base rather than the Qwik framework file. - * - * @param element - The element against which the `url` is resolved. Used to locate the container - * root and `q:base` attribute. - * @param url - Relative URL retrieved from the attribute that needs to be resolved against the - * container `q:base` attribute. - * @param symbol - The name of the symbol to import. - * @returns A promise that resolves to the imported symbol. - */ - // - importSymbol: ( - containerEl: Element | undefined, - url: string | URL | undefined | null, - symbol: string - ) => ValueOrPromise; - // - // !!DO NOT EDIT THIS COMMENT DIRECTLY!!! - // (edit ./readme.md#CorePlatform.raf instead) - /** - * Perform operation on next request-animation-frame. - * - * @param fn - The function to call when the next animation frame is ready. - */ - // - raf: (fn: () => any) => Promise; - // - // !!DO NOT EDIT THIS COMMENT DIRECTLY!!! - // (edit ./readme.md#CorePlatform.nextTick instead) - /** - * Perform operation on next tick. - * - * @param fn - The function to call when the tick is ready. - */ - // - nextTick: (fn: () => any) => Promise; - // - // !!DO NOT EDIT THIS COMMENT DIRECTLY!!! - // (edit ./readme.md#CorePlatform.chunkForSymbol instead) - /** - * Retrieve chunk name for the symbol. - * - * When the application is running on the server the symbols may be imported from different files - * (as server build is typically a single javascript chunk.) For this reason, it is necessary to - * convert the chunks from server format to client (browser) format. This is done by looking up - * symbols (which are globally unique) in the manifest. (Manifest is the mapping of symbols to the - * client chunk names.) - * - * @param symbolName - Resolve `symbolName` against the manifest and return the chunk that - * contains the symbol. - */ - // - chunkForSymbol: ( - symbolName: string, - chunk: string | null, - parent?: string - ) => readonly [symbol: string, chunk: string] | undefined; -} - -export interface CorePlatformServer extends CorePlatform { - isServer: true; -} diff --git a/packages/qwik/src/core/preloader/bundle-graph.ts b/packages/qwik/src/core/preloader/bundle-graph.ts index 7544a398670..af6ac209adf 100644 --- a/packages/qwik/src/core/preloader/bundle-graph.ts +++ b/packages/qwik/src/core/preloader/bundle-graph.ts @@ -1,4 +1,4 @@ -import { isBrowser } from '@builder.io/qwik/build'; +import { isBrowser } from '@qwik.dev/core/build'; import { config, isJSRegex } from './constants'; import { adjustProbabilities, bundles, log, shouldResetFactor, trigger } from './queue'; import type { BundleGraph, BundleImport, ImportProbability } from './types'; diff --git a/packages/qwik/src/core/preloader/constants.ts b/packages/qwik/src/core/preloader/constants.ts index 3007970ed11..e598c5cd256 100644 --- a/packages/qwik/src/core/preloader/constants.ts +++ b/packages/qwik/src/core/preloader/constants.ts @@ -1,9 +1,7 @@ -import { isBrowser } from '@builder.io/qwik/build'; +import { isBrowser } from '@qwik.dev/core/build'; // Browser-specific setup export const doc = isBrowser ? document : undefined!; -export const modulePreloadStr = 'modulepreload'; -export const preloadStr = 'preload'; export const config = { $DEBUG$: false, @@ -13,9 +11,9 @@ export const config = { // Determine which rel attribute to use based on browser support export const rel = - isBrowser && doc.createElement('link').relList.supports(modulePreloadStr) - ? modulePreloadStr - : preloadStr; + isBrowser && doc.createElement('link').relList.supports('modulepreload') + ? 'modulePreload' + : 'preload'; // Global state export const loadStart = Date.now(); diff --git a/packages/qwik/src/core/preloader/index.ts b/packages/qwik/src/core/preloader/index.ts index 77a4eb55455..8a4b411dd53 100644 --- a/packages/qwik/src/core/preloader/index.ts +++ b/packages/qwik/src/core/preloader/index.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ /** * Note: this file gets built separately from the rest of the core module, and is then kept separate * in the dist directory via manualChunks. This way it can run before the rest of the core module is diff --git a/packages/qwik/src/core/preloader/preloader.unit.ts b/packages/qwik/src/core/preloader/preloader.unit.ts index 0e61807f611..823a6794c2c 100644 --- a/packages/qwik/src/core/preloader/preloader.unit.ts +++ b/packages/qwik/src/core/preloader/preloader.unit.ts @@ -21,5 +21,5 @@ test('preloader script', () => { * dereference objects etc, but that actually results in worse compression */ const compressed = compress(Buffer.from(preLoader), { mode: 1, quality: 11 }); - expect([compressed.length, preLoader.length]).toEqual([1878, 5614]); + expect([compressed.length, preLoader.length]).toEqual([1855, 5490]); }); diff --git a/packages/qwik/src/core/preloader/queue.ts b/packages/qwik/src/core/preloader/queue.ts index ca1c521a3c8..786e32ccef3 100644 --- a/packages/qwik/src/core/preloader/queue.ts +++ b/packages/qwik/src/core/preloader/queue.ts @@ -1,4 +1,4 @@ -import { isBrowser } from '@builder.io/qwik/build'; +import { isBrowser } from '@qwik.dev/core/build'; import { base, getBundle, graph } from './bundle-graph'; import { config, doc, loadStart, rel } from './constants'; import type { BundleImport, BundleImports } from './types'; @@ -8,7 +8,7 @@ import { BundleImportState_Preload, BundleImportState_Queued, } from './types'; -import type { QwikSymbolEvent } from '../render/jsx/types/jsx-qwik-events'; +import type { QwikSymbolEvent } from '../shared/jsx/types/jsx-qwik-events'; export const bundles: BundleImports = new Map(); export let shouldResetFactor: boolean; diff --git a/packages/qwik/src/core/qrl/inlined-fn.ts b/packages/qwik/src/core/qrl/inlined-fn.ts deleted file mode 100644 index 9a742bca716..00000000000 --- a/packages/qwik/src/core/qrl/inlined-fn.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { assertDefined } from '../error/assert'; -import { SignalDerived } from '../state/signal'; -import { qSerialize } from '../util/qdev'; - -/** @internal */ -export const _fnSignal = any>( - fn: T, - args: Parameters, - fnStr?: string -) => { - return new SignalDerived, Parameters>(fn, args, fnStr); -}; - -export const serializeDerivedSignalFunc = (signal: SignalDerived) => { - const fnBody = qSerialize ? signal.$funcStr$ : 'null'; - assertDefined(fnBody, 'If qSerialize is true then fnStr must be provided.'); - let args = ''; - for (let i = 0; i < signal.$args$.length; i++) { - args += `p${i},`; - } - return `(${args})=>(${fnBody})`; -}; diff --git a/packages/qwik/src/core/qrl/qrl-class.ts b/packages/qwik/src/core/qrl/qrl-class.ts deleted file mode 100644 index 5f73755f45f..00000000000 --- a/packages/qwik/src/core/qrl/qrl-class.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { assertDefined } from '../error/assert'; -import { qError, QError_qrlIsNotFunction } from '../error/error'; -import { getPlatform, isServerPlatform } from '../platform/platform'; -import { verifySerializable } from '../state/common'; -import { isSignal, type SignalInternal } from '../state/signal'; -import { - invoke, - newInvokeContext, - newInvokeContextFromTuple, - tryGetInvokeContext, - type InvokeContext, - type InvokeTuple, -} from '../use/use-core'; -import { getQFuncs, QInstance } from '../util/markers'; -import { isPromise, maybeThen } from '../util/promises'; -import { qDev, qSerialize, qTest, seal } from '../util/qdev'; -import { isArray, isFunction, type ValueOrPromise } from '../util/types'; -// @ts-expect-error we don't have types for the preloader -import { p as preload } from '@builder.io/qwik/preloader'; -import type { QRLDev } from './qrl'; -import type { QRL, QrlArgs, QrlReturn } from './qrl.public'; -import { isBrowser } from '@builder.io/qwik/build'; - -export const isQrl = (value: unknown): value is QRLInternal => { - return typeof value === 'function' && typeof (value as any).getSymbol === 'function'; -}; - -// Make sure this value is same as value in `platform.ts` -export const SYNC_QRL = ''; - -/** Sync QRL is a function which is serialized into ` will escape the content, effectively breaking the inlined JS. -In order to disable content escaping use ' - , - ` - - - - ` - ); -}); - -test('single complex children', async () => { - await testSSR( -
-

hola

-
, - '

hola

', - { - containerTagName: 'container', - } - ); - await testSSR( -
- hola {2} -

hola

-
, - '
hola 2

hola

', - { - containerTagName: 'container', - } - ); -}); - -test('single multiple children', async () => { - await testSSR( -
    -
  • 1
  • -
  • 2
  • -
  • 3
  • -
  • 4
  • -
  • 5
  • -
  • 6
  • -
  • 7
  • -
  • 8
  • -
, - '
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
', - { - containerTagName: 'container', - } - ); -}); - -test('sanity', async () => { - await testSSR( - -
{`.rule > thing{}`}
- , - ` - -
.rule > thing{}
- - ` - ); -}); - -test('using fragment', async () => { - await testSSR( -
    - <> -
  • 1
  • -
  • 2
  • - -
  • 3
  • - <> -
  • 4
  • - <> -
  • 5
  • - <> - <> -
  • 6
  • - - - -
  • 7
  • - -
  • 8
  • -
, - '
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
', - { - containerTagName: 'container', - } - ); -}); - -test('using promises', async () => { - await testSSR( - {Promise.resolve('hola')}, - 'hola' - ); - await testSSR( - {Promise.resolve(

hola

)}, - '

hola

' - ); - - await testSSR( -
    - {Promise.resolve(
  • 1
  • )} -
  • 2
  • - {delay(100).then(() => ( -
  • 3
  • - ))} - {delay(10).then(() => ( -
  • 4
  • - ))} -
, - [ - '', - '
    ', - '', - '
  • ', - '1', - '
  • ', - '
  • ', - '2', - '
  • ', - '', - '
  • ', - '3', - '
  • ', - '', - '
  • ', - '4', - '
  • ', - '
', - '
', - ], - { - containerTagName: 'container', - } - ); -}); - -test('mixed children', async () => { - await testSSR( -
    -
  • 0
  • -
  • 1
  • -
  • 2
  • - {Promise.resolve(
  • 3
  • )} -
  • 4
  • - {delay(100).then(() => ( -
  • 5
  • - ))} - {delay(10).then(() => ( -
  • 6
  • - ))} -
, - ` - -
    -
  • 0
  • -
  • 1
  • -
  • 2
  • - -
  • 3
  • -
  • 4
  • - -
  • 5
  • - -
  • 6
  • -
-
`, - { - containerTagName: 'container', - } - ); -}); - -test('DelayResource', async () => { - await testSSR( - -
    - - -
- , - ` - -
    - - -
    thing
    - - -
    thing
    - -
- - ` - ); -}); - -test('using promises with DelayResource', async () => { - await testSSR( - -
    - {delay(10).then(() => ( -
  • thing
  • - ))} - -
- , - ` - -
    - -
  • thing
  • - - -
    thing
    - -
- - ` - ); -}); - -test('using component', async () => { - await testSSR( - , - ` - -
MyCmp{}
- -
`, - { - containerTagName: 'container', - } - ); -}); - -test('using component with key', async () => { - await testSSR( - - - , - ` - - -
MyCmp{}
- - - ` - ); -}); - -test('using element with key', async () => { - await testSSR( - -
- , - ` - -
- - ` - ); -}); - -test('using element with key containing double quotes', async () => { - await testSSR( - -
- , - ` - -
- - ` - ); -}); - -test('using component props', async () => { - await testSSR( - - stuff - , - ` - - -
-
MyCmp{"id":"12","host:prop":"attribute","innerHTML":"123","dangerouslySetInnerHTML":"432","onClick":"lazy.js","prop":"12"}
-
- - -
- `, - { - containerTagName: 'container', - } - ); -}); - -test('using component project content', async () => { - await testSSR( - -
slot
-
, - ` - - -
MyCmp{}
- - -
-`, - { - containerTagName: 'container', - } - ); -}); - -test('using complex component', async () => { - await testSSR( - - - , - ` - - -
- - -
- - - ` - ); -}); - -test('using complex component with slot', async () => { - await testSSR( - Hola, - ` - - -
- - - Hola - -
- -
`, - { - containerTagName: 'container', - } - ); -}); - -test('', async () => { - await testSSR( - - hola - <> - - - , - ` - - - hola - - - ` - ); -}); - -test('named slots', async () => { - await testSSR( - - Text -
START: 1
- <> -
END: 1
- from -
START: 2
- -
END: 2
- default -
, - ` - - -
- -
START: 1
-
START: 2
- -
Textfromdefault
- -
END: 1
-
END: 2
- -
- -
-`, - { - containerTagName: 'container', - } - ); -}); - -test('nested slots', async () => { - await testSSR( - - - - BEFORE CONTENT -
Content
- AFTER CONTENT -
-
-
, - ` - - -
- Before root - - -
- Before level 1 - - -
- Before level 2 - - BEFORE CONTENT -
Content
- AFTER CONTENT - - After level 2 -
- - - After level 1 -
- - - After root -
- -
`, - { - containerTagName: 'container', - } - ); -}); - -test('mixes slots', async () => { - await testSSR( - Content, - ` - - - -
Before 1 - - - Content - - - After 1 -
- - -
`, - { - containerTagName: 'container', - } - ); -}); - -test('component RenderSignals()', async () => { - vi.spyOn(console, 'warn'); - await testSSR( - , - ` - - - - value - - - - - ` - ); - expect(console.warn).toHaveBeenCalledTimes(2); -}); - -test('component useContextProvider()', async () => { - await testSSR( - - - , - ` - - - hello bye - - hello bye - - ` - ); -}); - -test('component useContextProvider() + useContext()', async () => { - await testSSR( - , - ` - hello bye - ` - ); -}); - -test('component slotted context', async () => { - await testSSR( - - - - - - - , - ` - - - - - - - - start - - - - - - - - - default - - - - - - - - - end - - - - - - - ` - ); -}); - -test('component useOn()', async () => { - await testSSR( - - - , - ` - - -
- - - ` - ); -}); - -test('component useOn([array])', async () => { - await testSSR( - - - , - ` - - - -
- - - ` - ); -}); - -test('component useStyles()', async () => { - await testSSR( - <> - - - - , - ` - - - -
- Text -
- - - ` - ); -}); - -test('component useStylesScoped()', async () => { - await testSSR( - <> - - -
projected
-
- - , - ` - - - - - -
-
- Scoped1 - -
projected
- -

Que tal?

-
- - -
-
- Scoped2 -

Bien

-
-
- - -
-
- Scoped2 -

Bien

-
-
- -
- - - - ` - ); -}); - -test('component useStylesScoped() + slot', async () => { - await testSSR( - <> - - , - ` - - - - - -
- -
One
- -
- - - - - - ` - ); -}); - -test('component useBrowserVisibleTask()', async () => { - await testSSR( - , - ` - -
- -
`, - { - containerTagName: 'container', - } - ); -}); - -test('component useBrowserVisibleTask() without elements', async () => { - await testSSR( - - - , - ` - - - - Hola - - - - - ` - ); -}); - -test('component useBrowserVisibleTask() inside ', async () => { - await testSSR( - - - - , - ` - - - - Hola - - - - - - - ` - ); -}); - -test('nested html', async () => { - await testSSR( - <> - - , - `` - ); -}); - -test('root html component', async () => { - await testSSR( - - - , - ` - - - - hola - - - - - - - ` - ); -}); - -test('containerTagName', async () => { - await testSSR( - <> - - -
- , - ` - - - -
Text
- - -
- -
-
`, - { - containerTagName: 'container', - base: '/manu/folder', - beforeContent: [jsx('link', { rel: 'stylesheet', href: '/global.css' })], - } - ); -}); - -test('containerAttributes', async () => { - await testSSR( - <> - - , - ` - - - - `, - { - containerAttributes: { - prefix: 'something', - }, - } - ); - await testSSR( - <> -
- , - ` - -
-
- `, - { - containerTagName: 'app', - containerAttributes: { - prefix: 'something', - class: 'thing', - }, - } - ); -}); - -test('custom q:render', async () => { - await testSSR( - <> - - , - ` - - - - `, - { - containerAttributes: { - 'q:render': 'static', - }, - } - ); - await testSSR( - <> - - , - ` - - - - `, - { - containerAttributes: { - 'q:render': '', - }, - } - ); -}); - -test('ssr marks', async () => { - await testSSR( - - {delay(100).then(() => ( -
  • 1
  • - ))} - {delay(10).then(() => ( -
  • 2
  • - ))} - -
    - -
    - {delay(120).then(() => ( -
  • 3
  • - ))} - , - ` - - -
  • 1
  • - -
  • 2
  • - -
    - -
    - -
  • 3
  • - - ` - ); -}); - -test('ssr raw', async () => { - await testSSR( - - - , - ` - - -
    hello
    - - ` - ); -}); - -test('html fragment', async () => { - await testSSR( - - - , - ` - - - -
    hello
    - - - ` - ); -}); - -test('html slot', async () => { - await testSSR( - - - - Qwik - - -
    - -
    , - ` - - - - - - Qwik - - - - -
    - - - - `, - { - beforeContent: [jsx('link', { rel: 'stylesheet', href: '/global.css' })], - base: '/manu/folder', - } - ); -}); - -test('null component', async () => { - await testSSR( - <> - - , - `` - ); -}); - -test('cleanse attribute name', async () => { - const o = { - '">': 'xss', - }; - await testSSR( - , - '' - ); -}); - -test('cleanse class attribute', async () => { - const o = { - class: '">', - }; - await testSSR( - , - '' - ); -}); - -test('class emoji valid', async () => { - const o = { - class: 'package📦', - }; - await testSSR( - , - '' - ); -}); - -test('issue 4283', async () => { - await testSSR( - - -

    index page

    -
    - , - ` - - - - -
    - - - - - - ` - ); -}); - -// TODO -// Merge props on host -// - host events -// - class -// - style -// Container with tagName -// End-to-end with qwikcity -// SVG rendering -// Performance metrics - -export const MyCmp = component$((props: Record) => { - return ( -
    -
    - MyCmp - {JSON.stringify(props)} -
    -
    - ); -}); - -export const MyCmpComplex = component$(() => { - const ref = useSignal(); - return ( -
    console.warn('from component')}> - - -
    - ); -}); - -export const SimpleSlot = component$((props: { name: string }) => { - return ( -
    - Before {props.name} - - After {props.name} -
    - ); -}); - -export const MixedSlot = component$(() => { - return ( - - - - ); -}); - -export const NamedSlot = component$(() => { - return ( -
    - -
    - -
    - -
    - ); -}); - -export const Events = component$(() => { - useOn( - 'click', - $(() => console.warn('click')) - ); - useOnWindow( - 'click', - $(() => console.warn('window:click')) - ); - useOnDocument( - 'click', - $(() => console.warn('document:click')) - ); - - return
    console.warn('scroll')}>
    ; -}); - -export const UseOnMultiple = component$(() => { - useOn( - ['click', 'scroll'], - $(() => console.warn('click or scroll')) - ); - useOnWindow( - ['click', 'scroll'], - $(() => console.warn('window:click or scroll')) - ); - useOnDocument( - ['click', 'scroll'], - $(() => console.warn('document:click or scroll')) - ); - - return
    console.warn('scroll')}>
    ; -}); - -export const Styles = component$(() => { - useStylesQrl(inlinedQrl('.host {color: red}', 'styles_987')); - - return
    Text
    ; -}); - -export const ScopedStyles1 = component$(() => { - useStylesScopedQrl(inlinedQrl('.host {color: red}', 'styles_scoped_1')); - useStylesScopedQrl(inlinedQrl('.blue {color: blue}', 'styles_scoped_2')); - - return ( -
    -
    - Scoped1 - -

    Que tal?

    -
    - - -
    - ); -}); - -export const ScopedStyles2 = component$(() => { - useStylesScopedQrl(inlinedQrl('.host {color: blue}', '20_styles_scoped')); - - return ( -
    -
    - Scoped2 -

    Bien

    -
    -
    - ); -}); - -export const RootStyles = component$(() => { - useStylesScopedQrl(inlinedQrl('.host {background: blue}', '20_stylesscopedblue')); - - return ( - - -
    One
    -
    Two
    -
    - - ); -}); - -export const ComponentA = component$(() => { - useStylesScopedQrl(inlinedQrl('.host {background: green}', '20_stylesscopedgreen')); - - return ( -
    - -
    - ); -}); - -const CTX_INTERNAL = createContextId<{ value: string }>('internal'); -const CTX_QWIK_CITY = createContextId<{ value: string }>('qwikcity'); -const CTX_VALUE = createContextId<{ value: string }>('value'); - -export const VariadicContext = component$(() => { - return ( - <> - - - - - - - - - - - ); -}); - -export const ReadValue = component$(() => { - const ctx = useContext(CTX_VALUE); - return {ctx.value}; -}); - -export const ContextWithValue = component$((props: { value: string }) => { - const value = { - value: props.value, - }; - useContextProvider(CTX_VALUE, value); - return ( - <> - - - ); -}); - -export const ContextWithValueAndUse = component$((props: { value: string }) => { - const value = { - value: props.value, - }; - useContextProvider(CTX_VALUE, value); - const ctx = useContext(CTX_VALUE); - return <>{ctx.value}; -}); - -export const Context = component$(() => { - useContextProvider(CTX_INTERNAL, { - value: 'hello', - }); - useContextProvider(CTX_QWIK_CITY, { - value: 'bye', - }); - return ( - <> - - - - ); -}); - -export const ContextConsumer = component$(() => { - const internal = useContext(CTX_INTERNAL); - const qwikCity = useContext(CTX_QWIK_CITY); - - return ( - <> - {internal.value} {qwikCity.value} - - ); -}); - -export const UseClientEffect = component$((props: any) => { - useVisibleTask$(() => { - console.warn('client effect'); - }); - useVisibleTask$(() => { - console.warn('second client effect'); - }); - useTask$(async () => { - await delay(10); - }); - - const Div = props.as ?? 'div'; - return
    ; -}); - -export const UseEmptyClientEffect = component$(() => { - useVisibleTask$(() => { - console.warn('client effect'); - }); - useVisibleTask$(() => { - console.warn('second client effect'); - }); - useTask$(async () => { - await delay(10); - }); - - return <>Hola; -}); - -export const HeadCmp = component$(() => { - useVisibleTask$(() => { - console.warn('client effect'); - }); - return ( - - hola - - - ); -}); - -export const RenderSignals = component$(() => { - const signal = useSignal('value'); - return ( - <> - - {signal.value} - - - - - ); -}); - -export const HtmlContext = component$(() => { - const store = useStore({}); - useStylesQrl(inlinedQrl(`body {background: blue}`, 'styles_DelayResource')); - useContextProvider(CTX_INTERNAL, store); - - return ; -}); - -async function testSSR( - node: JSXOutput, - expected: string | string[], - opts?: Partial -) { - let chunks: string[] = []; - const stream: StreamWriter = { - write(chunk) { - chunks.push(chunk); - }, - }; - await _renderSSR(node, { - stream, - containerTagName: 'html', - containerAttributes: {}, - manifestHash: 'test', - ...opts, - }); - chunks = chunks.map((c) => c.replace(/ q:instance="[^"]+"/, '')); - if (typeof expected === 'string') { - const options = { parser: 'html', htmlWhitespaceSensitivity: 'ignore' } as const; - expect(await format(chunks.join(''), options)).toBe( - await format(expected.replace(/(\n|^)\s+/gm, ''), options) - ); - } else { - expect(chunks).toEqual(expected); - } -} - -export const DelayResource = component$((props: { text: string; delay: number }) => { - useStylesQrl(inlinedQrl(`.cmp {background: blue}`, 'styles_DelayResource')); - - const resource = useResource$(async ({ track }) => { - track(() => props.text); - await delay(props.delay); - return props.text; - }); - return ( -
    - {value}} /> -
    - ); -}); - -export const NullCmp = component$(() => { - return null; -}); - -export const EffectTransparent = component$(() => { - useVisibleTask$(() => { - console.warn('log'); - }); - return ; -}); - -export const EffectTransparentRoot = component$(() => { - useVisibleTask$(() => { - console.warn('log'); - }); - return ( - -
    Hello
    -
    - ); -}); - -export const HideUntilVisible = component$(() => { - const isNotVisible = useSignal(true); - - useVisibleTask$(() => { - if (isNotVisible.value) { - isNotVisible.value = false; - } - }); - - // NOTE: if you comment the line below, - // there will only be one "Content" - if (isNotVisible.value) { - return
    ; - } - - return ( -
    -

    Hide until visible

    - -
    - ); -}); - -export const Issue4283 = component$(() => { - return ( - -

    Content

    - -
    - ); -}); - -async function throws(fn: () => T, expected?: string | RegExp): Promise { - try { - await fn(); - expect.unreachable('Expression should throw'); - } catch (e) { - if (expected) { - expect(String(e)).toBe(expected); - } - } -} diff --git a/packages/qwik/src/core/render/sync-qrl.unit.tsx b/packages/qwik/src/core/render/sync-qrl.unit.tsx deleted file mode 100644 index b2b35510591..00000000000 --- a/packages/qwik/src/core/render/sync-qrl.unit.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { assert, suite, test } from 'vitest'; -import { createDOM } from '../../testing/library'; -import { sync$ } from '../qrl/qrl.public'; -import { renderToString } from '../../server/render'; - -suite('sync-qrl', () => { - test('default updates the checkbox', async () => { - const { screen, render } = await createDOM(); - await render(); - const input = screen.querySelector('input')!; - assert.equal(input.checked, false); - input.click(); - assert.equal(input.checked, true); - }); - - test('default prevents updates the checkbox', async () => { - const { screen, userEvent, render } = await createDOM(); - await render( - { - if (target.getAttribute('shouldPreventDefault')) { - e.preventDefault(); - } - target.setAttribute('prevented', String(e.defaultPrevented)); - }), - ]} - /> - ); - const input = screen.querySelector('input')!; - await userEvent(input, 'click'); - assert.equal(input.getAttribute('prevented'), 'false'); - input.setAttribute('shouldPreventDefault', 'true'); - await userEvent(input, 'click'); - assert.equal(input.getAttribute('prevented'), 'true'); - }); - - // Currently the testing does not support resuming from SSR - test.skip('render SSR', async () => { - const response = await renderToString( - , - { containerTagName: 'container' } - ); - - const { screen, userEvent } = await createDOM({ html: response.html }); - const input = screen.querySelector('input')!; - await userEvent(input, 'click'); - assert.equal(input.getAttribute('prevented'), 'false'); - input.setAttribute('shouldPreventDefault', 'true'); - await userEvent(input, 'click'); - assert.equal(input.getAttribute('prevented'), 'true'); - }); -}); diff --git a/packages/qwik/src/core/render/types.ts b/packages/qwik/src/core/render/types.ts deleted file mode 100644 index b67de4e8d1f..00000000000 --- a/packages/qwik/src/core/render/types.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { ContainerState } from '../container/container'; -import type { QContext } from '../state/context'; -import type { QwikElement } from './dom/virtual-element'; - -/** @public */ -export interface RenderOperation { - $operation$: (...args: any[]) => void; - $args$: any[]; -} - -/** @public */ -export interface RenderContext { - readonly $static$: RenderStaticContext; - /** Current Qwik component */ - $cmpCtx$: QContext | null; - /** Current Slot parent */ - $slotCtx$: QContext | undefined; -} - -export interface RenderStaticContext { - readonly $locale$: string; - readonly $doc$: Document; - readonly $roots$: QContext[]; - readonly $hostElements$: Set; - readonly $visited$: (Node | QwikElement)[]; - readonly $operations$: RenderOperation[]; - readonly $postOperations$: RenderOperation[]; - readonly $containerState$: ContainerState; - readonly $addSlots$: [QwikElement, QwikElement][]; - readonly $rmSlots$: QwikElement[]; -} - -// Polyfills for ViewTransition API & scroll restoration -declare global { - interface ViewTransition { - ready: Promise; - finished: Promise; - updateCallbackDone: Promise; - skipTransition: () => void; - } - - interface Document { - startViewTransition?: (callback: () => void | Promise) => ViewTransition; - __q_view_transition__?: true | undefined; - __q_scroll_restore__?: (() => void) | undefined; - } -} diff --git a/packages/qwik/src/core/shared-types.ts b/packages/qwik/src/core/shared-types.ts new file mode 100644 index 00000000000..bcadfe0c511 --- /dev/null +++ b/packages/qwik/src/core/shared-types.ts @@ -0,0 +1,14 @@ +/** @file Shared types */ + +/** @internal */ +export type Stringifiable = string | boolean | number | null; + +/** @internal */ +export function isStringifiable(value: unknown): value is Stringifiable { + return ( + value === null || + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' + ); +} diff --git a/packages/qwik/src/core/shared/README.md b/packages/qwik/src/core/shared/README.md new file mode 100644 index 00000000000..fe194a37994 --- /dev/null +++ b/packages/qwik/src/core/shared/README.md @@ -0,0 +1,3 @@ +Contains code which is shared between server and client. + +(This means that the code con't depend on server or client specific API) diff --git a/packages/qwik/src/core/shared/component-execution.ts b/packages/qwik/src/core/shared/component-execution.ts new file mode 100644 index 00000000000..094648e1179 --- /dev/null +++ b/packages/qwik/src/core/shared/component-execution.ts @@ -0,0 +1,318 @@ +import { isDev } from '@qwik.dev/core/build'; +import { vnode_isVNode } from '../client/vnode'; +import { Slot } from '../shared/jsx/slot.public'; +import { isSignal } from '../reactive-primitives/utils'; +import { clearAllEffects } from '../reactive-primitives/cleanup'; +import { invokeApply, newInvokeContext, untrack } from '../use/use-core'; +import { type EventQRL, type UseOnMap } from '../use/use-on'; +import { isQwikComponent, type OnRenderFn } from './component.public'; +import { assertDefined } from './error/assert'; +import { Fragment, JSXNodeImpl, _jsxSorted, isJSXNode, type Props } from './jsx/jsx-runtime'; +import type { JSXNodeInternal, JSXOutput } from './jsx/types/jsx-node'; +import type { KnownEventNames } from './jsx/types/jsx-qwik-events'; +import type { QRLInternal } from './qrl/qrl-class'; +import { isQrl } from './qrl/qrl-utils'; +import type { Container, HostElement } from './types'; +import { EMPTY_OBJ } from './utils/flyweight'; +import { logWarn } from './utils/log'; +import { + ELEMENT_PROPS, + ELEMENT_SEQ_IDX, + OnRenderProp, + RenderEvent, + USE_ON_LOCAL, + USE_ON_LOCAL_SEQ_IDX, +} from './utils/markers'; +import { MAX_RETRY_ON_PROMISE_COUNT, isPromise, maybeThen, safeCall } from './utils/promises'; +import { isArray, isPrimitive, type ValueOrPromise } from './utils/types'; +import { getSubscriber } from '../reactive-primitives/subscriber'; +import { EffectProperty } from '../reactive-primitives/types'; +import { EventNameJSXScope } from './utils/event-names'; + +/** + * Use `executeComponent` to execute a component. + * + * Component execution can be complex because of: + * + * - It can by async + * - It can contain many tasks which need to be awaited + * - Each task can run multiple times if they track signals which change. + * - The JSX may be re-generated multiple times of a task needs to be rerun due to signal change. + * - It needs to keep track of hook state. + * + * For `component$`: `renderHost` === `subscriptionHost` For inlined-components: the + * `subscriptionHost` is a parent `component$` which needs to re-execute. + * + * @param container + * @param renderHost - VNode into which the component is rendered into. + * @param subscriptionHost - VNode which will be re-executed if the component needs to re-render. + * @param componentQRL + * @param props + * @returns + */ +export const executeComponent = ( + container: Container, + renderHost: HostElement, + subscriptionHost: HostElement | null, + componentQRL: OnRenderFn | QRLInternal> | null, + props: Props | null +): ValueOrPromise => { + const iCtx = newInvokeContext( + container.$locale$, + subscriptionHost || undefined, + undefined, + RenderEvent + ); + if (subscriptionHost) { + iCtx.$effectSubscriber$ = getSubscriber(subscriptionHost, EffectProperty.COMPONENT); + iCtx.$container$ = container; + } + let componentFn: (props: unknown) => ValueOrPromise; + container.ensureProjectionResolved(renderHost); + let isInlineComponent = false; + if (componentQRL === null) { + componentQRL = container.getHostProp(renderHost, OnRenderProp)!; + assertDefined(componentQRL, 'No Component found at this location'); + } + if (isQrl(componentQRL)) { + props = props || container.getHostProp(renderHost, ELEMENT_PROPS) || EMPTY_OBJ; + if (props.children) { + delete props.children; + } + componentFn = componentQRL.getFn(iCtx); + } else if (isQwikComponent(componentQRL)) { + const qComponentFn = componentQRL as any as ( + props: Props, + key: string | null, + flags: number + ) => JSXNodeInternal; + componentFn = () => invokeApply(iCtx, qComponentFn, [props || EMPTY_OBJ, null, 0]); + } else { + isInlineComponent = true; + const inlineComponent = componentQRL as (props: Props) => JSXOutput; + componentFn = () => invokeApply(iCtx, inlineComponent, [props || EMPTY_OBJ]); + } + + const executeComponentWithPromiseExceptionRetry = (retryCount = 0): ValueOrPromise => + safeCall( + () => { + if (!isInlineComponent) { + container.setHostProp(renderHost, ELEMENT_SEQ_IDX, null); + container.setHostProp(renderHost, USE_ON_LOCAL_SEQ_IDX, null); + } + + if (vnode_isVNode(renderHost)) { + clearAllEffects(container, renderHost); + } + + return componentFn(props); + }, + (jsx) => { + const useOnEvents = container.getHostProp(renderHost, USE_ON_LOCAL); + if (useOnEvents) { + return addUseOnEvents(jsx, useOnEvents); + } + return jsx; + }, + (err) => { + if (isPromise(err) && retryCount < MAX_RETRY_ON_PROMISE_COUNT) { + return err.then(() => + executeComponentWithPromiseExceptionRetry(++retryCount) + ) as Promise; + } else { + if (retryCount >= MAX_RETRY_ON_PROMISE_COUNT) { + throw new Error(`Max retry count of component execution reached`); + } + throw err; + } + } + ); + return executeComponentWithPromiseExceptionRetry(); +}; + +/** + * Adds `useOn` events to the JSX output. + * + * @param jsx The JSX output to modify. + * @param useOnEvents The `useOn` events to add. + * @returns The modified JSX output. + */ +function addUseOnEvents( + jsx: JSXOutput, + useOnEvents: UseOnMap +): ValueOrPromise | null | JSXOutput> { + const jsxElement = findFirstElementNode(jsx); + let jsxResult = jsx; + const qVisibleEvent = 'onQvisible$'; + return maybeThen(jsxElement, (jsxElement) => { + // headless components are components that don't render a real DOM element + const isHeadless = !jsxElement; + // placeholder element is a '; + return ( + + ); + }); + + const { vNode, document } = await render(, { debug }); + expect(vNode).toMatchVDOM( + + + + ); + + await expect(document.querySelector('button')).toMatchDOM( + + ); + + await trigger(document.body, 'button', 'click'); + expect(vNode).toMatchVDOM( + + + + ); + + await expect(document.querySelector('button')).toMatchDOM( + + ); + }); + + it('should render correctly with comment nodes', async () => { + const Cmp = component$(() => { + const counter = useSignal(0); + const doubleCounter = useComputed$(() => counter.value * 2); + return ( + <> + + + {counter.value} + +
    {doubleCounter.value}
    + + + ); + }); + const { vNode, document } = await render(, { debug }); + expect(vNode).toMatchVDOM( + + + + 0 +
    + 0 +
    +
    +
    + ); + await trigger(document.body, 'button', 'click'); + expect(vNode).toMatchVDOM( + + + + 1 +
    + 2 +
    +
    +
    + ); + }); + + it('should rerender components with the same key and different props', async () => { + const Child = component$<{ value: number; active: boolean }>((props) => { + return ( +
    + Child {props.value}, active: {props.active ? 'true' : 'false'} +
    + ); + }); + + const Cmp = component$(() => { + const signal = useSignal(1); + + const children = [1, 2]; + + return ( +
    + + + + <> + {children.map((value) => { + return ; + })} + +
    + ); + }); + + const { vNode, document } = await render(, { debug }); + await trigger(document.body, '#button-2', 'click'); + expect(vNode).toMatchVDOM( + +
    + + + + +
    + {'Child '} + {'1'} + {', active: '} + {'false'} +
    +
    + +
    + {'Child '} + {'2'} + {', active: '} + {'true'} +
    +
    +
    +
    +
    + ); + await trigger(document.body, '#button-1', 'click'); + expect(vNode).toMatchVDOM( + +
    + + + + +
    + {'Child '} + {'1'} + {', active: '} + {'true'} +
    +
    + +
    + {'Child '} + {'2'} + {', active: '} + {'false'} +
    +
    +
    +
    +
    + ); + }); + + it('should preserve the same elements', async () => { + const Cmp = component$(() => { + const keys = useSignal(['A', 'B', 'C']); + + return ( +
    (keys.value = ['B', 'C', 'A'])}> + {keys.value.map((key) => ( + + {key} + + ))} +
    + ); + }); + + const { vNode, document } = await render(, { debug }); + expect(vNode).toMatchVDOM( + +
    + A + B + C +
    +
    + ); + const a1 = document.getElementById('A'); + const b1 = document.getElementById('B'); + const c1 = document.getElementById('C'); + await trigger(document.body, 'div', 'click'); + expect(vNode).toMatchVDOM( + +
    + B + C + A +
    +
    + ); + const a2 = document.getElementById('A'); + const b2 = document.getElementById('B'); + const c2 = document.getElementById('C'); + expect(a1).toBe(a2); + expect(b1).toBe(b2); + expect(c1).toBe(c2); + }); + + it('should render correctly component only with text node and node sibling', async () => { + const Child = component$(() => { + return <>0; + }); + const Parent = component$(() => { + return ( + <> +
    + +
    + test + + ); + }); + const { vNode } = await render(, { debug }); + expect(vNode).toMatchVDOM( + + +
    + + 0 + +
    + test +
    +
    + ); + }); + + it('should rerender both text nodes', async () => { + const SecretForm = component$(() => { + const message = useSignal(''); + const secret = useSignal(''); + + return ( + <> + {message.value &&

    {message.value}

    } + {secret.value &&

    {secret.value}

    } + + + ); + }); + + const { vNode, document } = await render(, { debug }); + expect(vNode).toMatchVDOM( + + + {''} + {''} + + + + ); + await trigger(document.body, 'button', 'click'); + expect(vNode).toMatchVDOM( + + +

    + {'foo'} +

    +

    + {'bar'} +

    + +
    +
    + ); + }); + + it('should render all components', async () => { + const Ref = component$((props: { id: string }) => { + return
    ; + }); + + const Cmp = component$(() => { + const state = useStore({ + visible: false, + }); + + useVisibleTask$(() => { + state.visible = true; + }); + return ( +
    + + {state.visible && } + + + {state.visible && } + + + {state.visible && } +
    + ); + }); + + const { vNode, document } = await render(, { debug }); + + if (render === ssrRenderToDom) { + expect(vNode).toMatchVDOM( + +
    +
    +
    +
    +
    +
    + ); + await trigger(document.body, '#parent', 'qvisible'); + } + + expect(vNode).toMatchVDOM( + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + ); + }); + + it('should destructure arguments', async () => { + const PropsDestructuring = component$( + ({ message, id, count: c, ...rest }: Record) => { + const renders = useStore( + { renders: 0 }, + { + reactive: false, + } + ); + renders.renders++; + const rerenders = renders.renders + 0; + return ( +
    + + {message} {c} + +
    {rerenders}
    +
    + ); + } + ); + + const MyComp = component$(() => { + const state = useSignal(0); + return ( + <> + +