├── .eslintignore ├── .eslintrc ├── .github ├── release-drafter.yml └── workflows │ ├── main.yml │ ├── publish.yml │ └── release-drafter.yml ├── .gitignore ├── .lefthook └── commit-msg │ └── commit_check ├── .prettierignore ├── .prettierrc.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── demo ├── app │ ├── index.tsx │ └── routing.tsx ├── index.html ├── main.tsx ├── pages │ ├── home │ │ └── index.tsx │ ├── not-found │ │ └── index.tsx │ ├── posts-list │ │ └── index.tsx │ └── posts-single │ │ └── index.tsx ├── shared │ ├── api │ │ └── posts.ts │ └── ui │ │ └── layout.tsx └── vite-env.d.ts ├── lefthook.yml ├── package.json ├── pnpm-lock.yaml ├── release.config.js ├── rollup.config.js ├── src ├── create-is-opened.ts ├── create-route-view.tsx ├── create-routes-view.tsx ├── index.ts ├── link.tsx ├── route.tsx └── router-provider.tsx ├── tsconfig.eslint.json ├── tsconfig.json └── vite.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | **/node_modules/** 3 | build/** 4 | *.json 5 | !/src/**/package.json 6 | *.md 7 | *.less 8 | *.svg 9 | git 10 | **/dist/**/*.js 11 | vendored/** 12 | dist 13 | node_modules 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "plugin:@typescript-eslint/recommended", 5 | "prettier-standard" 6 | ], 7 | "parserOptions": { 8 | "project": "tsconfig.eslint.json", 9 | "sourceType": "module", 10 | "ecmaFeatures": { 11 | "jsx": true 12 | } 13 | }, 14 | "plugins": [ 15 | "@typescript-eslint", 16 | "prettier", 17 | "simple-import-sort", 18 | "import" 19 | ], 20 | "env": { 21 | "browser": true, 22 | "es6": true, 23 | "node": true, 24 | "jest": true 25 | }, 26 | "rules": { 27 | "camelcase": "off", 28 | "no-use-before-define": "off", 29 | "no-lone-blocks": "off", 30 | "prettier/prettier": [ 31 | "error", 32 | { 33 | "endOfLine": "auto" 34 | } 35 | ], 36 | "simple-import-sort/exports": "error", 37 | "simple-import-sort/imports": [ 38 | "error", 39 | { 40 | "groups": [ 41 | // Node.js builtins. You could also generate this regex if you use a `.js` config. 42 | // For example: `^(${require("module").builtinModules.join("|")})(/|$)` 43 | [ 44 | "^node:(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)" 45 | ], 46 | // Packages 47 | ["^\\w"], 48 | // Internal packages. 49 | ["^(@|config/)(/*|$)"], 50 | // Side effect imports. 51 | ["^\\u0000"], 52 | // Parent imports. Put `..` last. 53 | ["^\\.\\.(?!/?$)", "^\\.\\./?$"], 54 | // Other relative imports. Put same-folder imports and `.` last. 55 | ["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"], 56 | // Style imports. 57 | ["^.+\\.s?css$"] 58 | ] 59 | } 60 | ], 61 | "import/no-anonymous-default-export": [ 62 | "error", 63 | { 64 | "allowArrowFunction": true, 65 | "allowAnonymousFunction": true 66 | } 67 | ], 68 | "no-unused-vars": "off", 69 | "prefer-const": "off", 70 | "@typescript-eslint/ban-ts-comment": "off", // отключили до разбирательства с типами (есть туду) 71 | "@typescript-eslint/no-explicit-any": "off", // пока что отключил, чтоб не падал деплой 72 | "@typescript-eslint/triple-slash-reference": "off", 73 | "@typescript-eslint/explicit-module-boundary-types": "off", 74 | "@typescript-eslint/no-unused-vars": [ 75 | "warn", 76 | { 77 | "argsIgnorePattern": "^_", 78 | "varsIgnorePattern": "^_", 79 | "caughtErrorsIgnorePattern": "^_" 80 | } 81 | ], 82 | "import/no-named-default": "off", 83 | "no-implicit-globals": "error", 84 | "max-len": [ 85 | "warn", 86 | { 87 | "code": 140, 88 | "ignoreComments": true 89 | } 90 | ], 91 | "no-useless-escape": "off", 92 | "no-unmodified-loop-condition": "off", 93 | "import/export": "off" 94 | }, 95 | "overrides": [ 96 | { 97 | "files": ["src/**/*.js", "src/**/*.ts", "src/**/*.tsx"], 98 | "rules": { 99 | "import/newline-after-import": "error", 100 | "import/no-internal-modules": ["off"], 101 | "import/order": [ 102 | "error", 103 | { 104 | "newlines-between": "always", 105 | "groups": [["builtin", "external", "internal"]], 106 | "pathGroupsExcludedImportTypes": [["builtin", "external", "internal"]] 107 | } 108 | ] 109 | } 110 | } 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | categories: 2 | - title: '⚠️ Breaking changes' 3 | label: 'BREAKING CHANGES' 4 | 5 | - title: '🚀 Features' 6 | labels: 7 | - 'feature' 8 | - 'enhancement' 9 | 10 | - title: '🐛 Bug Fixes' 11 | labels: 12 | - 'fix' 13 | - 'bugfix' 14 | - 'bug' 15 | 16 | - title: '🧰 Maintenance' 17 | labels: 18 | - 'chore' 19 | - 'dependencies' 20 | 21 | - title: '📚 Documentation' 22 | label: 'documentation' 23 | 24 | - title: '🧪 Tests' 25 | label: 'tests' 26 | 27 | - title: '🏎 Optimizations' 28 | label: 'optimizations' 29 | 30 | version-resolver: 31 | major: 32 | labels: 33 | - 'BREAKING CHANGES' 34 | minor: 35 | labels: 36 | - 'feature' 37 | - 'enhancement' 38 | patch: 39 | labels: 40 | - 'fix' 41 | default: patch 42 | 43 | name-template: 'v$RESOLVED_VERSION' 44 | tag-template: 'v$RESOLVED_VERSION' 45 | 46 | change-template: '- $TITLE #$NUMBER (@$AUTHOR)' 47 | template: | 48 | $CHANGES 49 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node v${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['16.x', '18.x', '20.x'] 11 | os: [ubuntu-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup pnpm 18 | uses: pnpm/action-setup@v2 19 | 20 | - name: Use Node ${{ matrix.node }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node }} 24 | cache: 'pnpm' 25 | 26 | - name: Install deps 27 | run: pnpm install 28 | 29 | - name: Lint 30 | run: pnpm lint 31 | 32 | - name: Build 33 | run: pnpm build 34 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish CI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish-to-npm: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | 14 | - name: Setup pnpm 15 | uses: pnpm/action-setup@v2 16 | 17 | - name: Use Node.js 20.x 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 20.x 21 | cache: 'pnpm' 22 | 23 | - name: Install dependencies 24 | run: pnpm install --frozen-lockfile 25 | 26 | - name: Build package 27 | run: pnpm build 28 | 29 | - name: Extract version from release 30 | id: version 31 | uses: olegtarasov/get-tag@v2.1 32 | with: 33 | tagRegex: 'v(.*)' 34 | 35 | - name: Set version from release 36 | uses: reedyuk/npm-version@1.1.1 37 | with: 38 | version: ${{ steps.version.outputs.tag }} 39 | git-tag-version: 'false' 40 | 41 | - name: Create NPM config 42 | run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN 43 | env: 44 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 45 | 46 | - name: Check for NEXT tag 47 | id: next 48 | uses: actions-ecosystem/action-regex-match@v2 49 | with: 50 | text: ${{ steps.version.outputs.tag }} 51 | regex: '-next' 52 | 53 | - name: Check for RC tag 54 | id: rc 55 | uses: actions-ecosystem/action-regex-match@v2 56 | with: 57 | text: ${{ steps.version.outputs.tag }} 58 | regex: '-rc' 59 | 60 | - name: Publish atomic-router@${{ steps.version.outputs.tag }} with NEXT tag 61 | if: ${{ steps.next.outputs.match != '' }} 62 | run: npm publish --tag next 63 | 64 | - name: Publish atomic-router@${{ steps.version.outputs.tag }} with RC tag 65 | if: ${{ steps.rc.outputs.match != '' }} 66 | run: npm publish --tag rc 67 | 68 | - name: Publish atomic-router@${{ steps.version.outputs.tag }} to latest 69 | if: ${{ steps.next.outputs.match == '' }} 70 | run: npm publish 71 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: release-drafter/release-drafter@v5 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /.lefthook/commit-msg/commit_check: -------------------------------------------------------------------------------- 1 | if ! pnpm commitlint --edit --verbose; then 2 | exit 1 3 | fi 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | pnpm-lock.yaml 3 | vendored/** 4 | node_modules -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const prettierConfigStandard = require('prettier-config-standard'); 2 | 3 | const modifiedConfig = { 4 | ...prettierConfigStandard, 5 | semi: true, 6 | parser: 'typescript', 7 | singleQuote: true, 8 | trailingComma: 'es5', 9 | proseWrap: 'never', 10 | arrowParens: 'always', 11 | tabWidth: 2, 12 | }; 13 | 14 | module.exports = modifiedConfig; 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.5.0-dev.1 (2022-07-29) 2 | 3 | 4 | ### Features 5 | 6 | * initial ([dd6957b](https://github.com/Drevoed/atomic-router-solid/commit/dd6957bec22e30089d3a5af2fda50361227c147c)) 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Release process 2 | 3 | 1. Check out the [draft release](https://github.com/atomic-router/solid/releases). 4 | 1. All PRs should have correct labels and useful titles. You can [review available labels here](https://github.com/atomic-router/solid/blob/master/.github/release-drafter.yml). 5 | 1. Update labels for PRs and titles, next [manually run the release drafter action](https://github.com/atomic-router/solid/actions/workflows/release-drafter.yml) to regenerate the draft release. 6 | 1. Review the new version and press "Publish" 7 | 1. If required check "Create discussion for this release" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kirill Mironov 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Atomic-router-solid 2 | 3 | SolidJS bindings for [atomic-router](https://github.com/atomic-router/atomic-router) 4 | 5 | > ❗️ **Attention**: At the moment atomic-router team collecting issues and feature requests to redesign and release update. Use current version of atomic-router on your own risk. We are going to write migration guide when/if the release will contain breaking changes. Thank you for reporting issues 🧡 6 | 7 | ## Installation 8 | 9 | Install core and solid bindings: 10 | 11 | ```bash 12 | pnpm i atomic-router atomic-router-solid 13 | ``` 14 | 15 | Don't forget about peer dependencies, if you haven't installed them yet: 16 | 17 | ```bash 18 | pnpm i effector effector-solid solid-js 19 | ``` 20 | 21 | ## Documentation 22 | 23 | Please, open [official documentation for the bindings](https://atomic-router.github.io/solidjs/installation.html). 24 | 25 | All types is built from the [source code](https://github.com/atomic-router/solid/tree/main/src). 26 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /demo/app/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoutesView, Route, RouterProvider } from 'atomic-router-solid'; 2 | 3 | import { HomePage } from '../pages/home'; 4 | import { NotFound } from '../pages/not-found'; 5 | import { PostsList } from '../pages/posts-list'; 6 | import { PostsSingle } from '../pages/posts-single'; 7 | import { Layout } from '../shared/ui/layout'; 8 | 9 | import { router } from './routing'; 10 | 11 | const RoutesView = createRoutesView({ 12 | routes: [ 13 | { route: HomePage.route, view: HomePage.Page, layout: Layout }, 14 | { route: PostsList.route, view: PostsList.Page, layout: Layout }, 15 | { route: PostsSingle.route, view: PostsSingle.Page }, 16 | ], 17 | otherwise: NotFound.Page, 18 | }); 19 | 20 | export const App = () => { 21 | return ( 22 | 23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /demo/app/routing.tsx: -------------------------------------------------------------------------------- 1 | import { createHistoryRouter } from 'atomic-router'; 2 | import { sample } from 'effector'; 3 | import { createBrowserHistory } from 'history'; 4 | 5 | import { HomePage } from '../pages/home'; 6 | import { NotFound } from '../pages/not-found'; 7 | import { PostsList } from '../pages/posts-list'; 8 | import { PostsSingle } from '../pages/posts-single'; 9 | 10 | export const routes = [ 11 | { path: '/', route: HomePage.route }, 12 | { path: '/posts', route: PostsList.route }, 13 | { path: '/posts/:slug', route: PostsSingle.route }, 14 | { path: '/404', route: NotFound.route }, 15 | ]; 16 | 17 | export const history = createBrowserHistory(); 18 | 19 | export const router = createHistoryRouter({ 20 | routes, 21 | notFoundRoute: NotFound.route, 22 | }); 23 | 24 | router.setHistory(history); 25 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /demo/main.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'solid-js/web'; 2 | 3 | import { App } from './app'; 4 | 5 | render(() => , document.getElementById('root') as HTMLElement); 6 | -------------------------------------------------------------------------------- /demo/pages/home/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoute } from 'atomic-router'; 2 | import { Link } from 'atomic-router-solid'; 3 | 4 | import { PostsList } from '../posts-list'; 5 | 6 | const route = createRoute(); 7 | 8 | const Page = () => { 9 | return ( 10 |
11 |

This is home page

12 | Go to posts 13 |
14 |
15 | Non-existing page 16 |
17 | ); 18 | }; 19 | 20 | export const HomePage = { 21 | route, 22 | Page, 23 | }; 24 | -------------------------------------------------------------------------------- /demo/pages/not-found/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoute } from 'atomic-router'; 2 | import { Link } from 'atomic-router-solid'; 3 | 4 | import { HomePage } from '../home'; 5 | 6 | const route = createRoute(); 7 | 8 | const Page = () => { 9 | return ( 10 |
11 |

Not found

12 | Back to home 13 |
14 | ); 15 | }; 16 | 17 | export const NotFound = { 18 | route, 19 | Page, 20 | }; 21 | -------------------------------------------------------------------------------- /demo/pages/posts-list/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoute } from 'atomic-router'; 2 | import { Link } from 'atomic-router-solid'; 3 | import { attach, restore, sample } from 'effector'; 4 | import { useUnit } from 'effector-solid'; 5 | import { For, Show } from 'solid-js/web'; 6 | 7 | import { PostsApi } from '../../shared/api/posts'; 8 | import { PostsSingle } from '../posts-single'; 9 | 10 | const route = createRoute(); 11 | 12 | const getPostsFx = attach({ 13 | effect: PostsApi.getPostsFx, 14 | }); 15 | 16 | const $posts = restore(getPostsFx, []); 17 | 18 | sample({ 19 | clock: route.opened, 20 | target: getPostsFx, 21 | }); 22 | 23 | const Page = () => { 24 | return ( 25 |
26 |

Latest posts

27 | 28 |
29 | ); 30 | }; 31 | 32 | const Posts = () => { 33 | const pending = useUnit(getPostsFx.pending); 34 | 35 | return ( 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | const List = () => { 43 | const posts = useUnit($posts); 44 | 45 | return ( 46 | 47 | {(post) => ( 48 |
49 |

{post.title}

50 | 51 | Go to post 52 | 53 |
54 | )} 55 |
56 | ); 57 | }; 58 | 59 | export const PostsList = { 60 | route, 61 | Page, 62 | }; 63 | -------------------------------------------------------------------------------- /demo/pages/posts-single/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoute } from 'atomic-router'; 2 | import { attach, restore, sample } from 'effector'; 3 | import { useUnit } from 'effector-solid'; 4 | import { Show } from 'solid-js/web'; 5 | 6 | import { PostsApi } from '../../shared/api/posts'; 7 | 8 | const route = createRoute<{ slug: string }>(); 9 | 10 | const getPostFx = attach({ 11 | effect: PostsApi.getPostFx, 12 | }); 13 | 14 | const $post = restore(getPostFx, null); 15 | 16 | sample({ 17 | clock: route.opened, 18 | fn: ({ params }) => params.slug, 19 | target: getPostFx, 20 | }); 21 | 22 | const Page = () => { 23 | const pending = useUnit(getPostFx.pending); 24 | 25 | return ( 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | const Post = () => { 33 | const post = useUnit($post); 34 | 35 | return ( 36 | 37 | {(post) => ( 38 |
39 |

{post.title}

40 |

{post.text}

41 |
42 | )} 43 |
44 | ); 45 | }; 46 | 47 | export const PostsSingle = { 48 | route, 49 | Page, 50 | }; 51 | -------------------------------------------------------------------------------- /demo/shared/api/posts.ts: -------------------------------------------------------------------------------- 1 | import { createEffect } from 'effector'; 2 | 3 | const slugs = ['foo', 'bar', 'baz']; 4 | 5 | const posts = { 6 | foo: { slug: 'foo', title: 'Foo post', text: 'Hoho you found me!' }, 7 | bar: { slug: 'bar', title: 'Bar post', text: 'Hoho you found me!' }, 8 | baz: { slug: 'baz', title: 'Baz post', text: 'Hoho you found me!' }, 9 | } as const; 10 | 11 | type Post = typeof posts[keyof typeof posts]; 12 | 13 | const getPostsFx = createEffect(() => { 14 | return new Promise((resolve) => 15 | setTimeout( 16 | resolve, 17 | 1000, 18 | slugs.map((slug) => posts[slug]) 19 | ) 20 | ); 21 | }); 22 | 23 | const getPostFx = createEffect((slug: string) => { 24 | return new Promise((resolve, reject) => 25 | setTimeout(() => { 26 | if (slug in posts) { 27 | return resolve(posts[slug]); 28 | } 29 | reject(new Error()); 30 | }, 1000) 31 | ); 32 | }); 33 | 34 | export const PostsApi = { 35 | getPostsFx, 36 | getPostFx, 37 | }; 38 | -------------------------------------------------------------------------------- /demo/shared/ui/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ParentProps } from 'solid-js'; 2 | 3 | export function Layout(props: ParentProps) { 4 | return ( 5 |
6 | Layout! 7 | {props.children} 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /demo/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | prettier-js: 5 | glob: "*.{ts,js,tsx}" 6 | run: pnpm prettier --write {staged_files} 7 | lint: 8 | exclude: ".prettierrc.js|commitlint.config.js" 9 | glob: "*.{ts,tsx}" 10 | run: pnpm eslint --fix {staged_files} 11 | 12 | commit-msg: 13 | scripts: 14 | 'commit_check': 15 | runner: bash 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atomic-router-solid", 3 | "version": "0.0.0-this-version-will-be-set-from-ci", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/Drevoed/atomic-router-solid.git" 7 | }, 8 | "maintainers": [ 9 | "Kirill Mironov" 10 | ], 11 | "packageManager": "pnpm@8.11.0", 12 | "description": "", 13 | "main": "dist/cjs/index.js", 14 | "browser": "dist/esm/index.js", 15 | "module": "dist/esm/index.js", 16 | "types": "dist/index.d.ts", 17 | "files": [ 18 | "dist" 19 | ], 20 | "exports": { 21 | ".": { 22 | "import": "./dist/esm/index.js", 23 | "require": "./dist/cjs/index.js", 24 | "node": "./dist/cjs/index.js", 25 | "default": "./dist/esm/index.js", 26 | "types": "./dist/index.d.ts" 27 | } 28 | }, 29 | "scripts": { 30 | "dev": "vite", 31 | "prettier": "prettier --write ./**/*.{js,ts,tsx}", 32 | "prettier:json": "prettier --write --parser json ./**/*.json", 33 | "lint": "eslint --fix src/**/*.{ts,tsx}", 34 | "build": "NODE_ENV=production rollup -c rollup.config.js" 35 | }, 36 | "keywords": [ 37 | "router", 38 | "solid-js", 39 | "solid", 40 | "routinf" 41 | ], 42 | "author": { 43 | "name": "Kirill Mironov", 44 | "email": "vetrokm@gmail.com" 45 | }, 46 | "license": "MIT", 47 | "devDependencies": { 48 | "@babel/core": "^7.19.1", 49 | "@babel/preset-typescript": "^7.18.6", 50 | "@commitlint/cli": "~17.1.2", 51 | "@commitlint/config-conventional": "~17.1.0", 52 | "@rollup/plugin-babel": "^5.3.1", 53 | "@rollup/plugin-commonjs": "^22.0.2", 54 | "@rollup/plugin-node-resolve": "^14.1.0", 55 | "@semantic-release/changelog": "~6.0.1", 56 | "@semantic-release/git": "~10.0.1", 57 | "@swc/core": "^1.3.3", 58 | "@types/babel__core": "^7.1.19", 59 | "@types/node": "^18.7.21", 60 | "@typescript-eslint/eslint-plugin": "~5.38.0", 61 | "@typescript-eslint/parser": "~5.38.0", 62 | "atomic-router": "^0.9.0", 63 | "babel-preset-solid": "^1.5.6", 64 | "commitizen": "~4.2.5", 65 | "commitlint": "^18.4.3", 66 | "cz-conventional-changelog": "~3.3.0", 67 | "effector": "~23.0.0", 68 | "effector-solid": "~0.23.0", 69 | "eslint": "~8.24.0", 70 | "eslint-config-prettier": "~8.5.0", 71 | "eslint-config-prettier-standard": "~4.0.1", 72 | "eslint-config-standard": "~17.0.0", 73 | "eslint-plugin-import": "~2.26.0", 74 | "eslint-plugin-jsx-a11y": "~6.6.1", 75 | "eslint-plugin-n": "~15.3.0", 76 | "eslint-plugin-node": "~11.1.0", 77 | "eslint-plugin-prettier": "~4.2.1", 78 | "eslint-plugin-promise": "~6.0.1", 79 | "eslint-plugin-simple-import-sort": "~8.0.0", 80 | "history": "~5.3.0", 81 | "lefthook": "~1.5.4", 82 | "lint-staged": "~13.0.3", 83 | "prettier": "~2.7.1", 84 | "prettier-config-standard": "~5.0.0", 85 | "rollup": "^2.79.1", 86 | "rollup-plugin-dts": "^4.2.2", 87 | "rollup-plugin-swc3": "^0.6.0", 88 | "rollup-plugin-typescript2": "^0.34.0", 89 | "semantic-release": "~19.0.5", 90 | "solid-js": "~1.5.6", 91 | "typescript": "~4.9.5", 92 | "vite": "~3.1.3", 93 | "vite-plugin-solid": "~2.3.8", 94 | "vitest": "~0.23.4" 95 | }, 96 | "peerDependencies": { 97 | "atomic-router": "^0.9.0", 98 | "effector": "^23", 99 | "effector-solid": "^23", 100 | "history": "~5", 101 | "solid-js": "^1.4 || ^1.5" 102 | }, 103 | "dependencies": { 104 | "classcat": "~5.0.4" 105 | }, 106 | "config": { 107 | "commitizen": { 108 | "path": "./node_modules/cz-conventional-changelog" 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | '@semantic-release/commit-analyzer', 4 | '@semantic-release/release-notes-generator', 5 | '@semantic-release/npm', 6 | '@semantic-release/github', 7 | [ 8 | '@semantic-release/changelog', 9 | { 10 | changelogFile: 'CHANGELOG.md', 11 | }, 12 | ], 13 | [ 14 | '@semantic-release/git', 15 | { 16 | assets: ['CHANGELOG.md'], 17 | }, 18 | ], 19 | ], 20 | branches: [ 21 | '+([0-9])?(.{+([0-9]),x}).x', 22 | 'main', 23 | 'next', 24 | 'next-major', 25 | { name: 'beta', prerelease: true }, 26 | { name: 'alpha', prerelease: true }, 27 | ], 28 | }; 29 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rollup'; 2 | import pkg from './package.json'; 3 | import dts from 'rollup-plugin-dts'; 4 | import resolve from '@rollup/plugin-node-resolve'; 5 | import { swc } from 'rollup-plugin-swc3'; 6 | import babel from '@rollup/plugin-babel'; 7 | import { DEFAULT_EXTENSIONS } from '@babel/core'; 8 | 9 | export default defineConfig([ 10 | { 11 | input: 'src/index.ts', 12 | plugins: [ 13 | resolve(), 14 | babel({ 15 | presets: ['@babel/preset-typescript', 'babel-preset-solid'], 16 | extensions: [...DEFAULT_EXTENSIONS, '.ts', '.tsx'], 17 | include: ['src/**'], 18 | }), 19 | swc({ 20 | minify: process.env.NODE_ENV === 'production', 21 | }), 22 | ], 23 | external: [...Object.keys(pkg.peerDependencies), 'solid-js/web'], 24 | output: [ 25 | { 26 | file: 'dist/cjs/index.js', 27 | format: 'cjs', 28 | }, 29 | { 30 | file: 'dist/esm/index.js', 31 | format: 'esm', 32 | }, 33 | ], 34 | }, 35 | { 36 | input: 'src/index.ts', 37 | output: { 38 | file: 'dist/index.d.ts', 39 | }, 40 | plugins: [resolve(), dts()], 41 | }, 42 | ]); 43 | -------------------------------------------------------------------------------- /src/create-is-opened.ts: -------------------------------------------------------------------------------- 1 | import type { RouteInstance } from 'atomic-router'; 2 | import { useUnit } from 'effector-solid'; 3 | import { createMemo } from 'solid-js'; 4 | 5 | export function createIsOpened( 6 | route: RouteInstance | RouteInstance[] 7 | ) { 8 | return createMemo(() => { 9 | if (Array.isArray(route)) { 10 | const allRoutes = useUnit(route.map((route) => route.$isOpened)); 11 | return allRoutes.some((r) => r()); 12 | } 13 | 14 | return useUnit(route.$isOpened)(); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/create-route-view.tsx: -------------------------------------------------------------------------------- 1 | import type { RouteInstance, RouteParams } from 'atomic-router'; 2 | import type { Component } from 'solid-js'; 3 | import { Match, mergeProps, Switch } from 'solid-js'; 4 | import { Dynamic } from 'solid-js/web'; 5 | 6 | import { createIsOpened } from './create-is-opened'; 7 | 8 | export interface RouteViewConfig { 9 | route: RouteInstance | RouteInstance[]; 10 | view: Component; 11 | otherwise?: Component; 12 | } 13 | 14 | export function createRouteView< 15 | Props, 16 | Params extends RouteParams, 17 | Config extends { 18 | [key in keyof RouteViewConfig]?: RouteViewConfig< 19 | Props, 20 | Params 21 | >[key]; 22 | } 23 | >(config: Config) { 24 | return ( 25 | props: Props & Omit, keyof Config> 26 | ) => { 27 | const mergedConfig = mergeProps(config, props) as RouteViewConfig< 28 | Props, 29 | Params 30 | >; 31 | const isOpened = createIsOpened(mergedConfig.route); 32 | 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/create-routes-view.tsx: -------------------------------------------------------------------------------- 1 | import type { RouteInstance, RouteParams } from 'atomic-router'; 2 | import { 3 | Component, 4 | createMemo, 5 | JSXElement, 6 | Match, 7 | mergeProps, 8 | Switch, 9 | } from 'solid-js'; 10 | import { Dynamic, For, Show } from 'solid-js/web'; 11 | 12 | import { createIsOpened } from './create-is-opened'; 13 | 14 | interface RouteRecord { 15 | route: RouteInstance | RouteInstance[]; 16 | view: Component; 17 | layout?: Component<{ children: JSXElement }>; 18 | } 19 | 20 | export interface RoutesViewConfig { 21 | routes: RouteRecord[]; 22 | otherwise?: Component; 23 | } 24 | 25 | export function createRoutesView( 26 | config: Config 27 | ) { 28 | return (props: Omit) => { 29 | const mergedConfig = mergeProps(config, props) as Config; 30 | const routes = createMemo(() => 31 | mergedConfig.routes.map((routeRecord) => { 32 | const isOpened = createIsOpened(routeRecord.route); 33 | return { 34 | ...routeRecord, 35 | get isOpened() { 36 | return isOpened(); 37 | }, 38 | }; 39 | }) 40 | ); 41 | 42 | return ( 43 | 44 | 45 | {(route) => ( 46 | 47 | }> 51 | {(Layout) => ( 52 | 53 | 54 | 55 | )} 56 | 57 | 58 | )} 59 | 60 | 61 | 62 | 63 | 64 | 65 | ); 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-route-view'; 2 | export * from './create-routes-view'; 3 | export * from './link'; 4 | export * from './route'; 5 | export * from './router-provider'; 6 | -------------------------------------------------------------------------------- /src/link.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | buildPath, 3 | RouteInstance, 4 | RouteParams, 5 | RouteQuery, 6 | } from 'atomic-router'; 7 | import cc from 'classcat'; 8 | import { useUnit } from 'effector-solid'; 9 | import { createMemo, JSX, mergeProps, Show, splitProps } from 'solid-js'; 10 | 11 | import { useRouter } from './router-provider'; 12 | 13 | export type LinkProps = { 14 | to: RouteInstance | string; 15 | params?: Params; 16 | query?: RouteQuery; 17 | activeClass?: string; 18 | inactiveClass?: string; 19 | } & Exclude, 'href'>; 20 | 21 | export function Link(props: LinkProps) { 22 | props = mergeProps({ activeClass: 'active' }, props); 23 | 24 | const toIsString = createMemo(() => typeof props.to === 'string'); 25 | 26 | const [routeProps, normalProps] = splitProps(props, [ 27 | 'to', 28 | 'params', 29 | 'query', 30 | 'class', 31 | 'activeClass', 32 | 'inactiveClass', 33 | 'href', 34 | ]); 35 | 36 | return ( 37 | } 44 | /> 45 | } 46 | keyed={false}> 47 | 52 | 53 | ); 54 | } 55 | 56 | function NormalLink( 57 | props: { class?: string } & JSX.AnchorHTMLAttributes 58 | ) { 59 | return ; 60 | } 61 | 62 | function RouteLink( 63 | props: { 64 | to: RouteInstance; 65 | params?: Params; 66 | query?: RouteQuery; 67 | class?: string; 68 | activeClass?: string; 69 | inactiveClass?: string; 70 | } & JSX.AnchorHTMLAttributes 71 | ) { 72 | props = mergeProps( 73 | { 74 | activeClass: 'active', 75 | }, 76 | props 77 | ); 78 | 79 | const [split, rest] = splitProps(props, [ 80 | 'to', 81 | 'params', 82 | 'query', 83 | 'class', 84 | 'activeClass', 85 | 'inactiveClass', 86 | 'onClick', 87 | ]); 88 | const router = useRouter(); 89 | const routeObj = router.routes.find( 90 | (routeObj) => routeObj.route === split.to 91 | ); 92 | 93 | if (!routeObj) { 94 | throw new Error('[RouteLink] Route not found'); 95 | } 96 | 97 | const isOpened = useUnit(routeObj.route.$isOpened); 98 | 99 | const href = createMemo(() => 100 | buildPath({ 101 | pathCreator: routeObj.path, 102 | params: split.params || {}, 103 | query: split.query || {}, 104 | }) 105 | ); 106 | 107 | const classes = createMemo(() => { 108 | const combined = cc([ 109 | split.class, 110 | isOpened() ? split.activeClass : split.inactiveClass, 111 | ]); 112 | 113 | return combined === '' ? undefined : combined; 114 | }); 115 | 116 | return ( 117 | { 121 | evt.preventDefault(); 122 | split.to.navigate({ 123 | params: props.params || ({} as Params), 124 | query: props.query || {}, 125 | }); 126 | if (split.onClick) { 127 | if (typeof split.onClick === 'function') { 128 | split.onClick(evt); 129 | } else { 130 | split.onClick[0](evt, split.onClick[1]); 131 | } 132 | } 133 | }} 134 | {...rest}> 135 | {props.children} 136 | 137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /src/route.tsx: -------------------------------------------------------------------------------- 1 | import type { RouteInstance, RouteParams } from 'atomic-router'; 2 | import type { Component } from 'solid-js'; 3 | import { Show } from 'solid-js'; 4 | import { Dynamic } from 'solid-js/web'; 5 | 6 | import { createIsOpened } from './create-is-opened'; 7 | 8 | type RouteProps = { 9 | route: RouteInstance | RouteInstance[]; 10 | view: Component; 11 | }; 12 | 13 | export function Route(props: RouteProps) { 14 | const isOpened = createIsOpened(props.route); 15 | 16 | return ( 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/router-provider.tsx: -------------------------------------------------------------------------------- 1 | import type { createHistoryRouter } from 'atomic-router'; 2 | import { createContext, JSX, useContext } from 'solid-js'; 3 | 4 | type Router = ReturnType; 5 | 6 | export const RouterContext = createContext(null); 7 | 8 | export function RouterProvider(props: { 9 | router: Router; 10 | children: JSX.Element; 11 | }) { 12 | return ( 13 | 14 | {props.children} 15 | 16 | ); 17 | } 18 | 19 | export function useRouter() { 20 | return useContext(RouterContext) as Router; 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": false, 5 | "paths": { 6 | "*": ["./*"] 7 | }, 8 | "types": ["node", "sync", "mocha"] 9 | }, 10 | "include": ["**/*.ts", "**/*.tsx"], 11 | "exclude": ["*.test.ts", "*.test.tsx", "*.spec.ts", "*.spec.tsx"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "declarationMap": true, 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "noImplicitAny": false, 7 | "allowJs": false, 8 | "skipLibCheck": false, 9 | "importsNotUsedAsValues": "error", 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "preserve", 21 | "jsxImportSource": "solid-js", 22 | "baseUrl": ".", 23 | "paths": { 24 | "effector": ["node_modules/effector"], 25 | "history": ["node_modules/history"], 26 | "atomic-router-solid": ["src"] 27 | }, 28 | "types": ["vite/client"] 29 | }, 30 | "include": ["src/**/*.ts", "src/**/*.tsx"], 31 | "exclude": ["*.test.ts", "*.test.tsx", "*.spec.ts", "*.spec.tsx"] 32 | } 33 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import solidPlugin from 'vite-plugin-solid'; 4 | 5 | export default defineConfig({ 6 | root: 'demo', 7 | plugins: [solidPlugin()], 8 | build: { 9 | target: 'esnext', 10 | }, 11 | resolve: { 12 | alias: { 13 | 'atomic-router-solid': path.resolve(__dirname, './src'), 14 | }, 15 | }, 16 | }); 17 | --------------------------------------------------------------------------------