From 857a7d9ab85de5befbd161edf1b0502c8a050239 Mon Sep 17 00:00:00 2001 From: Oyemade Date: Thu, 30 Jun 2022 23:49:31 +0100 Subject: [PATCH] feat: generate qwik starter from QwikCity demo (npm create qwik@latest) --- .angulardoc.json | 4 + starters/qwik-express/.eslintignore | 30 +++ starters/qwik-express/.eslintrc.js | 40 ++++ starters/qwik-express/.gitignore | 35 ++++ starters/qwik-express/.prettierignore | 23 +++ starters/qwik-express/README.md | 71 ++++++++ starters/qwik-express/package.json | 40 ++++ starters/qwik-express/public/_headers | 2 + starters/qwik-express/public/favicon.ico | Bin 0 -> 4030 bytes .../favicons/android-chrome-192x192.png | Bin 0 -> 16395 bytes .../favicons/android-chrome-256x256.png | Bin 0 -> 24685 bytes .../public/favicons/apple-touch-icon.png | Bin 0 -> 13246 bytes .../qwik-express/public/favicons/favicon.svg | 1 + .../qwik-express/public/logos/qwik-logo.svg | 1 + starters/qwik-express/public/logos/qwik.svg | 1 + .../qwik-express/src/components/app/app.tsx | 13 ++ .../src/components/counter/counter.tsx | 19 ++ .../src/components/footer/footer.css | 5 + .../src/components/footer/footer.tsx | 19 ++ .../src/components/header/header.css | 5 + .../src/components/header/header.tsx | 21 +++ .../qwik-express/src/components/page/page.tsx | 24 +++ .../src/components/sidebar/sidebar.css | 12 ++ .../src/components/sidebar/sidebar.tsx | 54 ++++++ starters/qwik-express/src/entry.dev.tsx | 13 ++ starters/qwik-express/src/entry.express.tsx | 52 ++++++ starters/qwik-express/src/entry.ssr.tsx | 10 + starters/qwik-express/src/global.css | 15 ++ .../src/layouts/default/default.css | 171 ++++++++++++++++++ .../src/layouts/default/default.tsx | 24 +++ .../src/layouts/not-found/not-found.css | 0 .../src/layouts/not-found/not-found.tsx | 18 ++ starters/qwik-express/src/pages/INDEX | 16 ++ .../src/pages/blog/progressive.mdx | 70 +++++++ .../src/pages/blog/reactivity.mdx | 155 ++++++++++++++++ .../qwik-express/src/pages/blog/resumable.mdx | 107 +++++++++++ starters/qwik-express/src/pages/index.mdx | 26 +++ starters/qwik-express/src/root.tsx | 30 +++ starters/qwik-express/tsconfig.json | 21 +++ starters/qwik-express/vite.config.ts | 21 +++ 40 files changed, 1169 insertions(+) create mode 100644 .angulardoc.json create mode 100644 starters/qwik-express/.eslintignore create mode 100644 starters/qwik-express/.eslintrc.js create mode 100644 starters/qwik-express/.gitignore create mode 100644 starters/qwik-express/.prettierignore create mode 100644 starters/qwik-express/README.md create mode 100644 starters/qwik-express/package.json create mode 100644 starters/qwik-express/public/_headers create mode 100644 starters/qwik-express/public/favicon.ico create mode 100644 starters/qwik-express/public/favicons/android-chrome-192x192.png create mode 100644 starters/qwik-express/public/favicons/android-chrome-256x256.png create mode 100644 starters/qwik-express/public/favicons/apple-touch-icon.png create mode 100644 starters/qwik-express/public/favicons/favicon.svg create mode 100644 starters/qwik-express/public/logos/qwik-logo.svg create mode 100644 starters/qwik-express/public/logos/qwik.svg create mode 100644 starters/qwik-express/src/components/app/app.tsx create mode 100644 starters/qwik-express/src/components/counter/counter.tsx create mode 100644 starters/qwik-express/src/components/footer/footer.css create mode 100644 starters/qwik-express/src/components/footer/footer.tsx create mode 100644 starters/qwik-express/src/components/header/header.css create mode 100644 starters/qwik-express/src/components/header/header.tsx create mode 100644 starters/qwik-express/src/components/page/page.tsx create mode 100644 starters/qwik-express/src/components/sidebar/sidebar.css create mode 100644 starters/qwik-express/src/components/sidebar/sidebar.tsx create mode 100644 starters/qwik-express/src/entry.dev.tsx create mode 100644 starters/qwik-express/src/entry.express.tsx create mode 100644 starters/qwik-express/src/entry.ssr.tsx create mode 100644 starters/qwik-express/src/global.css create mode 100644 starters/qwik-express/src/layouts/default/default.css create mode 100644 starters/qwik-express/src/layouts/default/default.tsx create mode 100644 starters/qwik-express/src/layouts/not-found/not-found.css create mode 100644 starters/qwik-express/src/layouts/not-found/not-found.tsx create mode 100644 starters/qwik-express/src/pages/INDEX create mode 100644 starters/qwik-express/src/pages/blog/progressive.mdx create mode 100644 starters/qwik-express/src/pages/blog/reactivity.mdx create mode 100644 starters/qwik-express/src/pages/blog/resumable.mdx create mode 100644 starters/qwik-express/src/pages/index.mdx create mode 100644 starters/qwik-express/src/root.tsx create mode 100644 starters/qwik-express/tsconfig.json create mode 100644 starters/qwik-express/vite.config.ts diff --git a/.angulardoc.json b/.angulardoc.json new file mode 100644 index 000000000..6c036b480 --- /dev/null +++ b/.angulardoc.json @@ -0,0 +1,4 @@ +{ + "repoId": "89a60295-eef2-4537-9923-754797971f01", + "lastSync": 0 +} \ No newline at end of file diff --git a/starters/qwik-express/.eslintignore b/starters/qwik-express/.eslintignore new file mode 100644 index 000000000..b183822b6 --- /dev/null +++ b/starters/qwik-express/.eslintignore @@ -0,0 +1,30 @@ +**/*.log +**/.DS_Store +*. +.vscode/settings.json +.history +.yarn +bazel-* +bazel-bin +bazel-out +bazel-qwik +bazel-testlogs +dist +dist-dev +lib +etc +external +node_modules +temp +tsc-out +tsdoc-metadata.json +target +output +rollup.config.js +build +.cache +.vscode +.rollup.cache +dist +tsconfig.tsbuildinfo +vite.config.ts diff --git a/starters/qwik-express/.eslintrc.js b/starters/qwik-express/.eslintrc.js new file mode 100644 index 000000000..c31c7a947 --- /dev/null +++ b/starters/qwik-express/.eslintrc.js @@ -0,0 +1,40 @@ +module.exports = { + root: true, + env: { + browser: true, + es2021: true, + node: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:qwik/recommended', + ], + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + ecmaVersion: 2021, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + plugins: ['@typescript-eslint'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + 'prefer-spread': 'off', + 'no-case-declarations': 'off', + 'no-console': 'off', + '@typescript-eslint/no-unused-vars': ['error'], + }, +}; diff --git a/starters/qwik-express/.gitignore b/starters/qwik-express/.gitignore new file mode 100644 index 000000000..5e05c1b7c --- /dev/null +++ b/starters/qwik-express/.gitignore @@ -0,0 +1,35 @@ +# Build +build +dist +lib +server +functions/**/*.js + +# Development +node_modules + +# Cache +.cache +.mf +.vscode +.rollup.cache +tsconfig.tsbuildinfo + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Editor +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/starters/qwik-express/.prettierignore b/starters/qwik-express/.prettierignore new file mode 100644 index 000000000..1eb6df377 --- /dev/null +++ b/starters/qwik-express/.prettierignore @@ -0,0 +1,23 @@ +**/*.log +**/.DS_Store +*. +.vscode/settings.json +.history +.yarn +dist +dist-dev +etc +external +node_modules +temp +tsc-out +tsdoc-metadata.json +target +output +rollup.config.js +build +.cache +.vscode +.rollup.cache +dist +tsconfig.tsbuildinfo diff --git a/starters/qwik-express/README.md b/starters/qwik-express/README.md new file mode 100644 index 000000000..582ba480c --- /dev/null +++ b/starters/qwik-express/README.md @@ -0,0 +1,71 @@ +# Qwik qwik-express ⚡️ + +- Starter with built-in routing, powered by Qwik City +- Vite.js tooling. +- Express.js server. +- Prettier code formatter. + +## Development Builds + +### Client only + +During development, the index.html is not a result of server-side rendering, but rather the Qwik app is built using client-side JavaScript only. This is ideal for development with Vite and its ability to reload modules quickly and on-demand. However, this mode is only for development and does not showcase "how" Qwik works since JavaScript is required to execute, and Vite imports many development modules for the app to work. + +``` +npm run dev +``` + +### Server-side Rendering (SSR) and Client + +Server-side rendered index.html, with client-side modules prefetched and loaded by the browser. This can be used to test out server-side rendered content during development, but will be slower than the client-only development builds. + +``` +npm run dev.ssr +``` + +## Production Builds + +A production build should generate the client and server modules by running both client and server build commands. + +``` +npm run build +``` + +### Client Modules + +Production build that creates only the client-side modules that are dynamically imported by the browser. + +``` +npm run build.client +``` + +### Server Modules + +Production build that creates the server-side render (SSR) module that is used by the server to render the HTML. + +``` +npm run build.ssr +``` + +## Express Server + +This app has a minimal [Express server](https://expressjs.com/) implementation. After running a full build, you can preview the build using the command: + +``` +npm run serve +``` + +Then visit [http://localhost:8080/](http://localhost:8080/) + +-------------------- + +## Related + +- [Qwik Docs](https://qwik.builder.io/) +- [Qwik Github](https://github.com/BuilderIO/qwik) +- [@QwikDev](https://twitter.com/QwikDev) +- [Discord](https://qwik.builder.io/chat) +- [Vite](https://vitejs.dev/) +- [Partytown](https://partytown.builder.io/) +- [Mitosis](https://github.com/BuilderIO/mitosis) +- [Builder.io](https://www.builder.io/) diff --git a/starters/qwik-express/package.json b/starters/qwik-express/package.json new file mode 100644 index 000000000..cbb249f3b --- /dev/null +++ b/starters/qwik-express/package.json @@ -0,0 +1,40 @@ +{ + "name": "qwik-express", + "description": "Starter with built-in routing, powered by Qwik City Vite.js tooling. Express.js server. Prettier code formatter.", + "scripts": { + "build": "npm run typecheck && npm run build.client && npm run build.ssr", + "build.client": "vite build", + "build.ssr": "vite build --ssr src/entry.express.tsx", + "dev": "vite", + "dev.debug": "node --inspect-brk node_modules/vite/bin/vite.js --mode ssr", + "dev.ssr": "node --inspect node_modules/vite/bin/vite.js --mode ssr", + "fmt": "prettier --write .", + "fmt.check": "prettier --check .", + "lint": "eslint \"src/**/*.ts*\"", + "serve": "node server/entry.express.js", + "start": "npm run dev", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@builder.io/qwik": "0.0.33", + "@builder.io/qwik-city": "0.0.13", + "@types/eslint": "8.4.2", + "@types/express": "4.17.13", + "@types/node": "latest", + "@typescript-eslint/eslint-plugin": "5.27.0", + "@typescript-eslint/parser": "5.27.0", + "eslint": "8.16.0", + "eslint-plugin-qwik": "0.0.33", + "express": "4.17.3", + "node-fetch": "2.6.7", + "prettier": "2.6.2", + "typescript": "4.7.2", + "vite": "2.9.9" + }, + "engines": { + "node": ">=14" + }, + "homepage": "https://qwik.builder.io/", + "license": "", + "private": true +} diff --git a/starters/qwik-express/public/_headers b/starters/qwik-express/public/_headers new file mode 100644 index 000000000..08e5e27d5 --- /dev/null +++ b/starters/qwik-express/public/_headers @@ -0,0 +1,2 @@ +/build/* + Cache-Control: public, max-age=31536000, s-maxage=31536000, immutable diff --git a/starters/qwik-express/public/favicon.ico b/starters/qwik-express/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..51ba2e927d46a9ef1d98c36d931dc1e83d5671fd GIT binary patch literal 4030 zcmds(3vg8B701t-_&^yQUsTaaL88T2g((h0r&u?eePrKRHk+ha6o#rpOFHeqR7awM zKoPZ|ATO2BsTGZm$h6a1nPCQ-03jwJG9nWaTQJrI_6W@RrH;h>|Q*JdLEyVc4pyEGrAth@pkwe+rw^e)922@--Mh6 z6m}MVYuuryZlK|(ue&n3Y|^y8;=6v?`@{pg&MunM+VzNGFye7?yWQIyv=>YbI(+{V zlmAS_TXn=c>1uwBrt=!zVr9jIvgq<#ga0X>I^6r@{R}Qgd?s$Oxw+Lg@7WrQodUM} z%jR+^7&}P^RtCRKCVr1R{JyGUeuwt*RyxUl)F~q?CUA7wtqd-o12bZR8b}+Y4I=@AWc^CP?9RPQTcdLr=hs3-WDkF}Srt|P?6FIv22Yl*@S+QEP ziR(-bZnC)6f8}{p*K(O$_#obaSZ>$Z`As?tE`!VP$ndU>doX?s?ThOZj7N+dcy6h3 zL~rI=qb&w@@lm6lj#-?S<(FRvcQf6@+h{+(f%$~{7icF`3JG7h|Ad71FX%Hp$Ikir zM9C_y&Ng$6(I%d28!WCn8_bvHB3F<%Dt8IS;UxDh%)JfzCRdo1vzhyvS?P~*%kP$R zO}2%Pn(Tbc?1(j5Tq`!+aXU5Vx-aUPUj=g`P2o*c4=y9lnl@pS@89GedVsmNn&leq z?%}3iE#VqNE+51C!Y9_)9q?Q8?(^s7jmza%zW3b~i*Ux{HYjdnB7hV}Y8F{t2&5@sm+#Zw7FN6CM{e=HSktA1qu8(sQe*cQ^ zpWMTIraZ}%oBlwM>%4_rlfs2h#y;w7vw0sn0dAXf^giZwV6LUBz$gWm5obnjF~1m> z_!X|?)<+r49s5Qx<;(eoMy_>wkjwCEcCh#rE_|YH<(GvVzH3wK7Ow_V<66P#LA*7t z#CI(uY~gN!c28yw@4bL254sOma<#=FduS|fU(iu7FW|_hpk3<^SX1IvG?CZP-&KZi zg(-g5LBibt?YfOQ@WGf&d3ff)r(A8aX|uA=>4xaME0an(5g2yqUpaZSWo{Hpaz z$6T?1Iog?u81ugG(+Un1Es~yq{VV$j`K;v?tA1TY2f@8Q;+ijeR^ptdtV-X^>%d5G zC69zFwc_n7z|O~J36IH>hy6$EIsDj@9J2dVMoX^eLawuD>*@SOI`J!4a8V|;FSuvs z`Ddp3PI^ zEF<2X3fS{!L#ashzFcpleY5Sl@`sJjPK^vixns)?oM$|0 zO-kiTT+tJf>x(+MF=Q)9Yai#lQx4DIW>sVx+QLhx4#sxqVq6NJ!re0oeV|eG8&eeS z+FizehZprXk5PM*eq~*o&<~RNZ1uFAuz9njK96^m>kV}9=X1c$S$Wu~S2~}$EsDM$ zQ@Q`RmwDin%02t>KK6SGv3_ps6Y=X!a?yXZKB(1=7`{KdVb0xjsdJXBGc*mpkAG&H zJ92LQje{z8zk$)*x%Y5Y%wMMg>4s^Vc&tlm3kXyEm$hPi6tNMEym#J}+ISUu*JSgKt3)8gz^dwq@cBfLj_R!~2d z)|*`fc4GRAj&aYE59*EOhj317T{vUt{GMM&TZ)&BNDXYT;4G2Cm0l-3zB(rtXDh4f z7wh{saBoI_U6hsjvI3qDXPfAd-uPpTWmQJ=mvtUz7kb=ToY%VQEzVAGPr>h(Shomj zVFYKnNL7wS#uog}X88Oe&O3zPOP%_X_eb<5sx&~^rU^$3maHm$?rh9CtIq70fpg;h zm@f<5>By_hW~)21(qPW4GFUQk-nbp-x^y|iejhJv3-c_I)%}Sg#BOHjL)zD literal 0 HcmV?d00001 diff --git a/starters/qwik-express/public/favicons/android-chrome-192x192.png b/starters/qwik-express/public/favicons/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..6b8c27f8c08ff62f0c09e5d068961c7bf15d10d9 GIT binary patch literal 16395 zcmbtb^LHiB)4s87+qy9}Hg;p%&c+)jH@1_FZCe{-V_O^BHr{-`|HAu2cb}PaW}cp& zs;8>Es=^iJrI6t9;Q;^ul8m&t^4Hk&UxS7I>Xp}RVZH`92Wc&50005wzXk?K&&2(@ z3FfRUB?_pXBs~7QfV2?#Ap!u@#Ui{JK>+|lxiaD+s_tOtK3+*?7HWqZhw5rV<$UWw z(^=}IEn)%u=aadx%)iT!cOaEi@JN8UbN>RPj(~~cs)mwhKMler5l2WG%&V?rT_l$R zz;GH-wZNoQdcqPQ%SyvY6O4f&p_B{9j|G$2mrai?k8bj==m|Qm_V+6?_X3CCHcvAf zNV+?_hro_X3u5?A{y&{1KkPPm$;!&qU5TfKIo#Dvgkg$;*nr53o@if@!#Gs=6ABum zda_)#jhmnd>pzQ%QtFp_q2uEa&+AH
=b+IEZ@J5{=povH0_Iqk5U;CkGzhk+{P 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 literal 0 HcmV?d00001 diff --git a/starters/qwik-express/public/favicons/android-chrome-256x256.png b/starters/qwik-express/public/favicons/android-chrome-256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..a2883cb1c47549b33124e97b1458816c836dbfdd GIT binary patch literal 24685 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 QIso9cqNYNvyk+?R0LBMF#sB~S literal 0 HcmV?d00001 diff --git a/starters/qwik-express/public/favicons/apple-touch-icon.png b/starters/qwik-express/public/favicons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..249109dcb2587316a4c800a61cdb4e923e24e34e GIT binary patch literal 13246 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 g&gB1+q \ No newline at end of file diff --git a/starters/qwik-express/public/logos/qwik-logo.svg b/starters/qwik-express/public/logos/qwik-logo.svg new file mode 100644 index 000000000..4e9b6f1b3 --- /dev/null +++ b/starters/qwik-express/public/logos/qwik-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/starters/qwik-express/public/logos/qwik.svg b/starters/qwik-express/public/logos/qwik.svg new file mode 100644 index 000000000..91088ea3d --- /dev/null +++ b/starters/qwik-express/public/logos/qwik.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/starters/qwik-express/src/components/app/app.tsx b/starters/qwik-express/src/components/app/app.tsx new file mode 100644 index 000000000..7576d2d93 --- /dev/null +++ b/starters/qwik-express/src/components/app/app.tsx @@ -0,0 +1,13 @@ +import { component$, Host } from '@builder.io/qwik'; +import { Page } from '../page/page'; +import { useQwikCity } from '@builder.io/qwik-city'; + +export const App = component$(() => { + useQwikCity(); + + return ( + + + + ); +}); diff --git a/starters/qwik-express/src/components/counter/counter.tsx b/starters/qwik-express/src/components/counter/counter.tsx new file mode 100644 index 000000000..bec499dfe --- /dev/null +++ b/starters/qwik-express/src/components/counter/counter.tsx @@ -0,0 +1,19 @@ +import { component$, useScopedStyles$, useStore } from '@builder.io/qwik'; + +export const Counter = component$(() => { + const store = useStore({ count: 0 }); + useScopedStyles$(` + .counter { + border: 3px solid #1474ff; + padding: 10px; + border-radius: 10px; + color: #1474ff; + } +`); + + return ( + + ); +}); diff --git a/starters/qwik-express/src/components/footer/footer.css b/starters/qwik-express/src/components/footer/footer.css new file mode 100644 index 000000000..4b6a4e490 --- /dev/null +++ b/starters/qwik-express/src/components/footer/footer.css @@ -0,0 +1,5 @@ +.docs footer nav a { + @apply text-slate-600; + @apply no-underline; + @apply hover:underline; +} diff --git a/starters/qwik-express/src/components/footer/footer.tsx b/starters/qwik-express/src/components/footer/footer.tsx new file mode 100644 index 000000000..8c5723bda --- /dev/null +++ b/starters/qwik-express/src/components/footer/footer.tsx @@ -0,0 +1,19 @@ +import { component$, Host, useScopedStyles$ } from '@builder.io/qwik'; +import styles from './footer.css?inline'; + +export const Footer = component$( + () => { + useScopedStyles$(styles); + + return ( + +
+ Made with ♡ by the + Builder.io + team +
+
+ ); + }, + { tagName: 'footer' } +); diff --git a/starters/qwik-express/src/components/header/header.css b/starters/qwik-express/src/components/header/header.css new file mode 100644 index 000000000..36d769278 --- /dev/null +++ b/starters/qwik-express/src/components/header/header.css @@ -0,0 +1,5 @@ +header { + background: #1474ff; + padding: 30px; + color: white; +} diff --git a/starters/qwik-express/src/components/header/header.tsx b/starters/qwik-express/src/components/header/header.tsx new file mode 100644 index 000000000..f4c0f2110 --- /dev/null +++ b/starters/qwik-express/src/components/header/header.tsx @@ -0,0 +1,21 @@ +import { component$, Host, useScopedStyles$ } from '@builder.io/qwik'; +import styles from './header.css?inline'; + +export const Header = component$( + () => { + useScopedStyles$(styles); + + return ( + + + + ); + }, + { tagName: 'header' } +); diff --git a/starters/qwik-express/src/components/page/page.tsx b/starters/qwik-express/src/components/page/page.tsx new file mode 100644 index 000000000..81c176607 --- /dev/null +++ b/starters/qwik-express/src/components/page/page.tsx @@ -0,0 +1,24 @@ +import { component$ } from '@builder.io/qwik'; +import { useHeadMeta, usePage } from '@builder.io/qwik-city'; +import NotFound from '../../layouts/not-found/not-found'; + +export const Page = component$(() => { + const page = usePage(); + if (page) { + const attrs = page.attributes; + const Layout = page.layout; + const Content = page.content; + + useHeadMeta({ + title: attrs.title + ' - Qwik', + description: attrs.description, + }); + + return ( + + + + ); + } + return ; +}); diff --git a/starters/qwik-express/src/components/sidebar/sidebar.css b/starters/qwik-express/src/components/sidebar/sidebar.css new file mode 100644 index 000000000..635b1c1cd --- /dev/null +++ b/starters/qwik-express/src/components/sidebar/sidebar.css @@ -0,0 +1,12 @@ +aside { + padding: 10px; +} + +.menu h5 { + font-size: 18px; + font-weight: 800; +} + +.menu li a { + text-decoration: underline; +} diff --git a/starters/qwik-express/src/components/sidebar/sidebar.tsx b/starters/qwik-express/src/components/sidebar/sidebar.tsx new file mode 100644 index 000000000..d7e13aceb --- /dev/null +++ b/starters/qwik-express/src/components/sidebar/sidebar.tsx @@ -0,0 +1,54 @@ +import { component$, Host, useScopedStyles$ } from '@builder.io/qwik'; +import { usePage, usePageIndex } from '@builder.io/qwik-city'; +import styles from './sidebar.css?inline'; + +export const SideBar = component$( + () => { + useScopedStyles$(styles); + const page = usePage(); + const navIndex = usePageIndex(); + if (!page) { + return null; + } + + return ( + + + + + ); + }, + { tagName: 'aside' } +); diff --git a/starters/qwik-express/src/entry.dev.tsx b/starters/qwik-express/src/entry.dev.tsx new file mode 100644 index 000000000..a1fb0004f --- /dev/null +++ b/starters/qwik-express/src/entry.dev.tsx @@ -0,0 +1,13 @@ +import { render } from '@builder.io/qwik'; +import { Root } from './root'; + +/** + * Development entry point using only client-side modules: + * - Do not use this mode in production! + * - No SSR + * - No portion of the application is pre-rendered on the server. + * - All of the application is running eagerly in the browser. + * - More code is transferred to the browser than in SSR mode. + * - Optimizer/Serialization/Deserialization code is not exercised! + */ +render(document, ); diff --git a/starters/qwik-express/src/entry.express.tsx b/starters/qwik-express/src/entry.express.tsx new file mode 100644 index 000000000..2fb102f74 --- /dev/null +++ b/starters/qwik-express/src/entry.express.tsx @@ -0,0 +1,52 @@ +import express from 'express'; +import { join } from 'path'; +import { render } from './entry.ssr'; + +/** + * Create an express server + * https://expressjs.com/ + */ +const app = express(); + +/** + * Serve static client build files, + * hashed filenames, immutable cache-control + */ +app.use( + '/build', + express.static(join(__dirname, '..', 'dist', 'build'), { + immutable: true, + maxAge: '1y', + }) +); + +/** + * Serve static public files at the root + */ +app.use(express.static(join(__dirname, '..', 'dist'), { index: false })); + +/** + * Server-Side Render Qwik application + */ +app.get('/*', async (req, res, next) => { + try { + // Render the Root component to a string + const result = await render({ + url: req.url, + }); + + // respond with SSR'd HTML + res.send(result.html); + } catch (e) { + // Error while server-side rendering + next(e); + } +}); + +/** + * Start the express server + */ +app.listen(8080, () => { + /* eslint-disable */ + console.log(`http://localhost:8080/`); +}); diff --git a/starters/qwik-express/src/entry.ssr.tsx b/starters/qwik-express/src/entry.ssr.tsx new file mode 100644 index 000000000..97325e143 --- /dev/null +++ b/starters/qwik-express/src/entry.ssr.tsx @@ -0,0 +1,10 @@ +import { renderToString, RenderOptions } from '@builder.io/qwik/server'; +import { manifest } from '@qwik-client-manifest'; +import { Root } from './root'; + +export function render(opts: RenderOptions) { + return renderToString(, { + manifest, + ...opts, + }); +} diff --git a/starters/qwik-express/src/global.css b/starters/qwik-express/src/global.css new file mode 100644 index 000000000..9813f1128 --- /dev/null +++ b/starters/qwik-express/src/global.css @@ -0,0 +1,15 @@ +html { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; +} + +body { + padding: 0; + line-height: inherit; +} diff --git a/starters/qwik-express/src/layouts/default/default.css b/starters/qwik-express/src/layouts/default/default.css new file mode 100644 index 000000000..0b2765b5b --- /dev/null +++ b/starters/qwik-express/src/layouts/default/default.css @@ -0,0 +1,171 @@ +.docs { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); + display: grid; + grid-template-columns: 1fr 3fr; + grid-template-areas: + 'header header' + 'sidemenu content'; +} + +.docs header { + grid-area: header; +} + +.docs aside { + grid-area: sidemenu; +} + +.docs main { + margin-left: auto; + margin-right: auto; + margin-top: 3.5rem; + min-height: 100vh; + grid-area: content; +} + +.docs article { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.docs article a { + color: var(--qwik-dark-blue); + text-decoration: underline; +} + +.docs article a:hover { + text-decoration: none; +} + +.docs article h1 { + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity)); + font-size: 3rem; + line-height: 1; + font-weight: 700; + margin-bottom: 1.5rem; + position: relative; +} + +.docs article h2, +.docs article h3, +.docs article h4, +.docs article h5, +.docs article h6 { + font-size: 1.25rem; + line-height: 1.75rem; + font-weight: 600; + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity)); + margin-top: 2rem; + margin-bottom: 1rem; + position: relative; + scroll-margin-top: 80px; +} + +.docs article h2 { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.docs h2 a, +.docs h3 a, +.docs h4 a, +.docs h5 a, +.docs h6 a { + position: absolute; + width: 100%; + height: 32px; +} + +.icon-link { + display: none; +} + +.icon-link::after { + position: absolute; + content: '#'; + left: -23px; +} + +h2 a:hover .icon, +h3 a:hover .icon, +h4 a:hover .icon, +h5 a:hover .icon, +h6 a:hover .icon { + display: inline; +} + +.docs article p { + margin-top: 1.25rem; + margin-bottom: 1.25rem; + font-size: 17px; +} + +.docs article ul, +.docs article ol { + margin-top: 1rem; + margin-bottom: 1rem; + margin-left: 2rem; + font-size: 17px; + line-height: 1.7; + list-style: disc; +} + +.docs article li { + margin-top: 0.25rem; + margin-bottom: 0.25rem; + padding-left: 0.5rem; +} + +.docs article pre { + overflow: auto; + color: white; + background-color: rgb(1 31 51); + padding: 18px 15px; + border-radius: 8px; +} + +.docs article pre code { + display: block; + max-width: 50px; + background-color: transparent; +} + +.docs article code { + background-color: rgb(224, 224, 224); + padding: 2px 4px; + border-radius: 3px; + font-size: 0.9em; + line-height: 1.4; +} + +@media (max-width: 640px) { + .docs article code { + word-break: break-all; + } +} + +.docs article th, +.docs article td { + padding: 6px; + vertical-align: top; +} + +.docs article th:first-child, +.docs article td:first-child { + padding-left: 0; +} + +.docs article th:last-child, +.docs article td:last-child { + padding-right: 0; +} + +.docs article img { + border-radius: 5px; + overflow: hidden; +} diff --git a/starters/qwik-express/src/layouts/default/default.tsx b/starters/qwik-express/src/layouts/default/default.tsx new file mode 100644 index 000000000..63c4fb190 --- /dev/null +++ b/starters/qwik-express/src/layouts/default/default.tsx @@ -0,0 +1,24 @@ +import { component$, Host, Slot, useScopedStyles$ } from '@builder.io/qwik'; +import { Footer } from '../../components/footer/footer'; +import { Header } from '../../components/header/header'; +import { SideBar } from '../../components/sidebar/sidebar'; +import styles from './default.css?inline'; + +const DefaultLayout = component$(() => { + useScopedStyles$(styles); + + return ( + +
+ +
+
+ +
+
+
+ + ); +}); + +export default DefaultLayout; diff --git a/starters/qwik-express/src/layouts/not-found/not-found.css b/starters/qwik-express/src/layouts/not-found/not-found.css new file mode 100644 index 000000000..e69de29bb diff --git a/starters/qwik-express/src/layouts/not-found/not-found.tsx b/starters/qwik-express/src/layouts/not-found/not-found.tsx new file mode 100644 index 000000000..8d372c5fb --- /dev/null +++ b/starters/qwik-express/src/layouts/not-found/not-found.tsx @@ -0,0 +1,18 @@ +import { component$, Host, useScopedStyles$ } from '@builder.io/qwik'; +import { Header } from '../../components/header/header'; +import { SideBar } from '../../components/sidebar/sidebar'; +import styles from './not-found.css?inline'; + +const NotFound = component$(() => { + useScopedStyles$(styles); + + return ( + +
+ +
Not Found
+ + ); +}); + +export default NotFound; diff --git a/starters/qwik-express/src/pages/INDEX b/starters/qwik-express/src/pages/INDEX new file mode 100644 index 000000000..45a7abfa3 --- /dev/null +++ b/starters/qwik-express/src/pages/INDEX @@ -0,0 +1,16 @@ +# Site struct + +## Welcome + +- [Welcome](index.mdx) + +## Blog + +- [Resumable](blog/resumable.mdx) +- [Progressive](blog/progressive.mdx) +- [Reactive](blog/reactivity.mdx) + +## Docs + +- [Qwik](https://qwik.builder.io) +- [Partytown](https://partytown.builder.io/) diff --git a/starters/qwik-express/src/pages/blog/progressive.mdx b/starters/qwik-express/src/pages/blog/progressive.mdx new file mode 100644 index 000000000..1c2317dad --- /dev/null +++ b/starters/qwik-express/src/pages/blog/progressive.mdx @@ -0,0 +1,70 @@ +--- +title: Progressively +--- + +# Progressively + +Progressively is about downloading code as the application needs, without having to download the entire codebase eagerly. + +This connect with qwik's core tenant which focus on delaying **the loading** and execution of JavaScript for as long as possible. Qwik needs to break up the application into many lazy loadable chunks to achieve that. + +## Current state-of-the-art + +[Existing frameworks suffer of two problems](https://www.builder.io/blog/dont-blame-the-developer-for-what-the-frameworks-did): + +- Lazy loading boundaries are 100% delegated to the developer +- Frameworks can only lazy load components that are not in the current render tree. + +The issue is that the frameworks need to reconcile their internal state with the DOM. And that means that at least once on application hydration, they need to be able to do a full render to rebuild the framework's internal state. After the first render, the frameworks can be more surgical about their updates, but the damage has been done, the code has been downloaded. So we have two issues: + +Frameworks need to download and execute components to rebuild the render tree on startup. (See hydration is pure overhead) This forces eager download and execution of all components in the render tree. +The event handlers come with the components even though they are not needed at the time of rendering. The inclusion of event handlers forces the download of unnecessary code. + +## Solution + +Qwik architecture takes full advantage of modern tooling to automate the problem of entry point generation. Developers can write components normally, while the Qwik optimizer will split the components into chunks and download them as needed. + +In addition the framework runtime does not need to download code that is not required for interactivity, even if the component is part of the render tree. + +### Optimizer + +Optimizer is a code transformation that extracts functions into top-level importable symbols, which allows the Qwik runtime to lazy-load the JavaScript on a needed basis. + +The Optimizer and Qwik runtime work together to achieve the desired result of fine-grained lazy loading. + +Without Optimizer either: + +- The code would have to be broken up by the developer into importable parts. This would be unnatural to write an application, making for a bad DX. +- The application would have to load a lot of unnecessary code as there would be no lazy-loaded boundaries. + +Qwik runtime must understand the Optimizer output. The biggest difference to comprehend is that by breaking up the component into lazy-loadable chunks, the lazy-loading requirement introduces asynchronous code into the framework. The framework has to be written differently to take asynchronicity into account. Existing frameworks assume that all code is available synchronously. This assumption prevents an easy insertion of lazy-loading into existing frameworks. (For example, when a new component is created, the framework assumes that its initialization code can be invoked in a synchronous fashion. If this is the first time component is referenced, then its code needs to be lazy-loaded, and therefore the framework must take that into account.) + +### Lazy-loading + +Lazy-loading is asynchronous. Qwik is an asynchronous framework. Qwik understands that at any time, it may not have a reference to a callback, and therefore, it may need to lazy-load it. (In contrast, most existing frameworks assume that all of the code is available synchronously, making lazy-loading non-trivial.) + +In Qwik everything is lazy-loadable: + +- Component on-render (initialization block and render block) +- Component on-watch (side-effects, only downloaded if inputs change) +- Listeners (only downloaded on interaction) +- Styles (Only downloaded if the server did not already provide them) + +Lazy-loading is a core property of the framework and not an afterthought. + +### Optimizer and `$` + +Let's look at our example again: + +```tsx +const Counter = component$(() => { + const store = useStore({ count: 0 }); + + return ; +}); +``` + +Notice the presence of `$` in the code. `$` is a marker that tells the Optimizer that the function +following it should be lazy-loaded. +The `$` is a single character that hints to the Optimizer and the developer to let them know +that asynchronous lazy-loading occurs here. diff --git a/starters/qwik-express/src/pages/blog/reactivity.mdx b/starters/qwik-express/src/pages/blog/reactivity.mdx new file mode 100644 index 000000000..dc5b05f0d --- /dev/null +++ b/starters/qwik-express/src/pages/blog/reactivity.mdx @@ -0,0 +1,155 @@ +--- +title: Overview +--- + +# Reactivity + +Reactivity is a key component of Qwik. Reactivity allows Qwik to track which components are subscribed to which state. This information enables Qwik to invalidate only the relevant component on state change, which minimizes the number of components that need to be rerendered. Without reactivity, a state change would require rerendering from the root component, which would force the whole component tree to be eagerly downloaded. + +# Proxy + +Reactivity requires that the framework keeps track of the relationship between the state of the applications and the components. The framework must render the whole application at least once to build the reactivity graph. The build of reactive graph initially happens on the server and is serialized to HTML so that the browser can use the information without being forced to do a single pass through all components to rebuild the graph. (Qwik does not need to do hydration to register events or build up the reactivity graph.) + +Reactivity can be done in a few ways: + +1. using explicit registration of listeners using `.subscribe()` (for example, RxJS) +2. using implicit registration using a compiler (for example, Svelte) +3. using implicit registration using proxies. + +Qwik uses proxies for a few reasons: + +1. using explicit registration such as `.subscribe()` would require that the system would need to serialize all of the subscribe listeners to avoid hydration. Serializing subscribe closures would not be possible as all the subscribe functions would have to be lazy-loaded and asynchronous (too expensive). +2. using the compiler to create graphs implicitly would work, but only for components. Intra component communications would still require `.subscribe()` methods and hence suffer the issues described above. + +Because of the above constraints, Qwik uses proxies to keep track of the reactivity graph. + +- use `useStore()` to create a store proxy. +- the proxy notices the reads and creates subscriptions that are serializable. +- the proxy notices the writes and uses the subscription information to invalidate the relevant components. + +## Counter Example + +```tsx +const Counter = component$(() => { + const store = useStore({ count: 0 }); + + return ; +}); +``` + +1. The server performs an initial render of the component. The server rendering includes creating the proxy represented by `store`. +2. The initial render invokes the OnRender method, which has a reference to the `store` proxy. The rendering puts the proxy to "learn" mode. During the build-up of JSX, the proxy observers a read to `count` property. Because the proxy is in the "learn" mode, it records that the `Counter` has a subscription on the `store.count`. +3. The server serializes the state of the application into HTML. This includes the `store` as well as subscription information which says that `Counter` is subscribed to `store.count`. +4. In the browser, the user clicks the button. Because the click event handler closed over `store`, the Qwik restores the store proxy. The proxy contains the application state (the count) and the subscription, which associates the `Counter` with `state.count`. +5. The event handler increments the `store.count`. Because the `store` is a proxy, it notices the write and uses the subscription information to invalidate the `Counter`. +6. After `requestAnimationFrame`, the `Counter` downloads the rendering function and reruns the OnRender. +7. During the OnRender, the subscription list is cleared, and a new subscription list is built up by observing what reads the JSX building performs. + +## Unsubscribe example + +```tsx +const ComplexCounter = component$(() => { + const store = useStore({ count: 0, visible: true }); + + return ( + <> + + + {store.visible ? {store.count} : null} + + ); +}); +``` + +This example is a more complicated counter. + +- It contains the `increment` button, which always increments `store.count`. +- It contains a `show`/`hide` button which determines if the count is shown. + +1. On the initial render, the count is visible. Therefore the server creates a subscription that records that `ComplexCounter` needs to get rerender if either `store.count` or `store.visible` changes. +2. If the user clicks on `hide`, the `ComplexCounter` rerenders. The rerendering clears all of the subscriptions and records new ones. This time the JSX does not read `store.count`. Therefore, only `store.visible` gets added to the list of subscriptions. +3. User clicking on `increment` will update `store.count`, but doing so will not cause the component to rerender. This is correct because the counter is not visible, so rerendering would be a no-op. +4. If the user clicks `show` the component will rerender and this time the JSX will read both `store.visible` as well as `store.count`. The subscription list is once again updated. +5. Now, clicking on `increment` updates the `store.count`. Because the count is visible, the `ComplexCounter` is subscribed to `store.count`. + +Notice how the set of subscriptions automatically updates as the component renders different branches of its JSX. The advantage of the proxy is that the subscriptions update automatically as the applications execute, and the system can always compute the smallest set of invalidated components. + +## Deep objects + +So far, the examples show the store (`useStore()`) was a simple object with primitive values. However, the same behavior works on all properties of the store recursively. + +```tsx +const MyComp = component$(() => { + const store = useStore({ + person: { first: null, last: null }, + location: null + }); + + store.location = {street: 'main st'}; + + return ( +
+
{store.person.last}, {store.person.first}
+
{store.location.street} + + ); +}) +``` + +In the above examples, the Qwik will automatically wrap child objects `person` and `location` into a proxy and correctly create subscriptions on all deep properties. + +The wrapping behavior described above has one surprising side-effect. Writing and reading from a proxy auto wraps the object, which means that the identity of the object changes. This should normally not be an issue, but it is something that the developer should keep in mind. + +```tsx +const MyComp = component$(() => { + const store = useStore({ person: null }); + const person = { first: 'John', last: 'Smith' }; + store.person = person; // `store.person auto wraps object into proxy` + + if (store.person !== person) { + // The consequence of auto wrapping is that the object identity changes. + console.log('store auto-wrapped person into a proxy'); + } +}); +``` + +## Out-of-order rendering + +Qwik components can render out of order. A component can render without forcing a parent component to render first or to force child components to render as a consequence of the component render. This is an important property of Qwik because it allows Qwik applications to only re-render components which have been invalidated due to state change rather than re-rendering the whole component tree on change. + +When components render, they need to have access to their props. Parent components create props. The props must be serializable for the component to render independently from the parent. (See serialization for an in-depth discussion on what is serializable.) + +## Invalidating child components + +When re-rendering a component, the child component props can stay the same or be updated. A child component only invalidates if the child component props change. + +```tsx +const Child = component$((props: { count: number }) => { + return {props.count}; +}); + +const MyApp = component$(() => { + const store = useStore({ a: 0, b: 0, c: 0 }); + + return ( + <> + + + + {JSON.stringify(store)} + + + + + ); +}); +``` + +In the above example, there are two `` components. + +- Every time a button is clicked, one of the three counters is incremented. A change of counter state will cause the `MyApp` component to re-render on each click. +- If `store.c` has been incremented, none of the child components get re-render. (And therefore, their code does not get lazy-loaded.) +- If `store.a` has been incremented than only `` will re-render. +- If `store.b` has been incremented than only `` will re-render. + +Notice that the child components only re-render when their props change. This is an important property of Qwik applications as it significantly limits the amount of re-rendering the application must do on state change. While less re-rendering has performance benefits, the real benefit is that large portions of the applications do not get downloaded if they don't need to be re-rendered. diff --git a/starters/qwik-express/src/pages/blog/resumable.mdx b/starters/qwik-express/src/pages/blog/resumable.mdx new file mode 100644 index 000000000..1132f20c3 --- /dev/null +++ b/starters/qwik-express/src/pages/blog/resumable.mdx @@ -0,0 +1,107 @@ +--- +title: Resumable +--- + +# Resumable vs. Hydration + +A key concept of Qwik applications is that they are resumable from server-side-rendered state. The best way to explain resumability is to understand how the current generation of frameworks are replayable (hydration). + +When an SSR/SSG application boots up on a client, it requires that the framework on the client restore two pieces of information: + +1. Locate event listeners and install them on the DOM nodes to make the application interactive; +2. Build up an internal data structure representing the application component tree. +3. Restore the application state. + +Collectively, this is known as hydration. All current generations of frameworks require this step to make the application interactive. + +[Hydration is expensive](https://www.builder.io/blog/hydration-is-pure-overhead) for two reasons: + +1. The frameworks have to download all of the component code associated with the current page. +2. The frameworks have to execute the templates associated with the components on the page to rebuild the listener location and the internal component tree. + +![Resumable vs Hydration](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F04681212764f4025b2b5f5c6a258ad6e?format=webp&width=2000) + +Qwik is different because it does not require hydration to resume an application on the client. Not requiring hydration is what makes the Qwik application startup instantaneous. + +All frameworks hydration **replay** all the application logic in the client, instead Qwik pauses execution in the server, and resumes execution in the client, + +# Introducing Resumability + +Resumability is about pausing execution in the server, and resuming execution in the client without having to replay and download all of the application logic. + +A good mental model is that Qwik applications at any point in their lifecycle can be serialized and moved to a different VM instance. (Server to browser). There, the application simply resumes where the serialization stopped. No hydration is required. This is why we say that Qwik applications don't hydrate; they resume. + +In order to achieve this, qwik needs to solve the 3 problems (listeners, component tree, application state) in a way that is compatible with a no-code startup. + +## Listeners + +DOM without event listeners is just a static page; it is not an application. Today's standard for all sites is quite a high level of interactivity, so even the most static-looking sites are full of event listeners. These include menus, hovers, expanding details, or even full-on interactive apps. + +Existing frameworks solve the event listener by downloading the components and executing their templates to collect event listeners that are then attached to the DOM. The current approach has these issues: + +1. Requires the template code to be eagerly downloaded; +2. Requires template code to be eagerly executed; +3. Requires the event handler code to be downloaded eagerly (to be attached.) + +The above approach does not scale. As the application becomes more complicated, the amount of code needed to download eagerly and execute grows proportionally with the size of the application. This negatively impacts the application startup performance and hence the user experience. + +Qwik solves the above issue by serializing the event listens into DOM like so: + +```html + +``` + +Qwik still needs to collect the listener information, but this step is done as part of the SSR/SSG. The results of SSR/SSG are then serialized into HTML so that the browser does not need to do anything to resume the execution. Notice that the `onClickQrl` attribute contains all of the information to resume the application without doing anything eagerly. + +1. Qwikloader sets up a single global listener instead of many individual listeners per DOM element. This step can be done with no application code present. +2. The HTML contains a URL to the chunk and symbol name. The attribute tells Qwikloader which code chunk to download and which symbol to retrieve from the chunk. +3. Finally, to make all of the above possible, Qwik's event processing implementation understands asynchronicity which allows insertion of asynchronous lazy loading. + +## Component Trees + +Frameworks work with component trees. To that end, frameworks need a complete understanding of the component trees to know which components need to be rerendered and when. If you look into the existing framework SSR/SSG output, the component boundary information has been destroyed. There is no way of knowing where component boundaries are by looking at the resulting HTML. To recreate this information, frameworks re-execute the component templates and memoize the component boundary location. Re-execution is what hydration is. Hydration is expensive as it requires both the download of component templates and their execution. + +Qwik collects component boundary information as part of the SSR/SSG and then serializes that information into HTML. The result is that Qwik can: + +1. Rebuild the component hierarchy information without the component code actually being present. The component code can remain lazy. +2. Qwik can do this lazily only for the components which need to be rerendered rather than all upfront. +3. Qwik collects relationship information between stores and components. This creates a subscription model that informs Qwik which components need to be re-rendered as a result of state change. The subscription information also gets serialized into HTML. + +## Application State + +Existing frameworks usually have a way of serializing the application state into HTML so that the state can be restored as part of hydration. In this way, they are very similar to Qwik. However, Qwik has state management more tightly integrated into the lifecycle of the components. In practice, this means that component can be delayed loaded independently from the state of the component. This is not easily achievable in existing frameworks because component props are usually created by the parent component. This creates a chain reaction. In order to restore component X, its parents need to be restored as well. Qwik allows any component to be resumed without the parent component code being present. + +### Serialization + +The simplest way to think about serialization is through `JSON.stringify`. However, JSON has several limitations. Qwik can overcome some limitations, but some can't be overcome, and they place limitations on what the developer can do. Understanding these limitations is important when building Qwik applications. + +Limitations of JSON which Qwik solves: + +- JSON produces DAG. DAG stands for directed acyclic graph, which means that the object which is being serialized can't have circular references. This is a big limitation because the application state is often circular. Qwik ensures that when the graph of objects gets serialized, the circular references get properly saved and then restored. +- JSON can't serialize some object types. For example, DOM references, Dates, etc... Qwik serialization format ensures that such objects can correctly be serialized and restored. Here is a list of types that can be serialized with Qwik: + - DOM references + - Dates (not yet implemented) + - Function closures (if wrapped in QRL). + +Limitations of JSON that Qwik does not solve: + +- Serialization of classes (`instanceof` and prototype) +- Serialization of `Promise`s, Streams, etc... + +## Writing applications with serializability in mind + +The resumability property of the framework must extend to resumability of the application as well. This means that the framework must provide mechanisms for the developer to express Components and Entities of the applications in a way which can be serialized and then resumed (without re-bootstrapping). This necessitates that applications are written with resumability constraints in mind. It is simply not possible for developers to continue to write applications in a heap-centric way and expect that a better framework can somehow make up for this sub-optimal approach. + +Developers must write their applications in a DOM-centric way. This will require a change of behavior and retooling of web-developers skills. Frameworks need to provide guidance and APIs to make it easy for developers to write the applications in this way. + +## Other benefits of resumability + +The most obvious benefit of using resumability is for server-side-rendering. However, there are secondary benefits: + +- Serializing existing PWA apps so that users don't loose context when they return to the application +- Improved rendering performance because only changed components need to be re-rendered +- Fine-grained-lazy loading +- Decreased memory pressure, especially on mobile devices +- Progressive interactivity of existing static websites + +Reactivity is a key component of Qwik. Reactivity allows Qwik to track which components are subscribed to which state. This information enables Qwik to invalidate only the relevant component on state change, which minimizes the number of components that need to be rerendered. Without reactivity, a state change would require rerendering from the root component, which would force the whole component tree to be eagerly downloaded. diff --git a/starters/qwik-express/src/pages/index.mdx b/starters/qwik-express/src/pages/index.mdx new file mode 100644 index 000000000..056287c2d --- /dev/null +++ b/starters/qwik-express/src/pages/index.mdx @@ -0,0 +1,26 @@ +--- +title: Welcome +--- + +import { Counter } from '../components/counter/counter'; + +# Welcome + +Welcome to the fabolous Qwik City. QwikCity is our official kit to build amazing sites: + +- 🐎 Powered by Qwik. Zero hydration and instant interactivity. +- 🗃 File based routing (like Nextjs) +- 🌏 Deploy easily to edge: Netlify, Cloudflare, Vercel, Deno... +- 📖 Author content directly in Markdown or MDX. MDX brings the best of markdown and JSX, allowing you to render Qwik components directly in markdown, like this one: + + + +## Next steps + +Open your editor and check the following: + +- **The `pages` folder**: This is where the main content lives. Adding you files (`.md`, `.mdx`, or `.tsx`) will automatically create new URLs. +- **The `src/layout` folder**: This is what bring form to your content. Different kinds of pages, might need to be rendered by a different layout. + This way you can reuse easily the layout across different content. By default, the `src/layout/default` is used. +- **The `src/components` folder**: QwikCity is still a normal Qwik app. Any custom component, design systems, or funcionality should live here. Notice the `header`, `footer` and `content-nav` components. They are used by the `default` layout! +- **The `src/utils` folder**: Place here business logic, or functions that are not components. diff --git a/starters/qwik-express/src/root.tsx b/starters/qwik-express/src/root.tsx new file mode 100644 index 000000000..5144de583 --- /dev/null +++ b/starters/qwik-express/src/root.tsx @@ -0,0 +1,30 @@ +import { App } from './components/app/app'; + +import './global.css'; + +export const Root = () => { + return ( + + + + + + + + + ); +}; + +const Head = () => ( + <> + + + QwikCity Example + + + + + + + +); diff --git a/starters/qwik-express/tsconfig.json b/starters/qwik-express/tsconfig.json new file mode 100644 index 000000000..be000573e --- /dev/null +++ b/starters/qwik-express/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "allowJs": true, + "target": "ES2017", + "module": "ES2020", + "lib": ["es2020", "DOM"], + "jsx": "react-jsx", + "jsxImportSource": "@builder.io/qwik", + "strict": true, + "declaration": true, + "declarationDir": "lib/types", + "resolveJsonModule": true, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "incremental": true, + "isolatedModules": true, + "types": ["vite/client"] + }, + "include": ["src"] +} diff --git a/starters/qwik-express/vite.config.ts b/starters/qwik-express/vite.config.ts new file mode 100644 index 000000000..a844c4c96 --- /dev/null +++ b/starters/qwik-express/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite'; +import { qwikVite } from '@builder.io/qwik/optimizer'; +import { qwikCity } from '@builder.io/qwik-city/vite'; +import { resolve } from 'path'; + + +export default defineConfig(() => { + return { + + plugins: [ + qwikCity({ + pagesDir: resolve('src', 'pages'), + layouts: { + default: resolve('src', 'layouts', 'default', 'default.tsx'), + }, + }), + qwikVite(), + + ], + }; +});