├── .changeset
├── README.md
├── changelog-config.cjs
└── config.json
├── .github
├── FUNDING.yml
├── actions
│ └── setup
│ │ └── action.yml
└── workflows
│ ├── ci.yml
│ ├── fix.yml
│ ├── release-dev.yml
│ └── release.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── eslint.config.js
├── examples
├── custom-head
│ ├── README.md
│ ├── astro.config.ts
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── CustomFont.html
│ │ │ ├── CustomHead.astro
│ │ │ ├── Typography.astro
│ │ │ ├── Typography.stories.ts
│ │ │ └── global.css
│ │ └── env.d.ts
│ └── tsconfig.json
├── mixed
│ ├── README.md
│ ├── astro.config.ts
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ └── Card.astro
│ │ ├── env.d.ts
│ │ ├── layouts
│ │ │ └── Layout.astro
│ │ ├── pages
│ │ │ └── index.astro
│ │ ├── stories
│ │ │ ├── PreactCounter.stories.ts
│ │ │ └── PreactCounter.tsx
│ │ └── styles
│ │ │ └── global.css
│ └── tsconfig.json
├── playground
│ ├── .npmrc
│ ├── README.md
│ ├── astro.config.ts
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── astro
│ │ │ │ ├── AstroCounter.astro
│ │ │ │ └── AstroCounter.stories.ts
│ │ │ ├── lit
│ │ │ │ ├── LitCounter.astro
│ │ │ │ ├── LitCounter.stories.ts
│ │ │ │ └── LitCounter.ts
│ │ │ ├── preact
│ │ │ │ ├── PreactCounter.stories.ts
│ │ │ │ └── PreactCounter.tsx
│ │ │ ├── react
│ │ │ │ ├── ReactCounter.stories.ts
│ │ │ │ └── ReactCounter.tsx
│ │ │ ├── solid
│ │ │ │ ├── SolidCounter.stories.ts
│ │ │ │ └── SolidCounter.tsx
│ │ │ ├── svelte
│ │ │ │ ├── SvelteCounter.stories.ts
│ │ │ │ └── SvelteCounter.svelte
│ │ │ └── vue
│ │ │ │ ├── VueCounter.stories.ts
│ │ │ │ └── VueCounter.vue
│ │ ├── env.d.ts
│ │ └── styles
│ │ │ └── global.css
│ └── tsconfig.json
├── tailwindcss
│ ├── README.md
│ ├── astro.config.ts
│ ├── package.json
│ ├── src
│ │ ├── env.d.ts
│ │ ├── stories
│ │ │ ├── PreactCounter.stories.ts
│ │ │ └── PreactCounter.tsx
│ │ └── styles
│ │ │ └── global.css
│ └── tsconfig.json
└── unocss
│ ├── README.md
│ ├── astro.config.ts
│ ├── package.json
│ ├── src
│ ├── env.d.ts
│ └── stories
│ │ ├── PreactCounter.stories.ts
│ │ └── PreactCounter.tsx
│ ├── tsconfig.json
│ └── uno.config.ts
├── package.json
├── packages
├── astrobook
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── lib
│ │ ├── components
│ │ │ └── head.astro
│ │ └── pages
│ │ │ ├── app.astro
│ │ │ └── story.astro
│ ├── package.json
│ ├── src
│ │ ├── client.ts
│ │ └── index.ts
│ ├── tsconfig.build.json
│ └── tsconfig.json
├── core
│ ├── package.json
│ ├── src
│ │ ├── astro-integration.ts
│ │ ├── client.ts
│ │ ├── index.ts
│ │ ├── utils
│ │ │ ├── base.spec.ts
│ │ │ ├── base.ts
│ │ │ ├── get-exports.spec.ts
│ │ │ ├── get-exports.ts
│ │ │ ├── invariant.ts
│ │ │ └── path.ts
│ │ └── virtual-module
│ │ │ ├── get-story-modules.spec.ts
│ │ │ ├── get-story-modules.ts
│ │ │ ├── story-modules.ts
│ │ │ ├── virtual-module-ids.ts
│ │ │ ├── virtual-routes.ts
│ │ │ └── vite-plugin.ts
│ ├── tsconfig.json
│ ├── tsdown.config.ts
│ └── vitest.config.ts
├── types
│ ├── lib
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── types.d.ts
│ │ └── virtual.d.ts
│ ├── package.json
│ └── tsconfig.json
└── ui
│ ├── copy.ts
│ ├── package.json
│ ├── src
│ ├── components
│ │ ├── app.astro
│ │ ├── layout.astro
│ │ ├── layout.css
│ │ ├── sidebar-button-fullscreen.astro
│ │ ├── sidebar-button-theme.astro
│ │ ├── sidebar-button.astro
│ │ ├── sidebar-component-list.astro
│ │ ├── sidebar-title.astro
│ │ ├── sidebar.astro
│ │ └── theme-message.ts
│ ├── env.d.ts
│ ├── index.ts
│ └── style.css
│ ├── tsconfig.json
│ └── uno.config.ts
├── playwright.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── tests
├── example-urls.js
├── mixed
│ └── index.test.ts
├── playground
│ ├── counter.test.ts
│ └── home.test.ts
├── tailwindcss
│ └── index.test.ts
└── unocss
│ └── index.test.ts
├── tsconfig.json
├── turbo.json
└── vitest.workspace.ts
/.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/changelog-config.cjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /** @type import('@changesets/types').GetReleaseLine */
4 | async function getReleaseLine(changeset) {
5 | let returnVal = `- ` + formatCommit(changeset.commit)
6 |
7 | const lines = changeset.summary.split('\n').map((line) => line.trimEnd())
8 |
9 | for (const [index, line] of lines.entries()) {
10 | if (index === 0) {
11 | returnVal += line
12 | } else {
13 | returnVal += `\n ` + line
14 | }
15 | }
16 |
17 | return returnVal + '\n'
18 | }
19 |
20 | /**
21 | * @param {string | null | undefined} commit
22 | */
23 | function formatCommit(commit) {
24 | if (!commit || typeof commit !== 'string' || commit.length < 7) {
25 | return ''
26 | }
27 |
28 | const shortCommit = commit.slice(0, 7)
29 |
30 | return `[\`${shortCommit}\`](https://github.com/ocavue/astrobook/commit/${commit}) `
31 | }
32 |
33 | /** @type import('@changesets/types').GetDependencyReleaseLine */
34 | async function getDependencyReleaseLine() {
35 | return ''
36 | }
37 |
38 | /** @type import('@changesets/types').ChangelogFunctions */
39 | const functions = {
40 | getReleaseLine,
41 | getDependencyReleaseLine,
42 | }
43 |
44 | module.exports = functions
45 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json",
3 | "changelog": "./changelog-config.cjs",
4 | "commit": false,
5 | "fixed": [["@astrobook/*", "astrobook"]],
6 | "linked": [],
7 | "access": "restricted",
8 | "baseBranch": "master",
9 | "updateInternalDependencies": "patch",
10 | "ignore": [
11 | "example-playground",
12 | "example-mixed",
13 | "example-custom-head",
14 | "example-tailwindcss",
15 | "example-unocss"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [ocavue]
2 |
--------------------------------------------------------------------------------
/.github/actions/setup/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup
2 | description: Setup the environment
3 |
4 | inputs:
5 | node-version:
6 | description: The version of node.js
7 | required: false
8 | default: '20'
9 |
10 | runs:
11 | using: composite
12 | steps:
13 | - name: Install pnpm
14 | uses: pnpm/action-setup@v4
15 |
16 | - name: Setup node
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: ${{ inputs.node-version }}
20 | cache: pnpm
21 | registry-url: 'https://registry.npmjs.org'
22 |
23 | - name: Install
24 | run: pnpm install
25 | shell: bash
26 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | pull_request:
9 | branches:
10 | - master
11 |
12 | jobs:
13 | build:
14 | strategy:
15 | fail-fast: false
16 | matrix:
17 | os: [ubuntu-latest, windows-latest, macos-latest]
18 |
19 | runs-on: ${{ matrix.os }}
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 |
24 | - uses: ./.github/actions/setup
25 |
26 | - name: Build
27 | run: pnpm run build
28 |
29 | lint:
30 | runs-on: ubuntu-latest
31 | steps:
32 | - uses: actions/checkout@v4
33 |
34 | - uses: ./.github/actions/setup
35 |
36 | - name: Lint
37 | run: pnpm run lint
38 |
39 | - name: Typecheck
40 | run: pnpm run typecheck
41 |
42 | test-unit:
43 | strategy:
44 | fail-fast: false
45 | matrix:
46 | os: [ubuntu-latest, windows-latest, macos-latest]
47 |
48 | runs-on: ${{ matrix.os }}
49 | steps:
50 | - uses: actions/checkout@v4
51 |
52 | - uses: ./.github/actions/setup
53 |
54 | - name: Test
55 | run: pnpm run test
56 |
57 | test-e2e:
58 | strategy:
59 | fail-fast: false
60 | matrix:
61 | os: [ubuntu-latest, windows-latest, macos-latest]
62 | mode: [dev, preview]
63 |
64 | runs-on: ${{ matrix.os }}
65 | env:
66 | ASTROBOOK_TEST_MODE: ${{ matrix.mode }}
67 |
68 | steps:
69 | - uses: actions/checkout@v4
70 |
71 | - uses: ./.github/actions/setup
72 |
73 | - name: Install Playwright
74 | run: pnpm run test:install
75 |
76 | - name: Build
77 | if: ${{ matrix.mode == 'preview' }}
78 | run: pnpm run build
79 |
80 | - name: Test
81 | run: pnpm run test:e2e
82 |
--------------------------------------------------------------------------------
/.github/workflows/fix.yml:
--------------------------------------------------------------------------------
1 | # https://autofix.ci/setup
2 |
3 | name: autofix.ci
4 |
5 | on:
6 | pull_request:
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | fix:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - uses: ./.github/actions/setup
18 |
19 | - name: Fix
20 | run: pnpm run fix
21 |
22 | - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
23 |
--------------------------------------------------------------------------------
/.github/workflows/release-dev.yml:
--------------------------------------------------------------------------------
1 | name: Release (dev)
2 | on: [pull_request]
3 |
4 | jobs:
5 | release:
6 | runs-on: ubuntu-latest
7 |
8 | steps:
9 | - uses: actions/checkout@v4
10 |
11 | - uses: ./.github/actions/setup
12 |
13 | - run: pnpm run ci:publish-dev
14 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | concurrency: ${{ github.workflow }}-${{ github.ref }}
9 |
10 | jobs:
11 | prepare:
12 | name: Prepare
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - uses: ./.github/actions/setup
18 |
19 | - name: Create Release Pull Request
20 | id: changesets
21 | uses: changesets/action@v1
22 | with:
23 | version: pnpm ci:version
24 | commit: 'chore: version packages'
25 | title: 'chore: version packages'
26 | createGithubReleases: false
27 | env:
28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 |
30 | publish:
31 | name: Publish
32 | runs-on: ubuntu-latest
33 | steps:
34 | - uses: actions/checkout@v4
35 |
36 | - uses: ./.github/actions/setup
37 |
38 | - name: Build packages
39 | run: pnpm build:package
40 |
41 | - name: Publish to NPM
42 | env:
43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
44 | run: pnpm ci:publish
45 |
46 | - name: Get package version
47 | run: |
48 | MAIN_VERSION=$(jq -r ".version" packages/astrobook/package.json)
49 | echo "MAIN_RELEASE_TAG=v${MAIN_VERSION}" >> "$GITHUB_ENV"
50 |
51 | - name: Create GitHub release
52 | uses: ncipollo/release-action@v1
53 | with:
54 | commit: master
55 | tag: '${{ env.MAIN_RELEASE_TAG }}'
56 | body: 'Please refer to [CHANGELOG.md](https://github.com/ocavue/astrobook/blob/${{ env.MAIN_RELEASE_TAG }}/packages/astrobook/CHANGELOG.md) for details.'
57 | token: ${{ secrets.GITHUB_TOKEN }}
58 | continue-on-error: true
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | CHANGELOG.md
2 | !packages/astrobook/CHANGELOG.md
3 | .cache
4 | .DS_Store
5 | .idea
6 | *.log
7 | *.tgz
8 | coverage
9 | dist
10 | lib-cov
11 | logs
12 | node_modules
13 | temp
14 | .astro
15 | .tsup
16 | .turbo
17 | node_modules
18 | test-results
19 | playwright-report
20 | blob-report
21 | **/playwright/.cache
22 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | yarn.lock
2 | pnpm-lock.yaml
3 | package-lock.json
4 | CHANGELOG.md
5 |
6 | .next
7 | .cache
8 | .DS_Store
9 | .idea
10 | *.log
11 | *.tgz
12 | coverage
13 | dist
14 | lib-cov
15 | logs
16 | node_modules
17 | temp
18 | dist-types
19 | *.tsbuildinfo
20 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
5 | "overrides": [
6 | {
7 | "files": "*.astro",
8 | "options": {
9 | "parser": "astro"
10 | }
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "unocss.root": ["packages/ui"]
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 ocavue
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ./packages/astrobook/README.md
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { defineESLintConfig } from '@ocavue/eslint-config'
2 |
3 | export default defineESLintConfig()
4 |
--------------------------------------------------------------------------------
/examples/custom-head/README.md:
--------------------------------------------------------------------------------
1 | An example of using custom `
` tags with Astrobook.
2 |
3 | [](https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/custom-head)
4 |
--------------------------------------------------------------------------------
/examples/custom-head/astro.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'astro/config'
2 | import astrobook from 'astrobook'
3 |
4 | // https://astro.build/config
5 | export default defineConfig({
6 | server: {
7 | port: 4305,
8 | },
9 |
10 | // Enable many frameworks to support all different kinds of components.
11 | integrations: [
12 | astrobook({
13 | directory: 'src/components',
14 | head: './src/components/CustomHead.astro',
15 | title: 'Custom Title',
16 | }),
17 | ],
18 | })
19 |
--------------------------------------------------------------------------------
/examples/custom-head/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-custom-head",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "description": "Astrobook example",
7 | "scripts": {
8 | "dev": "astro dev",
9 | "start": "astro dev",
10 | "build": "astro build",
11 | "preview": "astro preview",
12 | "astro": "astro"
13 | },
14 | "dependencies": {
15 | "astro": "^5.8.1",
16 | "astrobook": "*"
17 | },
18 | "stackblitz": {
19 | "installDependencies": false,
20 | "startCommand": "pnpm install && pnpm run dev"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/custom-head/src/components/CustomFont.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
28 |
--------------------------------------------------------------------------------
/examples/custom-head/src/components/CustomHead.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import './global.css'
3 | import CustomFont from './CustomFont.html'
4 | ---
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/custom-head/src/components/Typography.astro:
--------------------------------------------------------------------------------
1 | ---
2 | interface Props {
3 | fontName: 'freckle-face' | 'lobster' | 'press-start-2p'
4 | }
5 |
6 | const { fontName } = Astro.props
7 | ---
8 |
9 |
10 |
11 |
Lorem ipsum dolor sit amet
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/custom-head/src/components/Typography.stories.ts:
--------------------------------------------------------------------------------
1 | import Typography from './Typography.astro'
2 |
3 | export default {
4 | component: Typography,
5 | }
6 |
7 | export const Lobster = {
8 | args: {
9 | fontName: 'lobster',
10 | },
11 | }
12 |
13 | export const FreckleFace = {
14 | args: {
15 | fontName: 'freckle-face',
16 | },
17 | }
18 |
19 | export const PressStart2P = {
20 | args: {
21 | fontName: 'press-start-2p',
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/examples/custom-head/src/components/global.css:
--------------------------------------------------------------------------------
1 | p {
2 | padding: 1rem;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/custom-head/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | declare module '*.vue' {
5 | import type { DefineComponent } from 'vue'
6 |
7 | const component: DefineComponent
8 | export default component
9 | }
10 |
11 | declare module '*.astro' {
12 | import type { AstroComponentFactory } from 'astro/runtime/server/index.js'
13 |
14 | const content: AstroComponentFactory
15 | export default content
16 | }
17 |
--------------------------------------------------------------------------------
/examples/custom-head/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "module": "esnext",
5 | "lib": ["esnext"],
6 | "jsx": "preserve",
7 | "declaration": true,
8 | "emitDeclarationOnly": true,
9 | "moduleResolution": "Bundler",
10 | "esModuleInterop": true,
11 | "outDir": "${configDir}/node_modules/.cache/tsc",
12 | "strict": true,
13 | "allowJs": true,
14 | "composite": true,
15 | "strictNullChecks": true,
16 | "verbatimModuleSyntax": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "resolveJsonModule": true,
19 | "skipLibCheck": true,
20 | "skipDefaultLibCheck": true
21 | },
22 | "include": ["./src", "./*.js", "./*.mjs", "./*.ts"]
23 | }
24 |
--------------------------------------------------------------------------------
/examples/mixed/README.md:
--------------------------------------------------------------------------------
1 | An example that shows how to add Astrobook into an existing Astro project.
2 |
3 | [](https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/mixed)
4 |
--------------------------------------------------------------------------------
/examples/mixed/astro.config.ts:
--------------------------------------------------------------------------------
1 | import preact from '@astrojs/preact'
2 | import { defineConfig } from 'astro/config'
3 | import astrobook from 'astrobook'
4 |
5 | // https://astro.build/config
6 | export default defineConfig({
7 | server: {
8 | port: 4302,
9 | },
10 |
11 | base: '/base',
12 |
13 | integrations: [
14 | preact(),
15 | astrobook({ directory: 'src/stories', subpath: '/docs/components' }),
16 | ],
17 | })
18 |
--------------------------------------------------------------------------------
/examples/mixed/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-mixed",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "description": "Astrobook example",
7 | "scripts": {
8 | "dev": "astro dev",
9 | "start": "astro dev",
10 | "build": "astro build",
11 | "preview": "astro preview",
12 | "astro": "astro"
13 | },
14 | "dependencies": {
15 | "@astrojs/preact": "^4.1.0",
16 | "astro": "^5.8.1",
17 | "astrobook": "*",
18 | "preact": "^10.26.8"
19 | },
20 | "stackblitz": {
21 | "installDependencies": false,
22 | "startCommand": "pnpm install && pnpm run dev"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/mixed/src/components/Card.astro:
--------------------------------------------------------------------------------
1 | ---
2 | interface Props {
3 | title: string
4 | body: string
5 | href: string
6 | }
7 |
8 | const { href, title, body } = Astro.props
9 | ---
10 |
11 |
12 |
13 |
14 | {title}
15 | →
16 |
17 |
18 | {body}
19 |
20 |
21 |
22 |
62 |
--------------------------------------------------------------------------------
/examples/mixed/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/mixed/src/layouts/Layout.astro:
--------------------------------------------------------------------------------
1 | ---
2 | interface Props {
3 | title: string
4 | }
5 |
6 | const { title } = Astro.props
7 | ---
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {title}
18 |
19 |
20 |
21 |
22 |
23 |
51 |
--------------------------------------------------------------------------------
/examples/mixed/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from '../layouts/Layout.astro'
3 | import Card from '../components/Card.astro'
4 | ---
5 |
6 |
7 |
8 |
36 | Welcome to Astro
37 |
38 | This is an example of an Astro project that includes Astrobook under a
39 | subpath.
40 |
41 |
48 |
49 |
50 |
51 |
112 |
--------------------------------------------------------------------------------
/examples/mixed/src/stories/PreactCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import { PreactCounter } from './PreactCounter'
2 |
3 | export default {
4 | component: PreactCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/mixed/src/stories/PreactCounter.tsx:
--------------------------------------------------------------------------------
1 | /** @jsxImportSource preact */
2 |
3 | import '../styles/global.css'
4 |
5 | import type { ComponentChildren } from 'preact'
6 | import { useState } from 'preact/hooks'
7 |
8 | /** A counter written with Preact */
9 | export function PreactCounter({
10 | step = 1,
11 | children,
12 | }: {
13 | step?: number
14 | children?: ComponentChildren
15 | }) {
16 | const [count, setCount] = useState(0)
17 | const add = () => setCount((i) => i + step)
18 | const subtract = () => setCount((i) => i - step)
19 |
20 | return (
21 | <>
22 |
23 |
24 |
{count}
25 |
26 |
27 | {children}
28 | >
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/examples/mixed/src/styles/global.css:
--------------------------------------------------------------------------------
1 | .counter {
2 | display: grid;
3 | font-size: 2em;
4 | grid-template-columns: repeat(3, minmax(0, 1fr));
5 | margin-top: 2em;
6 | place-items: center;
7 | }
8 |
9 | .counter-message {
10 | text-align: center;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/mixed/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "module": "esnext",
5 | "lib": ["esnext"],
6 | "jsx": "react-jsx",
7 | "declaration": true,
8 | "emitDeclarationOnly": true,
9 | "moduleResolution": "Bundler",
10 | "esModuleInterop": true,
11 | "outDir": "${configDir}/node_modules/.cache/tsc",
12 | "strict": true,
13 | "allowJs": true,
14 | "composite": true,
15 | "strictNullChecks": true,
16 | "verbatimModuleSyntax": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "resolveJsonModule": true,
19 | "skipLibCheck": true,
20 | "skipDefaultLibCheck": true,
21 | "jsxImportSource": "preact"
22 | },
23 | "include": ["./src", "./*.js", "./*.mjs", "./*.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/examples/playground/.npmrc:
--------------------------------------------------------------------------------
1 | # Lit libraries are required to be hoisted due to dependency issues.
2 | public-hoist-pattern[]=*lit*
3 |
--------------------------------------------------------------------------------
/examples/playground/README.md:
--------------------------------------------------------------------------------
1 | An example of using multiple UI rendering frameworks (React, Preact, Vue, Svelte, Solid, Lit, Astro) with Astrobook.
2 |
3 | [](https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/playground)
4 |
--------------------------------------------------------------------------------
/examples/playground/astro.config.ts:
--------------------------------------------------------------------------------
1 | import preact from '@astrojs/preact'
2 | import react from '@astrojs/react'
3 | import solid from '@astrojs/solid-js'
4 | import svelte from '@astrojs/svelte'
5 | import vue from '@astrojs/vue'
6 | import { defineConfig } from 'astro/config'
7 | import astrobook from 'astrobook'
8 |
9 | // https://astro.build/config
10 | export default defineConfig({
11 | server: {
12 | port: 4321,
13 | },
14 |
15 | // Enable many frameworks to support all different kinds of components.
16 | integrations: [
17 | react({ include: ['**/react/*'] }),
18 | preact({ include: ['**/preact/*'] }),
19 | solid({ include: ['**/solid/*'] }),
20 | svelte(),
21 | vue(),
22 | astrobook({ directory: 'src/components' }),
23 | ],
24 | })
25 |
--------------------------------------------------------------------------------
/examples/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-playground",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "description": "Astrobook example",
7 | "scripts": {
8 | "dev": "astro dev",
9 | "start": "astro dev",
10 | "build": "astro build",
11 | "preview": "astro preview",
12 | "astro": "astro"
13 | },
14 | "dependencies": {
15 | "@astrojs/preact": "^4.1.0",
16 | "@astrojs/react": "^4.3.0",
17 | "@astrojs/solid-js": "^5.1.0",
18 | "@astrojs/svelte": "^7.1.0",
19 | "@astrojs/vue": "^5.1.0",
20 | "@types/react": "^19.1.6",
21 | "@types/react-dom": "^19.1.5",
22 | "astro": "^5.8.1",
23 | "astrobook": "*",
24 | "lit": "^3.3.0",
25 | "preact": "^10.26.8",
26 | "react": "^19.1.0",
27 | "react-dom": "^19.1.0",
28 | "solid-js": "^1.9.7",
29 | "svelte": "^5.33.11",
30 | "vue": "^3.5.16"
31 | },
32 | "stackblitz": {
33 | "installDependencies": false,
34 | "startCommand": "pnpm install && pnpm run dev"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/playground/src/components/astro/AstroCounter.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import '../../styles/global.css'
3 |
4 | interface Props {
5 | step?: number
6 | }
7 | const step = Astro.props.step || 1
8 | ---
9 |
10 |
11 |
12 |
13 |
0
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
56 |
--------------------------------------------------------------------------------
/examples/playground/src/components/astro/AstroCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import AstroCounter from './AstroCounter.astro'
2 |
3 | export default {
4 | component: AstroCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/playground/src/components/lit/LitCounter.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import '../../styles/global.css'
3 |
4 | interface Props {
5 | step?: number
6 | }
7 | const step = Astro.props.step || 1
8 | ---
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/playground/src/components/lit/LitCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import LitCounter from './LitCounter.astro'
2 |
3 | export default {
4 | component: LitCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/playground/src/components/lit/LitCounter.ts:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css } from 'lit'
2 |
3 | export class LitCounter extends LitElement {
4 | static properties = {
5 | count: { type: Number, state: true },
6 | step: { type: Number },
7 | }
8 |
9 | declare count: number
10 | declare step: number
11 |
12 | static styles = css`
13 | .counter {
14 | display: grid;
15 | font-size: 2em;
16 | grid-template-columns: repeat(3, minmax(0, 1fr));
17 | margin-top: 2em;
18 | place-items: center;
19 | }
20 |
21 | .counter-message {
22 | text-align: center;
23 | }
24 | `
25 |
26 | constructor() {
27 | super()
28 | this.count = 0
29 | this.step = 1
30 | }
31 |
32 | subtract = () => {
33 | this.count -= this.step
34 | }
35 |
36 | add = () => {
37 | this.count += this.step
38 | }
39 |
40 | render() {
41 | return html`
42 |
43 |
44 |
${this.count}
45 |
46 |
47 |
48 |
49 |
50 | `
51 | }
52 | }
53 |
54 | customElements.define('lit-counter', LitCounter)
55 |
--------------------------------------------------------------------------------
/examples/playground/src/components/preact/PreactCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import { PreactCounter } from './PreactCounter'
2 |
3 | export default {
4 | component: PreactCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/playground/src/components/preact/PreactCounter.tsx:
--------------------------------------------------------------------------------
1 | /** @jsxImportSource preact */
2 |
3 | import '../../styles/global.css'
4 |
5 | import type { ComponentChildren } from 'preact'
6 | import { useState } from 'preact/hooks'
7 |
8 | /** A counter written with Preact */
9 | export function PreactCounter({
10 | step = 1,
11 | children,
12 | }: {
13 | step?: number
14 | children?: ComponentChildren
15 | }) {
16 | const [count, setCount] = useState(0)
17 | const add = () => setCount((i) => i + step)
18 | const subtract = () => setCount((i) => i - step)
19 |
20 | return (
21 | <>
22 |
23 |
24 |
{count}
25 |
26 |
27 | {children}
28 | >
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/examples/playground/src/components/react/ReactCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import { ReactCounter } from './ReactCounter'
2 |
3 | export default {
4 | component: ReactCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/playground/src/components/react/ReactCounter.tsx:
--------------------------------------------------------------------------------
1 | /** @jsxImportSource react */
2 |
3 | import '../../styles/global.css'
4 |
5 | import { useState, type ReactNode } from 'react'
6 |
7 | /** A counter written with React */
8 | export function ReactCounter({
9 | step = 1,
10 | children,
11 | }: {
12 | step?: number
13 | children?: ReactNode
14 | }) {
15 | const [count, setCount] = useState(0)
16 | const add = () => setCount((i) => i + step)
17 | const subtract = () => setCount((i) => i - step)
18 |
19 | return (
20 | <>
21 |
22 |
23 |
{count}
24 |
25 |
26 | {children}
27 | >
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/examples/playground/src/components/solid/SolidCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import SolidCounter from './SolidCounter'
2 |
3 | export default {
4 | component: SolidCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/playground/src/components/solid/SolidCounter.tsx:
--------------------------------------------------------------------------------
1 | /** @jsxImportSource solid-js */
2 |
3 | import '../../styles/global.css'
4 |
5 | import { createSignal, type JSX } from 'solid-js'
6 |
7 | /** A counter written with Solid */
8 | export default function SolidCounter(props: {
9 | step?: number
10 | children?: JSX.Element
11 | }) {
12 | const [count, setCount] = createSignal(0)
13 | const add = () => setCount(count() + (props.step || 1))
14 | const subtract = () => setCount(count() - (props.step || 1))
15 |
16 | return (
17 | <>
18 |
19 |
20 |
{count()}
21 |
22 |
23 | {props.children}
24 | >
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/examples/playground/src/components/svelte/SvelteCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import SvelteCounter from './SvelteCounter.svelte'
2 |
3 | export default {
4 | component: SvelteCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/playground/src/components/svelte/SvelteCounter.svelte:
--------------------------------------------------------------------------------
1 |
4 |
21 |
22 |
23 |
24 |
{$count}
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/playground/src/components/vue/VueCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import VueCounter from './VueCounter.vue'
2 |
3 | export default {
4 | component: VueCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/playground/src/components/vue/VueCounter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ count }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
38 |
--------------------------------------------------------------------------------
/examples/playground/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | declare module '*.vue' {
5 | import type { DefineComponent } from 'vue'
6 |
7 | const component: DefineComponent
8 | export default component
9 | }
10 |
11 | declare module '*.astro' {
12 | import type { AstroComponentFactory } from 'astro/runtime/server/index.js'
13 |
14 | const content: AstroComponentFactory
15 | export default content
16 | }
17 |
--------------------------------------------------------------------------------
/examples/playground/src/styles/global.css:
--------------------------------------------------------------------------------
1 | .counter {
2 | display: grid;
3 | font-size: 2em;
4 | grid-template-columns: repeat(3, minmax(0, 1fr));
5 | margin-top: 2em;
6 | place-items: center;
7 | }
8 |
9 | .counter-message {
10 | text-align: center;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "module": "esnext",
5 | "lib": ["esnext"],
6 | "jsx": "preserve",
7 | "declaration": true,
8 | "emitDeclarationOnly": true,
9 | "moduleResolution": "Bundler",
10 | "esModuleInterop": true,
11 | "outDir": "${configDir}/node_modules/.cache/tsc",
12 | "strict": true,
13 | "allowJs": true,
14 | "composite": true,
15 | "strictNullChecks": true,
16 | "verbatimModuleSyntax": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "resolveJsonModule": true,
19 | "skipLibCheck": true,
20 | "skipDefaultLibCheck": true
21 | },
22 | "include": ["./src", "./*.js", "./*.mjs", "./*.ts"]
23 | }
24 |
--------------------------------------------------------------------------------
/examples/tailwindcss/README.md:
--------------------------------------------------------------------------------
1 | An example that uses Astrobook and Tailwind CSS.
2 |
3 | [](https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/tailwindcss)
4 |
--------------------------------------------------------------------------------
/examples/tailwindcss/astro.config.ts:
--------------------------------------------------------------------------------
1 | import preact from '@astrojs/preact'
2 | import tailwindcss from '@tailwindcss/vite'
3 | import { defineConfig } from 'astro/config'
4 | import astrobook from 'astrobook'
5 |
6 | // https://astro.build/config
7 | export default defineConfig({
8 | server: {
9 | port: 4304,
10 | },
11 |
12 | vite: {
13 | plugins: [tailwindcss()],
14 | },
15 |
16 | integrations: [
17 | preact(),
18 | astrobook({
19 | css: ['./src/styles/global.css'],
20 | }),
21 | ],
22 | })
23 |
--------------------------------------------------------------------------------
/examples/tailwindcss/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-tailwindcss",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "description": "Astrobook example",
7 | "scripts": {
8 | "dev": "astro dev",
9 | "start": "astro dev",
10 | "build": "astro build",
11 | "preview": "astro preview",
12 | "astro": "astro"
13 | },
14 | "dependencies": {
15 | "@astrojs/preact": "^4.1.0",
16 | "@tailwindcss/vite": "^4.1.8",
17 | "astro": "^5.8.1",
18 | "astrobook": "*",
19 | "preact": "^10.26.8",
20 | "tailwindcss": "^4.1.8"
21 | },
22 | "stackblitz": {
23 | "installDependencies": false,
24 | "startCommand": "pnpm install && pnpm run dev"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/tailwindcss/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/tailwindcss/src/stories/PreactCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import { PreactCounter } from './PreactCounter'
2 |
3 | export default {
4 | component: PreactCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/tailwindcss/src/stories/PreactCounter.tsx:
--------------------------------------------------------------------------------
1 | /** @jsxImportSource preact */
2 |
3 | import type { ComponentChildren } from 'preact'
4 | import { useState } from 'preact/hooks'
5 |
6 | /** A counter written with Preact */
7 | export function PreactCounter({
8 | step = 1,
9 | children,
10 | }: {
11 | step?: number
12 | children?: ComponentChildren
13 | }) {
14 | const [count, setCount] = useState(0)
15 | const add = () => setCount((i) => i + step)
16 | const subtract = () => setCount((i) => i - step)
17 |
18 | return (
19 | <>
20 |
21 |
27 |
{count}
28 |
34 |
35 | {children}
36 | >
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/examples/tailwindcss/src/styles/global.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss';
2 |
--------------------------------------------------------------------------------
/examples/tailwindcss/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "module": "esnext",
5 | "lib": ["esnext"],
6 | "jsx": "react-jsx",
7 | "declaration": true,
8 | "emitDeclarationOnly": true,
9 | "moduleResolution": "Bundler",
10 | "esModuleInterop": true,
11 | "outDir": "${configDir}/node_modules/.cache/tsc",
12 | "strict": true,
13 | "allowJs": true,
14 | "composite": true,
15 | "strictNullChecks": true,
16 | "verbatimModuleSyntax": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "resolveJsonModule": true,
19 | "skipLibCheck": true,
20 | "skipDefaultLibCheck": true,
21 | "jsxImportSource": "preact"
22 | },
23 | "include": ["./src", "./*.js", "./*.mjs", "./*.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/examples/unocss/README.md:
--------------------------------------------------------------------------------
1 | An example that uses Astrobook and UnoCSS.
2 |
3 | [](https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/unocss)
4 |
--------------------------------------------------------------------------------
/examples/unocss/astro.config.ts:
--------------------------------------------------------------------------------
1 | import preact from '@astrojs/preact'
2 | import { defineConfig } from 'astro/config'
3 | import astrobook from 'astrobook'
4 | import unocss from 'unocss/astro'
5 |
6 | // https://astro.build/config
7 | export default defineConfig({
8 | server: {
9 | port: 4303,
10 | },
11 |
12 | integrations: [preact(), astrobook({}), unocss({ injectReset: true })],
13 | })
14 |
--------------------------------------------------------------------------------
/examples/unocss/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-unocss",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "description": "Astrobook example",
7 | "scripts": {
8 | "dev": "astro dev",
9 | "start": "astro dev",
10 | "build": "astro build",
11 | "preview": "astro preview",
12 | "astro": "astro"
13 | },
14 | "dependencies": {
15 | "@astrojs/preact": "^4.1.0",
16 | "@unocss/reset": "^66.1.2",
17 | "astro": "^5.8.1",
18 | "astrobook": "*",
19 | "preact": "^10.26.8",
20 | "unocss": "^66.1.2"
21 | },
22 | "stackblitz": {
23 | "installDependencies": false,
24 | "startCommand": "pnpm install && pnpm run dev"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/unocss/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/unocss/src/stories/PreactCounter.stories.ts:
--------------------------------------------------------------------------------
1 | import { PreactCounter } from './PreactCounter'
2 |
3 | export default {
4 | component: PreactCounter,
5 | }
6 |
7 | export const Default = {
8 | args: {},
9 | }
10 |
11 | export const LargeStep = {
12 | args: {
13 | step: 5,
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/unocss/src/stories/PreactCounter.tsx:
--------------------------------------------------------------------------------
1 | /** @jsxImportSource preact */
2 |
3 | import type { ComponentChildren } from 'preact'
4 | import { useState } from 'preact/hooks'
5 |
6 | /** A counter written with Preact */
7 | export function PreactCounter({
8 | step = 1,
9 | children,
10 | }: {
11 | step?: number
12 | children?: ComponentChildren
13 | }) {
14 | const [count, setCount] = useState(0)
15 | const add = () => setCount((i) => i + step)
16 | const subtract = () => setCount((i) => i - step)
17 |
18 | return (
19 | <>
20 |
21 |
27 |
{count}
28 |
34 |
35 | {children}
36 | >
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/examples/unocss/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "module": "esnext",
5 | "lib": ["esnext"],
6 | "jsx": "react-jsx",
7 | "declaration": true,
8 | "emitDeclarationOnly": true,
9 | "moduleResolution": "Bundler",
10 | "esModuleInterop": true,
11 | "outDir": "${configDir}/node_modules/.cache/tsc",
12 | "strict": true,
13 | "allowJs": true,
14 | "composite": true,
15 | "strictNullChecks": true,
16 | "verbatimModuleSyntax": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "resolveJsonModule": true,
19 | "skipLibCheck": true,
20 | "skipDefaultLibCheck": true,
21 | "jsxImportSource": "preact"
22 | },
23 | "include": ["./src", "./*.js", "./*.mjs", "./*.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/examples/unocss/uno.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'unocss'
2 | import { presetUno } from 'unocss/preset-uno'
3 |
4 | export default defineConfig({
5 | presets: [presetUno()],
6 | })
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "astrobook-monorepo",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "packageManager": "pnpm@10.11.0",
7 | "description": "Astrobook monorepo",
8 | "license": "MIT",
9 | "scripts": {
10 | "dev": "turbo --filter example-playground dev",
11 | "postinstall": "pnpm --filter=./packages/ui build",
12 | "build": "turbo run build",
13 | "build:package": "turbo build --filter='./packages/*' --concurrency=1",
14 | "lint": "eslint .",
15 | "fix": "manypkg fix && eslint --fix . && prettier --write .",
16 | "prepublishOnly": "pnpm run build",
17 | "test": "vitest",
18 | "test:install": "playwright install --with-deps",
19 | "test:e2e": "playwright test",
20 | "typecheck": "tsc --build .",
21 | "change": "changeset",
22 | "ci:version": "changeset version && pnpm install --no-frozen-lockfile",
23 | "ci:publish": "pnpm run build:package && pnpm publish --access public -r --no-git-checks --tag latest",
24 | "ci:publish-dev": "pnpm run build:package && pkg-pr-new publish './packages/*' --template ./examples/playground --pnpm"
25 | },
26 | "devDependencies": {
27 | "@changesets/cli": "^2.29.4",
28 | "@changesets/types": "^6.1.0",
29 | "@manypkg/cli": "^0.24.0",
30 | "@ocavue/eslint-config": "^3.1.0",
31 | "@playwright/test": "^1.52.0",
32 | "@types/node": "^20.17.5",
33 | "eslint": "^9.27.0",
34 | "pkg-pr-new": "^0.0.51",
35 | "playwright": "^1.52.0",
36 | "prettier": "^3.5.3",
37 | "prettier-plugin-astro": "^0.14.1",
38 | "prettier-plugin-tailwindcss": "^0.6.12",
39 | "turbo": "^2.5.4",
40 | "typescript": "^5.8.3",
41 | "vite": "^6.3.5",
42 | "vitest": "^3.1.4"
43 | },
44 | "renovate": {
45 | "extends": [
46 | "github>ocavue/config-renovate"
47 | ]
48 | },
49 | "pnpm": {
50 | "overrides": {
51 | "astrobook@*": "workspace:*"
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/packages/astrobook/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # astrobook
2 |
3 | ## 0.8.1
4 |
5 | ### Patch Changes
6 |
7 | - [`25b2d47`](https://github.com/ocavue/astrobook/commit/25b2d47dd438aa2af71f1a64b3615620809ae0d4) Update dependencies.
8 |
9 | ## 0.8.0
10 |
11 | ### Minor Changes
12 |
13 | - [`9c445c8`](https://github.com/ocavue/astrobook/commit/9c445c80f696f565355b71628bcbb1e0c6353c26) Add a new `css` option to adding custom styles to your Astrobook project. You
14 | can pass an array of CSS file paths to be imported into your Astrobook project.
15 | It's easier than adding a custom `head` component, especially when you have no
16 | experience with Astro components.
17 |
18 | ## 0.7.1
19 |
20 | ### Patch Changes
21 |
22 | - [`30729fb`](https://github.com/ocavue/astrobook/commit/30729fb316ba42ee22e2167e9226a3b812a5ad6b) Fix an issue where the theme toggle button was not working in some edge cases.
23 |
24 | ## 0.7.0
25 |
26 | ### Minor Changes
27 |
28 | - [`aebaa00`](https://github.com/ocavue/astrobook/commit/aebaa0090f9d8f73f1ed374eaa5cfd3e451641a0) Add a new `head` option. This allows you to configure the global styles and fonts. Please check the README.md for more information.
29 |
30 | Add a new `title` option to set the title for your Astrobook site.
31 |
32 | ## 0.6.0
33 |
34 | ### Minor Changes
35 |
36 | - [`74ee6e2`](https://github.com/ocavue/astrobook/commit/74ee6e2690146b6e39ddfa12a13d5115c4387fb9) Support Astro v5.
37 |
38 | ## 0.5.1
39 |
40 | ### Patch Changes
41 |
42 | - [`09c2d10`](https://github.com/ocavue/astrobook/commit/09c2d1029b94c2558fe9210b16059db7da7211cc) Group stories under the same directory in the sidebar.
43 |
44 | ## 0.5.0
45 |
46 | ### Minor Changes
47 |
48 | - [`4155bdf`](https://github.com/ocavue/astrobook/commit/4155bdf838ee0c1382407cefbc546c5250aab13f) Add a new `subpath` option so that Astrobook can be added to an existing Astro project.
49 |
50 | ## 0.4.4
51 |
52 | ### Patch Changes
53 |
54 | - [`b15252f`](https://github.com/ocavue/astrobook/commit/b15252fecd4965ae2a3f0f6fe0dea20ae346c58d) Fix an issue where the theme toggle button doesn't work.
55 |
56 | ## 0.4.3
57 |
58 | ### Patch Changes
59 |
60 | - [`dd343fb`](https://github.com/ocavue/astrobook/commit/dd343fba02ec12026192812f6c25940e9c360692) Update dependencies.
61 |
62 | ## 0.4.2
63 |
64 | ### Patch Changes
65 |
66 | - [`c1e8c88`](https://github.com/ocavue/astrobook/commit/c1e8c88671e2472c227495abaa7633ae082fea7f) Fix an issue where the theme toggle button was not focusable.
67 |
68 | ## 0.4.1
69 |
70 | ### Patch Changes
71 |
72 | - [`faa79f8`](https://github.com/ocavue/astrobook/commit/faa79f821ff63ee433f28aecd5fa261358d44c5a) Always show the theme button when scrolling down component list.
73 |
74 | ## 0.4.0
75 |
76 | ### Minor Changes
77 |
78 | - [`ea040d1`](https://github.com/ocavue/astrobook/commit/ea040d14585193f24ec50b89073bc18dc1837658) Add a button to toggle the color theme.
79 |
80 | ## 0.3.3
81 |
82 | ### Patch Changes
83 |
84 | - [`3e65d77`](https://github.com/ocavue/astrobook/commit/3e65d77a447ccffb9979d320ce97531a510807e9) Fix issues when running on Windows.
85 |
86 | ## 0.3.2
87 |
88 | ### Patch Changes
89 |
90 | - [`b55c9ca`](https://github.com/ocavue/astrobook/commit/b55c9caf38b5eb57572bc088f074c3d17d714b2e) Remove favicon.
91 |
92 | ## 0.3.1
93 |
94 | ### Patch Changes
95 |
96 | - [`8608ead`](https://github.com/ocavue/astrobook/commit/8608eadfe132d1470c2b592a7428ef9997de8c22) Refactor virtual routes to isolate each story in its own virtual component. This improves the performance and fixes style and script conflicts between stories.
97 |
98 | ## 0.3.0
99 |
100 | ### Minor Changes
101 |
102 | - [`b135226`](https://github.com/ocavue/astrobook/commit/b13522691dde443facf567c889ecf512dfb18ed4) Make the URL prettier by turning the story name into kebab-case.
103 |
104 | - [`399f35e`](https://github.com/ocavue/astrobook/commit/399f35e4301319053aaa983534509ea78d10f856) Support Astro `base` option.
105 |
106 | ### Patch Changes
107 |
108 | - [`e4b4b6e`](https://github.com/ocavue/astrobook/commit/e4b4b6e9151d3565574637e5dbf0d04227746adc) Enable scrolling in the main area.
109 |
110 | ## 0.2.0
111 |
112 | ### Minor Changes
113 |
114 | - [`a238792`](https://github.com/ocavue/astrobook/commit/a2387928b822f1ed8c0ec5cf5ba9d9ce61bbd3f1) Add `directory` option to `astrobook` integration to allow specifying the directory to scan for stories.
115 |
116 | - [`4a77ceb`](https://github.com/ocavue/astrobook/commit/4a77ceb75bc42f05c8474b9af47800da2c34b7b5) Add a fullscreen button.
117 |
118 | ## 0.1.1
119 |
120 | ### Patch Changes
121 |
122 | - [`de4ac26`](https://github.com/ocavue/astrobook/commit/de4ac26393aeaccfdd154ca47e7b828fdeedff6d) Fix incorrect `package.json` `files` field.
123 |
124 | ## 0.1.0
125 |
126 | ### Minor Changes
127 |
128 | - [`e1dcb95`](https://github.com/ocavue/astrobook/commit/e1dcb95c5e66c049c6cb94367d4ba09429635a30) Release first version.
129 |
--------------------------------------------------------------------------------
/packages/astrobook/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Astrobook
3 |
The minimal UI component playground
4 |
5 |
6 | 
7 |
8 | Astrobook is a UI component playground that supports multiple frameworks including **React**, **Vue**, **Preact**, **Svelte**, **Solid**, **Lit**, and **Astro**. It offers a unified environment to develop, test, and showcase components.
9 |
10 | ## Try it Online
11 |
12 | - An example of using multiple UI rendering frameworks (React, Preact, Vue, Svelte, Solid, Lit, Astro) with Astrobook.
13 |
14 | Online demo: [astrobook.pages.dev](https://astrobook.pages.dev/)
15 |
16 | [![Open in StackBlitz][stackblitz_badge]][example_playground]
17 |
18 | - An example of using custom `` tags with Astrobook.
19 |
20 | [![Open in StackBlitz][stackblitz_badge]][example_custom_head]
21 |
22 | - An example that shows how to add Astrobook into an existing Astro project.
23 |
24 | [![Open in StackBlitz][stackblitz_badge]][example_mixed]
25 |
26 | - An example of using TailwindCSS with Astrobook.
27 |
28 | [![Open in StackBlitz][stackblitz_badge]][example_tailwindcss]
29 |
30 | - An example of using UnoCSS with Astrobook.
31 |
32 | [![Open in StackBlitz][stackblitz_badge]][example_unocss]
33 |
34 | ## Quick start
35 |
36 | > [!NOTE]
37 | > Astrobook supports various frameworks. We use React as an example here. Check the [Astro docs](https://docs.astro.build/en/guides/integrations-guide/#official-integrations) for other integrations.
38 |
39 | 1. Install the packages
40 |
41 | ```bash
42 | npm install astro @astrojs/react astrobook
43 | ```
44 |
45 | 2. Create `astro.config.mjs` and add the `astrobook` integration
46 |
47 | ```js
48 | // astro.config.mjs
49 | import { defineConfig } from 'astro/config'
50 | import react from '@astrojs/react'
51 | import astrobook from 'astrobook'
52 |
53 | // https://astro.build/config
54 | export default defineConfig({
55 | integrations: [react(), astrobook()],
56 | })
57 | ```
58 |
59 | 3. Add scripts to your `package.json`
60 |
61 | ```json
62 | "scripts": {
63 | "dev": "astro dev",
64 | "build": "astro build"
65 | }
66 | ```
67 |
68 | 4. Write stories. Astrobook scans all `.stories.{ts,tsx,js,jsx,mts,mtsx,mjs,mjsx}` files. It's compatible with Storybook's [Component Story Format v3](https://storybook.js.org/docs/api/csf).
69 |
70 | ```ts
71 | // src/components/Button.stories.ts
72 | import { Button, type ButtonProps } from './Button.tsx'
73 |
74 | export default {
75 | component: Button,
76 | }
77 |
78 | export const PrimaryButton = {
79 | args: {
80 | variant: 'primary',
81 | } satisfies ButtonProps,
82 | }
83 |
84 | export const SecondaryButton = {
85 | args: {
86 | variant: 'secondary',
87 | } satisfies ButtonProps,
88 | }
89 | ```
90 |
91 | 5. Run `npm run dev` and open `http://localhost:4321` to see your stories.
92 |
93 | ## Options
94 |
95 | ### `directory`
96 |
97 | You can use the `directory` option to specify the directory to scan for stories. The default directory is current working directory.
98 |
99 | ```js
100 | // astro.config.mjs
101 | import { defineConfig } from 'astro/config'
102 | import astrobook from 'astrobook'
103 |
104 | export default defineConfig({
105 | integrations: [
106 | astrobook({
107 | directory: 'src/components',
108 | }),
109 | /* ...other integrations */
110 | ],
111 | })
112 | ```
113 |
114 | ### `subpath`
115 |
116 | You can run Astrobook as a standalone app. You can also add it to your existing Astro project. In the latter case, you can use the `subpath` option to specify the subpath of Astrobook.
117 |
118 | ```js
119 | // astro.config.mjs
120 | import { defineConfig } from 'astro/config'
121 | import astrobook from 'astrobook'
122 |
123 | export default defineConfig({
124 | integrations: [
125 | astrobook({
126 | subpath: '/docs/components',
127 | }),
128 | ],
129 | })
130 | ```
131 |
132 | In the example above, Astrobook will be available at `http://localhost:4321/docs/components`.
133 |
134 | Notice that the `subpath` option is relative to the [base URL](https://docs.astro.build/en/reference/configuration-reference/#base) of your Astro project. For example, if you configure both Astro's `base` and `astrobook`'s `subpath`, like so:
135 |
136 | ```js
137 | // astro.config.mjs
138 | import { defineConfig } from 'astro/config'
139 | import astrobook from 'astrobook'
140 |
141 | export default defineConfig({
142 | base: '/base',
143 | integrations: [
144 | astrobook({
145 | subpath: '/docs/components',
146 | }),
147 | ],
148 | })
149 | ```
150 |
151 | You Astro project will be available at `http://localhost:4321/base` and Astrobook will be available at `http://localhost:4321/base/docs/components`.
152 |
153 | ### `css`
154 |
155 | You can customize the styles by using the `css` option to specify the CSS files to be imported into your Astrobook site.
156 |
157 | ```js
158 | // astro.config.mjs
159 | import { defineConfig } from 'astro/config'
160 | import astrobook from 'astrobook'
161 |
162 | export default defineConfig({
163 | integrations: [
164 | astrobook({
165 | css: [
166 | // Relative path to your custom CSS file
167 | './src/styles/custom.css',
168 | ],
169 | }),
170 | ],
171 | })
172 | ```
173 |
174 | ### `head`
175 |
176 | You can further customize your Astrobook project by providing a custom `head` options. It's a path to an Astro component that includes custom tags to the `` of your Astrobook site. It should only include [elements permitted inside ``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head#see_also), like ``, `
191 | ```
192 |
193 | ```js
194 | // astro.config.mjs
195 | import { defineConfig } from 'astro/config'
196 | import astrobook from 'astrobook'
197 |
198 | export default defineConfig({
199 | integrations: [
200 | astrobook({
201 | // Relative path to your custom head component
202 | head: './src/components/CustomHead.astro',
203 | }),
204 | ],
205 | })
206 | ```
207 |
208 | ### `title`
209 |
210 | You can set the title for your website.
211 |
212 | ```js
213 | // astro.config.mjs
214 | import { defineConfig } from 'astro/config'
215 | import astrobook from 'astrobook'
216 |
217 | export default defineConfig({
218 | integrations: [
219 | astrobook({
220 | title: 'My Components Playground',
221 | }),
222 | ],
223 | })
224 | ```
225 |
226 | ## Advanced
227 |
228 | ### Toggle theme via message
229 |
230 | If you're running Astrobook in an iframe, you can toggle the theme via a message.
231 |
232 | ```js
233 | const theme = 'light' // or "dark"
234 | iframe.contentWindow.postMessage({ type: 'astrobook:set-theme', theme }, '*')
235 | ```
236 |
237 | ## Who's using Astrobook?
238 |
239 | - [ProseKit](https://prosekit.dev/astrobook)
240 |
241 | _[Add your project](https://github.com/ocavue/astrobook/edit/master/packages/astrobook/README.md)_
242 |
243 | ## License
244 |
245 | MIT
246 |
247 | [stackblitz_badge]: https://developer.stackblitz.com/img/open_in_stackblitz.svg
248 | [example_playground]: https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/playground
249 | [example_unocss]: https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/unocss
250 | [example_tailwindcss]: https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/tailwindcss
251 | [example_custom_head]: https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/custom-head
252 | [example_mixed]: https://stackblitz.com/github/ocavue/astrobook/tree/master/examples/mixed
253 |
--------------------------------------------------------------------------------
/packages/astrobook/lib/components/head.astro:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/astrobook/lib/pages/app.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import AppComponent from '@astrobook/ui/components/app.astro'
3 | import type { GetStaticPathsResult } from 'astro'
4 |
5 | export function getStaticPaths(): GetStaticPathsResult {
6 | return [
7 | {
8 | params: {
9 | path: undefined,
10 | },
11 | },
12 | ]
13 | }
14 | ---
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/astrobook/lib/pages/story.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import LayoutComponent from '@astrobook/ui/components/layout.astro'
3 | import type { ComponentProps } from 'astro/types'
4 |
5 | type Props = ComponentProps
6 | ---
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/astrobook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "astrobook",
3 | "type": "module",
4 | "version": "0.8.1",
5 | "description": "The minimal UI component playground",
6 | "author": "ocavue ",
7 | "license": "MIT",
8 | "funding": "https://github.com/sponsors/ocavue",
9 | "homepage": "https://github.com/ocavue/astrobook#readme",
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/ocavue/astrobook.git"
13 | },
14 | "bugs": "https://github.com/ocavue/astrobook/issues",
15 | "keywords": [
16 | "ui",
17 | "component",
18 | "playground",
19 | "storybook",
20 | "react",
21 | "vue",
22 | "preact",
23 | "svelte",
24 | "solid",
25 | "lit",
26 | "astro",
27 | "vite"
28 | ],
29 | "sideEffects": false,
30 | "main": "./src/index.ts",
31 | "module": "./src/index.ts",
32 | "types": "./src/index.ts",
33 | "exports": {
34 | ".": {
35 | "default": "./src/index.ts"
36 | },
37 | "./client": {
38 | "default": "./src/client.ts"
39 | },
40 | "./components/*": {
41 | "default": "./lib/components/*"
42 | },
43 | "./pages/*": {
44 | "default": "./lib/pages/*"
45 | }
46 | },
47 | "typesVersions": {
48 | "*": {
49 | ".": [
50 | "./src/index.ts"
51 | ],
52 | "./client": [
53 | "./src/client.ts"
54 | ],
55 | "./pages/*": [
56 | "./lib/pages/*"
57 | ],
58 | "./components/*": [
59 | "./lib/components/*"
60 | ]
61 | }
62 | },
63 | "files": [
64 | "dist",
65 | "lib"
66 | ],
67 | "scripts": {
68 | "build": "tsc --build tsconfig.build.json"
69 | },
70 | "dependencies": {
71 | "@astrobook/core": "workspace:*",
72 | "@astrobook/types": "workspace:*",
73 | "@astrobook/ui": "workspace:*"
74 | },
75 | "peerDependencies": {
76 | "astro": ">=4.0.0"
77 | },
78 | "peerDependenciesMeta": {
79 | "astro": {
80 | "optional": true
81 | }
82 | },
83 | "devDependencies": {
84 | "@types/node": "^20.17.5",
85 | "astro": "^5.8.1",
86 | "typescript": "^5.8.3",
87 | "vite": "^6.3.5",
88 | "vitest": "^3.1.4"
89 | },
90 | "publishConfig": {
91 | "exports": {
92 | ".": {
93 | "types": "./dist/index.d.ts",
94 | "import": "./dist/index.js",
95 | "default": "./dist/index.js"
96 | },
97 | "./client": {
98 | "types": "./dist/client.d.ts",
99 | "import": "./dist/client.js",
100 | "default": "./dist/client.js"
101 | },
102 | "./components/*": {
103 | "default": "./lib/components/*"
104 | },
105 | "./pages/*": {
106 | "default": "./lib/pages/*"
107 | }
108 | },
109 | "main": "./dist/index.js",
110 | "module": "./dist/index.js",
111 | "types": "./dist/index.d.ts",
112 | "typesVersions": {
113 | "*": {
114 | ".": [
115 | "./dist/index.d.ts"
116 | ],
117 | "./client": [
118 | "./dist/client.d.ts"
119 | ],
120 | "./pages/*": [
121 | "./lib/pages/*"
122 | ],
123 | "./components/*": [
124 | "./lib/components/*"
125 | ]
126 | }
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/packages/astrobook/src/client.ts:
--------------------------------------------------------------------------------
1 | export * from '@astrobook/core/client'
2 |
--------------------------------------------------------------------------------
/packages/astrobook/src/index.ts:
--------------------------------------------------------------------------------
1 | import { createAstrobookIntegration } from '@astrobook/core'
2 |
3 | export default createAstrobookIntegration
4 |
--------------------------------------------------------------------------------
/packages/astrobook/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "tsBuildInfoFile": "./dist/.tsbuildinfo",
5 | "emitDeclarationOnly": false,
6 | "outDir": "./dist",
7 | "rootDir": "./src",
8 | "composite": true
9 | },
10 | "include": ["./**/*.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/astrobook/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "composite": true
5 | },
6 | "include": ["src/**/*", "./*.js", "./*.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@astrobook/core",
3 | "type": "module",
4 | "version": "0.8.1",
5 | "description": "",
6 | "author": "ocavue ",
7 | "license": "MIT",
8 | "funding": "https://github.com/sponsors/ocavue",
9 | "homepage": "https://github.com/ocavue/astrobook#readme",
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/ocavue/astrobook.git"
13 | },
14 | "bugs": "https://github.com/ocavue/astrobook/issues",
15 | "keywords": [],
16 | "sideEffects": false,
17 | "main": "./src/index.ts",
18 | "module": "./src/index.ts",
19 | "types": "./src/index.ts",
20 | "exports": {
21 | ".": {
22 | "default": "./src/index.ts"
23 | },
24 | "./client": {
25 | "default": "./src/client.ts"
26 | }
27 | },
28 | "files": [
29 | "dist"
30 | ],
31 | "scripts": {
32 | "build": "tsdown"
33 | },
34 | "dependencies": {
35 | "@astrobook/types": "workspace:*",
36 | "acorn": "^8.14.1",
37 | "acorn-jsx": "^5.3.2",
38 | "fdir": "^6.4.5",
39 | "picomatch": "^4.0.2",
40 | "slash": "^5.1.0"
41 | },
42 | "devDependencies": {
43 | "@types/node": "^20.17.5",
44 | "@types/picomatch": "^4.0.0",
45 | "@types/react": "^19.1.6",
46 | "@types/react-dom": "^19.1.5",
47 | "astro": "^5.8.1",
48 | "just-kebab-case": "^4.2.0",
49 | "tsdown": "^0.12.5",
50 | "typescript": "^5.8.3",
51 | "vite": "^6.3.5",
52 | "vitest": "^3.1.4"
53 | },
54 | "publishConfig": {
55 | "exports": {
56 | ".": {
57 | "types": "./dist/index.d.ts",
58 | "import": "./dist/index.js",
59 | "default": "./dist/index.js"
60 | },
61 | "./client": {
62 | "types": "./dist/client.d.ts",
63 | "import": "./dist/client.js",
64 | "default": "./dist/client.js"
65 | }
66 | },
67 | "main": "./dist/index.js",
68 | "module": "./dist/index.js",
69 | "types": "./dist/index.d.ts",
70 | "typesVersions": {
71 | "*": {
72 | ".": [
73 | "./dist/index.d.ts"
74 | ],
75 | "./client": [
76 | "./dist/client.d.ts"
77 | ]
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/packages/core/src/astro-integration.ts:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises'
2 | import path from 'node:path'
3 | import pathPosix from 'node:path/posix'
4 | import { fileURLToPath } from 'node:url'
5 |
6 | import type { IntegrationOptions } from '@astrobook/types'
7 | import type { AstroIntegration } from 'astro'
8 |
9 | import {
10 | createVirtualRouteComponent,
11 | getVirtualRoutes,
12 | } from './virtual-module/virtual-routes'
13 | import { createVirtualFilesPlugin } from './virtual-module/vite-plugin'
14 |
15 | export function createAstrobookIntegration(
16 | options?: IntegrationOptions,
17 | ): AstroIntegration {
18 | return {
19 | name: 'astrobook',
20 | hooks: {
21 | 'astro:config:setup': async ({
22 | updateConfig,
23 | injectRoute,
24 | createCodegenDir,
25 | config,
26 | logger,
27 | }) => {
28 | const rootDir = path.resolve(options?.directory || '.')
29 | const astroBaseUrl = config.base || '/'
30 | const astrobookBaseUrl = options?.subpath || ''
31 | const baseUrl = pathPosix.join(astroBaseUrl, astrobookBaseUrl)
32 |
33 | logger.debug(`Creating dedicated folder`)
34 | let codegenDir: string
35 | if (createCodegenDir) {
36 | codegenDir = fileURLToPath(createCodegenDir())
37 | } else {
38 | // Astro v4, where `createCodegenDir()` is not available
39 | codegenDir = path.resolve('.astro', 'integrations', 'astrobook')
40 | await fs.mkdir(codegenDir, { recursive: true })
41 | }
42 | logger.debug(`Created dedicated folder at ${codegenDir}`)
43 |
44 | logger.debug(`Scanning for stories in ${rootDir}`)
45 | const routes = await getVirtualRoutes(rootDir, codegenDir, logger)
46 |
47 | logger.debug(`Writing files to ${codegenDir}`)
48 | await Promise.all(
49 | Array.from(routes.values()).map(async (route) => {
50 | const filePath = route.entrypoint
51 | const fileContent = createVirtualRouteComponent(route)
52 | await fs.mkdir(path.dirname(filePath), { recursive: true })
53 | await fs.writeFile(filePath, fileContent, { encoding: 'utf-8' })
54 | }),
55 | )
56 |
57 | updateConfig({
58 | vite: {
59 | plugins: [
60 | createVirtualFilesPlugin(
61 | rootDir,
62 | {
63 | baseUrl,
64 | head: options?.head || 'astrobook/components/head.astro',
65 | css: options?.css || [],
66 | title: options?.title || 'Astrobook',
67 | },
68 | config,
69 | ),
70 | ],
71 | },
72 | })
73 |
74 | logger.debug(`Injecting routes`)
75 | for (const route of routes.values()) {
76 | const pattern = pathPosix.join(astrobookBaseUrl, route.pattern)
77 | const entrypoint = path.normalize(
78 | path.relative('.', route.entrypoint),
79 | )
80 | injectRoute({
81 | pattern,
82 | entrypoint,
83 | prerender: true,
84 | })
85 | }
86 | injectRoute({
87 | pattern: astrobookBaseUrl,
88 | entrypoint: 'astrobook/pages/app.astro',
89 | prerender: true,
90 | })
91 | },
92 | },
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/packages/core/src/client.ts:
--------------------------------------------------------------------------------
1 | import type { AstroComponentFactory } from 'astro/runtime/server/index.js'
2 |
3 | export { getPathWithBase } from './utils/base'
4 |
5 | export function isAstroStory(module: { default?: { component?: unknown } }) {
6 | try {
7 | const component = module.default?.component
8 | if (!component) return false
9 |
10 | return isAstroComponentFactory(component)
11 | } catch {
12 | return false
13 | }
14 | }
15 |
16 | // Copy from https://github.com/withastro/astro/blob/astro@5.0.0/packages/astro/src/runtime/server/render/astro/factory.ts#L15
17 | export function isAstroComponentFactory(
18 | obj: unknown,
19 | ): obj is AstroComponentFactory {
20 | return obj == null
21 | ? false
22 | : (obj as Record).isAstroComponentFactory === true
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | import { createAstrobookIntegration } from './astro-integration'
2 |
3 | export { createAstrobookIntegration }
4 |
--------------------------------------------------------------------------------
/packages/core/src/utils/base.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 |
3 | import { getPathWithBase } from './base'
4 |
5 | test('getPathWithBase', () => {
6 | expect(getPathWithBase('/path', '')).toMatchInlineSnapshot(`"/path"`)
7 | expect(getPathWithBase('/path/', '')).toMatchInlineSnapshot(`"/path"`)
8 | expect(getPathWithBase('/path/', '/base')).toMatchInlineSnapshot(
9 | `"/base/path"`,
10 | )
11 | expect(getPathWithBase('/path', '/')).toMatchInlineSnapshot(`"/path"`)
12 | expect(getPathWithBase('/', '/')).toMatchInlineSnapshot(`"/"`)
13 | expect(getPathWithBase('/', 'base')).toMatchInlineSnapshot(`"/base"`)
14 | expect(getPathWithBase('/path', 'base/')).toMatchInlineSnapshot(
15 | `"/base/path"`,
16 | )
17 | })
18 |
--------------------------------------------------------------------------------
/packages/core/src/utils/base.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { ensureLeadingSlash, stripSlashes } from './path'
4 |
5 | export function getPathWithBase(path: string, base: string): string {
6 | return ensureLeadingSlash(
7 | [base, path].map(stripSlashes).filter(Boolean).join('/'),
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/src/utils/get-exports.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest'
2 |
3 | import { getExports } from './get-exports'
4 |
5 | describe('getExports', () => {
6 | it('can parse named exports', () => {
7 | expect(getExports('export const a = 1')).toEqual(['a'])
8 | expect(getExports('const a = 1; export { a }')).toEqual(['a'])
9 | expect(getExports('const a = 1; export { a as b }')).toEqual(['b'])
10 | expect(getExports('export function a () {}')).toEqual(['a'])
11 | expect(getExports('export class A {}')).toEqual(['A'])
12 | expect(getExports('export { a } from "./other"')).toEqual(['a'])
13 | })
14 |
15 | it('can parse named exports', () => {
16 | expect(getExports('export default { name: "a" }')).toEqual(['default'])
17 | expect(getExports('const a = 1; export { a as default }')).toEqual([
18 | 'default',
19 | ])
20 | })
21 |
22 | it('can parse JSX', () => {
23 | expect(
24 | getExports('export function a() { return hello
}'),
25 | ).toEqual(['a'])
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/packages/core/src/utils/get-exports.ts:
--------------------------------------------------------------------------------
1 | import { Parser } from 'acorn'
2 | import jsx from 'acorn-jsx'
3 |
4 | /**
5 | * Parses the content of the given file and returns all its exports
6 | */
7 | export function getExports(code: string): string[] {
8 | // Parse the code into an AST
9 | const parser = Parser.extend(jsx())
10 | const ast = parser.parse(code, {
11 | sourceType: 'module',
12 | ecmaVersion: 'latest',
13 | allowImportExportEverywhere: true,
14 | })
15 |
16 | const exports = new Set()
17 |
18 | // Walk through the AST
19 | ast.body.forEach((node) => {
20 | if (node.type === 'ExportNamedDeclaration') {
21 | node.specifiers.forEach((specifier) => {
22 | const { exported } = specifier
23 | if (exported.type === 'Identifier') {
24 | exports.add(exported.name)
25 | }
26 | })
27 |
28 | const { declaration } = node
29 | if (declaration?.type === 'VariableDeclaration') {
30 | const { declarations } = declaration
31 | declarations.forEach((declaration) => {
32 | const id = declaration.id
33 | if (id.type === 'Identifier') {
34 | exports.add(id.name)
35 | }
36 | })
37 | }
38 | if (declaration?.type === 'FunctionDeclaration') {
39 | exports.add(declaration.id.name)
40 | }
41 | if (declaration?.type === 'ClassDeclaration') {
42 | exports.add(declaration.id.name)
43 | }
44 | } else if (node.type === 'ExportDefaultDeclaration') {
45 | exports.add('default')
46 | }
47 | })
48 |
49 | return Array.from(exports).sort()
50 | }
51 |
--------------------------------------------------------------------------------
/packages/core/src/utils/invariant.ts:
--------------------------------------------------------------------------------
1 | import assert from 'node:assert'
2 |
3 | export function invariant(
4 | condition: unknown,
5 | message = 'Unexpected invariant violation',
6 | ): asserts condition {
7 | if (!condition) {
8 | assert(
9 | condition,
10 | `[astrobook] Unexpected internal error. Please open an issue at https://github.com/ocavue/astrobook/issues. ${message}`,
11 | )
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/utils/path.ts:
--------------------------------------------------------------------------------
1 | /** Ensure the passed path does not start and end with slashes. */
2 | export function stripSlashes(path: string): string {
3 | return path.split('/').filter(Boolean).join('/')
4 | }
5 |
6 | /** Ensure the passed path starts with a leading slash. */
7 | export function ensureLeadingSlash(path: string): string {
8 | if (path.startsWith('/')) {
9 | return path
10 | }
11 | return '/' + path
12 | }
13 |
14 | /** Ensure the passed path ends with a trailing slash. */
15 | export function ensureTrailingSlash(path: string): string {
16 | if (path.endsWith('/')) {
17 | return path
18 | }
19 | return path + '/'
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/src/virtual-module/get-story-modules.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 |
3 | import { convertStoryFileToModule } from './get-story-modules'
4 |
5 | test('convertStoryFileToModule', () => {
6 | expect(
7 | convertStoryFileToModule('/my-project', {
8 | filePath: '/my-project/path/to/Button.stories.js',
9 | defaultExport: true,
10 | namedExports: ['PrimaryButton', 'SecondaryButton'],
11 | }),
12 | ).toMatchInlineSnapshot(`
13 | {
14 | "directory": "path/to",
15 | "id": "path/to/button",
16 | "importPath": "/my-project/path/to/Button.stories.js",
17 | "name": "Button",
18 | "stories": [
19 | {
20 | "id": "path/to/button/primary-button",
21 | "name": "PrimaryButton",
22 | },
23 | {
24 | "id": "path/to/button/secondary-button",
25 | "name": "SecondaryButton",
26 | },
27 | ],
28 | }
29 | `)
30 | })
31 |
--------------------------------------------------------------------------------
/packages/core/src/virtual-module/get-story-modules.ts:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises'
2 | import path from 'node:path'
3 |
4 | import type { StoryModule } from '@astrobook/types'
5 | import { fdir } from 'fdir'
6 | import kebabCase from 'just-kebab-case'
7 | import slash from 'slash'
8 |
9 | import { getExports } from '../utils/get-exports'
10 | import { invariant } from '../utils/invariant'
11 |
12 | /**
13 | * List the absolute paths of all story files in the given directory.
14 | */
15 | async function listStoryFiles(rootDir: string): Promise {
16 | invariant(
17 | path.isAbsolute(rootDir),
18 | `Root directory must be absolute, but got '${rootDir}'`,
19 | )
20 |
21 | const filePaths = await new fdir()
22 | .withSymlinks()
23 | .withFullPaths()
24 | .normalize()
25 | .glob('./**/*.stories.{ts,tsx,js,jsx,mts,mtsx,mjs,mjsx}')
26 | .exclude((dirname) => dirname === 'node_modules')
27 | .crawl(rootDir)
28 | .withPromise()
29 |
30 | return filePaths.sort()
31 | }
32 |
33 | type ParsedStoryFile = {
34 | /**
35 | * The absolute path to the file. Unix-style slashes are used.
36 | */
37 | filePath: string
38 |
39 | /**
40 | * Whether the file has a default export
41 | */
42 | defaultExport: boolean
43 |
44 | /**
45 | * The named exports in the file
46 | */
47 | namedExports: string[]
48 | }
49 |
50 | async function parseStoryFiles(filePath: string): Promise {
51 | const code = await fs.readFile(filePath, 'utf-8')
52 | const exports = getExports(code)
53 | const defaultExport = exports.includes('default')
54 | const namedExports = exports.filter((name) => name !== 'default')
55 |
56 | return { filePath, defaultExport, namedExports }
57 | }
58 |
59 | export function convertStoryFileToModule(
60 | rootDir: string,
61 | file: ParsedStoryFile,
62 | ): StoryModule | undefined {
63 | if (file.namedExports.length === 0) {
64 | console.warn(`[astrobook] File ${file.filePath} has no named exports`)
65 | return
66 | }
67 |
68 | if (!file.defaultExport) {
69 | console.warn(`[astrobook] File ${file.filePath} has no default export`)
70 | return
71 | }
72 |
73 | const relativePath = path.normalize(path.relative(rootDir, file.filePath))
74 | const relativePathWithoutStories = relativePath.replace(
75 | /\.stories\.\w+$/i,
76 | '',
77 | )
78 | const parts = relativePathWithoutStories.split(path.sep)
79 |
80 | const directory = parts.slice(0, -1).join('/')
81 | const name = parts.pop()
82 | invariant(name, `Unexpected file path: ${file.filePath}`)
83 |
84 | const moduleId = directory
85 | ? `${directory}/${kebabCase(name)}`
86 | : kebabCase(name)
87 |
88 | return {
89 | id: moduleId,
90 | name,
91 | directory,
92 | importPath: slash(file.filePath),
93 | stories: file.namedExports.map((name) => {
94 | return { id: `${moduleId}/${kebabCase(name)}`, name }
95 | }),
96 | }
97 | }
98 |
99 | export async function getStoryModules(rootDir: string): Promise {
100 | const storyFilePaths = await listStoryFiles(rootDir)
101 | const storyFiles = await Promise.all(storyFilePaths.map(parseStoryFiles))
102 |
103 | return storyFiles
104 | .map((storyFile) => convertStoryFileToModule(rootDir, storyFile))
105 | .filter((module) => !!module)
106 | }
107 |
--------------------------------------------------------------------------------
/packages/core/src/virtual-module/story-modules.ts:
--------------------------------------------------------------------------------
1 | import { getStoryModules } from './get-story-modules'
2 |
3 | export async function loadStoryModules(rootDir: string): Promise {
4 | const modules = await getStoryModules(rootDir)
5 | return `export default ${JSON.stringify(modules)}`
6 | }
7 |
--------------------------------------------------------------------------------
/packages/core/src/virtual-module/virtual-module-ids.ts:
--------------------------------------------------------------------------------
1 | export const STORY_MODULES_ID = 'virtual:astrobook/story-modules.mjs'
2 | export const STORY_MODULES_RESOLVED_ID =
3 | '__virtual_astrobook_story_modules__.mjs'
4 |
5 | export const GLOBAL_CONFIG_ID = 'virtual:astrobook/global-config.mjs'
6 | export const GLOBAL_CONFIG_RESOLVED_ID = '__virtual_astrobook_user_config__.mjs'
7 |
8 | export const COMPONENT_HEAD_ID = 'virtual:astrobook/components/head.mjs'
9 | export const COMPONENT_HEAD_RESOLVED_ID =
10 | '__virtual_astrobook_COMPONENT_HEAD__.mjs'
11 |
12 | export const USER_CSS_ID = 'virtual:astrobook/user-css.mjs'
13 | export const USER_CSS_RESOLVED_ID = '__virtual_astrobook_user_css__.mjs'
14 |
--------------------------------------------------------------------------------
/packages/core/src/virtual-module/virtual-routes.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 |
3 | import type { Story, StoryModule } from '@astrobook/types'
4 | import type { AstroIntegrationLogger } from 'astro'
5 | import slash from 'slash'
6 |
7 | import { invariant } from '../utils/invariant'
8 |
9 | import { getStoryModules } from './get-story-modules'
10 |
11 | export interface VirtualRoute {
12 | pattern: string
13 | /**
14 | * The absolute path to the virtual entrypoint file. Posix slash format.
15 | *
16 | * It's important to use an absolute path here because we must ensure that we
17 | * only have one `id`, so that Astro's CSS plugin can find the correct CSS
18 | * content in the following line.
19 | * https://github.com/withastro/astro/blob/bc2796436dc3810e988c27b71b7a66fcb1ae8bda/packages/astro/src/core/build/plugins/plugin-css.ts#L144
20 | */
21 | entrypoint: string
22 | storyModule: StoryModule
23 | story: Story
24 | props: {
25 | hasSidebar: boolean
26 | story: string
27 | }
28 | }
29 |
30 | export async function getVirtualRoutes(
31 | rootDir: string,
32 | codegenDir: string,
33 | logger: AstroIntegrationLogger,
34 | ): Promise