├── .changeset ├── README.md └── config.json ├── .devcontainer └── devcontainer.json ├── .github └── workflows │ ├── ci.yml │ ├── init-submodules.sh │ ├── release.yml │ ├── test.yml │ └── version.sh ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── package.json ├── packages ├── adds-to-head │ ├── CHANGELOG.md │ ├── README.md │ ├── integration.ts │ └── package.json ├── bun-websocket │ ├── 0001-add-implementation.patch │ ├── 0002-use-implementation.patch │ ├── 0003-remove-ts-aliases.patch │ ├── CHANGELOG.md │ ├── README.md │ └── package.json ├── client-interaction │ ├── CHANGELOG.md │ ├── README.md │ ├── integration.ts │ ├── package.json │ └── runtime │ │ └── directive.ts ├── cloudflare-websocket │ ├── 0001-add-implementation.patch │ ├── 0002-use-implementation.patch │ ├── 0003-adjust-package.patch │ ├── CHANGELOG.md │ ├── README.md │ └── package.json ├── deno-websocket │ ├── 0001-add-implementation.patch │ ├── 0002-use-implementation.patch │ ├── 0003-update-package.patch │ ├── CHANGELOG.md │ ├── README.md │ └── package.json ├── dynamic-import │ ├── CHANGELOG.md │ ├── README.md │ ├── integration.ts │ ├── package.json │ ├── runtime │ │ └── virtual-module.ts │ └── types.d.ts ├── emotion-extract │ ├── CHANGELOG.md │ ├── README.md │ ├── ast-scanner.ts │ ├── env.d.ts │ ├── env.js │ ├── package.json │ ├── tsconfig.json │ └── vite.ts ├── emotion │ ├── CHANGELOG.md │ ├── README.md │ ├── ast-scanner.ts │ ├── integration.ts │ ├── package.json │ └── types.d.ts ├── global │ ├── CHANGELOG.md │ ├── README.md │ ├── integration.ts │ ├── package.json │ ├── runtime │ │ ├── als.ts │ │ ├── middleware.ts │ │ └── virtual-module.ts │ └── types.d.ts ├── node-websocket │ ├── 0001-add-implementation.patch │ ├── 0002-use-implementation.patch │ ├── 0003-isUpgradeRequest-local.patch │ ├── CHANGELOG.md │ ├── README.md │ └── package.json ├── prerender-patterns │ ├── CHANGELOG.md │ ├── README.md │ ├── integration.ts │ └── package.json ├── scope │ ├── CHANGELOG.md │ ├── README.md │ ├── integration.ts │ ├── package.json │ └── types.d.ts ├── server-only-modules │ ├── CHANGELOG.md │ ├── README.md │ ├── integration.ts │ └── package.json └── typed-api │ ├── CHANGELOG.md │ ├── README.md │ ├── integration.ts │ ├── package.json │ ├── runtime │ ├── api-context-storage.ts │ ├── client-internals.ts │ ├── client.ts │ ├── error-response.ts │ ├── errors.client.ts │ ├── errors.server.ts │ ├── errors.ts │ ├── middleware.ts │ ├── server-client-internals.ts │ ├── server-client.ts │ ├── server-internals.ts │ └── server.ts │ └── types.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tests-e2e ├── client-interaction.spec.ts ├── fixtures │ ├── client-interaction │ │ ├── astro.config.ts │ │ ├── package.json │ │ └── src │ │ │ ├── components │ │ │ └── Counter.jsx │ │ │ └── pages │ │ │ ├── idle.astro │ │ │ └── index.astro │ └── typed-api │ │ ├── astro.config.ts │ │ ├── package.json │ │ ├── src │ │ └── pages │ │ │ ├── all.astro │ │ │ ├── api │ │ │ ├── [...spread].ts │ │ │ ├── [multiple] │ │ │ │ └── [params] │ │ │ │ │ └── replyWithParams.ts │ │ │ ├── [singleParam].ts │ │ │ ├── echoReverse.ts │ │ │ ├── echoReversePrefixed.ts │ │ │ ├── error.ts │ │ │ └── validatedInput.ts │ │ │ ├── basics.astro │ │ │ ├── error.astro │ │ │ ├── multiple-params.astro │ │ │ ├── params.astro │ │ │ ├── post.astro │ │ │ ├── server-env-vars.astro │ │ │ ├── spread-params.astro │ │ │ └── zod-validation.astro │ │ └── tsconfig.json ├── package.json ├── typed-api.spec.ts └── utils.ts ├── tests ├── adds-to-head.test.ts ├── bun-websocket.test.ts ├── cloudflare-websocket.test.ts ├── deno-websocket.test.ts ├── dynamic-import.test.ts ├── emotion-extract.test.ts ├── emotion.build.test.ts ├── emotion.dev.test.ts ├── fixtures │ ├── adds-to-head │ │ ├── astro.config.ts │ │ ├── package.json │ │ └── src │ │ │ ├── components │ │ │ ├── HasScript.astro │ │ │ ├── HasStyle.astro │ │ │ └── IndirectRenderer.astro │ │ │ ├── content │ │ │ └── blog │ │ │ │ └── promo │ │ │ │ └── launch-week-component.mdx │ │ │ └── pages │ │ │ └── index.astro │ ├── dynamic-import │ │ ├── astro.config.ts │ │ ├── package.json │ │ └── src │ │ │ ├── components │ │ │ ├── A.astro │ │ │ ├── B.astro │ │ │ ├── in-a-folder │ │ │ │ └── C.astro │ │ │ └── needs-props.astro │ │ │ ├── env.d.ts │ │ │ └── pages │ │ │ ├── [page].astro │ │ │ └── multiple-instances.astro │ ├── emotion-extract │ │ ├── .dockerignore │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── app │ │ │ ├── app.css │ │ │ ├── root.tsx │ │ │ ├── routes.ts │ │ │ ├── routes │ │ │ │ └── home.tsx │ │ │ └── welcome │ │ │ │ ├── logo-dark.svg │ │ │ │ ├── logo-light.svg │ │ │ │ └── welcome.tsx │ │ ├── package.json │ │ ├── public │ │ │ └── favicon.ico │ │ ├── react-router.config.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── emotion │ │ ├── astro.config.ts │ │ ├── package.json │ │ └── src │ │ │ ├── components │ │ │ ├── preact.tsx │ │ │ ├── react.tsx │ │ │ ├── solid.tsx │ │ │ └── svelte.svelte │ │ │ ├── env.d.ts │ │ │ └── pages │ │ │ ├── astro.astro │ │ │ ├── import-alias.astro │ │ │ ├── import-namespace.astro │ │ │ ├── preact.astro │ │ │ ├── react.astro │ │ │ ├── solid.astro │ │ │ └── svelte.astro │ ├── global │ │ ├── astro.config.ts │ │ ├── package.json │ │ └── src │ │ │ ├── components │ │ │ ├── Counter.jsx │ │ │ ├── ServerRenderOnly.jsx │ │ │ └── Title.astro │ │ │ └── pages │ │ │ ├── [rest].astro │ │ │ └── mdx-page.mdx │ ├── prerender-patterns │ │ ├── astro.config.ts │ │ ├── package.json │ │ ├── source │ │ │ └── env.d.ts │ │ └── src │ │ │ ├── not-pages │ │ │ └── added-by-integration.astro │ │ │ └── pages │ │ │ ├── 404.astro │ │ │ ├── 500.astro │ │ │ ├── endpoint-default.ts │ │ │ ├── endpoint-set-to-prerender.ts │ │ │ ├── endpoint-set-to-render-on-demand.ts │ │ │ ├── page-default.astro │ │ │ ├── page-set-to-prerender.astro │ │ │ ├── page-set-to-render-on-demand.astro │ │ │ └── page-with-script.astro │ ├── scope │ │ ├── astro.config.ts │ │ ├── package.json │ │ └── src │ │ │ └── pages │ │ │ ├── page-a.astro │ │ │ └── page-b.astro │ ├── server-only-modules │ │ ├── astro.config.ts │ │ ├── package.json │ │ └── src │ │ │ ├── pages │ │ │ └── index.astro │ │ │ └── x.server.ts │ └── websocket │ │ ├── package.json │ │ └── src │ │ └── pages │ │ ├── arraybuffer.js │ │ ├── blob.js │ │ └── ws.js ├── global.test.ts ├── node-websocket.test.ts ├── package.json ├── prerender-patterns.test.ts ├── scope.test.ts ├── server-only-modules.test.ts └── utils.ts └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "lilnasy/gratelets" }], 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "postCreateCommand": "pnpm install" 3 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | merge_group: 7 | pull_request: 8 | paths-ignore: 9 | - ".vscode/**" 10 | - "**/*.md" 11 | - ".github/ISSUE_TEMPLATE/**" 12 | 13 | # Automatically cancel older in-progress jobs on the same branch 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request_target' && github.head_ref || github.ref }} 16 | cancel-in-progress: true 17 | 18 | env: 19 | ASTRO_TELEMETRY_DISABLED: true 20 | 21 | jobs: 22 | prelim: 23 | name: "Linux - Node 22" 24 | uses: ./.github/workflows/test.yml 25 | with: 26 | node: 22 27 | os: ubuntu-latest 28 | check: true 29 | Linux: 30 | name: "Linux - Node ${{ matrix.NODE_VERSION }}" 31 | needs: prelim 32 | uses: ./.github/workflows/test.yml 33 | with: 34 | node: ${{ matrix.NODE_VERSION }} 35 | os: ubuntu-latest 36 | strategy: 37 | matrix: 38 | NODE_VERSION: [18, 20, 23] 39 | fail-fast: false 40 | Windows: 41 | name: "Windows - Node 22" 42 | needs: prelim 43 | uses: ./.github/workflows/test.yml 44 | with: 45 | node: 22 46 | os: windows-latest 47 | macOS: 48 | name: "macOS - Node 22" 49 | needs: prelim 50 | uses: ./.github/workflows/test.yml 51 | with: 52 | node: 22 53 | os: macos-14 54 | Chrome: 55 | name: E2E Tests 56 | needs: prelim 57 | runs-on: ubuntu-latest 58 | steps: 59 | - name: Checkout 60 | uses: actions/checkout@v4 61 | 62 | - name: Setup PNPM 63 | uses: pnpm/action-setup@v4 64 | 65 | - name: Setup Node 20 66 | uses: actions/setup-node@v4 67 | with: 68 | node-version: 20 69 | cache: "pnpm" 70 | 71 | - name: Install dependencies 72 | run: pnpm install 73 | 74 | - name: Install Chromium 75 | run: pnpm install:chromium 76 | 77 | - name: Test 78 | run: pnpm run test:e2e --reporter github 79 | -------------------------------------------------------------------------------- /.github/workflows/init-submodules.sh: -------------------------------------------------------------------------------- 1 | git submodule update --init 2 | 3 | cd packages/node-websocket/withastro/adapters 4 | git config user.email "ci@c.i" 5 | git config user.name "CI" 6 | git am ../../*.patch 7 | cd ../../../../ 8 | 9 | cd packages/bun-websocket/NuroDev/astro-bun 10 | git config user.email "ci@c.i" 11 | git config user.name "CI" 12 | git am ../../*.patch 13 | cd ../../../.. 14 | 15 | cd packages/deno-websocket/denoland/deno-astro-adapter 16 | git config user.email "ci@c.i" 17 | git config user.name "CI" 18 | git am ../../*.patch 19 | cd ../../../.. 20 | 21 | cd packages/cloudflare-websocket/withastro/adapters 22 | git config user.email "ci@c.i" 23 | git config user.name "CI" 24 | git am ../../*.patch 25 | cd ../../../.. 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | ci: 10 | uses: ./.github/workflows/ci.yml 11 | changelog: 12 | name: Changelog PR or Release 13 | needs: ci 14 | if: ${{ github.repository_owner == 'lilnasy' }} 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | pull-requests: write 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Setup PNPM 23 | uses: pnpm/action-setup@v4 24 | 25 | - name: Setup Node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 22 29 | cache: "pnpm" 30 | 31 | - name: Install dependencies 32 | run: pnpm install 33 | 34 | - name: Init submodules 35 | run: sh .github/workflows/init-submodules.sh 36 | 37 | - name: Build 38 | run: pnpm --filter @emotion-extract/vite build 39 | 40 | - name: Create Release Pull Request or Publish 41 | id: changesets 42 | uses: changesets/action@v1 43 | with: 44 | version: sh .github/workflows/version.sh 45 | publish: pnpm exec changeset publish 46 | commit: "[ci] release" 47 | title: "[ci] release" 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | node: 7 | description: Node.js version 8 | type: string 9 | default: "20" 10 | os: 11 | description: Operating system 12 | type: string 13 | default: ubuntu-latest 14 | check: 15 | description: Type-check with tsc 16 | type: boolean 17 | default: false 18 | workflow_dispatch: 19 | inputs: 20 | node: 21 | description: "Node.js version" 22 | type: string 23 | default: "20" 24 | os: 25 | description: "Operating system" 26 | default: ubuntu-latest 27 | type: choice 28 | options: 29 | - ubuntu-latest 30 | - ubuntu-24.04 31 | - ubuntu-22.04 32 | - ubuntu-20.04 33 | - macos-latest 34 | - macos-15 35 | - macos-14 36 | - macos-13 37 | - macos-12 38 | - windows-latest 39 | - windows-2022 40 | - windows-2019 41 | check: 42 | description: Type-check with tsc 43 | type: boolean 44 | default: false 45 | env: 46 | ASTRO_TELEMETRY_DISABLED: true 47 | 48 | jobs: 49 | test: 50 | name: "Test" 51 | runs-on: ${{ inputs.os }} 52 | env: 53 | NODE_VERSION: ${{ inputs.node }} 54 | steps: 55 | - name: Checkout 56 | uses: actions/checkout@v4 57 | 58 | - name: Setup PNPM 59 | uses: pnpm/action-setup@v4 60 | 61 | - name: Setup Node ${{ inputs.node }} 62 | uses: actions/setup-node@v4 63 | with: 64 | node-version: ${{ inputs.node }} 65 | cache: "pnpm" 66 | 67 | - name: Setup Bun 68 | if: ${{ inputs.node == 23 }} 69 | uses: oven-sh/setup-bun@v2 70 | with: 71 | bun-version: 1.1.40 72 | 73 | - uses: denoland/setup-deno@v2 74 | with: 75 | deno-version: 2.1.4 76 | 77 | - name: Install dependencies 78 | run: pnpm install 79 | 80 | - name: Init submodules 81 | run: sh .github/workflows/init-submodules.sh 82 | 83 | - name: Build 84 | run: pnpm --filter @emotion-extract/vite build 85 | 86 | - name: Check 87 | if: ${{ inputs.check }} 88 | run: pnpm run check 89 | 90 | - name: Test 91 | run: pnpm run test 92 | -------------------------------------------------------------------------------- /.github/workflows/version.sh: -------------------------------------------------------------------------------- 1 | pnpm exec changeset version 2 | # reset submodules so that they are not included in release pr 3 | git submodule update --init -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | **/dist_* 4 | **/.astro 5 | tests/.wrangler -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/node-websocket/withastro/adapters"] 2 | path = packages/node-websocket/withastro/adapters 3 | url = https://github.com/withastro/adapters 4 | branch = main 5 | shallow = true 6 | active = false 7 | [submodule "packages/bun-websocket/NuroDev/astro-bun"] 8 | path = packages/bun-websocket/NuroDev/astro-bun 9 | url = https://github.com/NuroDev/astro-bun 10 | branch = main 11 | shallow = true 12 | active = false 13 | [submodule "packages/deno-websocket/denoland/deno-astro-adapter"] 14 | path = packages/deno-websocket/denoland/deno-astro-adapter 15 | url = https://github.com/denoland/deno-astro-adapter 16 | branch = main 17 | shallow = true 18 | active = false 19 | [submodule "packages/cloudflare-websocket/withastro/adapters"] 20 | path = packages/cloudflare-websocket/withastro/adapters 21 | url = https://github.com/withastro/adapters 22 | branch = cloudflare-astro-v5 23 | shallow = true 24 | active = false 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A collection of purpose-built plugins for Vite and Astro. 2 | 3 | ## Emotion Extract 4 | _CSS-in-JS with the performance of handwritten stylesheets._ 5 | 6 |
7 | 8 | https://github.com/user-attachments/assets/576b3d42-17bc-40e4-bee9-75d59098ba31 9 | 10 |
11 | 12 |
13 | 14 | [Read more](https://github.com/lilnasy/gratelets/tree/main/packages/emotion-extract). [See it on NPM](https://www.npmjs.com/package/@emotion-extract/vite?activeTab=readme). 15 | 16 |
17 | 18 | ## WebSocket adapters for Astro 19 | _Add realtime features to your server-rendered apps without paying a fortune._ 20 | 21 | [Node](https://github.com/lilnasy/gratelets/tree/main/packages/node-websocket) ([NPM](https://www.npmjs.com/package/astro-node-websocket)) 22 | [Cloudflare](https://github.com/lilnasy/gratelets/tree/main/packages/cloudflare-websocket) ([NPM](https://www.npmjs.com/package/astro-cloudflare-websocket)) 23 | [Bun](https://github.com/lilnasy/gratelets/tree/main/packages/bun-websocket) ([NPM](https://www.npmjs.com/package/astro-bun-websocket)) 24 | [Deno](https://github.com/lilnasy/gratelets/tree/main/packages/deno-websocket) ([NPM](https://www.npmjs.com/package/astro-deno-websocket)) 25 | 26 | ## Typed API for Astro 27 | _Refactor full-stack apps with confidence._ 28 | 29 | [Read more](https://github.com/lilnasy/gratelets/tree/main/packages/typed-api). [Find it on NPM](https://www.npmjs.com/package/astro-typed-api). 30 | 31 | ## Dynamic Imports for Astro 32 | _Personalize headless CMSs without compromising on SEO._ 33 | 34 | [Read more](https://github.com/lilnasy/gratelets/tree/main/packages/dynamic-import). [Find it on NPM](https://www.npmjs.com/package/astro-dynamic-import). 35 | 36 | ## ...and more 37 | - **astro-adds-to-head** - fix a corner case in content collections rendering. [README](https://github.com/lilnasy/gratelets/tree/main/packages/adds-to-head) | [NPM](https://www.npmjs.com/package/astro-adds-to-head) 38 | - **astro-client-interaction** - load and hydrate framework components on first interaction. [README](https://github.com/lilnasy/gratelets/tree/main/packages/client-interaction) | [NPM](https://www.npmjs.com/package/astro-client-interaction) 39 | - **astro-emotion** - CSS-in-Zero-JS using [Emotion](https://emotion.sh/). [README](https://github.com/lilnasy/gratelets/tree/main/packages/emotion) | [NPM](https://www.npmjs.com/package/astro-emotion) 40 | - **astro-global** - use the Astro global directly inside framework components and MDX pages. [README](https://github.com/lilnasy/gratelets/tree/main/packages/global) | [NPM](https://www.npmjs.com/package/astro-global) 41 | - **astro-prerender-patterns** - control rendering modes for all pages and endpoints right from the configuration. [README](https://github.com/lilnasy/gratelets/tree/main/packages/prerender-patterns) | [NPM](https://www.npmjs.com/package/astro-prerender-patterns) 42 | - **astro-scope** - get the hash used by the astro compiler to scope css rules. [README](https://github.com/lilnasy/gratelets/tree/main/packages/scope) | [NPM](https://www.npmjs.com/package/astro-scope) 43 | - **astro-server-only-modules** - prevent certain modules from being imported into client-side code. [README](https://github.com/lilnasy/gratelets/tree/main/packages/server-only-modules) | [NPM](https://www.npmjs.com/package/astro-server-only-modules) 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": "true", 4 | "type": "module", 5 | "scripts": { 6 | "check": "tsc", 7 | "test": "pnpm --filter tests exec vitest --run --api.port 51204 --pool=threads --poolOptions.threads.singleThread --bail 1", 8 | "install:chromium": "pnpm --filter tests-e2e exec playwright install chromium", 9 | "test:e2e": "pnpm --filter tests-e2e exec node --no-warnings --experimental-loader playwright/lib/transform/esmLoader node_modules/playwright/cli.js test --workers=1 --timeout 5000 -x" 10 | }, 11 | "packageManager": "pnpm@10.6.2", 12 | "devDependencies": { 13 | "@changesets/changelog-github": "0.5", 14 | "@changesets/cli": "2", 15 | "astro": "5", 16 | "typescript": "5.7" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/adds-to-head/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # astro-adds-to-head 2 | 3 | ## 1.0.0 4 | 5 | ### Major Changes 6 | 7 | - [#8](https://github.com/lilnasy/gratelets/pull/8) [`3511b44`](https://github.com/lilnasy/gratelets/commit/3511b4472d17989c6f7b96c2a57cbe37d49ce0c0) Thanks [@lilnasy](https://github.com/lilnasy)! - Initial release 8 | -------------------------------------------------------------------------------- /packages/adds-to-head/README.md: -------------------------------------------------------------------------------- 1 | # astro-adds-to-head 🗣️ 2 | 3 | This **[Astro integration][astro-integration]** lets you explicitly mark components that add scripts and styles to the parent page. 4 | 5 | - [Why astro-adds-to-head?](#why-astro-adds-to-head) 6 | - [Installation](#installation) 7 | - [Usage](#usage) 8 | - [Troubleshooting](#troubleshooting) 9 | - [Contributing](#contributing) 10 | - [Changelog](#changelog) 11 | 12 | ## Why astro-adds-to-head? 13 | 14 | Due to Astro's streaming, we only wait until the page's frontmatter has run before generating the `` element. Frontmatter of a component cannot add scripts or stylesheets because by the time it is run, the `` element is already sent to the browser so that it can start loading assets. 15 | 16 | This becomes an issue when a component uses the content collections API. The `render()` function adds styles and scripts relevant to the content entry to the element. Therefore, it must only be used in the routed page's frontmatter, not a component's. Using it elsewhere would lead to missing assets, as reported in this issue: withastro/astro#7761. 17 | 18 | This integration gives you a way to fix this corner case by letting you explicitly mark components that may contribute styles or scripts. When a component is marked, Astro will wait until it is rendered before rendering the `` element. 19 | 20 | ## Installation 21 | 22 | ### Manual Install 23 | 24 | First, install the `astro-adds-to-head` package using your package manager. If you're using npm or aren't sure, run this in the terminal: 25 | 26 | ```sh 27 | npm install astro-adds-to-head 28 | ``` 29 | 30 | Then, apply this integration to your `astro.config.*` file using the `integrations` property: 31 | 32 | ```diff lang="js" "mdx()" 33 | // astro.config.mjs 34 | import { defineConfig } from 'astro/config'; 35 | import mdx from '@astrojs/mdx'; 36 | + import addsToHead from 'astro-adds-to-head'; 37 | 38 | export default defineConfig({ 39 | // ... 40 | + integrations: [addsToHead()], 41 | // ^^^^^^^^^^^^ 42 | }); 43 | ``` 44 | 45 | ## Usage 46 | 47 | Once the integration is installed and added to the configuration file, add `export const addsToHead = true` to the component that calls the `render()` method of a content entry. 48 | 49 | ```astro 50 | --- 51 | const { Content } = await Astro.props.entry.render(); 52 | export const addsToHead = true 53 | --- 54 | 55 | ``` 56 | 57 | 58 | ## Troubleshooting 59 | 60 | For help, check out the `Discussions` tab on the [GitHub repo](https://github.com/lilnasy/gratelets/discussions). 61 | 62 | ## Contributing 63 | 64 | This package is maintained by [lilnasy](https://github.com/lilnasy) independently from Astro. The integration code is located at [packages/adds-to-head/integration.ts](https://github.com/lilnasy/gratelets/blob/main/packages/adds-to-head/integration.ts). You're welcome to contribute by submitting an issue or opening a PR! 65 | 66 | ## Changelog 67 | 68 | See [CHANGELOG.md](https://github.com/lilnasy/gratelets/blob/main/packages/adds-to-head/CHANGELOG.md) for a history of changes to this integration. 69 | 70 | [astro-integration]: https://docs.astro.build/en/guides/integrations-guide/ 71 | -------------------------------------------------------------------------------- /packages/adds-to-head/integration.ts: -------------------------------------------------------------------------------- 1 | import type { AstroIntegration } from "astro" 2 | import url from "node:url" 3 | 4 | interface Options {} 5 | 6 | export default function (_?: Options): AstroIntegration { 7 | return { 8 | name: "adds-to-head", 9 | hooks: { 10 | "astro:config:setup" ({ config, updateConfig, logger }) { 11 | const rootPath = url.fileURLToPath(config.root).replaceAll("\\", "/") 12 | 13 | updateConfig({ 14 | vite: { 15 | plugins: [{ 16 | name: "adds-to-head", 17 | transform(code, id) { 18 | if (id.startsWith(rootPath) === false) return 19 | 20 | const moduleInfo = this.getModuleInfo(id) 21 | if (moduleInfo === undefined || moduleInfo === null) return 22 | 23 | const metadata = moduleInfo?.meta?.astro 24 | if (metadata === undefined) return 25 | 26 | if ( 27 | code.includes("export const addsToHead = true;") || 28 | code.includes("export let addsToHead = true;") 29 | ) { 30 | logger.debug(`${id.slice(rootPath.length)} will now propagate assets.`) 31 | metadata.propagation = "self" 32 | } 33 | } 34 | }] 35 | } 36 | }) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/adds-to-head/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-adds-to-head", 3 | "version": "1.0.0", 4 | "description": "Fix a corner case in content collections rendering.", 5 | "author": "Arsh", 6 | "license": "Public Domain", 7 | "keywords": [ 8 | "withastro", 9 | "astro-component" 10 | ], 11 | "homepage": "https://github.com/lilnasy/gratelets/tree/main/packages/adds-to-head", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/lilnasy/gratelets", 15 | "directory": "packages/adds-to-head" 16 | }, 17 | "files": [ 18 | "integration.ts" 19 | ], 20 | "exports": { 21 | ".": "./integration.ts" 22 | }, 23 | "scripts": { 24 | "test": "pnpm -w test adds-to-head.test.ts" 25 | }, 26 | "type": "module", 27 | "devDependencies": { 28 | "@types/node": "20", 29 | "astro": "5" 30 | } 31 | } -------------------------------------------------------------------------------- /packages/bun-websocket/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # astro-bun-websocket 2 | 3 | ## 1.0.4 4 | 5 | ### Patch Changes 6 | 7 | - [#143](https://github.com/lilnasy/gratelets/pull/143) [`c8af2a6`](https://github.com/lilnasy/gratelets/commit/c8af2a683790240dd48e412f52cf64224a921b64) Thanks [@lilnasy](https://github.com/lilnasy)! - Fixed an issue where binary messages weren't properly converted to `Blob` objects. 8 | 9 | ## 1.0.3 10 | 11 | ### Patch Changes 12 | 13 | - [#136](https://github.com/lilnasy/gratelets/pull/136) [`462bfa9`](https://github.com/lilnasy/gratelets/commit/462bfa9e27447f839d752d13af5cdb77f587dc48) Thanks [@lilnasy](https://github.com/lilnasy)! - Removed an unused dependency from the package's dependency list. 14 | 15 | ## 1.0.2 16 | 17 | ### Patch Changes 18 | 19 | - [#134](https://github.com/lilnasy/gratelets/pull/134) [`1fe8f3a`](https://github.com/lilnasy/gratelets/commit/1fe8f3a6cfb1f6f50ba7305cbd84130dd63d76c1) Thanks [@lilnasy](https://github.com/lilnasy)! - The package has been updated to bring in features and improvements from `@NuroDev/astro-bun@2.0.5`. 20 | 21 | ## 1.0.1 22 | 23 | ### Patch Changes 24 | 25 | - [#114](https://github.com/lilnasy/gratelets/pull/114) [`ab033d4`](https://github.com/lilnasy/gratelets/commit/ab033d4b4e75d5dbd291ff5157d09a2cf3bfe45f) Thanks [@lilnasy](https://github.com/lilnasy)! - Updated the documentation to include a section on authentication. 26 | 27 | - [#114](https://github.com/lilnasy/gratelets/pull/114) [`ab033d4`](https://github.com/lilnasy/gratelets/commit/ab033d4b4e75d5dbd291ff5157d09a2cf3bfe45f) Thanks [@lilnasy](https://github.com/lilnasy)! - Removed an unnecessary dependency from the NPM package. 28 | 29 | ## 1.0.0 30 | 31 | ### Major Changes 32 | 33 | - [#112](https://github.com/lilnasy/gratelets/pull/112) [`18d9e18`](https://github.com/lilnasy/gratelets/commit/18d9e18e13ae5766909b13904db4b94d37cc0083) Thanks [@lilnasy](https://github.com/lilnasy)! - Initial release 34 | -------------------------------------------------------------------------------- /packages/bun-websocket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-bun-websocket", 3 | "type": "module", 4 | "description": "Use WebSockets in your Astro SSR Apps and run it on Bun", 5 | "version": "1.0.4", 6 | "license": "MIT", 7 | "keywords": [ 8 | "withastro", 9 | "astro", 10 | "astro-integration", 11 | "astro-adapter", 12 | "bun", 13 | "websocket" 14 | ], 15 | "homepage": "https://github.com/lilnasy/gratelets/packages/bun-websocket", 16 | "files": [ 17 | "NuroDev/astro-bun/package/src", 18 | "NuroDev/astro-bun/LICENSE" 19 | ], 20 | "exports": { 21 | ".": "./NuroDev/astro-bun/package/src/index.ts", 22 | "./websocket": { 23 | "development": "./NuroDev/astro-bun/package/src/websocket/dev-websocket.ts", 24 | "production": "./NuroDev/astro-bun/package/src/websocket/bun-websocket.ts" 25 | } 26 | }, 27 | "dependencies": { 28 | "@types/ws": "^8.5.13", 29 | "ws": "^8.18.0" 30 | }, 31 | "scripts": { 32 | "clone": "git submodule update --init .", 33 | "create_patches": "cd NuroDev/astro-bun && git format-patch tags/2.0.5 -o ../..", 34 | "load_patches": "git submodule update --init . && cd NuroDev/astro-bun && git am ../../*.patch", 35 | "delete_all_changes_and_unload_patches": "cd NuroDev/astro-bun && git reset --hard tags/2.0.5", 36 | "delete_all_changes_and_reload_patches": "cd NuroDev/astro-bun && git reset --hard tags/2.0.5 && git am ../../*.patch", 37 | "test": "pnpm -w test bun-websocket.test.ts" 38 | } 39 | } -------------------------------------------------------------------------------- /packages/client-interaction/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # astro-client-interaction 2 | 3 | ## 1.2.0 4 | 5 | ### Minor Changes 6 | 7 | - [#106](https://github.com/lilnasy/gratelets/pull/106) [`55d85cc`](https://github.com/lilnasy/gratelets/commit/55d85cc9ad4272636e282cc9ba151c702d2beddf) Thanks [@lilnasy](https://github.com/lilnasy)! - Updates the package fields to allow installation alongside Astro 5. This is a clerical change, and the behavior of the integration itself is unchanged. 8 | 9 | ## 1.1.0 10 | 11 | ### Minor Changes 12 | 13 | - [#83](https://github.com/lilnasy/gratelets/pull/83) [`e19eab0`](https://github.com/lilnasy/gratelets/commit/e19eab0feba92c492cdc89d2a4b15f284d683142) Thanks [@leomp12](https://github.com/leomp12)! - Introduces the "idle" value to the `client:interaction` directive. When set, an interaction will schedule the loading of the component for when the browser is idle, instead of loading it immediately. 14 | 15 | ```astro 16 | --- 17 | import Component from "../components/Counter.jsx" 18 | --- 19 | 20 | ``` 21 | 22 | By default, a component with the `client:interaction` directive could be loaded before or after the ones with the `client:idle` directive, depending on the timing of the first interaction. This feature allows `client:interaction` components to predictably lower loading priority than `client:idle` components. 23 | 24 | ## 1.0.0 25 | 26 | ### Major Changes 27 | 28 | - [#79](https://github.com/lilnasy/gratelets/pull/79) [`2e47043`](https://github.com/lilnasy/gratelets/commit/2e47043982f8695b9f8ace4139b694c502452be2) Thanks [@lilnasy](https://github.com/lilnasy)! - Initial release 29 | -------------------------------------------------------------------------------- /packages/client-interaction/README.md: -------------------------------------------------------------------------------- 1 | # astro-client-interaction 👆 2 | 3 | This **[Astro integration][astro-integration]** lets you load and hydrate framework components on first interaction by introducing the `client:interaction` directive. 4 | 5 | - [Why astro-client-interaction?](#why-astro-client-interaction) 6 | - [Installation](#installation) 7 | - [Usage](#usage) 8 | - [Troubleshooting](#troubleshooting) 9 | - [Contributing](#contributing) 10 | - [Changelog](#changelog) 11 | 12 | ## Why astro-client-interaction? 13 | 14 | This integration is useful when you have several components that may never be interacted with, and are non-essential to your visitors' experience. Delaying their loading and hydration can help improve the performance of your site by reducing the amount of bandwidth usage and main-thread work that needs to be done on the initial page load. 15 | 16 | ## Installation 17 | 18 | ### Manual Install 19 | 20 | First, install the `astro-client-interaction` package using your package manager. If you're using npm or aren't sure, run this in the terminal: 21 | 22 | ```sh 23 | npm install astro-client-interaction 24 | ``` 25 | 26 | Then, apply this integration to your `astro.config.*` file using the `integrations` property: 27 | 28 | ```diff lang="js" "clientIx()" 29 | // astro.config.mjs 30 | import { defineConfig } from 'astro/config'; 31 | + import clienIx from 'astro-client-interaction'; 32 | 33 | export default defineConfig({ 34 | // ... 35 | integrations: [clienIx()], 36 | // ^^^^^^^ 37 | }); 38 | ``` 39 | 40 | ## Usage 41 | 42 | Once the integration is installed and added to the configuration file, you can add the `client:interaction` directive to any component that should only hydrate after the visitor presses a key, clicks or touches any part of the page. 43 | 44 | ```astro 45 | --- 46 | import Component from "../components/Counter.jsx" 47 | --- 48 | 49 | ``` 50 | 51 | Interactions may happen while the page is loading. This means that components with the `client:interaction` directive sometimes get loaded before the ones with the `client:idle` directive. This behavior becomes problematic if you use directives to designate "priorities" to components. To make sure that `client:interaction` components are always treated as lower priority than `client:idle`, you can set the value of `client:interaction` to `"idle"`. 52 | 53 | ```astro 54 | --- 55 | import Component from "../components/Counter.jsx" 56 | --- 57 | 58 | ``` 59 | 60 | When the "idle" value is set, an interaction will schedule the loading of the component for when the browser is idle, instead of loading it immediately on interaction. 61 | 62 | ## Troubleshooting 63 | 64 | For help, check out the `Discussions` tab on the [GitHub repo](https://github.com/lilnasy/gratelets/discussions). 65 | 66 | ## Contributing 67 | 68 | This package is maintained by [lilnasy](https://github.com/lilnasy) independently from Astro. The integration code is located at [packages/client-interaction/integration.ts](https://github.com/lilnasy/gratelets/blob/main/packages/client-interaction/integration.ts). You're welcome to contribute by submitting an issue or opening a PR! 69 | 70 | ## Changelog 71 | 72 | See [CHANGELOG.md](https://github.com/lilnasy/gratelets/blob/main/packages/client-interaction/CHANGELOG.md) for a history of changes to this integration. 73 | 74 | [astro-integration]: https://docs.astro.build/en/guides/integrations-guide/ 75 | -------------------------------------------------------------------------------- /packages/client-interaction/integration.ts: -------------------------------------------------------------------------------- 1 | import { type AstroIntegration } from "astro" 2 | 3 | declare module "astro" { 4 | interface AstroClientDirectives { 5 | /** 6 | * The `client:interaction` directive allows you to delay the hydration of a component until the user interacts with the page. 7 | * 8 | * ```jsx 9 | * 10 | * ``` 11 | * 12 | * Interactions may happen while the page is still loading. 13 | * Setting the value of the directive to "idle" allows to defer loading until the browser is also idle. 14 | * 15 | * ```jsx 16 | * 17 | * ``` 18 | */ 19 | "client:interaction"?: boolean | "idle" 20 | } 21 | } 22 | 23 | interface Options {} 24 | 25 | export default function (_: Partial = {}): AstroIntegration { 26 | return { 27 | name: "astro-client-interaction", 28 | hooks: { 29 | "astro:config:setup" ({ addClientDirective }) { 30 | addClientDirective({ 31 | name: "interaction", 32 | entrypoint: "astro-client-interaction/runtime/directive.ts" 33 | }) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/client-interaction/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-client-interaction", 3 | "version": "1.2.0", 4 | "description": "Load and hydrate framework components on first interaction by adding a `client:interaction` directive.", 5 | "author": "Arsh", 6 | "license": "Public Domain", 7 | "keywords": [ 8 | "withastro", 9 | "astro-component" 10 | ], 11 | "homepage": "https://github.com/lilnasy/gratelets/tree/main/packages/client-interaction", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/lilnasy/gratelets", 15 | "directory": "packages/client-interaction" 16 | }, 17 | "files": [ 18 | "integration.ts", 19 | "runtime" 20 | ], 21 | "exports": { 22 | ".": "./integration.ts", 23 | "./runtime/*": "./runtime/*" 24 | }, 25 | "scripts": { 26 | "test": "pnpm -w test:e2e client-interaction.spec.ts" 27 | }, 28 | "type": "module", 29 | "peerDependencies": { 30 | "astro": "4 || 5" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "20" 34 | } 35 | } -------------------------------------------------------------------------------- /packages/client-interaction/runtime/directive.ts: -------------------------------------------------------------------------------- 1 | const firstInteraction = new Promise(resolve => { 2 | const controller = new AbortController; 3 | for (const event of [ "keydown", "mousedown", "pointerdown", "touchstart" ]) { 4 | document.addEventListener( 5 | event, 6 | () => (resolve(), controller.abort()), 7 | { once: true, passive: true, signal: controller.signal } 8 | ) 9 | } 10 | }) 11 | 12 | export default (async (load, opts) => { 13 | await firstInteraction 14 | const hy = async () => { 15 | const hydrate = await load() 16 | await hydrate() 17 | } 18 | if (opts.value === 'idle') { 19 | if (typeof window.requestIdleCallback === 'function') { 20 | window.requestIdleCallback(hy) 21 | return 22 | } 23 | setTimeout(hy, 100) 24 | return 25 | } 26 | hy() 27 | }) satisfies import('astro').ClientDirective 28 | -------------------------------------------------------------------------------- /packages/cloudflare-websocket/0003-adjust-package.patch: -------------------------------------------------------------------------------- 1 | From 5e64df12e15bd36196f4511483a700f3c295658e Mon Sep 17 00:00:00 2001 2 | From: Arsh <69170106+lilnasy@users.noreply.github.com> 3 | Date: Sun, 22 Dec 2024 01:52:33 +0530 4 | Subject: [PATCH 3/3] adjust package 5 | 6 | --- 7 | packages/cloudflare/src/index.ts | 10 +++++----- 8 | packages/cloudflare/src/utils/image-config.ts | 5 +++-- 9 | 2 files changed, 8 insertions(+), 7 deletions(-) 10 | 11 | diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts 12 | index b6794d6e..eadeabdb 100644 13 | --- a/packages/cloudflare/src/index.ts 14 | +++ b/packages/cloudflare/src/index.ts 15 | @@ -153,7 +153,7 @@ export default function createIntegration(args?: Options): AstroIntegration { 16 | let platformProxy: PlatformProxy; 17 | 18 | return { 19 | - name: '@astrojs/cloudflare', 20 | + name: "astro-cloudflare-websocket", 21 | hooks: { 22 | 'astro:config:setup': ({ 23 | command, 24 | @@ -206,7 +206,7 @@ export default function createIntegration(args?: Options): AstroIntegration { 25 | addWatchFile(new URL('./wrangler.jsonc', config.root)); 26 | } 27 | addMiddleware({ 28 | - entrypoint: '@astrojs/cloudflare/entrypoints/middleware.js', 29 | + entrypoint: new URL("./entrypoints/middleware.ts", import.meta.url), 30 | order: 'pre', 31 | }); 32 | if (command === "dev") { 33 | @@ -222,7 +222,7 @@ export default function createIntegration(args?: Options): AstroIntegration { 34 | 'astro:config:done': ({ setAdapter, config, buildOutput, logger }) => { 35 | if (buildOutput === 'static') { 36 | logger.warn( 37 | - '[@astrojs/cloudflare] This adapter is intended to be used with server rendered pages, which this project does not contain any of. As such, this adapter is unnecessary.' 38 | + 'This adapter is intended to be used with server rendered pages, which this project does not contain any of. As such, this adapter is unnecessary.' 39 | ); 40 | } 41 | 42 | @@ -230,8 +230,8 @@ export default function createIntegration(args?: Options): AstroIntegration { 43 | finalBuildOutput = buildOutput; 44 | 45 | setAdapter({ 46 | - name: '@astrojs/cloudflare', 47 | - serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.js', 48 | + name: "astro-cloudflare-websocket", 49 | + serverEntrypoint: new URL("./entrypoints/server.ts", import.meta.url), 50 | exports: ['default'], 51 | adapterFeatures: { 52 | edgeMiddleware: false, 53 | diff --git a/packages/cloudflare/src/utils/image-config.ts b/packages/cloudflare/src/utils/image-config.ts 54 | index 58e0f76a..6b18b8b9 100644 55 | --- a/packages/cloudflare/src/utils/image-config.ts 56 | +++ b/packages/cloudflare/src/utils/image-config.ts 57 | @@ -1,5 +1,6 @@ 58 | import type { AstroConfig, AstroIntegrationLogger, HookParameters } from 'astro'; 59 | import { passthroughImageService, sharpImageService } from 'astro/config'; 60 | +import { fileURLToPath } from "node:url" 61 | 62 | export function setImageConfig( 63 | service: string, 64 | @@ -17,7 +18,7 @@ export function setImageConfig( 65 | service: 66 | command === 'dev' 67 | ? sharpImageService() 68 | - : { entrypoint: '@astrojs/cloudflare/image-service' }, 69 | + : { entrypoint: fileURLToPath(new URL('../entrypoints/image-service.ts', import.meta.url)) }, 70 | }; 71 | 72 | case 'compile': 73 | @@ -25,7 +26,7 @@ export function setImageConfig( 74 | ...config, 75 | service: sharpImageService(), 76 | endpoint: { 77 | - entrypoint: command === 'dev' ? undefined : '@astrojs/cloudflare/image-endpoint', 78 | + entrypoint: command === 'dev' ? undefined : fileURLToPath(new URL('../entrypoints/image-endpoint.ts', import.meta.url)), 79 | }, 80 | }; 81 | 82 | -- 83 | 2.47.1 84 | 85 | -------------------------------------------------------------------------------- /packages/cloudflare-websocket/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # astro-cloudflare-websocket 2 | 3 | ## 1.0.2 4 | 5 | ### Patch Changes 6 | 7 | - [#136](https://github.com/lilnasy/gratelets/pull/136) [`462bfa9`](https://github.com/lilnasy/gratelets/commit/462bfa9e27447f839d752d13af5cdb77f587dc48) Thanks [@lilnasy](https://github.com/lilnasy)! - Removed an unused dependency from the package's dependency list. 8 | 9 | - [#136](https://github.com/lilnasy/gratelets/pull/136) [`462bfa9`](https://github.com/lilnasy/gratelets/commit/462bfa9e27447f839d752d13af5cdb77f587dc48) Thanks [@lilnasy](https://github.com/lilnasy)! - Fixed an issue where the platform proxy was not available for websocket requests in dev mode. 10 | 11 | ## 1.0.1 12 | 13 | ### Patch Changes 14 | 15 | - [#134](https://github.com/lilnasy/gratelets/pull/134) [`1fe8f3a`](https://github.com/lilnasy/gratelets/commit/1fe8f3a6cfb1f6f50ba7305cbd84130dd63d76c1) Thanks [@lilnasy](https://github.com/lilnasy)! - The package has been updated to bring in features and improvements from `@astrojs/cloudflare@12.2.1`. 16 | 17 | ## 1.0.0 18 | 19 | ### Major Changes 20 | 21 | - [#119](https://github.com/lilnasy/gratelets/pull/119) [`be20c23`](https://github.com/lilnasy/gratelets/commit/be20c23f21eeeab2a45408b326debc3673af4e0a) Thanks [@lilnasy](https://github.com/lilnasy)! - Initial release 22 | -------------------------------------------------------------------------------- /packages/cloudflare-websocket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-cloudflare-websocket", 3 | "type": "module", 4 | "description": "Use WebSockets in your Astro SSR Apps and run it on Cloudflare Workers.", 5 | "version": "1.0.2", 6 | "license": "MIT", 7 | "keywords": [ 8 | "withastro", 9 | "astro", 10 | "astro-integration", 11 | "astro-adapter", 12 | "cloudflare", 13 | "workers", 14 | "websocket" 15 | ], 16 | "homepage": "https://github.com/lilnasy/gratelets/packages/cloudflare-websocket", 17 | "files": [ 18 | "withastro/adapters/packages/cloudflare/src" 19 | ], 20 | "exports": { 21 | ".": "./withastro/adapters/packages/cloudflare/src/index.ts", 22 | "./websocket": { 23 | "development": "./withastro/adapters/packages/cloudflare/src/websocket/dev-websocket.ts", 24 | "production": "./withastro/adapters/packages/cloudflare/src/websocket/cloudflare-websocket.ts" 25 | } 26 | }, 27 | "dependencies": { 28 | "@astrojs/internal-helpers": "0.4.2", 29 | "@astrojs/underscore-redirects": "^0.6.0", 30 | "@cloudflare/workers-types": "^4.20250109.0", 31 | "@types/ws": "^8.5.14", 32 | "estree-walker": "^3.0.3", 33 | "magic-string": "^0.30.17", 34 | "miniflare": "^3.20241230.1", 35 | "tiny-glob": "^0.2.9", 36 | "vite": "^6.0.7", 37 | "wrangler": "^3.101.0", 38 | "ws": "^8.18.0" 39 | }, 40 | "devDependencies": { 41 | "rollup": "^4.30.1" 42 | }, 43 | "scripts": { 44 | "clone": "git submodule update --init .", 45 | "create_patches": "cd withastro/adapters && git format-patch tags/@astrojs/cloudflare@12.2.1 -o ../..", 46 | "load_patches": "git submodule update --init . && cd withastro/adapters && git am ../../*.patch", 47 | "delete_all_changes_and_unload_patches": "cd withastro/adapters && git reset --hard tags/@astrojs/cloudflare@12.2.1", 48 | "delete_all_changes_and_reload_patches": "cd withastro/adapters && git reset --hard tags/@astrojs/cloudflare@12.2.1 && git am ../../*.patch", 49 | "test": "pnpm -w test cloudflare-websocket.test.ts" 50 | } 51 | } -------------------------------------------------------------------------------- /packages/deno-websocket/0003-update-package.patch: -------------------------------------------------------------------------------- 1 | From ab6db3ef1ccfee610f5f6f962238e3617e81d278 Mon Sep 17 00:00:00 2001 2 | From: Arsh <69170106+lilnasy@users.noreply.github.com> 3 | Date: Sun, 16 Mar 2025 21:26:59 -0400 4 | Subject: [PATCH 3/3] update package 5 | 6 | --- 7 | src/index.ts | 8 ++++---- 8 | src/server.ts | 2 +- 9 | 2 files changed, 5 insertions(+), 5 deletions(-) 10 | 11 | diff --git a/src/index.ts b/src/index.ts 12 | index 726b03f..bfad58b 100644 13 | --- a/src/index.ts 14 | +++ b/src/index.ts 15 | @@ -117,15 +117,15 @@ const COMPATIBLE_NODE_MODULES = [ 16 | // We shim deno-specific imports so we can run the code in Node 17 | // to prerender pages. In the final Deno build, this import is 18 | // replaced with the Deno-specific contents listed below. 19 | -const DENO_IMPORTS_SHIM = `@deno/astro-adapter/__deno_imports.ts`; 20 | +const DENO_IMPORTS_SHIM = `astro-deno-websocket/__deno_imports.ts`; 21 | const DENO_IMPORTS = 22 | `export { serveFile } from "jsr:@std/http@${STD_VERSION}/file-server"; 23 | export { fromFileUrl } from "jsr:@std/path@${STD_VERSION}";`; 24 | 25 | export function getAdapter(args?: Options): AstroAdapter { 26 | return { 27 | - name: "@deno/astro-adapter", 28 | - serverEntrypoint: "@deno/astro-adapter/server.ts", 29 | + name: "astro-deno-websocket", 30 | + serverEntrypoint: new URL("./server.ts", import.meta.url), 31 | args: args ?? {}, 32 | exports: ["stop", "handle", "start", "running"], 33 | supportedAstroFeatures: { 34 | @@ -175,7 +175,7 @@ export default function createIntegration(args?: Options): AstroIntegration { 35 | let _vite: any; 36 | let viteDevServer: ViteDevServer; 37 | return { 38 | - name: "@deno/astro-adapter", 39 | + name: "astro-deno-websocket", 40 | hooks: { 41 | "astro:config:setup": ({ addMiddleware, command, updateConfig }) => { 42 | updateConfig({ 43 | diff --git a/src/server.ts b/src/server.ts 44 | index a6d559c..4757fb7 100644 45 | --- a/src/server.ts 46 | +++ b/src/server.ts 47 | @@ -4,7 +4,7 @@ import { App } from "astro/app"; 48 | import type { Options } from "./types"; 49 | 50 | // @ts-expect-error 51 | -import { fromFileUrl, serveFile } from "@deno/astro-adapter/__deno_imports.ts"; 52 | +import { fromFileUrl, serveFile } from "astro-deno-websocket/__deno_imports.ts"; 53 | 54 | // @ts-ignore 55 | let _server: Server | undefined = undefined; 56 | -- 57 | 2.47.0.windows.2 58 | 59 | -------------------------------------------------------------------------------- /packages/deno-websocket/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # astro-deno-websocket 2 | 3 | ## 1.1.0 4 | 5 | ### Minor Changes 6 | 7 | - [#143](https://github.com/lilnasy/gratelets/pull/143) [`c8af2a6`](https://github.com/lilnasy/gratelets/commit/c8af2a683790240dd48e412f52cf64224a921b64) Thanks [@lilnasy](https://github.com/lilnasy)! - Aligned production behavior of binary messages to match dev mode. Now, binary messages are consistently provided as `Blob` objects by default. 8 | 9 | ## 1.0.0 10 | 11 | ### Major Changes 12 | 13 | - [#114](https://github.com/lilnasy/gratelets/pull/114) [`ab033d4`](https://github.com/lilnasy/gratelets/commit/ab033d4b4e75d5dbd291ff5157d09a2cf3bfe45f) Thanks [@lilnasy](https://github.com/lilnasy)! - Initial release. 14 | -------------------------------------------------------------------------------- /packages/deno-websocket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-deno-websocket", 3 | "type": "module", 4 | "description": "Use WebSockets in your Astro SSR Apps and run it on Deno", 5 | "version": "1.1.0", 6 | "license": "MIT", 7 | "keywords": [ 8 | "withastro", 9 | "astro", 10 | "astro-integration", 11 | "astro-adapter", 12 | "deno", 13 | "websocket" 14 | ], 15 | "homepage": "https://github.com/lilnasy/gratelets/packages/deno-websocket", 16 | "files": [ 17 | "denoland/deno-astro-adapter/src", 18 | "denoland/deno-astro-adapter/LICENSE" 19 | ], 20 | "exports": { 21 | ".": "./denoland/deno-astro-adapter/src/index.ts", 22 | "./__deno_imports.ts": "./denoland/deno-astro-adapter/src/__deno_imports.ts", 23 | "./websocket": { 24 | "development": "./denoland/deno-astro-adapter/src/websocket/dev-websocket.ts", 25 | "production": "./denoland/deno-astro-adapter/src/websocket/deno-websocket.ts" 26 | } 27 | }, 28 | "dependencies": { 29 | "@types/lodash": "^4.17.13", 30 | "@types/ws": "^8.5.13", 31 | "esbuild": "^0.19.2", 32 | "lodash": "^4.17.21", 33 | "ws": "^8.18.0" 34 | }, 35 | "scripts": { 36 | "clone": "git submodule update --init .", 37 | "create_patches": "cd denoland/deno-astro-adapter && git format-patch 7789dc0 -o ../..", 38 | "load_patches": "git submodule update --init . && cd denoland/deno-astro-adapter && git am ../../*.patch", 39 | "delete_all_changes_and_unload_patches": "cd denoland/deno-astro-adapter && git reset --hard 7789dc0", 40 | "delete_all_changes_and_reload_patches": "cd denoland/deno-astro-adapter && git reset --hard 7789dc0 && git am ../../*.patch", 41 | "test": "pnpm -w test deno-websocket.test.ts" 42 | } 43 | } -------------------------------------------------------------------------------- /packages/dynamic-import/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # astro-dynamic-import 2 | 3 | ## 2.0.3 4 | 5 | ### Patch Changes 6 | 7 | - [#125](https://github.com/lilnasy/gratelets/pull/125) [`ce0b7b6`](https://github.com/lilnasy/gratelets/commit/ce0b7b654ac8dd46aabbe3debba96b5e5d6b6865) Thanks [@spacedawwwg](https://github.com/spacedawwwg)! - Fixes an issue introduced in v2.0.2 where the types for the "astro:import" would not be added to the project. 8 | 9 | ## 2.0.2 10 | 11 | ### Patch Changes 12 | 13 | - [#126](https://github.com/lilnasy/gratelets/pull/126) [`47f496a`](https://github.com/lilnasy/gratelets/commit/47f496a6bfc42913a25dead817aa52b209d2007b) Thanks [@lilnasy](https://github.com/lilnasy)! - Fixes an issue where the integration could not import a required value (PROPAGATED_ASSET_FLAG) when installed in a monorepo. 14 | 15 | ## 2.0.1 16 | 17 | ### Patch Changes 18 | 19 | - [#122](https://github.com/lilnasy/gratelets/pull/122) [`33ce679`](https://github.com/lilnasy/gratelets/commit/33ce6794df312fc3147cefa625a0c8f968ef1317) Thanks [@lilnasy](https://github.com/lilnasy)! - Fixes an issue where the integration attempted to read a file (env.d.ts) that does not exist in Astro 5.x projects. 20 | 21 | ## 2.0.0 22 | 23 | ### Major Changes 24 | 25 | - [#106](https://github.com/lilnasy/gratelets/pull/106) [`55d85cc`](https://github.com/lilnasy/gratelets/commit/55d85cc9ad4272636e282cc9ba151c702d2beddf) Thanks [@lilnasy](https://github.com/lilnasy)! - Updates the package to support changes in how Astro 5 handles generated types. Changes to `env.d.ts` are no longer performed, and the types are automatically added to your project when you import the integration to the Astro configuration file. 26 | 27 | References to `astro-dynamic-import/client` for types can now safely be removed from your project. 28 | 29 | ## 1.1.2 30 | 31 | ### Patch Changes 32 | 33 | - [#102](https://github.com/lilnasy/gratelets/pull/102) [`0f7f2df`](https://github.com/lilnasy/gratelets/commit/0f7f2dfa23e6f7f97370c09699c77ebb7468ac52) Thanks [@lilnasy](https://github.com/lilnasy)! - The package has been updated to fix an internal type checking error. This release does not include any changes to runtime behavior. 34 | 35 | ## 1.1.1 36 | 37 | ### Patch Changes 38 | 39 | - [#97](https://github.com/lilnasy/gratelets/pull/97) [`53c3047`](https://github.com/lilnasy/gratelets/commit/53c30470b08a356395f36f697863b5ae40635605) Thanks [@evertonadame](https://github.com/evertonadame)! - Fixes an issue where an internally used module (`"astro-dynamic-import:internal"`) would sometimes fail to be resolved by vite. 40 | 41 | ## 1.1.0 42 | 43 | ### Minor Changes 44 | 45 | - [#88](https://github.com/lilnasy/gratelets/pull/88) [`3ec89c4`](https://github.com/lilnasy/gratelets/commit/3ec89c45d43736ed5b7ce13c66ae0d6ce5e26ef5) Thanks [@stevenwoodson](https://github.com/stevenwoodson)! - Dynamic imports are now even more optimized. Multiple uses of the same dynamically imported component will result in only one addition of the necessary scripts and styles. 46 | 47 | ## 1.0.2 48 | 49 | ### Patch Changes 50 | 51 | - [#32](https://github.com/lilnasy/gratelets/pull/32) [`fb2111d`](https://github.com/lilnasy/gratelets/commit/fb2111d8601e8974cd2695a03030ee73093c9e3c) Thanks [@lilnasy](https://github.com/lilnasy)! - Includes previously missing files in the NPM package. 52 | 53 | - [#32](https://github.com/lilnasy/gratelets/pull/32) [`fb2111d`](https://github.com/lilnasy/gratelets/commit/fb2111d8601e8974cd2695a03030ee73093c9e3c) Thanks [@lilnasy](https://github.com/lilnasy)! - Fixes typo where "adds" was written in place of "astro". 54 | 55 | ## 1.0.0 56 | 57 | ### Major Changes 58 | 59 | - [#30](https://github.com/lilnasy/gratelets/pull/30) [`a5245a7`](https://github.com/lilnasy/gratelets/commit/a5245a7c69a18a23be50f5442b2b469805299e7d) Thanks [@lilnasy](https://github.com/lilnasy)! - Initial release 60 | -------------------------------------------------------------------------------- /packages/dynamic-import/README.md: -------------------------------------------------------------------------------- 1 | # astro-dynamic-import 🌊 2 | 3 | This **[Astro integration][astro-integration]** lets you import components dynamically, allowing you to pick and choose which components add their scripts and styles to the page. Now you can let a CMS or DB query decide your components without astro sending JS and CSS for all of them! 4 | 5 | - [Why Import Dynamically](#why-import-dynamically) 6 | - [Installation](#installation) 7 | - [Usage](#usage) 8 | - [Troubleshooting](#troubleshooting) 9 | - [Contributing](#contributing) 10 | - [Changelog](#changelog) 11 | 12 | ## Why Import Dynamically? 13 | 14 | Astro decides which scripts and styles get included on the page based on the import statements alone. If different components are picked based on the request, this heuristic quickly results in too many assets being sent to the browser. Importing dynamically lets you import components based on a condition and only the rendered components will send their associated assets to the browser. 15 | 16 | ## Installation 17 | 18 | ### Manual Install 19 | 20 | First, install the `astro-dynamic-import` package using your package manager. If you're using npm or aren't sure, run this in the terminal: 21 | 22 | ```sh 23 | npm install astro-dynamic-import 24 | ``` 25 | Next, apply this integration to your `astro.config.*` file using the `integrations` property: 26 | 27 | ```diff lang="js" "dynamicImport()" 28 | // astro.config.mjs 29 | import { defineConfig } from 'astro/config'; 30 | + import dynamicImport from 'astro-dynamic-import'; 31 | 32 | export default defineConfig({ 33 | // ... 34 | integrations: [dynamicImport()], 35 | // ^^^^^^^^^^^^^ 36 | }); 37 | ``` 38 | 39 | ## Usage 40 | 41 | Once the integration is installed and added to the configuration file, you can import the default function from the `"astro:import"` namespace. 42 | 43 | ```astro 44 | --- 45 | import dynamic from "astro:import" 46 | const toImport = random() ? "components/Counter.astro" : "components/Ticker.astro" 47 | const Component = await dynamic(toImport) 48 | const initial = 3 49 | --- 50 | 51 | ``` 52 | 53 | The `dynamic` function takes the path to a component relative to your source directory (`"/src"` by default). It returns a promise that resolves to the astro component. 54 | 55 | Note that only `.astro` components can be imported. The components must be located in the `src/components` folder. The imported component must be used in an `.astro` file located within `src/pages` due to reasons explained [here](https://github.com/lilnasy/gratelets/tree/main/packages/adds-to-head#why-astro-adds-to-head). 56 | 57 | ## Troubleshooting 58 | 59 | For help, check out the `Discussions` tab on the [GitHub repo](https://github.com/lilnasy/gratelets/discussions). 60 | 61 | ## Contributing 62 | 63 | This package is maintained by [lilnasy](https://github.com/lilnasy) independently from Astro. The integration code is located at [packages/dynamic-import/integration.ts](https://github.com/lilnasy/gratelets/blob/main/packages/dynamic-import/integration.ts). You're welcome to contribute by opening a PR or submitting an issue! 64 | 65 | ## Changelog 66 | 67 | See [CHANGELOG.md](https://github.com/lilnasy/gratelets/blob/main/packages/dynamic-import/CHANGELOG.md) for a history of changes to this integration. 68 | 69 | [astro-integration]: https://docs.astro.build/en/guides/integrations-guide/ 70 | -------------------------------------------------------------------------------- /packages/dynamic-import/integration.ts: -------------------------------------------------------------------------------- 1 | import url from "node:url" 2 | import path from "node:path" 3 | import type { AstroIntegration } from "astro" 4 | import "./types.d.ts" 5 | 6 | /** 7 | * Not used, the integration does not have any configuration options 8 | */ 9 | interface Options {} 10 | 11 | /** 12 | * Adds the ability to dynamically import components, 13 | * including scripts and styles of only the picked components. 14 | */ 15 | export default function (_?: Options): AstroIntegration { 16 | return { 17 | name: "astro-dynamic-import", 18 | hooks: { 19 | "astro:config:setup" ({ config, updateConfig }) { 20 | const srcDirName = path.relative(url.fileURLToPath(config.root), url.fileURLToPath(config.srcDir)).replaceAll("\\", "/") 21 | updateConfig({ vite: { 22 | optimizeDeps: { 23 | exclude: ["astro-dynamic-import:internal"], 24 | }, 25 | plugins: [{ 26 | name: "astro-dynamic-import/vite", 27 | resolveId(source) { 28 | if (source === "astro:import") { return this.resolve("astro-dynamic-import/runtime/virtual-module.ts") } 29 | if (source === "astro-dynamic-import:internal") return source 30 | }, 31 | load(id) { 32 | if (id === "astro-dynamic-import:internal") { 33 | return `export const srcDirName = ${JSON.stringify(srcDirName)}\n` + 34 | `export const lookupMap = import.meta.glob('/${srcDirName}/components/**/*.astro', { query: { astroPropagatedAssets: true } })\n` 35 | } 36 | } 37 | }]}}) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/dynamic-import/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-dynamic-import", 3 | "version": "2.0.3", 4 | "description": "Let your CMS decide which components to import.", 5 | "author": "Arsh", 6 | "license": "Public Domain", 7 | "keywords": [ 8 | "withastro", 9 | "astro-component" 10 | ], 11 | "homepage": "https://github.com/lilnasy/gratelets/tree/main/packages/dynamic-import", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/lilnasy/gratelets", 15 | "directory": "packages/dynamic-import" 16 | }, 17 | "files": [ 18 | "integration.ts", 19 | "runtime", 20 | "types.d.ts" 21 | ], 22 | "exports": { 23 | ".": "./integration.ts", 24 | "./runtime/*": "./runtime/*" 25 | }, 26 | "scripts": { 27 | "test": "pnpm -w test dynamic-import.test.ts" 28 | }, 29 | "type": "module", 30 | "peerDependencies": { 31 | "astro": "4 || 5" 32 | }, 33 | "devDependencies": { 34 | "@types/node": "20" 35 | } 36 | } -------------------------------------------------------------------------------- /packages/dynamic-import/runtime/virtual-module.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path" 2 | import { 3 | createComponent, 4 | createHeadAndContent, 5 | renderComponent, 6 | renderTemplate, 7 | renderUniqueStylesheet, 8 | renderScriptElement, 9 | unescapeHTML, 10 | type AstroComponentFactory, 11 | type ComponentSlots 12 | } from "astro/runtime/server/index.js" 13 | //@ts-expect-error 14 | import { srcDirName, lookupMap as _lookupMap } from "astro-dynamic-import:internal" 15 | import type { SSRResult } from "astro" 16 | 17 | const lookupMap: Record> = {} 18 | /** 19 | * Everytime we propagate scripts and styles of a component onto a page, 20 | * we add the component to the set of components that have already been added against the page's SSRResult. 21 | * 22 | * We use WeakMap and WeakSet to avoid holding references to the results and components, 23 | * allowing them to be removed from memory once used. 24 | */ 25 | const addedToPageMap = new WeakMap>() 26 | 27 | export default async function(srcRelativeSpecifier: string) { 28 | const absoluteSpecifier = path.posix.join("/", srcDirName, srcRelativeSpecifier) 29 | const component = lookupMap[absoluteSpecifier] 30 | if (component === undefined) throw new DynamicImportError(srcRelativeSpecifier, absoluteSpecifier) 31 | return component 32 | } 33 | 34 | for (const absoluteSpecifier in _lookupMap) { 35 | const importable = _lookupMap[absoluteSpecifier] 36 | Object.assign(lookupMap, { 37 | get [absoluteSpecifier]() { 38 | lookupMap[absoluteSpecifier] = lazyImportToComponent(absoluteSpecifier, importable) 39 | return lookupMap[absoluteSpecifier] 40 | } 41 | }) 42 | } 43 | 44 | async function lazyImportToComponent(absoluteSpecifier: string, importable: any) { 45 | const entry = await importable() 46 | const { collectedStyles, collectedLinks, collectedScripts, getMod } = entry.default 47 | const componentModule = await getMod() 48 | return createComponent({ 49 | factory(result: SSRResult, props: Record, slots: ComponentSlots) { 50 | const component = componentModule.default 51 | 52 | const renderTemplateResult = renderTemplate`${renderComponent( 53 | result, 54 | `dynamically-imported:${component.name}`, 55 | component, 56 | props, 57 | slots 58 | )}` 59 | 60 | // if the component has already been added to the page, we render just the content, skipping duplicate head elements 61 | if (addedToPageMap.get(result)?.has(component)) return createHeadAndContent("", renderTemplateResult) 62 | 63 | // retrieve the set, and if it hasnt been created yet, create it, add it to the map, and then immediately retrieve it 64 | const setOfAddedComponents = addedToPageMap.get(result) ?? addedToPageMap.set(result, new WeakSet).get(result)! 65 | setOfAddedComponents.add(component) 66 | 67 | const styles = collectedStyles.map((style: any) => renderUniqueStylesheet(result, { type: 'inline', content: style })).join('') 68 | const links = collectedLinks.map((link: any) => renderUniqueStylesheet(result, { type: 'external', src: prependForwardSlash(link) })).join('') 69 | const scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join('') 70 | 71 | return createHeadAndContent(unescapeHTML(styles + links + scripts) as any, renderTemplateResult) 72 | }, 73 | moduleId: `dynamically-imported:${absoluteSpecifier}`, 74 | propagation: "self" 75 | }) 76 | } 77 | 78 | class DynamicImportError extends Error { 79 | name = "DynamicImportError" 80 | constructor(unmatchedComponent: string, processedSpecifier: string) { 81 | let message = '' 82 | message += `Could not dynamically import '${unmatchedComponent}'\n` 83 | message += `'${processedSpecifier}' was not one of the following:\n` 84 | message += `${Object.keys(_lookupMap).map((specifier) => ` - ${specifier}`).join("\n")}` 85 | super(message) 86 | } 87 | } 88 | 89 | function prependForwardSlash(path: string) { 90 | return path[0] === "/" ? path : "/" + path; 91 | } 92 | -------------------------------------------------------------------------------- /packages/dynamic-import/types.d.ts: -------------------------------------------------------------------------------- 1 | type AstroComponentFactory = import("astro/runtime/server/index.js").AstroComponentFactory 2 | 3 | declare module "astro:import" { 4 | export default function (specifier: string): Promise 5 | } 6 | -------------------------------------------------------------------------------- /packages/emotion-extract/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @emotion-extract/vite 2 | 3 | ## 1.13.3 4 | 5 | ### Patch Changes 6 | 7 | - [#146](https://github.com/lilnasy/gratelets/pull/146) [`159b4a9`](https://github.com/lilnasy/gratelets/commit/159b4a9e52936472cc75e14a583d1469b800e41a) Thanks [@lilnasy](https://github.com/lilnasy)! - Fixes a packaging mistake that prevented the library from being used. 8 | -------------------------------------------------------------------------------- /packages/emotion-extract/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module "emotion:extract" { 2 | export const css: (template: TemplateStringsArray) => string 3 | export const injectGlobal: (template: TemplateStringsArray) => void 4 | } 5 | -------------------------------------------------------------------------------- /packages/emotion-extract/env.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an empty file to allow "@emotion-extract/vite/env" to be imported. 3 | */ 4 | -------------------------------------------------------------------------------- /packages/emotion-extract/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@emotion-extract/vite", 3 | "version": "1.13.3", 4 | "description": "Use Emotion CSS with the performance of handwritten stylesheets.", 5 | "author": "Arsh", 6 | "license": "Public Domain", 7 | "keywords": [ 8 | "vite", 9 | "emotion", 10 | "css" 11 | ], 12 | "homepage": "https://github.com/lilnasy/gratelets/tree/main/packages/emotion-extract", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/lilnasy/gratelets", 16 | "directory": "packages/emotion-extract" 17 | }, 18 | "files": [ 19 | "dist", 20 | "env.js", 21 | "env.d.ts" 22 | ], 23 | "exports": { 24 | ".": "./dist/vite.js", 25 | "./env": { 26 | "types": "./env.d.ts", 27 | "import": "./env.js" 28 | } 29 | }, 30 | "scripts": { 31 | "test": "pnpm -w test emotion-extract.test.ts", 32 | "build": "tsc" 33 | }, 34 | "type": "module", 35 | "dependencies": { 36 | "@emotion/css": "11", 37 | "acorn-walk": "8", 38 | "magic-string": "0.30" 39 | }, 40 | "devDependencies": { 41 | "@types/node": "20", 42 | "vite": "6" 43 | } 44 | } -------------------------------------------------------------------------------- /packages/emotion-extract/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": [ "dist" ], 4 | "compilerOptions": { 5 | "noEmit": false, 6 | "rewriteRelativeImportExtensions": true, 7 | "declaration": true, 8 | "outDir": "./dist" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/emotion-extract/vite.ts: -------------------------------------------------------------------------------- 1 | import crypto from "node:crypto" 2 | import { simple as walk } from "acorn-walk" 3 | import createEmotion, { type Options as EmotionOptions } from "@emotion/css/create-instance" 4 | import MagicString from "magic-string" 5 | import { visitors, type State } from "./ast-scanner.ts" 6 | import type { ModuleGraph, Plugin, TransformResult } from "vite" 7 | 8 | export interface Options extends EmotionOptions {} 9 | 10 | export default function createVitePlugins(options: Partial = {}): Plugin[] { 11 | 12 | /** 13 | * A cache of the js/ts files that have had their css extracted. 14 | * 15 | * **key**: the source code of the js/ts file 16 | * 17 | * **value**: the generated css code + js with styles replaced with class names 18 | */ 19 | const transformCache = new Map 20 | 21 | /** 22 | * The up-to-date versions of the generated css files. 23 | * 24 | * **key**: the virtual id of the css file (`/emotion_extract_internal_${hash}.css`) 25 | * 26 | * **value**: the generated css code 27 | */ 28 | const stylesheets = new Map 29 | 30 | const { css, cache, flush, injectGlobal } = createEmotion({ key: "css", ...options }) 31 | 32 | let moduleGraph: ModuleGraph | undefined 33 | 34 | return [{ 35 | name: "emotion-extract/vite", 36 | configureServer(server) { 37 | moduleGraph = server.moduleGraph 38 | }, 39 | transform(code, id) { 40 | if (code.includes("emotion:extract") === false) return 41 | const hash = crypto.hash("md5", id, "hex").slice(0, 8) 42 | const cssId = `/emotion_extract_internal_${hash}.css` 43 | const seen = transformCache.get(code) 44 | if (seen) { 45 | const [ stylesheet, transformResult ] = seen 46 | revalidate(moduleGraph, stylesheets, cssId, stylesheet) 47 | return transformResult 48 | } 49 | if (transformCache.size > 100) { 50 | transformCache.delete(transformCache.keys().next().value!) 51 | } 52 | const ast = this.parse(code) 53 | const magicString = new MagicString(code, { filename: id }) 54 | const state: State = { id, css, injectGlobal, magicString } 55 | walk(ast, visitors, undefined, state) 56 | const stylesheet = Object.values(cache.inserted).join('\n') 57 | flush() 58 | revalidate(moduleGraph, stylesheets, cssId, stylesheet) 59 | magicString.prepend(`import ${JSON.stringify(cssId)}\n`) 60 | const transformResult: TransformResult = { 61 | code: magicString.toString(), 62 | map: magicString.generateMap({ hires: true }) 63 | } 64 | transformCache.set(code, [ stylesheet, transformResult ]) 65 | return transformResult 66 | }, 67 | resolveId(rawId) { 68 | const id = rawId.split("?")[0] 69 | if (stylesheets.has(id)) return rawId 70 | // HACK: prevent warnings from vite when it scans a file before letting it be transformed 71 | if (rawId === "emotion:extract") return rawId 72 | }, 73 | load(rawId) { 74 | const id = rawId.split("?")[0] 75 | return stylesheets.get(id) 76 | } 77 | }, { 78 | name: "emotion-extract/vite/server", 79 | enforce: "post", 80 | apply: "serve", 81 | applyToEnvironment(environment) { 82 | return environment.config.consumer === "server" 83 | }, 84 | transform(_, id) { 85 | const css = stylesheets.get(id) 86 | if (css) return `export default ${JSON.stringify(css)}` 87 | } 88 | }] 89 | } 90 | 91 | function revalidate( 92 | moduleGraph: ModuleGraph | undefined, 93 | stylesheets: Map, 94 | cssId: string, 95 | stylesheet: string, 96 | ) { 97 | const existing = stylesheets.get(cssId) 98 | if (existing !== stylesheet) { 99 | stylesheets.set(cssId, stylesheet) 100 | const module = moduleGraph?.getModuleById(cssId) 101 | if (module) { 102 | moduleGraph?.invalidateModule(module) 103 | module.lastHMRTimestamp = Date.now() 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /packages/emotion/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # astro-emotion 2 | 3 | ## 3.0.0 4 | 5 | ### Major Changes 6 | 7 | - [#106](https://github.com/lilnasy/gratelets/pull/106) [`55d85cc`](https://github.com/lilnasy/gratelets/commit/55d85cc9ad4272636e282cc9ba151c702d2beddf) Thanks [@lilnasy](https://github.com/lilnasy)! - Updates the package to support changes in how Astro 5 handles generated types. Changes to `env.d.ts` are no longer performed, and the types are automatically added to your project when you import the integration to the Astro configuration file. 8 | 9 | References to `astro-dynamic-import/client` for types can now safely be removed from your project. 10 | 11 | ### Minor Changes 12 | 13 | - [#106](https://github.com/lilnasy/gratelets/pull/106) [`55d85cc`](https://github.com/lilnasy/gratelets/commit/55d85cc9ad4272636e282cc9ba151c702d2beddf) Thanks [@lilnasy](https://github.com/lilnasy)! - Optimizes the extraction of styles by using a cache. Parsing of the source code of the modules importing `astro-emotiion`, and replacement of styles with generated class names is now skipped if the exact source code was previously processed. This prevents unnecessary work caused by the fact that most files are processed twice, once for SSR and once for the client. 14 | 15 | ## 2.0.0 16 | 17 | ### Major Changes 18 | 19 | - [#104](https://github.com/lilnasy/gratelets/pull/104) [`278199a`](https://github.com/lilnasy/gratelets/commit/278199a8dcc40aa2eb9c8cc0444f9bcfe1a4aaaf) Thanks [@lilnasy](https://github.com/lilnasy)! - **New features**: 20 | 21 | - You can now customize the emotion instance by passing options to the integration! For example, the following option will change the generated class name pattern from `e-xxxxx` to `css-xxxxx`: 22 | 23 | ```ts 24 | defineConfig({ 25 | ... 26 | integrations: [emotion({ key: "css" })] 27 | ... 28 | }) 29 | ``` 30 | 31 | - During development, changes made to css blocks will now reflect immediately on the browser without a full page refresh! 32 | 33 | **Internal changes**: 34 | 35 | - Naming of the generated css files has been updated to use hashes. This prevents noisy 404 requests that appear when the dev server is restarted while a browser tab is open with the preview. 36 | 37 | **Breaking changes**: 38 | 39 | - The hashing uses node's [`crypto.hash()`](https://nodejs.org/api/crypto.html#cryptohashalgorithm-data-outputencoding) function, which is only available starting Node v20.12. Node 18 is no longer supported. Please use either Node v20 or v22. 40 | 41 | ## 1.0.1 42 | 43 | ### Patch Changes 44 | 45 | - [#16](https://github.com/lilnasy/gratelets/pull/16) [`f626698`](https://github.com/lilnasy/gratelets/commit/f62669833917448a9c52546e977aa90a40e694fb) Thanks [@lilnasy](https://github.com/lilnasy)! - Fixes an issue where a required file was not included in the NPM package. 46 | 47 | ## 1.0.0 48 | 49 | ### Major Changes 50 | 51 | - [#13](https://github.com/lilnasy/gratelets/pull/13) [`2c636f4`](https://github.com/lilnasy/gratelets/commit/2c636f4bf10ecc36fa066310bd0a22348ead81b1) Thanks [@lilnasy](https://github.com/lilnasy)! - Initial release 52 | -------------------------------------------------------------------------------- /packages/emotion/README.md: -------------------------------------------------------------------------------- 1 | # astro-emotion 👩‍🎤 2 | 3 | This **[Astro integration][astro-integration]** brings [Emotion's](https://emotion.sh/docs/introduction) CSS rules to every `.astro` file and [framework component](https://docs.astro.build/en/core-concepts/framework-components/) in your project. 4 | 5 | - [Why Emotion](#why-emotion) 6 | - [Installation](#installation) 7 | - [Usage](#usage) 8 | - [Configuration](#configuration) 9 | - [Examples](#examples) 10 | - [Troubleshooting](#troubleshooting) 11 | - [Contributing](#contributing) 12 | - [Changelog](#changelog) 13 | 14 | ## Why Emotion? 15 | 16 | Emotion lets you colocate CSS rules with your JSX instead of having them in a separate file. You might find it easier to write and maintain your styles using vanilla CSS properties! 17 | 18 | `astro-emotion` does not require a runtime to be sent to the browser or your SSR app. Instead, it works by reading your components' source code, and creating stylesheets from it during build-time. This approach is often called "macros" or "runes" in other ecosystems. This integration offers two macros - `css`, and `injectGlobal`. The `css` template tag processes CSS properties and compiles them into a scoped class name, which you can add to your HTML elements. The `injectGlobal` template tag lets you add global styles to the current page. 19 | 20 | Emotion is also a great choice to add styles to React, Preact, or Solid components, which don't support a ` 8 | -------------------------------------------------------------------------------- /tests/fixtures/adds-to-head/src/components/IndirectRenderer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { Content } = await Astro.props.entry.render(); 3 | export const addsToHead = true 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /tests/fixtures/adds-to-head/src/content/blog/promo/launch-week-component.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Launch week!' 3 | description: 'Join us for the exciting launch of SPACE BLOG' 4 | publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' 5 | tags: ['announcement'] 6 | --- 7 | 8 | import WithScripts from '../../../components/HasScript.astro'; 9 | import WithStyles from '../../../components/HasStyle.astro'; 10 | 11 | Join us for the space blog launch! 12 | 13 | - THIS THURSDAY 14 | - Houston, TX 15 | - Dress code: **interstellar casual** ✨ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/fixtures/adds-to-head/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getEntryBySlug } from "astro:content" 3 | import IndirectRenderer from "../components/IndirectRenderer.astro" 4 | 5 | const entry = await getEntryBySlug("blog", "promo/launch-week-component") 6 | --- 7 | 8 | 9 | Launch Week 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/fixtures/dynamic-import/astro.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config" 2 | import dynamicImport from "astro-dynamic-import" 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | integrations: [dynamicImport()] 7 | }) 8 | -------------------------------------------------------------------------------- /tests/fixtures/dynamic-import/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/dynamic-import", 3 | "private": true, 4 | "dependencies": { 5 | "astro": "5", 6 | "astro-dynamic-import": "workspace:*" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/fixtures/dynamic-import/src/components/A.astro: -------------------------------------------------------------------------------- 1 |

Contents of A

2 | 7 | 10 | -------------------------------------------------------------------------------- /tests/fixtures/dynamic-import/src/components/B.astro: -------------------------------------------------------------------------------- 1 |

Contents of B

2 | 7 | 10 | -------------------------------------------------------------------------------- /tests/fixtures/dynamic-import/src/components/in-a-folder/C.astro: -------------------------------------------------------------------------------- 1 |

Contents of C

2 | 7 | 10 | -------------------------------------------------------------------------------- /tests/fixtures/dynamic-import/src/components/needs-props.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | x: number; 4 | } 5 | const { x } = Astro.props 6 | if (!x) throw new Error('x is required') 7 | --- 8 | {Array.from({ length: x }).map((_, i) =>
{i}
)} 9 | -------------------------------------------------------------------------------- /tests/fixtures/dynamic-import/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /tests/fixtures/dynamic-import/src/pages/[page].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { GetStaticPaths } from "astro" 3 | import dynamic from "astro:import" 4 | 5 | interface Props { 6 | component: "A" | "B" 7 | } 8 | 9 | export function getStaticPaths(): ReturnType { 10 | return [ 11 | { params: { page: "A" }, props: { component: "A" } }, 12 | { params: { page: "B" }, props: { component: "B" } }, 13 | { params: { page: "C" }, props: { component: "in-a-folder/C" } }, 14 | { params: { page: "D" }, props: { component: "needs-props" } } 15 | ] 16 | } 17 | 18 | const { component } = Astro.props 19 | const Component = await dynamic(`components/${component}.astro`) 20 | --- 21 | 22 | -------------------------------------------------------------------------------- /tests/fixtures/dynamic-import/src/pages/multiple-instances.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import dynamic from "astro:import" 3 | 4 | const Component = await dynamic(`components/A.astro`) 5 | --- 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/.dockerignore: -------------------------------------------------------------------------------- 1 | .react-router 2 | build 3 | node_modules 4 | README.md -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | 4 | # React Router 5 | /.react-router/ 6 | /build/ 7 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS development-dependencies-env 2 | COPY . /app 3 | WORKDIR /app 4 | RUN npm ci 5 | 6 | FROM node:20-alpine AS production-dependencies-env 7 | COPY ./package.json package-lock.json /app/ 8 | WORKDIR /app 9 | RUN npm ci --omit=dev 10 | 11 | FROM node:20-alpine AS build-env 12 | COPY . /app/ 13 | COPY --from=development-dependencies-env /app/node_modules /app/node_modules 14 | WORKDIR /app 15 | RUN npm run build 16 | 17 | FROM node:20-alpine 18 | COPY ./package.json package-lock.json /app/ 19 | COPY --from=production-dependencies-env /app/node_modules /app/node_modules 20 | COPY --from=build-env /app/build /app/build 21 | WORKDIR /app 22 | CMD ["npm", "run", "start"] -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to React Router! 2 | 3 | A modern, production-ready template for building full-stack React applications using React Router. 4 | 5 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default) 6 | 7 | ## Features 8 | 9 | - 🚀 Server-side rendering 10 | - ⚡️ Hot Module Replacement (HMR) 11 | - 📦 Asset bundling and optimization 12 | - 🔄 Data loading and mutations 13 | - 🔒 TypeScript by default 14 | - 🎉 TailwindCSS for styling 15 | - 📖 [React Router docs](https://reactrouter.com/) 16 | 17 | ## Getting Started 18 | 19 | ### Installation 20 | 21 | Install the dependencies: 22 | 23 | ```bash 24 | npm install 25 | ``` 26 | 27 | ### Development 28 | 29 | Start the development server with HMR: 30 | 31 | ```bash 32 | npm run dev 33 | ``` 34 | 35 | Your application will be available at `http://localhost:5173`. 36 | 37 | ## Building for Production 38 | 39 | Create a production build: 40 | 41 | ```bash 42 | npm run build 43 | ``` 44 | 45 | ## Deployment 46 | 47 | ### Docker Deployment 48 | 49 | To build and run using Docker: 50 | 51 | ```bash 52 | docker build -t my-app . 53 | 54 | # Run the container 55 | docker run -p 3000:3000 my-app 56 | ``` 57 | 58 | The containerized application can be deployed to any platform that supports Docker, including: 59 | 60 | - AWS ECS 61 | - Google Cloud Run 62 | - Azure Container Apps 63 | - Digital Ocean App Platform 64 | - Fly.io 65 | - Railway 66 | 67 | ### DIY Deployment 68 | 69 | If you're familiar with deploying Node applications, the built-in app server is production-ready. 70 | 71 | Make sure to deploy the output of `npm run build` 72 | 73 | ``` 74 | ├── package.json 75 | ├── package-lock.json (or pnpm-lock.yaml, or bun.lockb) 76 | ├── build/ 77 | │ ├── client/ # Static assets 78 | │ └── server/ # Server-side code 79 | ``` 80 | 81 | ## Styling 82 | 83 | This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer. 84 | 85 | --- 86 | 87 | Built with ❤️ using React Router. 88 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/app/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @theme { 4 | --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, 5 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 6 | } 7 | 8 | html, 9 | body { 10 | @apply bg-white dark:bg-gray-950; 11 | 12 | @media (prefers-color-scheme: dark) { 13 | color-scheme: dark; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | isRouteErrorResponse, 3 | Links, 4 | Meta, 5 | Outlet, 6 | Scripts, 7 | ScrollRestoration, 8 | } from "react-router"; 9 | 10 | import type { Route } from "./+types/root"; 11 | import "./app.css"; 12 | 13 | export const links: Route.LinksFunction = () => [ 14 | { rel: "preconnect", href: "https://fonts.googleapis.com" }, 15 | { 16 | rel: "preconnect", 17 | href: "https://fonts.gstatic.com", 18 | crossOrigin: "anonymous", 19 | }, 20 | { 21 | rel: "stylesheet", 22 | href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", 23 | }, 24 | ]; 25 | 26 | export function Layout({ children }: { children: React.ReactNode }) { 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {children} 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | export default function App() { 45 | return ; 46 | } 47 | 48 | export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { 49 | let message = "Oops!"; 50 | let details = "An unexpected error occurred."; 51 | let stack: string | undefined; 52 | 53 | if (isRouteErrorResponse(error)) { 54 | message = error.status === 404 ? "404" : "Error"; 55 | details = 56 | error.status === 404 57 | ? "The requested page could not be found." 58 | : error.statusText || details; 59 | } else if (import.meta.env.DEV && error && error instanceof Error) { 60 | details = error.message; 61 | stack = error.stack; 62 | } 63 | 64 | return ( 65 |
66 |

{message}

67 |

{details}

68 | {stack && ( 69 |
70 |           {stack}
71 |         
72 | )} 73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/app/routes.ts: -------------------------------------------------------------------------------- 1 | import { type RouteConfig, index } from "@react-router/dev/routes"; 2 | 3 | export default [index("routes/home.tsx")] satisfies RouteConfig; 4 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/app/routes/home.tsx: -------------------------------------------------------------------------------- 1 | import type { Route } from "./+types/home"; 2 | import { Welcome } from "../welcome/welcome"; 3 | 4 | export function meta({}: Route.MetaArgs) { 5 | return [ 6 | { title: "New React Router App" }, 7 | { name: "description", content: "Welcome to React Router!" }, 8 | ]; 9 | } 10 | 11 | export default function Home() { 12 | return ; 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/app/welcome/welcome.tsx: -------------------------------------------------------------------------------- 1 | import logoDark from "./logo-dark.svg"; 2 | import logoLight from "./logo-light.svg"; 3 | import { css } from "emotion:extract"; 4 | 5 | export function Welcome() { 6 | return ( 7 |
8 |
9 |
10 |
11 | React Router 16 | React Router 21 |
22 |
23 |
24 | 44 |
45 |
46 |
47 | ); 48 | } 49 | 50 | const resources = [ 51 | { 52 | href: "https://reactrouter.com/docs", 53 | text: "React Router Docs", 54 | icon: ( 55 | 63 | 68 | 69 | ), 70 | }, 71 | { 72 | href: "https://rmx.as/discord", 73 | text: "Join Discord", 74 | icon: ( 75 | 83 | 87 | 88 | ), 89 | }, 90 | ]; 91 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-router-ssr", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "react-router build", 7 | "dev": "react-router dev", 8 | "start": "react-router-serve ./build/server/index.js", 9 | "typecheck": "react-router typegen && tsc" 10 | }, 11 | "dependencies": { 12 | "@react-router/node": "^7.4.0", 13 | "@react-router/serve": "^7.4.0", 14 | "isbot": "^5.1.17", 15 | "react": "^19.0.0", 16 | "react-dom": "^19.0.0", 17 | "react-router": "^7.4.0" 18 | }, 19 | "devDependencies": { 20 | "@emotion-extract/vite": "workspace:*", 21 | "@react-router/dev": "^7.4.0", 22 | "@tailwindcss/vite": "^4.0.0", 23 | "@types/node": "^20", 24 | "@types/react": "^19.0.1", 25 | "@types/react-dom": "^19.0.1", 26 | "react-router-devtools": "^1.1.0", 27 | "tailwindcss": "^4.0.0", 28 | "typescript": "^5.7.2", 29 | "vite": "^6.2.2", 30 | "vite-tsconfig-paths": "^5.1.4" 31 | } 32 | } -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lilnasy/gratelets/cfd5a6ca42b3bf88a19322f8e09e03b172232b97/tests/fixtures/emotion-extract/public/favicon.ico -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/react-router.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "@react-router/dev/config"; 2 | 3 | export default { 4 | // Config options... 5 | // Server-side render by default, to enable SPA mode set this to `false` 6 | ssr: true, 7 | } satisfies Config; 8 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*", 4 | "**/.server/**/*", 5 | "**/.client/**/*", 6 | ".react-router/types/**/*" 7 | ], 8 | "compilerOptions": { 9 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 10 | "types": ["node", "vite/client"], 11 | "target": "ES2022", 12 | "module": "ES2022", 13 | "moduleResolution": "bundler", 14 | "jsx": "react-jsx", 15 | "rootDirs": [".", "./.react-router/types"], 16 | "baseUrl": ".", 17 | "paths": { 18 | "~/*": ["./app/*"] 19 | }, 20 | "esModuleInterop": true, 21 | "verbatimModuleSyntax": true, 22 | "noEmit": true, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "strict": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/fixtures/emotion-extract/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { reactRouter } from "@react-router/dev/vite"; 2 | import tailwindcss from "@tailwindcss/vite"; 3 | import { defineConfig } from "vite"; 4 | import tsconfigPaths from "vite-tsconfig-paths"; 5 | import emotion from "@emotion-extract/vite"; 6 | import "@emotion-extract/vite/env"; 7 | 8 | export default defineConfig({ 9 | plugins: [tailwindcss(), reactRouter(), tsconfigPaths(), emotion()], 10 | }); 11 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/astro.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config" 2 | import emotion from "astro-emotion" 3 | import preact from "@astrojs/preact" 4 | import svelte from "@astrojs/svelte" 5 | import react from "@astrojs/react" 6 | import solid from "@astrojs/solid-js" 7 | 8 | // https://astro.build/config 9 | export default defineConfig({ 10 | integrations: [ 11 | emotion(), 12 | svelte(), 13 | preact({ include: "**/preact.tsx" }), 14 | react({ include: "**/react.tsx" }), 15 | solid({ include: "**/solid.tsx" }) 16 | ] 17 | }) 18 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/emotion", 3 | "private": true, 4 | "dependencies": { 5 | "@astrojs/preact": "4", 6 | "@astrojs/react": "4", 7 | "@astrojs/solid-js": "5", 8 | "@astrojs/svelte": "7", 9 | "@preact/signals": "1", 10 | "@types/react": "18", 11 | "@types/react-dom": "18", 12 | "astro": "5", 13 | "astro-emotion": "workspace:*", 14 | "preact": "10", 15 | "react": "18", 16 | "react-dom": "18", 17 | "solid-js": "1", 18 | "svelte": "5" 19 | } 20 | } -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/components/preact.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource preact */ 2 | import { signal } from "@preact/signals" 3 | import { css, injectGlobal } from "astro:emotion" 4 | 5 | injectGlobal`body { margin: 0 }` 6 | 7 | export default function Counter({ children = [], count = signal(0) }) { 8 | const add = () => count.value++ 9 | const subtract = () => count.value-- 10 | 11 | return ( 12 | <> 13 |
20 | 21 |
{count}
22 | 23 |
24 |
{children}
25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/components/react.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { css, injectGlobal } from "astro:emotion" 3 | 4 | injectGlobal`body { margin: 0 }` 5 | 6 | export default function Counter({ 7 | children = [], 8 | count: initialCount = 0, 9 | }) { 10 | const [count, setCount] = useState(initialCount); 11 | const add = () => setCount(x => x + 1); 12 | const subtract = () => setCount(x => x - 1); 13 | 14 | return ( 15 | <> 16 |
23 | 24 |
{count}
25 | 26 |
27 |
{children}
28 | 29 | ) 30 | } -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/components/solid.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | import { createSignal } from "solid-js" 3 | import { css, injectGlobal } from "astro:emotion" 4 | 5 | injectGlobal`body { margin: 0 }` 6 | 7 | export default function Counter({ children = [], count: initialCount = 0 }) { 8 | const [count, setCount] = createSignal(initialCount); 9 | const add = () => setCount(count() + 1); 10 | const subtract = () => setCount(count() - 1); 11 | 12 | return ( 13 | <> 14 |
21 | 22 |
{count()}
23 | 24 |
25 |
{children}
26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/components/svelte.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
18 | 19 |
{count}
20 | 21 |
22 |
23 | 24 |
25 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/pages/astro.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { css, injectGlobal } from "astro:emotion" 3 | 4 | injectGlobal` 5 | @font-face { 6 | font-family: Hand; 7 | font-style: normal; 8 | font-weight: 400; 9 | src: local('Patrick Hand SC') 10 | }` 11 | --- 12 | 13 | 14 | Title 15 | 16 | 17 |

Test

24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/pages/import-alias.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { css as c, injectGlobal as i } from "astro:emotion" 3 | 4 | i` 5 | @font-face { 6 | font-family: Hand; 7 | font-style: normal; 8 | font-weight: 400; 9 | src: local('Patrick Hand SC') 10 | }` 11 | --- 12 | 13 | 14 | Title 15 | 16 | 17 |

Test

24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/pages/import-namespace.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import * as Emotion from "astro:emotion" 3 | 4 | Emotion.injectGlobal` 5 | @font-face { 6 | font-family: Hand; 7 | font-style: normal; 8 | font-weight: 400; 9 | src: local('Patrick Hand SC') 10 | }` 11 | --- 12 | 13 | 14 | Title 15 | 16 | 17 |

Test

24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/pages/preact.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Component from "../components/preact.tsx" 3 | import { signal } from "@preact/signals" 4 | const count = signal(0) 5 | --- 6 | 7 | 8 | Title 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/pages/react.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Component from "../components/react.tsx" 3 | const count = 0 4 | --- 5 | 6 | 7 | Title 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/pages/solid.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Component from "../components/solid.tsx" 3 | --- 4 | 5 | 6 | Title 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/fixtures/emotion/src/pages/svelte.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Component from "../components/svelte.svelte" 3 | --- 4 | 5 | 6 | Title 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/fixtures/global/astro.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config" 2 | import mdx from "@astrojs/mdx" 3 | import preact from "@astrojs/preact" 4 | import global from "astro-global" 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | integrations: [mdx(), preact(), global()], 9 | }) 10 | -------------------------------------------------------------------------------- /tests/fixtures/global/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/global", 3 | "private": true, 4 | "dependencies": { 5 | "@astrojs/mdx": "4", 6 | "@astrojs/preact": "3", 7 | "astro": "5", 8 | "astro-global": "workspace:*", 9 | "preact": "10" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/fixtures/global/src/components/Counter.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "preact/hooks" 2 | 3 | export default function Counter({ children }) { 4 | const [count, setCount] = useState(0); 5 | const add = () => setCount((i) => i + 1); 6 | const subtract = () => setCount((i) => i - 1); 7 | 8 | return ( 9 | <> 10 |
11 | 12 |
{count}
13 | 14 |
15 |
{children}
16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/global/src/components/ServerRenderOnly.jsx: -------------------------------------------------------------------------------- 1 | import Astro from "astro:global" 2 | 3 | export default function ({ children }) { 4 | return

You are at {Astro.url.pathname}

5 | } 6 | -------------------------------------------------------------------------------- /tests/fixtures/global/src/components/Title.astro: -------------------------------------------------------------------------------- 1 |

2 | 3 | 8 | -------------------------------------------------------------------------------- /tests/fixtures/global/src/pages/[rest].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import PreactComponent from "../components/ServerRenderOnly.jsx" 3 | 4 | export function getStaticPaths() { 5 | return [ 6 | { params: { rest: "x" } }, 7 | { params: { rest: "y" } }, 8 | { params: { rest: "z" } }, 9 | ] 10 | } 11 | --- 12 | 13 | -------------------------------------------------------------------------------- /tests/fixtures/global/src/pages/mdx-page.mdx: -------------------------------------------------------------------------------- 1 | import Counter from '../components/Counter.jsx'; 2 | import Title from '../components/Title.astro'; 3 | import Astro from 'astro:global' 4 | 5 | export const components = { h1: Title }; 6 | 7 | export const authors = [ 8 | { name: 'Jane', email: 'hi@jane.com' }, 9 | { name: 'John', twitter: '@john2002' }, 10 | ]; 11 | export const published = new Date('2022-02-01'); 12 | 13 | ### You are currently at {Astro.url.pathname} 14 | 15 | # Hello world! 16 | 17 | Written by: {new Intl.ListFormat('en').format(authors.map(d => d.name))}. 18 | 19 | Published on: {new Intl.DateTimeFormat('en', {dateStyle: 'long'}).format(published)}. 20 | 21 | This is a **counter**! 22 | 23 | ## Syntax highlighting 24 | 25 | We also support syntax highlighting in MDX out-of-the-box! This example uses our default [Shiki theme](https://github.com/shikijs/shiki). See the [MDX integration docs](https://docs.astro.build/en/guides/integrations-guide/mdx/#syntax-highlighting) for configuration options. 26 | 27 | ```astro 28 | --- 29 | const weSupportAstro = true; 30 | --- 31 | 32 |

Hey, what theme is that? Looks nice!

33 | ``` 34 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/astro.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config" 2 | import prerenderPatterns, { prerender, renderOnDemand } from "astro-prerender-patterns" 3 | import { testAdapter } from "../../utils.ts" 4 | 5 | // https://astro.build/config 6 | export default defineConfig({ 7 | output: "static", 8 | adapter: testAdapter, 9 | /* uncomment to test manually */ 10 | // integrations: [ 11 | // prerenderPatterns((path) => { 12 | // console.log(path) 13 | // if (path === "src/pages/page-default.astro") return renderOnDemand 14 | // }) 15 | // ] 16 | }) 17 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/prerender-patterns", 3 | "private": true, 4 | "dependencies": { 5 | "astro": "5", 6 | "astro-prerender-patterns": "workspace:*" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/source/env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/not-pages/added-by-integration.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello world!

4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/pages/404.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Not Found

4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/pages/500.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Something went wrong

4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/pages/endpoint-default.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from "astro" 2 | 3 | export const GET: APIRoute = ctx => new Response(ctx.request.url) 4 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/pages/endpoint-set-to-prerender.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from "astro" 2 | 3 | export const prerender = true 4 | 5 | export const GET: APIRoute = ctx => new Response(ctx.request.url) 6 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/pages/endpoint-set-to-render-on-demand.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from "astro" 2 | 3 | export const prerender = false 4 | 5 | export const GET: APIRoute = ctx => new Response(ctx.request.url) 6 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/pages/page-default.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello world!

4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/pages/page-set-to-prerender.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export const prerender = true 3 | --- 4 | 5 | 6 |

Hello world!

7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/pages/page-set-to-render-on-demand.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export const prerender = false 3 | --- 4 | 5 | 6 |

Hello world!

7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/fixtures/prerender-patterns/src/pages/page-with-script.astro: -------------------------------------------------------------------------------- 1 | This page is necessary to make sure "astro:build:setup" hooks runs for the client build. Without a client script, there is no client setup. 2 | 5 | -------------------------------------------------------------------------------- /tests/fixtures/scope/astro.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import scope from "astro-scope" 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | integrations: [scope()] 7 | }); 8 | -------------------------------------------------------------------------------- /tests/fixtures/scope/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/scope", 3 | "private": true, 4 | "dependencies": { 5 | "astro": "5", 6 | "astro-scope": "workspace:*" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/fixtures/scope/src/pages/page-a.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import scope from "astro:scope" 3 | --- 4 | 8 | 13 |

{scope}

14 | -------------------------------------------------------------------------------- /tests/fixtures/scope/src/pages/page-b.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import scope from "astro:scope" 3 | --- 4 | 8 | 13 |

{scope}

14 | -------------------------------------------------------------------------------- /tests/fixtures/server-only-modules/astro.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config" 2 | import serverOnlyModules from "astro-server-only-modules" 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | integrations: process.env.INCLUDE === "true" ? [serverOnlyModules()] : [], 7 | }); 8 | -------------------------------------------------------------------------------- /tests/fixtures/server-only-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/server-only-modules", 3 | "private": true, 4 | "dependencies": { 5 | "astro": "5", 6 | "astro-server-only-modules": "workspace:*" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/fixtures/server-only-modules/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Astro 8 | 12 | 13 | 14 |

Astro

15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/fixtures/server-only-modules/src/x.server.ts: -------------------------------------------------------------------------------- 1 | export const X = "X" 2 | -------------------------------------------------------------------------------- /tests/fixtures/websocket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@test/node-ws", 3 | "private": true, 4 | "dependencies": { 5 | "astro": "5", 6 | "astro-node-websocket": "workspace:*" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/fixtures/websocket/src/pages/arraybuffer.js: -------------------------------------------------------------------------------- 1 | export const prerender = false 2 | export { GET } from "./blob.js" 3 | -------------------------------------------------------------------------------- /tests/fixtures/websocket/src/pages/blob.js: -------------------------------------------------------------------------------- 1 | export const prerender = false 2 | 3 | /** @type {import("astro").APIRoute} */ 4 | export function GET(context) { 5 | const { response, socket } = context.locals.upgradeWebSocket() 6 | // this GET function is reused by pages/arraybuffer.js module 7 | // (it also handles the /arraybuffer route) 8 | const { pathname } = context.url 9 | if (pathname === "/arraybuffer") { 10 | socket.binaryType = "arraybuffer" 11 | } 12 | socket.onmessage = async (/** @type {MessageEvent} */ e) => { 13 | if (pathname === "/arraybuffer") { 14 | const text = new TextDecoder().decode(new Uint8Array(e.data)) 15 | const reversed = text.split('').reverse().join('') 16 | socket.send(new TextEncoder().encode(reversed)) 17 | } else if (pathname === "/blob") { 18 | const text = await e.data.text() 19 | const reversed = text.split('').reverse().join('') 20 | socket.send(new Blob([reversed])) 21 | } else { 22 | socket.send("unknown path") 23 | } 24 | } 25 | return response 26 | } 27 | -------------------------------------------------------------------------------- /tests/fixtures/websocket/src/pages/ws.js: -------------------------------------------------------------------------------- 1 | export const prerender = false 2 | 3 | /** @type {import("astro").APIRoute} */ 4 | export function GET(context) { 5 | const { headers } = context.request 6 | if ( 7 | headers.get("upgrade") === "websocket" && 8 | headers.get("sec-websocket-protocol") !== "unsupported-protocol" 9 | ) { 10 | const { response, socket } = context.locals.upgradeWebSocket() 11 | socket.onmessage = (/** @type {MessageEvent} */ e) => { 12 | socket.send([...e.data].reverse().join("")) 13 | } 14 | return response 15 | } 16 | return new Response("Upgrade Required", { 17 | status: 426, 18 | headers: { 19 | "X-Error": "Non Upgrade Request" 20 | } 21 | }) 22 | } -------------------------------------------------------------------------------- /tests/global.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, test, expect } from "vitest" 2 | import { build, type BuildFixture } from "./utils.ts" 3 | 4 | describe("astro-global", () => { 5 | let fixture: BuildFixture 6 | beforeAll(async () => { 7 | fixture = await build("./fixtures/global") 8 | }) 9 | 10 | test("An MDX component can use the Astro global directly", async () => { 11 | const renderedMdx = fixture.readTextFile("mdx-page/index.html") 12 | expect(renderedMdx).to.include(">You are currently at /mdx-page/") 13 | }) 14 | 15 | test("A preact component can use the Astro global directly", async () => { 16 | const x = fixture.readTextFile("x/index.html") 17 | expect(x).to.include("

You are at /x/

") 18 | 19 | const y = fixture.readTextFile("y/index.html") 20 | expect(y).to.include("

You are at /y/

") 21 | 22 | const z = fixture.readTextFile("z/index.html") 23 | expect(z).to.include("

You are at /z/

") 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tests", 3 | "private": true, 4 | 5 | "scripts": { 6 | "test": "vitest --run --api.port 51204 --pool=threads --poolOptions.threads.singleThread --bail 1" 7 | }, 8 | "devDependencies": { 9 | "@astrojs/node": "9", 10 | "astro": "5", 11 | "astro-bun-websocket": "workspace:*", 12 | "astro-cloudflare-websocket": "workspace:*", 13 | "astro-deno-websocket": "workspace:*", 14 | "astro-node-websocket": "workspace:*", 15 | "cheerio": "1.0.0-rc.12", 16 | "typescript": "5.7", 17 | "vitest": "2" 18 | } 19 | } -------------------------------------------------------------------------------- /tests/scope.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, test, expect } from "vitest" 2 | import { build, type BuildFixture } from "./utils.ts" 3 | 4 | const resultRegex = /