├── .eslintrc ├── .github └── workflows │ ├── node.js.yml │ └── release.yml ├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── index.html ├── netlify.toml ├── package.json ├── prerender.js ├── renovate.json ├── server.js ├── src ├── App.jsx ├── AutoRouting.jsx ├── components │ ├── Counter.jsx │ ├── Header.jsx │ ├── Nav.jsx │ └── Wrapper.jsx ├── entry-client.jsx ├── entry-server.jsx ├── index.css ├── store │ └── root.js └── views │ ├── About.jsx │ ├── Home.jsx │ ├── NotFound.jsx │ ├── example │ ├── Index.jsx │ ├── Page.jsx │ └── [id].jsx │ └── hello │ └── index.mdx ├── tailwind.config.js ├── tests ├── main.js └── setup │ └── env.js ├── vite.config.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:valtio/recommended", "plugin:react/recommended"], 3 | "rules": { 4 | "valtio/state-snapshot-rule": "warn", 5 | "react/prop-types": 0 6 | }, 7 | "settings": { 8 | "react": { 9 | "version": "detect" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | branches: [main] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14.x, 15.x] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v2 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: yarn 28 | - run: yarn format 29 | - run: yarn test 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - master 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version: 14 20 | - name: Install dependencies 21 | run: yarn 22 | - name: Release 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 26 | run: npx semantic-release 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | coverage -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .github 3 | CONTRIBUTING.md 4 | README.md 5 | *.css -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.1](https://github.com/thelinuxlich/react-modern-starter/compare/v1.0.0...v1.0.1) (2021-04-08) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add semantic-release github plugin ([4a40c90](https://github.com/thelinuxlich/react-modern-starter/commit/4a40c905c662eade574605091adc57c6ab4b8d83)) 7 | 8 | # 1.0.0 (2021-04-08) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * add semantic-release plugins ([f61cc6b](https://github.com/thelinuxlich/react-modern-starter/commit/f61cc6b14096bd594520e504d4ceec73a2a483aa)) 14 | * change semantic-release to use yarn ([5bbcabc](https://github.com/thelinuxlich/react-modern-starter/commit/5bbcabc535ec9a9134930d964d27600a9ca21e6b)) 15 | * **deps:** update dependency valtio to v1 ([360ce8d](https://github.com/thelinuxlich/react-modern-starter/commit/360ce8d0fe9f4c6683fe6344ede0add565690a15)) 16 | * **deps:** update dependency valtio to v1.0.1 ([622243d](https://github.com/thelinuxlich/react-modern-starter/commit/622243d459a530efcebb44ca17a838c3eff1a42d)) 17 | * **deps:** update dependency valtio to v1.0.2 ([7722504](https://github.com/thelinuxlich/react-modern-starter/commit/7722504d8f06fe62cca07308b91195c88bfa97e5)) 18 | * **deps:** update dependency valtio to v1.0.3 ([cd76f17](https://github.com/thelinuxlich/react-modern-starter/commit/cd76f1731bdd5322f2be7a965898f1469d4d1373)) 19 | * **deps:** update react monorepo to v17.0.2 ([75279ae](https://github.com/thelinuxlich/react-modern-starter/commit/75279aed6772bb94715ed493db1b0c21c8763227)) 20 | * add publishConfig ([ddedba1](https://github.com/thelinuxlich/react-modern-starter/commit/ddedba1390ce73744a95aa5e160fa66d73943283)) 21 | * add tailwind typography to mdx file ([bf9eb25](https://github.com/thelinuxlich/react-modern-starter/commit/bf9eb254750b7e51d66876c4bcba321284bbc5b4)) 22 | * don't need the bumper ([ea593b7](https://github.com/thelinuxlich/react-modern-starter/commit/ea593b72fed3721be1d24122ce510eed1a70d14c)) 23 | * finally the hooks are working again, phew! ([f1da5f1](https://github.com/thelinuxlich/react-modern-starter/commit/f1da5f1b34bad2944ca93e61e3f8dcf0e85d14fa)) 24 | * fix format task ([0b4aadd](https://github.com/thelinuxlich/react-modern-starter/commit/0b4aaddccdd568505b3bbc03ce2ab860760fea6a)) 25 | * fix lint-staged to ignore mdx ([ba85a90](https://github.com/thelinuxlich/react-modern-starter/commit/ba85a90146d8b0055da2ad51d211c75a69136a86)) 26 | * fix some configs ([e2cdd4e](https://github.com/thelinuxlich/react-modern-starter/commit/e2cdd4eafd1f23144919c93290c06cc2bf4db7f9)) 27 | * fix vite-plugin-babel-macros failing ([dd1c5fa](https://github.com/thelinuxlich/react-modern-starter/commit/dd1c5faf99f1435f253bd9894fc358079acb331d)) 28 | * format task ignores mdx now ([d311c03](https://github.com/thelinuxlich/react-modern-starter/commit/d311c0332981e7f2b9b5443a6e3bef898b28c845)) 29 | * mDX! ([38da73a](https://github.com/thelinuxlich/react-modern-starter/commit/38da73a36da4cc5a5bc8dadd0c6a83c4000bf0ad)) 30 | * now using state as snapshot ([e5be838](https://github.com/thelinuxlich/react-modern-starter/commit/e5be83823cf880989b85f2c68e2a4618b3948b04)) 31 | * skip git hooks for release ([0d36146](https://github.com/thelinuxlich/react-modern-starter/commit/0d36146cd0517e4f4f0e7aa9cf0b19e626190ae7)) 32 | * skip hooks on release ([db28ded](https://github.com/thelinuxlich/react-modern-starter/commit/db28ded9a2e2ef52135ca90c75e9e02b99d1d243)) 33 | * small fix in package.json ([09522a3](https://github.com/thelinuxlich/react-modern-starter/commit/09522a3952475a34ca1b52d8835efc42978e62c8)) 34 | * some files were missing ([19e53bc](https://github.com/thelinuxlich/react-modern-starter/commit/19e53bcd4b89cda2e09979398beb1320292d9c88)) 35 | * update yarn lock ([788624c](https://github.com/thelinuxlich/react-modern-starter/commit/788624cdbb616b0c8c7ac41df719abe2f493ae83)) 36 | * **deps:** update dependency valtio to v0.7.1 ([ed4b750](https://github.com/thelinuxlich/react-modern-starter/commit/ed4b750db2d99fcef69fd8c2d266286e94e05305)) 37 | * **deps:** update dependency valtio to v0.8.0 ([8252f59](https://github.com/thelinuxlich/react-modern-starter/commit/8252f59f990d41c241927d10f7f27778360436b7)) 38 | * **deps:** update dependency valtio to v0.8.1 ([b598230](https://github.com/thelinuxlich/react-modern-starter/commit/b5982306fd3ea66a7e6fa0e4173cf5fd7f0cf649)) 39 | * **deps:** update dependency valtio to v0.8.2 ([5d9101b](https://github.com/thelinuxlich/react-modern-starter/commit/5d9101b1645bf4e3c0ddfff08b39286deb178d28)) 40 | * update version ([9c2b080](https://github.com/thelinuxlich/react-modern-starter/commit/9c2b080b85aaa4eb639c34c15fc84d4eab6fc223)) 41 | * **readme.md:** add git-notify to the README ([b013c9a](https://github.com/thelinuxlich/react-modern-starter/commit/b013c9abbb85a8f51d47fd7426115736360ee83b)) 42 | 43 | 44 | ### Features 45 | 46 | * add release bumper to package.json ([e29283b](https://github.com/thelinuxlich/react-modern-starter/commit/e29283be83530407143edb95d3c0949885867ac4)) 47 | * add semantic-release to the project ([884f277](https://github.com/thelinuxlich/react-modern-starter/commit/884f27704d39a14de939182d897504b457ee8346)) 48 | * added valtio/macro ([ba402f1](https://github.com/thelinuxlich/react-modern-starter/commit/ba402f1044472a5c019b69d0992bb961a6f6d2d1)) 49 | * just fixing the module format required by tailwind config ([5cb1902](https://github.com/thelinuxlich/react-modern-starter/commit/5cb19029ac52d36020f5c082d515d951088c51b4)) 50 | * mDX will help us write markdown files with React components ([84c89c6](https://github.com/thelinuxlich/react-modern-starter/commit/84c89c603c4b260ee30755f2d344774535f7a4e7)) 51 | * mDX! ([a456382](https://github.com/thelinuxlich/react-modern-starter/commit/a456382457df1ae665a9180144a95155f195c65f)) 52 | * release-it will enable a nice release task for us ([a9839c3](https://github.com/thelinuxlich/react-modern-starter/commit/a9839c3475adba6ec3bdd5a61f072f46280cc931)) 53 | * replace release-it with semantic-release for automated releases ([385c7a8](https://github.com/thelinuxlich/react-modern-starter/commit/385c7a8fe60e60717f2377932fa4e186021af99c)) 54 | * shortcut for imports ([cdb3e96](https://github.com/thelinuxlich/react-modern-starter/commit/cdb3e96e920a641b37625523e8ac5f56c8a42f63)) 55 | * test ([4b618ab](https://github.com/thelinuxlich/react-modern-starter/commit/4b618ab1156df82ee34930554504ea14d36ef565)) 56 | * typography will be nice with MDX and forms is a must-have ([a0c1d26](https://github.com/thelinuxlich/react-modern-starter/commit/a0c1d269aee33bb7e7be92c31a66a1acef98f7f2)) 57 | * update semantic-release to use the main branch ([400c3b8](https://github.com/thelinuxlich/react-modern-starter/commit/400c3b8a7e3fb648980c5a35d53673c393d48bbb)) 58 | * **component:** add git-standup and its task ([de0b7a8](https://github.com/thelinuxlich/react-modern-starter/commit/de0b7a8fed6db0fcb5bc9136d50fa7b21fac6272)) 59 | * **component:** now with git-notify to communicate important updates to your team ([9272f95](https://github.com/thelinuxlich/react-modern-starter/commit/9272f95125591795d50ac0bc3b35a650edded329)) 60 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alisson Cavalcante Agiani 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 | ![Node.js CI](https://github.com/thelinuxlich/react-modern-starter/workflows/Node.js%20CI/badge.svg)[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)[![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com/)    2 | 3 | # React Modern Starter 4 | 5 | - [React](https://reactjs.org/docs/getting-started.html) for rendering 6 | - [React Router](https://reactrouter.com/web/guides/quick-start) for routing 7 | - [react-seo](https://github.com/americanexpress/react-seo) for SEO 8 | - [MDX](https://mdxjs.com/) for easy markdown files powered by React Components 9 | - [Valtio](https://github.com/pmndrs/valtio) for state management 10 | - [WindyCSS](https://github.com/voorjaar/windicss/wiki/Introduction) for easy bundling TailwindCSS into the stack(typography and forms plugin builtin) 11 | - [Vite](https://vitejs.dev/guide/) for bundling 12 | - [uvu](https://github.com/lukeed/uvu) for testing 13 | - [linkedom](https://github.com/WebReflection/linkedom) for speedy simulation of the browser environment in uvu tests 14 | - [C8](https://github.com/bcoe/c8) for code coverage 15 | - [Prettier-Standard](https://github.com/sheerun/prettier-standard) for formatting and linting 16 | - [lint-staged](https://github.com/okonet/lint-staged) with precommit task for linting 17 | - [Commitizen](https://github.com/commitizen/cz-cli) intercepting your commits to help you add nice formatted messages 18 | - [Renovate](https://github.com/renovatebot/renovate) for automated dependency updates 19 | - [git-notify](https://github.com/jevakallio/git-notify) for communicating important updates during git pull to your team 20 | - [git-standup](https://github.com/kamranahmedse/git-standup) to recall what you did yesterday 21 | - [semantic-release](https://github.com/semantic-release/semantic-release) to automatically release new versions of your project(remember to set NPM_TOKEN in your repository) 22 | - Github Actions CI preconfigured for running lint + tests 23 | - SSR/SSG builtin 24 | - Rudimentary autorouting based on directory convention(similar to Next.js) 25 | - Optional configuration for Netlify deployment 26 | 27 | ## Setup 28 | 29 | - Clone to local 30 | - `cd react-modern-starter` 31 | - `yarn` 32 | 33 | ## Tasks 34 | 35 | - `yarn standup` (receive a pretty list of things you did on your last working day) 36 | - `yarn dev` (standard dev server) 37 | - `yarn format` (format and lint the codebase following prettier-standard rules) 38 | - `yarn build` (bundle client and server(SSR) for production) 39 | - `yarn build:client` (bundle client for production) 40 | - `yarn build:server` (bundle server(SSR) for production) 41 | - `yarn generate` (bundle static SSR/SSG) 42 | - `yarn serve` (preview the production bundle(SSR)) 43 | - `yarn test` (run the test suite and generate code coverage) 44 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Modern Starter 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build.environment] 2 | NPM_FLAGS = "--prefix=/dev/null" 3 | NODE_VERSION = "14" 4 | 5 | [build] 6 | publish = "dist/client" 7 | command = "yarn build:client" 8 | 9 | [[redirects]] 10 | from = "/*" 11 | to = "/index.html" 12 | status = 200 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-modern-starter", 3 | "version": "1.0.5", 4 | "license": "MIT", 5 | "scripts": { 6 | "standup": "git-standup", 7 | "dev": "vite --port 4000", 8 | "format": "prettier-standard --lint '**/*.{js,css,jsx}'", 9 | "build": "yarn build:client && yarn build:server", 10 | "build:client": "vite build --outDir dist/client", 11 | "build:server": "vite build --ssr src/entry-server.jsx --outDir dist/server", 12 | "generate": "vite build --outDir dist/static && yarn build:server && node prerender", 13 | "serve": "cross-env NODE_ENV=production SSR_PORT=4001 node -r esm server", 14 | "test": "c8 uvu tests -r esbuild-register -i setup" 15 | }, 16 | "lint-staged": { 17 | "*.{js,css,jsx}": "prettier-standard --lint" 18 | }, 19 | "publishConfig": { 20 | "registry": "https://registry.npmjs.org/" 21 | }, 22 | "dependencies": { 23 | "@americanexpress/react-seo": "1.4.2", 24 | "express": "4.17.1", 25 | "global": "4.4.0", 26 | "react": "17.0.2", 27 | "react-dom": "17.0.2", 28 | "react-router-dom": "5.3.0", 29 | "valtio": "1.2.4" 30 | }, 31 | "devDependencies": { 32 | "@babel/plugin-syntax-jsx": "7.14.5", 33 | "@mdx-js/mdx": "2.0.0-next.9", 34 | "@mdx-js/react": "2.0.0-next.9", 35 | "@semantic-release/changelog": "6.0.0", 36 | "@vitejs/plugin-react-refresh": "1.3.6", 37 | "@semantic-release/git": "10.0.0", 38 | "c8": "7.9.0", 39 | "compression": "1.7.4", 40 | "cross-env": "7.0.3", 41 | "cz-conventional-changelog": "3.3.0", 42 | "esbuild": "0.13.3", 43 | "esbuild-register": "3.0.0", 44 | "eslint": "7.32.0", 45 | "eslint-plugin-valtio": "0.4.1", 46 | "esm": "3.2.25", 47 | "git-notify": "0.2.3", 48 | "git-standup": "2.3.2", 49 | "husky": "7.0.2", 50 | "linkedom": "0.12.1", 51 | "lint-staged": "11.1.2", 52 | "prettier": "2.4.1", 53 | "prettier-standard": "16.4.1", 54 | "serve-static": "1.14.1", 55 | "uvu": "0.5.1", 56 | "vite": "2.6.2", 57 | "vite-plugin-babel-macros": "1.0.5", 58 | "vite-plugin-mdx": "3.5.6", 59 | "vite-plugin-ssr": "0.3.3", 60 | "vite-plugin-windicss": "1.4.8" 61 | }, 62 | "husky": { 63 | "hooks": { 64 | "pre-commit": "lint-staged", 65 | "prepare-commit-msg": "exec < /dev/tty && git cz --hook || true", 66 | "post-merge": "git-notify merge $HUSKY_GIT_PARAMS", 67 | "post-rewrite": "git-notify rewrite $HUSKY_GIT_PARAMS", 68 | "post-checkout": "git-notify checkout $HUSKY_GIT_PARAMS" 69 | } 70 | }, 71 | "config": { 72 | "commitizen": { 73 | "path": "./node_modules/cz-conventional-changelog" 74 | } 75 | }, 76 | "release": { 77 | "branches": [ 78 | "master", 79 | "main" 80 | ], 81 | "plugins": [ 82 | "@semantic-release/commit-analyzer", 83 | "@semantic-release/release-notes-generator", 84 | "@semantic-release/changelog", 85 | "@semantic-release/npm", 86 | "@semantic-release/github", 87 | [ 88 | "@semantic-release/git", 89 | { 90 | "assets": [ 91 | "package.json" 92 | ], 93 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 94 | } 95 | ] 96 | ] 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /prerender.js: -------------------------------------------------------------------------------- 1 | // Pre-render the app into static HTML. 2 | // run `yarn generate` and then `dist/static` can be served as a static site. 3 | 4 | const fs = require('fs') 5 | const path = require('path') 6 | 7 | const toAbsolute = p => path.resolve(__dirname, p) 8 | 9 | const template = fs.readFileSync(toAbsolute('dist/static/index.html'), 'utf-8') 10 | const { render } = require('./dist/server/entry-server.js') 11 | 12 | // determine routes to pre-render from src/views 13 | const routesToPrerender = fs.readdirSync(toAbsolute('src/views')).map(file => { 14 | const name = file.replace(/\.jsx$/, '').toLowerCase() 15 | return name === 'home' ? `/` : `/${name}` 16 | }) 17 | 18 | ;(async () => { 19 | // pre-render each route... 20 | for (const url of routesToPrerender) { 21 | const context = {} 22 | const appHtml = await render(url, context) 23 | 24 | const html = template.replace(``, appHtml) 25 | 26 | const filePath = `dist/static${url === '/' ? '/index' : url}.html` 27 | fs.writeFileSync(toAbsolute(filePath), html) 28 | console.log('pre-rendered:', filePath) 29 | } 30 | })() 31 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"] 3 | } 4 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const fs = require('fs') 3 | const path = require('path') 4 | const express = require('express') 5 | 6 | const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD 7 | 8 | async function createServer ( 9 | root = process.cwd(), 10 | isProd = process.env.NODE_ENV === 'production' 11 | ) { 12 | const resolve = p => path.resolve(__dirname, p) 13 | 14 | const indexProd = isProd 15 | ? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8') 16 | : '' 17 | 18 | const app = express() 19 | 20 | /** 21 | * @type {import('vite').ViteDevServer} 22 | */ 23 | let vite 24 | if (!isProd) { 25 | vite = await require('vite').createServer({ 26 | root, 27 | logLevel: isTest ? 'error' : 'info', 28 | server: { 29 | middlewareMode: true 30 | } 31 | }) 32 | // use vite's connect instance as middleware 33 | app.use(vite.middlewares) 34 | } else { 35 | app.use(require('compression')()) 36 | app.use( 37 | require('serve-static')(resolve('dist/client'), { 38 | index: false 39 | }) 40 | ) 41 | } 42 | 43 | app.use('*', async (req, res) => { 44 | try { 45 | const url = req.originalUrl 46 | 47 | let template, render 48 | if (!isProd) { 49 | // always read fresh template in dev 50 | template = fs.readFileSync(resolve('index.html'), 'utf-8') 51 | template = await vite.transformIndexHtml(url, template) 52 | render = (await vite.ssrLoadModule('/src/entry-server.jsx')).render 53 | } else { 54 | template = indexProd 55 | render = require('./dist/server/entry-server.js').render 56 | } 57 | 58 | const context = {} 59 | const appHtml = render(url, context) 60 | 61 | if (context.url) { 62 | // Somewhere a `` was rendered 63 | return res.redirect(301, context.url) 64 | } 65 | 66 | const html = template.replace(``, appHtml) 67 | 68 | res 69 | .status(200) 70 | .set({ 'Content-Type': 'text/html' }) 71 | .end(html) 72 | } catch (e) { 73 | !isProd && vite.ssrFixStacktrace(e) 74 | console.log(e.stack) 75 | res.status(500).end(e.stack) 76 | } 77 | }) 78 | 79 | return { app, vite } 80 | } 81 | 82 | if (!isTest) { 83 | createServer().then(({ app }) => 84 | app.listen(process.env.SSR_PORT, () => { 85 | console.log('http://localhost:' + process.env.SSR_PORT) 86 | }) 87 | ) 88 | } 89 | 90 | // for test use 91 | exports.createServer = createServer 92 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { Switch } from 'react-router-dom' 2 | import React from 'react' 3 | import Seo from '@americanexpress/react-seo' 4 | import 'virtual:windi.css' 5 | import './index.css' 6 | 7 | import routes from './AutoRouting' 8 | import Nav from './components/Nav' 9 | 10 | const App = () => { 11 | return ( 12 |
13 | 22 |
25 | ) 26 | } 27 | 28 | export default App 29 | -------------------------------------------------------------------------------- /src/AutoRouting.jsx: -------------------------------------------------------------------------------- 1 | import { Route } from 'react-router-dom' 2 | import React from 'react' 3 | const routes = [] 4 | 5 | // a poor man's autorouting based on file structure. Home and NotFound components convention-based 6 | const modules = import.meta.globEager('./views/**/*') 7 | let NotFound = null 8 | let Home = null 9 | for (const path in modules) { 10 | const Mod = modules[path].default 11 | let exactPath = path 12 | .replace('./views', '') 13 | .replace(/.(js|md)[x]?/, '') 14 | .toLowerCase() 15 | const _path = exactPath.split('/') 16 | const routeName = _path[_path.length - 1] 17 | if (_path.length === 2 && routeName === 'home') { 18 | Home = Mod 19 | } else if (_path.length === 2 && routeName === 'notfound') { 20 | NotFound = Mod 21 | } else if (_path.length > 2 && routeName === 'index') { 22 | exactPath = exactPath.replace('index', '') 23 | routes.push( 24 | 25 | ) 26 | } else if (_path.length > 2 && routeName === '[id]') { 27 | exactPath = exactPath.replace('[id]', '') 28 | routes.push( 29 | 30 | ) 31 | } else { 32 | routes.push( 33 | 34 | ) 35 | } 36 | } 37 | 38 | // first route should be the Home component, if it exists 39 | if (Home) { 40 | routes.unshift() 41 | } 42 | 43 | // last route should be the NotFound component, if it exists 44 | if (NotFound) { 45 | routes.push() 46 | } 47 | 48 | export default routes 49 | -------------------------------------------------------------------------------- /src/components/Counter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import state from '@/store/root' 3 | import { useSnapshot } from 'valtio' 4 | 5 | const Counter = () => { 6 | const snap = useSnapshot(state) 7 | return ( 8 | 14 | ) 15 | } 16 | 17 | export default Counter 18 | -------------------------------------------------------------------------------- /src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Header = ({ title }) => { 4 | return ( 5 |
6 |
7 |

8 | {title} 9 |

10 |
11 |
12 | ) 13 | } 14 | 15 | export default Header 16 | -------------------------------------------------------------------------------- /src/components/Nav.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import state from '../store/root' 3 | import { useSnapshot } from 'valtio' 4 | import { Link, useLocation } from 'react-router-dom' 5 | 6 | const Nav = () => { 7 | const location = useLocation() 8 | const snap = useSnapshot(state) 9 | 10 | const links = [ 11 | { text: 'Home', to: '/' }, 12 | { text: 'About', to: '/about' }, 13 | { text: 'Hello MDX', to: '/hello' } 14 | ] 15 | 16 | const activeClass = 'text-white bg-gray-900' 17 | const inactiveClass = 'text-gray-300 hover:text-white hover:bg-gray-700' 18 | 19 | return ( 20 | 220 | ) 221 | } 222 | 223 | export default Nav 224 | -------------------------------------------------------------------------------- /src/components/Wrapper.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Wrapper = ({ children, ...otherProps }) => ( 4 |
8 | {children} 9 |
10 | ) 11 | 12 | export default Wrapper 13 | -------------------------------------------------------------------------------- /src/entry-client.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom' 2 | import React from 'react' 3 | import { BrowserRouter } from 'react-router-dom' 4 | import App from './App' 5 | 6 | ReactDOM.hydrate( 7 | 8 | 9 | , 10 | document.getElementById('app') 11 | ) 12 | -------------------------------------------------------------------------------- /src/entry-server.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOMServer from 'react-dom/server' 3 | import { StaticRouter } from 'react-router-dom' 4 | import App from './App' 5 | 6 | export function render (url, context) { 7 | return ReactDOMServer.renderToString( 8 | 9 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* any default global css goes here */ 2 | -------------------------------------------------------------------------------- /src/store/root.js: -------------------------------------------------------------------------------- 1 | import { proxy } from 'valtio' 2 | 3 | const state = proxy({ 4 | showProfileMenu: false, 5 | showMenu: false, 6 | counter: 0 7 | }) 8 | 9 | export default state 10 | -------------------------------------------------------------------------------- /src/views/About.jsx: -------------------------------------------------------------------------------- 1 | import Header from '@/components/Header' 2 | import React from 'react' 3 | 4 | const About = () => { 5 | return ( 6 | <> 7 |
8 |
9 |
10 |
11 |
12 |
13 |

14 | This demo is using  15 |
16 | 22 | Tailwind CSS 23 | 24 |

25 |

26 | All the code present here is part of a free sample from  27 | 31 | Tailwind UI 32 | 33 |  but, you don't need it. 34 |

35 |
36 | 44 |
45 |
46 |
47 |
48 |
49 |
50 | 51 | ) 52 | } 53 | 54 | export default About 55 | -------------------------------------------------------------------------------- /src/views/Home.jsx: -------------------------------------------------------------------------------- 1 | import Header from '@/components/Header' 2 | import React from 'react' 3 | 4 | const Home = () => { 5 | return ( 6 | <> 7 |
8 |
9 |
10 | {/* Replace with your content */} 11 |
12 |
13 | Here goes your content. You can also go the About page. 14 |
15 |
16 | {/* /End replace */} 17 |
18 |
19 | 20 | ) 21 | } 22 | 23 | export default Home 24 | -------------------------------------------------------------------------------- /src/views/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const NotFound = () => { 4 | return
Not found
5 | } 6 | 7 | export default NotFound 8 | -------------------------------------------------------------------------------- /src/views/example/Index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Page = () => { 4 | return
Example index
5 | } 6 | 7 | export default Page 8 | -------------------------------------------------------------------------------- /src/views/example/Page.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Page = () => { 4 | return
Example page
5 | } 6 | 7 | export default Page 8 | -------------------------------------------------------------------------------- /src/views/example/[id].jsx: -------------------------------------------------------------------------------- 1 | import { useParams } from 'react-router-dom' 2 | import React from 'react' 3 | 4 | const Page = () => { 5 | const { id } = useParams() 6 | return
Example page ID: {id}
7 | } 8 | 9 | export default Page 10 | -------------------------------------------------------------------------------- /src/views/hello/index.mdx: -------------------------------------------------------------------------------- 1 | import Counter from '@/components/Counter' 2 | import Wrapper from '@/components/Wrapper' 3 | 4 | 5 | ### Hello 6 | 7 | This text is written in Markdown. 8 | 9 | MDX allows Rich React components to be used directly in Markdown: 10 | 11 | Edit `Counter.jsx` or `Hello.mdx` and save to experience HMR updates. 12 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const typography = require('windicss/plugin/typography') 2 | const forms = require('windicss/plugin/forms') 3 | 4 | module.exports = { 5 | darkMode: 'class', 6 | plugins: [typography, forms] 7 | } 8 | -------------------------------------------------------------------------------- /tests/main.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu' 2 | import * as assert from 'uvu/assert' 3 | import * as ENV from './setup/env' 4 | 5 | import Header from '../src/components/Header.jsx' 6 | 7 | test.before(ENV.setup) 8 | test.before.each(ENV.reset) 9 | 10 | test('should render the header with the correct title', () => { 11 | const { container } = ENV.render(Header, { title: 'Welcome!' }) 12 | assert.snapshot( 13 | container.innerHTML, 14 | '

Welcome!

' 15 | ) 16 | }) 17 | 18 | test.run() 19 | -------------------------------------------------------------------------------- /tests/setup/env.js: -------------------------------------------------------------------------------- 1 | import { parseHTML } from 'linkedom' 2 | import * as React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import { act } from 'react-dom/test-utils' 5 | 6 | const { window, document, Event } = parseHTML('
') 7 | 8 | export function setup () { 9 | global.window = window 10 | global.document = document 11 | global.Event = Event 12 | } 13 | 14 | export function reset () { 15 | document.title = '' 16 | document.head.innerHTML = '' 17 | document.body.innerHTML = '
' 18 | } 19 | 20 | export function render (Tag, props = {}) { 21 | try { 22 | const container = document.querySelector('#main') 23 | const component = React.createElement(Tag, props) 24 | ReactDOM.render(component, container) 25 | return { container, component } 26 | } catch (e) { 27 | console.log(e) 28 | } 29 | } 30 | 31 | export async function fire (elem, event, details) { 32 | await act(() => { 33 | const evt = new Event(event, details) 34 | elem.dispatchEvent(evt) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import reactRefresh from '@vitejs/plugin-react-refresh' 2 | import WindiCSS from 'vite-plugin-windicss' 3 | import macrosPlugin from 'vite-plugin-babel-macros' 4 | import path from 'path' 5 | import mdx from 'vite-plugin-mdx' 6 | 7 | export default { 8 | plugins: [ 9 | WindiCSS({ 10 | safelist: 'prose prose-sm m-auto' 11 | }), 12 | reactRefresh(), 13 | mdx(), 14 | macrosPlugin() 15 | ], 16 | resolve: { 17 | alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }] 18 | }, 19 | ssr: { 20 | noExternal: ['@mdx-js/react'] 21 | } 22 | } 23 | --------------------------------------------------------------------------------