diff --git a/.changeset/fluffy-tools-do.md b/.changeset/fluffy-tools-do.md new file mode 100644 index 00000000000..434fbec9edf --- /dev/null +++ b/.changeset/fluffy-tools-do.md @@ -0,0 +1,5 @@ +--- +'@builder.io/qwik': patch +--- + +docs: remove shop diff --git a/.changeset/warm-tools-tickle.md b/.changeset/warm-tools-tickle.md new file mode 100644 index 00000000000..b1956385cae --- /dev/null +++ b/.changeset/warm-tools-tickle.md @@ -0,0 +1,5 @@ +--- +'@builder.io/qwik': patch +--- + +docs: add Qwik blog + articles diff --git a/packages/docs/public/_redirects b/packages/docs/public/_redirects index a4965aab781..ddc89bbb39f 100644 --- a/packages/docs/public/_redirects +++ b/packages/docs/public/_redirects @@ -67,4 +67,5 @@ /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 \ No newline at end of file +/docs/think-qwik/ /docs/concepts/think-qwik/ 308 +/docs/env-variables/ /docs/guides/env-variables/ 308 diff --git a/packages/docs/src/components/footer/footer.tsx b/packages/docs/src/components/footer/footer.tsx index e74e9c2b86d..568a73bfcac 100644 --- a/packages/docs/src/components/footer/footer.tsx +++ b/packages/docs/src/components/footer/footer.tsx @@ -3,6 +3,7 @@ import { QwikLogo } from '~/components/svgs/qwik-logo'; import { DiscordLogo } from '~/components/svgs/discord-logo'; import { GithubLogo } from '~/components/svgs/github-logo'; import { TwitterLogo } from '~/components/svgs/twitter-logo'; +import { BlueskyLogo } from '~/components/svgs/bluesky-logo'; const baseUrl = 'https://qwik.dev'; const linkColumns = [ @@ -51,7 +52,7 @@ export const Footer = component$(() => {
-

MIT License © 2024

+

MIT License © {new Date().getFullYear()}

@@ -66,7 +67,11 @@ export const FooterLinks = component$(() => { {linkColumns.map((column, colIndex) => (
{column.map((link, linkIndex) => ( - + {link.title} ))} @@ -81,6 +86,7 @@ export const FooterSocialLinks = component$(() => { { href: 'https://qwik.dev/chat', title: 'Discord', Logo: DiscordLogo }, { href: 'https://github.com/QwikDev/qwik', title: 'GitHub', Logo: GithubLogo }, { href: 'https://twitter.com/QwikDev', title: 'Twitter', Logo: TwitterLogo }, + { href: 'https://bsky.app/profile/qwik.dev', title: 'Bluesky', Logo: BlueskyLogo }, ]; return ( diff --git a/packages/docs/src/components/header/header.tsx b/packages/docs/src/components/header/header.tsx index 805189c279d..4c7f1faa6dc 100644 --- a/packages/docs/src/components/header/header.tsx +++ b/packages/docs/src/components/header/header.tsx @@ -91,15 +91,6 @@ export const Header = component$(() => {
  • - - Shop - -
  • - {/*
  • { > Blog -
  • */} +
  • ( + + + +); diff --git a/packages/docs/src/components/svgs/discord-logo.tsx b/packages/docs/src/components/svgs/discord-logo.tsx index a550600ffbc..c038b6dc69a 100644 --- a/packages/docs/src/components/svgs/discord-logo.tsx +++ b/packages/docs/src/components/svgs/discord-logo.tsx @@ -12,8 +12,8 @@ export const DiscordLogo = ({ width, height }: DiscordLogoProps) => ( height={height} viewBox="0 0 512 512" role="img" - fill="currentColor" aria-label="Discord Logo" + class="fill-[var(--text-color)]" > diff --git a/packages/docs/src/components/svgs/github-logo.tsx b/packages/docs/src/components/svgs/github-logo.tsx index 31bdaf0272c..b1b22df0519 100644 --- a/packages/docs/src/components/svgs/github-logo.tsx +++ b/packages/docs/src/components/svgs/github-logo.tsx @@ -8,9 +8,9 @@ export const GithubLogo = ({ width, height }: GithubLogoProps) => ( viewBox="0 0 16 16" width={width} height={height} - fill="currentColor" role="img" aria-label="GitHub Logo" + class="fill-[var(--text-color)]" > diff --git a/packages/docs/src/components/svgs/twitter-logo.tsx b/packages/docs/src/components/svgs/twitter-logo.tsx index 2efe3d039db..3cfd2ef1635 100644 --- a/packages/docs/src/components/svgs/twitter-logo.tsx +++ b/packages/docs/src/components/svgs/twitter-logo.tsx @@ -10,8 +10,8 @@ export const TwitterLogo = ({ width, height }: TwitterLogoProps) => ( height={height} viewBox="0 0 512 512" role="img" - fill="currentColor" aria-label="Twitter Logo" + class="fill-[var(--text-color)]" > diff --git a/packages/docs/src/routes/(blog)/blog/(articles)/astro-qwik/index.mdx b/packages/docs/src/routes/(blog)/blog/(articles)/astro-qwik/index.mdx new file mode 100644 index 00000000000..6eb1d7a8524 --- /dev/null +++ b/packages/docs/src/routes/(blog)/blog/(articles)/astro-qwik/index.mdx @@ -0,0 +1,157 @@ +--- +title: 'Astro + Qwik: Houston, we have Resumability!' +authorName: 'Jack Shelton' +tags: ['Qwik'] +date: 'November 8, 2023' +canonical: 'https://www.builder.io/blog/astro-qwik' +--- + +import { ArticleBlock } from '~/routes/(blog)/blog/components/mdx/article-block'; +import CodeSandbox from '~/components/code-sandbox/index.tsx'; + + + +Hey Devs! My name is [Jack](https://twitter.com/TheJackShelton), and I am a freelance fullstack developer based in Texas. I also work on this thing called [Qwik UI](https://qwikui.com/) in my free time. 🙂 + +## The problem + +In mid-late 2022, I had the opportunity to try [Astro](https://astro.build/), a server-first, content-focused, and extremely flexible meta-framework on my day job. + +It quickly became my go-to for creating landing pages, marketing websites, and even small web apps. If my clients needed something, it was almost always a click and integration guide away. + +![An image of a rocket on a notebook in space.](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fed9d96db1222462b89baf4f03e80617e?width=707) + +Astro sites significantly outperformed the previous static site generators I had used. I appreciated the speed and found it intuitive to work with. + +Unfortunately, as I added more interactivity, my Astro sites began to slow down, despite Astro being the fastest meta-framework I had used up to that point! + +My clients and I took notice of this. I spent a lot of time researching how to improve site speed, but in the end it always felt like an uphill battle of optimizing versus adding the stuff my clients needed. + +## The journey + +In an ideal world, you could build your application, and it would be extremely fast, regardless of the level of interactivity or the size of your page. This appears to be an unsolvable problem. The more components we add, the slower it gets. + +I wondered, did it have to be this way? Is it even possible to have a fast and easy development process? Is it the [developer’s fault](https://www.builder.io/blog/dont-blame-the-developer-for-what-the-frameworks-did) when our apps are slow? + +Looking back, I’d say no! Developers should focus on what matters most: building the product! So, how do we solve this issue? For a while, it was a big what if. 😅 + +That what if quickly became a reality when I discovered Qwik’s [resumability](https://www.builder.io/blog/resumability-from-ground-up). I was 100% certain it was exactly what I was looking for. + +So I did some research. I saw a lot of comparisons between the two in terms of performance. April this year I thought “why don’t we use them together?” One is a meta-framework, and the other a UI framework. + +Would it still be resumable? + +Is Astro’s partial hydration approach going to be a problem with a framework that doesn’t have [hydration](https://www.builder.io/blog/hydration-is-pure-overhead)? + +How would it work with islands? + +Well, turns out I wasn’t the only one! + +![A screenshot of a Discord chat between Misko and Jack.](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F988e08cd27f34323b77b937432ed88d5?width=707) + +After some awesome help from Misko, creator of Angular and Qwik, as well as the Astro core team, I can finally answer that question I had back in April. + +## Introducing @qwikdev/astro: + +Astro’s first resumable UI framework. + +### Starts fast, stays fast + +One of Astro's key features is **Zero JS, by default**. + +Unfortunately, this is usually not the case after adding a JavaScript framework and any subsequent components. + +If we want to introduce interactivity with a framework, such as React, Vue, Svelte, and so on, the framework runtime is then introduced. The number of components added to the page also increases linearly O(n) with the amount of JavaScript. + +### Adding Qwik + +Qwik builds on top of Astro's **Zero JS, by default** principle and then some. Thanks to resumability, the components are not executed unless resumed. Even with interactivity, the framework is also not executed until it needs to be. [It is O(1) constant](https://www.builder.io/blog/our-current-frameworks-are-on-we-need-o1), … with zero effort on the developer. + +![An Illustration of hydration vs resumability.](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F41f896cda32b43279d7c32ee6725c79f?width=707) + +This integration for Astro enables resumability, fine-grained lazy loading, and many of the Qwik core stuff we know and love. + +This includes all of the Astro goodies, like view transitions, MDX collections, and the hundreds of integrations that Astro provides. + +**So where would @qwikdev/astro make sense?** + +You can use it anywhere you’re currently using a framework component in Astro! Using Qwik and resumability should significantly speed up your site. + +### Do Qwik components need hydration directives? + +Short answer, no! + +In other UI frameworks, a hydration directive, such as `client:only` or `client:load`, would be needed for interactivity. However, these are not needed with Qwik because there is no hydration! + +When using Qwik inside a meta-framework like Astro or Qwik City, components are loaded on the server, prefetched in a separate thread, and "resumed" on the client. + +For example, here's how we use a Qwik counter component in Astro. + +**counter.tsx** + +```tsx +import { component$, useSignal } from "@builder.io/qwik"; + +export const Counter = component$(() => { + const counter = useSignal(0); + + return ; +}); +``` + +**index.astro** + +```tsx +--- +import { Counter } from "../components/counter"; +--- + + + +

    Astro.js - Qwik

    + /* no hydration directive! */ + + + +``` + +### Does resumability work? + +Yes! We can lazily execute code on interaction, just like using Qwik with Qwik City. + + + +Here, we are refreshing the page, and you'll notice that nothing was executed until the button was clicked. Without resumability, our `` component would have been executed on page load. + +The 402 byte q-chunk is our Counter's `onClick$` handler. + +### What's in that 17.61kb chunk? + +That's the framework! We do not execute it until it is needed. In this case, it is gzipped using SSG. + +**How about islands?** + +Instead of islands, we have Qwik containers! These fit quite well into Astro’s island model, having similar limitations. + +Below is an example of a Qwik container in Astro. In the DOM, you'll notice there aren't any custom elements. This is because, to Astro, Qwik appears as static data. + +![Untitled](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F1f8b5720be2b4157a614b754abd8f041?width=707) + +This is just the beginning! You can try out the integration in Alpha today, by running: + +```bash +npx astro add @qwikdev/astro +``` + +> This integration is currently in Alpha release. We encourage you to try it and share your feedback.

    If you run into any problems, open up an issue in our [GitHub Repository](https://github.com/QwikDev/astro/issues). + +You can also view the package on [NPM](https://www.npmjs.com/package/@qwikdev/astro), [Github](https://github.com/QwikDev/astro), [Qwik integration page](https://qwik.dev/docs/integrations/astro/), and the [Astro integration page](https://astro.build/integrations/?search=qwik).

    +Join the fun and [contribute](https://github.com/QwikDev/astro/blob/main/contributing.md)! + +Stay awesome devs, + +-Jack + +
    \ No newline at end of file diff --git a/packages/docs/src/routes/(blog)/blog/(articles)/framer-motion-qwik/index.mdx b/packages/docs/src/routes/(blog)/blog/(articles)/framer-motion-qwik/index.mdx new file mode 100644 index 00000000000..373ae56ce98 --- /dev/null +++ b/packages/docs/src/routes/(blog)/blog/(articles)/framer-motion-qwik/index.mdx @@ -0,0 +1,358 @@ +--- +title: 'Building Framer Motion Animations Inside a Qwik Application' +authorName: 'Yoav Ganbar' +tags: ['Web development'] +date: 'March 21, 2023' +canonical: 'https://www.builder.io/blog/framer-motion-qwik' +--- + +import { ArticleBlock } from '~/routes/(blog)/blog/components/mdx/article-block'; +import CodeSandbox from '~/components/code-sandbox/index.tsx'; + + + +Animations are one of the coolest things you can add to your site to make it pop and be different than the rest of the cohort. + +You can do most animations with CSS, but you have to know a lot to get good results. + +That is why a lot of people in the React ecosystem love [Framer Motion](https://www.framer.com/motion/). + +It has a clear, declarative, and concise API that makes it a joy to build animations on the web. + +## Advantages of using Framer Motion + +Framer Motion is a powerful animation library that can create smooth and beautiful animations on web pages. Here are some of the advantages of using Framer Motion: + +- **User-friendly**: Framer Motion has an intuitive API that makes it easy to create animations, even for those new to animation. +- **Declarative**: Framer Motion uses a declarative syntax that clarifies what is happening in your animations. +- **Customizable**: Framer Motion provides a wide range of customizable options that help you create animations that fit your specific needs. +- **Performance**: Framer Motion is optimized for performance, so you can create complex animations without worrying about slowing down your site. +- **Community**: Framer Motion has a large and active community that provides support and resources to help you get the most out of the library. + +## Integration with Qwik + +> You can find the code in this post on [GitHub](https://github.com/BuilderIO/qwik-react-framer-motion). + +I’ve previously written about [how to run React inside Qwik](https://www.builder.io/blog/resumable-react-how-to-use-react-inside-qwik), but here’s a TLDR; refresher: + +1. Start a new Qwik app: `pnpm create qwik@latest` +2. Add React integration: `pnpm run qwik add react` + +Then we can add `framer-motion`: + +```bash +pnpm install framer-motion +``` + +Now we can write a React component with `framer-motion`. + +## Creating a “Qwikified” component + +It’s pretty straightforward. Let’s say we have this code: + +```tsx +import { motion } from "framer-motion"; + +const MyComponent = () => { + return ( + + ); +}; +``` + +All we need to do to “Qwikify” it is: + +```tsx +// FILE: src/integrations/react/framer.tsx +// ========================================== + +// 👇🏽 this tells Qwik that the JSX here is React +/** @jsxImportSource react */ + +import { motion } from "framer-motion"; + +// one function to import +import { qwikify$ } from '@builder.io/qwik-react'; + +const MyComponent = () => ( + +); + +// All you need is to export: +export const FramerQwik = qwikify$(MyComponent); +``` + +With the help of our good old friend GitHub Copilot, let’s break it down: + +1. Import the `qwikify$` function from `@builder.io/qwik-react`. +2. Import the `motion` component from `framer-motion`. +3. Create a `MyComponent` functional component that returns a `div` element. +4. Pass the `animate` prop to the `motion.div` element. +5. Pass an object as the value of the animate prop that contains the animation properties. +6. Pass the `className` prop to the `motion.div` element. +7. Export the component using `qwikify$` function. + +Then we can use it in a Qwik app: + +```tsx +// FILE: src/routes/index.tsx +// ========================================== + +import { component$ } from '@builder.io/qwik'; +import { FramerQwik } from '~/integrations/react/framer'; + +export default component$(() => { + return ( +
    +

    Qwik/React Framer Motion

    +
    + +
    +
    + ); +}); +``` + +Note in the above example, we use `client:idle` which is a directive as to when to hydrate a React component. This tells Qwik to wait for the browser `idle` event and only then hydrate React, resulting in a React island within the Qwik ocean. + +How about something more complicated? + +### React Framer Motion Image gallery inside Qwik + +Let’s take an example from the `framer-motion` docs and convert it to a Qwik component. + +#### Little image gallery example: + +Let’s create a new file: `src/integrations/react/image-gallery.tsx`. + +We’ll put it all in one file except for the CSS which I’ll just drop in `src/global.css`. + +```tsx +/** @jsxImportSource react */ + +import { useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { wrap } from 'popmotion'; +import { qwikify$ } from '@builder.io/qwik-react'; + +const images = [ + 'https://d33wubrfki0l68.cloudfront.net/dd23708ebc4053551bb33e18b7174e73b6e1710b/dea24/static/images/wallpapers/shared-colors@2x.png', + 'https://d33wubrfki0l68.cloudfront.net/49de349d12db851952c5556f3c637ca772745316/cfc56/static/images/wallpapers/bridge-02@2x.png', + 'https://d33wubrfki0l68.cloudfront.net/594de66469079c21fc54c14db0591305a1198dd6/3f4b1/static/images/wallpapers/bridge-01@2x.png', +]; + +const variants = { + enter: (direction: number) => { + return { + x: direction > 0 ? 1000 : -1000, + opacity: 0, + }; + }, + center: { + zIndex: 1, + x: 0, + opacity: 1, + }, + exit: (direction: number) => { + return { + zIndex: 0, + x: direction < 0 ? 1000 : -1000, + opacity: 0, + }; + }, +}; + +const swipeConfidenceThreshold = 10000; +const swipePower = (offset: number, velocity: number) => { + return Math.abs(offset) * velocity; +}; + +export const Example = () => { + const [[page, direction], setPage] = useState([0, 0]); + + const imageIndex = wrap(0, images.length, page); + + const paginate = (newDirection: number) => { + setPage([page + newDirection, newDirection]); + }; + + return ( +
    + + { + const swipe = swipePower(offset.x, velocity.x); + + if (swipe < -swipeConfidenceThreshold) { + paginate(1); + } else if (swipe > swipeConfidenceThreshold) { + paginate(-1); + } + }} + /> + +
    paginate(1)}> + {'‣'} +
    +
    paginate(-1)}> + {'‣'} +
    +
    + ); +}; + +export const ImageGallery = qwikify$(Example); +``` + +At this point all we have to do is import the component in our `src/routes/index.tsx` and decide how to load it: + +```tsx +// FILE: src/routes/index.tsx +// ========================================== + +import { component$ } from '@builder.io/qwik'; +import { FramerQwik } from '~/integrations/react/framer'; +import { ImageGallery } from '~/integrations/react/image-gallery'; + +export default component$(() => { + return ( +
    +

    Qwik/React Framer Motion

    +
    + +
    + +
    + ); +}); +``` + +One thing to note is the slight difference from the original CodeSandbox is that I've changed the fragment (`<>`) to a + +with a `className` just to make styling easier. Otherwise, in case of the component, we’ve added the `client:visible` loading strategy to hydrate this React island only when the gallery is visible in the viewport. + +This is what it looks like: + + + +Everything works! + +One of the coolest parts here (at least to nerdy me 😅) is that the code for the gallery only executes when it’s in view. + +Check it out (notice the Network tab): + + + +## Bonus: “Motion One” an alternative to `framer-motion` + +Using `framer-motion` is cool and all, but it comes with the cost of importing React and hydration. + +What if I told you that there’s an animation library that is just as performant as Framer, weighs less, and has a very similar API? + +Also, it was created by the same person that built `framer-motion` (and [Pose](https://popmotion.io/pose/), and [Popmotion](https://popmotion.io/pose/)) — [Matt Perry](https://github.com/mattgperry)! + +I’m talking about [Motion One](https://motion.dev/). + +This is not a React library, but a Vanilla JS animation library that is built for performance and has a very low bundle footprint of 3.8Kb. + +Motion One has all the same bells and whistles that framer has, integrations with Solid & Vue. [Get started](https://motion.dev/docs/quick-start). + +Need I say more? + +### Qwik Motion One example + +Though this doesn’t have a Qwik SDK, let’s see how we’d add a basic animation with it. + +Because this library is pure JS, we can use a Qwik method, `useVisibleTask()`, to run this code only in the browser, as in the example below: + +```tsx +import { component$, useVisibleTask$ } from '@builder.io/qwik'; +import { animate } from 'motion'; + +export default component$(() => { + useVisibleTask$(() => { + animate( + '#animation-target', + { + scale: [1, 2, 2, 1, 1], + rotate: [0, 0, 270, 270, 0], + borderRadius: ['20%', '20%', '50%', '50%', '20%'], + backgroundColor: [ + '#ff008c', + '#d309e1', + '#9c1aff', + '#7700ff', + '#ff008c', + ], + }, + { + duration: 2, + easing: 'ease-in-out', + repeat: 2, + direction: 'alternate', + } + ); + }); + return ( +
    + ); +}); + +function yuval() { } +``` + +We’re using the same values for the animation, as in the `framer-motion` example. However, we pass the values as an object. + +Also notice that the third argument to the `animate` function is where the options go, unlike in `framer` where there’s a `transition` key that gets passed down in the `animate` prop. + +## Conclusion + +In this post I’ve shown how we can use both `framer-motion` and `Motion One` inside a Qwik application. + +Using powerful animation libraries with such little friction is a huge win in my view. + +If you are already versed with `framer-motion`, there’s very little friction in just adding the same animations you may have already built in a React application. + +Having this option might help with incremental adoption of Qwik in cases in which both performance and beautiful motion design are paramount. + +
    \ No newline at end of file diff --git a/packages/docs/src/routes/(blog)/blog/(articles)/introducing-qwik-starters/index.mdx b/packages/docs/src/routes/(blog)/blog/(articles)/introducing-qwik-starters/index.mdx new file mode 100644 index 00000000000..5d902f27c15 --- /dev/null +++ b/packages/docs/src/routes/(blog)/blog/(articles)/introducing-qwik-starters/index.mdx @@ -0,0 +1,125 @@ +--- +title: 'Introducing Qwik starters - get up and running with Qwik now' +authorName: 'Miško Hevery' +tags: ['Qwik'] +date: 'December 14, 2021' +canonical: 'https://www.builder.io/blog/introducing-qwik-starters' +--- + +import { ArticleBlock } from '~/routes/(blog)/blog/components/mdx/article-block'; +import CodeSandbox from '~/components/code-sandbox/index.tsx'; + + + +Nothing is more satisfying than playing with code and discovering new things! Yes, it is finally here, `npm init qwik` for you to try and discover a different way to build web apps that stay lean and performant no matter their size. It is the same technology that is powering [builder.io](https://www.builder.io) and gets [100/100 PageSpeed](https://www.builder.io/blog/how-we-cut-99-percent-js-with-qwik-and-partytown). + +Qwik starter CLI is a simple starter for you to try experimenting with Qwik first hand and to get a better understanding of just how different it is. + +The CLI consist of these four examples, that will be expanded in the near future: + +1. `starter`: A basic hello world. +2. `starter-builder`: A basic hello world integrated with Builder's [Qwik API](https://www.builder.io/c/docs/qwik-api). +3. `starter-partytown`: A basic hello world showing how expensive tasks can be run on web-worker with [Partytown](https://github.com/BuilderIO/partytown) +4. `todo`: A classic [TodoMVC](https://todomvc.com/) application. + +## Basic Starter + +```shell +> npm init qwik +💫 Let's create a Qwik project 💫 + +✔ Project name … qwik-starter +✔ Select a starter › Starter +✔ Select a server › Express + +⭐️ Success! Project saved in qwik-starter directory + +📟 Next steps: + cd qwik-starter + npm install + npm start + +> (cd qwik-starter; npm install; npm start) +``` + +starter app + +Try it in [StackBlitz](https://stackblitz.com/edit/qwik-starter). + +## Starter with Builder Qwik API + +```shell +> npm init qwik +💫 Let's create a Qwik project 💫 + +✔ Project name … qwik-builder +✔ Select a starter › Starter Builder +✔ Select a server › Express + +⭐️ Success! Project saved in qwik-builder directory + +📟 Next steps: + cd qwik-builder + npm install + npm start + +> (cd qwik-builder; npm install; npm start) +``` + +qwik builder app + +Try it in [StackBlitz](https://stackblitz.com/edit/qwik-todo-builder). + +## Starter with Partytown + +```shell +> npm init qwik +💫 Let's create a Qwik project 💫 + +✔ Project name … qwik-partytown +✔ Select a starter › Starter Partytown +✔ Select a server › Express + +⭐️ Success! Project saved in qwik-partytown directory + +📟 Next steps: + cd qwik-partytown + npm install + npm start + +> (cd qwik-partytown; npm install; npm start) +``` + +qwik partytown app + +## Classic TodoMVC + +```shell +> npm init qwik +💫 Let's create a Qwik project 💫 + +✔ Project name … qwik-todo +✔ Select a starter › Todo +✔ Select a server › Express + +⭐️ Success! Project saved in qwik-todo directory + +📟 Next steps: + cd qwik-todo + npm install + npm start + +> (cd qwik-todo; npm install; npm start) +``` + +qwik todo app + +Try it in [StackBlitz](https://stackblitz.com/edit/qwik-todo-demo). + +## Profile away + +We encourage you to open the dev tools and put all of the examples through the profiler to see how little time is spent on the main thread. + +Happy coding and please provide feedback 🚀 + + \ No newline at end of file diff --git a/packages/docs/src/routes/(blog)/blog/(articles)/module-extraction-the-silent-web-revolution/index.mdx b/packages/docs/src/routes/(blog)/blog/(articles)/module-extraction-the-silent-web-revolution/index.mdx new file mode 100644 index 00000000000..60689d50d8a --- /dev/null +++ b/packages/docs/src/routes/(blog)/blog/(articles)/module-extraction-the-silent-web-revolution/index.mdx @@ -0,0 +1,212 @@ +--- +title: 'Code Extraction: The Silent Web Revolution' +authorName: 'Manu Mtz.-Almeida' +tags: ['Qwik'] +date: 'February 21, 2023' +canonical: 'https://www.builder.io/blog/module-extraction-the-silent-web-revolution' +--- + +import { ArticleBlock } from '~/routes/(blog)/blog/components/mdx/article-block'; +import CodeSandbox from '~/components/code-sandbox/index.tsx'; + + + +Code or Module Extraction is the new silent revolution of the web happening right now. + +Bundling tooling in JS, like Webpack, Rollup and Vite do one thing very well: **merge modules and remove what's not used** (tree-shaking), but they have no clue how to split code automatically. **Code splitting is not what you think it is.** + +Undoubtedly, putting things together takes less energy; even the [2nd law of thermodynamics](https://en.wikipedia.org/wiki/Second_law_of_thermodynamics) states this. Separating things is much harder. + +> In opposition to nuclear physics, web tooling solved **fusion** (merge) years ago. The hard part here is **fission** (split). + +## What module extraction really is + +Modern meta frameworks such as Qwik, Remix, Solid Start, and Next 13 are already doing module extraction without you knowing. The main use case is allowing developers to write server- and client-side in the same module. + +Module Extraction radically differs from code splitting because developers are not doing it explicitly by creating a new module or dynamic import. Instead, the meta-framework uses internal rules and custom transforms to **extract** parts of the source code into different **modules that can execute independently**. + +Even if you don't like mixing server and client code, it's an ongoing trend enabling magnificent DX and performant improvements. + +## Why we put things together + +Our brain uses the [working memory](https://en.wikipedia.org/wiki/Working_memory) to store a small amount of information that can later be used for the execution of cognitive tasks. The problem is that cognitive load grows exponentially for the number of items we keep in memory. When we separate things, we need to remember the current task and where everything is located. When related topics are close together, our brain is at ease, this even explains the popularity of tools like Tailwind. + +For a long time, CSS, HTML, and JS had to live in 3 different files, but modern frameworks realized that the inherent coupling is too high between them. **Most of the time, the separation of concerns creates even more complexity**. + +> The goal of a software engineer is NOT to reduce coupling or separate concerns but to reduce the net complexity of the system. Patterns like [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) [and SOLID](https://en.wikipedia.org/wiki/SOLID) are guides that we must use wisely. **Always make it clear and concise**. + +- Svelte and Vue invented _single file components_ using their own DSL (Domain Specific Language) to assemble HTML, CSS, and JS into a single file. +- React introduced JSX, bringing HTML-like syntax to JS, and later, CSS-in-JS libraries did the rest for styles. + +Thanks to all of them, the web is the industry that it is today. They enabled the creation of massively complex applications by big teams. + +## Why we separate things + +### Performance + +Any web developer working in a sufficiently large app is **forced** to do code splitting, and dynamically import components and styles. + +Yes! **They are forced to.** + +Stop for a second and realize that C++, Java, Swift, and Rust developers never have to think about this. **The compiler and operating system solve this problem by automatically** [loading and unloading](https://en.wikipedia.org/wiki/Virtual_memory) the program dynamically as it's needed. + +> 1GB of C binary can start immediately; unfortunately, we can not say the same about 1GB of Javascript. Even with WebAssembly, we need to deal with the network. + +### Server compared to client + +Apps usually require writing code that executes only on the server side; this is needed to implement user authentication or allow direct access to a database. + +Like in the early days of the web, developers today are **forced** to separate server and client logic into different files, even if the logic between both worlds is deeply related. + +**Yes! They are forced to.** + +It was not always that way, even good old [ASP.NET](http://asp.net/) has concepts to collocate Client and Server code next to each other. What if we could have the best of both worlds? + +```tsx + +``` + +### Refactoring + +**Separating things can be good when it's not a requirement;** sometimes it's good to keep things separated. + +For instance, React, Vue, and Solid are **highly composable**, allowing us to move pieces of functionality to different modules. + +In React, `use-` methods allow teams to refactor parts of functionality that they are using across different components and **keep related logic in the same place.** + +Frameworks like React and Qwik are designed to be used by big teams and complex apps in need of this ability. + +## Module Extraction as a primitive + +Like [Ryan Carniato](https://twitter.com/RyanCarniato) (creator of [Solid.js](https://www.solidjs.com/)) would say: "**Primitives, not frameworks"**. + +Qwik is the first framework to have module extraction as a primitive, a **primitive that developers can extend** and use for many more things than Server/Client execution. + +In Qwik, we use the `$` sign to signal the developer when to extract a piece of code; think about module extraction as the ability to create multiple modules inside a single file. + +While we could make `ME` transparent and hide the `$`, it's beneficial for developers to know when it's happening. + +Because `$` is a primitive, developers can create their Module Extraction utilities, and the semantics are well explained and documented. + +Up until this time, module extraction was an internal trick mostly used by meta-frameworks when they recognize well-named exports or functions, like `export const loaders` or `export const actions`. + +Qwik uses module extraction extensively; for example, each event handler is extracted so that when a user interacts, we can run that single piece of the original module in isolation, enabling apps that are instantly interactive regardless of complexity! + +a slide with a graph showing a figurative x-axis showing how Qwik enables O(1) apps. Text on the right: "Correlation between functionality and amount of JS is broken + +Module extraction is the main primitive of how [Qwik achieves resumability](https://www.builder.io/blog/resumability-vs-hydration); not only that, it can even [make React resumable](https://www.builder.io/blog/resumable-react-how-to-use-react-inside-qwik) 🤯. + +- **✅ Composable:** developers can create their Module Extraction. +- **✅ Closure extraction:** `$` can work at the closure level and extract functions that capture the scope. +- **✅ Fully co-locatable:** `$` can be in any file, and at any level, even inside JSX. It's not a root-level module extraction only. +- **✅ Nested:** `$` can even be inside another `$`. + +## How Module Extraction works in Qwik + +At the core of Qwik’s module extraction is the ability to perform “**closure extraction**”. + +A closure is a term in computer science to refer to a function that uses captured scope variables. + +Notice this example: + +```typescript +// This is a pure function: takes inputs as arguments and returns an output. +function thisIsAFunction(a, b) { + return a + b; +} + +function main() { + const captured = 10; + // This is a closure: in addition, the function captures the variable "captured". + function thisIsAClosure(a, b) { + return (a + b) * captured; + } +} +``` + +As you can tell, a closure is just like a function that captures the state through [lexical scoping](). + +### Extracting closures is not trivial + +So, extracting pure functions is as easy as copying the entire function body somewhere else, and everything will still work, but how about moving a closure? + +```typescript +function thisIsAClosure(a, b) { + // Exception: "captured" is undefined + return (a + b) * captured; +} +``` + +If we move the closure from the previous example, some variables are missing, which will not work. + +### Qwik Optimizer to the rescue + +To solve this problem, we created the Qwik Optimizer in [Rust](https://www.rust-lang.org/) using [SWC](https://github.com/swc-project/swc), the same tech used by [TurboBuild](https://turbo.build/), to perform advanced optimizations without sacrificing build times. + +The Optimizer looks for `$` and applies a transformation that extracts the expression following the `$` and turns it into a lazy-loadable and importable symbol. + +Let's start by looking at a simple `Counter` example: + +```typescript +export const Counter = component$(() => { + const store = useStore({ count: 0 }); + + return ; +}); +``` + +The above code represents what a developer would write to describe the component. Below are the transformations the Optimizer applies to the code to make the code lazy-loadable. + +```typescript +const Counter = component(qrl('./chunk-a.js', 'Counter_onMount')); +``` + +`chunk-a.js`: + +```typescript +export const Counter_onMount = () => { + const store = useStore({ count: 0 }); + return ; +}; +``` + +`chunk-b.js`: + +```typescript +const Counter_onClick = () => { + const [store] = useLexicalScope(); + return store.count++; +}; +``` + +Notice that every occurrence of `$` results in a new lazy loadable symbol. + +If you want to know more, check out the 1-hour video below, where I explain all the details of our Optimizer, or check the following links: + +- [The dollar $ sign](https://qwik.dev/docs/advanced/dollar/) +- [QRL](https://qwik.dev/docs/advanced/qrl/) + +
    + +
    + +## Closing notes + +Module extraction is the new silent revolution of the web happening right now! Its properties are so powerful that we will hear more and more about it. Qwik was the first framework to introduce it as a primitive, but our prediction is that other frameworks will begin embracing ME as a primitive, too, not a hidden set of rules implemented at the meta-framework level. + +New meta-frameworks like Solid Start are taking the proper steps and adopting Qwik's semantics to implement module extraction with their new `server$`. + +Who said you need to choose between edge or serverless? + +What if you could write a single component file and leverage the entire cloud infrastructure or even web workers? + +The future is bright, from new styling solutions to fully leveraging the power of the cloud, all from a single file. + +
    \ No newline at end of file diff --git a/packages/docs/src/routes/(blog)/blog/(articles)/qwik-1-2-performance-autopilot/index.mdx b/packages/docs/src/routes/(blog)/blog/(articles)/qwik-1-2-performance-autopilot/index.mdx new file mode 100644 index 00000000000..6596882f179 --- /dev/null +++ b/packages/docs/src/routes/(blog)/blog/(articles)/qwik-1-2-performance-autopilot/index.mdx @@ -0,0 +1,383 @@ +--- +title: 'Qwik 1.2: Performance in Autopilot' +authorName: 'Manu Mtz.-Almeida' +tags: ['Web development'] +date: 'June 29, 2023' +canonical: 'https://www.builder.io/blog/qwik-1-2-performance-autopilot' +--- + +import { ArticleBlock } from '~/routes/(blog)/blog/components/mdx/article-block'; +import { DiscordLink } from '~/routes/(blog)/blog/components/mdx/discord-link'; +import CodeSandbox from '~/components/code-sandbox/index.tsx'; + + + +## Qwik 1.2.0 is full of new features, fixes, and performance improvements designed to make you fall in love. + +```json +"@builder.io/qwik": "~1.2.0", +"@builder.io/qwik-city": "~1.2.0", +"eslint-plugin-qwik": "~1.2.0", +``` + +## Don’t blame developers for bad performance + +Performance issues are often a human-design problem, not just a technical one. + +Qwik's philosophy puts the responsibility on the framework (us), not the developers. Sites are usually not slow because of a lack of diligence among developers, but because it takes too much effort to make sites fast. + +**Our dream is to never write a list of **performance** best practices or 100-tricks-to-make-your-site-fast!** + +## Continuous performance visibility + +Developers need visibility of the issues in order to fix them. Debuggers, audit tools, and dev tools are all tools created with the idea of increasing visibility. What if we could do the same for performance and make it continuous? + +> “Until you make the unconscious conscious, +> it will direct your life and you will call it fate.” +> – Carl Jung + +We want Qwik developers to have a continuous and passive look at common performance issues in their apps and give them tools to fix those with no effort. + + + +Starting with 1.2.0, developers will have constant visibility of easy-to-solve performance issues in their app. We are starting with images, but will extend it to fonts and third-party scripts like Google Analytics soon. + +## Component-less image optimization + +Image optimization is an essential optimization that all web developers should be familiar with. Qwik 1.2.0 is releasing a new image optimization API that relies on the `vite-imagetools` package, a battle-tested plugin used by thousands of existing apps. + +We took it a step further by giving developers a way to import images as JSX: + +```tsx +import QwikLogo from './logo.png?jsx'; + +export default component$(() => { + return ( +
    + +
    + ); +}); +``` + +👆 this little snippet will generate the following `` tag 👇 + +image optimization + +The community and the Qwik team love this API for a number of reasons: + +- Zero runtime, zero JS +- Zero props by default, simple API +- Zero 404, strongly typed API +- Zero layout reflows (Automatic width/height) +- Hashed images, immutable cached +- Automatic `.webap` / `.avif` format optimization +- Automated `srcSet` generation +- Extendable (use any `` attribute) +- Loading lazy and async decoding by default +- Lightweight, a single `` node in the HTML + +

    +

    + + + +## Zero-runtime styling with PandaCSS + +PandaCSS is a new CSS-in-JS, zero-runtime, atomic styling solution that works in Qwik, Next.js, Vue, Remix, and so on: + +- CSS-in-JS: You can inline all your styles, like in emotion-js. +- Zero-runtime: Just like Tailwind or Vanilla Extract, all the CSS is generated at compiler time. +- Atomic: The amount of CSS scales well. As the number of components grows, the amount of CSS will remain almost constant. + +Qwik and PandaCSS share a lot of core principles: + +- Performance by default, without extra effort +- No performance footguns. You will not find “best practices” to make Panda CSS fast. It’s always fast. +- Refactorizable: inlined CSS or not, it’s up to you! + +To celebrate it, you can run the following command in your Qwik app! And voilà! It’s done! + +```shell +pnpm qwik add pandacss +``` + +(No more installation steps!) + +Check it out! + +```tsx +import { component$ } from "@builder.io/qwik"; +import { css } from "~/styled-system/css"; + +export default component$(() => { + return ( +
    + This box is styled with PandaCSS. +
    + ); +}); +``` + +## npm run qwik new + +Big shout-out to our [Roman (@zanettin)](https://github.com/zanettin), he has been a key part of our community, helping in , triaging issues and shipping new features! + +The new `new` 😅 command, allows developers to create new components and routes. + +You want a new route for the /about page? + +```shell +pnpm qwik new /about +``` + +You want a new component? sure thing + +```shell +pnpm qwik new my-button +``` + +It's also beautiful, check it out 👇 + + + +## AWS lambda integration + +Our community hero, [Leifer](https://twitter.com/LeiferMendez) contributed AWS lambda support for Qwik! He and his team have been using Qwik with AWS for a while now and decided to help the rest of the mortals to do the same. + +**AWS Lambda** joins Google Cloud, Vercel, and Netlify as one of the supported deployment targets for Qwik. Starting with 1.2.0, this is the only command you need to type to deploy to AWS! + +```bash +pnpm qwik add aws-lambda +``` + +We are especially excited about this contribution because it empowers enterprise users even more. + + + +## `clientConn` API + +Now it’s easier than ever to know key information about your users. This new API helps you get not only the IP address of the user, but their country and city as well. + +It is built on top of `edge` APIs of Cloudflare, Vercel, Netlify, and Google Cloud. + +```tsx +export const useGetCountryAndIP = routeLoader$(({clientConn}) => { + return { + ip: clientConn.ip, + country: clientConn.country + }; +}); +``` + +The best part is that you don’t need to know how any of those work. ClientConn API abstracts this complexity and provides a normalized API you can use regardless of where you deploy or self-host. + +## SPA navigation improvements + +Our community members `@jordanw66` and `@billykwok` deserve a big shout out. They have been rocking it making the SPA navigation buttery smooth, from fixing bugs to adding new APIs, like perfect scroll restoration and `replace` instead of push for navigation. + +- feat: Critical DX/UX improvement to SPA navigation in Qwik City by @billykwok +- feat: frame-perfect and state-backed durable SPA scroll restoration by @jordanw66 +- feat: bulletproof SPA recovery by @jordanw66 +- feat: scroll opt-out on nav() and Link by @jordanw66 +- fix: save scrollState on visibilitychange by @jordanw66 +- fix: perfect hash scroll by @jordanw66 + +## Rendering performance + +Qwik is getting faster and smarter without missing a beat. Improvements mostly focus in the optimizer, allowing to better understand how the JSX can be dynamic, and which components are invariable. This information is critical for Qwik’s dynamic data tree shaking that only serializes the bare minimum! + +- perf: optimizer knows non-variadic components by @manucorporat +- feat: transform compiler architecture by @manucorporat + +We have also made improvements to our prefetching algorithms to emit `modulepreload` only for ESM modules with a lot of dependents. The idea behind this optimization is to reduce even more the FID (First Input Delay) on the slowest devices. This new strategy allows the browsers to pre-parse and compile the most likely modules to execute before they are even needed. + +- perf: enable navigationPreload by @manucorporat +- perf: leverage modulepreload for common chunks by @manucorporat + +## Qwik Labs 🧪 to infinity and beyond + +We are thrilled to introduce Qwik Labs, a place designed for sharing and experimenting with the new ideas we are currently developing. The primary objective of Qwik Labs is to make it effortless for our community to test our upcoming ideas and provide early feedback, thereby enhancing their quality. + +Note that Qwik Labs are not intended for production use, and the API will probably change based on developer feedback, hence the reason they are packaged in a separate NPM package. + +We are excited to keep the Qwik Labs with these projects: + +- [Qwik Insights](https://qwik.dev/docs/labs/insights/): a way to collect real-world metrics about how the users are using the Qwik Application and then use the information to create ideal bundles to further improve the prefetching and application startup performance. (A form of [profile-guided optimization](https://en.wikipedia.org/wiki/Profile-guided_optimization).) + +Qwik Insights chart + +* Typed Routes: TypeScript is great at telling us all the places we need to change when we refactor our code – except one place; routes URLs, params, and queries. Typed Routes is a plugin for Vite that generates type information for your routes so that TypeScript can let you know if the application URL does not match the route definitions. + +## Qwik $hop!! + +Yes! Another amazing community leader [Giorgio Boa](https://bsky.app/profile/gioboa.bsky.social), built our official shop from the ground up! + +[Go check it out and get some socks](https://qwik.dev/shop/); I heard they can make you run qwiker. + +Qwik shop + +https://qwik.dev/shop/ + +## Other features and fixes + +- feat: serialize support for Set and Map by @manucorporat in https://github.com/QwikDev/qwik/pull/4375 +- feat(adapter): aws starter adapter by @leifermendez in https://github.com/QwikDev/qwik/pull/4390 +- feat: CLI option new by @zanettin in https://github.com/QwikDev/qwik/pull/4273 +- feat: client info API by @manucorporat in https://github.com/QwikDev/qwik/pull/4433 +- docs: fix link to speculative-module-fetching by @Craiqser in https://github.com/QwikDev/qwik/pull/4421 +- fix: use levenshtein distance to provide even better 404 by @manucorporat in https://github.com/QwikDev/qwik/pull/4389 +- docs: add CodeSandbox demo to Modular Forms guide by @fabian-hiller in https://github.com/QwikDev/qwik/pull/4095 +- docs: fix link to Bundle Optimization by @Craiqser in https://github.com/QwikDev/qwik/pull/4418 +- feat: image performance dev tools by @manucorporat in https://github.com/QwikDev/qwik/pull/4424 +- fix: slow test by @manucorporat in [https://github.com/QwikDev/qwik/pull/4431](https://github.com/QwikDev/qwik/pull/4431](https://github.com/QwikDev/qwik/pull/4431)) +- docs: qwiksand-box by @manucorporat in https://github.com/QwikDev/qwik/pull/4429 +- fix: image-size dep by @manucorporat in https://github.com/QwikDev/qwik/pull/4435 +- docs: fix example to match by @hamatoyogi in https://github.com/QwikDev/qwik/pull/4439 +- chore: remove unused starter file by @adamdbradley in https://github.com/QwikDev/qwik/pull/4446 +- fix: set qwik builder counter initial value by @adamdbradley in https://github.com/QwikDev/qwik/pull/4449 +- fix: cli background install by @adamdbradley in https://github.com/QwikDev/qwik/pull/4450 +- fix: jsx rendering order by @manucorporat in https://github.com/QwikDev/qwik/pull/4458 +- Reorganize .gitignore by @szepeviktor in https://github.com/QwikDev/qwik/pull/4456 +- docs: Routing - Change getLocation() to useLocation() by @chsanch in https://github.com/QwikDev/qwik/pull/4454 +- fix: visible task execution after removal by @manucorporat in https://github.com/QwikDev/qwik/pull/4459 +- Fix typo in MDX example code by @erikras in https://github.com/QwikDev/qwik/pull/4457 +- fix(use-on.ts): fixed useOn methods to pass correct eventName to _useOn by @OrenSayag in https://github.com/QwikDev/qwik/pull/4453 +- docs: routeLoader adjustment by @hamatoyogi in https://github.com/QwikDev/qwik/pull/4462 +- fix: root gitignore file by @zanettin in https://github.com/QwikDev/qwik/pull/4460 +- fix: add missing nonce to popstate script by @DustinJSilk in https://github.com/QwikDev/qwik/pull/4468 +- Include a woman in the "community" emoji by @erikras in https://github.com/QwikDev/qwik/pull/4471 +- docs: Update the middleware / endpoint documentation by @mhevery in https://github.com/QwikDev/qwik/pull/4442 +- fix: prefetch urls with different search queries by @DustinJSilk in https://github.com/QwikDev/qwik/pull/4474 +- refactor: improve DX of "qwik new" by @manucorporat in https://github.com/QwikDev/qwik/pull/4472 +- fix: bundling for testing by @manucorporat in https://github.com/QwikDev/qwik/pull/4475 +- feat: image vite transform by @manucorporat in https://github.com/QwikDev/qwik/pull/4479 +- refactor: containerState for appendHeadStyle by @manucorporat in https://github.com/QwikDev/qwik/pull/4478 +- fix: implicitly end middleware chain on response by @mhevery in https://github.com/QwikDev/qwik/pull/4441 +- refactor: visible tasks can run in parallel by @manucorporat in https://github.com/QwikDev/qwik/pull/4477 +- Update pages.json by @hexa-it in https://github.com/QwikDev/qwik/pull/4473 +- feat: update starter dev-tools by @adamdbradley in https://github.com/QwikDev/qwik/pull/4483 +- Add new documentation for deprecated features. by @nsdonato in https://github.com/QwikDev/qwik/pull/4476 +- feat: add no-unnecessary-condition eslint rule by @manucorporat in https://github.com/QwikDev/qwik/pull/4485 +- fix(parse-pathname): path segment encoding by @Varixo in https://github.com/QwikDev/qwik/pull/4486 +- docs(*): update contribution docs by @Wimpert in https://github.com/QwikDev/qwik/pull/4490 +- Allow replace state when navigating by @Wimpert in https://github.com/QwikDev/qwik/pull/4488 +- fix: test bundle mode by @manucorporat in https://github.com/QwikDev/qwik/pull/4491 +- fix: 'Numberish' type used for width/height didn't allow % by @KenAKAFrosty in https://github.com/QwikDev/qwik/pull/4434 +- fix: missing navigation update to static page by @manucorporat in https://github.com/QwikDev/qwik/pull/4493 +- 🦄 by @manucorporat in https://github.com/QwikDev/qwik/pull/4495 +- feat: add image to starter by @manucorporat in https://github.com/QwikDev/qwik/pull/4497 +- fix: $localize optimizer bug by @manucorporat in https://github.com/QwikDev/qwik/pull/4498 +- docs(adding content security policy to the advanced topics) by @the-zimmermann in https://github.com/QwikDev/qwik/pull/4440 +- fix: clientConn types by @manucorporat in https://github.com/QwikDev/qwik/pull/4501 +- Pr docs qwik city by @mhevery in https://github.com/QwikDev/qwik/pull/4494 +- chore: fix dev release by @manucorporat in https://github.com/QwikDev/qwik/pull/4511 +- refactor: simplify new templates by @manucorporat in https://github.com/QwikDev/qwik/pull/4512 +- fix: cli new interactive by @manucorporat in https://github.com/QwikDev/qwik/pull/4516 +- feat: svg optimization with esm by @manucorporat in https://github.com/QwikDev/qwik/pull/4526 +- fix: spa redirects from non-pages by @manucorporat in https://github.com/QwikDev/qwik/pull/4518 +- docs: fix typos in Overview and State files. by @eecopa in https://github.com/QwikDev/qwik/pull/4524 +- docs: fixed a typo in image link by @avanderpluijm in https://github.com/QwikDev/qwik/pull/4514 +- docs: fixed small typos in qwik-city documentation. by @VinuB-Dev in https://github.com/QwikDev/qwik/pull/4522 +- feat: pandacss integration by @manucorporat in https://github.com/QwikDev/qwik/pull/4515 +- docs: added custom icons by @LoganAffleck in https://github.com/QwikDev/qwik/pull/4513 +- Update app.tsx in runtime-less example by @primeagen-rustaceans in https://github.com/QwikDev/qwik/pull/4467 +- docs: fix typos by @enesflow in https://github.com/QwikDev/qwik/pull/4500 +- docs: move think-qwik page to concepts route by @moinulmoin in https://github.com/QwikDev/qwik/pull/4499 +- docs: fixed typos in comments and docs by @ehrencrona in https://github.com/QwikDev/qwik/pull/4430 +- doc: add redirect to new think-qwik docs by @manucorporat in https://github.com/QwikDev/qwik/pull/4533 +- fix: missing component in layout by @manucorporat in https://github.com/QwikDev/qwik/pull/4535 +- fix(router): use encodeURI instead by @manucorporat in https://github.com/QwikDev/qwik/pull/4534 +- fix: click to component for svg by @manucorporat in https://github.com/QwikDev/qwik/pull/4537 +- fix: regression when navigating to / by @manucorporat in https://github.com/QwikDev/qwik/pull/4538 +- feat: automatically set qwik icons by @manucorporat in https://github.com/QwikDev/qwik/pull/4539 +- fix: renderToString mode is always ignored and returns an empty page by @ncharalampidis in https://github.com/QwikDev/qwik/pull/4528 +- fix: image?jsx strip export default by @manucorporat in https://github.com/QwikDev/qwik/pull/4541 +- fix(router): redirect handling by @manucorporat in https://github.com/QwikDev/qwik/pull/4543 +- chore: update deps by @manucorporat in https://github.com/QwikDev/qwik/pull/4540 +- chore: create @builder.io/qwik-labs by @mhevery in https://github.com/QwikDev/qwik/pull/4545 +- Sidebar style by @LoganAffleck in https://github.com/QwikDev/qwik/pull/4554 +- fix: QwikIntrinsicElements does not include ref by @manucorporat in https://github.com/QwikDev/qwik/pull/4555 +- docs: add panda css by @anubra266 in https://github.com/QwikDev/qwik/pull/4544 +- feat(insights): create a new insights application by @mhevery in https://github.com/QwikDev/qwik/pull/4547 +- docs: add ss-link to showcase by @Leizhenpeng in https://github.com/QwikDev/qwik/pull/2689 +- fix: navigationPreload waitUntil by @manucorporat in https://github.com/QwikDev/qwik/pull/4561 +- feat: layout shift detection by @manucorporat in https://github.com/QwikDev/qwik/pull/4560 +- docs: add resource + fix css by @hamatoyogi in https://github.com/QwikDev/qwik/pull/4562 +- fix(qwik-city buildtime): make generated file ids unique by @hbendev in https://github.com/QwikDev/qwik/pull/4564 +- feat(insights): basic UI for seeing symbols by @mhevery in https://github.com/QwikDev/qwik/pull/4565 +- docs: Add Qwik Labs section by @mhevery in https://github.com/QwikDev/qwik/pull/4568 +- chore: fix broken lock file by @manucorporat in https://github.com/QwikDev/qwik/pull/4574 +- fix: route new template by @manucorporat in https://github.com/QwikDev/qwik/pull/4576 +- docs(qwik-labs edit link route): fixed qwik labs "edit page" link & so… by @thejackshelton in https://github.com/QwikDev/qwik/pull/4571 +- feat(labs): typed routes by @mhevery in https://github.com/QwikDev/qwik/pull/4580 +- docs(labs): add typed routes by @mhevery in https://github.com/QwikDev/qwik/pull/4582 +- chore: fix saving of artifacts to build by @mhevery in https://github.com/QwikDev/qwik/pull/4583 +- chore(labs): fix distribution build by @mhevery in https://github.com/QwikDev/qwik/pull/4584 +- Internal renaming suggestions by @shairez in https://github.com/QwikDev/qwik/pull/4581 +- fix(labs): encodeencodeURIComponent for params by @mhevery in https://github.com/QwikDev/qwik/pull/4587 +- feat: panda css use vite-macro plugin by @manucorporat in https://github.com/QwikDev/qwik/pull/4588 +- Qwik shop 🎁 by @gioboa in https://github.com/QwikDev/qwik/pull/4225 +- fix: shop cache by @manucorporat in https://github.com/QwikDev/qwik/pull/4590 +- docs(advanced & concepts docs improvements): clarity / structure changes by @thejackshelton in https://github.com/QwikDev/qwik/pull/4592 +- Add replaceState to link component by @Wimpert in https://github.com/QwikDev/qwik/pull/4492 +- docs: simplify menu by @manucorporat in https://github.com/QwikDev/qwik/pull/4593 +- fix: improve pandacss integration by @manucorporat in https://github.com/QwikDev/qwik/pull/4594 +- fix: default prettier format for starters by @manucorporat in https://github.com/QwikDev/qwik/pull/4595 +- chore(labs): copy build artifacts into the build git repo by @mhevery in https://github.com/QwikDev/qwik/pull/4589 +- chore: update deps by @manucorporat in https://github.com/QwikDev/qwik/pull/4597 +- docs: fix insights docs by @gioboa in https://github.com/QwikDev/qwik/pull/4598 +- chore(labs): include package.json in full build by @mhevery in https://github.com/QwikDev/qwik/pull/4599 +- docs: Dynamic og image implementation by @mrhoodz in https://github.com/QwikDev/qwik/pull/4579 +- fix: correct documentation URLs for rules created by ESLintUtils.RuleCreator by @wtlin1228 in https://github.com/QwikDev/qwik/pull/4604 +- fix: Social and Vendor are production only components by @wtlin1228 in https://github.com/QwikDev/qwik/pull/4610 +- docs: image optimization by @manucorporat in https://github.com/QwikDev/qwik/pull/4608 +- chore: change useStore link by @GustavoMelloGit in https://github.com/QwikDev/qwik/pull/4601 +- fix(qwik-core): add types for the style attribute by @hbendev in https://github.com/QwikDev/qwik/pull/4577 +- docs: Update deprecation information about the basePathname by @julianobrasil in https://github.com/QwikDev/qwik/pull/4437 +- docs: fix typos on Qwik City home by @corydeppen in https://github.com/QwikDev/qwik/pull/4602 +- feat: improve error message for duplicated loaders by @manucorporat in https://github.com/QwikDev/qwik/pull/4619 +- docs: dynamic ogImage feature url format fix and clean up by @mrhoodz in https://github.com/QwikDev/qwik/pull/4617 +- fix: detect invalid html by @manucorporat in https://github.com/QwikDev/qwik/pull/4623 +- 1.2.0 by @manucorporat in https://github.com/QwikDev/qwik/pull/4600 +- fix: image devtools by @manucorporat in https://github.com/QwikDev/qwik/pull/4626 +- feat: qwik add builder.io by @adamdbradley in https://github.com/QwikDev/qwik/pull/4627 + +## New Contributors + +- @erikras made their first contribution in https://github.com/QwikDev/qwik/pull/4457 +- @OrenSayag made their first contribution in https://github.com/QwikDev/qwik/pull/4453 +- @hexa-it made their first contribution in https://github.com/QwikDev/qwik/pull/4473 +- @the-zimmermann made their first contribution in https://github.com/QwikDev/qwik/pull/4440 +- @eecopa made their first contribution in https://github.com/QwikDev/qwik/pull/4524 +- @avanderpluijm made their first contribution in https://github.com/QwikDev/qwik/pull/4514 +- @VinuB-Dev made their first contribution in https://github.com/QwikDev/qwik/pull/4522 +- @LoganAffleck made their first contribution in https://github.com/QwikDev/qwik/pull/4513 +- @primeagen-rustaceans made their first contribution in https://github.com/QwikDev/qwik/pull/4467 +- @enesflow made their first contribution in https://github.com/QwikDev/qwik/pull/4500 +- @moinulmoin made their first contribution in https://github.com/QwikDev/qwik/pull/4499 +- @jordanw66 made their first contribution in https://github.com/QwikDev/qwik/pull/4509 +- @ehrencrona made their first contribution in https://github.com/QwikDev/qwik/pull/4430 +- @ncharalampidis made their first contribution in https://github.com/QwikDev/qwik/pull/4528 +- @anubra266 made their first contribution in https://github.com/QwikDev/qwik/pull/4544 +- @Leizhenpeng made their first contribution in https://github.com/QwikDev/qwik/pull/2689 +- @mrhoodz made their first contribution in https://github.com/QwikDev/qwik/pull/4579 +- @corydeppen made their first contribution in https://github.com/QwikDev/qwik/pull/4602 + +
    \ No newline at end of file diff --git a/packages/docs/src/routes/(blog)/blog/(articles)/qwik-2-coming-soon/index.mdx b/packages/docs/src/routes/(blog)/blog/(articles)/qwik-2-coming-soon/index.mdx new file mode 100644 index 00000000000..4da85d4feea --- /dev/null +++ b/packages/docs/src/routes/(blog)/blog/(articles)/qwik-2-coming-soon/index.mdx @@ -0,0 +1,233 @@ +--- +title: 'Towards Qwik 2.0: Lighter, Faster, Better' +authorName: 'The Qwik Team' +tags: ['Qwik'] +date: 'February 9, 2024' +canonical: 'https://www.builder.io/blog/qwik-2-coming-soon' +--- + +import { ArticleBlock } from '~/routes/(blog)/blog/components/mdx/article-block'; +import CodeSandbox from '~/components/code-sandbox/index.tsx'; + + + +Qwik prides itself on having instant-on applications with an HTML-first approach. We have performed a lot of comparison testing, and in each case, resumability wins over hydration in terms of JS downloaded, executed, and delayed before the user interaction is processed, and we’re really excited about that! + +But resumability comes with a cost. In Qwik v2.0, we will focus on lowering these costs further, and we would like to discuss them here. + +For a system to be resumable, the HTML must encode additional information: + +1. **Listener location** — This is in the form of an attribute, and it is very small (not much improvement possible here). +2. **Component boundaries** — This bit is a nontrivial amount of additional HTML text. We will focus on this part in this post. +3. **Application state** — We are also making improvements here, but this will be for a later post. + +> We are committed to backward compatibility, so, we're not planning to introduce ANY breaking changes in this release. However, we feel this is a significant rewrite of the internals, so...it is a reason to celebrate! + +## Understanding the application + +Whichever framework you choose, the framework must understand the application's internal structure. By this, we mean knowing where the component boundaries are, which text nodes are bound to which expressions, where to insert new rows in a loop, and so on. This data is a kind of tree where the framework has references to relevant DOM nodes so that it can update them. + +So, how do you get a hold of that tree? Well, there are two choices: + +1. Re-execute the application and rebuild the tree from code. ⇒ That’s hydration. +2. Serialize the tree into the HTML somehow. ⇒ It turns out HTML is already such a tree; it’s just missing a few things. + +## How it's done in Qwik v1 + +Let’s assume we have a minimal component: + +```tsx +import { component$, useSignal } from '@builder.io/qwik'; + +export const Counter = component$(() => { + const count = useSignal(123); + return ( + <> + Count: {count.value}! + + + ); +}); + +export const Layout = component$(() => { + return ( +
    + +
    + ); +}); + +// Think of it as:
    +``` + +The output HTML looks like this: + +```html +
    +
    + + + + Count: + 123! + + + + +
    + +
    +``` + +Right away, you might notice that Qwik needs to create “virtual nodes” in the form of comments. So why does Qwik need this information? + +- ``: In this example, a parent [Layout](https://qwik.dev/docs/layout/) component (not shown) is used for routing. The parent component creates the `
    ` element and [projects the route content](https://qwik.dev/docs/components/slots/#slots) (Counter) into the layout component. So, the framework needs to know where the component projected output should be inserted. Qwik needs a virtual node for that. +- ``: This virtual node represents the Counter component. The component requires component props, which are stored in the data section (` + +
  • +``` + +WOW! No more comment nodes! The output is super clean! And yet, all the same information is still encoded in the output. Let’s go over it. Visit the Qwik [container](https://qwik.dev/docs/advanced/containers/) documentation for more details. + +### Moving virtual nodes to the end of the HTML + +Instead of having the virtual node information mixed with the HTML output, it is now all moved into the ` + + +You can resume your app once it’s in your browser. + +And then, you can even pause it, with all its state, and resume it on another browser 🤯 + +Behold: + + + +What just happened in that video? 🥴 + +* I interacted with the app on browser A. +* Paused the app with `$0.qwik.pause()` (`$0` in devtools is a way to select the element you’re inspecting), it ran on the `` element, which is a Qwik container. +* Copied the HTML. +* Opened a new browser (Safari, not that it matters). +* Went to our app’s URL, where I got the counter once again with a `0` state. +* Deleted the HTML. +* Pasted the “paused” app HTML. +* The state was restored in a new browser 🕺🏽. +* Everything is still interactive. + +If that doesn’t blow your mind, I’m not sure what will. The use cases this opens up are endless. + +As long as the Qwik loader had run, when you move from one Qwik to another or one Qwik container to another, you can transfer state. + +Mull over that. + +### A Qwik pause + +So we’ve talked about the why, what, and how. Why are we at this point where we need a new paradigm? Why you should consider using Qwik? How does it work? And we'll cover some cool features. + +But we still haven’t shown how it’s fast and performant it is. + +Let’s dig in. + +## Measuring performance + +Most people measure web apps and website performance nowadays with Google Lighthouse, [Page Speed Insights](https://pagespeed.web.dev/) , or the good old [WebPageTest](https://www.webpagetest.org). + +To be able to compare a web app it’s always better to compare apples to apples. So, in order to do so, we’ll look at the [Builder.io](https://www.builder.io/) website. Specifically the home page. + +"Why?" you may ask. Because the Builder home page has 2 versions: + +1. Qwik +2. Next.js (v10) + +Considering that there have been major improvements in Next in the last 3 versions, this is the closest thing we have that has similar content and functionality. + +In order to render the old Next.js version we have a query param to use, so when we hit `http://www.builder.io/?render=next` we’ll get it. + +To compare the two, I ran a [WebPageTest on both versions of our home page](https://www.webpagetest.org/video/compare.php?tests=221212_BiDcBQ_C35,221212_BiDc2T_C34&thumbSize=200&ival=1000&end=visual) (Mobile 4G USA test configuration) and used the comparison feature. + +An image of the filmstrip output from WebPageTest of Builder.io's Next.js site version next to Qwik site version depicting load time. + +Right away, the Next.js site starts loading something visual while Qwik showed almost everything after 2.5 seconds. + +An image of the filmstrip output from WebPageTest of Builder.io's Next.js site version load time. + +The Next.js version fully loads after 9.5 seconds. + +Now let’s look at all the timings (**notice that Qwik version is red and Next.js is blue**): + +A screenshot of the timings graph from WebPageTest comparing Builder's Next.js site version vs Builder's Qwik site version. + +Note that TTI metric on the bottom doesn’t show for the Next.js version, not because it’s at `0` but because for some reason WebPageTest has issues with getting that metric. So, I ran Page Speed Insights on it: + +A screenshot of Lighthouse score for Builder's Next.js site version version. The performance score is 33. + +This run worked, but I had many failed runs attempted. And, as we can see TTI on it is benched at 10.6 seconds. + +It’s still early days to get a lot of real user performance data from [Google Chrome User Experience Report](https://developers.google.com/web/tools/chrome-user-experience-report) (CrUX for short) to show how Qwik performs. But there is some: + +A grpah of metrics from Crux that shows the percentage of good CWV scores. Arrows pointing at Qwik lines and table data. The Y axis is origins having good CWV, the X axis is a timeline from January 2020 to September 2022. + +Yes, a whopping 4 is the number of sites that show up on the report when choosing popular frontend tech, but it’s somewhat of an indication. + +## Conclusion + +Qwik is made by 3 performance nerds (they said it, not me 😉) that have 4 frontend frameworks under their belt. + +An image of the Qwik team: Misko Hevery, Manu Almeida, and Adam Bradley with the title 'Built by Performance Nerds'. + +They are inspired by all the latest frameworks and hard work that has been done by incredible engineers, and in so, they have brought a lot of the loved features of those frameworks. + +The general realization they came to, that birthed the innovation for Qwik, was that server-side rendering for all SPA frameworks was an afterthought. At some point, you hit the hydration bottleneck, and you simply cannot optimize anymore on your end as a developer. + +it’s easy to put things together, but It’s hard to split things up. + +
    +

    The idea is not to hydrate a whole app at once, it's to keep it dormant until a user interaction is needed. And then, only hydrate that one part. This is a great representation of how Qwik's lazy loading, of only what's needed, works. pic.twitter.com/fS1xZ8xT8V

    — Builder.io (@builderio) November 23, 2022
    + +
    + +> Qwik does not just support SSR/SSG/SPA, it is a hybrid. You can choose whichever fits your needs. + +The demo in this post doesn’t do justice in demonstrating Qwik’s full capabilities — it’s not real-world enough. It shines in highly interactive big apps, as it would perform the same as our simple example. It doesn’t matter how many features or lines of code an app has. + +One of Qwik’s best features is it makes doing the right thing achievable. You don’t have to work hard with it to get good performance. It’s a given, just by its nature. + +Other than that, there are more features to Qwik: + +* [Easy Micro Frontends](https://blog.cloudflare.com/better-micro-frontends/) +* You can [leverage the entire React ecosystem](https://www.youtube.com/watch?v=IGIPBAWRw_M&t=174s) within Qwik +* DX: Click to go to component source code, and more on the way! + +Qwik and QwikCity are still in beta, but the community is starting to blossom. **It’s never been more straightforward to build fast web apps than it is with Qwik. So what are you waiting for? Give it a try, the team is looking for more feedback** 🙂. + + \ No newline at end of file diff --git a/packages/docs/src/routes/(blog)/blog/(articles)/type-safe-forms-in-qwik/index.mdx b/packages/docs/src/routes/(blog)/blog/(articles)/type-safe-forms-in-qwik/index.mdx new file mode 100644 index 00000000000..cc66a1b571e --- /dev/null +++ b/packages/docs/src/routes/(blog)/blog/(articles)/type-safe-forms-in-qwik/index.mdx @@ -0,0 +1,390 @@ +--- +title: 'Type Safe Forms in Qwik with Modular Forms' +authorName: 'Yoav Ganbar' +tags: ['Web development'] +date: 'April 11, 2023' +canonical: 'https://www.builder.io/blog/type-safe-forms-in-qwik' +--- + +import { ArticleBlock } from '~/routes/(blog)/blog/components/mdx/article-block'; +import CodeSandbox from '~/components/code-sandbox/index.tsx'; + + + +As Qwik has reached RC status, let’s explore how Modular Forms with Qwik can enhance your developer experience while ensuring both client and server type safety. + +Let’s dig in! + +## Server-side type safety with Zod + +If you have been paying attention to all the TypeScript nerds lately, you’ve probably heard of [Zod](https://zod.dev/). + +And I’m not talking about the villain from Superman 2 (which appeared also in more recent movies from the franchise). + + + +In case you haven’t, Zod is a TypeScript-first schema declaration and validation library. + +Zod is all about making your life easier as a developer. The whole point is to get rid of all that boring type declaration stuff and validation. + +Basically, you just gotta declare your validator _one freakin' time,_ and boom! Zod takes care of the rest by automatically figuring out the TypeScript type. + +Plus, you can mix and match simple types to make more complex data structures like it ain't no thang. + +Bonus points: + +- Works with Vanilla JavaScript +- No dependencies +- Small bundle footprint (13.1kB [according to Bundleaphobia](https://bundlephobia.com/package/zod@3.21.4), although the site advertises 8k) + +### Server validation + +What does this have to do with Qwik? Glad you asked (patting my own back). + +Zod, which has first-class support inside Qwik’s `routeAction$()` function, can handle form validation server-side. + +Take this code as an example: + +```tsx +// FILE: src/routes/index.tsx + +import { routeAction$ } from '@builder.io/qwik-city'; + +export const useAddUser = routeAction$(async (user) => { + // `user` is typed Record + const userID = await db.users.add(user); + return { + success: true, + userID, + }; +}); +``` + +Qwik does a decent job of giving us a type of `Record`, but that is not ideal. There’s no validation that the `user` payload actually has what is needed to create a user inside our database. + +This is where Zod comes into play: + +```tsx +// FILE: src/routes/index.tsx + +import { routeAction$, zod$, z } from '@builder.io/qwik-city'; + +export const useAddUser = routeAction$( + async (user) => { + // `user` is typed { name: string } + const userID = await db.users.add(user); + return { + success: true, + userID, + }; + }, + zod$({ + name: z.string(), + }) +); +``` + +By adding the `zod$` function and passing in our validation scheme with the `z` primitive, we can get the correct type for the `user` object, as well as server-side validation. + +This means that if we accidentally pass a `user` object (or an attacker tries something fishy) that doesn’t have the `name` property, the server will throw an error. + +> For more information about handling errors in Qwik `routeAction`, check out the Qwik [Action Failures](https://qwik.dev/docs/action/#action-failures) documentation. + +## About the client side + +As in the above, MPA (Multi-Page Application) form handling on the server is pretty nice. However, this is still not amazing UX. + +For a user to understand that something wrong has happened, such as having entered the wrong type of input, they still need to wait for a server response. + +That’s a frustrating experience if you ask me. + +There’s a reason the React ecosystem has a slew of libraries that handle forms. To create a good client-side user experience, there’s a lot of boilerplate you’d need to write and edge cases that you need to handle. + +Notable mentions are: Formik, React Hook Form, and React Final Form, which have made writing complex forms much easier. + +## Modular Forms + +Modular Forms is a form library for Qwik and SolidJS created by Fabian Hiller. + +Fabian created a complex form for his SaaS business in 2018, but found that manually handling all the form validation was tiring and prone to errors. He decided to create a `useForm` hook to offload the repetitive code and make it reusable. + +However, he was unsatisfied with the development experience and tested different form libraries before creating his own. Every decision in the library has a well-thought-out reason, as he’s listed on the library site. + +## Client-side form validation with Modular Forms + +Since Qwik already uses Zod, Modular Forms supports defining the form values as a Zod schema. + +### Defining a form + +Let’s create a minimal login form using this approach: + +```tsx +// FILE: src/routes/modular-forms/index.tsx + +import { routeLoader$, z } from '@builder.io/qwik-city'; +import { InitialValues } from '@modular-forms/qwik'; + +const formSchema = z.object({ + email: z.string().nonempty(), + password: z.string().min(8), +}); + +// Note: you can also use z.input +// since Zod supports data transformation. +type LoginForm = z.infer; + +export const useLoginForm = routeLoader$>(() => ({ + email: '', + password: '', +})); +``` + +We have created a `routeLoader$`, which is data from the server with default values for our form. + +Modular Forms need the default values to initialize the store of the form. Thanks to Qwik's resumability, this step can be done entirely on the server without runtime costs in the browser. + +### Creating a form + +Now we can create our `useForm` hook to build out our form UI and client validations: + +```tsx +export default component$(() => { + const [loginForm, { Form, Field, FieldArray }] = useForm({ + loader: useFormLoader(), + }); +}); +``` + +Note that the hook returns a tuple which we can name whatever we want à la React `useState` hook style. + +As we can tell from the above, we get three components out of this hook that we can use to build out our UI: `Form`, `Field`, and `FieldArray`. + +The `Field` component uses a headless approach, which means that it does not render any HTML, which gives you, as a developer, maximum flexibility. + +### Adding fields to the form + +All we have to do to add fields to our form is the following: + +```tsx +export default component$(() => { + const [loginForm, { Form, Field }] = useForm({ + loader: useFormLoader(), + }); + + return ( +
    + + {(field, props) => } + + + {(field, props) => } + + +
    + ); +}); +``` + +### Validating on the client side + +At this point, we will get no indication to any errors on the client, or on the server, as we do not have a `routeAction$` to handle the form submission, nor any sort of client validation functions. + +Modular Forms comes with its own validation functions, which you can use, but for the sake of this post, I will continue using Zod. + +> **Tip:** It’s important to note that validations in Modular Forms, except for server actions, happen on the browser. + +To add Zod validations we need to change our schema a bit, like so: + +```tsx +const formSchema = z.object({ + email: z.string().nonempty(), + password: z.string().min(8), + email: z + .string() + .nonempty('please enter your email') + .email('enter a valid email'), + password: z + .string() + .min(1, 'please enter a password') + .min(8, 'You password must have 8 characters or more.'), +}); +``` + +The second argument to the Zod’s validation helper functions is just the error message that will be thrown. + +In order to activate those validations, we need to pull in the `zodForm$` adapter and add it as an argument to the `useForm` hook: + +```tsx +const [loginForm, { Form, Field }] = useForm({ + loader: useFormLoader(), + validate: zodForm$(formSchema), +}); +``` + +Once we’ve added that, we need to display these errors. They will now be a part of the `field` argument. In case there is an error, the message will appear in the `error` property of that field. + +To show the error, we can then add the following to our `JSX`: + +```tsx +export default component$(() => { + const [_, { Form, Field }] = useForm({ + loader: useFormLoader(), + validate: zodForm$(formSchema), + }); + + return ( +
    +

    Qwik Modular Forms

    +
    + + {(field, props) => ( + <> + + {field.error &&
    {field.error}
    } + + )} +
    + + {(field, props) => ( + <> + + {field.error &&
    {field.error}
    } + + )} +
    + +
    +
    + ); +}); +``` + +The result is that the error messages will render underneath the field, in case the field is invalid: + + + +### Handling submissions + +If you’ve been observant enough, you might have noticed that even though we have validation on the client, we still don’t actually submit it. + +Now, to add this to our form, all we have to do is add a `formAction$` function to our `useForm`: + +```tsx +// FILE: src/routes/modular-forms/index.tsx + +// .... our previous code + +export const useFormAction = formAction$((values) => { + // Runs on server + console.log(values); + // This validates the values on the server side. + // And cannot be manipulated by an attacker. ✅ +}, zodForm$(formSchema)); + +export default component$(() => { + const [_, { Form, Field }] = useForm({ + loader: useFormLoader(), + validate: zodForm$(formSchema), + action: useFormAction(), + }); + + // ... the rest of our previous code +} +``` + +To optionally process the form values client-side as well, we can add a function that is passed to the `onSubmit$` property of the `Form` component. + +```tsx +export default component$(() => { + // ... + + const handleSubmit: SubmitHandler = $((values, event) => { + // Runs on client + }); + + return ( +
    + … +
    + ); +} +``` + +Something that I have not found clear was how you get the response from the server back on the client. Perhaps this is a needed feature to request, as this library is still in beta. + +It’s just something that I got accustomed to with `routeAction$`, and I expected it to behave the same and give me a signal with the server response. + +I asked Fabian, the creator of the library, and he helped me out. + +As the library is both for SolidJS and Qwik, the intention is to keep a similar API. All that to say that in order to access the server response, all you need to do is return it with the `FormActionResult` signature. + +To get our end to end types correct, we now need to add a return type as a second generic to our `formAction$` call: + +```tsx +export const useFormAction = formAction$( + async ({ email, password }) => { + // Runs on server + // simulating adding a user to the DB. + const createdUserID = await db.users.add({ email, password }) + return { + status: 'success', + message: 'User added successfully', + data: { createdUserID }, + }; + }, + zodForm$(formSchema) +); +``` + +Now we get that sweet auto complete in our client-side code: + + + +Notice that we also get a loading state through the `submitting` property as well as the response data once the server responds: + + + + +### Bonus: Progressively enhanced form out of the box + +This is more a feature of Qwik than the Modular Forms library, however, it’s worth noting. To show this off, all we need is to turn off JavaScript in your browser (Chrome devtools → open command palette → Disable JavaScript). + +Once deactivated, the form will still work as below: + + + +## TL;DR + +So, basically, if you're a developer and you want to build forms that are easy to use and maintain while ensuring type safety on both the client and server side, then you should check out the combination of Modular Forms and Qwik. + +One of the cool things about Modular Forms is that it uses Zod for validation and schema definition on the server-side. This makes it super efficient and reliable. Also, it has built-in validation functions that you use to validate form data on the client-side. This helps to improve the user experience and prevent errors. + +Now, Qwik is also pretty sweet because it has a progressively enhanced form feature. This means that even if JavaScript is off in the user's browser, the forms still work. So, everyone can use the forms, no matter what their browser settings are. + +All in all, Modular Forms and Qwik make it super straightforward and safe for developers to build forms for their web applications and websites. So, if that's something you need to do, you should definitely give this combo a try! + +
    \ No newline at end of file diff --git a/packages/docs/src/routes/(blog)/blog/components/articles-grid.tsx b/packages/docs/src/routes/(blog)/blog/components/articles-grid.tsx index 1a25e2b2419..a3c98f36d58 100644 --- a/packages/docs/src/routes/(blog)/blog/components/articles-grid.tsx +++ b/packages/docs/src/routes/(blog)/blog/components/articles-grid.tsx @@ -9,7 +9,7 @@ export const ArticlesGrid = component$(() => { {blogArticles.map((post, key) => (
    @@ -21,13 +21,13 @@ export const ArticlesGrid = component$(() => { />
    -
    -

    +
    +

    {post.title}

    -
    - {blogArticles[0].tags.map((tag, key) => ( +
    + {post.tags.map((tag, key) => ( {
    - 5 min read + {post.readingTime || '5'} min read
    diff --git a/packages/docs/src/routes/(blog)/blog/components/featured-article.tsx b/packages/docs/src/routes/(blog)/blog/components/featured-article.tsx index ce511e7a18b..bb2d2833bca 100644 --- a/packages/docs/src/routes/(blog)/blog/components/featured-article.tsx +++ b/packages/docs/src/routes/(blog)/blog/components/featured-article.tsx @@ -7,24 +7,32 @@ export const FeaturedArticle = component$(() => { return (
    -
    +
    {blogArticles[0].title}
    -
    +

    {blogArticles[0].title}

    -
    +
    {blogArticles[0].tags.map((tag, key) => ( {tag} @@ -33,7 +41,7 @@ export const FeaturedArticle = component$(() => {
    - 5 min read + {blogArticles[0].readingTime || '5'} min read
    diff --git a/packages/docs/src/routes/(blog)/blog/components/mdx/article-block.tsx b/packages/docs/src/routes/(blog)/blog/components/mdx/article-block.tsx new file mode 100644 index 00000000000..c743a808835 --- /dev/null +++ b/packages/docs/src/routes/(blog)/blog/components/mdx/article-block.tsx @@ -0,0 +1,22 @@ +import { component$, Slot } from '@builder.io/qwik'; +import { ArticleHero } from './article-hero'; +import { useDocumentHead, useLocation } from '@builder.io/qwik-city'; +import { authors, blogArticles } from '~/routes/(blog)/data'; + +type Props = { authorLink: string }; + +export const ArticleBlock = component$(({ authorLink }) => { + const location = useLocation(); + const { frontmatter } = useDocumentHead(); + const article = blogArticles.find(({ path }) => path === location.url.pathname); + const author = authors[frontmatter.authorName]; + + return ( +
    + +
    + +
    +
    + ); +}); diff --git a/packages/docs/src/routes/(blog)/blog/components/mdx/article-block/article-block.tsx b/packages/docs/src/routes/(blog)/blog/components/mdx/article-block/article-block.tsx deleted file mode 100644 index eccd2381656..00000000000 --- a/packages/docs/src/routes/(blog)/blog/components/mdx/article-block/article-block.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { component$, Slot } from '@builder.io/qwik'; -import { ArticleHero } from '../article-hero/article-hero'; - -type Props = { imageSrc: string; authorLink: string }; - -export const ArticleBlock = component$(({ imageSrc, authorLink }) => { - return ( -
    - -
    - -
    -
    - ); -}); diff --git a/packages/docs/src/routes/(blog)/blog/components/mdx/article-hero/article-hero.tsx b/packages/docs/src/routes/(blog)/blog/components/mdx/article-hero.tsx similarity index 87% rename from packages/docs/src/routes/(blog)/blog/components/mdx/article-hero/article-hero.tsx rename to packages/docs/src/routes/(blog)/blog/components/mdx/article-hero.tsx index b854b861918..7b47ff88946 100644 --- a/packages/docs/src/routes/(blog)/blog/components/mdx/article-hero/article-hero.tsx +++ b/packages/docs/src/routes/(blog)/blog/components/mdx/article-hero.tsx @@ -2,9 +2,9 @@ import { component$ } from '@builder.io/qwik'; import { useDocumentHead } from '@builder.io/qwik-city'; import { Image } from 'qwik-image'; -type Props = { imageSrc: string; authorLink: string }; +type Props = { image: string; authorLink: string }; -export const ArticleHero = component$(({ imageSrc, authorLink }) => { +export const ArticleHero = component$(({ image, authorLink }) => { const { title, frontmatter } = useDocumentHead(); if (!frontmatter.authorName || !frontmatter.tags || !frontmatter.date) { @@ -60,8 +60,8 @@ export const ArticleHero = component$(({ imageSrc, authorLink }) => {
    -
    - {title} +
    + {title}
    ); diff --git a/packages/docs/src/routes/(blog)/blog/components/mdx/discord-link.tsx b/packages/docs/src/routes/(blog)/blog/components/mdx/discord-link.tsx new file mode 100644 index 00000000000..a1aa0e405f8 --- /dev/null +++ b/packages/docs/src/routes/(blog)/blog/components/mdx/discord-link.tsx @@ -0,0 +1,9 @@ +import { component$ } from '@builder.io/qwik'; + +type Props = { text?: string }; + +export const DiscordLink = component$(({ text = 'Discord server' }) => ( +
    + {text} + +)); diff --git a/packages/docs/src/routes/(blog)/blog/components/newsletter.tsx b/packages/docs/src/routes/(blog)/blog/components/newsletter.tsx deleted file mode 100644 index 19d3d79c69e..00000000000 --- a/packages/docs/src/routes/(blog)/blog/components/newsletter.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { component$ } from '@builder.io/qwik'; -import { SendIcon } from '../icons/send-icon'; - -export const Newsletter = component$(() => { - return ( -
    -
    -

    Stay Updated

    -

    - Get the latest articles and insights delivered straight to your inbox. -

    - -
    - - -
    -
    -
    - ); -}); diff --git a/packages/docs/src/routes/(blog)/blog/index.tsx b/packages/docs/src/routes/(blog)/blog/index.tsx index 2e0741e333b..1fc354d9ef4 100644 --- a/packages/docs/src/routes/(blog)/blog/index.tsx +++ b/packages/docs/src/routes/(blog)/blog/index.tsx @@ -2,7 +2,6 @@ import { component$, useStyles$ } from '@builder.io/qwik'; import type { DocumentHead } from '@builder.io/qwik-city'; import { FeaturedArticle } from './components/featured-article'; import { ArticlesGrid } from './components/articles-grid'; -import { Newsletter } from './components/newsletter'; export default component$(() => { useStyles$(` @@ -45,9 +44,6 @@ export default component$(() => {

    Latest Articles

    -
    - -
    ); }); diff --git a/packages/docs/src/routes/(blog)/data.ts b/packages/docs/src/routes/(blog)/data.ts index 3a82809eec5..e18d35929c7 100644 --- a/packages/docs/src/routes/(blog)/data.ts +++ b/packages/docs/src/routes/(blog)/data.ts @@ -1,31 +1,150 @@ +export const authors: Record = { + 'The Qwik Team': { socialLink: 'https://bsky.app/profile/qwik.dev' }, + 'Jack Shelton': { socialLink: 'https://twitter.com/TheJackShelton' }, + 'Vishwas Gopinath': { socialLink: 'https://twitter.com/CodevolutionWeb' }, + 'Manu Mtz.-Almeida': { socialLink: 'https://twitter.com/manucorporat' }, + 'Steve Sewell': { socialLink: 'https://twitter.com/steve8708' }, + 'Yoav Ganbar': { socialLink: 'https://twitter.com/HamatoYogi' }, + 'Miško Hevery': { socialLink: 'https://twitter.com/mhevery' }, +}; + type BlogArticle = { title: string; image: string; - featuredImage: string; path: string; tags: string[]; + featuredTitlePosition?: 'top' | 'bottom' | 'none'; + readingTime: number; }; export const blogArticles: BlogArticle[] = [ { - title: 'Example', - image: 'https://placehold.co/400x200', - featuredImage: 'https://placehold.co/1200x400', - path: '/blog/qwik-next-leap', - tags: ['Web development'], + title: 'Moving Forward Together', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F78b0d6ebdc154e2db77876ec00aef6f7', + path: '/blog/qwik-next-leap/', + tags: ['Web Development'], + featuredTitlePosition: 'top', + readingTime: 3, + }, + { + title: 'Towards Qwik 2.0: Lighter, Faster, Better', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9dd98bcf8fee4b42a718449c5151d53d', + path: '/blog/qwik-2-coming-soon/', + tags: ['Qwik'], + readingTime: 9, + }, + { + title: 'Astro + Qwik: Houston, we have Resumability!', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2418eac0b25046b197bf7b8bf5dfd637', + path: '/blog/astro-qwik/', + tags: ['Qwik'], + readingTime: 4, + }, + { + title: 'Qwik City Routing: A Visual Guide', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F4f52dab5f87142a38002269540e69cef', + path: '/blog/qwik-city-routing/', + tags: ['Web Development'], + readingTime: 11, + }, + { + title: 'Qwik 1.2: Performance in Autopilot', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F447d6d0349d4442496c3d7e246ce3d24', + path: '/blog/qwik-1-2-performance-autopilot/', + tags: ['Web Development'], + readingTime: 10, + }, + { + title: "Boost Your Site Perf with Qwik's useVisibleTask$ Hook", + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F5d90a118ceac4678a9e0576e61a955a9', + path: '/blog/qwik-tasks/', + tags: ['Web Development'], + readingTime: 4, + }, + { + title: 'Qwik Reaches v1.0', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fca46e4da83b7429ea159e2a42a197288', + path: '/blog/qwik-v1/', + tags: ['Qwik'], + readingTime: 5, + }, + { + title: 'Type Safe Forms in Qwik with Modular Forms', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F7abec2f848764aa99fead32861505344', + path: '/blog/type-safe-forms-in-qwik/', + tags: ['Web Development'], + readingTime: 8, + }, + { + title: 'Qwik Reaches RC Milestone', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F043bec968cb7465fac152147b9e5bd57', + path: '/blog/qwik-rc-milestone/', + tags: ['Qwik'], + readingTime: 2, + }, + { + title: 'Building Framer Motion Animations with Qwik', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fbf1420524b2241619cc68efbba7b7c13', + path: '/blog/framer-motion-qwik/', + tags: ['Web Development'], + readingTime: 7, + }, + { + title: 'Code Extraction: The Silent Web Revolution', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F0a8170ff2659474883ee032a0129cddd', + path: '/blog/module-extraction-the-silent-web-revolution/', + tags: ['Qwik'], + readingTime: 9, + }, + { + title: 'Introducing Qwik City Server Functions', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F48d8fd6d22ae4c3b9c1ec4817ce4046d', + path: '/blog/qwik-city-server-functions/', + tags: ['Qwik'], + readingTime: 6, + }, + { + title: 'Resumable React: How to Use React Inside Qwik', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9071a7def0b240e5b5ca6aab756e81b8', + path: '/blog/resumable-react-how-to-use-react-inside-qwik/', + tags: ['Web Development'], + readingTime: 8, + }, + { + title: 'The Qase for Qwik: Love At First TTI', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F69e3a278f34d4bfc9deb783dc705cd9a', + path: '/blog/the-qase-for-qwik-love-at-first-tti/', + tags: ['Qwik'], + readingTime: 18, }, { - title: 'Example', - image: 'https://placehold.co/400x200', - featuredImage: 'https://placehold.co/1200x400', - path: '/blog/qwik-next-leap', - tags: ['Web development'], + title: 'Qwik and Qwik City have reached beta! 🎉', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F5f8db18f68c74f6f9919f3877b6246b4', + path: '/blog/qwik-and-qwik-city-have-reached-beta/', + tags: ['Web Development'], + readingTime: 3, }, { - title: 'Example', - image: 'https://placehold.co/400x200', - featuredImage: 'https://placehold.co/1200x400', - path: '/blog/qwik-next-leap', - tags: ['Web development'], + title: 'Introducing Qwik starters - get up and running with Qwik now', + image: + 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F85209017e99f4753a56614f6712817c5', + path: '/blog/introducing-qwik-starters/', + tags: ['Qwik'], + readingTime: 1, }, ]; diff --git a/packages/docs/src/routes/(blog)/layout.tsx b/packages/docs/src/routes/(blog)/layout.tsx index 92306a193a7..4279f0f55df 100644 --- a/packages/docs/src/routes/(blog)/layout.tsx +++ b/packages/docs/src/routes/(blog)/layout.tsx @@ -11,6 +11,14 @@ export const onRequest: RequestHandler = async (request) => { export default component$(() => { useStyles$(docsStyles); + useStyles$(` + .docs article p { + font-size: 18px; + } + + #qwik-image-warning-container { + display: none; + }`); useImageProvider({ imageTransformer$: $(({ src }: ImageTransformerProps): string => src) }); @@ -22,7 +30,7 @@ export default component$(() => {