diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2ad477a56..79d618012 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,7 +8,7 @@ on: - main jobs: - deploy: + deploy-v3: runs-on: ubuntu-latest permissions: contents: read @@ -33,3 +33,36 @@ jobs: accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} gitHubToken: ${{ secrets.GITHUB_TOKEN }} command: pages deploy packages/docs/.vitepress/dist --project-name=js-toolkit + + deploy-v4: + runs-on: ubuntu-latest + permissions: + contents: read + deployments: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: 'npm' + + - run: npm ci + + # The v4 exports map has no `typescript` condition, so twoslash resolves the + # package types out of `dist`. The committed `@twoslash-cache` comments make + # that unnecessary on a cache hit — this is what keeps a cache *miss* from + # failing the build instead of silently type-checking against nothing. + - name: Build the v4 package + run: npm run build:v4 + + - name: Build v4 docs + run: npm run docs:v4:build + + - name: Deploy to Cloudflare Pages + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + gitHubToken: ${{ secrets.GITHUB_TOKEN }} + command: pages deploy packages/v4/docs/.vitepress/dist --project-name=js-toolkit-v4 diff --git a/.gitignore b/.gitignore index 907afb4a8..f02dfddd8 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,8 @@ packages/demo/dist/ .bench-report.json /packages/docs/.vitepress/cache /packages/docs/.vitepress/dist +/packages/v4/docs/.vitepress/cache +/packages/v4/docs/.vitepress/dist coverage/ /full.d.ts /index.d.ts diff --git a/.oxlintrc.json b/.oxlintrc.json index a8fde06f4..539e01f2e 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -114,7 +114,8 @@ "**/*.bench.ts", "**/__benchmarks__/**", "packages/v4/test/**", - "packages/v4/scripts/**" + "packages/v4/scripts/**", + "packages/v4/docs/**" ], "rules": { "complexity": "off", diff --git a/README.md b/README.md index 4eaa17e82..55f1c1ce2 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,14 @@ Visit [js-toolkit.studiometa.dev](https://js-toolkit.studiometa.dev) to learn mo ## Packages -| Package | Description | -| ------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| [`@studiometa/js-toolkit`](./packages/js-toolkit/) | The framework and its utility functions. | -| [`@studiometa/eslint-plugin-js-toolkit`](./packages/eslint-plugin/) | Oxlint/ESLint rules enforcing the framework's best practices. | -| [`packages/v4`](./packages/v4/) | The v4 prototype, private and not published. | -| [`packages/docs`](./packages/docs/) | Sources of [js-toolkit.studiometa.dev](https://js-toolkit.studiometa.dev). | -| [`packages/demo`](./packages/demo/) | A playground application used during development. | +| Package | Description | +| ------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| [`@studiometa/js-toolkit`](./packages/js-toolkit/) | The framework and its utility functions. | +| [`@studiometa/eslint-plugin-js-toolkit`](./packages/eslint-plugin/) | Oxlint/ESLint rules enforcing the framework's best practices. | +| [`packages/v4`](./packages/v4/) | The v4 prototype, private and not published. | +| [`packages/docs`](./packages/docs/) | Sources of [js-toolkit.studiometa.dev](https://js-toolkit.studiometa.dev). | +| [`packages/v4/docs`](./packages/v4/docs/) | Sources of [js-toolkit-v4.studiometa.dev](https://js-toolkit-v4.studiometa.dev). | +| [`packages/demo`](./packages/demo/) | A playground application used during development. | ## Installation diff --git a/package-lock.json b/package-lock.json index d441558f8..af72a70c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5386,6 +5386,20 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/@studiometa/tailwind-config": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@studiometa/tailwind-config/-/tailwind-config-3.0.0.tgz", + "integrity": "sha512-SauwUlgEcptxMMyy1TGL7Xod22654RncBT6whdefvUbgYw/46tnnXLDKVJ9vDb1QylWVkSxBUAUHzTov2AM9KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "postcss": "^8.5.6", + "tailwindcss": "^4.1.16" + } + }, "node_modules/@studiometa/ui": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@studiometa/ui/-/ui-1.7.0.tgz", @@ -24784,18 +24798,6 @@ "tailwindcss": "4.2.4" } }, - "packages/demo/node_modules/@studiometa/tailwind-config": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "postcss": "^8.5.6", - "tailwindcss": "^4.1.16" - } - }, "packages/demo/node_modules/@studiometa/webpack-config-preset-tailwindcss-4": { "version": "6.4.0", "dev": true, @@ -24827,18 +24829,6 @@ "vitepress-plugin-llms": "1.12.0" } }, - "packages/docs/node_modules/@studiometa/tailwind-config": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "postcss": "^8.5.6", - "tailwindcss": "^4.1.16" - } - }, "packages/eslint-plugin": { "name": "@studiometa/eslint-plugin-js-toolkit", "version": "3.9.0", @@ -24883,13 +24873,19 @@ }, "devDependencies": { "@rollup/plugin-swc": "^0.4.1", + "@shikijs/vitepress-twoslash": "4.0.2", + "@studiometa/tailwind-config": "3.0.0", "@swc/core": "^1.15.47", + "@tailwindcss/vite": "4.2.2", "@vitest/browser": "4.1.10", "@vitest/browser-playwright": "4.1.10", "fast-glob": "3.3.3", "playwright": "^1.50.0", + "tailwindcss": "4.2.2", "tsdown": "0.22.14", "vite": "8.2.1", + "vitepress": "2.0.0-alpha.19", + "vitepress-plugin-llms": "1.12.0", "vitest": "4.1.10" } } diff --git a/package.json b/package.json index fd2839b87..a2b33a1d3 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "docs:dev": "npm run dev --workspace=@studiometa/js-toolkit-docs", "docs:build": "npm run build --workspace=@studiometa/js-toolkit-docs", "docs:preview": "npm run preview --workspace=@studiometa/js-toolkit-docs", + "docs:v4:dev": "npm run docs:dev -w @studiometa/js-toolkit-v4", + "docs:v4:build": "npm run docs:build -w @studiometa/js-toolkit-v4", + "docs:v4:preview": "npm run docs:preview -w @studiometa/js-toolkit-v4", "build": "npm run build:js-toolkit && npm run build:v4 && npm run build:eslint-plugin", "build:js-toolkit": "npm run build -w @studiometa/js-toolkit", "build:v4": "npm run build -w @studiometa/js-toolkit-v4", diff --git a/packages/v4/docs/.vitepress/config.ts b/packages/v4/docs/.vitepress/config.ts new file mode 100644 index 000000000..431cd017e --- /dev/null +++ b/packages/v4/docs/.vitepress/config.ts @@ -0,0 +1,345 @@ +import { defineConfig } from 'vitepress'; +import { createTwoslashWithInlineCache } from '@shikijs/vitepress-twoslash/cache-inline'; + +const withTwoslashInlineCache = createTwoslashWithInlineCache(); + +export default withTwoslashInlineCache( + defineConfig({ + vite: { + build: { + chunkSizeWarningLimit: 600, + }, + }, + lang: 'en-US', + title: '@studiometa/js-toolkit v4', + description: + 'v4 of the JS Toolkit by Studio Meta: one registry, one mutation engine and one scheduler behind a data-attributes driven micro-framework.', + lastUpdated: true, + head: [['link', { rel: 'icon', type: 'image/x-icon', href: '/logo.png' }]], + markdown: { + // Explicitly load these languages for types hightlighting + languages: ['js', 'jsx', 'ts', 'tsx', 'bash', 'html'], + }, + themeConfig: { + outline: 'deep', + repo: 'studiometa/js-toolkit', + docsDir: 'packages/v4/docs', + lastUpdated: { + text: 'Last updated', + }, + editLinks: true, + editLinkText: 'Edit this page on GitHub', + sidebarDepth: 3, + footer: { + message: 'MIT Licensed', + copyright: 'Copyright © 2020–present Studio Meta', + }, + search: { + provider: 'local', + }, + socialLinks: [{ icon: 'github', link: 'https://github.com/studiometa/js-toolkit' }], + nav: [ + { text: 'Guide', link: '/guide/' }, + { text: 'API Reference', link: '/api/' }, + { text: 'Utils Reference', link: '/utils/' }, + { text: 'Concepts', link: '/guide/concepts/philosophy.html' }, + { + text: `v4`, + items: [ + { text: 'Migrating from v3', link: '/guide/migration/v3-to-v4.html' }, + { + text: 'Design document', + link: 'https://github.com/studiometa/js-toolkit/blob/main/packages/v4/DESIGN.md', + }, + { + text: 'Rationale', + link: 'https://github.com/studiometa/js-toolkit/blob/main/packages/v4/RATIONALE.md', + }, + { text: 'v3.x documentation', link: 'https://js-toolkit.studiometa.dev/' }, + ], + }, + ], + sidebar: { + '/guide/': getGuideSidebar(), + '/api/html/': getApiSidebar({ expanded: 'html' }), + '/api/registry/': getApiSidebar({ expanded: 'registry' }), + '/api/context/': getApiSidebar({ expanded: 'context' }), + '/api/services/': getApiSidebar({ expanded: 'services' }), + '/api/decorators/': getApiSidebar({ expanded: 'decorators' }), + '/api/scheduler/': getApiSidebar({ expanded: 'scheduler' }), + '/api/dom/': getApiSidebar({ expanded: 'dom' }), + '/api/storage/': getApiSidebar({ expanded: 'storage' }), + '/api/diagnostics/': getApiSidebar({ expanded: 'diagnostics' }), + '/api/test/': getApiSidebar({ expanded: 'test' }), + '/api/': getApiSidebar({ expanded: 'api' }), + '/utils/': getUtilsSidebar(), + }, + }, + }), +); + +function getGuideSidebar() { + return [ + { text: 'Getting Started', link: '/guide/' }, + { text: 'Installation', link: '/guide/introduction/installation.html' }, + { text: 'Components', link: '/guide/introduction/managing-components.html' }, + { text: 'Refs', link: '/guide/introduction/managing-refs.html' }, + { text: 'Options', link: '/guide/introduction/managing-options.html' }, + { text: 'Lifecycle', link: '/guide/introduction/lifecycle-hooks.html' }, + { text: 'Events', link: '/guide/introduction/working-with-events.html' }, + { text: 'Services', link: '/guide/introduction/using-services.html' }, + { + text: 'Going further', + collapsed: false, + items: [ + { text: 'Mount strategies', link: '/guide/going-further/mount-strategies.html' }, + { text: 'Autoloading', link: '/guide/going-further/autoloading.html' }, + { text: 'Shared state', link: '/guide/going-further/sharing-state.html' }, + { text: 'Decorators', link: '/guide/going-further/using-decorators.html' }, + { text: 'The scheduler', link: '/guide/going-further/scheduling-work.html' }, + { text: 'Swapping content', link: '/guide/going-further/swapping-content.html' }, + { text: 'Storage', link: '/guide/going-further/using-storage.html' }, + { text: 'Diagnostics', link: '/guide/going-further/handling-diagnostics.html' }, + { text: 'Testing', link: '/guide/going-further/testing-components.html' }, + { + text: 'TypeScript', + link: '/guide/going-further/typing-components.html', + keywords: ['types', 'typings', 'typescript', 'jsdoc'], + }, + ], + }, + { + text: 'Concepts', + collapsed: false, + items: [ + { text: 'Philosophy', link: '/guide/concepts/philosophy.html' }, + { text: 'The registry', link: '/guide/concepts/the-registry.html' }, + { text: 'The attribute grammar', link: '/guide/concepts/attribute-grammar.html' }, + { text: 'The mutation engine', link: '/guide/concepts/mutation-engine.html' }, + ], + }, + { + text: 'Migration', + collapsed: false, + items: [{ text: 'v3 → v4', link: '/guide/migration/v3-to-v4.html' }], + }, + ]; +} + +function getApiSidebar({ expanded = 'api' } = {}) { + return [ + { + text: 'Base class', + link: '/api/', + collapsed: expanded !== 'api', + items: getBaseSidebar(), + }, + { + text: 'HTML', + link: '/api/html/', + collapsed: expanded !== 'html', + items: getHtmlSidebar(), + }, + { + text: 'Registry', + link: '/api/registry/', + collapsed: expanded !== 'registry', + items: getRegistrySidebar(), + }, + { + text: 'Shared state', + link: '/api/context/', + collapsed: expanded !== 'context', + items: getContextSidebar(), + }, + { + text: 'Services', + link: '/api/services/', + collapsed: expanded !== 'services', + items: getServicesSidebar(), + }, + { + text: 'Decorators', + link: '/api/decorators/', + collapsed: expanded !== 'decorators', + items: getDecoratorsSidebar(), + }, + { + text: 'Scheduler', + link: '/api/scheduler/', + collapsed: expanded !== 'scheduler', + items: getSchedulerSidebar(), + }, + { + text: 'DOM', + link: '/api/dom/', + collapsed: expanded !== 'dom', + items: getDomSidebar(), + }, + { + text: 'Storage', + link: '/api/storage/', + collapsed: expanded !== 'storage', + items: getStorageSidebar(), + }, + { + text: 'Diagnostics', + link: '/api/diagnostics/', + collapsed: expanded !== 'diagnostics', + items: getDiagnosticsSidebar(), + }, + { + text: 'Testing', + link: '/api/test/', + collapsed: expanded !== 'test', + items: getTestSidebar(), + }, + ]; +} + +function getBaseSidebar() { + return [ + { text: 'Configuration', link: '/api/configuration.html' }, + { text: 'Lifecycle hooks', link: '/api/methods-hooks-lifecycle.html' }, + { text: 'Options hooks', link: '/api/methods-hooks-options.html' }, + { text: 'Events hooks', link: '/api/methods-hooks-events.html' }, + { text: 'Services hooks', link: '/api/methods-hooks-services.html' }, + { text: 'Instance properties', link: '/api/instance-properties.html' }, + { text: 'Instance methods', link: '/api/instance-methods.html' }, + { text: 'Instance events', link: '/api/instance-events.html' }, + ]; +} + +function getHtmlSidebar() { + return [ + { text: 'data-component', link: '/api/html/data-component.html' }, + { text: 'data-mount', link: '/api/html/data-mount.html' }, + { text: 'data-ref', link: '/api/html/data-ref.html' }, + { text: 'data-option-<​name>', link: '/api/html/data-option.html' }, + ]; +} + +function getRegistrySidebar() { + return [ + { text: 'registerComponent', link: '/api/registry/registerComponent.html' }, + { text: 'registerComponents', link: '/api/registry/registerComponents.html' }, + { text: 'registerManifest', link: '/api/registry/registerManifest.html' }, + { text: 'defineManifest', link: '/api/registry/defineManifest.html' }, + { text: 'fromMetaGlob', link: '/api/registry/fromMetaGlob.html' }, + { text: 'fromWebpackContext', link: '/api/registry/fromWebpackContext.html' }, + { text: 'getInstance', link: '/api/registry/getInstance.html' }, + { text: 'getInstances', link: '/api/registry/getInstances.html' }, + { text: 'getMountedInstances', link: '/api/registry/getMountedInstances.html' }, + { text: 'getUnmountedInstances', link: '/api/registry/getUnmountedInstances.html' }, + ]; +} + +function getContextSidebar() { + return [ + { text: 'createContext', link: '/api/context/createContext.html' }, + { text: 'signal', link: '/api/context/signal.html' }, + { text: 'provideContext', link: '/api/context/provideContext.html' }, + { text: 'provideRootContext', link: '/api/context/provideRootContext.html' }, + { text: 'injectContext', link: '/api/context/injectContext.html' }, + { text: 'injectContextSync', link: '/api/context/injectContextSync.html' }, + { text: 'subscribeContext', link: '/api/context/subscribeContext.html' }, + { text: 'createGroup', link: '/api/context/createGroup.html' }, + ]; +} + +function getServicesSidebar() { + return [ + { text: 'useRaf', link: '/api/services/useRaf.html' }, + { text: 'useScroll', link: '/api/services/useScroll.html' }, + { text: 'useWindowScroll', link: '/api/services/useWindowScroll.html' }, + { text: 'useScrollProgress', link: '/api/services/useScrollProgress.html' }, + { text: 'useResize', link: '/api/services/useResize.html' }, + { text: 'useWindowSize', link: '/api/services/useWindowSize.html' }, + { text: 'usePointer', link: '/api/services/usePointer.html' }, + { text: 'useDrag', link: '/api/services/useDrag.html' }, + { text: 'useKey', link: '/api/services/useKey.html' }, + { text: 'useInView', link: '/api/services/useInView.html' }, + { text: 'useMutation', link: '/api/services/useMutation.html' }, + { text: 'useBreakpoint', link: '/api/services/useBreakpoint.html' }, + { text: 'useMediaQuery', link: '/api/services/useMediaQuery.html' }, + { text: 'usePrefersReducedMotion', link: '/api/services/usePrefersReducedMotion.html' }, + { text: 'Mixins (with*)', link: '/api/services/mixins.html' }, + { text: 'createService', link: '/api/services/createService.html' }, + { text: 'createServiceMixin', link: '/api/services/createServiceMixin.html' }, + { text: 'perTarget', link: '/api/services/perTarget.html' }, + { text: 'toggle', link: '/api/services/toggle.html' }, + { text: 'until', link: '/api/services/until.html' }, + ]; +} + +function getDecoratorsSidebar() { + return [ + { text: '@component', link: '/api/decorators/component.html' }, + { text: '@on', link: '/api/decorators/on.html' }, + { text: '@provide', link: '/api/decorators/provide.html' }, + { text: '@inject', link: '/api/decorators/inject.html' }, + { text: '@children', link: '/api/decorators/children.html' }, + { text: '@read / @write', link: '/api/decorators/read-write.html' }, + ]; +} + +function getSchedulerSidebar() { + return [ + { text: 'defaultScheduler', link: '/api/scheduler/defaultScheduler.html' }, + { text: 'nextFrame', link: '/api/scheduler/nextFrame.html' }, + { text: 'viewTransition', link: '/api/scheduler/viewTransition.html' }, + ]; +} + +function getDomSidebar() { + return [ + { text: 'swap', link: '/api/dom/swap.html' }, + { text: 'domUpdate', link: '/api/dom/domUpdate.html' }, + { text: 'emitExtendable', link: '/api/dom/emitExtendable.html' }, + { text: 'watchAttributes', link: '/api/dom/watchAttributes.html' }, + { text: 'watchAttributeNamespace', link: '/api/dom/watchAttributeNamespace.html' }, + { text: 'whenDOMSettled', link: '/api/dom/whenDOMSettled.html' }, + { text: 'Breakpoints', link: '/api/dom/breakpoints.html' }, + ]; +} + +function getStorageSidebar() { + return [ + { text: 'createStorage', link: '/api/storage/createStorage.html' }, + { text: 'Presets', link: '/api/storage/presets.html' }, + { text: 'Providers', link: '/api/storage/providers.html' }, + ]; +} + +function getDiagnosticsSidebar() { + return [ + { text: 'EVENTS', link: '/api/diagnostics/EVENTS.html' }, + { text: 'DIAGNOSTICS', link: '/api/diagnostics/DIAGNOSTICS.html' }, + { text: 'reportDiagnostic', link: '/api/diagnostics/reportDiagnostic.html' }, + { text: 'warn', link: '/api/diagnostics/warn.html' }, + ]; +} + +function getTestSidebar() { + return [{ text: 'Test helpers', link: '/api/test/' }]; +} + +function getUtilsSidebar() { + return [ + { text: 'Overview', link: '/utils/' }, + { text: 'Type guards', link: '/utils/is.html' }, + { text: 'Strings', link: '/utils/strings.html' }, + { text: 'Math', link: '/utils/math.html' }, + { text: 'Easings', link: '/utils/easings.html' }, + { text: 'Motion', link: '/utils/motion.html' }, + { text: 'CSS', link: '/utils/css.html' }, + { text: 'Transitions', link: '/utils/transitions.html' }, + { text: 'DOM', link: '/utils/dom.html' }, + { text: 'Focus', link: '/utils/focus.html' }, + { text: 'Scroll', link: '/utils/scroll.html' }, + { text: 'History', link: '/utils/history.html' }, + { text: 'Loading', link: '/utils/load.html' }, + { text: 'Timing', link: '/utils/timing.html' }, + { text: 'Objects & random', link: '/utils/objects.html' }, + ]; +} diff --git a/packages/v4/docs/.vitepress/theme/components/Loader.vue b/packages/v4/docs/.vitepress/theme/components/Loader.vue new file mode 100644 index 000000000..68076ea6b --- /dev/null +++ b/packages/v4/docs/.vitepress/theme/components/Loader.vue @@ -0,0 +1,31 @@ + diff --git a/packages/v4/docs/.vitepress/theme/components/PreviewIframe.vue b/packages/v4/docs/.vitepress/theme/components/PreviewIframe.vue new file mode 100644 index 000000000..3270cfebd --- /dev/null +++ b/packages/v4/docs/.vitepress/theme/components/PreviewIframe.vue @@ -0,0 +1,67 @@ + + +