├── .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 |
--------------------------------------------------------------------------------