diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0528ae650530..5b135d6219c8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ { "name": "Qwik devcontainer", "hostRequirements": { - "cpus": 4 + "cpus": 4, }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ @@ -15,23 +15,23 @@ "ms-azuretools.vscode-docker", "mitsuhiko.insta", "silvenon.mdx", - "csstools.postcss" + "csstools.postcss", ], "build": { - "dockerfile": "Dockerfile" + "dockerfile": "Dockerfile", }, "waitFor": "updateContentCommand", "updateContentCommand": "corepack prepare & pnpm install", "forwardPorts": [3300, 9229], "customizations": { "codespaces": { - "openFiles": ["CONTRIBUTING.md"] - } + "openFiles": ["CONTRIBUTING.md"], + }, }, "portsAttributes": { "3300": { "label": "Serve", - "onAutoForward": "openPreview" - } - } + "onAutoForward": "openPreview", + }, + }, } diff --git a/.envrc b/.envrc new file mode 100644 index 000000000000..68ff6ad3d9d5 --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +use flake +PATH_add ./node_modules/.bin diff --git a/.eslintignore b/.eslintignore index 1028da0c6585..e46b14bd09cb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -18,6 +18,7 @@ scripts/**/* **/server/**/*.js *.tsbuildinfo packages/docs/api/**/* +packages/docs/public/repl/bundled/**/* packages/docs/src/routes/examples/apps/**/* packages/docs/src/routes/playground/app/**/* packages/docs/src/routes/tutorial/**/* diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 09a884be7f92..ad3afe7dd8ff 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -4,13 +4,24 @@ module.exports = { es2021: true, node: true, }, - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + // Enable this for testing but it makes the lint quite slow + // 'plugin:qwik/recommended', + ], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', + // Needed when using the qwik plugin + // project: ['./tsconfig.json'], }, - plugins: ['@typescript-eslint', 'no-only-tests'], + plugins: [ + '@typescript-eslint', + 'no-only-tests', + // 'qwik' + ], rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42c192474472..daf541e94afa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,16 +109,19 @@ jobs: - run: corepack enable - name: Install NPM Dependencies - run: pnpm install --frozen-lockfile + run: | + # Ensure that the qwik binary gets made + mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile - name: Build Packages - run: pnpm tsm scripts/index.ts --tsc --build --cli --api --set-dist-tag="${{ github.event.inputs.disttag }}" + run: pnpm tsm scripts/index.ts --tsc --build --cli --api --qwiklabs --eslint --platform-binding-wasm-copy --set-dist-tag="${{ github.event.inputs.disttag }}" - name: Print Qwik Dist Build run: tree packages/qwik/dist/ - name: Upload Qwik Build Artifacts - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v3 with: name: dist-dev-builder-io-qwik path: packages/qwik/dist/ @@ -128,7 +131,7 @@ jobs: run: tree packages/create-qwik/dist/ - name: Upload Create Qwik CLI Build Artifacts - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v3 with: name: dist-dev-create-qwik path: packages/create-qwik/dist/ @@ -141,7 +144,7 @@ jobs: run: tree packages/eslint-plugin-qwik/dist/ - name: Upload Eslint rules Build Artifacts - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v3 with: name: dist-dev-eslint-plugin-qwik path: packages/eslint-plugin-qwik/dist/ @@ -203,7 +206,10 @@ jobs: - name: Install NPM Dependencies if: ${{ needs.changes.outputs.fullbuild == 'true' }} - run: pnpm install --frozen-lockfile + run: | + # Ensure that the qwik binary gets made + mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile - uses: jetli/wasm-pack-action@v0.3.0 if: ${{ needs.changes.outputs.fullbuild == 'true' }} @@ -220,7 +226,7 @@ jobs: - name: Upload WASM Build Artifacts if: ${{ needs.changes.outputs.fullbuild == 'true' }} - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v3 with: name: dist-bindings-wasm path: packages/qwik/dist/bindings/* @@ -311,7 +317,10 @@ jobs: - name: Install NPM Dependencies if: ${{ needs.changes.outputs.fullbuild == 'true' }} - run: pnpm install --frozen-lockfile + run: | + # Ensure that the qwik binary gets made + mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile - name: Build Platform Binding if: ${{ needs.changes.outputs.fullbuild == 'true' }} @@ -350,12 +359,14 @@ jobs: - name: Install NPM Dependencies if: ${{ needs.changes.outputs.insightsbuild == 'true' }} - run: pnpm install --frozen-lockfile + run: | + # Ensure that the qwik binary gets made + mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile - name: Build Qwik Insights if: ${{ needs.changes.outputs.insightsbuild == 'true' }} - working-directory: packages/insights - run: pnpm run build.ci + run: pnpm run build.packages.insights ############ BUILD DOCS ############ build-docs: @@ -381,12 +392,14 @@ jobs: - name: Install NPM Dependencies if: ${{ needs.changes.outputs.docsbuild == 'true' }} - run: pnpm install --frozen-lockfile + run: | + # Ensure that the qwik binary gets made + mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile - name: Build Qwik Docs if: ${{ needs.changes.outputs.docsbuild == 'true' }} - working-directory: packages/docs - run: pnpm run build + run: pnpm run build.packages.docs ############ BUILD DISTRIBUTION ############ build-distribution: @@ -421,13 +434,11 @@ jobs: if: ${{ needs.changes.outputs.fullbuild == 'true' }} - name: Install NPM Dependencies - if: ${{ needs.changes.outputs.fullbuild == 'true' }} - run: pnpm install --frozen-lockfile - - - name: Create packages/qwik/dist/ directory if: ${{ needs.changes.outputs.fullbuild == 'true' }} run: | + # Ensure that the qwik binary gets made mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile - name: Download Build Artifacts if: ${{ needs.changes.outputs.fullbuild == 'true' }} @@ -455,7 +466,7 @@ jobs: - name: Upload Qwik Distribution Artifact if: ${{ needs.changes.outputs.fullbuild == 'true' }} - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v3 with: name: builderio-qwik-distribution path: packages/qwik/dist/* @@ -463,7 +474,7 @@ jobs: - name: Build QwikCity / QwikLabs if: ${{ needs.changes.outputs.fullbuild == 'true' }} - run: pnpm tsm scripts/index.ts --tsc --qwikcity --qwiklabs --api + run: pnpm tsm scripts/index.ts --tsc --qwikcity --qwiklabs --api --eslint - name: Print QwikCity Lib Build if: ${{ needs.changes.outputs.fullbuild == 'true' }} @@ -471,7 +482,7 @@ jobs: - name: Upload QwikCity Build Artifacts if: ${{ needs.changes.outputs.fullbuild == 'true' }} - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v3 with: name: builderio-qwikcity-distribution path: packages/qwik-city/lib/ @@ -483,7 +494,7 @@ jobs: - name: Upload QwikLabs Build Artifacts if: ${{ needs.changes.outputs.fullbuild == 'true' }} - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v3 with: name: builderio-qwiklabs-distribution path: | @@ -543,7 +554,10 @@ jobs: mv builderio-qwiklabs-distribution/vite/* packages/qwik-labs/vite/ - name: Install NPM Dependencies - run: pnpm install --frozen-lockfile + run: | + # Ensure that the qwik binary gets made + mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile - name: Commit Build Artifacts if: ${{ needs.changes.outputs.fullbuild == 'true' && github.event_name == 'push' }} @@ -622,7 +636,10 @@ jobs: - name: Install NPM Dependencies if: ${{ needs.changes.outputs.fullbuild == 'true' }} - run: pnpm install --frozen-lockfile + run: | + # Ensure that the qwik binary gets made + mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile - name: Install Playwright if: ${{ needs.changes.outputs.fullbuild == 'true' }} @@ -630,7 +647,7 @@ jobs: - name: Playwright E2E Tests if: ${{ needs.changes.outputs.fullbuild == 'true' }} - run: pnpm run test.e2e.${{ matrix.settings.browser }} + run: pnpm run test.e2e.${{ matrix.settings.browser }} --timeout 60000 --retries 3 --workers 1 - name: Validate Create Qwik Cli if: ${{ needs.changes.outputs.fullbuild == 'true' }} @@ -659,7 +676,13 @@ jobs: - run: corepack enable - name: Install NPM Dependencies - run: pnpm install --frozen-lockfile + run: | + # Ensure that the qwik binary gets made + mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile + + - name: Build core + run: pnpm run build.core - name: Unit Tests run: pnpm run test.unit @@ -749,12 +772,19 @@ jobs: - run: corepack enable - name: Install NPM Dependencies - run: pnpm install --frozen-lockfile + run: | + # Ensure that the qwik binary gets made + mkdir -p packages/qwik/dist/bindings/ + pnpm install --frozen-lockfile - name: Prettier Check if: ${{ always() }} run: pnpm run lint.prettier + - name: Build ESLint + if: ${{ always() }} + run: pnpm tsm scripts/index.ts --eslint + - name: ESLint Check if: ${{ always() }} run: pnpm run lint.eslint diff --git a/.gitignore b/.gitignore index 190d4c161356..8897fc65c04d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ etc temp .history +.lh *. **/*.log *.node @@ -16,6 +17,7 @@ qwik-app/ /server/ /starters/**/server/ /packages/*/server/ +!/packages/qwik/server/ /packages/*/src/styled-system/ todo-express/ target @@ -37,6 +39,7 @@ tsdoc-metadata.json .idea .eslintcache test-results +.direnv # Package Managers .yarn/* @@ -45,3 +48,4 @@ test-results # Local Netlify folder .netlify +sandbox diff --git a/.nvmrc b/.nvmrc index aacb5181047f..a3597ecbd10c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.17 +20.11 diff --git a/.prettierignore b/.prettierignore index 1c639711ff17..13b92229c592 100644 --- a/.prettierignore +++ b/.prettierignore @@ -32,11 +32,13 @@ packages/docs/server packages/docs/src/routes/api packages/docs/**/*.md packages/docs/**/*.mdx +packages/docs/public/repl/bundled packages/insights/drizzle packages/insights/.netlify packages/insights/scripts packages/insights/**/*.gen.d.ts packages/qwik-labs/lib-types +packages/qwik-labs/vite # TODO: Figure out why this doesn't pass in CI packages/qwik/src/core/props/props.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index c59877b158f6..9c8acee05e90 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,6 +13,15 @@ "program": "${workspaceFolder}/packages/docs/node_modules/vite/bin/vite.js", "args": ["--mode", "ssr", "--force"] }, + { + "type": "node", + "name": "insights dev.debug", + "request": "launch", + "skipFiles": ["/**"], + "cwd": "${workspaceFolder}/packages/insights", + "program": "${workspaceFolder}/packages/insights/node_modules/vite/bin/vite.js", + "args": ["--mode", "ssr", "--force"] + }, { "type": "node", "name": "docs build.client", @@ -49,7 +58,7 @@ "internalConsoleOptions": "neverOpen", "program": "${workspaceFolder}/./node_modules/vitest/vitest.mjs", "cwd": "${workspaceFolder}", - "args": ["--threads=false", "packages/qwik/src/core/container/render.unit.tsx"] + "args": ["${file}"] } ] } diff --git a/.vscode/qwik.code-snippets b/.vscode/qwik.code-snippets index 380a2624d332..3bce793184d5 100644 --- a/.vscode/qwik.code-snippets +++ b/.vscode/qwik.code-snippets @@ -14,8 +14,8 @@ " });", " ", " return <${4:button} on${5:Click}$={(${6:e}) => {$7}}>$8", - "});" - ] + "});", + ], }, "Qwik component (simple)": { "scope": "javascriptreact,typescriptreact", @@ -24,50 +24,50 @@ "body": [ "export const ${1:${TM_FILENAME_BASE/(.*)/${1:/capitalize}/}} = component$(() => {", " return <${2:button}>$4", - "});" - ] + "});", + ], }, "Qwik state": { "scope": "javascriptreact,typescriptreact", "prefix": "q:useStore$", "description": "useStore() declaration", - "body": ["const ${1:state} = useStore({", " $2", "});", "$0"] + "body": ["const ${1:state} = useStore({", " $2", "});", "$0"], }, "Qwik signal": { "scope": "javascriptreact,typescriptreact", "prefix": "q:useSignal", "description": "useSignal() declaration", - "body": ["const ${1:signal} = useSignal($2);", "$0"] + "body": ["const ${1:signal} = useSignal($2);", "$0"], }, "$ hook": { "scope": "javascriptreact,typescriptreact", "prefix": "q:$", "description": "$() function hook", - "body": ["$(() => {", " $0", "});", ""] + "body": ["$(() => {", " $0", "});", ""], }, "useVisibleTask": { "scope": "javascriptreact,typescriptreact", "prefix": "q:useVisibleTask", "description": "useVisibleTask$() function hook", - "body": ["useVisibleTask$(({ track }) => {", " $0", "});", ""] + "body": ["useVisibleTask$(({ track }) => {", " $0", "});", ""], }, "useTask": { "scope": "javascriptreact,typescriptreact", "prefix": "q:useTask", "description": "useTask$() function hook", - "body": ["useTask$(({ track }) => {", " track(() => $1);", " $0", "});", ""] + "body": ["useTask$(({ track }) => {", " track(() => $1);", " $0", "});", ""], }, "useResource": { "scope": "javascriptreact,typescriptreact", "prefix": "q:useResource$", "description": "useResource$() declaration", - "body": ["const $1 = useResource$(({ track, previous, cleanup }) => {", " $0", "});", ""] + "body": ["const $1 = useResource$(({ track, previous, cleanup }) => {", " $0", "});", ""], }, "useOn": { "scope": "javascriptreact,typescriptreact", "prefix": "q:useOn", "description": "useOn declaration", - "body": ["useOn(", "'$1',", "$((event) => {", " const { $3 } = event as $2;", " })", ");"] + "body": ["useOn(", "'$1',", "$((event) => {", " const { $3 } = event as $2;", " })", ");"], }, "useOnDocument": { "scope": "javascriptreact,typescriptreact", @@ -79,8 +79,8 @@ "$((event) => {", " const { $3 } = event as $2;", " })", - ");" - ] + ");", + ], }, "useOnWindow": { "scope": "javascriptreact,typescriptreact", @@ -92,7 +92,7 @@ "$((event) => {", " const { $3 } = event as $2;", " })", - ");" - ] - } + ");", + ], + }, } diff --git a/.vscode/settings.json b/.vscode/settings.json index 18b1a8623155..7ebc374b10b1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,7 @@ "**/dist-dev/**", "**/cypress/**", "**/.{idea,git,cache,output,temp}/**" - ] + ], + "javascript.preferences.autoImportFileExcludePatterns": ["node:test"], + "typescript.preferences.preferTypeOnlyAutoImports": true } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cadbf49015e8..1b041a258d09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ We adopt [trunk-based development](https://trunkbaseddevelopment.com/) therefore ### Good first issue -The issues marked with [_Good first issue_](https://github.com/BuilderIO/qwik/issues?q=is:open+is:issue+label:%22good+first+issue%22) are a good starting point to familiarize yourself with the project. +The issues marked with [_Good first issue_](https://github.com/BuilderIO/qwik/issues?q=is%3Aissue+is%3Aopen+label%3A%22COMMUNITY%3A++good+first+issue%22) are a good starting point to familiarize yourself with the project. Before solving the problem, please check with the maintainers that the issue is still relevant. Feel free to leave a comment on the issue to show your intention to work on it and prevent other people from unintentionally duplicating your effort. @@ -61,16 +61,28 @@ This is the best approach because all required dependencies will be installed in You need to have these tools up and running in your local machine: - [VSCode](https://code.visualstudio.com/) -- [Docker](https://www.docker.com/) + +and either [Docker](https://www.docker.com/) or [Nix](https://nixos.org). ### Steps +If you want to use Docker: + - Install the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension in your VSCode. - Once installed you will be prompted to 'Reopen the folder to develop in a container [learn more](https://code.visualstudio.com/docs/devcontainers/containers) or Clone repository in Docker volume for [better I/O performance](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-a-git-repository-or-github-pr-in-an-isolated-container-volume)'. If you're not prompted, you can run the `Dev Containers: Open Folder in Container` command from the [VSCode Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette). +If you want to use Nix: + +- Install [Nix](https://nixos.org/download.html) on your machine and enable flakes. The [DetSys installer](https://github.com/DeterminateSystems/nix-installer) makes that easy. +- run `nix develop` in the project root. + +Nix+Direnv (optional): + +You can additionally use [direnv](https://direnv.net/) to automatically load the dev environment when you enter the project directory. + ### Using development container without Dev Containers and VSCode -If you would like to make use of the devlopment container solution, but don't use VSCode or Dev Containers, you still can do so, by following steps: +If you would like to make use of the development container solution, but don't use VSCode or Dev Containers, you still can do so, by following steps: - Build development container locally: `cd .devcontainer; docker build -t qwik-container .` - Run development container from Qwik project root, binding the directory to container: `cd ..; docker run --rm -d --name qwik-container -p 3300:3300 -p 9229:9299 -v $PWD:/home/circleci/project -t qwik-container` @@ -258,6 +270,16 @@ pnpm --filter qwik-docs start More commands can be found in each package's package.json scripts section. +### Updating dependencies + +To update all dependencies, run: + +```shell +pnpm deps +``` + +This will show an interactive UI to update all dependencies. Be careful about performing major updates, especially for the docs site, since not all functionalitty has test coverage there. + ## Starter CLI `create-qwik` - [Starter CLI](https://github.com/BuilderIO/qwik/blob/main/starters/README.md) diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000000..dc9abd840c96 --- /dev/null +++ b/flake.lock @@ -0,0 +1,96 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1704874635, + "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1681358109, + "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1705025860, + "narHash": "sha256-9vcqo5CJLOHU63S7pVlP0u4OhgJxrXebQR4vqMPXLRg=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "d458975da373a37422577886566fce8201bc1254", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000000..036b873f4681 --- /dev/null +++ b/flake.nix @@ -0,0 +1,40 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + rust-overlay.url = "github:oxalica/rust-overlay"; + }; + + outputs = { self, rust-overlay, nixpkgs }: + let + b = builtins; + devShell = system: _pkgs: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + in + { + default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + bashInteractive + gitMinimal + + nodejs_20 + corepack_20 + + # Qwik optimizer deps + wasm-pack + # Provides rustc and cargo + ((rust-bin.fromRustupToolchainFile + ./rust-toolchain).override { + targets = [ "wasm32-unknown-unknown" ]; + }) + ]; + }; + }; + in + { + devShells = b.mapAttrs (devShell) nixpkgs.legacyPackages; + }; +} diff --git a/package.json b/package.json index fcd66bd83e18..d4af53ba39c1 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,37 @@ { "name": "qwik-monorepo", - "version": "1.3.0", + "version": "1.4.5", + "comments": { + "01": "devDependencies includes reference to @builder.io/qwik: workspace: *. This is needed or e2e tests will fail", + "02": " It would be nice to be able to remove this dependency and fix the test.", + "03": "devDependencies can't include reference to @builder.io/qwik-city or e2e test will fail." + }, "config": { "commitizen": { "path": "./node_modules/cz-conventional-changelog" + }, + "syncpack": { + "versionGroups": [ + { + "label": "use workspace protocol for local packages and allow patch versions (used in e.g. qwik-react)", + "dependencies": [ + "$LOCAL" + ], + "dependencyTypes": [ + "!local" + ], + "pinVersion": "workspace:^" + } + ], + "semverGroups": [ + { + "label": "Undici should always be * until we remove it", + "range": "*", + "dependencies": [ + "undici" + ] + } + ] } }, "contributors": [ @@ -27,64 +55,71 @@ "esbuild-plugin-raw": "^0.1.7" }, "devDependencies": { - "@builder.io/partytown": "^0.8.1", + "@builder.io/partytown": "^0.9.0", + "@builder.io/qwik": "workspace:^", "@clack/prompts": "^0.7.0", - "@microsoft/api-documenter": "^7.23.13", - "@microsoft/api-extractor": "^7.38.4", - "@napi-rs/cli": "^2.16.3", - "@napi-rs/triples": "1.1.0", - "@node-rs/helper": "1.3.3", + "@eslint/eslintrc": "^3.0.0", + "@mdx-js/mdx": "2.3.0", + "@microsoft/api-documenter": "^7.23.19", + "@microsoft/api-extractor": "^7.39.3", + "@napi-rs/cli": "^2.18.0", + "@napi-rs/triples": "^1.2.0", + "@node-rs/helper": "^1.5.0", "@octokit/action": "3.18.1", - "@playwright/test": "^1.40.1", + "@playwright/test": "^1.41.1", "@types/brotli": "^1.3.4", "@types/cross-spawn": "^6.0.6", - "@types/eslint": "^8.44.8", + "@types/eslint": "^8.56.2", "@types/express": "^4.17.21", "@types/mri": "^1.1.5", - "@types/node": "^20.10.3", + "@types/node": "^20.11.6", "@types/path-browserify": "^1.0.2", "@types/prompts": "^2.4.9", - "@types/react": "^18.2.42", + "@types/react": "^18.2.48", "@types/semver": "^7.5.6", "@types/which-pm-runs": "^1.0.2", - "@typescript-eslint/eslint-plugin": "^6.13.2", - "@typescript-eslint/parser": "^6.13.2", - "@typescript-eslint/rule-tester": "^6.13.2", - "@typescript-eslint/utils": "^6.13.2", + "@typescript-eslint/eslint-plugin": "^6.19.1", + "@typescript-eslint/parser": "^6.19.1", + "@typescript-eslint/rule-tester": "^6.19.1", + "@typescript-eslint/utils": "^6.19.1", "all-contributors-cli": "6.26.1", "brotli": "1.3.3", "commitizen": "4.3.0", "concurrently": "^8.2.2", - "create-qwik": "workspace:*", + "create-qwik": "workspace:^", "cross-spawn": "7.0.3", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "cz-conventional-changelog": "3.3.0", - "esbuild": "^0.19.8", - "eslint": "^8.55.0", + "esbuild": "^0.19.12", + "eslint": "^8.56.0", "eslint-plugin-no-only-tests": "3.1.0", - "eslint-plugin-qwik": "^1.2.19", + "eslint-plugin-qwik": "workspace:^", "execa": "7.2.0", "express": "4.18.2", "install": "^0.13.0", - "monaco-editor": "^0.44.0", + "monaco-editor": "^0.45.0", "mri": "1.2.0", "ora": "6.3.1", "path-browserify": "1.0.1", - "prettier": "^3.1.0", - "prettier-plugin-jsdoc": "^1.1.1", - "pretty-quick": "^3.1.3", + "prettier": "^3.2.4", + "prettier-plugin-jsdoc": "^1.3.0", + "pretty-quick": "^4.0.0", "prompts": "2.4.2", - "rollup": "3.26.3", + "rollup": "^4.9.6", "semver": "7.5.4", "snoop": "^1.0.4", - "syncpack": "^10.7.3", - "terser": "^5.25.0", + "source-map": "0.7.4", + "svgo": "^3.2.0", + "syncpack": "^12.3.0", + "terser": "^5.27.0", "tsm": "^2.3.0", - "typescript": "^5.3.2", - "undici": "^5.26.0", - "vite": "^5.0.6", - "vite-tsconfig-paths": "^4.2.1", - "vitest": "^1.0.1", + "typescript": "5.3.3", + "undici": "*", + "vfile": "^6.0.1", + "vite": "^5.0.12", + "vite-imagetools": "^6.2.9", + "vite-tsconfig-paths": "^4.3.1", + "vitest": "^1.2.1", "watchlist": "0.3.1", "which-pm-runs": "1.1.0", "zod": "^3.22.4" @@ -95,10 +130,10 @@ "yarn": "please-use-pnpm", "pnpm": ">=8.6.12" }, - "packageManager": "pnpm@8.11.0", + "packageManager": "pnpm@8.14.0", "pnpm": { "overrides": { - "vfile": "6.0.1", + "vfile": "^6.0.1", "@supabase/realtime-js": "2.8.4" }, "patchedDependencies": { @@ -109,24 +144,28 @@ "scripts": { "api.update": "tsm scripts/index.ts --tsc --api --dev", "build": "tsm scripts/index.ts --tsc --build --qwikcity --qwiklabs --api --platform-binding-wasm-copy", + "build.clean": "rm -rf packages/qwik/dist/ && rm -rf packages/qwik-city/lib/ && rm -rf packages/qwik-city/lib/ && rm -rf packages/docs/dist/ && rm -rf packages/insights/dist/ && rm -rf packages/qwik-labs/lib/ && rm -rf packages/qwik-labs/lib-types/", "build.cli": "tsm scripts/index.ts --cli --dev", "build.cli.prod": "tsm scripts/index.ts --cli", - "build.core": "tsm scripts/index.ts --tsc --build --qwikcity --platform-binding-wasm-copy", + "build.core": "tsm scripts/index.ts --tsc --build --qwikcity --api --platform-binding-wasm-copy", "build.eslint": "tsm scripts/index.ts --eslint", - "build.full": "tsm scripts/index.ts --tsc --build --supabaseauthhelpers --api --eslint --qwikcity --qwikworker --qwiklabs --qwikreact --qwikauth --cli --platform-binding --wasm", + "build.full": "tsm scripts/index.ts --tsc --tsc-docs --build --supabaseauthhelpers --api --eslint --qwikcity --qwikworker --qwiklabs --qwikreact --qwikauth --cli --platform-binding --wasm", + "build.local": "tsm scripts/index.ts --tsc --tsc-docs --build --supabaseauthhelpers --api --eslint --qwikcity --qwikworker --qwiklabs --qwikreact --qwikauth --cli --platform-binding-wasm-copy", "build.only_javascript": "tsm scripts/index.ts --tsc --build --api", + "build.packages.docs": "pnpm run build.local && tsm ./scripts/patch-workspace-packages.ts && pnpm -C ./packages/docs/ run build", + "build.packages.insights": "pnpm run build.local && tsm ./scripts/patch-workspace-packages.ts && pnpm -C ./packages/insights/ run build", "build.platform": "tsm scripts/index.ts --platform-binding", "build.platform.copy": "tsm scripts/index.ts --platform-binding-wasm-copy", "build.qwik-city": "tsm scripts/index.ts --tsc --qwikcity", "build.validate": "tsm scripts/index.ts --tsc --build --api --eslint --qwikcity --platform-binding --wasm --validate", - "build.vite": "tsm scripts/index.ts --tsc --build --qwikcity --qwiklabs --platform-binding-wasm-copy", + "build.vite": "tsm scripts/index.ts --tsc --build --api --qwikcity --eslint --qwiklabs --platform-binding-wasm-copy", "build.wasm": "tsm scripts/index.ts --wasm", "build.watch": "tsm scripts/index.ts --build --qwikcity --watch --dev --platform-binding", "cli": "pnpm build.cli && node packages/create-qwik/dist/create-qwik.cjs && tsm scripts/validate-cli.ts --copy-local-qwik-dist", - "cli.qwik": "pnpm build.cli && node packages/qwik/dist/qwik.cjs", + "cli.qwik": "pnpm build.cli && node packages/qwik/qwik-cli.cjs", "cli.validate": "tsm scripts/validate-cli.ts", "commit": "git-cz", - "deps": "pnpm upgrade -i -r --latest", + "deps": "pnpm upgrade -i -r --latest && syncpack fix-mismatches", "docs.dev": "cd packages/docs && pnpm dev", "docs.preview": "cd packages/docs && pnpm preview", "docs.sync": "tsm scripts/docs_sync/index.ts && pnpm fmt", @@ -147,7 +186,7 @@ "release.prepare": "pnpm lint && pnpm test.unit --run && tsm scripts/index.ts --tsc --build --api --eslint --platform-binding --wasm --prepare-release", "serve": "tsm --inspect --conditions=development starters/dev-server.ts 3300", "serve.debug": "tsm --inspect-brk --conditions=development starters/dev-server.ts 3300", - "start": "concurrently \"npm:build.watch\" \"npm:test.watch\" \"npm:tsc.watch\" -n build,test,tsc -c green,magenta,cyan", + "start": "concurrently \"npm:build.watch\" \"npm:tsc.watch\" -n build,tsc -c green,cyan", "test": "pnpm build.full && pnpm test.unit && pnpm test.e2e", "test.e2e": "pnpm test.e2e.chromium && pnpm test.e2e.webkit", "test.e2e.chromium": "playwright test starters --browser=chromium --config starters/playwright.config.ts", diff --git a/packages/create-qwik/package.json b/packages/create-qwik/package.json index 587f1f1df674..a186de2d2d86 100644 --- a/packages/create-qwik/package.json +++ b/packages/create-qwik/package.json @@ -1,7 +1,7 @@ { "name": "create-qwik", "description": "Interactive CLI for create Qwik projects and adding features.", - "version": "1.3.0", + "version": "1.4.5", "author": "Builder.io Team", "bin": "./create-qwik.cjs", "bugs": "https://github.com/BuilderIO/qwik/issues", diff --git a/packages/create-qwik/src/helpers/jokes.json b/packages/create-qwik/src/helpers/jokes.json index 00416074beef..1fbb6468efb5 100644 --- a/packages/create-qwik/src/helpers/jokes.json +++ b/packages/create-qwik/src/helpers/jokes.json @@ -34,7 +34,6 @@ ["What did the fish say when it hit the wall?", "Dam."], ["Want to hear a joke about a piece of paper?", "Never mind...it's tearable"], ["What did the big flower say to the littler flower?", "Hi, bud!"], - ["What has ears but cannot hear?", "A field of corn."], ["What's the best thing about elevator jokes?", "They work on so many levels."], ["Why can't your nose be inches long?", "Because then it'd be a foot!"], ["Why does Superman get invited to dinners?", "Because he is a Supperhero."], diff --git a/packages/docs/.gitignore b/packages/docs/.gitignore index c8cde852d837..9d3903b727d0 100644 --- a/packages/docs/.gitignore +++ b/packages/docs/.gitignore @@ -4,6 +4,10 @@ dist server functions/**/*.js +# Bundled files for REPL +# Managed by check-qwik-build.ts +public/repl/bundled + !src/routes/api/qwik/server/ # Development diff --git a/packages/docs/.prettierignore b/packages/docs/.prettierignore index 54a20b8a6039..3fd66ad850ec 100644 --- a/packages/docs/.prettierignore +++ b/packages/docs/.prettierignore @@ -19,7 +19,6 @@ server .cache .vscode .rollup.cache -dist tsconfig.tsbuildinfo src/pages/**/*.mdx src/**/*.gen.* \ No newline at end of file diff --git a/packages/docs/check-qwik-build.ts b/packages/docs/check-qwik-build.ts new file mode 100644 index 000000000000..ab7cadb4b087 --- /dev/null +++ b/packages/docs/check-qwik-build.ts @@ -0,0 +1,45 @@ +// verify that ../qwik/dist/core.d.ts exists or run `pnpm run build.core` in the root directory +// we need it for development and for the REPL +import fs, { copyFileSync, mkdirSync } from 'fs'; +import path from 'path'; +import { spawnSync } from 'child_process'; +import { qwikFiles } from './src/repl/qwikFiles'; + +const __dirname = path.dirname(new URL(import.meta.url).pathname); +const qwikPkgDir = path.join(__dirname, '..', 'qwik', 'dist'); + +if (!fs.existsSync(path.join(qwikPkgDir, 'core.d.ts'))) { + console.warn( + `\n\n=== Running 'pnpm run build.local' to generate missing imports for the docs ===\n` + ); + const out = spawnSync('pnpm', ['run', 'build.local'], { + cwd: path.join(__dirname, '..', '..'), + stdio: 'inherit', + }); + if (out.status !== 0) { + console.error('Failed to build local packages'); + process.exit(1); + } +} + +// Copy the qwik files to public/ for the REPL +const qwikBundleDir = path.join(__dirname, 'public', 'repl', 'bundled', 'qwik'); +// We cheat, knowing that build/ is the deepest dir +mkdirSync(path.join(qwikBundleDir, 'build'), { recursive: true }); +for (const f of qwikFiles) { + const p = f.split('/'); + copyFileSync(path.join(qwikPkgDir, ...p), path.join(qwikBundleDir, ...p)); +} + +if (!fs.existsSync(path.join(__dirname, 'dist', 'repl', '~repl-server-host.js'))) { + console.warn( + `\n\n=== Running 'pnpm run build.client' to generate missing REPL service worker dist/repl/~repl-server-host.js ===\n` + ); + const out = spawnSync('pnpm', ['run', 'build.client'], { + stdio: 'inherit', + }); + if (out.status !== 0) { + console.error('Failed to build REPL service worker'); + process.exit(1); + } +} diff --git a/packages/docs/package.json b/packages/docs/package.json index fce245af5642..8bc08af6be56 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -7,48 +7,50 @@ "devDependencies": { "@algolia/autocomplete-core": "1.7.4", "@algolia/client-search": "4.14.3", - "@builder.io/partytown": "^0.8.1", - "@builder.io/qwik": "github:BuilderIo/qwik-build#0080db89d43112fea2ed6b281f70f61d00f42edd", - "@builder.io/qwik-city": "github:BuilderIo/qwik-city-build#fd39ca1abf09e4016ff738679030b2486ea5f7fc", - "@builder.io/qwik-labs": "github:BuilderIo/qwik-labs-build#c336fbf3ac62af6b056b8f7b203a6c1d48756f91", - "@builder.io/qwik-react": "0.5.0", - "@builder.io/sdk-qwik": "^0.6.2", + "@builder.io/partytown": "^0.9.0", + "@builder.io/qwik": "workspace:^", + "@builder.io/qwik-city": "workspace:^", + "@builder.io/qwik-labs": "workspace:^", + "@builder.io/qwik-react": "workspace:^", + "@builder.io/sdk-qwik": "^0.12.2", "@docsearch/css": "^3.5.2", - "@emotion/react": "^11.11.1", + "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", - "@modular-forms/qwik": "^0.21.0", - "@mui/material": "^5.14.13", - "@mui/x-data-grid": "^6.16.1", - "@supabase/supabase-js": "2.38.1", - "@types/prismjs": "^1.26.1", - "@types/react": "^18.2.28", - "@types/react-dom": "^18.2.13", - "@unpic/core": "^0.0.31", - "@unpic/qwik": "^0.0.27", + "@modular-forms/qwik": "^0.22.0", + "@mui/material": "^5.15.4", + "@mui/system": "^5.4.1", + "@mui/x-data-grid": "^6.18.7", + "@supabase/supabase-js": "^2.39.3", + "@types/prismjs": "^1.26.3", + "@types/react": "^18.2.48", + "@types/react-dom": "^18.2.18", + "@unpic/core": "^0.0.42", + "@unpic/qwik": "^0.0.38", "algoliasearch": "4.16.0", "autoprefixer": "^10.4.16", "fflate": "^0.8.1", "gray-matter": "4.0.3", "openai": "^3.3.0", - "postcss": "^8.4.31", - "prettier": "^3.0.3", + "postcss": "^8.4.33", + "prettier": "^3.2.4", "prism-themes": "1.9.0", "prismjs": "1.29.0", - "puppeteer": "^20.9.0", + "puppeteer": "^21.7.0", "qwik-image": "^0.0.8", "react": "18.2.0", "react-dom": "18.2.0", - "rehype-pretty-code": "^0.10.1", - "shiki": "^0.14.5", + "rehype-pretty-code": "^0.11.0", + "shiki": "^0.14.7", + "shikiji": "^0.7.0 || ^0.8.0 || ^0.9.0", "snarkdown": "^2.0.0", - "tailwindcss": "3.3.3", + "tailwindcss": "^3.4.1", "tsm": "^2.3.0", - "typescript": "^5.2.2", - "undici": "^5.26.0", - "valibot": "^0.17.1", - "vite": "^5.0.0", - "vite-plugin-inspect": "^0.7.42", - "wrangler": "^3.18.0" + "typescript": "5.3.3", + "undici": "*", + "valibot": "^0.25.0", + "vite": "^5.0.12", + "vite-plugin-inspect": "^0.8.1", + "wrangler": "^3.22.4" }, "engines": { "node": ">=18.11", @@ -58,12 +60,7 @@ }, "homepage": "https://qwik.builder.io/", "license": "MIT", - "packageManager": "pnpm@8.6.12", - "pnpm": { - "overrides": { - "@supabase/realtime-js": "2.8.4" - } - }, + "packageManager": "pnpm@8.14.0", "private": true, "scripts": { "build": "qwik build", @@ -74,8 +71,9 @@ "codesandbox.sync": "tsm codesandbox.sync.ts", "contributors": "tsm contributors.ts", "deploy": "wrangler pages publish ./dist", - "dev": "vite --mode ssr --open", + "dev": "tsm check-qwik-build.ts && vite --mode ssr --open", "dev.debug": "node --inspect-brk ../../node_modules/vite/bin/vite.js --mode ssr --force", + "prebuild.core": "tsm check-qwik-build.ts", "preview": "qwik build preview && vite preview --open", "preview.only": "NODE_DEBUG=net,http node --inspect-brk ../../node_modules/vite/bin/vite.js preview", "preview.wrangler": "wrangler pages dev ./dist", diff --git a/packages/docs/public/showcases/blueagle_top_.webp b/packages/docs/public/showcases/blueagle_top_.webp new file mode 100644 index 000000000000..7796758eb208 Binary files /dev/null and b/packages/docs/public/showcases/blueagle_top_.webp differ diff --git a/packages/docs/public/showcases/linkfang-portfolio_vercel_app_.webp b/packages/docs/public/showcases/linkfang-portfolio_vercel_app_.webp new file mode 100644 index 000000000000..d188a739995e Binary files /dev/null and b/packages/docs/public/showcases/linkfang-portfolio_vercel_app_.webp differ diff --git a/packages/docs/public/showcases/sasthyaseba_com.webp b/packages/docs/public/showcases/sasthyaseba_com.webp new file mode 100644 index 000000000000..b5695f7afdbb Binary files /dev/null and b/packages/docs/public/showcases/sasthyaseba_com.webp differ diff --git a/packages/docs/public/showcases/valibot_dev_.webp b/packages/docs/public/showcases/valibot_dev_.webp new file mode 100644 index 000000000000..75723b8665ad Binary files /dev/null and b/packages/docs/public/showcases/valibot_dev_.webp differ diff --git a/packages/docs/scripts/pages.json b/packages/docs/scripts/pages.json index 04044b11b64b..7b3c93ed0713 100644 --- a/packages/docs/scripts/pages.json +++ b/packages/docs/scripts/pages.json @@ -1,4 +1,17 @@ [ + { + "href": "https://sasthyaseba.com", + "size": "large", + "tags": "site,healthcare,services,webrtc,websocket" + }, + { + "href": "https://linkfang-portfolio.vercel.app/", + "tags": "portfolio" + }, + { + "href": "https://blueagle.top/", + "tags": "portfolio,site,blog" + }, { "href": "https://quotemingle.com/", "tags": "site" @@ -211,5 +224,9 @@ { "href": "https://juneikerc.com", "tags": "portfolio,site,blog,services" + }, + { + "href": "https://valibot.dev/", + "tags": "site,documentation" } ] diff --git a/packages/docs/src/components/builder-content/index.tsx b/packages/docs/src/components/builder-content/index.tsx index 1c8cbdf8e02b..bed010902e0e 100644 --- a/packages/docs/src/components/builder-content/index.tsx +++ b/packages/docs/src/components/builder-content/index.tsx @@ -119,10 +119,14 @@ export async function getBuilderContent({ qwikUrl.searchParams.set('cachebust', 'true'); } - const response = await fetch(qwikUrl.href); - if (response.ok) { - const content: BuilderContent = JSON.parse(await response.text()); - return content; + try { + const response = await fetch(qwikUrl.href); + if (response.ok) { + const content: BuilderContent = JSON.parse(await response.text()); + return content; + } + } catch (err) { + console.error(err); } - throw new Error(`Unable to load Builder content from ${qwikUrl.toString()}`); + return { html: `
Unable to load Builder content from ${qwikUrl.toString()}
` }; } diff --git a/packages/docs/src/components/docsearch/search-box.tsx b/packages/docs/src/components/docsearch/search-box.tsx index 268c12749549..d917fc2e439b 100644 --- a/packages/docs/src/components/docsearch/search-box.tsx +++ b/packages/docs/src/components/docsearch/search-box.tsx @@ -1,10 +1,4 @@ -import { - component$, - useVisibleTask$, - useContext, - type Signal, - type PropFunction, -} from '@builder.io/qwik'; +import { component$, useVisibleTask$, useContext, type Signal, type QRL } from '@builder.io/qwik'; import { MAX_QUERY_SIZE } from './constants'; import { SearchContext } from './context'; @@ -23,7 +17,7 @@ interface SearchBoxProps { state: DocSearchState; autoFocus: boolean; inputRef: Signal; - onClose$: PropFunction<() => void>; + onClose$: QRL<() => void>; } export const SearchBox = component$((props: SearchBoxProps) => { diff --git a/packages/docs/src/repl/bundled.tsx b/packages/docs/src/repl/bundled.tsx new file mode 100644 index 000000000000..791c1e25e024 --- /dev/null +++ b/packages/docs/src/repl/bundled.tsx @@ -0,0 +1,43 @@ +import { version as qwikVersion } from '../../../qwik'; + +import prettierPkgJson from 'prettier/package.json'; +import prettierParserHtml from 'prettier/plugins/html.js?url'; +import prettierStandaloneJs from 'prettier/standalone.js?url'; +import type { BundledFiles } from './types'; +import { qwikFiles } from './qwikFiles'; + +export const bundled: BundledFiles = { + '@builder.io/qwik': { + version: qwikVersion, + ...Object.fromEntries( + qwikFiles.map((f) => [`/${f}`, `${import.meta.env.BASE_URL}repl/bundled/qwik/${f}`]) + ), + }, + prettier: { + version: prettierPkgJson.version, + '/plugins/html.js': prettierParserHtml, + '/standalone.js': prettierStandaloneJs, + }, +}; + +export const getNpmCdnUrl = ( + bundled: BundledFiles, + pkgName: string, + pkgVersion: string, + pkgPath: string +) => { + if (pkgVersion === 'bundled') { + const files = bundled[pkgName]; + if (files) { + pkgVersion = files.version; + const url = files[pkgPath]; + if (url) { + return url; + } + } else { + // fall back to latest + pkgVersion = ''; + } + } + return `https://cdn.jsdelivr.net/npm/${pkgName}${pkgVersion ? '@' + pkgVersion : ''}${pkgPath}`; +}; diff --git a/packages/docs/src/repl/editor.tsx b/packages/docs/src/repl/editor.tsx index ffd2e06e5136..cd2d81c86d4c 100644 --- a/packages/docs/src/repl/editor.tsx +++ b/packages/docs/src/repl/editor.tsx @@ -1,7 +1,7 @@ import { component$, type NoSerialize, - type PropFunction, + type QRL, useVisibleTask$, useContext, useSignal, @@ -52,11 +52,11 @@ export const Editor = component$((props: EditorProps) => { }); useTask$(async ({ track }) => { - track(() => props.input.version); + const v = track(() => props.input.version); track(() => store.editor); - if (props.input.version && store.editor) { - await addQwikLibs(props.input.version); + if (v && store.editor) { + await addQwikLibs(v); } }); @@ -78,7 +78,7 @@ export interface EditorProps { input: ReplAppInput; ariaLabel: string; lineNumbers: 'on' | 'off'; - onChange$: PropFunction<(path: string, code: string) => void>; + onChange$: QRL<(path: string, code: string) => void>; wordWrap: 'on' | 'off'; store: ReplStore; } diff --git a/packages/docs/src/repl/monaco.tsx b/packages/docs/src/repl/monaco.tsx index 577ca45e79d8..0d2f58eba44d 100644 --- a/packages/docs/src/repl/monaco.tsx +++ b/packages/docs/src/repl/monaco.tsx @@ -4,6 +4,7 @@ import type MonacoTypes from 'monaco-editor'; import type { EditorProps, EditorStore } from './editor'; import type { ReplStore } from './types'; import { getColorPreference } from '../components/theme-toggle/theme-toggle'; +import { bundled, getNpmCdnUrl } from './bundled'; export const initMonacoEditor = async ( containerElm: any, @@ -15,6 +16,7 @@ export const initMonacoEditor = async ( const ts = monaco.languages.typescript; ts.typescriptDefaults.setCompilerOptions({ + ...ts.typescriptDefaults.getCompilerOptions(), allowJs: true, allowNonTsExtensions: true, esModuleInterop: true, @@ -25,7 +27,6 @@ export const initMonacoEditor = async ( noEmit: true, skipLibCheck: true, target: ts.ScriptTarget.Latest, - typeRoots: ['node_modules/@types'], }); ts.javascriptDefaults.setDiagnosticsOptions({ @@ -198,8 +199,11 @@ export const addQwikLibs = async (version: string) => { const deps = await loadDeps(version); deps.forEach((dep) => { - if (dep && typeof dep.code === 'string' && typeof dep.path === 'string') { - typescriptDefaults.addExtraLib(dep.code, `file://${dep.path}`); + if (dep && typeof dep.code === 'string') { + typescriptDefaults.addExtraLib( + `declare module '${dep.pkgName}${dep.import}' { ${dep.code}\n }`, + `/node_modules/${dep.pkgName}${dep.pkgPath}` + ); } }); @@ -208,29 +212,33 @@ export const addQwikLibs = async (version: string) => { const loadDeps = async (qwikVersion: string) => { const deps: NodeModuleDep[] = [ + // qwik { pkgName: '@builder.io/qwik', pkgVersion: qwikVersion, pkgPath: '/core.d.ts', - path: '/node_modules/@types/builder.io__qwik/index.d.ts', + import: '', }, + // JSX runtime { pkgName: '@builder.io/qwik', pkgVersion: qwikVersion, pkgPath: '/jsx-runtime.d.ts', - path: '/node_modules/@types/builder.io__qwik/jsx-runtime.d.ts', + import: '/jsx-runtime', }, + // server API { pkgName: '@builder.io/qwik', pkgVersion: qwikVersion, pkgPath: '/server.d.ts', - path: '/node_modules/@types/builder.io__qwik/server.d.ts', + import: '/server', }, + // build constants { pkgName: '@builder.io/qwik', pkgVersion: qwikVersion, pkgPath: '/build/index.d.ts', - path: '/node_modules/@types/builder.io__qwik/build/index.d.ts', + import: '/build', }, ]; @@ -247,7 +255,7 @@ const loadDeps = async (qwikVersion: string) => { pkgName: dep.pkgName, pkgVersion: dep.pkgVersion, pkgPath: dep.pkgPath, - path: dep.path, + import: dep.import, }; monacoCtx.deps.push(storedDep); @@ -266,18 +274,19 @@ const loadDeps = async (qwikVersion: string) => { }; const fetchDep = async (cache: Cache, dep: NodeModuleDep) => { - const url = getCdnUrl(dep.pkgName, dep.pkgVersion, dep.pkgPath); + const url = getNpmCdnUrl(bundled, dep.pkgName, dep.pkgVersion, dep.pkgPath); const req = new Request(url); const cachedRes = await cache.match(req); if (cachedRes) { - return cachedRes.clone().text(); + return cachedRes.text(); } const fetchRes = await fetch(req); if (fetchRes.ok) { - if (!req.url.includes('localhost')) { + // dev mode uses / and prod bundles use data: urls + if (/^(http|\/)/.test(req.url) && !req.url.includes('localhost')) { await cache.put(req, fetchRes.clone()); } - return fetchRes.clone().text(); + return fetchRes.text(); } throw new Error(`Unable to fetch: ${url}`); }; @@ -324,12 +333,8 @@ const monacoCtx: MonacoContext = { tsWorker: null, }; -const getCdnUrl = (pkgName: string, pkgVersion: string, pkgPath: string) => { - return `https://cdn.jsdelivr.net/npm/${pkgName}@${pkgVersion}${pkgPath}`; -}; - -const MONACO_VERSION = '0.33.0'; -const MONACO_VS_URL = getCdnUrl('monaco-editor', MONACO_VERSION, '/min/vs'); +const MONACO_VERSION = '0.45.0'; +const MONACO_VS_URL = getNpmCdnUrl(bundled, 'monaco-editor', MONACO_VERSION, '/min/vs'); const MONACO_LOADER_URL = `${MONACO_VS_URL}/loader.js`; const CLIENT_LIB = ` @@ -359,8 +364,8 @@ interface MonacoContext { interface NodeModuleDep { pkgName: string; pkgPath: string; + import: string; pkgVersion: string; - path: string; code?: string; promise?: Promise; } diff --git a/packages/docs/src/repl/qwikFiles.tsx b/packages/docs/src/repl/qwikFiles.tsx new file mode 100644 index 000000000000..e5625471d6b4 --- /dev/null +++ b/packages/docs/src/repl/qwikFiles.tsx @@ -0,0 +1,13 @@ +// Special case for qwik, we serve that from /public, populated by the prebuild script + +export const qwikFiles = [ + 'build/index.d.ts', + 'core.cjs', + 'core.d.ts', + 'core.min.mjs', + 'core.mjs', + 'jsx-runtime.d.ts', + 'optimizer.cjs', + 'server.cjs', + 'server.d.ts', +]; diff --git a/packages/docs/src/repl/repl-detail-panel.tsx b/packages/docs/src/repl/repl-detail-panel.tsx index cda5826f0431..b18bdcab17c3 100644 --- a/packages/docs/src/repl/repl-detail-panel.tsx +++ b/packages/docs/src/repl/repl-detail-panel.tsx @@ -1,3 +1,4 @@ +import { bundled } from './bundled'; import { ReplConsole } from './repl-console'; import { ReplOptions } from './repl-options'; import { ReplTabButton } from './repl-tab-button'; @@ -27,7 +28,11 @@ export const ReplDetailPanel = ({ input, store }: ReplDetailPanelProps) => {
{store.selectedOutputDetail === 'console' ? : null} {store.selectedOutputDetail === 'options' ? ( - + ) : null}
diff --git a/packages/docs/src/repl/repl-input-panel.tsx b/packages/docs/src/repl/repl-input-panel.tsx index 19b95d1719f5..dab4e48331ee 100644 --- a/packages/docs/src/repl/repl-input-panel.tsx +++ b/packages/docs/src/repl/repl-input-panel.tsx @@ -1,4 +1,4 @@ -import type { PropFunction } from '@builder.io/qwik'; +import type { QRL } from '@builder.io/qwik'; import { Editor } from './editor'; import { ReplCommands } from './repl-commands'; import { ReplTabButton } from './repl-tab-button'; @@ -65,8 +65,8 @@ const formatFilePath = (path: string) => { interface ReplInputPanelProps { input: ReplAppInput; store: ReplStore; - onInputChange$: PropFunction<(path: string, code: string) => void>; - onInputDelete$: PropFunction<(path: string) => void>; + onInputChange$: QRL<(path: string, code: string) => void>; + onInputDelete$: QRL<(path: string) => void>; enableDownload?: boolean; enableCopyToPlayground?: boolean; enableInputDelete?: boolean; diff --git a/packages/docs/src/repl/repl-options.tsx b/packages/docs/src/repl/repl-options.tsx index 760d2ba4cb32..22f38d49063d 100644 --- a/packages/docs/src/repl/repl-options.tsx +++ b/packages/docs/src/repl/repl-options.tsx @@ -1,6 +1,6 @@ import type { ReplAppInput } from './types'; -export const ReplOptions = ({ input, versions }: ReplOptionsProps) => { +export const ReplOptions = ({ input, versions, qwikVersion }: ReplOptionsProps) => { return (
{ label="Version" inputProp="version" options={versions} + labels={{ bundled: qwikVersion }} input={input} isLoading={versions.length === 0} /> @@ -58,7 +59,7 @@ const StoreOption = (props: StoreOptionProps) => { selected={value === props.input[props.inputProp] ? true : undefined} key={value} > - {value} + {props.labels?.[value] || value} ))} {props.isLoading ? : null} @@ -74,6 +75,7 @@ export const ENTRY_STRATEGY_OPTIONS = ['component', 'hook', 'single', 'smart', ' interface StoreOptionProps { label: string; options: string[]; + labels?: { [value: string]: string }; input: ReplAppInput; inputProp: keyof ReplAppInput; isLoading?: boolean; @@ -88,4 +90,5 @@ interface StoreBooleanProps { interface ReplOptionsProps { input: ReplAppInput; versions: string[]; + qwikVersion: string; } diff --git a/packages/docs/src/repl/repl-share-url.ts b/packages/docs/src/repl/repl-share-url.ts index 59f27997447d..490e1af6058a 100644 --- a/packages/docs/src/repl/repl-share-url.ts +++ b/packages/docs/src/repl/repl-share-url.ts @@ -15,9 +15,8 @@ export const parsePlaygroundShareUrl = (shareable: string) => { const data = { ...dataDefaults }; const version = params.get('v')! || params.get('version')!; - if (typeof version === 'string' && version.split('.').length > 2) { - data.version = version; - } + data.version = + typeof version === 'string' && version.split('.').length > 2 ? version : 'bundled'; const buildMode = params.get('buildMode')!; if (BUILD_MODE_OPTIONS.includes(buildMode)) { @@ -117,7 +116,9 @@ export const dictionary = strToU8( export const createPlaygroundShareUrl = (data: PlaygroundShareUrl, pathname = '/playground/') => { const params = new URLSearchParams(); - params.set('v', data.version); + if (data.version !== 'bundled') { + params.set('v', data.version); + } if (data.buildMode !== dataDefaults.buildMode) { params.set('buildMode', data.buildMode); } diff --git a/packages/docs/src/repl/repl-tab-button.tsx b/packages/docs/src/repl/repl-tab-button.tsx index 080b54464f02..16274c8cac8d 100644 --- a/packages/docs/src/repl/repl-tab-button.tsx +++ b/packages/docs/src/repl/repl-tab-button.tsx @@ -1,7 +1,7 @@ -import type { PropFunction } from '@builder.io/qwik'; +import type { PropsOf, Component } from '@builder.io/qwik'; import { CloseIcon } from '../components/svgs/close-icon'; -export const ReplTabButton = (props: ReplTabButtonProps) => { +export const ReplTabButton: Component = (props) => { return (
{ interface ReplTabButtonProps { text: string; isActive: boolean; - onClick$: PropFunction<() => void>; - onClose$?: PropFunction<() => void>; + onClick$: PropsOf<'button'>['onClick$']; + onClose$?: PropsOf<'button'>['onClick$']; cssClass?: Record; enableInputDelete?: boolean; } diff --git a/packages/docs/src/repl/repl-version.ts b/packages/docs/src/repl/repl-version.ts index e1115d1e9e70..a5131192dd21 100644 --- a/packages/docs/src/repl/repl-version.ts +++ b/packages/docs/src/repl/repl-version.ts @@ -1,21 +1,24 @@ /* eslint-disable no-console */ +import { bundled } from './bundled'; + +const bundledVersion = bundled['@builder.io/qwik'].version; // The golden oldies -const keepList = new Set('1.0.0,1.1.5'.split(',')); -// The bad apples +const keepList = new Set('1.0.0,1.1.5,1.2.13'.split(',')); + +// The bad apples - add versions that break the REPL here const blockList = new Set( '1.2.0,1.2.1,1.2.2,1.2.3,1.2.4,1.2.5,1.2.6,1.2.7,1.2.8,1.2.9,1.2.10,1.2.11,1.2.14,1.2.15,1.3.0'.split( ',' ) ); -export const getReplVersion = async (version: string | undefined) => { - let versions: string[] = []; +export const getReplVersion = async (version: string | undefined, offline: boolean) => { let npmData: NpmData | null = null; try { npmData = JSON.parse(localStorage.getItem(NPM_STORAGE_KEY)!); - if (isExpiredNpmData(npmData)) { + if (!offline && isExpiredNpmData(npmData)) { // fetch most recent NPM version data console.debug(`Qwik REPL, fetch npm data: ${QWIK_NPM_DATA}`); const npmRsp = await fetch(QWIK_NPM_DATA); @@ -29,82 +32,74 @@ export const getReplVersion = async (version: string | undefined) => { } catch (e) { console.warn('getReplVersion', e); } + const npmVersions = npmData?.versions || []; - if (npmData && Array.isArray(npmData.versions)) { - versions = npmData.versions.filter((v) => { - if (keepList.has(v)) { - // always include keepList, but we add them back later - return false; - } - if (v === version) { - return true; - } - if (blockList.has(v)) { - // always exclude blockList - return false; - } - if (npmData?.tags.latest === v) { - // always include "latest" - return true; - } - if (v.includes('-')) { - // filter out dev builds - return false; - } - const parts = v.split('.'); - if (parts.length !== 3) { - // invalid, must have 3 parts - return false; - } - if (isNaN(parts[2] as any)) { - // last part cannot have letters in it - return false; - } - // mini-semver check, must be >= than 0.0.100 - if (parts[0] === '0' && parts[1] === '0') { - if (parseInt(parts[2], 10) < 100) { - return false; - } - } + let hasVersion = false; + let versions = npmVersions.filter((v) => { + if (keepList.has(v) || v === bundledVersion) { + // always include keepList, but we add them back later + return false; + } + if (v === version) { + hasVersion = true; return true; - }); - - if (versions.length > 20 - keepList.size) { - versions = versions.slice(0, 20 - keepList.size); } - versions.unshift(...keepList); - // sort by version number - versions.sort((a, b) => { - const aParts = a.split('.'); - const bParts = b.split('.'); - for (let i = 0; i < 3; i++) { - const aNum = parseInt(aParts[i], 10); - const bNum = parseInt(bParts[i], 10); - if (aNum > bNum) { - return -1; - } - if (aNum < bNum) { - return 1; - } + if (blockList.has(v)) { + // always exclude blockList + return false; + } + if (npmData?.tags.latest === v) { + // always include "latest" + return true; + } + if (v.includes('-')) { + // filter out dev builds + return false; + } + const parts = v.split('.'); + if (parts.length !== 3) { + // invalid, must have 3 parts + return false; + } + if (isNaN(parts[2] as any)) { + // last part cannot have letters in it + return false; + } + // mini-semver check, must be >= than 0.0.100 + if (parts[0] === '0' && parts[1] === '0') { + if (parseInt(parts[2], 10) < 100) { + return false; } - return 0; - }); - - if (!version || !npmData.versions.includes(version)) { - version = versions[0]; } + return true; + }); + if (versions.length > 19 - keepList.size) { + versions = versions.slice(0, 19 - keepList.size); } - - if (!npmData) { - console.debug(`Qwik REPL, npm data not found`); - } - - if (!Array.isArray(versions) || versions.length === 0) { - console.debug(`Qwik REPL, versions not found`); + versions.unshift(...keepList); + if (hasVersion && !versions.includes(version!)) { + versions.push(version!); } + // sort by version number + versions.sort((a, b) => { + const aParts = a.split('.'); + const bParts = b.split('.'); + for (let i = 0; i < 3; i++) { + const aNum = parseInt(aParts[i], 10); + const bNum = parseInt(bParts[i], 10); + if (aNum > bNum) { + return -1; + } + if (aNum < bNum) { + return 1; + } + } + return 0; + }); - if (!version) { - console.debug(`Qwik REPL, version not found`); + versions.unshift('bundled'); + if (!hasVersion || !version) { + version = 'bundled'; } return { version, versions }; diff --git a/packages/docs/src/repl/repl.tsx b/packages/docs/src/repl/repl.tsx index 9111e9aed01e..cfae3bfbd64a 100644 --- a/packages/docs/src/repl/repl.tsx +++ b/packages/docs/src/repl/repl.tsx @@ -15,6 +15,7 @@ import type { ReplStore, ReplUpdateMessage, ReplMessage, ReplAppInput } from './ import { ReplDetailPanel } from './repl-detail-panel'; import { getReplVersion } from './repl-version'; import { updateReplOutput } from './repl-output-update'; +import { bundled } from './bundled'; export const Repl = component$((props: ReplProps) => { useStyles$(styles); @@ -76,19 +77,27 @@ export const Repl = component$((props: ReplProps) => { } }); - useVisibleTask$(async () => { - // only run on the client - const v = await getReplVersion(input.version); - if (v.version) { + useVisibleTask$( + async () => { + // only run on the client + // Get the version asap, most likely it will be cached. + const v = await getReplVersion(input.version, true); store.versions = v.versions; input.version = v.version; store.serverUrl = new URL(`/repl/~repl-server-host.html?${store.clientId}`, origin).href; - window.addEventListener('message', (ev) => receiveMessageFromReplServer(ev, store)); - } else { - console.debug(`Qwik REPL version not set`); - } - }); + window.addEventListener('message', (ev) => receiveMessageFromReplServer(ev, store, input)); + + // Now get the version from the network + const vNew = await getReplVersion(input.version, false); + store.versions = vNew.versions; + if (vNew.version !== input.version) { + input.version = v.version; + sendUserUpdateToReplServer(input, store); + } + }, + { strategy: 'document-idle' } + ); useTask$(({ track }) => { track(() => input.buildId); @@ -118,23 +127,34 @@ export const Repl = component$((props: ReplProps) => { ); }); -export const receiveMessageFromReplServer = (ev: MessageEvent, store: ReplStore) => { +export const receiveMessageFromReplServer = ( + ev: MessageEvent, + store: ReplStore, + input: ReplAppInput +) => { + if (ev.origin !== window.origin) { + return; + } const msg: ReplMessage = ev.data; - const type = msg?.type; - const clientId = msg?.clientId; - if (clientId === store.clientId) { - if (type === 'replready') { - // keep a reference to the repl server window - store.serverWindow = noSerialize(ev.source as any); - } else if (type === 'result') { - // received a message from the server - updateReplOutput(store, msg); - } else if (type === 'event') { - // received an event from the user's app - store.events = [...store.events, msg.event]; - } else if (type === 'apploaded') { - store.isLoading = false; - } + + if (!(msg && msg.type && msg.clientId === store.clientId)) { + return; + } + const type = msg.type; + if (type === 'replready') { + // keep a reference to the repl server window + store.serverWindow = noSerialize(ev.source as any); + sendUserUpdateToReplServer(input, store); + } else if (type === 'result') { + // received a message from the server + updateReplOutput(store, msg); + } else if (type === 'event') { + // received an event from the user's app + store.events = [...store.events, msg.event]; + } else if (type === 'apploaded') { + store.isLoading = false; + } else { + console.log('unknown repl message', msg); } }; @@ -153,6 +173,7 @@ export const sendUserUpdateToReplServer = (input: ReplAppInput, store: ReplStore }, version: input.version, serverUrl: store.serverUrl, + bundled, }, }; diff --git a/packages/docs/src/repl/types.ts b/packages/docs/src/repl/types.ts index 04a5471fc9a6..b2414fce3193 100644 --- a/packages/docs/src/repl/types.ts +++ b/packages/docs/src/repl/types.ts @@ -15,12 +15,14 @@ export interface ReplAppInput { debug?: boolean; } +export type BundledFiles = { [pkgName: string]: { [path: string]: string; version: string } }; export interface ReplInputOptions extends Omit { buildId: number; srcInputs: ReplModuleInput[]; version: string; buildMode: 'development' | 'production'; serverUrl: string; + bundled: BundledFiles; } export interface ReplStore { diff --git a/packages/docs/src/repl/worker/repl-constants.ts b/packages/docs/src/repl/worker/repl-constants.ts index d59873746367..fe0c487fc8f8 100644 --- a/packages/docs/src/repl/worker/repl-constants.ts +++ b/packages/docs/src/repl/worker/repl-constants.ts @@ -1,6 +1,5 @@ export const QWIK_PKG_NAME = '@builder.io/qwik'; export const ROLLUP_VERSION = '2.75.6'; -export const PRETTIER_VERSION = '2.7.1'; export const TERSER_VERSION = '5.14.1'; export const QWIK_REPL_DEPS_CACHE = 'QwikReplDeps'; diff --git a/packages/docs/src/repl/worker/repl-dependencies.ts b/packages/docs/src/repl/worker/repl-dependencies.ts index 90e2d908b2e2..17e92926a36c 100644 --- a/packages/docs/src/repl/worker/repl-dependencies.ts +++ b/packages/docs/src/repl/worker/repl-dependencies.ts @@ -1,7 +1,6 @@ /* eslint-disable no-console */ -import type { ReplInputOptions } from '../types'; +import type { BundledFiles, ReplInputOptions } from '../types'; import { - PRETTIER_VERSION, QWIK_PKG_NAME, QWIK_REPL_DEPS_CACHE, ROLLUP_VERSION, @@ -9,12 +8,33 @@ import { } from './repl-constants'; import type { QwikWorkerGlobal } from './repl-service-worker'; -export const depResponse = async ( - cache: Cache, +let options: ReplInputOptions; +let cache: Cache; + +// Copied from bundled.tsx. Can't import it because it breaks the sw bundle. +const getNpmCdnUrl = ( + bundled: BundledFiles, pkgName: string, pkgVersion: string, pkgPath: string ) => { + if (pkgVersion === 'bundled') { + const files = bundled[pkgName]; + if (files) { + pkgVersion = files.version; + const url = files[pkgPath]; + if (url) { + return url; + } + } else { + // fall back to latest + pkgVersion = ''; + } + } + return `https://cdn.jsdelivr.net/npm/${pkgName}${pkgVersion ? '@' + pkgVersion : ''}${pkgPath}`; +}; + +export const depResponse = async (pkgName: string, pkgVersion: string, pkgPath: string) => { const req = getNpmCdnRequest(pkgName, pkgVersion, pkgPath); const cachedRes = await cache.match(req); if (cachedRes) { @@ -22,7 +42,7 @@ export const depResponse = async ( } const fetchRes = await fetch(req); if (fetchRes.ok) { - if (!req.url.includes('localhost')) { + if (/^(http|\/)/.test(req.url) && !req.url.includes('localhost')) { await cache.put(req, fetchRes.clone()); } return fetchRes; @@ -30,31 +50,33 @@ export const depResponse = async ( }; const exec = async (cache: Cache, pkgName: string, pkgVersion: string, pkgPath: string) => { - const res = await depResponse(cache, pkgName, pkgVersion, pkgPath); + const res = await depResponse(pkgName, pkgVersion, pkgPath); if (res) { console.debug(`Run: ${res.url}`); // eslint-disable-next-line no-new-func const run = new Function(await res.clone().text()); run(); } else { - throw new Error(`Unable to run: ${getNpmCdnUrl(pkgName, pkgVersion, pkgPath)}`); + throw new Error( + `Unable to run: ${getNpmCdnUrl(options.bundled, pkgName, pkgVersion, pkgPath)}` + ); } }; -export const loadDependencies = async (options: ReplInputOptions) => { - const version = options.version; - - const cache = await caches.open(QWIK_REPL_DEPS_CACHE); +export const loadDependencies = async (replOptions: ReplInputOptions) => { + options = replOptions; + const qwikVersion = options.version; + cache = await caches.open(QWIK_REPL_DEPS_CACHE); await Promise.all([ - depResponse(cache, QWIK_PKG_NAME, version, '/core.cjs'), - depResponse(cache, QWIK_PKG_NAME, version, '/core.mjs'), - depResponse(cache, QWIK_PKG_NAME, version, '/core.min.mjs'), - depResponse(cache, QWIK_PKG_NAME, version, '/optimizer.cjs'), - depResponse(cache, QWIK_PKG_NAME, version, '/server.cjs'), - depResponse(cache, 'rollup', ROLLUP_VERSION, '/dist/rollup.browser.js'), - depResponse(cache, 'prettier', PRETTIER_VERSION, '/standalone.js'), - depResponse(cache, 'prettier', PRETTIER_VERSION, '/parser-html.js'), + depResponse(QWIK_PKG_NAME, qwikVersion, '/core.cjs'), + depResponse(QWIK_PKG_NAME, qwikVersion, '/core.mjs'), + depResponse(QWIK_PKG_NAME, qwikVersion, '/core.min.mjs'), + depResponse(QWIK_PKG_NAME, qwikVersion, '/optimizer.cjs'), + depResponse(QWIK_PKG_NAME, qwikVersion, '/server.cjs'), + depResponse('rollup', ROLLUP_VERSION, '/dist/rollup.browser.js'), + depResponse('prettier', 'bundled', '/standalone.js'), + depResponse('prettier', 'bundled', '/plugins/html.js'), ]); self.qwikBuild = { @@ -63,30 +85,30 @@ export const loadDependencies = async (options: ReplInputOptions) => { isDev: false, }; - if (!isSameQwikVersion(self.qwikCore?.version, version)) { - await exec(cache, QWIK_PKG_NAME, version, '/core.cjs'); + if (!isSameQwikVersion(self.qwikCore?.version, qwikVersion)) { + await exec(cache, QWIK_PKG_NAME, qwikVersion, '/core.cjs'); if (self.qwikCore) { console.debug(`Loaded @builder.io/qwik: ${self.qwikCore.version}`); } else { - throw new Error(`Unable to load @builder.io/qwik ${version}`); + throw new Error(`Unable to load @builder.io/qwik ${qwikVersion}`); } } - if (!isSameQwikVersion(self.qwikOptimizer?.versions.qwik, version)) { - await exec(cache, QWIK_PKG_NAME, version, '/optimizer.cjs'); + if (!isSameQwikVersion(self.qwikOptimizer?.versions.qwik, qwikVersion)) { + await exec(cache, QWIK_PKG_NAME, qwikVersion, '/optimizer.cjs'); if (self.qwikOptimizer) { console.debug(`Loaded @builder.io/qwik/optimizer: ${self.qwikOptimizer.versions.qwik}`); } else { - throw new Error(`Unable to load @builder.io/qwik/optimizer ${version}`); + throw new Error(`Unable to load @builder.io/qwik/optimizer ${qwikVersion}`); } } - if (!isSameQwikVersion(self.qwikServer?.versions.qwik, version)) { - await exec(cache, QWIK_PKG_NAME, version, '/server.cjs'); + if (!isSameQwikVersion(self.qwikServer?.versions.qwik, qwikVersion)) { + await exec(cache, QWIK_PKG_NAME, qwikVersion, '/server.cjs'); if (self.qwikServer) { console.debug(`Loaded @builder.io/qwik/server: ${self.qwikServer.versions.qwik}`); } else { - throw new Error(`Unable to load @builder.io/qwik/server ${version}`); + throw new Error(`Unable to load @builder.io/qwik/server ${qwikVersion}`); } } @@ -99,18 +121,18 @@ export const loadDependencies = async (options: ReplInputOptions) => { } } - if (self.prettier?.version !== PRETTIER_VERSION) { - await exec(cache, 'prettier', PRETTIER_VERSION, '/standalone.js'); - await exec(cache, 'prettier', PRETTIER_VERSION, '/parser-html.js'); + if (!self.prettier) { + await exec(cache, 'prettier', 'bundled', '/standalone.js'); + await exec(cache, 'prettier', 'bundled', '/plugins/html.js'); if (self.prettier) { - console.debug(`Loaded prettier: ${self.prettier!.version}`); + console.debug(`Loaded prettier: ${(self.prettier as any)!.version}`); } else { - throw new Error(`Unable to load prettier ${PRETTIER_VERSION}`); + throw new Error(`Unable to load prettier`); } } if (options.buildMode === 'production' && !self.Terser) { - await depResponse(cache, 'terser', TERSER_VERSION, '/dist/bundle.min.js'); + await depResponse('terser', TERSER_VERSION, '/dist/bundle.min.js'); await exec(cache, 'terser', TERSER_VERSION, '/dist/bundle.min.js'); if (self.Terser) { console.debug(`Loaded terser: ${TERSER_VERSION}`); @@ -131,15 +153,14 @@ export const loadDependencies = async (options: ReplInputOptions) => { }; const getNpmCdnRequest = (pkgName: string, pkgVersion: string, pkgPath: string) => { - return new Request(getNpmCdnUrl(pkgName, pkgVersion, pkgPath)); -}; - -const getNpmCdnUrl = (pkgName: string, pkgVersion: string, pkgPath: string) => { - return `https://cdn.jsdelivr.net/npm/${pkgName}${pkgVersion ? '@' + pkgVersion : ''}${pkgPath}`; + return new Request(getNpmCdnUrl(options.bundled, pkgName, pkgVersion, pkgPath)); }; const isSameQwikVersion = (a: string | undefined, b: string) => { - if (!a || (a !== b && !a.includes('-dev') && !b.includes('-dev'))) { + if (b === 'bundled') { + b = options.bundled['@builder.io/qwik'].version; + } + if (!a || a !== b) { return false; } return true; diff --git a/packages/docs/src/repl/worker/repl-plugins.ts b/packages/docs/src/repl/worker/repl-plugins.ts index e8d7247649e8..dbd0c874272b 100644 --- a/packages/docs/src/repl/worker/repl-plugins.ts +++ b/packages/docs/src/repl/worker/repl-plugins.ts @@ -4,7 +4,6 @@ import type { QwikWorkerGlobal } from './repl-service-worker'; import type { MinifyOptions } from 'terser'; import type { ReplInputOptions } from '../types'; import { depResponse } from './repl-dependencies'; -import { QWIK_REPL_DEPS_CACHE } from './repl-constants'; export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 'ssr'): Plugin => { const srcInputs = options.srcInputs; @@ -76,20 +75,14 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's `; } if (id === '\0qwikCore') { - const cache = await caches.open(QWIK_REPL_DEPS_CACHE); if (options.buildMode === 'production') { - const rsp = await depResponse( - cache, - '@builder.io/qwik', - options.version, - '/core.min.mjs' - ); + const rsp = await depResponse('@builder.io/qwik', options.version, '/core.min.mjs'); if (rsp) { return rsp.clone().text(); } } - const rsp = await depResponse(cache, '@builder.io/qwik', options.version, '/core.mjs'); + const rsp = await depResponse('@builder.io/qwik', options.version, '/core.mjs'); if (rsp) { return rsp.clone().text(); } diff --git a/packages/docs/src/repl/worker/repl-server.ts b/packages/docs/src/repl/worker/repl-server.ts index fe84b10eebab..09060e6cdf56 100644 --- a/packages/docs/src/repl/worker/repl-server.ts +++ b/packages/docs/src/repl/worker/repl-server.ts @@ -24,6 +24,7 @@ export const initReplServer = (win: Window, doc: Document, nav: Navigator) => { iframe.classList.add('loading'); iframe.src = `/repl/` + result.clientId + `/`; iframe.dataset.buildId = String(result.buildId); + iframe.setAttribute('sandbox', 'allow-popups allow-modals allow-scripts allow-same-origin'); iframe.addEventListener('load', () => { if (!iframe.nextElementSibling) { @@ -46,6 +47,9 @@ export const initReplServer = (win: Window, doc: Document, nav: Navigator) => { }; const receiveMessageFromMainApp = (ev: MessageEvent) => { + if (ev.origin !== win.location.origin) { + return; + } if (swRegistration && swRegistration.active) { try { if (ev.data) { @@ -74,6 +78,9 @@ export const initReplServer = (win: Window, doc: Document, nav: Navigator) => { }; const receiveMessageFromUserApp = (ev: MessageEvent) => { + if (ev.origin !== win.location.origin) { + return; + } if (ev.data) { const msg: ReplMessage = JSON.parse(ev.data); if (msg?.type === 'event') { diff --git a/packages/docs/src/routes/(ecosystem)/showcase/generated-pages.json b/packages/docs/src/routes/(ecosystem)/showcase/generated-pages.json index a1d0f641f352..2bf19cd36f05 100644 --- a/packages/docs/src/routes/(ecosystem)/showcase/generated-pages.json +++ b/packages/docs/src/routes/(ecosystem)/showcase/generated-pages.json @@ -1,4 +1,53 @@ [ + { + "title": "Book Doctor Appointment and Ambulance Service Online | Sasthya Seba", + "imgSrc": "/showcases/sasthyaseba_com.webp", + "perf": { + "score": 0.98, + "fcpDisplay": "1.4 s", + "fcpScore": 0.97, + "lcpDisplay": "2.3 s", + "lcpScore": 0.93, + "ttiDisplay": "1.4 s", + "ttiScore": 1, + "ttiTime": 1418.935 + }, + "href": "https://sasthyaseba.com", + "size": "large", + "tags": "site,healthcare,services,webrtc,websocket" + }, + { + "title": "Wep Apps | Zhou's Portfolio", + "imgSrc": "/showcases/linkfang-portfolio_vercel_app_.webp", + "perf": { + "score": 0.98, + "fcpDisplay": "1.4 s", + "fcpScore": 0.97, + "lcpDisplay": "2.2 s", + "lcpScore": 0.94, + "ttiDisplay": "1.4 s", + "ttiScore": 1, + "ttiTime": 1386 + }, + "href": "https://linkfang-portfolio.vercel.app/", + "tags": "portfolio" + }, + { + "title": "Blueagle", + "imgSrc": "/showcases/blueagle_top_.webp", + "perf": { + "score": 0.98, + "fcpDisplay": "1.6 s", + "fcpScore": 0.94, + "lcpDisplay": "2.0 s", + "lcpScore": 0.97, + "ttiDisplay": "2.2 s", + "ttiScore": 0.99, + "ttiTime": 2237.5 + }, + "href": "https://blueagle.top/", + "tags": "portfolio,site,blog" + }, { "title": "QuoteMingle", "imgSrc": "/showcases/quotemingle_com_.webp", @@ -823,5 +872,21 @@ }, "href": "https://juneikerc.com", "tags": "portfolio,site,blog,services" + }, + { + "title": "Valibot: The modular and type safe schema library", + "imgSrc": "/showcases/valibot_dev_.webp", + "perf": { + "score": 1, + "fcpDisplay": "1.1 s", + "fcpScore": 0.99, + "lcpDisplay": "1.7 s", + "lcpScore": 0.99, + "ttiDisplay": "1.1 s", + "ttiScore": 1, + "ttiTime": 1107 + }, + "href": "https://valibot.dev/", + "tags": "site,documentation" } ] diff --git a/packages/docs/src/routes/api/qwik-city-middleware-aws-lambda/api.json b/packages/docs/src/routes/api/qwik-city-middleware-aws-lambda/api.json new file mode 100644 index 000000000000..bf78ddbd441b --- /dev/null +++ b/packages/docs/src/routes/api/qwik-city-middleware-aws-lambda/api.json @@ -0,0 +1,48 @@ +{ + "id": "qwik-city-middleware-aws-lambda", + "package": "@builder.io/qwik-city/middleware/aws-lambda", + "members": [ + { + "name": "createQwikCity", + "id": "createqwikcity", + "hierarchy": [ + { + "name": "createQwikCity", + "id": "createqwikcity" + } + ], + "kind": "Function", + "content": "```typescript\nexport declare function createQwikCity(opts: AwsOpt): {\n fixPath: (pathT: string) => string;\n router: (req: import(\"http\").IncomingMessage | import(\"http2\").Http2ServerRequest, res: import(\"http\").ServerResponse, next: import(\"@builder.io/qwik-city/middleware/node\").NodeRequestNextFunction) => Promise;\n staticFile: (req: import(\"http\").IncomingMessage | import(\"http2\").Http2ServerRequest, res: import(\"http\").ServerResponse, next: (e?: any) => void) => Promise;\n notFound: (req: import(\"http\").IncomingMessage | import(\"http2\").Http2ServerRequest, res: import(\"http\").ServerResponse, next: (e: any) => void) => Promise;\n handle: (req: any, res: any) => void;\n};\n```\n\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| opts | AwsOpt | |\n\n**Returns:**\n\n{ fixPath: (pathT: string) => string; router: (req: import(\"http\").IncomingMessage \\| import(\"http2\").Http2ServerRequest, res: import(\"http\").ServerResponse<import(\"http\").IncomingMessage>, next: import(\"@builder.io/qwik-city/middleware/node\").NodeRequestNextFunction) => Promise<void>; staticFile: (req: import(\"http\").IncomingMessage \\| import(\"http2\").Http2ServerRequest, res: import(\"http\").ServerResponse<import(\"http\").IncomingMessage>, next: (e?: any) => void) => Promise<void>; notFound: (req: import(\"http\").IncomingMessage \\| import(\"http2\").Http2ServerRequest, res: import(\"http\").ServerResponse<import(\"http\").IncomingMessage>, next: (e: any) => void) => Promise<void>; handle: (req: any, res: any) => void; }", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/middleware/aws-lambda/index.ts", + "mdFile": "qwik-city.createqwikcity.md" + }, + { + "name": "PlatformAwsLambda", + "id": "platformawslambda", + "hierarchy": [ + { + "name": "PlatformAwsLambda", + "id": "platformawslambda" + } + ], + "kind": "Interface", + "content": "```typescript\nexport interface PlatformAwsLambda extends Object \n```\n**Extends:** Object", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/middleware/aws-lambda/index.ts", + "mdFile": "qwik-city.platformawslambda.md" + }, + { + "name": "QwikCityAwsLambdaOptions", + "id": "qwikcityawslambdaoptions", + "hierarchy": [ + { + "name": "QwikCityAwsLambdaOptions", + "id": "qwikcityawslambdaoptions" + } + ], + "kind": "Interface", + "content": "```typescript\nexport interface QwikCityAwsLambdaOptions extends ServerRenderOptions \n```\n**Extends:** ServerRenderOptions", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/middleware/aws-lambda/index.ts", + "mdFile": "qwik-city.qwikcityawslambdaoptions.md" + } + ] +} \ No newline at end of file diff --git a/packages/docs/src/routes/api/qwik-city-middleware-aws-lambda/index.md b/packages/docs/src/routes/api/qwik-city-middleware-aws-lambda/index.md new file mode 100644 index 000000000000..2fc27ed38a4d --- /dev/null +++ b/packages/docs/src/routes/api/qwik-city-middleware-aws-lambda/index.md @@ -0,0 +1,59 @@ +--- +title: \@builder.io/qwik-city/middleware/aws-lambda API Reference +--- + +# [API](/api) › @builder.io/qwik-city/middleware/aws-lambda + +## createQwikCity + +```typescript +export declare function createQwikCity(opts: AwsOpt): { + fixPath: (pathT: string) => string; + router: ( + req: import("http").IncomingMessage | import("http2").Http2ServerRequest, + res: import("http").ServerResponse, + next: import("@builder.io/qwik-city/middleware/node").NodeRequestNextFunction, + ) => Promise; + staticFile: ( + req: import("http").IncomingMessage | import("http2").Http2ServerRequest, + res: import("http").ServerResponse, + next: (e?: any) => void, + ) => Promise; + notFound: ( + req: import("http").IncomingMessage | import("http2").Http2ServerRequest, + res: import("http").ServerResponse, + next: (e: any) => void, + ) => Promise; + handle: (req: any, res: any) => void; +}; +``` + +| Parameter | Type | Description | +| --------- | ------ | ----------- | +| opts | AwsOpt | | + +**Returns:** + +{ fixPath: (pathT: string) => string; router: (req: import("http").IncomingMessage \| import("http2").Http2ServerRequest, res: import("http").ServerResponse<import("http").IncomingMessage>, next: import("@builder.io/qwik-city/middleware/node").NodeRequestNextFunction) => Promise<void>; staticFile: (req: import("http").IncomingMessage \| import("http2").Http2ServerRequest, res: import("http").ServerResponse<import("http").IncomingMessage>, next: (e?: any) => void) => Promise<void>; notFound: (req: import("http").IncomingMessage \| import("http2").Http2ServerRequest, res: import("http").ServerResponse<import("http").IncomingMessage>, next: (e: any) => void) => Promise<void>; handle: (req: any, res: any) => void; } + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/middleware/aws-lambda/index.ts) + +## PlatformAwsLambda + +```typescript +export interface PlatformAwsLambda extends Object +``` + +**Extends:** Object + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/middleware/aws-lambda/index.ts) + +## QwikCityAwsLambdaOptions + +```typescript +export interface QwikCityAwsLambdaOptions extends ServerRenderOptions +``` + +**Extends:** ServerRenderOptions + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/middleware/aws-lambda/index.ts) diff --git a/packages/docs/src/routes/api/qwik-city-middleware-request-handler/api.json b/packages/docs/src/routes/api/qwik-city-middleware-request-handler/api.json index 81fb2a7cb9b7..5663846b1989 100644 --- a/packages/docs/src/routes/api/qwik-city-middleware-request-handler/api.json +++ b/packages/docs/src/routes/api/qwik-city-middleware-request-handler/api.json @@ -68,7 +68,7 @@ } ], "kind": "Interface", - "content": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie\n\n\n```typescript\nexport interface CookieOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [domain?](#) | | string | _(Optional)_ Defines the host to which the cookie will be sent. If omitted, this attribute defaults to the host of the current document URL, not including subdomains. |\n| [expires?](#) | | Date \\| string | _(Optional)_ Indicates the maximum lifetime of the cookie as an HTTP-date timestamp. If both expires and maxAge are set, maxAge has precedence. |\n| [httpOnly?](#) | | boolean | _(Optional)_ Forbids JavaScript from accessing the cookie, for example, through the document.cookie property. |\n| [maxAge?](#) | | number \\| \\[number, 'seconds' \\| 'minutes' \\| 'hours' \\| 'days' \\| 'weeks'\\] | _(Optional)_ Indicates the number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. If both expires and maxAge are set, maxAge has precedence. You can also use the array syntax to set the max-age using minutes, hours, days or weeks. For example, { maxAge: [3, "days"] } would set the cookie to expire in 3 days. |\n| [path?](#) | | string | _(Optional)_ Indicates the path that must exist in the requested URL for the browser to send the Cookie header. |\n| [sameSite?](#) | | 'strict' \\| 'lax' \\| 'none' \\| boolean | _(Optional)_ Controls whether or not a cookie is sent with cross-site requests, providing some protection against cross-site request forgery attacks (CSRF). |\n| [secure?](#) | | boolean | _(Optional)_ Indicates that the cookie is sent to the server only when a request is made with the https: scheme (except on localhost) |", + "content": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie\n\n\n```typescript\nexport interface CookieOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [domain?](#) | | string | _(Optional)_ Defines the host to which the cookie will be sent. If omitted, this attribute defaults to the host of the current document URL, not including subdomains. |\n| [expires?](#) | | Date \\| string | _(Optional)_ Indicates the maximum lifetime of the cookie as an HTTP-date timestamp. If both expires and maxAge are set, maxAge has precedence. |\n| [httpOnly?](#) | | boolean | _(Optional)_ Forbids JavaScript from accessing the cookie, for example, through the document.cookie property. |\n| [maxAge?](#) | | number \\| \\[number, 'seconds' \\| 'minutes' \\| 'hours' \\| 'days' \\| 'weeks'\\] | _(Optional)_ Indicates the number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. If both expires and maxAge are set, maxAge has precedence. You can also use the array syntax to set the max-age using minutes, hours, days or weeks. For example, { maxAge: [3, "days"] } would set the cookie to expire in 3 days. |\n| [path?](#) | | string | _(Optional)_ Indicates the path that must exist in the requested URL for the browser to send the Cookie header. |\n| [sameSite?](#) | | 'strict' \\| 'lax' \\| 'none' \\| 'Strict' \\| 'Lax' \\| 'None' \\| boolean | _(Optional)_ Controls whether or not a cookie is sent with cross-site requests, providing some protection against cross-site request forgery attacks (CSRF). |\n| [secure?](#) | | boolean | _(Optional)_ Indicates that the cookie is sent to the server only when a request is made with the https: scheme (except on localhost) |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/middleware/request-handler/types.ts", "mdFile": "qwik-city.cookieoptions.md" }, diff --git a/packages/docs/src/routes/api/qwik-city-middleware-request-handler/index.md b/packages/docs/src/routes/api/qwik-city-middleware-request-handler/index.md index a8f8a763517b..1dd1bbbfd297 100644 --- a/packages/docs/src/routes/api/qwik-city-middleware-request-handler/index.md +++ b/packages/docs/src/routes/api/qwik-city-middleware-request-handler/index.md @@ -74,7 +74,7 @@ export interface CookieOptions | [httpOnly?](#) | | boolean | _(Optional)_ Forbids JavaScript from accessing the cookie, for example, through the document.cookie property. | | [maxAge?](#) | | number \| [number, 'seconds' \| 'minutes' \| 'hours' \| 'days' \| 'weeks'] | _(Optional)_ Indicates the number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. If both expires and maxAge are set, maxAge has precedence. You can also use the array syntax to set the max-age using minutes, hours, days or weeks. For example, { maxAge: [3, "days"] } would set the cookie to expire in 3 days. | | [path?](#) | | string | _(Optional)_ Indicates the path that must exist in the requested URL for the browser to send the Cookie header. | -| [sameSite?](#) | | 'strict' \| 'lax' \| 'none' \| boolean | _(Optional)_ Controls whether or not a cookie is sent with cross-site requests, providing some protection against cross-site request forgery attacks (CSRF). | +| [sameSite?](#) | | 'strict' \| 'lax' \| 'none' \| 'Strict' \| 'Lax' \| 'None' \| boolean | _(Optional)_ Controls whether or not a cookie is sent with cross-site requests, providing some protection against cross-site request forgery attacks (CSRF). | | [secure?](#) | | boolean | _(Optional)_ Indicates that the cookie is sent to the server only when a request is made with the https: scheme (except on localhost) | [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/middleware/request-handler/types.ts) diff --git a/packages/docs/src/routes/api/qwik-city/api.json b/packages/docs/src/routes/api/qwik-city/api.json index cece6c1e16b6..84a47358c7ab 100644 --- a/packages/docs/src/routes/api/qwik-city/api.json +++ b/packages/docs/src/routes/api/qwik-city/api.json @@ -2,6 +2,23 @@ "id": "qwik-city", "package": "@builder.io/qwik-city", "members": [ + { + "name": "\"link:app\"", + "id": "linkprops-_link_app_", + "hierarchy": [ + { + "name": "LinkProps", + "id": "linkprops-_link_app_" + }, + { + "name": "\"link:app\"", + "id": "linkprops-_link_app_" + } + ], + "kind": "PropertySignature", + "content": "```typescript\n'link:app'?: boolean;\n```", + "mdFile": "qwik-city.linkprops._link_app_.md" + }, { "name": "Action", "id": "action", @@ -236,7 +253,7 @@ } ], "kind": "Variable", - "content": "```typescript\nForm: ({ action, spaReset, reloadDocument, onSubmit$, ...rest }: FormProps, key: string | null) => QwikJSX.Element\n```", + "content": "```typescript\nForm: ({ action, spaReset, reloadDocument, onSubmit$, ...rest }: FormProps, key: string | null) => import(\"@builder.io/qwik\").JSXOutput\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/form-component.tsx", "mdFile": "qwik-city.form.md" }, @@ -348,7 +365,7 @@ } ], "kind": "Variable", - "content": "```typescript\nLink: import(\"@builder.io/qwik\").Component>\n```", + "content": "```typescript\nLink: import(\"@builder.io/qwik\").Component\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/link-component.tsx", "mdFile": "qwik-city.link.md" }, @@ -362,7 +379,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface LinkProps extends AnchorAttributes \n```\n**Extends:** AnchorAttributes\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [prefetch?](#) | | boolean | _(Optional)_ |\n| [reload?](#) | | boolean | _(Optional)_ |\n| [replaceState?](#) | | boolean | _(Optional)_ |\n| [scroll?](#) | | boolean | _(Optional)_ |", + "content": "```typescript\nexport interface LinkProps extends AnchorAttributes \n```\n**Extends:** AnchorAttributes\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [\"link:app\"?](#linkprops-_link_app_) | | boolean | _(Optional)_ |\n| [prefetch?](#) | | boolean \\| 'js' |

_(Optional)_ \\*\\*Defaults to \\_true\\_.\\*\\*

Whether Qwik should prefetch and cache the target page of this \\*\\*Link\\*\\*, this includes invoking any \\*\\*routeLoader$\\*\\*, \\*\\*onGet\\*\\*, etc.

This \\*\\*improves UX performance\\*\\* for client-side (\\*\\*SPA\\*\\*) navigations.

Prefetching occurs when a the Link enters the viewport in production (\\*\\*on:qvisibile\\*\\*), or with \\*\\*mouseover/focus\\*\\* during dev.

Prefetching will not occur if the user has the \\*\\*data saver\\*\\* setting enabled.

Setting this value to \\*\\*"js"\\*\\* will prefetch only javascript bundles required to render this page on the client, \\*\\*false\\*\\* will disable prefetching altogether.

|\n| [reload?](#) | | boolean | _(Optional)_ |\n| [replaceState?](#) | | boolean | _(Optional)_ |\n| [scroll?](#) | | boolean | _(Optional)_ |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/link-component.tsx", "mdFile": "qwik-city.linkprops.md" }, @@ -460,7 +477,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikCityMockProps \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [params?](#) | | Record<string, string> | _(Optional)_ |\n| [url?](#) | | string | _(Optional)_ |", + "content": "```typescript\nexport interface QwikCityMockProps \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [goto?](#) | | [RouteNavigate](#routenavigate) | _(Optional)_ |\n| [params?](#) | | Record<string, string> | _(Optional)_ |\n| [url?](#) | | string | _(Optional)_ |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/qwik-city-component.tsx", "mdFile": "qwik-city.qwikcitymockprops.md" }, @@ -474,7 +491,7 @@ } ], "kind": "Variable", - "content": "```typescript\nQwikCityMockProvider: import(\"@builder.io/qwik\").Component>\n```", + "content": "```typescript\nQwikCityMockProvider: import(\"@builder.io/qwik\").Component\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/qwik-city-component.tsx", "mdFile": "qwik-city.qwikcitymockprovider.md" }, @@ -516,7 +533,7 @@ } ], "kind": "Variable", - "content": "```typescript\nQwikCityProvider: import(\"@builder.io/qwik\").Component>\n```", + "content": "```typescript\nQwikCityProvider: import(\"@builder.io/qwik\").Component\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/qwik-city-component.tsx", "mdFile": "qwik-city.qwikcityprovider.md" }, @@ -642,7 +659,7 @@ } ], "kind": "Variable", - "content": "```typescript\nRouterOutlet: import(\"@builder.io/qwik\").Component>>\n```", + "content": "```typescript\nRouterOutlet: import(\"@builder.io/qwik\").Component\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/router-outlet-component.tsx", "mdFile": "qwik-city.routeroutlet.md" }, @@ -660,6 +677,20 @@ "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/server-functions.ts", "mdFile": "qwik-city.server_.md" }, + { + "name": "ServerFunction", + "id": "serverfunction", + "hierarchy": [ + { + "name": "ServerFunction", + "id": "serverfunction" + } + ], + "kind": "TypeAlias", + "content": "```typescript\nexport type ServerFunction = {\n (this: RequestEventBase, ...args: any[]): any;\n};\n```", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/types.ts", + "mdFile": "qwik-city.serverfunction.md" + }, { "name": "serverQrl", "id": "serverqrl", @@ -674,6 +705,20 @@ "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/server-functions.ts", "mdFile": "qwik-city.serverqrl.md" }, + { + "name": "ServerQRL", + "id": "serverqrl", + "hierarchy": [ + { + "name": "ServerQRL", + "id": "serverqrl" + } + ], + "kind": "TypeAlias", + "content": "```typescript\nserverQrl: (qrl: QRL) => ServerQRL\n```", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/types.ts", + "mdFile": "qwik-city.serverqrl.md" + }, { "name": "ServiceWorkerRegister", "id": "serviceworkerregister", diff --git a/packages/docs/src/routes/api/qwik-city/index.md b/packages/docs/src/routes/api/qwik-city/index.md index 705cb9b3c664..d1da8b4b72a5 100644 --- a/packages/docs/src/routes/api/qwik-city/index.md +++ b/packages/docs/src/routes/api/qwik-city/index.md @@ -4,6 +4,12 @@ title: \@builder.io/qwik-city API Reference # [API](/api) › @builder.io/qwik-city +## "link:app" + +```typescript +'link:app'?: boolean; +``` + ## Action ```typescript @@ -358,7 +364,7 @@ export type FailReturn = T & Failed; Form: ( { action, spaReset, reloadDocument, onSubmit$, ...rest }: FormProps, key: string | null, -) => QwikJSX.Element; +) => import("@builder.io/qwik").JSXOutput; ``` [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/form-component.tsx) @@ -454,9 +460,7 @@ export type JSONValue = ## Link ```typescript -Link: import("@builder.io/qwik").Component< - import("@builder.io/qwik").PropFunctionProps ->; +Link: import("@builder.io/qwik").Component; ``` [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/link-component.tsx) @@ -469,12 +473,13 @@ export interface LinkProps extends AnchorAttributes **Extends:** AnchorAttributes -| Property | Modifiers | Type | Description | -| ------------------ | --------- | ------- | ------------ | -| [prefetch?](#) | | boolean | _(Optional)_ | -| [reload?](#) | | boolean | _(Optional)_ | -| [replaceState?](#) | | boolean | _(Optional)_ | -| [scroll?](#) | | boolean | _(Optional)_ | +| Property | Modifiers | Type | Description | +| ------------------------------------ | --------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ["link:app"?](#linkprops-_link_app_) | | boolean | _(Optional)_ | +| [prefetch?](#) | | boolean \| 'js' |

_(Optional)_ \*\*Defaults to \_true\_.\*\*

Whether Qwik should prefetch and cache the target page of this \*\*Link\*\*, this includes invoking any \*\*routeLoader$\*\*, \*\*onGet\*\*, etc.

This \*\*improves UX performance\*\* for client-side (\*\*SPA\*\*) navigations.

Prefetching occurs when a the Link enters the viewport in production (\*\*on:qvisibile\*\*), or with \*\*mouseover/focus\*\* during dev.

Prefetching will not occur if the user has the \*\*data saver\*\* setting enabled.

Setting this value to \*\*"js"\*\* will prefetch only javascript bundles required to render this page on the client, \*\*false\*\* will disable prefetching altogether.

| +| [reload?](#) | | boolean | _(Optional)_ | +| [replaceState?](#) | | boolean | _(Optional)_ | +| [scroll?](#) | | boolean | _(Optional)_ | [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/link-component.tsx) @@ -549,19 +554,18 @@ export declare type PathParams = Record; export interface QwikCityMockProps ``` -| Property | Modifiers | Type | Description | -| ------------ | --------- | ---------------------------- | ------------ | -| [params?](#) | | Record<string, string> | _(Optional)_ | -| [url?](#) | | string | _(Optional)_ | +| Property | Modifiers | Type | Description | +| ------------ | --------- | ------------------------------- | ------------ | +| [goto?](#) | | [RouteNavigate](#routenavigate) | _(Optional)_ | +| [params?](#) | | Record<string, string> | _(Optional)_ | +| [url?](#) | | string | _(Optional)_ | [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/qwik-city-component.tsx) ## QwikCityMockProvider ```typescript -QwikCityMockProvider: import("@builder.io/qwik").Component< - import("@builder.io/qwik").PropFunctionProps ->; +QwikCityMockProvider: import("@builder.io/qwik").Component; ``` [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/qwik-city-component.tsx) @@ -598,9 +602,7 @@ export interface QwikCityProps ## QwikCityProvider ```typescript -QwikCityProvider: import("@builder.io/qwik").Component< - import("@builder.io/qwik").PropFunctionProps ->; +QwikCityProvider: import("@builder.io/qwik").Component; ``` [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/qwik-city-component.tsx) @@ -704,9 +706,7 @@ export type RouteNavigate = QRL< ## RouterOutlet ```typescript -RouterOutlet: import("@builder.io/qwik").Component< - import("@builder.io/qwik").PropFunctionProps> ->; +RouterOutlet: import("@builder.io/qwik").Component; ``` [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/router-outlet-component.tsx) @@ -719,6 +719,16 @@ server$: (first: T) => ServerQRL; [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/server-functions.ts) +## ServerFunction + +```typescript +export type ServerFunction = { + (this: RequestEventBase, ...args: any[]): any; +}; +``` + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/types.ts) + ## serverQrl ```typescript @@ -727,6 +737,14 @@ serverQrl: (qrl: QRL) => ServerQRL; [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/server-functions.ts) +## ServerQRL + +```typescript +serverQrl: (qrl: QRL) => ServerQRL; +``` + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik-city/runtime/src/types.ts) + ## ServiceWorkerRegister ```typescript diff --git a/packages/docs/src/routes/api/qwik-optimizer/api.json b/packages/docs/src/routes/api/qwik-optimizer/api.json index 90e1028d6331..b85a633cec2e 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/api.json +++ b/packages/docs/src/routes/api/qwik-optimizer/api.json @@ -299,7 +299,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface OptimizerOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [binding?](#) | | any | _(Optional)_ |\n| [inlineStylesUpToBytes?](#) | | number | _(Optional)_ |\n| [sys?](#) | | [OptimizerSystem](#optimizersystem) | _(Optional)_ |", + "content": "```typescript\nexport interface OptimizerOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [binding?](#) | | any | _(Optional)_ |\n| [inlineStylesUpToBytes?](#) | | number | _(Optional)_ Inline the global styles if they're smaller than this |\n| [sourcemap?](#) | | boolean | _(Optional)_ Enable sourcemaps |\n| [sys?](#) | | [OptimizerSystem](#optimizersystem) | _(Optional)_ |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.optimizeroptions.md" }, @@ -428,7 +428,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikRollupPluginOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [buildMode?](#) | | [QwikBuildMode](#qwikbuildmode) |

_(Optional)_ Build production or development.

Default development

|\n| [csr?](#) | | boolean | _(Optional)_ |\n| [debug?](#) | | boolean |

_(Optional)_ Prints verbose Qwik plugin debug logs.

Default false

|\n| [entryStrategy?](#) | | [EntryStrategy](#entrystrategy) |

_(Optional)_ The Qwik entry strategy to use while building for production. During development the type is always hook.

Default { type: "smart" })

|\n| [manifestInput?](#) | | [QwikManifest](#qwikmanifest) |

_(Optional)_ The SSR build requires the manifest generated during the client build. The manifestInput option can be used to manually provide a manifest.

Default undefined

|\n| [manifestOutput?](#) | | (manifest: [QwikManifest](#qwikmanifest)) => Promise<void> \\| void |

_(Optional)_ The client build will create a manifest and this hook is called with the generated build data.

Default undefined

|\n| [optimizerOptions?](#) | | [OptimizerOptions](#optimizeroptions) | _(Optional)_ |\n| [rootDir?](#) | | string |

_(Optional)_ The root of the application, which is commonly the same directory as package.json and rollup.config.js.

Default process.cwd()

|\n| [srcDir?](#) | | string |

_(Optional)_ The source directory to find all the Qwik components. Since Qwik does not have a single input, the srcDir is used to recursively find Qwik files.

Default src

|\n| [srcInputs?](#) | | [TransformModuleInput](#transformmoduleinput)\\[\\] \\| null |

_(Optional)_ Alternative to srcDir, where srcInputs is able to provide the files manually. This option is useful for an environment without a file system, such as a webworker.

Default: null

|\n| [target?](#) | | [QwikBuildTarget](#qwikbuildtarget) |

_(Optional)_ Target client or ssr.

Default client

|\n| [transformedModuleOutput?](#) | | ((transformedModules: [TransformModule](#transformmodule)\\[\\]) => Promise<void> \\| void) \\| null | _(Optional)_ Hook that's called after the build and provides all of the transformed modules that were used before bundling. |", + "content": "```typescript\nexport interface QwikRollupPluginOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [buildMode?](#) | | [QwikBuildMode](#qwikbuildmode) |

_(Optional)_ Build production or development.

Default development

|\n| [csr?](#) | | boolean | _(Optional)_ |\n| [debug?](#) | | boolean |

_(Optional)_ Prints verbose Qwik plugin debug logs.

Default false

|\n| [entryStrategy?](#) | | [EntryStrategy](#entrystrategy) |

_(Optional)_ The Qwik entry strategy to use while building for production. During development the type is always hook.

Default { type: "smart" })

|\n| [lint?](#) | | boolean | _(Optional)_ Run eslint on the source files for the ssr build or dev server. This can slow down startup on large projects. Defaults to true |\n| [manifestInput?](#) | | [QwikManifest](#qwikmanifest) |

_(Optional)_ The SSR build requires the manifest generated during the client build. The manifestInput option can be used to manually provide a manifest.

Default undefined

|\n| [manifestOutput?](#) | | (manifest: [QwikManifest](#qwikmanifest)) => Promise<void> \\| void |

_(Optional)_ The client build will create a manifest and this hook is called with the generated build data.

Default undefined

|\n| [optimizerOptions?](#) | | [OptimizerOptions](#optimizeroptions) | _(Optional)_ |\n| [rootDir?](#) | | string |

_(Optional)_ The root of the application, which is commonly the same directory as package.json and rollup.config.js.

Default process.cwd()

|\n| [srcDir?](#) | | string |

_(Optional)_ The source directory to find all the Qwik components. Since Qwik does not have a single input, the srcDir is used to recursively find Qwik files.

Default src

|\n| [srcInputs?](#) | | [TransformModuleInput](#transformmoduleinput)\\[\\] \\| null |

_(Optional)_ Alternative to srcDir, where srcInputs is able to provide the files manually. This option is useful for an environment without a file system, such as a webworker.

Default: null

|\n| [target?](#) | | [QwikBuildTarget](#qwikbuildtarget) |

_(Optional)_ Target client or ssr.

Default client

|\n| [transformedModuleOutput?](#) | | ((transformedModules: [TransformModule](#transformmodule)\\[\\]) => Promise<void> \\| void) \\| null | _(Optional)_ Hook that's called after the build and provides all of the transformed modules that were used before bundling. |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/optimizer/src/plugins/rollup.ts", "mdFile": "qwik.qwikrolluppluginoptions.md" }, diff --git a/packages/docs/src/routes/api/qwik-optimizer/index.md b/packages/docs/src/routes/api/qwik-optimizer/index.md index f43276e217dd..450b54e7b8c9 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/index.md +++ b/packages/docs/src/routes/api/qwik-optimizer/index.md @@ -281,11 +281,12 @@ export interface Optimizer export interface OptimizerOptions ``` -| Property | Modifiers | Type | Description | -| --------------------------- | --------- | ----------------------------------- | ------------ | -| [binding?](#) | | any | _(Optional)_ | -| [inlineStylesUpToBytes?](#) | | number | _(Optional)_ | -| [sys?](#) | | [OptimizerSystem](#optimizersystem) | _(Optional)_ | +| Property | Modifiers | Type | Description | +| --------------------------- | --------- | ----------------------------------- | ------------------------------------------------------------------ | +| [binding?](#) | | any | _(Optional)_ | +| [inlineStylesUpToBytes?](#) | | number | _(Optional)_ Inline the global styles if they're smaller than this | +| [sourcemap?](#) | | boolean | _(Optional)_ Enable sourcemaps | +| [sys?](#) | | [OptimizerSystem](#optimizersystem) | _(Optional)_ | [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts) @@ -436,6 +437,7 @@ export interface QwikRollupPluginOptions | [csr?](#) | | boolean | _(Optional)_ | | [debug?](#) | | boolean |

_(Optional)_ Prints verbose Qwik plugin debug logs.

Default false

| | [entryStrategy?](#) | | [EntryStrategy](#entrystrategy) |

_(Optional)_ The Qwik entry strategy to use while building for production. During development the type is always hook.

Default { type: "smart" })

| +| [lint?](#) | | boolean | _(Optional)_ Run eslint on the source files for the ssr build or dev server. This can slow down startup on large projects. Defaults to true | | [manifestInput?](#) | | [QwikManifest](#qwikmanifest) |

_(Optional)_ The SSR build requires the manifest generated during the client build. The manifestInput option can be used to manually provide a manifest.

Default undefined

| | [manifestOutput?](#) | | (manifest: [QwikManifest](#qwikmanifest)) => Promise<void> \| void |

_(Optional)_ The client build will create a manifest and this hook is called with the generated build data.

Default undefined

| | [optimizerOptions?](#) | | [OptimizerOptions](#optimizeroptions) | _(Optional)_ | diff --git a/packages/docs/src/routes/api/qwik-server/api.json b/packages/docs/src/routes/api/qwik-server/api.json index 8ce2abec9f3b..0085d30f9e7b 100644 --- a/packages/docs/src/routes/api/qwik-server/api.json +++ b/packages/docs/src/routes/api/qwik-server/api.json @@ -12,10 +12,24 @@ } ], "kind": "Function", - "content": "Provides the qwikloader.js file as a string. Useful for tooling to inline the qwikloader script into HTML.\n\n\n```typescript\nexport declare function getQwikLoaderScript(opts?: {\n events?: string[];\n debug?: boolean;\n}): string;\n```\n\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| opts | { events?: string\\[\\]; debug?: boolean; } | _(Optional)_ |\n\n**Returns:**\n\nstring", + "content": "Provides the `qwikloader.js` file as a string. Useful for tooling to inline the qwikloader script into HTML.\n\n\n```typescript\nexport declare function getQwikLoaderScript(opts?: {\n events?: string[];\n debug?: boolean;\n}): string;\n```\n\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| opts | { events?: string\\[\\]; debug?: boolean; } | _(Optional)_ |\n\n**Returns:**\n\nstring", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/server/scripts.ts", "mdFile": "qwik.getqwikloaderscript.md" }, + { + "name": "getQwikPrefetchWorkerScript", + "id": "getqwikprefetchworkerscript", + "hierarchy": [ + { + "name": "getQwikPrefetchWorkerScript", + "id": "getqwikprefetchworkerscript" + } + ], + "kind": "Function", + "content": "Provides the `qwik-prefetch-service-worker.js` file as a string. Useful for tooling to inline the qwikloader script into HTML.\n\n\n```typescript\nexport declare function getQwikPrefetchWorkerScript(opts?: {\n debug?: boolean;\n}): string;\n```\n\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| opts | { debug?: boolean; } | _(Optional)_ |\n\n**Returns:**\n\nstring", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/server/scripts.ts", + "mdFile": "qwik.getqwikprefetchworkerscript.md" + }, { "name": "InOrderAuto", "id": "inorderauto", @@ -110,7 +124,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikLoaderOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [events?](#) | | string\\[\\] | _(Optional)_ |\n| [include?](#) | | 'always' \\| 'never' \\| 'auto' | _(Optional)_ |\n| [position?](#) | | 'top' \\| 'bottom' | _(Optional)_ |", + "content": "```typescript\nexport interface QwikLoaderOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [include?](#) | | 'always' \\| 'never' \\| 'auto' | _(Optional)_ |\n| [position?](#) | | 'top' \\| 'bottom' | _(Optional)_ |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/server/types.ts", "mdFile": "qwik.qwikloaderoptions.md" }, @@ -138,7 +152,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface RenderOptions extends SerializeDocumentOptions \n```\n**Extends:** [SerializeDocumentOptions](#serializedocumentoptions)\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [base?](#) | | string \\| ((options: [RenderOptions](#renderoptions)) => string) | _(Optional)_ Specifies the root of the JS files of the client build. Setting a base, will cause the render of the q:base attribute in the q:container element. |\n| [containerAttributes?](#) | | Record<string, string> | _(Optional)_ |\n| [containerTagName?](#) | | string | _(Optional)_ When set, the app is serialized into a fragment. And the returned html is not a complete document. Defaults to html |\n| [locale?](#) | | string \\| ((options: [RenderOptions](#renderoptions)) => string) | _(Optional)_ Language to use when rendering the document. |\n| [prefetchStrategy?](#) | | [PrefetchStrategy](#prefetchstrategy) \\| null | _(Optional)_ |\n| [qwikLoader?](#) | | [QwikLoaderOptions](#qwikloaderoptions) |

_(Optional)_ Specifies if the Qwik Loader script is added to the document or not.

Defaults to { include: true }.

|\n| [serverData?](#) | | Record<string, any> | _(Optional)_ |\n| [snapshot?](#) | | boolean | _(Optional)_ Defaults to true |", + "content": "```typescript\nexport interface RenderOptions extends SerializeDocumentOptions \n```\n**Extends:** [SerializeDocumentOptions](#serializedocumentoptions)\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [base?](#) | | string \\| ((options: [RenderOptions](#renderoptions)) => string) | _(Optional)_ Specifies the root of the JS files of the client build. Setting a base, will cause the render of the q:base attribute in the q:container element. |\n| [containerAttributes?](#) | | Record<string, string> | _(Optional)_ |\n| [containerTagName?](#) | | string | _(Optional)_ When set, the app is serialized into a fragment. And the returned html is not a complete document. Defaults to html |\n| [locale?](#) | | string \\| ((options: [RenderOptions](#renderoptions)) => string) | _(Optional)_ Language to use when rendering the document. |\n| [prefetchStrategy?](#) | | [PrefetchStrategy](#prefetchstrategy) \\| null | _(Optional)_ |\n| [qwikLoader?](#) | | [QwikLoaderOptions](#qwikloaderoptions) |

_(Optional)_ Specifies if the Qwik Loader script is added to the document or not.

Defaults to { include: true }.

|\n| [qwikPrefetchServiceWorker?](#) | | QwikPrefetchServiceWorkerOptions |

_(Optional)_ Specifies if the Qwik Prefetch Service Worker script is added to the document or not.

Defaults to { include: false }. NOTE: This may be change in the future.

|\n| [serverData?](#) | | Record<string, any> | _(Optional)_ |\n| [snapshot?](#) | | boolean | _(Optional)_ Defaults to true |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/server/types.ts", "mdFile": "qwik.renderoptions.md" }, diff --git a/packages/docs/src/routes/api/qwik-server/index.md b/packages/docs/src/routes/api/qwik-server/index.md index 83e6fd6b4817..0a30850c2663 100644 --- a/packages/docs/src/routes/api/qwik-server/index.md +++ b/packages/docs/src/routes/api/qwik-server/index.md @@ -6,7 +6,7 @@ title: \@builder.io/qwik/server API Reference ## getQwikLoaderScript -Provides the qwikloader.js file as a string. Useful for tooling to inline the qwikloader script into HTML. +Provides the `qwikloader.js` file as a string. Useful for tooling to inline the qwikloader script into HTML. ```typescript export declare function getQwikLoaderScript(opts?: { @@ -25,6 +25,26 @@ string [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/server/scripts.ts) +## getQwikPrefetchWorkerScript + +Provides the `qwik-prefetch-service-worker.js` file as a string. Useful for tooling to inline the qwikloader script into HTML. + +```typescript +export declare function getQwikPrefetchWorkerScript(opts?: { + debug?: boolean; +}): string; +``` + +| Parameter | Type | Description | +| --------- | -------------------- | ------------ | +| opts | { debug?: boolean; } | _(Optional)_ | + +**Returns:** + +string + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/server/scripts.ts) + ## InOrderAuto ```typescript @@ -118,7 +138,6 @@ export interface QwikLoaderOptions | Property | Modifiers | Type | Description | | -------------- | --------- | ----------------------------- | ------------ | -| [events?](#) | | string[] | _(Optional)_ | | [include?](#) | | 'always' \| 'never' \| 'auto' | _(Optional)_ | | [position?](#) | | 'top' \| 'bottom' | _(Optional)_ | @@ -142,16 +161,17 @@ export interface RenderOptions extends SerializeDocumentOptions **Extends:** [SerializeDocumentOptions](#serializedocumentoptions) -| Property | Modifiers | Type | Description | -| ------------------------- | --------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [base?](#) | | string \| ((options: [RenderOptions](#renderoptions)) => string) | _(Optional)_ Specifies the root of the JS files of the client build. Setting a base, will cause the render of the q:base attribute in the q:container element. | -| [containerAttributes?](#) | | Record<string, string> | _(Optional)_ | -| [containerTagName?](#) | | string | _(Optional)_ When set, the app is serialized into a fragment. And the returned html is not a complete document. Defaults to html | -| [locale?](#) | | string \| ((options: [RenderOptions](#renderoptions)) => string) | _(Optional)_ Language to use when rendering the document. | -| [prefetchStrategy?](#) | | [PrefetchStrategy](#prefetchstrategy) \| null | _(Optional)_ | -| [qwikLoader?](#) | | [QwikLoaderOptions](#qwikloaderoptions) |

_(Optional)_ Specifies if the Qwik Loader script is added to the document or not.

Defaults to { include: true }.

| -| [serverData?](#) | | Record<string, any> | _(Optional)_ | -| [snapshot?](#) | | boolean | _(Optional)_ Defaults to true | +| Property | Modifiers | Type | Description | +| ------------------------------- | --------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [base?](#) | | string \| ((options: [RenderOptions](#renderoptions)) => string) | _(Optional)_ Specifies the root of the JS files of the client build. Setting a base, will cause the render of the q:base attribute in the q:container element. | +| [containerAttributes?](#) | | Record<string, string> | _(Optional)_ | +| [containerTagName?](#) | | string | _(Optional)_ When set, the app is serialized into a fragment. And the returned html is not a complete document. Defaults to html | +| [locale?](#) | | string \| ((options: [RenderOptions](#renderoptions)) => string) | _(Optional)_ Language to use when rendering the document. | +| [prefetchStrategy?](#) | | [PrefetchStrategy](#prefetchstrategy) \| null | _(Optional)_ | +| [qwikLoader?](#) | | [QwikLoaderOptions](#qwikloaderoptions) |

_(Optional)_ Specifies if the Qwik Loader script is added to the document or not.

Defaults to { include: true }.

| +| [qwikPrefetchServiceWorker?](#) | | QwikPrefetchServiceWorkerOptions |

_(Optional)_ Specifies if the Qwik Prefetch Service Worker script is added to the document or not.

Defaults to { include: false }. NOTE: This may be change in the future.

| +| [serverData?](#) | | Record<string, any> | _(Optional)_ | +| [snapshot?](#) | | boolean | _(Optional)_ Defaults to true | [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/server/types.ts) diff --git a/packages/docs/src/routes/api/qwik-testing/api.json b/packages/docs/src/routes/api/qwik-testing/api.json index 49d71db58a2f..d75e8f3b049d 100644 --- a/packages/docs/src/routes/api/qwik-testing/api.json +++ b/packages/docs/src/routes/api/qwik-testing/api.json @@ -12,7 +12,7 @@ } ], "kind": "Variable", - "content": "CreatePlatform and CreateDocument\n\n\n```typescript\ncreateDOM: () => Promise<{\n render: (jsxElement: JSXNode) => Promise;\n screen: HTMLElement;\n userEvent: (queryOrElement: string | Element | keyof HTMLElementTagNameMap | null, eventNameCamel: string | keyof WindowEventMap, eventPayload?: any) => Promise;\n}>\n```", + "content": "CreatePlatform and CreateDocument\n\n\n```typescript\ncreateDOM: ({ html }?: {\n html?: string | undefined;\n}) => Promise<{\n render: (jsxElement: JSXOutput) => Promise;\n screen: HTMLElement;\n userEvent: (queryOrElement: string | Element | keyof HTMLElementTagNameMap | null, eventNameCamel: string | keyof WindowEventMap, eventPayload?: any) => Promise;\n}>\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/testing/library.ts", "mdFile": "qwik.createdom.md" } diff --git a/packages/docs/src/routes/api/qwik-testing/index.md b/packages/docs/src/routes/api/qwik-testing/index.md index 0ef056301d91..1da0e14129ac 100644 --- a/packages/docs/src/routes/api/qwik-testing/index.md +++ b/packages/docs/src/routes/api/qwik-testing/index.md @@ -9,10 +9,10 @@ title: \@builder.io/qwik/testing API Reference CreatePlatform and CreateDocument ```typescript -createDOM: () => +createDOM: ({ html }?: { html?: string | undefined }) => Promise<{ render: ( - jsxElement: JSXNode, + jsxElement: JSXOutput, ) => Promise; screen: HTMLElement; userEvent: ( diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json index a1ccf3fe4ec9..2d827bd89ba9 100644 --- a/packages/docs/src/routes/api/qwik/api.json +++ b/packages/docs/src/routes/api/qwik/api.json @@ -2,6 +2,20 @@ "id": "qwik", "package": "@builder.io/qwik", "members": [ + { + "name": "_qrlSync", + "id": "_qrlsync", + "hierarchy": [ + { + "name": "_qrlSync", + "id": "_qrlsync" + } + ], + "kind": "Variable", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nExtract function into a synchronously loadable QRL.\n\nNOTE: Synchronous QRLs functions can't close over any variables, including exports.\n\n\n```typescript\n_qrlSync: (fn: TYPE, serializedFn?: string) => SyncQRL\n```", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts", + "mdFile": "qwik._qrlsync.md" + }, { "name": "\"q:slot\"", "id": "componentbaseprops-_q_slot_", @@ -446,7 +460,7 @@ } ], "kind": "TypeAlias", - "content": "Type representing the Qwik component.\n\n`Component` is the type returned by invoking `component$`.\n\n```tsx\ninterface MyComponentProps {\n someProp: string;\n}\nconst MyComponent: Component = component$((props: MyComponentProps) => {\n return {props.someProp};\n});\n```\n\n\n```typescript\nexport type Component = Record> = FunctionComponent>;\n```\n**References:** [FunctionComponent](#functioncomponent), [PublicProps](#publicprops)", + "content": "Type representing the Qwik component.\n\n`Component` is the type returned by invoking `component$`.\n\n```tsx\ninterface MyComponentProps {\n someProp: string;\n}\nconst MyComponent: Component = component$((props: MyComponentProps) => {\n return {props.someProp};\n});\n```\n\n\n```typescript\nexport type Component = FunctionComponent>;\n```\n**References:** [FunctionComponent](#functioncomponent), [PublicProps](#publicprops)", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts", "mdFile": "qwik.component.md" }, @@ -460,7 +474,7 @@ } ], "kind": "Variable", - "content": "Declare a Qwik component that can be used to create UI.\n\nUse `component$` to declare a Qwik component. A Qwik component is a special kind of component that allows the Qwik framework to lazy load and execute the component independently of other Qwik components as well as lazy load the component's life-cycle hooks and event handlers.\n\nSide note: You can also declare regular (standard JSX) components that will have standard synchronous behavior.\n\nQwik component is a facade that describes how the component should be used without forcing the implementation of the component to be eagerly loaded. A minimum Qwik definition consists of:\n\n\\#\\#\\# Example\n\nAn example showing how to create a counter component:\n\n```tsx\nexport interface CounterProps {\n initialValue?: number;\n step?: number;\n}\nexport const Counter = component$((props: CounterProps) => {\n const state = useStore({ count: props.initialValue || 0 });\n return (\n
\n {state.count}\n \n
\n );\n});\n```\n- `component$` is how a component gets declared. - `{ value?: number; step?: number }` declares the public (props) interface of the component. - `{ count: number }` declares the private (state) interface of the component.\n\nThe above can then be used like so:\n\n```tsx\nexport const OtherComponent = component$(() => {\n return ;\n});\n```\nSee also: `component`, `useCleanup`, `onResume`, `onPause`, `useOn`, `useOnDocument`, `useOnWindow`, `useStyles`\n\n\n```typescript\ncomponent$: >(onMount: (props: PROPS) => JSXNode | null) => Component>\n```", + "content": "Declare a Qwik component that can be used to create UI.\n\nUse `component$` to declare a Qwik component. A Qwik component is a special kind of component that allows the Qwik framework to lazy load and execute the component independently of other Qwik components as well as lazy load the component's life-cycle hooks and event handlers.\n\nSide note: You can also declare regular (standard JSX) components that will have standard synchronous behavior.\n\nQwik component is a facade that describes how the component should be used without forcing the implementation of the component to be eagerly loaded. A minimum Qwik definition consists of:\n\n\\#\\#\\# Example\n\nAn example showing how to create a counter component:\n\n```tsx\nexport interface CounterProps {\n initialValue?: number;\n step?: number;\n}\nexport const Counter = component$((props: CounterProps) => {\n const state = useStore({ count: props.initialValue || 0 });\n return (\n
\n {state.count}\n \n
\n );\n});\n```\n- `component$` is how a component gets declared. - `{ value?: number; step?: number }` declares the public (props) interface of the component. - `{ count: number }` declares the private (state) interface of the component.\n\nThe above can then be used like so:\n\n```tsx\nexport const OtherComponent = component$(() => {\n return ;\n});\n```\nSee also: `component`, `useCleanup`, `onResume`, `onPause`, `useOn`, `useOnDocument`, `useOnWindow`, `useStyles`\n\n\n```typescript\ncomponent$: (onMount: OnRenderFn) => Component\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts", "mdFile": "qwik.component_.md" }, @@ -642,7 +656,7 @@ } ], "kind": "Interface", - "content": "The Qwik-specific attributes that DOM elements accept\n\n\n```typescript\nexport interface DOMAttributes extends QwikAttributesBase, RefAttr, QwikEvents \n```\n**Extends:** QwikAttributesBase, RefAttr<EL>, QwikEvents<EL>\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [class?](#) | | [ClassList](#classlist) \\| [Signal](#signal)<[ClassList](#classlist)> \\| undefined | _(Optional)_ |", + "content": "The Qwik-specific attributes that DOM elements accept\n\n\n```typescript\nexport interface DOMAttributes extends DOMAttributesBase, QwikEvents \n```\n**Extends:** DOMAttributesBase<EL>, QwikEvents<EL>\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [class?](#) | | [ClassList](#classlist) \\| [Signal](#signal)<[ClassList](#classlist)> \\| undefined | _(Optional)_ |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.ts", "mdFile": "qwik.domattributes.md" }, @@ -662,45 +676,37 @@ }, { "name": "Element", - "id": "h-jsx-element", + "id": "qwikjsx-element", "hierarchy": [ { - "name": "h", - "id": "h-jsx-element" - }, - { - "name": "JSX", - "id": "h-jsx-element" + "name": "QwikJSX", + "id": "qwikjsx-element" }, { "name": "Element", - "id": "h-jsx-element" + "id": "qwikjsx-element" } ], - "kind": "Interface", - "content": "```typescript\ninterface Element extends QwikJSX.Element \n```\n**Extends:** [QwikJSX.Element](#)", - "mdFile": "qwik.h.jsx.element.md" + "kind": "TypeAlias", + "content": "```typescript\ntype Element = JSXOutput;\n```\n**References:** [JSXOutput](#jsxoutput)", + "mdFile": "qwik.qwikjsx.element.md" }, { "name": "ElementChildrenAttribute", - "id": "h-jsx-elementchildrenattribute", + "id": "qwikjsx-elementchildrenattribute", "hierarchy": [ { - "name": "h", - "id": "h-jsx-elementchildrenattribute" - }, - { - "name": "JSX", - "id": "h-jsx-elementchildrenattribute" + "name": "QwikJSX", + "id": "qwikjsx-elementchildrenattribute" }, { "name": "ElementChildrenAttribute", - "id": "h-jsx-elementchildrenattribute" + "id": "qwikjsx-elementchildrenattribute" } ], "kind": "Interface", - "content": "```typescript\ninterface ElementChildrenAttribute \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [children?](#) | | any | _(Optional)_ |", - "mdFile": "qwik.h.jsx.elementchildrenattribute.md" + "content": "```typescript\ninterface ElementChildrenAttribute \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [children](#) | | [JSXChildren](#jsxchildren) | |", + "mdFile": "qwik.qwikjsx.elementchildrenattribute.md" }, { "name": "ElementType", @@ -716,7 +722,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\ntype ElementType = string | ((...args: any[]) => JSXNode | null);\n```\n**References:** [JSXNode](#jsxnode)", + "content": "```typescript\ntype ElementType = string | FunctionComponent>;\n```\n**References:** [FunctionComponent](#functioncomponent)", "mdFile": "qwik.qwikjsx.elementtype.md" }, { @@ -761,6 +767,20 @@ "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts", "mdFile": "qwik.event_.md" }, + { + "name": "EventHandler", + "id": "eventhandler", + "hierarchy": [ + { + "name": "EventHandler", + "id": "eventhandler" + } + ], + "kind": "TypeAlias", + "content": "A DOM event handler\n\n\n```typescript\nexport type EventHandler = {\n bivarianceHack(event: EV, element: EL): any;\n}['bivarianceHack'];\n```", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.ts", + "mdFile": "qwik.eventhandler.md" + }, { "name": "eventQrl", "id": "eventqrl", @@ -826,8 +846,8 @@ "id": "functioncomponent" } ], - "kind": "Interface", - "content": "```typescript\nexport interface FunctionComponent

= Record> \n```", + "kind": "TypeAlias", + "content": "Any function taking a props object that returns JSXOutput.\n\nThe `key`, `flags` and `dev` parameters are for internal use.\n\n\n```typescript\nexport type FunctionComponent

= {\n renderFn(props: P, key: string | null, flags: number, dev?: DevJSX): JSXOutput;\n}['renderFn'];\n```\n**References:** [DevJSX](#devjsx), [JSXOutput](#jsxoutput)", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-node.ts", "mdFile": "qwik.functioncomponent.md" }, @@ -855,7 +875,7 @@ } ], "kind": "Function", - "content": "```typescript\nexport declare namespace h \n```\n\n\n| Function | Description |\n| --- | --- |\n| [h(type)](#) | |\n| [h(type, data)](#) | |\n| [h(type, text)](#) | |\n| [h(type, children)](#) | |\n| [h(type, data, text)](#) | |\n| [h(type, data, children)](#) | |\n| [h(sel, data, children)](#) | |\n\n\n| Namespace | Description |\n| --- | --- |\n| [JSX](#h-jsx) | |", + "content": "```typescript\nexport declare namespace h \n```\n\n\n| Function | Description |\n| --- | --- |\n| [h(type)](#) | |\n| [h(type, data)](#) | |\n| [h(type, text)](#) | |\n| [h(type, children)](#) | |\n| [h(type, data, text)](#) | |\n| [h(type, data, children)](#) | |\n| [h(sel, data, children)](#) | |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/factory.ts", "mdFile": "qwik.h.md" }, @@ -869,7 +889,7 @@ } ], "kind": "Namespace", - "content": "```typescript\nexport declare namespace h \n```\n\n\n| Function | Description |\n| --- | --- |\n| [h(type)](#) | |\n| [h(type, data)](#) | |\n| [h(type, text)](#) | |\n| [h(type, children)](#) | |\n| [h(type, data, text)](#) | |\n| [h(type, data, children)](#) | |\n| [h(sel, data, children)](#) | |\n\n\n| Namespace | Description |\n| --- | --- |\n| [JSX](#h-jsx) | |", + "content": "```typescript\nexport declare namespace h \n```\n\n\n| Function | Description |\n| --- | --- |\n| [h(type)](#) | |\n| [h(type, data)](#) | |\n| [h(type, text)](#) | |\n| [h(type, children)](#) | |\n| [h(type, data, text)](#) | |\n| [h(type, data, children)](#) | |\n| [h(sel, data, children)](#) | |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/factory.ts", "mdFile": "qwik.h.md" }, @@ -925,7 +945,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface HTMLAttributes extends HTMLElementAttrs, DOMAttributes \n```\n**Extends:** HTMLElementAttrs, [DOMAttributes](#domattributes)<E>", + "content": "```typescript\nexport interface HTMLAttributes extends HTMLElementAttrs, DOMAttributes \n```\n**Extends:** [HTMLElementAttrs](#htmlelementattrs), [DOMAttributes](#domattributes)<E>", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts", "mdFile": "qwik.htmlattributes.md" }, @@ -943,6 +963,20 @@ "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts", "mdFile": "qwik.htmlcrossoriginattribute.md" }, + { + "name": "HTMLElementAttrs", + "id": "htmlelementattrs", + "hierarchy": [ + { + "name": "HTMLElementAttrs", + "id": "htmlelementattrs" + } + ], + "kind": "Interface", + "content": "```typescript\nexport interface HTMLElementAttrs extends HTMLAttributesBase, FilterBase \n```\n**Extends:** HTMLAttributesBase, FilterBase<HTMLElement>", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts", + "mdFile": "qwik.htmlelementattrs.md" + }, { "name": "HTMLFragment", "id": "htmlfragment", @@ -1071,45 +1105,34 @@ }, { "name": "IntrinsicAttributes", - "id": "h-jsx-intrinsicattributes", + "id": "qwikjsx-intrinsicattributes", "hierarchy": [ { - "name": "h", - "id": "h-jsx-intrinsicattributes" - }, - { - "name": "JSX", - "id": "h-jsx-intrinsicattributes" + "name": "QwikJSX", + "id": "qwikjsx-intrinsicattributes" }, { "name": "IntrinsicAttributes", - "id": "h-jsx-intrinsicattributes" + "id": "qwikjsx-intrinsicattributes" } ], "kind": "Interface", - "content": "```typescript\ninterface IntrinsicAttributes extends QwikJSX.IntrinsicAttributes \n```\n**Extends:** [QwikJSX.IntrinsicAttributes](#)", - "mdFile": "qwik.h.jsx.intrinsicattributes.md" + "content": "```typescript\ninterface IntrinsicAttributes extends QwikIntrinsicAttributes \n```\n**Extends:** QwikIntrinsicAttributes", + "mdFile": "qwik.qwikjsx.intrinsicattributes.md" }, { "name": "IntrinsicElements", - "id": "h-jsx-intrinsicelements", + "id": "intrinsicelements", "hierarchy": [ - { - "name": "h", - "id": "h-jsx-intrinsicelements" - }, - { - "name": "JSX", - "id": "h-jsx-intrinsicelements" - }, { "name": "IntrinsicElements", - "id": "h-jsx-intrinsicelements" + "id": "intrinsicelements" } ], "kind": "Interface", - "content": "```typescript\ninterface IntrinsicElements extends QwikJSX.IntrinsicElements \n```\n**Extends:** [QwikJSX.IntrinsicElements](#)", - "mdFile": "qwik.h.jsx.intrinsicelements.md" + "content": "```typescript\nexport interface IntrinsicElements extends IntrinsicHTMLElements, IntrinsicSVGElements \n```\n**Extends:** IntrinsicHTMLElements, IntrinsicSVGElements", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts", + "mdFile": "qwik.intrinsicelements.md" }, { "name": "isSignal", @@ -1135,27 +1158,10 @@ } ], "kind": "Variable", - "content": "```typescript\njsx: >(type: T, props: T extends FunctionComponent> ? PROPS : Record, key?: string | number | null) => JSXNode\n```", + "content": "```typescript\njsx: >(type: T, props: T extends FunctionComponent ? PROPS : Record, key?: string | number | null) => JSXNode\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/jsx-runtime.ts", "mdFile": "qwik.jsx.md" }, - { - "name": "JSX", - "id": "h-jsx", - "hierarchy": [ - { - "name": "h", - "id": "h-jsx" - }, - { - "name": "JSX", - "id": "h-jsx" - } - ], - "kind": "Namespace", - "content": "```typescript\nnamespace JSX \n```\n\n\n| Interface | Description |\n| --- | --- |\n| [Element](#h-jsx-element) | |\n| [ElementChildrenAttribute](#h-jsx-elementchildrenattribute) | |\n| [IntrinsicAttributes](#h-jsx-intrinsicattributes) | |\n| [IntrinsicElements](#h-jsx-intrinsicelements) | |", - "mdFile": "qwik.h.jsx.md" - }, { "name": "JSXChildren", "id": "jsxchildren", @@ -1180,7 +1186,7 @@ } ], "kind": "Variable", - "content": "```typescript\njsxDEV: >>(type: T, props: T extends FunctionComponent> ? PROPS : Record, key: string | number | null | undefined, _isStatic: boolean, opts: JsxDevOpts, _ctx: unknown) => JSXNode\n```", + "content": "```typescript\njsxDEV: >>(type: T, props: T extends FunctionComponent ? PROPS : Record, key: string | number | null | undefined, _isStatic: boolean, opts: JsxDevOpts, _ctx: unknown) => JSXNode\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/jsx-runtime.ts", "mdFile": "qwik.jsxdev.md" }, @@ -1194,10 +1200,24 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface JSXNode \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [children](#) | | [JSXChildren](#jsxchildren) \\| null | |\n| [dev?](#) | | [DevJSX](#devjsx) | _(Optional)_ |\n| [flags](#) | | number | |\n| [immutableProps](#) | | Record<any, unknown> \\| null | |\n| [key](#) | | string \\| null | |\n| [props](#) | | T extends [FunctionComponent](#functioncomponent)<infer B> ? B : Record<any, unknown> | |\n| [type](#) | | T | |", + "content": "A JSX Node, an internal structure. You probably want to use `JSXOutput` instead.\n\n\n```typescript\nexport interface JSXNode \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [children](#) | | [JSXChildren](#jsxchildren) \\| null | |\n| [dev?](#) | | [DevJSX](#devjsx) | _(Optional)_ |\n| [flags](#) | | number | |\n| [immutableProps](#) | | Record<any, unknown> \\| null | |\n| [key](#) | | string \\| null | |\n| [props](#) | | T extends [FunctionComponent](#functioncomponent)<infer P> ? P : Record<any, unknown> | |\n| [type](#) | | T | |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-node.ts", "mdFile": "qwik.jsxnode.md" }, + { + "name": "JSXOutput", + "id": "jsxoutput", + "hierarchy": [ + { + "name": "JSXOutput", + "id": "jsxoutput" + } + ], + "kind": "TypeAlias", + "content": "Any valid output for a component\n\n\n```typescript\nexport type JSXOutput = JSXNode | string | number | boolean | null | undefined | JSXOutput[];\n```\n**References:** [JSXNode](#jsxnode), [JSXOutput](#jsxoutput)", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-node.ts", + "mdFile": "qwik.jsxoutput.md" + }, { "name": "JSXTagName", "id": "jsxtagname", @@ -1600,7 +1620,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type OnRenderFn> = (props: PROPS) => JSXNode | null;\n```\n**References:** [JSXNode](#jsxnode)", + "content": "```typescript\nexport type OnRenderFn = (props: PROPS) => JSXOutput;\n```\n**References:** [JSXOutput](#jsxoutput)", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts", "mdFile": "qwik.onrenderfn.md" }, @@ -1674,6 +1694,34 @@ "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts", "mdFile": "qwik.paramhtmlattributes.md" }, + { + "name": "PrefetchGraph", + "id": "prefetchgraph", + "hierarchy": [ + { + "name": "PrefetchGraph", + "id": "prefetchgraph" + } + ], + "kind": "Variable", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nLoad the prefetch graph for the container.\n\nEach Qwik container needs to include its own prefetch graph.\n\n\n```typescript\nPrefetchGraph: (opts?: {\n base?: string;\n manifestHash?: string;\n manifestURL?: string;\n}) => import(\"@builder.io/qwik/jsx-runtime\").JSXNode<\"script\">\n```", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts", + "mdFile": "qwik.prefetchgraph.md" + }, + { + "name": "PrefetchServiceWorker", + "id": "prefetchserviceworker", + "hierarchy": [ + { + "name": "PrefetchServiceWorker", + "id": "prefetchserviceworker" + } + ], + "kind": "Variable", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nInstall a service worker which will prefetch the bundles.\n\nThere can only be one service worker per page. Because there can be many separate Qwik Containers on the page each container needs to load its prefetch graph using `PrefetchGraph` component.\n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n}) => import(\"@builder.io/qwik/jsx-runtime\").JSXNode<\"script\">\n```", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts", + "mdFile": "qwik.prefetchserviceworker.md" + }, { "name": "ProgressHTMLAttributes", "id": "progresshtmlattributes", @@ -1698,7 +1746,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type PropFnInterface = {\n (...args: ARGS): Promise;\n};\n```", + "content": "> Warning: This API is now obsolete.\n> \n> Use `QRL<>` instead\n> \n\n\n```typescript\nexport type PropFnInterface = {\n __qwik_serializable__?: any;\n (...args: ARGS): Promise;\n};\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts", "mdFile": "qwik.propfninterface.md" }, @@ -1712,7 +1760,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type PropFunction any> = T extends (...args: infer ARGS) => infer RET ? PropFnInterface> : never;\n```\n**References:** [PropFnInterface](#propfninterface)", + "content": "Alias for `QRL`. Of historic relevance only.\n\n\n```typescript\nexport type PropFunction = QRL;\n```\n**References:** [QRL](#qrl)", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts", "mdFile": "qwik.propfunction.md" }, @@ -1726,7 +1774,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type PropFunctionProps> = {\n [K in keyof PROPS]: PROPS[K] extends undefined ? PROPS[K] : PROPS[K] extends ((...args: infer ARGS) => infer RET) | undefined ? PropFnInterface> : PROPS[K];\n};\n```\n**References:** [PropFnInterface](#propfninterface)", + "content": "> Warning: This API is now obsolete.\n> \n> Use `QRL<>` on your function props instead\n> \n\n\n```typescript\nexport type PropFunctionProps> = {\n [K in keyof PROPS]: PROPS[K] extends undefined ? PROPS[K] : PROPS[K] extends ((...args: infer ARGS) => infer RET) | undefined ? PropFnInterface> : PROPS[K];\n};\n```\n**References:** [PropFnInterface](#propfninterface)", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts", "mdFile": "qwik.propfunctionprops.md" }, @@ -1740,7 +1788,7 @@ } ], "kind": "TypeAlias", - "content": "Infers `Props` from the component.\n\n```typescript\nexport const OtherComponent = component$(() => {\n return $(() => );\n});\n```\n\n\n```typescript\nexport type PropsOf = COMP extends Component ? NonNullable : COMP extends FunctionComponent ? NonNullable> : COMP extends string ? QwikIntrinsicElements[COMP] : Record;\n```\n**References:** [Component](#component), [FunctionComponent](#functioncomponent), [PublicProps](#publicprops), [QwikIntrinsicElements](#qwikintrinsicelements)", + "content": "Infers `Props` from the component or tag.\n\n\n```typescript\nexport type PropsOf = COMP extends string ? COMP extends keyof QwikIntrinsicElements ? QwikIntrinsicElements[COMP] : QwikIntrinsicElements['span'] : NonNullable extends never ? never : COMP extends FunctionComponent ? PROPS extends Record ? IsAny extends true ? never : ObjectProps : COMP extends Component ? ObjectProps : PROPS : never;\n```\n**References:** [QwikIntrinsicElements](#qwikintrinsicelements), [FunctionComponent](#functioncomponent), [Component](#component)\n\n\n\n```tsx\nconst Desc = component$(({desc, ...props}: { desc: string } & PropsOf<'div'>) => {\n return

{desc}
;\n});\n\nconst TitleBox = component$(({title, ...props}: { title: string } & PropsOf) => {\n return

{title}

;\n});\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts", "mdFile": "qwik.propsof.md" }, @@ -1754,7 +1802,7 @@ } ], "kind": "TypeAlias", - "content": "Extends the defined component PROPS, adding the default ones (children and q:slot)..\n\n\n```typescript\nexport type PublicProps> = TransformProps & ComponentBaseProps & ComponentChildren;\n```\n**References:** [ComponentBaseProps](#componentbaseprops)", + "content": "Extends the defined component PROPS, adding the default ones (children and q:slot) and allowing plain functions to QRL arguments.\n\n\n```typescript\nexport type PublicProps = (PROPS extends Record ? Omit & _Only$ : unknown extends PROPS ? {} : PROPS) & ComponentBaseProps & ComponentChildren;\n```\n**References:** [ComponentBaseProps](#componentbaseprops)", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts", "mdFile": "qwik.publicprops.md" }, @@ -1786,6 +1834,20 @@ "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts", "mdFile": "qwik.qrl.md" }, + { + "name": "QRLEventHandlerMulti", + "id": "qrleventhandlermulti", + "hierarchy": [ + { + "name": "QRLEventHandlerMulti", + "id": "qrleventhandlermulti" + } + ], + "kind": "TypeAlias", + "content": "> This API is provided as a beta preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nAn event handler for Qwik events, can be a handler QRL or an array of handler QRLs.\n\n\n```typescript\nexport type QRLEventHandlerMulti = QRL> | undefined | null | QRLEventHandlerMulti[];\n```\n**References:** [QRL](#qrl), [EventHandler](#eventhandler), [QRLEventHandlerMulti](#qrleventhandlermulti)", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.ts", + "mdFile": "qwik.qrleventhandlermulti.md" + }, { "name": "QuoteHTMLAttributes", "id": "quotehtmlattributes", @@ -1814,6 +1876,20 @@ "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-events.ts", "mdFile": "qwik.qwikanimationevent.md" }, + { + "name": "QwikAttributes", + "id": "qwikattributes", + "hierarchy": [ + { + "name": "QwikAttributes", + "id": "qwikattributes" + } + ], + "kind": "Interface", + "content": "The Qwik DOM attributes without plain handlers, for use as function parameters\n\n\n```typescript\nexport interface QwikAttributes extends DOMAttributesBase, QwikEvents \n```\n**Extends:** DOMAttributesBase<EL>, QwikEvents<EL, false>\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [class?](#) | | [ClassList](#classlist) \\| undefined | _(Optional)_ |", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.ts", + "mdFile": "qwik.qwikattributes.md" + }, { "name": "QwikChangeEvent", "id": "qwikchangeevent", @@ -1908,7 +1984,7 @@ } ], "kind": "TypeAlias", - "content": "The DOM props without plain handlers, for use inside functions\n\n\n```typescript\nexport type QwikHTMLElements = {\n [tag in keyof HTMLElementTagNameMap]: Augmented & HTMLElementAttrs & QwikAttributes;\n} & {\n [unknownTag: string]: {\n [prop: string]: any;\n } & HTMLElementAttrs & QwikAttributes;\n};\n```", + "content": "The DOM props without plain handlers, for use inside functions\n\n\n```typescript\nexport type QwikHTMLElements = {\n [tag in keyof HTMLElementTagNameMap]: Augmented & HTMLElementAttrs & QwikAttributes;\n};\n```\n**References:** [HTMLElementAttrs](#htmlelementattrs), [QwikAttributes](#qwikattributes)", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts", "mdFile": "qwik.qwikhtmlelements.md" }, @@ -1978,7 +2054,7 @@ } ], "kind": "Namespace", - "content": "```typescript\nexport declare namespace QwikJSX \n```\n\n\n| Interface | Description |\n| --- | --- |\n| [Element](#) | |\n| [ElementChildrenAttribute](#) | |\n| [IntrinsicAttributes](#) | |\n| [IntrinsicElements](#) | |\n\n\n| Type Alias | Description |\n| --- | --- |\n| [ElementType](#qwikjsx-elementtype) | |", + "content": "```typescript\nexport declare namespace QwikJSX \n```\n\n\n| Interface | Description |\n| --- | --- |\n| [ElementChildrenAttribute](#qwikjsx-elementchildrenattribute) | |\n| [IntrinsicAttributes](#qwikjsx-intrinsicattributes) | |\n| [IntrinsicElements](#) | |\n\n\n| Type Alias | Description |\n| --- | --- |\n| [Element](#qwikjsx-element) | |\n| [ElementType](#qwikjsx-elementtype) | |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik.ts", "mdFile": "qwik.qwikjsx.md" }, @@ -2146,7 +2222,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type ReadonlySignal = Readonly>;\n```\n**References:** [Signal](#signal)", + "content": "```typescript\nexport type ReadonlySignal = Readonly>;\n```\n**References:** [Signal](#signal)", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/state/signal.ts", "mdFile": "qwik.readonlysignal.md" }, @@ -2160,7 +2236,7 @@ } ], "kind": "Variable", - "content": "Render JSX.\n\nUse this method to render JSX. This function does reconciling which means it always tries to reuse what is already in the DOM (rather then destroy and recreate content.) It returns a cleanup function you could use for cleaning up subscriptions.\n\n\n```typescript\nrender: (parent: Element | Document, jsxNode: JSXNode | FunctionComponent, opts?: RenderOptions) => Promise\n```", + "content": "Render JSX.\n\nUse this method to render JSX. This function does reconciling which means it always tries to reuse what is already in the DOM (rather then destroy and recreate content.) It returns a cleanup function you could use for cleaning up subscriptions.\n\n\n```typescript\nrender: (parent: Element | Document, jsxOutput: JSXOutput | FunctionComponent, opts?: RenderOptions) => Promise\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/dom/render.public.ts", "mdFile": "qwik.render.md" }, @@ -2230,7 +2306,7 @@ } ], "kind": "Variable", - "content": "This method works like an async memoized function that runs whenever some tracked value changes and returns some data.\n\n`useResource` however returns immediate a `ResourceReturn` object that contains the data and a state that indicates if the data is available or not.\n\nThe status can be one of the following:\n\n- 'pending' - the data is not yet available. - 'resolved' - the data is available. - 'rejected' - the data is not available due to an error or timeout.\n\n\\#\\#\\# Example\n\nExample showing how `useResource` to perform a fetch to request the weather, whenever the input city name changes.\n\n```tsx\nconst Cmp = component$(() => {\n const cityS = useSignal('');\n\n const weatherResource = useResource$(async ({ track, cleanup }) => {\n const cityName = track(cityS);\n const abortController = new AbortController();\n cleanup(() => abortController.abort('cleanup'));\n const res = await fetch(`http://weatherdata.com?city=${cityName}`, {\n signal: abortController.signal,\n });\n const data = await res.json();\n return data as { temp: number };\n });\n\n return (\n
\n \n {\n return
Temperature: {weather.temp}
;\n }}\n />\n
\n );\n});\n```\n\n\n```typescript\nResource: (props: ResourceProps) => JSXNode\n```", + "content": "This method works like an async memoized function that runs whenever some tracked value changes and returns some data.\n\n`useResource` however returns immediate a `ResourceReturn` object that contains the data and a state that indicates if the data is available or not.\n\nThe status can be one of the following:\n\n- 'pending' - the data is not yet available. - 'resolved' - the data is available. - 'rejected' - the data is not available due to an error or timeout.\n\n\\#\\#\\# Example\n\nExample showing how `useResource` to perform a fetch to request the weather, whenever the input city name changes.\n\n```tsx\nconst Cmp = component$(() => {\n const cityS = useSignal('');\n\n const weatherResource = useResource$(async ({ track, cleanup }) => {\n const cityName = track(cityS);\n const abortController = new AbortController();\n cleanup(() => abortController.abort('cleanup'));\n const res = await fetch(`http://weatherdata.com?city=${cityName}`, {\n signal: abortController.signal,\n });\n const data = await res.json();\n return data as { temp: number };\n });\n\n return (\n
\n \n {\n return
Temperature: {weather.temp}
;\n }}\n />\n
\n );\n});\n```\n\n\n```typescript\nResource: (props: ResourceProps) => JSXOutput\n```", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/use/use-resource.ts", "mdFile": "qwik.resource.md" }, @@ -2300,7 +2376,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface ResourceProps \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [onPending?](#) | | () => [JSXNode](#jsxnode) | _(Optional)_ |\n| [onRejected?](#) | | (reason: Error) => [JSXNode](#jsxnode) | _(Optional)_ |\n| [onResolved](#) | | (value: T) => [JSXNode](#jsxnode) | |\n| [value](#) | readonly | [ResourceReturn](#resourcereturn)<T> \\| [Signal](#signal)<Promise<T> \\| T> \\| Promise<T> | |", + "content": "```typescript\nexport interface ResourceProps \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [onPending?](#) | | () => [JSXOutput](#jsxoutput) | _(Optional)_ |\n| [onRejected?](#) | | (reason: Error) => [JSXOutput](#jsxoutput) | _(Optional)_ |\n| [onResolved](#) | | (value: T) => [JSXOutput](#jsxoutput) | |\n| [value](#) | readonly | [ResourceReturn](#resourcereturn)<T> \\| [Signal](#signal)<Promise<T> \\| T> \\| Promise<T> | |", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/use/use-resource.ts", "mdFile": "qwik.resourceprops.md" }, @@ -2692,10 +2768,24 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface SVGProps extends SVGAttributes, QwikAttributes \n```\n**Extends:** [SVGAttributes](#svgattributes), QwikAttributes<T>", + "content": "```typescript\nexport interface SVGProps extends SVGAttributes, QwikAttributes \n```\n**Extends:** [SVGAttributes](#svgattributes), [QwikAttributes](#qwikattributes)<T>", "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts", "mdFile": "qwik.svgprops.md" }, + { + "name": "sync$", + "id": "sync_", + "hierarchy": [ + { + "name": "sync$", + "id": "sync_" + } + ], + "kind": "Variable", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nExtract function into a synchronously loadable QRL.\n\nNOTE: Synchronous QRLs functions can't close over any variables, including exports.\n\n\n```typescript\nsync$: (fn: T) => SyncQRL\n```", + "editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts", + "mdFile": "qwik.sync_.md" + }, { "name": "TableHTMLAttributes", "id": "tablehtmlattributes", diff --git a/packages/docs/src/routes/api/qwik/index.md b/packages/docs/src/routes/api/qwik/index.md index 8c4d90101e88..542f183b45b9 100644 --- a/packages/docs/src/routes/api/qwik/index.md +++ b/packages/docs/src/routes/api/qwik/index.md @@ -4,6 +4,21 @@ title: \@builder.io/qwik API Reference # [API](/api) › @builder.io/qwik +## \_qrlSync + +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +Extract function into a synchronously loadable QRL. + +NOTE: Synchronous QRLs functions can't close over any variables, including exports. + +```typescript +_qrlSync: (fn: TYPE, serializedFn?: string) => + SyncQRL; +``` + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts) + ## "q:slot" ```typescript @@ -389,9 +404,7 @@ const MyComponent: Component = component$( ``` ```typescript -export type Component< - PROPS extends Record = Record, -> = FunctionComponent>; +export type Component = FunctionComponent>; ``` **References:** [FunctionComponent](#functioncomponent), [PublicProps](#publicprops) @@ -441,9 +454,7 @@ export const OtherComponent = component$(() => { See also: `component`, `useCleanup`, `onResume`, `onPause`, `useOn`, `useOnDocument`, `useOnWindow`, `useStyles` ```typescript -component$: >( - onMount: (props: PROPS) => JSXNode | null, -) => Component>; +component$: (onMount: OnRenderFn) => Component; ``` [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts) @@ -727,10 +738,10 @@ export interface DialogHTMLAttributes extends Attrs<'dialog', The Qwik-specific attributes that DOM elements accept ```typescript -export interface DOMAttributes extends QwikAttributesBase, RefAttr, QwikEvents +export interface DOMAttributes extends DOMAttributesBase, QwikEvents ``` -**Extends:** QwikAttributesBase, RefAttr<EL>, QwikEvents<EL> +**Extends:** DOMAttributesBase<EL>, QwikEvents<EL> | Property | Modifiers | Type | Description | | ----------- | --------- | ---------------------------------------------------------------------------------------- | ------------ | @@ -749,10 +760,10 @@ export type EagernessOptions = "visible" | "load" | "idle"; ## Element ```typescript -interface Element extends QwikJSX.Element +type Element = JSXOutput; ``` -**Extends:** [QwikJSX.Element](#) +**References:** [JSXOutput](#jsxoutput) ## ElementChildrenAttribute @@ -760,17 +771,17 @@ interface Element extends QwikJSX.Element interface ElementChildrenAttribute ``` -| Property | Modifiers | Type | Description | -| -------------- | --------- | ---- | ------------ | -| [children?](#) | | any | _(Optional)_ | +| Property | Modifiers | Type | Description | +| ------------- | --------- | --------------------------- | ----------- | +| [children](#) | | [JSXChildren](#jsxchildren) | | ## ElementType ```typescript -type ElementType = string | ((...args: any[]) => JSXNode | null); +type ElementType = string | FunctionComponent>; ``` -**References:** [JSXNode](#jsxnode) +**References:** [FunctionComponent](#functioncomponent) ## EmbedHTMLAttributes @@ -802,6 +813,18 @@ event$: (first: T) => QRL; [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts) +## EventHandler + +A DOM event handler + +```typescript +export type EventHandler = { + bivarianceHack(event: EV, element: EL): any; +}["bivarianceHack"]; +``` + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.ts) + ## eventQrl ```typescript @@ -843,10 +866,23 @@ Fragment: FunctionComponent<{ ## FunctionComponent +Any function taking a props object that returns JSXOutput. + +The `key`, `flags` and `dev` parameters are for internal use. + ```typescript -export interface FunctionComponent

= Record> +export type FunctionComponent

= { + renderFn( + props: P, + key: string | null, + flags: number, + dev?: DevJSX, + ): JSXOutput; +}["renderFn"]; ``` +**References:** [DevJSX](#devjsx), [JSXOutput](#jsxoutput) + [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-node.ts) ## getPlatform @@ -877,10 +913,6 @@ export declare namespace h | [h(type, data, children)](#) | | | [h(sel, data, children)](#) | | -| Namespace | Description | -| ------------- | ----------- | -| [JSX](#h-jsx) | | - [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/factory.ts) ## h @@ -899,10 +931,6 @@ export declare namespace h | [h(type, data, children)](#) | | | [h(sel, data, children)](#) | | -| Namespace | Description | -| ------------- | ----------- | -| [JSX](#h-jsx) | | - [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/factory.ts) ## HrHTMLAttributes @@ -942,7 +970,7 @@ export type HTMLAttributeReferrerPolicy = ReferrerPolicy; export interface HTMLAttributes extends HTMLElementAttrs, DOMAttributes ``` -**Extends:** HTMLElementAttrs, [DOMAttributes](#domattributes)<E> +**Extends:** [HTMLElementAttrs](#htmlelementattrs), [DOMAttributes](#domattributes)<E> [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts) @@ -958,6 +986,16 @@ export type HTMLCrossOriginAttribute = [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts) +## HTMLElementAttrs + +```typescript +export interface HTMLElementAttrs extends HTMLAttributesBase, FilterBase +``` + +**Extends:** HTMLAttributesBase, FilterBase<HTMLElement> + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts) + ## HTMLFragment ```typescript @@ -1151,18 +1189,20 @@ export interface InsHTMLAttributes extends Attrs<'ins', T> ## IntrinsicAttributes ```typescript -interface IntrinsicAttributes extends QwikJSX.IntrinsicAttributes +interface IntrinsicAttributes extends QwikIntrinsicAttributes ``` -**Extends:** [QwikJSX.IntrinsicAttributes](#) +**Extends:** QwikIntrinsicAttributes ## IntrinsicElements ```typescript -interface IntrinsicElements extends QwikJSX.IntrinsicElements +export interface IntrinsicElements extends IntrinsicHTMLElements, IntrinsicSVGElements ``` -**Extends:** [QwikJSX.IntrinsicElements](#) +**Extends:** IntrinsicHTMLElements, IntrinsicSVGElements + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts) ## isSignal @@ -1179,7 +1219,7 @@ isSignal: (obj: any) => obj is Signal ```typescript jsx: >( type: T, - props: T extends FunctionComponent> + props: T extends FunctionComponent ? PROPS : Record, key?: string | number | null, @@ -1188,19 +1228,6 @@ jsx: >( [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/jsx-runtime.ts) -## JSX - -```typescript -namespace JSX -``` - -| Interface | Description | -| ----------------------------------------------------------- | ----------- | -| [Element](#h-jsx-element) | | -| [ElementChildrenAttribute](#h-jsx-elementchildrenattribute) | | -| [IntrinsicAttributes](#h-jsx-intrinsicattributes) | | -| [IntrinsicElements](#h-jsx-intrinsicelements) | | - ## JSXChildren ```typescript @@ -1227,7 +1254,7 @@ export type JSXChildren = ```typescript jsxDEV: >>( type: T, - props: T extends FunctionComponent> + props: T extends FunctionComponent ? PROPS : Record, key: string | number | null | undefined, @@ -1241,8 +1268,10 @@ jsxDEV: >>( ## JSXNode +A JSX Node, an internal structure. You probably want to use `JSXOutput` instead. + ```typescript -export interface JSXNode +export interface JSXNode ``` | Property | Modifiers | Type | Description | @@ -1252,11 +1281,30 @@ export interface JSXNode | [flags](#) | | number | | | [immutableProps](#) | | Record<any, unknown> \| null | | | [key](#) | | string \| null | | -| [props](#) | | T extends [FunctionComponent](#functioncomponent)<infer B> ? B : Record<any, unknown> | | +| [props](#) | | T extends [FunctionComponent](#functioncomponent)<infer P> ? P : Record<any, unknown> | | | [type](#) | | T | | [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-node.ts) +## JSXOutput + +Any valid output for a component + +```typescript +export type JSXOutput = + | JSXNode + | string + | number + | boolean + | null + | undefined + | JSXOutput[]; +``` + +**References:** [JSXNode](#jsxnode), [JSXOutput](#jsxoutput) + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-node.ts) + ## JSXTagName ```typescript @@ -1584,12 +1632,10 @@ export interface OlHTMLAttributes extends Attrs<'ol', T> ## OnRenderFn ```typescript -export type OnRenderFn> = ( - props: PROPS, -) => JSXNode | null; +export type OnRenderFn = (props: PROPS) => JSXOutput; ``` -**References:** [JSXNode](#jsxnode) +**References:** [JSXOutput](#jsxoutput) [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts) @@ -1649,6 +1695,43 @@ export interface ParamHTMLAttributes extends Attrs<'base', T, [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts) +## PrefetchGraph + +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +Load the prefetch graph for the container. + +Each Qwik container needs to include its own prefetch graph. + +```typescript +PrefetchGraph: (opts?: { + base?: string; + manifestHash?: string; + manifestURL?: string; +}) => import("@builder.io/qwik/jsx-runtime").JSXNode<"script">; +``` + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts) + +## PrefetchServiceWorker + +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +Install a service worker which will prefetch the bundles. + +There can only be one service worker per page. Because there can be many separate Qwik Containers on the page each container needs to load its prefetch graph using `PrefetchGraph` component. + +```typescript +PrefetchServiceWorker: (opts: { + base?: string; + path?: string; + verbose?: boolean; + fetchBundleGraph?: boolean; +}) => import("@builder.io/qwik/jsx-runtime").JSXNode<"script">; +``` + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts) + ## ProgressHTMLAttributes ```typescript @@ -1661,8 +1744,13 @@ export interface ProgressHTMLAttributes extends Attrs<'progre ## PropFnInterface +> Warning: This API is now obsolete. +> +> Use `QRL<>` instead + ```typescript export type PropFnInterface = { + __qwik_serializable__?: any; (...args: ARGS): Promise; }; ``` @@ -1671,19 +1759,22 @@ export type PropFnInterface = { ## PropFunction +Alias for `QRL`. Of historic relevance only. + ```typescript -export type PropFunction any> = - T extends (...args: infer ARGS) => infer RET - ? PropFnInterface> - : never; +export type PropFunction = QRL; ``` -**References:** [PropFnInterface](#propfninterface) +**References:** [QRL](#qrl) [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts) ## PropFunctionProps +> Warning: This API is now obsolete. +> +> Use `QRL<>` on your function props instead + ```typescript export type PropFunctionProps> = { [K in keyof PROPS]: PROPS[K] extends undefined @@ -1700,35 +1791,60 @@ export type PropFunctionProps> = { ## PropsOf -Infers `Props` from the component. +Infers `Props` from the component or tag. ```typescript -export const OtherComponent = component$(() => { - return $(() => ); -}); +export type PropsOf = COMP extends string + ? COMP extends keyof QwikIntrinsicElements + ? QwikIntrinsicElements[COMP] + : QwikIntrinsicElements["span"] + : NonNullable extends never + ? never + : COMP extends FunctionComponent + ? PROPS extends Record + ? IsAny extends true + ? never + : ObjectProps + : COMP extends Component + ? ObjectProps + : PROPS + : never; ``` -```typescript -export type PropsOf = COMP extends Component - ? NonNullable - : COMP extends FunctionComponent - ? NonNullable> - : COMP extends string - ? QwikIntrinsicElements[COMP] - : Record; -``` +**References:** [QwikIntrinsicElements](#qwikintrinsicelements), [FunctionComponent](#functioncomponent), [Component](#component) + +```tsx +const Desc = component$( + ({ desc, ...props }: { desc: string } & PropsOf<"div">) => { + return

{desc}
; + }, +); -**References:** [Component](#component), [FunctionComponent](#functioncomponent), [PublicProps](#publicprops), [QwikIntrinsicElements](#qwikintrinsicelements) +const TitleBox = component$( + ({ title, ...props }: { title: string } & PropsOf) => { + return ( + +

{title}

+
+ ); + }, +); +``` [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts) ## PublicProps -Extends the defined component PROPS, adding the default ones (children and q:slot).. +Extends the defined component PROPS, adding the default ones (children and q:slot) and allowing plain functions to QRL arguments. ```typescript -export type PublicProps> = - TransformProps & ComponentBaseProps & ComponentChildren; +export type PublicProps = (PROPS extends Record + ? Omit & _Only$ + : unknown extends PROPS + ? {} + : PROPS) & + ComponentBaseProps & + ComponentChildren; ``` **References:** [ComponentBaseProps](#componentbaseprops) @@ -1769,6 +1885,24 @@ qrl: ( [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts) +## QRLEventHandlerMulti + +> This API is provided as a beta preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +An event handler for Qwik events, can be a handler QRL or an array of handler QRLs. + +```typescript +export type QRLEventHandlerMulti = + | QRL> + | undefined + | null + | QRLEventHandlerMulti[]; +``` + +**References:** [QRL](#qrl), [EventHandler](#eventhandler), [QRLEventHandlerMulti](#qrleventhandlermulti) + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.ts) + ## QuoteHTMLAttributes ```typescript @@ -1793,6 +1927,22 @@ export type QwikAnimationEvent = NativeAnimationEvent; [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-events.ts) +## QwikAttributes + +The Qwik DOM attributes without plain handlers, for use as function parameters + +```typescript +export interface QwikAttributes extends DOMAttributesBase, QwikEvents +``` + +**Extends:** DOMAttributesBase<EL>, QwikEvents<EL, false> + +| Property | Modifiers | Type | Description | +| ----------- | --------- | ------------------------------------ | ------------ | +| [class?](#) | | [ClassList](#classlist) \| undefined | _(Optional)_ | + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.ts) + ## QwikChangeEvent > Warning: This API is now obsolete. @@ -1883,14 +2033,11 @@ export type QwikHTMLElements = { > & HTMLElementAttrs & QwikAttributes; -} & { - [unknownTag: string]: { - [prop: string]: any; - } & HTMLElementAttrs & - QwikAttributes; }; ``` +**References:** [HTMLElementAttrs](#htmlelementattrs), [QwikAttributes](#qwikattributes) + [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts) ## QwikIdleEvent @@ -1961,15 +2108,15 @@ export type QwikInvalidEvent = Event; export declare namespace QwikJSX ``` -| Interface | Description | -| ----------------------------- | ----------- | -| [Element](#) | | -| [ElementChildrenAttribute](#) | | -| [IntrinsicAttributes](#) | | -| [IntrinsicElements](#) | | +| Interface | Description | +| ------------------------------------------------------------- | ----------- | +| [ElementChildrenAttribute](#qwikjsx-elementchildrenattribute) | | +| [IntrinsicAttributes](#qwikjsx-intrinsicattributes) | | +| [IntrinsicElements](#) | | | Type Alias | Description | | ----------------------------------- | ----------- | +| [Element](#qwikjsx-element) | | | [ElementType](#qwikjsx-elementtype) | | [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik.ts) @@ -2128,7 +2275,7 @@ export type QwikWheelEvent = NativeWheelEvent; ## ReadonlySignal ```typescript -export type ReadonlySignal = Readonly>; +export type ReadonlySignal = Readonly>; ``` **References:** [Signal](#signal) @@ -2144,7 +2291,7 @@ Use this method to render JSX. This function does reconciling which means it alw ```typescript render: ( parent: Element | Document, - jsxNode: JSXNode | FunctionComponent, + jsxOutput: JSXOutput | FunctionComponent, opts?: RenderOptions, ) => Promise; ``` @@ -2249,7 +2396,7 @@ const Cmp = component$(() => { ``` ```typescript -Resource: (props: ResourceProps) => JSXNode; +Resource: (props: ResourceProps) => JSXOutput; ``` [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/use/use-resource.ts) @@ -2317,9 +2464,9 @@ export interface ResourceProps | Property | Modifiers | Type | Description | | ---------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------ | -| [onPending?](#) | | () => [JSXNode](#jsxnode) | _(Optional)_ | -| [onRejected?](#) | | (reason: Error) => [JSXNode](#jsxnode) | _(Optional)_ | -| [onResolved](#) | | (value: T) => [JSXNode](#jsxnode) | | +| [onPending?](#) | | () => [JSXOutput](#jsxoutput) | _(Optional)_ | +| [onRejected?](#) | | (reason: Error) => [JSXOutput](#jsxoutput) | _(Optional)_ | +| [onResolved](#) | | (value: T) => [JSXOutput](#jsxoutput) | | | [value](#) | readonly | [ResourceReturn](#resourcereturn)<T> \| [Signal](#signal)<Promise<T> \| T> \| Promise<T> | | [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/use/use-resource.ts) @@ -2902,10 +3049,24 @@ export interface SVGAttributes extends AriaAttribut export interface SVGProps extends SVGAttributes, QwikAttributes ``` -**Extends:** [SVGAttributes](#svgattributes), QwikAttributes<T> +**Extends:** [SVGAttributes](#svgattributes), [QwikAttributes](#qwikattributes)<T> [Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-generated.ts) +## sync$ + +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +Extract function into a synchronously loadable QRL. + +NOTE: Synchronous QRLs functions can't close over any variables, including exports. + +```typescript +sync$: (fn: T) => SyncQRL; +``` + +[Edit this section](https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/qrl/qrl.public.ts) + ## TableHTMLAttributes ```typescript diff --git a/packages/docs/src/routes/demo/cookbook/algolia-search/index.tsx b/packages/docs/src/routes/demo/cookbook/algolia-search/index.tsx new file mode 100644 index 000000000000..03c1a730453f --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/algolia-search/index.tsx @@ -0,0 +1,166 @@ +import { $, component$, useSignal, useStylesScoped$ } from '@builder.io/qwik'; + +type AlgoliaResult = { + hits: { + type: string; + anchor?: string; + content?: string; + url: string; + }[]; +}; + +export default component$(() => { + useStylesScoped$(` + .search { + font-size: 100%; + width: calc(100% - 38px); + border-radius: 0.5rem; + border: 1px black solid; + padding: 1rem; + color: black; + outline: none; + } + + .search-button { + border: none; + padding: 6px 0px; + cursor: pointer; + background-color: transparent; + position: absolute; + right: 2.4rem; + padding: 0.85rem 0.5rem 0.4rem 0.5rem; + outline: none; + } + + .list { + display: flex; + flex-direction: column; + align-items: center; + } + + .list li { + counter-increment: cardCount; + display: flex; + color: white; + margin-top: 1rem; + margin-bottom: 1rem; + max-width: 500px; + } + + .list li::before { + content: counter(cardCount, decimal-leading-zero); + background: white; + color: var(--cardColor); + font-size: 2em; + font-weight: 700; + transform: translateY(calc(-1 * 1rem)); + margin-right: calc(-1 * 1rem); + z-index: 1; + display: flex; + align-items: center; + padding-inline: 0.5em; + border: 1px solid black; + } + + .list li .content { + background-color: var(--cardColor); + display: grid; + padding: 0.5em calc(1em + 1.5rem) 0.5em calc(1em + 1rem); + grid-template-areas: + "icon title" + "icon text"; + gap: 0.25em; + clip-path: polygon( + 0 0, + calc(100% - 1.5rem) 0, + 100% 50%, + calc(100% - 1.5rem) 100%, + calc(100% - 1.5rem) calc(100% + 1rem), + 0 calc(100% + 1rem) + ); + } + + .list li .content .title { + grid-area: title; + font-size: 1.25em; + } + + .list li .content .text { + grid-area: text; + color: black; + } + `); + const termSignal = useSignal(''); + const hitsSig = useSignal([]); + + const onSearch = $(async (query: string) => { + const algoliaURL = new URL( + `/1/indexes/${import.meta.env.VITE_ALGOLIA_INDEX}/query`, + `https://${import.meta.env.VITE_ALGOLIA_APP_ID}-dsn.algolia.net` + ); + const response = await fetch(algoliaURL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Algolia-Application-Id': import.meta.env.VITE_ALGOLIA_APP_ID!, + 'X-Algolia-API-Key': import.meta.env.VITE_ALGOLIA_SEARCH_KEY!, + }, + body: JSON.stringify({ query }), + }); + const algoliaResult: AlgoliaResult = await response.json(); + hitsSig.value = algoliaResult.hits; + }); + + return ( +
+
+
+ { + if (e.key === 'Enter') { + onSearch(termSignal.value); + } + }} + /> + +
+
+
+ {hitsSig.value.map(({ anchor, content, url }, key) => ( +
  • +
    +
    + {(anchor || content || url || '').substring(0, 30)} +
    + + Documentation link + +
    +
  • + ))} +
    +
    + ); +}); diff --git a/packages/docs/src/routes/demo/cookbook/combine-request-handlers/index.tsx b/packages/docs/src/routes/demo/cookbook/combine-request-handlers/index.tsx new file mode 100644 index 000000000000..de9999fe1d0e --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/combine-request-handlers/index.tsx @@ -0,0 +1,32 @@ +import type { RequestHandler } from '@builder.io/qwik-city'; + +/** + * Combines multiple request handlers into a single request handler. + * + * The handlers will be called in order: + * + * 1. Handler1 before next() + * 2. Handler2 before next() + * 3. Handler3 before next() + * 4. Next() + * 5. Handler3 after next() + * 6. Handler2 after next() + * 7. Handler1 after next() + * + * @public + */ + +export const combineRequestHandlers = + (...handlers: RequestHandler[]): RequestHandler => + async (originalContext) => { + let lastNext = originalContext.next; + for (let i = handlers.length - 1; i >= 0; i--) { + const currentHandler = handlers[i]; + const nextInChain = lastNext; + lastNext = async () => { + await currentHandler({ ...originalContext, next: nextInChain }); + }; + } + + await lastNext(); + }; diff --git a/packages/docs/src/routes/demo/cookbook/nav-link/example/index.tsx b/packages/docs/src/routes/demo/cookbook/nav-link/example/index.tsx new file mode 100644 index 000000000000..174c8b0d4469 --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/nav-link/example/index.tsx @@ -0,0 +1,23 @@ +import { component$ } from '@builder.io/qwik'; +import { NavLink } from '..'; + +export default component$(() => { + return ( + <> + Links +
    + + /docs + +
    +
    + + /demo/cookbook/nav-link/example/ + +
    + + ); +}); diff --git a/packages/docs/src/routes/demo/cookbook/nav-link/index.tsx b/packages/docs/src/routes/demo/cookbook/nav-link/index.tsx new file mode 100644 index 000000000000..563fffc30a18 --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/nav-link/index.tsx @@ -0,0 +1,35 @@ +import { Slot, component$ } from '@builder.io/qwik'; +import { Link, useLocation, type LinkProps } from '@builder.io/qwik-city'; + +type NavLinkProps = LinkProps & { activeClass?: string }; + +export const NavLink = component$( + ({ activeClass, ...props }: NavLinkProps) => { + const location = useLocation(); + const toPathname = props.href ?? ''; + const locationPathname = location.url.pathname; + + const startSlashPosition = + toPathname !== '/' && toPathname.startsWith('/') + ? toPathname.length - 1 + : toPathname.length; + const endSlashPosition = + toPathname !== '/' && toPathname.endsWith('/') + ? toPathname.length - 1 + : toPathname.length; + const isActive = + locationPathname === toPathname || + (locationPathname.endsWith(toPathname) && + (locationPathname.charAt(endSlashPosition) === '/' || + locationPathname.charAt(startSlashPosition) === '/')); + + return ( + + + + ); + } +); diff --git a/packages/docs/src/routes/demo/cookbook/portal/portal-provider.tsx b/packages/docs/src/routes/demo/cookbook/portal/portal-provider.tsx index cec40c6df5af..76b38fd62def 100644 --- a/packages/docs/src/routes/demo/cookbook/portal/portal-provider.tsx +++ b/packages/docs/src/routes/demo/cookbook/portal/portal-provider.tsx @@ -10,8 +10,8 @@ import { type ContextId, type QRL, type Signal, + type JSXOutput, } from '@builder.io/qwik'; -import { type JSXNode } from '@builder.io/qwik/jsx-runtime'; import CSS from './portal-provider.css?inline'; // Define public API for opening up Portals @@ -24,7 +24,7 @@ export const PortalAPI = createContextId< * @returns A function used for closing the portal. */ QRL< - (name: string, jsx: JSXNode, contexts?: ContextPair[]) => () => void + (name: string, jsx: JSXOutput, contexts?: ContextPair[]) => () => void > >('PortalProviderAPI'); @@ -39,7 +39,7 @@ const PortalsContextId = createContextId>('Portals'); interface Portal { name: string; - jsx: JSXNode; + jsx: JSXOutput; close: QRL<() => void>; contexts: Array>; } @@ -51,7 +51,7 @@ export const PortalProvider = component$(() => { // Provide the public API for the PopupManager for other components. useContextProvider( PortalAPI, - $((name: string, jsx: JSXNode, contexts?: ContextPair[]) => { + $((name: string, jsx: JSXOutput, contexts?: ContextPair[]) => { const portal: Portal = { name, jsx, @@ -94,7 +94,7 @@ export const Portal = component$<{ name: string }>(({ name }) => { }); export const WrapJsxInContext = component$<{ - jsx: JSXNode; + jsx: JSXOutput; contexts: Array>; }>(({ jsx, contexts }) => { contexts.forEach(({ id, value }) => { diff --git a/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/index.tsx b/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/index.tsx new file mode 100644 index 000000000000..35a40a0e9a40 --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/index.tsx @@ -0,0 +1,31 @@ +import { component$ } from '@builder.io/qwik'; +import { Form } from '@builder.io/qwik-city'; +import { useCommonRouteAction, useCommonRouteLoader } from './shared/loaders'; + +// As mentioned, here we are re-exporting them +export { useCommonRouteAction, useCommonRouteLoader } from './shared/loaders'; + +export default component$(() => { + const commonRouteAction = useCommonRouteAction(); + const commonRouteLoader = useCommonRouteLoader(); + + return ( +
    +
    +
    CommonRouteAction
    +
    response:
    +
    + {commonRouteAction.value?.data.join(' ') || ''} +
    + +
    +
    +
    CommonRouteLoader
    +
    response:
    +
    + {commonRouteLoader.value.join(' ')} +
    +
    +
    + ); +}); diff --git a/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/shared/loaders.ts b/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/shared/loaders.ts new file mode 100644 index 000000000000..8c0f1e17bf8a --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/shared/loaders.ts @@ -0,0 +1,11 @@ +import { routeAction$, routeLoader$ } from '@builder.io/qwik-city'; + +export const useCommonRouteAction = routeAction$(async () => { + // ... + return { success: true, data: ['Qwik', 'Partytown'] }; +}); + +export const useCommonRouteLoader = routeLoader$(async () => { + // ... + return ['Mitosis', 'Builder.io']; +}); diff --git a/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/third-party/index.tsx b/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/third-party/index.tsx new file mode 100644 index 000000000000..ebd7e7a5556c --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/third-party/index.tsx @@ -0,0 +1,13 @@ +import { component$ } from '@builder.io/qwik'; +import { ThirdPartyPaymentComponent } from './third-party-library'; + +// As mentioned, here we are re-exporting the third-party loader +export { useThirdPartyPaymentLoader } from './third-party-library'; + +export default component$(() => { + return ( +
    + +
    + ); +}); diff --git a/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/third-party/third-party-library.tsx b/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/third-party/third-party-library.tsx new file mode 100644 index 000000000000..c9b03718c3b4 --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/re-exporting-loaders/third-party/third-party-library.tsx @@ -0,0 +1,48 @@ +import { component$ } from '@builder.io/qwik'; +import { routeLoader$ } from '@builder.io/qwik-city'; + +export const useThirdPartyPaymentLoader = routeLoader$(() => { + return { name: 'John Doe' }; +}); + +export const ThirdPartyPaymentComponent = component$(() => { + const thirdPartyPaymentLoader = useThirdPartyPaymentLoader(); + return ( +
    +
    +
    +
    +

    Name

    +

    {thirdPartyPaymentLoader.value.name}

    +
    + +
    +
    +

    Card Number

    +

    4642 3489 9867 7632

    +
    +
    +
    +
    +

    Valid

    +

    11/15

    +
    +
    +

    Expiry

    +

    03/25

    +
    +
    +

    CVV

    +

    ···

    +
    +
    +
    +
    +
    + ); +}); diff --git a/packages/docs/src/routes/demo/cookbook/sync-event/index.tsx b/packages/docs/src/routes/demo/cookbook/sync-event/index.tsx new file mode 100644 index 000000000000..d3cf9049fcce --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/sync-event/index.tsx @@ -0,0 +1,38 @@ +import { component$, useSignal, sync$, $ } from '@builder.io/qwik'; + +export default component$(() => { + const shouldPreventDefault = useSignal(true); + return ( + + ); +}); diff --git a/packages/docs/src/routes/demo/events/custom-event/index.tsx b/packages/docs/src/routes/demo/events/custom-event/index.tsx index 2d9b6bf6fe00..278cf2528452 100644 --- a/packages/docs/src/routes/demo/events/custom-event/index.tsx +++ b/packages/docs/src/routes/demo/events/custom-event/index.tsx @@ -1,4 +1,4 @@ -import { component$, Slot, useStore } from '@builder.io/qwik'; +import { component$, type QRL, Slot, useStore } from '@builder.io/qwik'; export default component$(() => { return ( @@ -9,7 +9,7 @@ export default component$(() => { }); type ButtonProps = { - onTripleClick$: () => void; + onTripleClick$: QRL<() => void>; }; export const Button = component$(({ onTripleClick$ }) => { diff --git a/packages/docs/src/routes/demo/resumability/component.tsx b/packages/docs/src/routes/demo/resumability/component.tsx index b6df7ea7ab66..80123ae60ad9 100644 --- a/packages/docs/src/routes/demo/resumability/component.tsx +++ b/packages/docs/src/routes/demo/resumability/component.tsx @@ -1,5 +1,5 @@ import { - type QwikIntrinsicElements, + type PropsOf, component$, useStylesScoped$, Slot, @@ -214,7 +214,7 @@ export const UnderstandingResumability = component$(() => { ); }); -export function ReadyIcon(props: QwikIntrinsicElements['svg'], key: string) { +export function ReadyIcon(props: PropsOf<'svg'>, key: string) { return ( { target.dispatchEvent( new CustomEvent('hover', { bubbles: true, - detail: async (jsx: JSXNode) => { + detail: async (jsx: JSXOutput) => { if (state.close) return; state.currentTarget = e.target as HTMLElement; state.close = noSerialize(await portal('popup', jsx)); @@ -83,4 +83,4 @@ function isPortal(element: HTMLElement) { return element.closest('[data-portal]') != null; } -export type HoverEvent = CustomEvent<(jsx: JSXNode) => void>; +export type HoverEvent = CustomEvent<(jsx: JSXOutput) => void>; diff --git a/packages/docs/src/routes/demo/state/counter-store/index.tsx b/packages/docs/src/routes/demo/state/counter-store/index.tsx index 822090a94ab1..fff4ba002d5e 100644 --- a/packages/docs/src/routes/demo/state/counter-store/index.tsx +++ b/packages/docs/src/routes/demo/state/counter-store/index.tsx @@ -1,12 +1,16 @@ import { component$, useStore } from '@builder.io/qwik'; export default component$(() => { - const state = useStore({ count: 0 }); + const state = useStore({ count: 0, name: 'Qwik' }); return ( <>

    Count: {state.count}

    + (state.name = el.value)} + /> ); }); diff --git a/packages/docs/src/routes/demo/state/no-serialize/index.tsx b/packages/docs/src/routes/demo/state/no-serialize/index.tsx index f77bb419eee4..489300084fad 100644 --- a/packages/docs/src/routes/demo/state/no-serialize/index.tsx +++ b/packages/docs/src/routes/demo/state/no-serialize/index.tsx @@ -15,6 +15,7 @@ export default component$(() => { monacoInstance: undefined, }); + // eslint-disable-next-line qwik/no-use-visible-task useVisibleTask$(() => { const editor = monacoEditor.create(editorRef.value!, { value: 'Hello, world!', diff --git a/packages/docs/src/routes/demo/tsconfig.json b/packages/docs/src/routes/demo/tsconfig.json index a70f3b275f36..adcf381e0e22 100644 --- a/packages/docs/src/routes/demo/tsconfig.json +++ b/packages/docs/src/routes/demo/tsconfig.json @@ -18,9 +18,9 @@ "noEmit": true, "types": ["node", "vite/client"], "paths": { - "~/*": ["./src/*"] - } + "~/*": ["./src/*"], + }, }, "files": ["./.eslintrc.cjs", "**/*.jsx"], - "include": ["."] + "include": ["."], } diff --git a/packages/docs/src/routes/docs/(qwik)/components/events/index.mdx b/packages/docs/src/routes/docs/(qwik)/components/events/index.mdx index e3cb7a691c87..af7e3716b036 100644 --- a/packages/docs/src/routes/docs/(qwik)/components/events/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/components/events/index.mdx @@ -20,7 +20,8 @@ contributors: - mrhoodz - julianobrasil - maiieul -updated_at: '2023-10-03T18:53:59Z' + - Balastrong +updated_at: '2024-01-09T20:55:11Z' created_at: '2023-03-20T23:45:13Z' --- @@ -54,15 +55,15 @@ export default component$(() => { ``` +You can also use `bind:propertyName` to conveniently have a [two-way binding](/docs/(qwik)/components/rendering/index.mdx#bind-attribute) between a signal and an input element. + Notice that `onClick$` ends with [`$`](/docs/(qwik)/advanced/dollar/index.mdx). This is a hint to both the [Optimizer](/docs/(qwik)/advanced/optimizer/index.mdx) and the developer that a special transformation occurs at this location. The presence of the `$` suffix implies a lazy-loaded boundary here. The code associated with the `click` handler will not be loaded into the JavaScript VM until the user triggers the `click` event, however, it will be [loaded into the browser cache](/docs/advanced/speculative-module-fetching/) eagerly so as not to cause delays on first interactions. > In real-world applications, the listener may refer to complex code. By creating a lazy-loaded boundary (with the `$`), Qwik can tree-shake all of the code behind the click listener and delay its loading until the user clicks the button. ## Reusing event handlers -If we want to reuse the same event handler for multiple elements or event, we need to import `$` from `@builder.io/qwik` and wrap the event handler in it. - -This way we must extract the event handler into a [`QRLs`](/docs/advanced/qrl/) and pass it to the event listener. +If we want to reuse the same event handler for multiple elements or events, we need to wrap the event handler into the `$()` function exported by `@builder.io/qwik` to transform it into a [`QRL`](/docs/advanced/qrl/). ```tsx {5} /increment/#a @@ -243,9 +244,9 @@ export default component$(() => { ## Custom event props -When creating your components it is often useful to pass custom event props that look like event handlers, (even though they are not DOM events, only callbacks). Component boundaries in Qwik must be serializable for the optimizer to split them up into separate chunks, and functions are not serializable unless they are converted to a QRL using the `$` sign. +When creating your components it is often useful to pass custom event props that look like event handlers, (even though they are not DOM events, only callbacks). Component boundaries in Qwik must be serializable for the optimizer to split them up into separate chunks, and functions are not serializable unless they are converted to a QRL. -When typing `component$` with the Generics syntax, Qwik will handle the type transformation automatically for you. +For example, listening for triple click events, which html cannot do by default, would require creating an `onTripleClick$` custom event prop. ```tsx import { component$, Slot, useStore } from '@builder.io/qwik'; @@ -253,13 +254,13 @@ import { component$, Slot, useStore } from '@builder.io/qwik'; export default component$(() => { return ( ); }); type ButtonProps = { - onTripleClick$: () => void; + onTripleClick$: QRL<() => void>; }; export const Button = component$(({ onTripleClick$ }) => { @@ -289,16 +290,10 @@ export const Button = component$(({ onTripleClick$ }) => { ); }); - ``` -⚠️ When using type annotations, we need to wrap the event type with `PropFunction` in order to tell TypeScript that the function can't be called synchronously. -```tsx -component$(({ onTripleClick$ } : { onTripleClick$?: PropFunction<() => void> }) => { - ... -}); -``` +> Notice the use of the `QRL` type in `onTripleClick$: QRL<() => void>;`. It is like wrapping a function in `$()` but at the type level. If you had `const greet = $(() => "hi 👋");` and hovered over 'greet', you would see that 'greet' is of type `QRL<() => "hi 👋">` ## Window and Document events diff --git a/packages/docs/src/routes/docs/(qwik)/components/overview/index.mdx b/packages/docs/src/routes/docs/(qwik)/components/overview/index.mdx index 0115edef3a0c..6d5869a412cd 100644 --- a/packages/docs/src/routes/docs/(qwik)/components/overview/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/components/overview/index.mdx @@ -26,7 +26,10 @@ contributors: - mrhoodz - eecopa - drumnistnakano -updated_at: '2023-10-03T18:53:59Z' + - maiieul + - wmertens + - aendel +updated_at: '2023-12-14T18:38:38Z' created_at: '2023-03-20T23:45:13Z' --- @@ -107,7 +110,7 @@ export default component$(() => { ## Props -Props are used to pass data from the parent into the component. Props are accesible via the `props` argument to the component$ function. +Props are used to pass data from the parent into the component. Props are accessible via the `props` argument to the component$ function. In this example a component `Item` declares optional `name`, `quantity`, `description`, and `price`. @@ -375,6 +378,30 @@ As the name implies, inline components are best used sparingly for lightweight pieces of markup since they offer the convenience of being bundled with the parent component. +## Polymorphic components + +When you want to output a different type of element depending on props, you can do it using something like this: + +```tsx +export const Poly = component$( + ({ + as: Cmp = 'div' as C, + ...props + }: { as?: C } & PropsOf) => { + return hi; + } +); + +// These all work with correct types +<> + Hello from a div + Blog + console.log(el.value)} /> + + +``` + +Note the `string extends C`, that is only true when TypeScript couldn't infer the type from the `as` prop, and so you can specify the default type. ## API Overview @@ -393,9 +420,9 @@ bundled with the parent component. ### Events -- [`useOn()`](../events/index.mdx) - appends a listener to the current component programmatically -- [`useOnWindow()`](../events/index.mdx) - appends a listener to the window object programmatically -- [`useOnDocument()`](../events/index.mdx) - appends a listener to the document object programmatically +- [`useOn()`](../events/index.mdx#useonwindowdocument-hook) - appends a listener to the current component programmatically +- [`useOnWindow()`](../events/index.mdx#useonwindowdocument-hook) - appends a listener to the window object programmatically +- [`useOnDocument()`](../events/index.mdx#useonwindowdocument-hook) - appends a listener to the document object programmatically ### Tasks/Lifecycle diff --git a/packages/docs/src/routes/docs/(qwik)/components/rendering/index.mdx b/packages/docs/src/routes/docs/(qwik)/components/rendering/index.mdx index efc9280e8411..9c16125c368c 100644 --- a/packages/docs/src/routes/docs/(qwik)/components/rendering/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/components/rendering/index.mdx @@ -16,6 +16,8 @@ contributors: - igorbabko - mrhoodz - thejackshelton + - Balastrong + - aendel updated_at: '2023-09-19T17:37:26Z' created_at: '2023-03-20T23:45:13Z' --- @@ -37,7 +39,7 @@ Qwik presents a few differences from other JSX frameworks: - Components can use the `useSignal` hook to create reactive state. - Event handlers are declared with the `$` suffix. - For ``, the `onChange` event is called `onInput$` in Qwik. -- HTML attributes are prefered. `class` instead of `className`. `for` instead of `htmlFor`. +- HTML attributes are preferred. `class` instead of `className`. `for` instead of `htmlFor`. ```tsx /component$/#a /onClick$/#b /class/#c import { component$, useSignal } from '@builder.io/qwik'; @@ -169,6 +171,8 @@ export default component$(() => { }) ``` +> It does not work with `useStore` since it does not return a signal, but you can still use the manual approach ( value + onInput$ ) as shown below. + The `bind:` is compiled away by the Qwik optimizer to a property set and an event handler, ie, it is just syntax sugar. ```tsx /bind:value/ /bind:checked/ /firstName/#a /acceptConditions/#b diff --git a/packages/docs/src/routes/docs/(qwik)/components/state/index.mdx b/packages/docs/src/routes/docs/(qwik)/components/state/index.mdx index 01450f6273dd..5f1d06e3136c 100644 --- a/packages/docs/src/routes/docs/(qwik)/components/state/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/components/state/index.mdx @@ -32,6 +32,7 @@ contributors: - fabian-hiller - julianobrasil - aivarsliepa + - Balastrong updated_at: '2023-10-04T21:48:45Z' created_at: '2023-03-20T23:45:13Z' --- @@ -82,12 +83,16 @@ Use `useStore(initialStateObject)` hook to create a reactive object. It takes an import { component$, useStore } from '@builder.io/qwik'; export default component$(() => { - const state = useStore({ count: 0 }); + const state = useStore({ count: 0, name: 'Qwik' }); return ( <>

    Count: {state.count}

    + (state.name = el.value)} + /> ); }); @@ -131,8 +136,8 @@ export default component$(() => { Add to list
      - {store.list.map((item, index) => ( -
    • {item}
    • + {store.list.map((item, key) => ( +
    • {item}
    • ))}
    @@ -404,9 +409,7 @@ export default component$(() => {
    ```tsx > import { component$, useSignal, useTask$ } from '@builder.io/qwik'; > import { isServer } from '@builder.io/qwik/build'; -> +> > export default component$(() => { > const text = useSignal('Initial text'); > const isBold = useSignal(false); -> +> > useTask$(({ track }) => { > track(() => text.value); > if (isServer) { @@ -340,7 +341,7 @@ Sometimes a task needs to run only on the browser and after rendering, in that c > isBold.value = true; > delay(1000).then(() => (isBold.value = false)); > }); -> +> > return ( >
    >
    > ); > }); -> +> > const delay = (time: number) => new Promise((res) => setTimeout(res, time)); > ``` >
    diff --git a/packages/docs/src/routes/docs/(qwik)/getting-started/index.mdx b/packages/docs/src/routes/docs/(qwik)/getting-started/index.mdx index c76829482aaa..4a25eb437945 100644 --- a/packages/docs/src/routes/docs/(qwik)/getting-started/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/getting-started/index.mdx @@ -57,7 +57,7 @@ To get started with Qwik locally, you need the following: ## Create an app using the CLI -First, create a Qwik application with the Qwik CLI, which generates a blank starter so that you can quickly familiarize yourself with it. +First, create a Qwik application with the Qwik CLI, which generates a blank starter so that you can quickly familiarize yourself with it. You can use this same command to create either Qwik or Qwik City project. Run the Qwik CLI in your shell. Qwik supports npm, yarn, pnpm and bun. Choose the package manager you prefer and run one of the following commands: @@ -76,7 +76,7 @@ Start the development server: npm start pnpm start yarn start -bun start +bun start (on windows: bun run start) ``` ## Qwik Joke App diff --git a/packages/docs/src/routes/docs/(qwikcity)/action/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/action/index.mdx index 6d6b70b42522..df215c7eee7d 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/action/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/action/index.mdx @@ -22,7 +22,8 @@ contributors: - aivarsliepa - wtlin1228 - adamdbradley -updated_at: '2023-10-03T18:53:23Z' + - gioboa +updated_at: '2023-12-15T11:00:00Z' created_at: '2023-03-20T23:45:13Z' --- @@ -394,6 +395,8 @@ We recommend starting with `routeAction$()` and only use `globalAction$()` when `routeAction$()` can only be declared inside the `src/routes` folder, in a `layout.tsx` or `index.tsx` file, and they MUST be exported, just like a `routeLoader$()`. Since `routeAction$()`s are only accessible within the route it's declared, they are recommended when the action needs to access some user data, or it's a protected route. Think about it like a "private" action. +> If you want to manage common reusable routeAction$() it is essential that this function is re-exported from within 'layout.tsx' or 'index.tsx file of the existing route otherwise it will not run or throw exception. For more information [check the cookbook](/docs/cookbook/re-exporting-loaders/index.mdx). + ```tsx title="src/routes/form/index.tsx" import { routeAction$ } from '@builder.io/qwik-city'; diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/complex-forms/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/complex-forms/index.mdx index fa190a77c4e6..7f3918fe78d8 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/complex-forms/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/advanced/complex-forms/index.mdx @@ -4,6 +4,7 @@ description: Learn how to create complex forms with arrays and objects. contributors: - ulic75 - hamatoyogi + - aendel updated_at: '2023-08-21T21:04:20Z' created_at: '2023-08-21T21:04:20Z' --- @@ -18,7 +19,7 @@ Complex forms would typically contain data in a structure that is more than just ### Objects -Nested items can be created by seperating the items with `.` (dot) in the name. For example, `person.name` would be converted to `{ person: { name: 'sam' } }`. +Nested items can be created by separating the items with `.` (dot) in the name. For example, `person.name` would be converted to `{ person: { name: 'sam' } }`. ### Arrays @@ -50,7 +51,7 @@ The key to creating a complex form is in the naming of the inputs. Below would b #### Output object -The after submiting the form the data would be parsed in an object like this: +The after submitting the form the data would be parsed in an object like this: ```json { "person": [ diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx index 68e80c912265..ed265bef9734 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx @@ -11,6 +11,7 @@ contributors: - thejackshelton - zanettin - wtlin1228 + - aendel updated_at: '2023-06-25T19:43:33Z' created_at: '2023-03-20T23:45:13Z' --- @@ -164,11 +165,10 @@ Another important note is that Qwik City's request intercepting is _only_ for Qw So while Qwik City does provide a way to help prefetch and cache bundles, it does not take full control of the app's service worker. This still allows developers to add their service worker logic without conflicting with Qwik. -## Disabled During Development and Preview +## Disabled During Development -One gotcha during development and using Vite's preview mode is that the service worker is disabled which also disables speculative module fetching. During development we want to always ensure the latest development code is being used, rather than previous code. +Speculative module fetching only kicks in preview or on a production build. In development, the service worker is disabled which also disables speculative module fetching. This is because during development we want to always ensure the latest development code is being used, rather than what's been previously cached. -Speculative module fetching only kicks on a production build. To see speculative module fetching in action you'll need to run the production build on a server other than development or preview. ### HTTP Cache vs. Service Worker Cache @@ -182,7 +182,7 @@ Additionally, when "Empty Cache and Hard Reload" is used, the browser sends a `n ### Emptying the Service Worker Cache -The recommended way to test specualtive module fetching is to: +The recommended way to test speculative module fetching is to: - **Unregister the service worker**: In Chrome DevTools, go to the Application tab, and under Service Workers, click the "Unregister" link for the for your site's service worker. - **Delete the "QwikBuild" Cache Storage**: In Chrome DevTools, go to the Application tab, and under Cache Storage on the left side, right click "Delete" on the "QwikBuild" cache storage. diff --git a/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx index 060673a02051..3ea2e4c0f293 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx @@ -312,6 +312,34 @@ test.each(cases)('should render card with %s %s', async ({text, link}) => { }); ``` +> a `goto` prop can be passed to customize the `navigate` behavior during tests + +```tsx title="src/components/button.spec.tsx" +import { $ } from '@builder.io/qwik'; +import { createDOM } from '@builder.io/qwik/testing'; +import { test, expect, vi } from 'vitest'; + +// Component with one prop. Uses useNavigate internally. Omitted for brevity +import { Button } from '../button'; + +const goto = vi.fn(async (path, options) => { + console.log(`Navigating to ${path} with ${options}`); +}); + +test('should render the button and navigate', async () => { + const { screen, render, userEvent } = await createDOM(); + const goto$ = $(goto); + await render( + + - +
    {signal.value > 0 ? 'Bigger than zero' : 'Smaller than zero'} - Current value: {signal.value} @@ -62,11 +63,25 @@ export default component$(() => { }); ``` -## Avoid registering DOM events in `useVisibleTask$()` +## Use `useVisibleTask$()` as a last resort + +Although convenient, `useVisibleTask$()` runs code eagerly and blocks the main thread, preventing user interaction until the task is finished. You can think of it as an escape hatch. + +When in doubt, instead of "useVisibleTask$()" use: +- `useTask$()` -> perform code execution in SSR mode. +- `useOn()` -> listen to events on the root element of `the current component`. +- `useOnWindow()` -> listen to events on the `window` object. +- `useOnDocument()` -> listen to events on the `document` object. + +Sometimes though, it is the only way to achieve the result. + +In that case, you can add `// eslint-disable-next-line qwik/no-use-visible-task` to the line before "useVisibleTask$" to remove the warning. + +### Register DOM events with `useOn()`, `useOnWindow()`, or `useOnDocument()` Qwik allows to register event listeners in a declarative way, using the `useOn()` or using JSX. -When using `useVisibleTask` to programmatically register events, we are downloading and executing JavaScript eagerly, even if the event is not triggered. +When using `useVisibleTask$()` to programmatically register events, we are downloading and executing JavaScript eagerly, even if the event is not triggered. ```tsx title="Suboptimal implementation" // Don't do this! @@ -98,12 +113,6 @@ useOnDocument( ); ``` -When in doubt, instead of `useVisibleTask$()` use: -- `useOn()`: listen to events on the root element of `the current component`. -- `useOnWindow()`: listen to events on the `window` object. -- `useOnDocument()`: listen to events on the `document` object. - - ## Avoid accessing the location from the `window` object Don't access `window.location` directly, use `useLocation()` hook instead. diff --git a/packages/docs/src/routes/docs/(qwikcity)/guides/bundle/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/guides/bundle/index.mdx index 0f379d20b86b..c7db393291bd 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/guides/bundle/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/guides/bundle/index.mdx @@ -157,6 +157,8 @@ Qwik apps employ a service worker to ensure that bundles are prefetched into the See [Speculative Module Fetching](/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx). +> Notice that Service Worker feature is available only in secure contexts (HTTPS), in some or all supporting browsers. See the [`serviceWorker` property API specs](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/serviceWorker). + ## Events All information regarding when the symbols are loaded can be observed by listening to the following custom events: diff --git a/packages/docs/src/routes/docs/(qwikcity)/guides/mdx/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/guides/mdx/index.mdx index 8d62851f13de..2b1b65b3465e 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/guides/mdx/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/guides/mdx/index.mdx @@ -33,7 +33,7 @@ src/ └── index.mdx # https://example.com/some/path ``` -```Markdown title="src/routes/some/path/index.mdx" +```markdown title="src/routes/some/path/index.mdx" --- title: Hello World Title --- @@ -59,7 +59,7 @@ src/ └── index.mdx # https://example.com/some/path ``` -```Markdown title="src/routes/some/path/index.mdx" +```markdown title="src/routes/some/path/index.mdx" --- title: Hello World Title --- @@ -161,7 +161,7 @@ The above example will generate the following HTML code. ## Reading frontmatter data Frontmatter keys are accessible by leveraging the `useDocumentHead()` hook. -```Markdown title="src/routes/some/path/index.mdx" +```markdown title="src/routes/some/path/index.mdx" --- title: Hello World Title tags: diff --git a/packages/docs/src/routes/docs/(qwikcity)/guides/qwik-nutshell/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/guides/qwik-nutshell/index.mdx index 3b373b5a1c48..9c529842a200 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/guides/qwik-nutshell/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/guides/qwik-nutshell/index.mdx @@ -1,6 +1,6 @@ --- title: Qwik in a nutshell | Introduction -description: Learn the general concepts of Qwik in this shoet introduction guide. +description: Learn the general concepts of Qwik in this short introduction guide. contributors: - manucorporat - AnthonyPAlicea @@ -15,6 +15,7 @@ contributors: - iancharlesdouglas - adamdbradley - hamatoyogi + - aendel updated_at: '2023-10-03T18:53:23Z' created_at: '2023-03-30T19:49:50Z' --- @@ -36,7 +37,7 @@ Qwik components are very similar to React components. They are functions that re import { component$, Slot } from '@builder.io/qwik'; import type { ClassList } from '@builder.io/qwik' -export component$((props: { class?: ClassList }) => { // ✅ +export const MyOtherComponent = component$((props: { class?: ClassList }) => { // ✅ return
    ; }); ``` @@ -72,7 +73,7 @@ export const MyComponent = component$((props: MyComponentProps) => { }} >

    Count: {count.value}

    - {/* ✅ */} + {/* ✅ */} {count.value > 10 &&

    Count is greater than 10

    } {count.value > 10 ?

    Count is greater than 10

    :

    Count is less than 10

    }
    diff --git a/packages/docs/src/routes/docs/(qwikcity)/guides/react-cheat-sheet/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/guides/react-cheat-sheet/index.mdx index 2c400bd0fd6a..83f94a25d5ad 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/guides/react-cheat-sheet/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/guides/react-cheat-sheet/index.mdx @@ -76,6 +76,61 @@ export const Button = component$(() => {

    Attention ⚠️: JSX handlers such as onClick$ and onInput$ are only executed on the client. This is because they are DOM events, since there is no DOM on the server, they will not be executed on the server.

    +## Declare local state + +### ⚛️ From React + +```tsx +export function UseStateExample() { + const [value, setValue] = useState(0); + return
    Value is: {value}
    ; +} +``` + +### ⚡️ To Qwik + +```tsx +export const LocalStateExample = component$(() => { + const count = useSignal(0); + return
    Value is: {count.value}
    ; +}); +``` + +
    + What if I have a more complex state? +

    useStore() Works very similarly to useSignal(), but it takes an object as its initial value and the reactivity extends to nested objects and arrays by default. One can think of a store as a multiple-value signal or an object made of several signals.

    +
    + +## Create a counter component + +### ⚛️ From React + +```tsx +export function Counter() { + const [count, setCount] = useState(0); + return ( + <> +

    Value is: {count}

    + + + ); +} +``` + +### ⚡️ To Qwik + +```tsx +export const Counter = component$(() => { + const count = useSignal(0); + return ( + <> +

    Value is: {count.value}

    + + + ); +}); +``` + ## Using Props ### ⚛️ From React @@ -132,61 +187,6 @@ export const Child = component$(({ userData }) => {

    The reactive signal returned by useSignal() consists of an object with a single property .value. If you change the value property of the signal, any component that depends on it will be updated automatically.

    -## Declare local state - -### ⚛️ From React - -```tsx -export function UseStateExample() { - const [value, setValue] = useState(0); - return
    Value is: {value}
    ; -} -``` - -### ⚡️ To Qwik - -```tsx -export const LocalStateExample = component$(() => { - const count = useSignal(0); - return
    Value is: {count.value}
    ; -}); -``` - -
    - What if I have a more complex state? -

    useStore() Works very similarly to useSignal(), but it takes an object as its initial value and the reactivity extends to nested objects and arrays by default. One can think of a store as a multiple-value signal or an object made of several signals.

    -
    - -## Create a counter component - -### ⚛️ From React - -```tsx -export function Counter() { - const [count, setCount] = useState(0); - return ( - <> -

    Value is: {count}

    - - - ); -} -``` - -### ⚡️ To Qwik - -```tsx -export const Counter = component$(() => { - const count = useSignal(0); - return ( - <> -

    Value is: {count.value}

    - - - ); -}); -``` - ## Create a clock that increments every second ### ⚛️ From React diff --git a/packages/docs/src/routes/docs/(qwikcity)/guides/static-site-generation/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/guides/static-site-generation/index.mdx index 6c432a68ecfa..8fff2905cf13 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/guides/static-site-generation/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/guides/static-site-generation/index.mdx @@ -57,7 +57,7 @@ Your build files will be generated into the `dist` folder. You can build your static site using: ```shell -npm run build +npm run build.server ``` ### SSG Config diff --git a/packages/docs/src/routes/docs/(qwikcity)/layout/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/layout/index.mdx index 564ef5f25df7..949542aa397b 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/layout/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/layout/index.mdx @@ -8,6 +8,7 @@ contributors: - nnelgxorz - the-r3aper7 - mrhoodz + - aendel updated_at: '2023-06-25T19:43:33Z' created_at: '2023-03-20T23:45:13Z' --- @@ -62,7 +63,7 @@ src/ It will be used for all routes under the `src/routes` directory. It will render the `Header`, `Menu`, and `Footer` components, and also render the nested routes under the `Slot` component. ```tsx title="src/routes/layout.tsx" -import { component$ } from '@builder.io/qwik'; +import { component$, Slot } from '@builder.io/qwik'; export default component$(() => { return ( @@ -90,7 +91,7 @@ export default component$(() => { ### `src/routes/about/index.tsx` -Same as the `src/routes/index.tsx` file, but for the `about` route, similary it will be rendered under the `Slot` component in the `src/routes/layout.tsx` file, so even though it doesn't have any of the `Header`, `Menu`, or `Footer` components, it will still be rendered with them. +Same as the `src/routes/index.tsx` file, but for the `about` route, similarly it will be rendered under the `Slot` component in the `src/routes/layout.tsx` file, so even though it doesn't have any of the `Header`, `Menu`, or `Footer` components, it will still be rendered with them. ```tsx title="src/routes/about/index.tsx" import { component$ } from '@builder.io/qwik'; diff --git a/packages/docs/src/routes/docs/(qwikcity)/project-structure/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/project-structure/index.mdx index e6816bd31e92..f18b64614e1d 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/project-structure/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/project-structure/index.mdx @@ -137,7 +137,7 @@ qwik-app-demo └── vite.config.ts ``` -If you prefer to generate the `Button` component with the `Button/index.tsx` naming convention, you can could use the command: +If you prefer to generate the `Button` component with the `Button/index.tsx` naming convention, you could use the command: ```tsx pnpm qwik new --barrel Button ``` diff --git a/packages/docs/src/routes/docs/(qwikcity)/qwikcity/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/qwikcity/index.mdx index 0bb9f678ae57..1152cf2d93d4 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/qwikcity/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/qwikcity/index.mdx @@ -51,6 +51,17 @@ Qwik (core) and Qwik City (routing) solve problems at two layers of abstraction. Use Qwik City to build an e-commerce website, blog site, or any other website that needs routing, layouts, or data retrieval/updates. Qwik City is built on Qwik, and therefore Qwik City sites are resumable and only download the minimal amount of JavaScript with fine-grained lazy loading. +## Getting Started with Qwik City + +Visit [Create an app using the CLI](/docs/getting-started/#create-an-app-using-the-cli) to see how to create a new Qwik City starter project. It is as simple as: + +```shell +npm create qwik@latest +pnpm create qwik@latest +yarn create qwik +bun create qwik@latest +``` + ## High Level API Overview This table shows which file (`index.tsx` vs `layout.tsx`) the respective feature should be implemented in. diff --git a/packages/docs/src/routes/docs/(qwikcity)/route-loader/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/route-loader/index.mdx index 69899f801423..272c85779609 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/route-loader/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/route-loader/index.mdx @@ -13,7 +13,8 @@ contributors: - mrhoodz - mjschwanitz - adamdbradley -updated_at: '2023-09-15T11:23:15Z' + - gioboa +updated_at: '2023-12-15T11:00:00Z' created_at: '2023-03-20T23:45:13Z' --- @@ -23,6 +24,8 @@ Route Loaders load data in the server so it becomes available to use inside Qwik Route Loaders can only be declared inside the `src/routes` folder, in a `layout.tsx` or `index.tsx` file, and they MUST be exported. +> If you want to manage common reusable routeLoaders$ it is essential that this function is re-exported from within 'layout.tsx' or 'index.tsx file of the existing route otherwise it will not run or throw exception. For more information [check the cookbook](/docs/cookbook/re-exporting-loaders/index.mdx). + ```tsx /routeLoader$/ /useProductData/#a title="src/routes/product/[productId]/index.tsx" import { component$ } from '@builder.io/qwik'; import { routeLoader$ } from '@builder.io/qwik-city'; diff --git a/packages/docs/src/routes/docs/cookbook/algolia-search/index.mdx b/packages/docs/src/routes/docs/cookbook/algolia-search/index.mdx new file mode 100644 index 000000000000..15ece0d149c4 --- /dev/null +++ b/packages/docs/src/routes/docs/cookbook/algolia-search/index.mdx @@ -0,0 +1,207 @@ +--- +title: Cookbook | Algolia Search +contributors: + - gioboa +updated_at: '2024-01-09T11:00:00Z' +created_at: '2024-01-09T11:00:00Z' +--- + +import CodeSandbox from '../../../../components/code-sandbox/index.tsx'; + +# Algolia Search + +Algolia is a search platform that provides a set of features and APIs that make it easy to implement powerful search experiences for users. +In fact, out of the box it offers tools and infrastructure to integrate fast and relevant search functionality into our applications. +Thanks to the [Algolia](https://www.algolia.com/) APIs it is very simple to integrate it into your Qwik application. + +## Solution + +To start using it you need a personal Algolia user and retrieve your API Keys which will be used to interact with Algolia. +- **Index Name**: An index is where the data used by Algolia is stored. It's the equivalent of a table in a database but optimized for search and discovery operations. +- **Application ID**: This is your unique application identifier. It's used to identify you when using Algolia's API. +- **Search-Only API Key**: This is the public API key to use in your frontend code. This key is only usable for search queries and sending data to the Insights API. + +So once you have this information you can define your environment variables by creating or editing the `.env` or `.env.local` file at the root of your project. + +```bash title=".env" +VITE_ALGOLIA_INDEX= +VITE_ALGOLIA_APP_ID= +VITE_ALGOLIA_SEARCH_KEY= +``` + +Below you can find a working example of a first usable implementation. +Then obviously you can customize and create your graphical interface. +Many times it makes sense to have a modal where you show the search and the results, such as the search you find in this Qwik documentation. +You can check what Algolia returns via the [official documentation](https://www.algolia.com/doc/). + + +```tsx +import { $, component$, useSignal, useStylesScoped$ } from '@builder.io/qwik'; + +type AlgoliaResult = { + hits: { + type: string; + anchor?: string; + content?: string; + url: string; + }[]; +}; + +export default component$(() => { + useStylesScoped$(` + .search { + font-size: 100%; + width: calc(100% - 38px); + border-radius: 0.5rem; + border: 1px black solid; + padding: 1rem; + color: black; + outline: none; + } + + .search-button { + border: none; + padding: 6px 0px; + cursor: pointer; + background-color: transparent; + position: absolute; + right: 2.4rem; + padding: 0.85rem 0.5rem 0.4rem 0.5rem; + outline: none; + } + + .list { + display: flex; + flex-direction: column; + align-items: center; + } + + .list li { + counter-increment: cardCount; + display: flex; + color: white; + margin-top: 1rem; + margin-bottom: 1rem; + max-width: 500px; + } + + .list li::before { + content: counter(cardCount, decimal-leading-zero); + background: white; + color: var(--cardColor); + font-size: 2em; + font-weight: 700; + transform: translateY(calc(-1 * 1rem)); + margin-right: calc(-1 * 1rem); + z-index: 1; + display: flex; + align-items: center; + padding-inline: 0.5em; + border: 1px solid black; + } + + .list li .content { + background-color: var(--cardColor); + display: grid; + padding: 0.5em calc(1em + 1.5rem) 0.5em calc(1em + 1rem); + grid-template-areas: + "icon title" + "icon text"; + gap: 0.25em; + clip-path: polygon( + 0 0, + calc(100% - 1.5rem) 0, + 100% 50%, + calc(100% - 1.5rem) 100%, + calc(100% - 1.5rem) calc(100% + 1rem), + 0 calc(100% + 1rem) + ); + } + + .list li .content .title { + grid-area: title; + font-size: 1.25em; + } + + .list li .content .text { + grid-area: text; + color: black; + } + `); + const termSignal = useSignal(''); + const hitsSig = useSignal([]); + + const onSearch = $(async (query: string) => { + const algoliaURL = new URL( + `/1/indexes/${import.meta.env.VITE_ALGOLIA_INDEX}/query`, + `https://${import.meta.env.VITE_ALGOLIA_APP_ID}-dsn.algolia.net` + ); + const response = await fetch(algoliaURL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Algolia-Application-Id': import.meta.env.VITE_ALGOLIA_APP_ID!, + 'X-Algolia-API-Key': import.meta.env.VITE_ALGOLIA_SEARCH_KEY!, + }, + body: JSON.stringify({ query }), + }); + const algoliaResult: AlgoliaResult = await response.json(); + hitsSig.value = algoliaResult.hits; + }); + + return ( +
    +
    +
    + { + if (e.key === 'Enter') { + onSearch(termSignal.value); + } + }} + /> + +
    +
    +
    + {hitsSig.value.map(({ anchor, content, url }, key) => ( +
  • +
    +
    + {(anchor || content || url || '').substring(0, 30)} +
    + + Documentation link + +
    +
  • + ))} +
    +
    + ); +}); +``` +
    + diff --git a/packages/docs/src/routes/docs/cookbook/combine-request-handlers/index.mdx b/packages/docs/src/routes/docs/cookbook/combine-request-handlers/index.mdx new file mode 100644 index 000000000000..1f6d39c57e48 --- /dev/null +++ b/packages/docs/src/routes/docs/cookbook/combine-request-handlers/index.mdx @@ -0,0 +1,59 @@ +--- +title: Cookbook | Combine Request Handlers +contributors: + - gioboa + - erikras +updated_at: '2024-01-18T11:00:00Z' +created_at: '2024-01-18T11:00:00Z' +--- + +import {CodeFile} from '../../../../components/code-sandbox/index.tsx'; + +# Combine Request Handlers + +## Problem + +In a Qwik application we often have several request handlers to perform [middleware functions](https://qwik.builder.io/docs/middleware/). +In fact, according to the principle of [single responsibility](https://en.wikipedia.org/wiki/Single_responsibility_principle) middleware functions are developed to perform only one task (e.g. to create a database connection). +With these premises, it can be complex to combine multiple request handlers while maintaining the order in which they are called. +For example, you have one request handler to connect to a database, and another to load the current user record, the latter depends on the former, so they must be called in order. + +## Solution + +If you want to chain multiple request handlers together you can use this utility function. + + +```tsx +import type { RequestHandler } from '@builder.io/qwik-city'; + +/** + * Combines multiple request handlers into a single request handler. + * + * The handlers will be called in order: + * + * 1. Handler1 before next() + * 2. Handler2 before next() + * 3. Handler3 before next() + * 4. Next() + * 5. Handler3 after next() + * 6. Handler2 after next() + * 7. Handler1 after next() + * + * @public + */ + +export const combineRequestHandlers = (...handlers: RequestHandler[]): RequestHandler => + async (originalContext) => { + let lastNext = originalContext.next; + for (let i = handlers.length - 1; i >= 0; i--) { + const currentHandler = handlers[i]; + const nextInChain = lastNext; + lastNext = async () => { + await currentHandler({ ...originalContext, next: nextInChain }); + }; + } + + await lastNext(); + }; +``` + diff --git a/packages/docs/src/routes/docs/cookbook/fonts/index.mdx b/packages/docs/src/routes/docs/cookbook/fonts/index.mdx new file mode 100644 index 000000000000..bf1afbf61ebd --- /dev/null +++ b/packages/docs/src/routes/docs/cookbook/fonts/index.mdx @@ -0,0 +1,144 @@ +--- +title: Cookbook | Font optimization +contributors: + - thejackshelton +updated_at: '2023-12-28T16:33:00Z' +created_at: '2023-12-28T16:33:00Z' +--- + +# Fonts + +A memorable typeface can be a great way to make your site stand out. However, it's important to be mindful of the performance implications of using custom fonts. + +Unlike system fonts, that come pre-installed on a user's machine, custom fonts need to be downloaded. + +## FOIT vs. FOUT + +When a user visits a website, the browser will request the font files from the server. The browser will then use the font files to render the text on the page. + +There are two main strategies: + +- Delay text display until the web font is downloaded (**FOIT** - Flash Of Invisible Text). +- Use a locally installed "fallback" font until the web font is ready (**FOUT** - Flash Of Unstyled Text). + +Both methods have drawbacks. FOIT withholds text from the user, while FOUT can cause a disruptive visual experiences. Both cause CLS issues. As long as web fonts need to be downloaded, these issues persist. + +### Font Display + +Luckily, we can make use of the [font-display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display) property to control how the browser handles font loading. This allows us to strike a balance between the two strategies. + +> We suggest playing around with the different loading strategies, as well as how the font display timeline works. + +The two most common values for the `font-display` property are `swap` and `fallback`. + +## Google Fonts + +Google Fonts is a popular open source library, offering over 1500 font families. + +While they are easy to use, they involve downloading a CSS file and fonts from different domains, causing a noticeable delay and switch in font loading despite using `swap`. + +Here's what happens: + +1. The browser spots the `` tag, which prompts it to request a CSS file. +2. After analyzing the file, it realizes a web font from https://fonts.gstatic.com is needed, leading to another request. + +To mitigate this, we can **self host** our fonts. + +## Self Hosting + +Rather than using a third-party provider like Google Fonts, we can self-host our fonts. This means we download the font files and serve them from our own domain. + +Some benefits include: + +- Improved performance +- More privacy as Google tracks font usage. +- Self-hosted fonts load offline, useful for PWA's or low connectivity situations. + +### Fontsource + +Self-hosting with fontsource is as easy as an npm install. It includes all of the google fonts, as well as [other open source fonts](https://github.com/fontsource/font-files/tree/main/fonts/other), without the hassle of managing the files yourself. + +They also have a [Qwik City guide](https://fontsource.org/docs/guides/qwik), on how to add fontsource to your project. + +### Manual + +Sometimes we want to self host a font that is not included in Google fonts, or fontsource. + +If we have a `ttf` or `otf` file, we want to convert it to a `woff` or `woff2` file. To do that, we can use Fontsquirrel's [Webfont Generator Tool](https://www.fontsquirrel.com/tools/webfont-generator). + +Next we need to create an `@font-face` rule in our CSS. We want to create a new rule for each font weight and style we want to use. + +```css +@font-face { + font-display: swap; + font-family: "Peace Sans"; + font-style: normal; + font-weight: 400; + src: url("../assets/fonts/peace-sans.woff2") format("woff2"); +} +``` + +The above is an example of an `@font-face` rule for the font "Peace Sans" with a font weight of 400, with a relative URL to our font file. We can then use the font in our CSS like so: + +```css +body { + font-family: 'Peace Sans', sans-serif; +} +``` + +#### Manually self-hosting Google Fonts + +The [Google Webfonts Helper](https://gwfh.mranftl.com/fonts) is a tool that allows you to download the optimized google fonts. + +Unfortunately, this does not include variable fonts. To get around this, you can use the Google Fonts API, and then download the optimized variable font file in Chrome's `network` tab. + +### Reducing font size + +If we are not using certain glyphs, we can reduce the font size by using the `unicode-range` property. [Glyphhanger](https://www.npmjs.com/package/glyphhanger) is a tool that allows us to grab a specific subset of a font. + +> A common scenario, would be using only the latin subset of a font. This can reduce our font file size. + +## Fallback fonts + +If you recall an earlier section, we mentioned that we can get CLS issues when our custom fonts load in. + +Recently, there has been an effort of "font matching", or adjusting typography properties to reduce the CLS impact of custom fonts. Let's take a look at how we can do that. + +### Fallback Font Generator + +[This tool](https://screenspan.net/fallback) generates a system fallback with special properties. Here's an over-exaggerated [demo](https://screenspan.net/prototypes/fallback-font/) of how it works + +You can use the `size-adjust`, `ascent-override`, and `descent-override` properties to adjust the system fallback font to match the custom font. + +### Fontaine + +[Fontaine](https://github.com/unjs/fontaine) will automatically adjust the above properties to avoid CLS issues. They provide a vite plugin that you can add in your vite config. + +## System Fonts + +The most performant option is system fonts. This is because the user already has the font installed on their machine, and the browser can render the text immediately. + +System fonts often are often called *"font stacks"*, which are a collection of system fonts that are similar enough to each other. If the first font is not available, the browser can use the next one in the stack. + +Tailwind CSS also provides [utility classes](https://tailwindcss.com/docs/font-family) for common system fonts. [Modern Font Stacks](https://github.com/system-fonts/modern-font-stacks) is an example tool you can use to test out different font stacks, including a [Tailwind CSS plugin](https://github.com/BorisAnthony/mfs-tailwind) from a community member. + +## Font UX + +### Do not use the `ch` unit for body max-width + +It's recommended to have a max-width of `75ch` or 75 characters so that the user does not have to move their head to read the text, as well as it's easier to follow. + +However, if we are using a custom font, we should be mindful of the `ch` unit. This is because the `ch` unit is based on the width of the `0` character, which can vary between fonts. + +This can cause some very peculiar layout shifts. We want to set a max-width using `px` or `rem`. + +### Use `rem` with the `font-size` property. + +Different units each have their own [accessibility concerns](https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility/). We want to use `rem` with the `font-size` property, as it is based on the root font size. Otherwise, the user's chosen font size will not be respected. + +### Line height recommendations + +The browser's default line height is not preferable for reading. With body fonts, we want to have a line height around 1.5. For headings, we want to have a line height around 1.2. + + + diff --git a/packages/docs/src/routes/docs/cookbook/glob-import/index.mdx b/packages/docs/src/routes/docs/cookbook/glob-import/index.mdx index b66c173cae55..427dc9e905d6 100644 --- a/packages/docs/src/routes/docs/cookbook/glob-import/index.mdx +++ b/packages/docs/src/routes/docs/cookbook/glob-import/index.mdx @@ -50,7 +50,7 @@ The reason for this behavior, is that `import.meta.glob` with `eager.false` brea As a workaround for now, you can use the build time `isDev` boolean from `"@builder.io/qwik/build"`: - + ```tsx import { type Component, diff --git a/packages/docs/src/routes/docs/cookbook/index.mdx b/packages/docs/src/routes/docs/cookbook/index.mdx index 492cd54b2e60..77caeb527935 100644 --- a/packages/docs/src/routes/docs/cookbook/index.mdx +++ b/packages/docs/src/routes/docs/cookbook/index.mdx @@ -7,8 +7,9 @@ contributors: - Craiqser - Inaam-Ur-Rehman - maiieul -updated_at: '2023-10-10T19:26:56Z' -created_at: '2023-08-14T18:24:46Z' + - Adbib + - gioboa + - aendel --- # Cookbook @@ -16,7 +17,15 @@ created_at: '2023-08-14T18:24:46Z' A cookbook contains a collection of useful patterns for solving common problems in front-end development. Examples: -- [Modal Dialog Pop-Up](./portal/) + +- [Algolia search](./algolia-search/) +- [Combine Request Handlers](./combine-request-handlers/) +- [Deploy with Node using Docker](./node-docker-deploy/) +- [Font optimization](./fonts/) +- [Glob Import with import.meta.glob](./glob-import/) - [Media Controller with iOS Support](./mediaController/) -- [Glob Import with `import.meta.glob`](./glob-import/) -- [Theme Managment](./theme-management/) +- [NavLink](./nav-link/) +- [Portals](./portals/) +- [Re-exporting loaders](./re-exporting-loaders/) +- [Synchronous Events with State](./sync-events/) +- [Theme Management](./theme-management/) diff --git a/packages/docs/src/routes/docs/cookbook/mediaController/index.mdx b/packages/docs/src/routes/docs/cookbook/mediaController/index.mdx index 9d67e6cec664..355b479d29d2 100644 --- a/packages/docs/src/routes/docs/cookbook/mediaController/index.mdx +++ b/packages/docs/src/routes/docs/cookbook/mediaController/index.mdx @@ -35,7 +35,7 @@ To achieve consistent media playback across various browsers and devices, a holi Below, you'll find a prototype of an audio and video controller, tailored to provide a consistent user experience across multiple platforms. - + Universal media controller code compatible with iOS devices looks like this: @@ -81,7 +81,7 @@ export default component$(() => { .content { width: 60%; min-width: 250px; - } + } button { padding: 20px; font-weight: bold; @@ -180,7 +180,7 @@ export default component$(() => { id="playsInlineCheckbox" class="checkbox" checked={playsInlineSignal.value} - onChange$={() => { + onchange$={() => { videoElementSignal.value?.pause(); playsInlineSignal.value = !playsInlineSignal.value; }} diff --git a/packages/docs/src/routes/docs/cookbook/nav-link/index.mdx b/packages/docs/src/routes/docs/cookbook/nav-link/index.mdx new file mode 100644 index 000000000000..981c33955434 --- /dev/null +++ b/packages/docs/src/routes/docs/cookbook/nav-link/index.mdx @@ -0,0 +1,100 @@ +--- +title: Cookbook | Navbar link +contributors: + - Adbib +--- + +import CodeSandbox, { CodeFile } from '../../../../components/code-sandbox/index.tsx'; + +# NavLink Component + +If you want to add `active` state to your links you can use this solution. +The NavLink component enhances Qwik `` component by adding: + +- **Active Status**: Apply a class when the link href matches the current URL pathname. + +This allows styling the active state for navigation. + +## How it Works + +Under the hood, NavLink uses the `useLocation` hook to get navigation status. +It checks if the link href matches the current URL pathname to set activeClass. +This allows NavLink to know the active state automatically based on navigation. + + +```tsx +import { Slot, component$ } from '@builder.io/qwik'; +import { Link, useLocation, type LinkProps } from '@builder.io/qwik-city'; + +type NavLinkProps = LinkProps & { activeClass?: string }; + +export const NavLink = component$( + ({ activeClass, ...props }: NavLinkProps) => { + const location = useLocation(); + const toPathname = props.href ?? ''; + const locationPathname = location.url.pathname; + + const startSlashPosition = + toPathname !== '/' && toPathname.startsWith('/') + ? toPathname.length - 1 + : toPathname.length; + const endSlashPosition = + toPathname !== '/' && toPathname.endsWith('/') + ? toPathname.length - 1 + : toPathname.length; + const isActive = + locationPathname === toPathname || + (locationPathname.endsWith(toPathname) && + (locationPathname.charAt(endSlashPosition) === '/' || + locationPathname.charAt(startSlashPosition) === '/')); + + return ( + + + + ); + } +); +``` + + + +## Usage + +You can use NavLink with the addition of `activeClass` props: + + +```tsx +import { component$ } from '@builder.io/qwik'; +import { NavLink } from '..'; + +export default component$(() => { + return ( + <> + Links +
    + + /docs + +
    +
    + + /demo/cookbook/nav-link/example/ + +
    + + ); +}); +``` +
    + +## Tailwind +If you are using Tailwind CSS make sure to edit your tailwind config file, and add `important=true` to your export, then add `!` before the CSS classes you're using `activeClass="!text-green-600"` to make them important when the link is active. + diff --git a/packages/docs/src/routes/docs/cookbook/node-docker-deploy/index.mdx b/packages/docs/src/routes/docs/cookbook/node-docker-deploy/index.mdx new file mode 100644 index 000000000000..fad762a2aeae --- /dev/null +++ b/packages/docs/src/routes/docs/cookbook/node-docker-deploy/index.mdx @@ -0,0 +1,93 @@ +--- +title: Cookbook | Deploy with Node using Docker +contributors: + - nelsonprsousa + - aendel +updated_at: '2023-12-28T16:00:00Z' +created_at: '2023-12-28T16:00:00Z' +--- + +# Deploy with Node using Docker + +This Dockerfile is used to build a Docker image for our Qwik Node.js application. You can edit it accordingly to use npm, pnpm or bun, by replacing yarn commands and `yarn.lock` file. + +We are using the official Node.js Alpine image with Node version set to 18.18.2 (a long term support version). You are free to choose some other version based on your needs. + +We then proceed to install dependencies, utilizing Docker caching mechanism by leveraging bind mounts for `package.json` and `yarn.lock` to avoid unnecessary reinstallation of dependencies. + +Once the dependencies are installed, we proceed to build, setting the NODE_ENV and ORIGIN environment variables. + +We finally specify the default command to run our Qwik application with `yarn serve`, exposing the port 3000: Please note that port number should match the one you chose on your entry adapter (e.g.: `src/entry.express.tsx`). + + ```docker + ARG NODE_VERSION=18.18.2 + +################################################################################ +# Use node image for base image for all stages. +FROM node:${NODE_VERSION}-alpine as base + +# Set working directory for all build stages. +WORKDIR /usr/src/app + +################################################################################ +# Create a stage for installing production dependencies. +FROM base as deps + +# Download dependencies as a separate step to take advantage of Docker's caching. +# Leverage a cache mount to /root/.yarn to speed up subsequent builds. +# Leverage bind mounts to package.json and yarn.lock to avoid having to copy them +# into this layer. +RUN --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=yarn.lock,target=yarn.lock \ + --mount=type=cache,target=/root/.yarn \ + yarn install --frozen-lockfile + +################################################################################ +# Create a stage for building the application. +FROM deps as build + +# Copy the rest of the source files into the image. +COPY . . + +# Run the build script. +RUN yarn run build + +################################################################################ +# Create a new stage to run the application with minimal runtime dependencies +# where the necessary files are copied from the build stage. +FROM base as final + +# Use production node environment by default. +ENV NODE_ENV production +ENV ORIGIN https://example.com + +# Run the application as a non-root user. +USER node + +# Copy package.json so that package manager commands can be used. +COPY package.json . + +# Copy the production dependencies from the deps stage and also +# the built application from the build stage into the image. +COPY --from=deps /usr/src/app/node_modules ./node_modules +COPY --from=build /usr/src/app/dist ./dist +COPY --from=build /usr/src/app/server ./server + +# Expose the port that the application listens on. +EXPOSE 3000 + +# Run the application. +CMD yarn serve + ``` + + You can now build your docker image: + + ```shell + docker build -t your-image . + ``` + +And start a Docker container: + + ```shell +docker run -dp 127.0.0.1:3000:3000 your-image + ``` diff --git a/packages/docs/src/routes/docs/cookbook/portal/index.mdx b/packages/docs/src/routes/docs/cookbook/portals/index.mdx similarity index 65% rename from packages/docs/src/routes/docs/cookbook/portal/index.mdx rename to packages/docs/src/routes/docs/cookbook/portals/index.mdx index 4dcbafa1a861..156b3a8652fa 100644 --- a/packages/docs/src/routes/docs/cookbook/portal/index.mdx +++ b/packages/docs/src/routes/docs/cookbook/portals/index.mdx @@ -1,28 +1,65 @@ --- -title: Cookbook | Portal +title: Cookbook | Portals contributors: - mhevery + - thejackshelton - fabian-hiller - igorbabko + - aendel updated_at: '2023-10-03T18:53:59Z' created_at: '2023-08-21T21:16:38Z' --- import CodeSandbox, {CodeFile} from '../../../../components/code-sandbox/index.tsx'; -# Portal +# Portals -A common problem in front-end development is to pop up a modal dialog from a component. The complication is that the modal dialog needs to be rendered in a different part of the DOM tree, and the component that triggers the modal needs to have a way to affect the location of rendering. +In front-end development, sometimes we need to display a component (like a modal or tooltip) in a different place from where it was triggered. The issue is that the UI element needs to be rendered in a different part of the DOM tree, and the component that triggers the element needs to have a way to affect the location of rendering. -In other frameworks, this is often solved by dedicated API such as [`createPortal()`](https://react.dev/reference/react-dom/createPortal). However such APIs don't work well with server-side rendering and so an alternative approach is needed. +In other frameworks, this is often solved by a dedicated API such as [`createPortal()`](https://react.dev/reference/react-dom/createPortal). However such APIs don't work well with server-side rendering and so an alternative approach is needed. -## Alternatives +## Qwik UI -You may want to consider these modern browser alternatives to modals: -- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog -- https://developer.chrome.com/blog/introducing-popover-api/ +Luckily, there is native behavior that handles this for us, called the [top layer](https://developer.chrome.com/blog/what-is-the-top-layer). The Qwik UI team has done an awesome job of filling in the gaps, and allowing us to use this behavior in production. -## Problem +### Modals + +We use modals when we do not want the user to interact with the rest of the page. The rest of the content is inert, or unable to be interacted with. + +[Qwik UI's modal component](https://qwikui.com/docs/headless/modal/) uses the dialog element's [showModal](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal) method, which is well-supported in browsers and automatically handles placing UI outside of the HTML Document. + +It also includes behavior such as focus and scroll locking, alert dialogs, automatic entry and exit animation support, and backdrop animations. At the time of writing, support for the dialog element is currently at 96%. + +### Non-modal UI + +If the UI element can interact with the rest of the page, then it is not modal. + +Some examples of non-modal components are: +- Popovers +- Overlays +- Toasts +- Tooltips +- Dropdown menus +- Selects +- Comboboxes + +[MDN's popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) replaces the need for portals in non-modal components. Support is also in every major browser. At the time of writing, it's at ~73%. + +Qwik UI has taken it upon themselves by providing a polyfill with feature parity to the native spec. You can use the Popover API's behavior in production today with [Qwik UI's Popover](https://qwikui.com/docs/headless/popover/) component. + +In the case of components like a select or combobox, Qwik UI also provides the ability to opt-in to "floating" behavior. For example, when a listbox anchors to an input element. You can do so by adding `floating={true}` to your Popover component. This will execute a bit of extra javascript needed for floating behavior. + +It is intentionally opt-in, at some point the CSS Anchor API will provide a native solution, and so there should be an easy migration path when that receives more general support. + +> Because these solutions are built on top of the native specs, that also means there's less javascript we need to prefetch, and therefore less work that needs to be done! + +Both Qwik UI's popover and modal components can be used regardless of meta-framework or microfrontend, as long as there is support for Qwik. + +## Custom Portals + +If the behavior of the above components do not fit the use case, there is also the ability to create a custom portal component in Qwik. We'll create a modal component from scratch. + +> The following is an SSR portal implementation using Qwik City. If you are using multiple frontend frameworks alongside Qwik, you may prefer a [React-like portal implementation](https://github.com/qwikifiers/qwik-ui/blob/main/packages/kit-headless/src/components/popover/popover-impl.tsx#L42). The fundamental problems to solve are: 1. decide where the popup should be rendered in the application. (Let's call this ``) @@ -30,7 +67,7 @@ The fundamental problems to solve are: ## Solution - + @@ -44,7 +81,7 @@ Let's break down the solution into steps: ### Using the PortalProvider Let's assume that we already have a `PortalProvider` and let's focus on how it is used first. -1. We get a hold of the `PortalProvider` API through: +1. We get a hold of the `PortalProvider` API through: ```tsx const portal = useContext(PortalAPI); ``` @@ -171,8 +208,8 @@ import { type ContextId, type QRL, type Signal, + type JSXOutput, } from '@builder.io/qwik'; -import { type JSXNode } from '@builder.io/qwik/jsx-runtime'; import CSS from './portal-provider.css?inline'; // Define public API for opening up Portals @@ -185,7 +222,7 @@ export const PortalAPI = createContextId< * @returns A function used for closing the portal. */ QRL< - (name: string, jsx: JSXNode, contexts?: ContextPair[]) => () => void + (name: string, jsx: JSXOutput, contexts?: ContextPair[]) => () => void > >('PortalProviderAPI'); @@ -200,9 +237,9 @@ const PortalsContextId = createContextId>('Portals'); interface Portal { name: string; - jsx: JSXNode; + jsx: JSXOutput; close: QRL<() => void>; - contexts: Array>; + contexts: Array>; } export const PortalProvider = component$(() => { @@ -212,7 +249,7 @@ export const PortalProvider = component$(() => { // Provide the public API for the PopupManager for other components. useContextProvider( PortalAPI, - $((name: string, jsx: JSXNode, contexts?: ContextPair[]) => { + $((name: string, jsx: JSXOutput, contexts?: ContextPair[]) => { const portal: Portal = { name, jsx, @@ -245,8 +282,8 @@ export const Portal = component$<{ name: string }>(({ name }) => { const myPortals = portals.value.filter((portal) => portal.name === name); return ( <> - {myPortals.map((portal) => ( -
    + {myPortals.map((portal, key) => ( +
    ))} @@ -255,10 +292,13 @@ export const Portal = component$<{ name: string }>(({ name }) => { }); export const WrapJsxInContext = component$<{ - jsx: JSXNode; + jsx: JSXOutput; contexts: Array>; }>(({ jsx, contexts }) => { - contexts.forEach(({ id, value }) => useContextProvider(id, value)); + contexts.forEach(({ id, value }) => { + // eslint-disable-next-line + useContextProvider(id, value); + }); return ( <> {/* Workaround: https://github.com/BuilderIO/qwik/issues/4966 */} diff --git a/packages/docs/src/routes/docs/cookbook/re-exporting-loaders/index.mdx b/packages/docs/src/routes/docs/cookbook/re-exporting-loaders/index.mdx new file mode 100644 index 000000000000..1351d49ab4a8 --- /dev/null +++ b/packages/docs/src/routes/docs/cookbook/re-exporting-loaders/index.mdx @@ -0,0 +1,167 @@ +--- +title: Cookbook | Re-exporting loaders +contributors: + - gioboa + - aendel +updated_at: '2023-12-15T11:00:00Z' +created_at: '2023-12-15T11:00:00Z' +--- + +import CodeSandbox, {CodeFile} from '../../../../components/code-sandbox/index.tsx'; + +# Re-exporting loaders + +`routeAction$` and `routeLoader$` are typically declared in route boundary files such as layout.tsx, index.tsx and plugin.tsx inside the routesDir directory +[here is docs](https://qwik.builder.io/docs/route-loader/). + +Sometimes you would like to declared them outside of the route boundaries. +This may be useful when you want to create reusable logic or a library. +In such a case, it is essential that this function is re-exported from within the router boundary otherwise it will not run or throw exception. + +## Solution + +You can define `routeAction$` and `routeLoader$` in your custom path and re-export them in your layout.tsx, index.tsx and plugin.tsx files. + +### Reusable logic example + +Let's imagine we have a `routeLoader$` that checks whether our user is logged in or not. +It wouldn't make sense to copy and paste the same code into many parts of our code to make it work. +As per good practices, the ideal method is to centralize the logic. +Here in this example we declare `routeAction$` and `routeLoader$` in a centralized file so we can then reuse it in our files. + +Example with `./shared/loaders.ts` + +```tsx +import { routeAction$, routeLoader$ } from '@builder.io/qwik-city'; + +export const useCommonRouteAction = routeAction$(async () => { + // ... + return { success: true, data: ['Qwik', 'Partytown'] }; +}); + +export const useCommonRouteLoader = routeLoader$(async () => { + // ... + return ['Mitosis', 'Builder.io']; +}); +``` + + +Now you can use your common `routeAction$` and `routeLoader$` in paths like this one `./src/routes/index.tsx`. + + +```tsx +import { component$ } from '@builder.io/qwik'; +import { Form } from '@builder.io/qwik-city'; +import { useCommonRouteAction, useCommonRouteLoader } from './shared/loaders'; + +// As mentioned, here we are re-exporting them +export { useCommonRouteAction, useCommonRouteLoader } from './shared/loaders'; + +export default component$(() => { + const commonRouteAction = useCommonRouteAction(); + const commonRouteLoader = useCommonRouteLoader(); + + return ( +
    +
    +
    CommonRouteAction
    +
    response:
    +
    + {commonRouteAction.value?.data.join(' ') || ''} +
    + +
    +
    +
    CommonRouteLoader
    +
    response:
    +
    {commonRouteLoader.value.join(' ')}
    +
    +
    + ); +}); +``` +
    + + + +### Third-party libraries example + +It may happen that we need to integrate third-party libraries over which we have no control over how it works. +Let's think for example about integrating a payment method into our application. +We are provided with a component to integrate into the page, but we have no control over what happens under the hood of this component. +Here, if this library needs `routeAction$` or `routeLoader$` we must re-export them to allow the correct functioning of our library. + +#### Here is our code: + + +```tsx +import { component$ } from '@builder.io/qwik'; +import { ThirdPartyPaymentComponent } from './third-party-library'; + +// As mentioned, here we are re-exporting the third-party loader +export { useThirdPartyPaymentLoader } from './third-party-library'; + +export default component$(() => { + return ( +
    + +
    + ); +}); +``` +
    + +#### Here is the library code: + + +```tsx +import { component$ } from '@builder.io/qwik'; +import { routeLoader$ } from '@builder.io/qwik-city'; + +export const useThirdPartyPaymentLoader = routeLoader$(() => { + return { name: 'John Doe' }; +}); + +export const ThirdPartyPaymentComponent = component$(() => { + const thirdPartyPaymentLoader = useThirdPartyPaymentLoader(); + return ( +
    +
    +
    +
    +

    Name

    +

    {thirdPartyPaymentLoader.value.name}

    +
    + +
    +
    +

    Card Number

    +

    4642 3489 9867 7632

    +
    +
    +
    +
    +

    Valid

    +

    11/15

    +
    +
    +

    Expiry

    +

    03/25

    +
    +
    +

    CVV

    +

    ···

    +
    +
    +
    +
    +
    + ); +}); +``` +
    diff --git a/packages/docs/src/routes/docs/cookbook/sync-events/index.mdx b/packages/docs/src/routes/docs/cookbook/sync-events/index.mdx new file mode 100644 index 000000000000..027c4ae3f8bf --- /dev/null +++ b/packages/docs/src/routes/docs/cookbook/sync-events/index.mdx @@ -0,0 +1,76 @@ +--- +title: Cookbook | Synchronous Events with State +contributors: + - mhevery +--- + +import CodeSandbox, {CodeFile} from '../../../../components/code-sandbox/index.tsx'; + + +# `sync$()` Synchronous Events (BETA) + +Qwik processes events asynchronously. This means that some APIs such as `event.preventDefault()` and `event.stopPropagation()` do not work as expected. To work around this limitation, Qwik provides a `sync$()` API which allows you to process events synchronously. But `sync$()` comes with a few caveats: +1. `sync$()` can't close over any state. +2. `sync$()` can't call other functions which are declared in scope or imported. +3. `sync$()` is serialized into HTML and therefore we should be conscious of the size of the function. + +A typical way to deal with these limitations is to split the event handling into two parts: +1. `sync$()` which is called synchronously and can call methods such as `event.preventDefault()` and `event.stopPropagation()`. +2. `$()` which is called asynchronously and can close over the state and call other functions, and has no restriction on the size. + +Because `sync$()` can't access the state what is the best strategy to deal with it? The answer is to use element attributes to pass state into the `sync$()` function. + +## Example: `sync$()` with state + +In this example, we have a behavior where we want to prevent the default behavior of the link based on some state. We do this by breaking the code into three parts: +1. `sync$()`: a synchronous portion that is kept to the minimum, +2. `$()`: an asynchronous portion that can be arbitrarily large, and can close over state, +3. `data-should-prevent-default`: an attribute on the element that is used to pass state into the `sync$()` function. + + + + + + + +```tsx +import { component$, useSignal, sync$, $ } from '@builder.io/qwik'; + +export default component$(() => { + const shouldPreventDefault = useSignal(true); + return ( + + ); +}); +``` + diff --git a/packages/docs/src/routes/docs/cookbook/theme-management/index.mdx b/packages/docs/src/routes/docs/cookbook/theme-management/index.mdx index b7c845edd7cb..975e4cd80106 100644 --- a/packages/docs/src/routes/docs/cookbook/theme-management/index.mdx +++ b/packages/docs/src/routes/docs/cookbook/theme-management/index.mdx @@ -3,15 +3,16 @@ title: Cookbook | Dark and Light Theme contributors: - Inaam-Ur-Rehman - gioboa + - aendel updated_at: '2023-10-10T20:38:51Z' created_at: '2023-10-10T16:59:26Z' --- +# Theme management -## Problem -In modern website we have seen that it has the feature of dark and light theme that creates a good impact on the users. They can change the theme according to their prefrences. +In modern website we have seen that it has the feature of dark and light theme that creates a good impact on the users. They can change the theme according to their preferences. -To achive this we are also using tailwindcss for styling. +To achieve this we are also using tailwindcss for styling. First we have to install tailwindcss in our website. To add tailwind in Qwik project is very simple. You have to just run this command in terminal. @@ -19,7 +20,7 @@ First we have to install tailwindcss in our website. To add tailwind in Qwik pro npm run qwik add tailwind ``` -You have to enable the darkmode from your ```tailwind.config.js``` file. It shoul look like this. +You have to enable the darkmode from your ```tailwind.config.js``` file. It should look like this. ```js module.exports = { diff --git a/packages/docs/src/routes/docs/deployments/node/index.mdx b/packages/docs/src/routes/docs/deployments/node/index.mdx index 0e5f0ecb182c..565cf8edcb38 100644 --- a/packages/docs/src/routes/docs/deployments/node/index.mdx +++ b/packages/docs/src/routes/docs/deployments/node/index.mdx @@ -70,15 +70,17 @@ It's **very important to correctly configure the `ORIGIN` env variable**, which > ORIGIN=https://example.com node server/entry.express > ``` +You can check [how to deploy with Docker here](/docs/cookbook/node-docker-deploy/index.mdx). + ### CSRF Protection By default, all Qwik City applications are protected against [CSRF attacks](https://owasp.org/www-community/attacks/csrf) for all POST, PATCH, DELETE form submits. This protection is enabled by default and it's the reason why you need to set the `ORIGIN` environment variable when deploying your application for production. -If you want to disable CSRF protection, you can set `checkOrigin: false` in the `createQwikCity()` options in `src/entry.express.tsx`: +If you want to disable CSRF protection, you can set `checkOrigin: false` in the `createQwikCity()` options in `src/entry.preview.tsx` or `src/entry.[server].tsx`: -```tsx {6} /checkOrigin/ title="entry.express.tsx" +```tsx {6} /checkOrigin/ title="entry.preview.tsx" // ... const { router, notFound, staticFile } = createQwikCity({ render, diff --git a/packages/docs/src/routes/docs/deployments/static/index.mdx b/packages/docs/src/routes/docs/deployments/static/index.mdx index 072746f2794e..0f9e3ee33ca9 100644 --- a/packages/docs/src/routes/docs/deployments/static/index.mdx +++ b/packages/docs/src/routes/docs/deployments/static/index.mdx @@ -2,6 +2,7 @@ title: Static Site contributors: - the-r3aper7 + - aendel updated_at: '2023-10-11T15:09:55Z' created_at: '2023-10-11T15:09:55Z' --- @@ -20,7 +21,7 @@ npm run qwik add static Above command will create a directory at project root named `adapters/static/vite.config.ts` with the below code. -```tsx file="apdaters/static/vite.config.ts" +```tsx file="adapters/static/vite.config.ts" import { extendConfig } from '@builder.io/qwik-city/vite'; import baseConfig from '../../vite.config'; diff --git a/packages/docs/src/routes/docs/integrations/astro/index.mdx b/packages/docs/src/routes/docs/integrations/astro/index.mdx index 492b00a655b7..c54c74fea8d6 100644 --- a/packages/docs/src/routes/docs/integrations/astro/index.mdx +++ b/packages/docs/src/routes/docs/integrations/astro/index.mdx @@ -15,6 +15,8 @@ It also allows you to write components using your favorite UI framework (or no f This results in a fast, SEO-friendly output that can be deployed to any static hosting environment or server. +> For more details on the integration and to view the source code, check out the [QwikDev Astro Integration on GitHub](https://github.com/QwikDev/astro). + ## Astro instead of Qwik City When integrating Astro with Qwik, it's important to note that Qwik City APIs are not compatible with Astro. @@ -97,6 +99,8 @@ Now, add the integration to your `astro.config.*` file using the `integrations` // ^^^^^ }); ``` + +> If you are also using other integrations like `react()` or `preact()`, you will need to put `qwikdev()` before them in the list. Or you will have `Not a QRL` errors. Something like this: `integrations: [qwikdev(), react(), preact()]`. ## Qwik does not hydrate, it is **fundamentally different** @@ -143,11 +147,218 @@ It can be consumed in our `index.astro` page like so: ``` -### Roadmap +## Starts fast, stays fast + +One of Astro's key features is **Zero JS, by default**. Unfortunately, after adding a JavaScript framework, and any subsequent components this is usually not the case. + +If we want to introduce interactivity with a framework such as React, Vue, Svelte, etc., the framework runtime is then introduced. The number of components added to the page also increases linearly O(n) with the amount of JavaScript. + +### Astro + Qwik + +Qwik builds on top of Astro's **Zero JS, by defaut** 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, and zero effort on the developer. + +Instead, upon page load, a tiny 1kb minified piece of JavaScript, known as the [Qwikloader](https://qwik.builder.io/docs/advanced/qwikloader/), downloads the rest of the application as needed. + +### Fine-grained lazy loading + +Hydration forces your hand [to eagerly execute code](https://www.builder.io/blog/hydration-sabotages-lazy-loading). It's not a problem with components that are outside of the tree, such as modals, but it must exhaustively check each component in the render tree just in case. + +Qwik works exceptionally well in Astro due to Resumability and its ability to lazy load code in a fine-grained manner. Especially for marketing sites, blogs, and content oriented sites with many components. + +### Instant interactivity + +As of `@qwikdev/astro` v0.4, we have added support for [Speculative Module Fetching](https://qwik.builder.io/docs/advanced/speculative-module-fetching/) in Astro. + +This enables instant interactivity for your Qwik components. Speculative module fetching will prefetch the application bundles in the background of a service worker, so that when needed, the code is already present in the browser cache. + +> You should be able to use [Qwik insights](https://qwik.builder.io/docs/labs/insights/) out of the box! + +## Containers vs. Islands + +While Astro generally adopts an islands architecture with other frameworks, Qwik uses a different strategy known as [Qwik containers](https://qwik.builder.io/docs/advanced/containers/). Despite the differences in approach, both share similar limitations. + +In the DOM, you may notice there aren't any `` custom elements, this is because to Astro, Qwik looks like static data. + +> This is because in Qwik, the handlers themselves are the roots / entrypoints of the application. + +### Communicating across containers + +One common limitation is trying to pass state into another island or container. + +Sharing state is crucial in modern web development. The question is, how can we achieve this when state needs to be shared across different containers or islands? + +#### Why not use global signals or nanostores? + +Other frameworks with Astro address this by using [nano stores](https://github.com/nanostores/nanostores), or [global signals](https://www.solidjs.com/tutorial/stores_nocontext). + +While you may see all of your tests passing, and the application working as expected, we do not recommend using nanostores or global signals. They can lead to some unexpected behavior in an SSR context. + +For example, in Solid's tutorial the following is mentioned: + +> While it is possible to use global state and computations, Context is sometimes a better solution. Additionally, it is important to note that global state should not be used in SSR (server side rendering) solutions, such as Solid Start. On the server, global state is shared across requests, and the lack of data isolation can (and will) lead to bugs, memory leaks and has security implications. It is recommended that application state should always be provided via context instead of relying on global. + +#### Custom Events + +In Qwik, it was a design decision to not include global signal state. + +Instead, we recommend the use of **custom events**, which offer several advantages: + +- Performance (avoid unnecessary state synchronization) +- Does not wake up the framework on page load +- Micro Frontend (MFE) Support +- Different versions can exist on the page +- Event Driven +- Decoupled + +[This example](https://github.com/thejackshelton/astro-qwik-global-state-example/blob/main/src/components/counter.tsx) shows how custom events can be used throughout your application. Pay attention to `counter.tsx`, `random-island.tsx`, and our `index.astro` page. + +## Using multiple JSX frameworks + +To use multiple JSX frameworks like Qwik, React, Preact, or Solid in Astro, you need to set up rules for which files each framework should handle. + +For example, you can place all Qwik components in a folder named `qwik`. Then, configure Astro to process any file within this folder using the Qwik integration. + +```tsx +import { defineConfig } from "astro/config"; +import qwik from "@qwikdev/astro"; +import react from "@astrojs/react"; + +export default defineConfig({ + integrations: [ + qwik({ include: "**/qwik/*" }), + react({ include: "**/react/*" }), + solid({ include: "**/solid/*" }), + ], +}); +``` + +Above we're using the Qwik, React, and Solid integrations in the same Astro project. + +If we look at the first integration, it's going to look for any file in the `qwik` folder and use Qwik for any file in this folder. + +For simplicity, consider grouping common framework components in the same folder (like `/components/react/` and `/components/qwik/`). However, this is optional. + +### Qwik React + +If you're using React, we suggest using the `@builder.io/qwik-react` integration. It's a drop-in replacement for `@astrojs/react`, and allows a seamless transition to Qwik. + +```tsx +import { defineConfig } from "astro/config"; + +import qwikdev from "@qwikdev/astro"; +import { qwikReact } from "@builder.io/qwik-react/vite"; + +// https://astro.build/config +export default defineConfig({ + integrations: [qwikdev()], + vite: { + plugins: [qwikReact()], + }, +}); +``` + +With Qwik-React, we can "qwikify" our React components, and use them in our Qwik application, even nesting Qwik and React components outside of an Astro file! -There are some things missing from Astro that we would like to add in the future. That being better [prefetching](https://qwik.builder.io/docs/advanced/modules-prefetching/#prefetching-modules) and [Insights](https://qwik.builder.io/docs/labs/insights/). +> You do not need to specify an include property with qwikReact. -If there's anything else you think would be awesome with Astro & Qwik, feel free to take a crack at it. +[Here's an example](https://github.com/thejackshelton/qwik-react-astro-template) of a React component with the `qwik-react` integration. + +```tsx +/** @jsxImportSource react */ +import { qwikify$ } from "@builder.io/qwik-react"; +import { useState } from "react"; + +const ReactCounter = () => { + const [count, setCount] = useState(0); + + return ; +}; + +// "Qwikified" React component +export const QReactCounter = qwikify$(ReactCounter); +``` + +After creating our counter, it can be consumed in our [index.astro](https://github.com/thejackshelton/qwik-react-astro-template/blob/main/src/pages/index.astro) file. + +```tsx + +``` + +Notice that in `.astro` files we use a `qwik:` hydration directive prefix, this is to prevent a conflict with Astro's hydration directives that are provided out of the box. + +You can also use the `client:*` prefix, but only in tsx files. You can find a list of directives in [Adding Interactivity](https://qwik.builder.io/docs/integrations/react/#adding-interactivity) section of the Qwik docs. + +> Qwik React components still have hydration, thus it is recommended to use Qwik-React as a migration strategy to resumable components. + +### jsxImportSource + +Unfortunately, TypeScript can only have one `jsxImportSource` default. If you're using React, Solid, or Preact's Astro integration in your Astro app alongside, please override each component's import source. + +> If you're using [@astrojs/react](https://www.npmjs.com/package/@astrojs/react), you can use [qwik-react](https://qwik.builder.io/docs/integrations/react/#qwik-react-%EF%B8%8F) instead. The proper configuration will be supported out of the box. + +```tsx +/** @jsxImportSource react */ +import { useState } from "react"; + +export const ReactCounter = () => { + const [count, setCount] = useState(0); + return ; +}; +``` + +Solid JS for example, is: + +``` +/** @jsxImportSource solid-js */ +``` + +Preact for example, is: + +``` +/** @jsxImportSource preact */ +``` + +## Named Slots + +For named slots within Astro, instead of adding `q:slot` on the markup, add `slot` instead. + +**my-slot-comp.tsx** + +```tsx +import { Slot, component$, useSignal } from "@builder.io/qwik"; + +export const MySlotComp = component$<{ initial: number }>((props) => { + return ( + <> + + + ); +}); +``` + +**index.astro** + +```astro + +
    Content inside the slot named test!
    +
    +``` + +Default slots work as expected in their Qwik City counterpart. + +## Community Guides + +- [Paul Scanlon](https://www.paulie.dev/) shows a hands-on look at [using Qwik in Astro over React and Vanilla JS](https://thenewstack.io/how-quiks-astro-integration-beats-both-react-and-vanilla-js/). + +- [Rishi Raj Jain](https://twitter.com/rishi_raj_jain_) has written an awesome guide on setting up Qwik with Astro's [Vercel SSR Adapter](https://dev.to/reeshee/qwik-look-at-resumability-with-astro-on-vercel-44fj). + +- [Paul Scanlon](https://www.paulie.dev/) explores using [Qwik as a React alternative](https://thenewstack.io/take-a-qwik-break-from-react-with-astro/) in Astro. + +## Videos + +- Watch Jason & Steve [discuss the Qwik Astro integration] on the [LWJ show](https://www.youtube.com/@learnwithjason). + +- [Awesome's Qwik + Astro video](https://www.youtube.com/watch?v=wKvkYUNBa5k) goes into how Astro just got even faster. ## Contributing @@ -160,6 +371,7 @@ There's also a `qwik-astro` channel in the builder.io discord to discuss API cha Special thanks to Matthew and Nate from the Astro core team! This integration would not be possible without their help. Nate's handles: + - [Twitter](https://twitter.com/n_moore) - [GitHub](https://github.com/natemoo-re) diff --git a/packages/docs/src/routes/docs/integrations/bootstrap/index.mdx b/packages/docs/src/routes/docs/integrations/bootstrap/index.mdx index 37a47435b735..52b5c76eaca9 100644 --- a/packages/docs/src/routes/docs/integrations/bootstrap/index.mdx +++ b/packages/docs/src/routes/docs/integrations/bootstrap/index.mdx @@ -4,6 +4,7 @@ keywords: 'bootstrap, spinners, alerts, buttons' contributors: - mugan86 - mhevery + - aendel updated_at: '2023-09-26T19:56:54Z' created_at: '2023-09-26T19:56:54Z' --- @@ -29,7 +30,7 @@ The previous command updates your app with the necessary dependencies. It also adds new files inside to your project folder: -- `src/models/bootstrap.ts`: Model to define Boostrap components info to use in props. +- `src/models/bootstrap.ts`: Model to define Bootstrap components info to use in props. - `src/constants/data.ts`: Constant values information that we will use in the example to be created with this integration. - `src/components/bootstrap/button.tsx`: Button component feature with Bootstrap. - `src/components/bootstrap/alert.tsx`: Alert component feature with Bootstrap. @@ -37,7 +38,7 @@ It also adds new files inside to your project folder: - `src/components/bootstrap/index.ts`: Entry point where we will add the components of the elements that will be used, for easier and cleaner access to them. - `src/components/bootstrap/navbar.tsx`: Navbar component functionality with Bootstrap to demonstrate how to add and use JavaScript functionalities without encountering the 'document is not defined' error due to improper import declaration in the Qwik lifecycle. - `src/routes/bootstrap/layout.tsx`: Layout where we add Bootstrap styling configuration to ensure styles are applied to all routes nested within the main Bootstrap route. -- `src/routes/bootstrap/index.tsx`: Boostrap components option home page. +- `src/routes/bootstrap/index.tsx`: Bootstrap components option home page. - `src/routes/bootstrap/buttons/index.tsx`: Example to consume Button component with demo data. - `src/routes/bootstrap/alerts/index.tsx`: Example to consume Alert component with demo data. - `src/routes/bootstrap/spinners/index.tsx`: Example to consume Spinner component with demo data. diff --git a/packages/docs/src/routes/docs/integrations/builderio/index.mdx b/packages/docs/src/routes/docs/integrations/builderio/index.mdx index 4455aaa6ceb3..f7e9efff62d2 100644 --- a/packages/docs/src/routes/docs/integrations/builderio/index.mdx +++ b/packages/docs/src/routes/docs/integrations/builderio/index.mdx @@ -7,6 +7,7 @@ contributors: - Benny-Nottonson - mrhoodz - steve8708 + - aendel updated_at: '2023-09-28T17:30:02Z' created_at: '2023-05-02T09:18:30Z' --- @@ -90,7 +91,7 @@ export default component$(() => { See our full integration guides [here](https://www.builder.io/c/docs/developers) -Also, when you push your integration to production, go back and update your preview URL to your production URL so now anyone on your team can visuall create content in your Qwik app! +Also, when you push your integration to production, go back and update your preview URL to your production URL so now anyone on your team can visually create content in your Qwik app! Also, to integrate structured data, see [this guide](https://www.builder.io/c/docs/integrate-cms-data) diff --git a/packages/docs/src/routes/docs/integrations/icons/index.mdx b/packages/docs/src/routes/docs/integrations/icons/index.mdx index 2caefa9a4706..18218d8988bd 100644 --- a/packages/docs/src/routes/docs/integrations/icons/index.mdx +++ b/packages/docs/src/routes/docs/integrations/icons/index.mdx @@ -7,6 +7,7 @@ contributors: - manucorporat - Benny-Nottonson - mrhoodz + - aendel updated_at: '2023-07-18T17:47:48Z' created_at: '2023-04-25T11:05:50Z' --- @@ -20,7 +21,7 @@ Icons are an important part of any application. There are already more than 180. ## `qwikest/icons` -This package allows for a stremalined way to add icons to your Qwik app from a variety of icon sets. +This package allows for a streamlined way to add icons to your Qwik app from a variety of icon sets. - `Bs`: Bootstrap Icons - `Go`: Octicons by GitHub @@ -69,9 +70,9 @@ Icones is powered by [iconify](https://iconify.design/) which allows to easy add Click the `Qwik` button to copy the icon code to your clipboard, then paste it into your project. ```tsx -import type { QwikIntrinsicElements } from '@builder.io/qwik' +import type { PropsOf } from '@builder.io/qwik' -export function OcticonAlertFill12(props: QwikIntrinsicElements['svg'], key: string) { +export function OcticonAlertFill12(props: PropsOf<'svg'>, key: string) { return ( ) diff --git a/packages/docs/src/routes/docs/integrations/image-optimization/index.mdx b/packages/docs/src/routes/docs/integrations/image-optimization/index.mdx index 3bb720940b4f..af8978d7be8e 100644 --- a/packages/docs/src/routes/docs/integrations/image-optimization/index.mdx +++ b/packages/docs/src/routes/docs/integrations/image-optimization/index.mdx @@ -15,6 +15,7 @@ contributors: - avanderpluijm - fabian-hiller - manucorporat + - aendel updated_at: '2023-10-03T18:53:23Z' created_at: '2023-04-19T22:13:46Z' --- @@ -212,7 +213,7 @@ export default component$(() => { ## `qwik-image` -Performant images with automatic optimisation. +Performant images with automatic optimization. ```bash npm install qwik-image diff --git a/packages/docs/src/routes/docs/integrations/modular-forms/index.mdx b/packages/docs/src/routes/docs/integrations/modular-forms/index.mdx index 0afb0ce8b1e6..3bf707a73203 100644 --- a/packages/docs/src/routes/docs/integrations/modular-forms/index.mdx +++ b/packages/docs/src/routes/docs/integrations/modular-forms/index.mdx @@ -179,7 +179,7 @@ If we now put all the building blocks together, we get a working login form. Bel ```tsx // @ts-nocheck /* eslint-disable @typescript-eslint/no-unused-vars */ -import { $, component$ } from '@builder.io/qwik'; +import { $, component$, type QRL } from '@builder.io/qwik'; import { routeLoader$ } from '@builder.io/qwik-city'; import type { InitialValues, SubmitHandler } from '@modular-forms/qwik'; import { formAction$, useForm, valiForm$ } from '@modular-forms/qwik'; @@ -214,7 +214,7 @@ export default component$(() => { validate: valiForm$(LoginSchema), }); - const handleSubmit: SubmitHandler = $((values, event) => { + const handleSubmit: QRL> = $((values, event) => { // Runs on client console.log(values); }); diff --git a/packages/docs/src/routes/docs/integrations/og-img/index.mdx b/packages/docs/src/routes/docs/integrations/og-img/index.mdx new file mode 100644 index 000000000000..cc9bd7ca975a --- /dev/null +++ b/packages/docs/src/routes/docs/integrations/og-img/index.mdx @@ -0,0 +1,92 @@ +--- +title: OG Image (og-img) | Integrations +keywords: 'open-graph, image, satori, resvg' +contributors: + - fabian-hiller + - aendel +updated_at: '2024-01-10T21:56:50Z' +created_at: '2024-01-10T21:56:50Z' +--- + +# OG Image (og-img) + +With [`og-img`](https://github.com/fabian-hiller/og-img) you can easily add dynamic Open Graph images to your Qwik website. These are displayed, for example, when your website is shared on social media or via messenger services. + +> `og-img` is a framework agnostic package for generating Open Graph images using [Satori](https://github.com/vercel/satori) and [resvg](https://github.com/RazrFalcon/resvg). + +To get started, install the npm package: + +```bash +npm install og-img +``` + +## How it works + +To generate an image, all you need to do is return an `ImageResponse` via a server endpoint. To create one, add a new route to your Qwik website and export a function called `onGet` in the index file. To easily define the content of your image, you can use the `html` tagged template literal. + +> To get proper syntax highlighting for the tagged template literal in Visual Studio Code, you can install the [lit-html](https://marketplace.visualstudio.com/items?itemName=bierner.lit-html) extension. + +```ts title="src/routes/og-image/index.ts" +import type { RequestHandler } from '@builder.io/qwik-city'; +import { fetchFont, ImageResponse, html } from 'og-img'; + +export const onGet: RequestHandler = async ({ send }) => { + send( + new ImageResponse( + // Use Tailwind CSS or style attribute + html` +
    + Hello, world! +
    + `, + { + width: 1200, + height: 600, + fonts: [ + { + name: 'Roboto', + // Use `fs` (Node.js only) or `fetch` to read font file + data: await fetchFont( + 'https://www.example.com/fonts/roboto-400.ttf' + ), + weight: 400, + style: 'normal', + }, + ], + } + ) + ); +}; +``` + +Then all you need to do is point to this API endpoint with a meta tag in the head of your Qwik website to embed the Open Graph image. + +```html + + Hello, world! + + +``` + +You can also generate the meta tag dynamically by export a `head` object in your routes. + +```tsx title="src/routes/index.tsx" +import { component$ } from '@builder.io/qwik'; +import type { DocumentHead } from '@builder.io/qwik-city'; + +export default component$(() => { + return

    Hello, world!

    ; +}); + +export const head: DocumentHead = { + title: 'Hello, world!', + meta: [ + { + property: 'og:image', + content: 'https://www.example.com/og-image', + }, + ], +}; +``` + +You can use URL parameters to dynamically change the content of your Open Graph image. Take a look at [Valibot's Open Graph image](https://valibot.dev/og-image/?title=Example%20Title&description=The%20content%20of%20this%20image%20was%20generated%20dynamically). You can find the source code [here](https://github.com/fabian-hiller/valibot/blob/main/website/src/routes/og-image/index.ts). diff --git a/packages/docs/src/routes/docs/integrations/prisma/index.mdx b/packages/docs/src/routes/docs/integrations/prisma/index.mdx index 3be9d62e8ebb..db805aa557d7 100644 --- a/packages/docs/src/routes/docs/integrations/prisma/index.mdx +++ b/packages/docs/src/routes/docs/integrations/prisma/index.mdx @@ -1,6 +1,6 @@ --- title: Prisma | Integrations -keywords: 'prisma, orm, database, data, storage, postgress, mondodb' +keywords: 'prisma, orm, database, data, storage, postgres, mongodb' contributors: - manucorporat - ulic75 @@ -11,6 +11,7 @@ contributors: - mrhoodz - enesflow - fabian-hiller + - aendel updated_at: '2023-10-03T18:53:23Z' created_at: '2023-04-25T11:05:50Z' --- diff --git a/packages/docs/src/routes/docs/integrations/react/index.mdx b/packages/docs/src/routes/docs/integrations/react/index.mdx index ebb4b32fa129..327bbdd9f7a7 100644 --- a/packages/docs/src/routes/docs/integrations/react/index.mdx +++ b/packages/docs/src/routes/docs/integrations/react/index.mdx @@ -77,7 +77,7 @@ The above command will perform the following: return { ..., plugins: [ - ..., + ..., // The important part qwikReact() ], @@ -92,7 +92,7 @@ The above command will perform the following: > - `@mui/material 5.11.9` > - `@mui/x-data-grid 5.17.24` > - `src/route`: -> - `/src/routes/react`: New public route showcasing react integration +> - `/src/routes/react`: New public route showcasing react integration > - `/src/integrations/react`: Here's where the react component lives > > We will ignore these in this guide and instead take you through the process from the beginning. @@ -102,7 +102,7 @@ The above command will perform the following: Let's start with a simple example. We will create a simple React component and then wrap it in a Qwik component. We will then use the Qwik component in a Qwik route. - +
    ```tsx /qwikify$/ /QGreetings/ @@ -148,7 +148,7 @@ React and Qwik components can not be mixed in the same file, if you check your p The above example shows how to SSR static React content on the server. The benefit is that that component will never re-render in the browser and therefore its code never downloads to the client. But what if the component needs to be interactive, and therefore we need to download its behavior in the browser? Let's start with building a simple counter example in React. - +
    ```tsx /qwikify$/ /QGreetings/ @@ -191,7 +191,7 @@ export default component$(() => { Notice that clicking on the `Count` button does nothing. This is because the React has not been downloaded and therefore the component was not hydrated. We need to tell Qwik to download the React component and hydrate it, but we need to specify the condition under which we want to do that. Doing it eagerly would lose all of the benefits of turning the React application into islands. In this case, we want to download the component when the user hovers over the button, we do so by adding `{: eagerness: 'hover' }` to `qwikify$()`. -
    @@ -246,7 +246,7 @@ By giving the `eagerness` property to `qwikify$()`, we are allowing you to fine- In the previous example, we had a single island that we delay-hydrated. But once you have multiple islands, there will be a need to communicate between them. This example shows how to do inter-island communication with Qwik. -
    @@ -317,7 +317,7 @@ Also notice that `console.log('Qwik Render');` never executes in the browser. In the previous example, we had two islands. The `QButton` had to be eagerly hydrated so that React can set up the `onClick` event handler. This is a bit wasteful because the `QButton` island will never need to be re-rendered as its output is static. Clicking on the `QButton` will not cause the `QButton` island to re-render. In such a case, we can ask Qwik to register the `click` listener instead of hydrating the whole component in React just to attach a listener. This is done by using the `host:` prefix in the event name. -
    @@ -376,7 +376,7 @@ Now hovering over the button will not do anything (no React hydration). Clicking A common use case is to pass content children to components. This works with Qwik React as well. In the React component just declare `children` in your props and use them as expected (See `react.tsx`). -
    @@ -437,10 +437,10 @@ Notice that the `QFrame` island is never hydrated because it has no `eagerness` ## 6. Using React libraries -Finally, it is possible to use React libraries in your Qwik application. In this example [Material UI](https://mui.com/) and [Emotion](https://emotion.sh/docs/introduction) are used to render this React example. +Finally, it is possible to use React libraries in your Qwik application. In this example [Material UI](https://mui.com/) and [Emotion](https://emotion.sh/docs/introduction) are used to render this React example. -
    @@ -508,7 +508,7 @@ export default component$(() => {
    -The React example is hydrated on hover and works as you would expect. +The React example is hydrated on hover and works as you would expect. # Rules @@ -558,7 +558,7 @@ The `qwikify$(ReactCmp, options?): QwikCmp` allows to implement partial hydratio Notice that by default no React code will run in the browser, meaning that React component will NOT be interactive by default, for example, in the following example, we _qwikify_ the [Slider](https://mui.com/material-ui/react-slider/) component from MUI, but it will not be interactive (it is missing an `eagerness` property to tell Qwik when the React component should be hydrated in the browser.) -
    diff --git a/packages/docs/src/routes/docs/integrations/storybook/index.mdx b/packages/docs/src/routes/docs/integrations/storybook/index.mdx index edd50d6c7275..dad733a4a670 100644 --- a/packages/docs/src/routes/docs/integrations/storybook/index.mdx +++ b/packages/docs/src/routes/docs/integrations/storybook/index.mdx @@ -6,7 +6,8 @@ contributors: - igorbabko - Benny-Nottonson - mrhoodz -updated_at: '2023-10-03T18:53:23Z' + - thenhawke +updated_at: '2024-01-15T18:53:23Z' created_at: '2023-04-25T19:05:53Z' --- @@ -31,3 +32,81 @@ Now you can serve the Storybook dashboard: ```shell npm run storybook ``` + +## Examples + +### Simple component + +The following demonstrates a simple story that uses a qwik component: + +```tsx title="src/components/button.tsx" +import { component$ } from "@builder.io/qwik"; + +export interface ButtonProps { + label: string; +} + +export const Button = component$(({label}) => { + return ( + + ); +}); +``` + +```tsx title="src/components/button.stories.tsx" +import type { Meta, StoryObj } from "storybook-framework-qwik"; +import {Button, type ButtonProps} from "./button"; + +const meta: Meta = { + component: Button, +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + label: "Hello World", + } +}; +``` + +### Component That Uses QwikCity + +When using Qwikcity, you must pass a context to the story by using the [``](https://qwik.builder.io/docs/api/#qwikcitymockprovider): + +```tsx title="src/components/with-link.tsx" +import { component$ } from "@builder.io/qwik"; +import { Link } from "@builder.io/qwik-city"; + +export const WithLink = component$(() => { + return ( + Google Link + ); +}); +``` + +```tsx title="src/components/with-link.stories.tsx" +import type { Meta, StoryObj } from "storybook-framework-qwik"; +import { QwikCityMockProvider } from "@builder.io/qwik-city"; + +import { WithLink } from "./with-link"; + +const meta: Meta = { + component: WithLink, +}; + +type Story = StoryObj; +export default meta; + +export const Primary: Story = { + render: () => + + + +}; +``` + +For more information please refer to the [storybook documentation.](https://storybook.js.org/docs) \ No newline at end of file diff --git a/packages/docs/src/routes/docs/integrations/supabase/index.mdx b/packages/docs/src/routes/docs/integrations/supabase/index.mdx index 9cba4d8f9ea3..4d411ee7b4de 100644 --- a/packages/docs/src/routes/docs/integrations/supabase/index.mdx +++ b/packages/docs/src/routes/docs/integrations/supabase/index.mdx @@ -8,6 +8,7 @@ contributors: - Benny-Nottonson - mrhoodz - samijaber + - aendel updated_at: '2023-10-03T18:53:23Z' created_at: '2023-04-25T11:05:50Z' --- @@ -31,7 +32,7 @@ PUBLIC_SUPABASE_ANON_KEY=eyJhb....... You should be able to get this values from the Supabase dashboard, create a new `.env.local` file at the root of the project and paste them there. -> Note: It's possible to use Supabase with Qwik completely client-side, however, you would lose some of the performance benefits that you can acheive with leveraging the server. For a working example and code head over to [this repository](https://github.com/hamatoyogi/qwik-supabase-example). +> Note: It's possible to use Supabase with Qwik completely client-side, however, you would lose some of the performance benefits that you can achieve with leveraging the server. For a working example and code head over to [this repository](https://github.com/hamatoyogi/qwik-supabase-example). ## Server-side diff --git a/packages/docs/src/routes/docs/integrations/vitest/index.mdx b/packages/docs/src/routes/docs/integrations/vitest/index.mdx index 6076c6193647..acf8382ed603 100644 --- a/packages/docs/src/routes/docs/integrations/vitest/index.mdx +++ b/packages/docs/src/routes/docs/integrations/vitest/index.mdx @@ -24,10 +24,10 @@ You can add vitest easily by using the following Qwik starter script: npm run qwik add vitest ``` -After running the command, vitest will be installed and a new component will be added to your project. The component will be added to the `src/components/example` directory as long as a new unit test is named `example.spec.ts`. -If you are looking for an example for a Component with QwikCity checkout [QwikCityMockProvider](/docs/api/index.mdx#QwikCityMockProvider) +After running the command, vitest will be installed and a new component will be added to your project. The component will be added to the `src/components/example` directory as long as a new unit test named `example.spec.tsx`. +If you are looking for an example for a Component with QwikCity checkout [QwikCityMockProvider](/docs/(qwikcity)/api/index.mdx#qwikcitymockprovider). -```tsx title="example.spec.ts" +```tsx title="example.spec.tsx" import { createDOM } from '@builder.io/qwik/testing'; import { test, expect } from 'vitest'; import { ExampleTest } from './example'; diff --git a/packages/docs/src/routes/docs/menu.md b/packages/docs/src/routes/docs/menu.md index 477d873287c0..31b4db5ae5e8 100644 --- a/packages/docs/src/routes/docs/menu.md +++ b/packages/docs/src/routes/docs/menu.md @@ -38,9 +38,17 @@ ## Cookbook - [Overview](/docs/cookbook/index.mdx) +- [Algolia Search](/docs/cookbook/algolia-search/index.mdx) +- [Combine Handlers](/docs/cookbook/combine-request-handlers/index.mdx) +- [Fonts](/docs/cookbook/fonts/index.mdx) - [Glob Import](/docs/cookbook/glob-import/index.mdx) - [Media Controller](/docs/cookbook/mediaController/index.mdx) -- [Portal](/docs/cookbook/portal/index.mdx) +- [NavLink](/docs/cookbook/nav-link/index.mdx) +- [Node Docker deploy](/docs/cookbook/node-docker-deploy/index.mdx) +- [Portals](/docs/cookbook/portals/index.mdx) +- [Re-exporting loaders](/docs/cookbook/re-exporting-loaders/index.mdx) +- [Sync events w state](/docs/cookbook/sync-events/index.mdx) +- [Theme Managment](/docs/cookbook/theme-management/index.mdx) ## Integrations @@ -56,6 +64,7 @@ - [Leaflet Map](integrations/leaflet-map/index.mdx) - [Modular Forms](integrations/modular-forms/index.mdx) - [Nx Monorepos](integrations/nx/index.mdx) +- [OG Image](integrations/og-img/index.mdx) - [Orama](integrations/orama/index.mdx) - [Panda CSS](integrations/panda-css/index.mdx) - [Partytown](integrations/partytown/index.mdx) diff --git a/packages/docs/src/routes/examples/[...id]/examples.css b/packages/docs/src/routes/examples/[...id]/examples.css index 0386afb6381a..6b7d20c8d883 100644 --- a/packages/docs/src/routes/examples/[...id]/examples.css +++ b/packages/docs/src/routes/examples/[...id]/examples.css @@ -11,10 +11,10 @@ grid-template-rows: 100%; grid-template-columns: 100vw 100vw; grid-template-areas: 'example-content-panel example-repl-panel'; - height: calc(100% - var(--header-height)); + height: 100%; + height: calc(100% - var(--header-height) - var(--panel-toggle-height)); transform: translate3d(0, 0, 0); transition: transform 500ms; - height: 100%; } .examples-panel-input { @@ -35,7 +35,6 @@ border-width: 1px; border-top-style: solid; border-top-left-radius: 5px; - overflow: hidden; } .examples .repl { @@ -43,7 +42,7 @@ grid-template-rows: auto; grid-template-columns: 100vw 100vw 100vw; grid-template-areas: 'repl-input-panel repl-output-panel repl-detail-panel'; - height: calc(100vh - var(--header-height)); + height: 100%; } @media (min-width: 768px) { diff --git a/packages/docs/src/routes/examples/apps/partial/hackernews-index/app.tsx b/packages/docs/src/routes/examples/apps/partial/hackernews-index/app.tsx index 02a3208d9561..e93259861f4b 100644 --- a/packages/docs/src/routes/examples/apps/partial/hackernews-index/app.tsx +++ b/packages/docs/src/routes/examples/apps/partial/hackernews-index/app.tsx @@ -14,10 +14,10 @@ export const HackerNews = component$(() => { }); return ( - <> +
    ); }); diff --git a/packages/docs/src/routes/examples/apps/partial/hackernews-index/hacker-news.css b/packages/docs/src/routes/examples/apps/partial/hackernews-index/hacker-news.css index 03a5c554b631..414f936d4f5a 100644 --- a/packages/docs/src/routes/examples/apps/partial/hackernews-index/hacker-news.css +++ b/packages/docs/src/routes/examples/apps/partial/hackernews-index/hacker-news.css @@ -1,4 +1,4 @@ -body { +.hacker-news { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-size: 15px; diff --git a/packages/docs/src/routes/playground/playground.css b/packages/docs/src/routes/playground/playground.css index c90233bf3bae..50a39de72378 100644 --- a/packages/docs/src/routes/playground/playground.css +++ b/packages/docs/src/routes/playground/playground.css @@ -1,6 +1,7 @@ .playground { width: 100%; height: cal(100vh - var(--header-height)); + height: cal(100dvh - var(--header-height)); --playground-header-height: 0px; } @@ -14,6 +15,7 @@ 'repl-input-panel repl-detail-panel'; top: calc(var(--header-height) + var(--playground-header-height)); height: calc(100vh - var(--header-height) - var(--panel-toggle-height)); + height: calc(100dvh - var(--header-height) - var(--panel-toggle-height)); } .playground .repl-col-resize-bar { @@ -56,6 +58,9 @@ } @media (max-width: 768px) { + body { + overflow-x: hidden; + } .playground .repl { width: 300vw; grid-template-rows: 100%; diff --git a/packages/docs/src/routes/plugin@builder.io-redirect.ts b/packages/docs/src/routes/plugin@builder.io-redirect.ts new file mode 100644 index 000000000000..b020285d28c2 --- /dev/null +++ b/packages/docs/src/routes/plugin@builder.io-redirect.ts @@ -0,0 +1,36 @@ +import type { RequestHandler } from '@builder.io/qwik-city/middleware/request-handler/types'; + +/** + * @file + * + * Redirects requests from Builder.io to the qwik.dev domain. + * + * This redirect has been placed here because of security vulnerabilities on the builder.io domain. + * + * # Issue + * + * - Because of Qwik REPL is is possible to write arbitrary code that runs on a builder.io subdomain + * with qwik.builder.io/repl + * - This opens vulnerabilities around XSS, cookie jacking, because builder.io uses cross-subdomain + * cookies + * + * # Solution + * + * - Move the qwik.builder.io/repl of the qwik.builder.io domain to the qwik.dev domain. + * - Place a 308 redirect here to ensure that all requests to the builder.io domain are redirected to + * the qwik.dev domain. + */ + +export const onRequest: RequestHandler = ({ request, redirect }) => { + const url = new URL(request.url); + if (url.hostname === 'qwik.builder.io') { + // Redirect to the Builder.io plugin + url.hostname = 'qwik.dev'; + const pathname = url.pathname; + if (pathname.startsWith('/repl/')) { + // Prevent anything from /repl/ from being redirected so that we don't accidentally serve a script tag. + url.pathname = ''; + } + throw redirect(308, url.toString()); + } +}; diff --git a/packages/docs/src/routes/tutorial/events/synchronous-sync/index.mdx b/packages/docs/src/routes/tutorial/events/synchronous-sync/index.mdx new file mode 100644 index 000000000000..709301b7ed14 --- /dev/null +++ b/packages/docs/src/routes/tutorial/events/synchronous-sync/index.mdx @@ -0,0 +1,68 @@ +--- +title: Synchronous Event Processing | Tutorial +contributors: + - adamdbradley + - manucorporat + - kerbelp + - mhevery + - cunzaizhuyi + - spenserblack + - hamatoyogi + - mrhoodz +updated_at: '2023-06-25T19:43:33Z' +created_at: '2022-08-02T12:07:45Z' +--- + +While not a common use case, you may occasionally need to process events synchronously. + +Since Qwik processes asynchronously by default, your code must be explicitly configured for synchronous calls. + +There are two ways of processing events synchronously: +1. preferred way: use `sync$()` to load code synchronously. Fast, resumable but with **significant restrictions** on event handler size. +2. eager registration: use `useVisibleTask$()` to load code synchronously. No restrictions, but requires eager code execution, which goes against resumability. + +This example shows how to use synchronous code blocks using `sync$()`. + +```tsx + { + if (event.ctrlKey) { + event.preventDefault(); + } + })}> + link + +``` + +## `sync$()` Restrictions (BETA) + +The `sync$()` function is a resumable way to process events synchronously. However, it has some significant restrictions. The `sync$()` **can't close over anything**. The implications are that you can't: +- access any state: The recommended way to get the state into function is to place on element attributes. +- access any non-browser functions: No imports or closing over any variables or functions are allowed. +- the function should be small as it will get inlined into the resulting HTML. + +For this reason, we recommended breaking up the event handlers into two parts: +1. **sync$()**: The part which must be synchronous. This part should be small and not close over anything. +2. **$()**: The part which can be asynchronous. This part can be large and can close over anything including the state. + +```tsx + { + // This part is synchronous and can't close over anything. + if (event.ctrlKey) { + event.preventDefault(); + } + }), + $(() => { + // This part can be asynchronous and can close over anything. + console.log('clicked'); + }) + ]}> + link + +``` + + + + **Your task:** Convert the `onClick$` from asynchronous event to synchronous event by using [`useVisibleTask$`](/docs/(qwik)/components/tasks/index.mdx#usevisibletask) lifecycle and [normal event registration](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). diff --git a/packages/docs/src/routes/tutorial/events/synchronous-sync/problem/app.tsx b/packages/docs/src/routes/tutorial/events/synchronous-sync/problem/app.tsx new file mode 100644 index 000000000000..d4b984d40cc6 --- /dev/null +++ b/packages/docs/src/routes/tutorial/events/synchronous-sync/problem/app.tsx @@ -0,0 +1,17 @@ +import { component$ } from '@builder.io/qwik'; + +export default component$(() => { + return ( + { + if (event.ctrlKey) { + event.preventDefault(); + } + console.log('clicked'); + }} + > + click me! + + ); +}); diff --git a/packages/docs/src/routes/tutorial/events/synchronous-sync/solution/app.tsx b/packages/docs/src/routes/tutorial/events/synchronous-sync/solution/app.tsx new file mode 100644 index 000000000000..ed85a64c17a4 --- /dev/null +++ b/packages/docs/src/routes/tutorial/events/synchronous-sync/solution/app.tsx @@ -0,0 +1,21 @@ +import { component$, sync$, $ } from '@builder.io/qwik'; + +export default component$(() => { + return ( + { + if (event.ctrlKey) { + event.preventDefault(); + } + }), + $(() => { + console.log('clicked'); + }), + ]} + > + click me! + + ); +}); diff --git a/packages/docs/src/routes/tutorial/events/synchronous-visible/index.mdx b/packages/docs/src/routes/tutorial/events/synchronous-visible/index.mdx new file mode 100644 index 000000000000..5f0b2e7ade3c --- /dev/null +++ b/packages/docs/src/routes/tutorial/events/synchronous-visible/index.mdx @@ -0,0 +1,27 @@ +--- +title: Synchronous Event Processing | Tutorial +contributors: + - adamdbradley + - manucorporat + - kerbelp + - mhevery + - cunzaizhuyi + - spenserblack + - hamatoyogi + - mrhoodz +updated_at: '2023-06-25T19:43:33Z' +created_at: '2022-08-02T12:07:45Z' +--- + +While not a common use case, you may occasionally need to process events synchronously. + +Since Qwik processes asynchronously by default, your code must be explicitly configured for synchronous calls. + +There are two ways of processing events synchronously: +1. preferred way: use `sync$()` to load code synchronously. Fast, resumable but with **significant restrictions** on event handler size. +2. eager registration: use `useVisibleTask$()` to load code synchronously. No restrictions, but requires eager code execution, which goes against resumability. + +This example shows how to eagerly execute code and set up a classical event handler with no restrictions but with the cost of eager execution. + + +**Your task:** Convert the `onClick$` from asynchronous event to synchronous event by using [`useVisibleTask$`](/docs/(qwik)/components/tasks/index.mdx#usevisibletask) lifecycle and [normal event registration](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). diff --git a/packages/docs/src/routes/tutorial/events/synchronous/problem/app.tsx b/packages/docs/src/routes/tutorial/events/synchronous-visible/problem/app.tsx similarity index 100% rename from packages/docs/src/routes/tutorial/events/synchronous/problem/app.tsx rename to packages/docs/src/routes/tutorial/events/synchronous-visible/problem/app.tsx diff --git a/packages/docs/src/routes/tutorial/events/synchronous/solution/app.tsx b/packages/docs/src/routes/tutorial/events/synchronous-visible/solution/app.tsx similarity index 100% rename from packages/docs/src/routes/tutorial/events/synchronous/solution/app.tsx rename to packages/docs/src/routes/tutorial/events/synchronous-visible/solution/app.tsx diff --git a/packages/docs/src/routes/tutorial/events/synchronous/index.mdx b/packages/docs/src/routes/tutorial/events/synchronous/index.mdx deleted file mode 100644 index 836d5ae1e2b7..000000000000 --- a/packages/docs/src/routes/tutorial/events/synchronous/index.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Synchronous Event Processing | Tutorial -contributors: - - adamdbradley - - manucorporat - - kerbelp - - mhevery - - cunzaizhuyi - - spenserblack - - hamatoyogi - - mrhoodz -updated_at: '2023-06-25T19:43:33Z' -created_at: '2022-08-02T12:07:45Z' ---- - -While not a common use case, you may occasionally need to process events synchronously. - -Since Qwik processes asynchronously by default, your code must be explicitly configured for synchronous calls. This example shows how to eagerly load an event handler that processes a synchronous event. - -> **Your task:** Convert the `onClick$` from asynchronous event to synchronous event by using [`useVisibleTask$`](/docs/(qwik)/components/tasks/index.mdx#usevisibletask) lifecycle and [normal event registration](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). diff --git a/packages/docs/src/routes/tutorial/props/closures/index.mdx b/packages/docs/src/routes/tutorial/props/closures/index.mdx index a0b7d69cdc21..caca639b3002 100644 --- a/packages/docs/src/routes/tutorial/props/closures/index.mdx +++ b/packages/docs/src/routes/tutorial/props/closures/index.mdx @@ -32,12 +32,12 @@ Most of the time we use the first form as it allows us to inline our callbacks d A component can declare a callback in its props by: - Property that ends in `$` (as in `goodbye$`) -- The type of the property is `PropFunction` where `T` is the lazy reference type that the QRL points to (function signature). +- The type of the property is `QRL` where `T` is the lazy reference type that the QRL points to (function signature). ```tsx interface MyComponentProps { - goodbye$: PropFunction<() => void>; - hello$: PropFunction<() => void>; + goodbye$: QRL<() => void>; + hello$: QRL<() => void>; } export const MyComponent = component$((props: MyComponentProps) => { ... }); diff --git a/packages/docs/src/routes/tutorial/props/closures/problem/app.tsx b/packages/docs/src/routes/tutorial/props/closures/problem/app.tsx index 7e19707dc987..deed4b9981bd 100644 --- a/packages/docs/src/routes/tutorial/props/closures/problem/app.tsx +++ b/packages/docs/src/routes/tutorial/props/closures/problem/app.tsx @@ -1,4 +1,4 @@ -import { component$, $, type PropFunction } from '@builder.io/qwik'; +import { component$, $, type QRL } from '@builder.io/qwik'; export default component$(() => { const goodbye$ = $(() => alert('Good Bye!')); @@ -10,8 +10,8 @@ export default component$(() => { }); interface MyComponentProps { - goodbye$: PropFunction<() => void>; - hello$: PropFunction<(name: string) => void>; + goodbye$: QRL<() => void>; + hello$: QRL<(name: string) => void>; } export const MyComponent = component$((props: MyComponentProps) => { return ( diff --git a/packages/docs/src/routes/tutorial/props/closures/solution/app.tsx b/packages/docs/src/routes/tutorial/props/closures/solution/app.tsx index 7e19707dc987..deed4b9981bd 100644 --- a/packages/docs/src/routes/tutorial/props/closures/solution/app.tsx +++ b/packages/docs/src/routes/tutorial/props/closures/solution/app.tsx @@ -1,4 +1,4 @@ -import { component$, $, type PropFunction } from '@builder.io/qwik'; +import { component$, $, type QRL } from '@builder.io/qwik'; export default component$(() => { const goodbye$ = $(() => alert('Good Bye!')); @@ -10,8 +10,8 @@ export default component$(() => { }); interface MyComponentProps { - goodbye$: PropFunction<() => void>; - hello$: PropFunction<(name: string) => void>; + goodbye$: QRL<() => void>; + hello$: QRL<(name: string) => void>; } export const MyComponent = component$((props: MyComponentProps) => { return ( diff --git a/packages/docs/src/routes/tutorial/reactivity/resource/solution/app.tsx b/packages/docs/src/routes/tutorial/reactivity/resource/solution/app.tsx index 1917306c30cb..b1cf3f35e016 100644 --- a/packages/docs/src/routes/tutorial/reactivity/resource/solution/app.tsx +++ b/packages/docs/src/routes/tutorial/reactivity/resource/solution/app.tsx @@ -27,7 +27,7 @@ export default component$(() => {

    diff --git a/packages/docs/src/routes/tutorial/tutorial-content-footer.tsx b/packages/docs/src/routes/tutorial/tutorial-content-footer.tsx index 44e798c00451..f564c389a055 100644 --- a/packages/docs/src/routes/tutorial/tutorial-content-footer.tsx +++ b/packages/docs/src/routes/tutorial/tutorial-content-footer.tsx @@ -1,20 +1,37 @@ -import { component$ } from '@builder.io/qwik'; +import { component$, useSignal } from '@builder.io/qwik'; import { ensureDefaultFiles, type TutorialStore } from './layout'; export const TutorialContentFooter = component$(({ store }: TutorialContentFooterProps) => { + let solutionViewSig = useSignal(false); + return ( ) : null} diff --git a/packages/insights/src/components/router-head/router-head.tsx b/packages/insights/src/components/router-head/router-head.tsx index 53575767c1a3..7c1ff7c898b7 100644 --- a/packages/insights/src/components/router-head/router-head.tsx +++ b/packages/insights/src/components/router-head/router-head.tsx @@ -23,7 +23,7 @@ export const RouterHead = component$(() => { ))} {head.styles.map((s) => ( -