├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── config.yml
└── workflows
│ └── main.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── .vscode
├── extensions.json
└── settings.json
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── postcss.config.cjs
├── public
├── img
│ └── pwa
│ │ ├── icons-192.png
│ │ └── icons-512.png
├── manifest.webmanifest
└── sw.js
├── src
├── client
│ └── home.hydrate.tsx
├── colors.scss
├── components
│ ├── LazyExample
│ │ ├── LazyExample.client.module.scss
│ │ ├── LazyExample.client.scss
│ │ └── LazyExample.tsx
│ ├── TodoList.scss
│ └── TodoList.tsx
├── layouts
│ ├── MainLayout.scss
│ └── MainLayout.tsx
├── pages
│ ├── AboutPage.tsx
│ └── HomePage.tsx
├── server
│ ├── makeHtml.tsx
│ └── server.tsx
└── styles.scss
├── tsconfig.json
├── typings
└── modules.d.ts
└── webpack
├── const.cjs
├── webpack.client.common.cjs
├── webpack.client.dev.cjs
├── webpack.client.prod.cjs
├── webpack.common.cjs
├── webpack.server.cjs
└── webpack.server.prod.cjs
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: yandeu
2 | open_collective: yandeu
3 | patreon: yandeu
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Have a question?**
14 | Join the [discussions](https://github.com/nanojsx/nano/discussions) instead.
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: Ask a question
4 | url: https://github.com/nanojsx/nano/discussions
5 | about: Ask questions and discuss with other community members
6 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # read: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
2 |
3 | name: CI
4 |
5 | on:
6 | push:
7 | paths:
8 | - '.github/**'
9 | - 'src/**'
10 | - 'test/**'
11 | - 'webpack/**'
12 | pull_request:
13 | paths:
14 | - '.github/**'
15 | - 'src/**'
16 | - 'test/**'
17 | - 'webpack/**'
18 |
19 | jobs:
20 | build:
21 | runs-on: ubuntu-latest
22 |
23 | strategy:
24 | matrix:
25 | node-version: [16.x, 18.x]
26 |
27 | steps:
28 | - name: Checkout repository
29 | uses: actions/checkout@v3
30 |
31 | - name: Use Node.js ${{ matrix.node-version }}
32 | uses: actions/setup-node@v3
33 | with:
34 | node-version: ${{ matrix.node-version }}
35 |
36 | - name: Install Dependencies
37 | run: npm install
38 |
39 | - name: Build Packages
40 | run: npm run build
41 |
42 | - name: Run Prettier
43 | run: npm run format
44 |
45 | # - name: Run ESLint
46 | # run: npm run lint
47 |
48 | # - name: Upload coverage to Codecov
49 | # uses: codecov/codecov-action@v2
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | /node_modules
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | "@yandeu/prettier-config"
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["esbenp.prettier-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules\\typescript\\lib"
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Yannick Deubel (https://github.com/yandeu); Project Url: https://github.com/nanojsx/template
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nano JSX Template
2 |
3 | A [Nano JSX](https://nanojsx.github.io/) Template for building Isomorphic JSX Apps.
4 |
5 | ## Download
6 |
7 | ```bash
8 | # download
9 | npx degit nanojsx/template nano
10 |
11 | # directory
12 | cd nano
13 |
14 | # install
15 | npm i
16 |
17 | # development (on http://localhost:3000/)
18 | npm start
19 |
20 | # production
21 | npm run build
22 |
23 | # serve (on http://localhost:3000/)
24 | npm run serve
25 | ```
26 |
27 | ## Structure
28 |
29 | ```bash
30 | root
31 | ├── public # all your static files
32 | ├── src
33 | │ ├── client # bundles for hydration
34 | │ ├── components # your custom components
35 | │ ├── layouts # your app's layouts
36 | │ ├── pages # your pages
37 | │ └── server # all server-side code
38 | ```
39 |
40 | Every file in `/client` will be bundles separately.
41 |
42 | ## TODOs
43 |
44 | All the things below will hopefully be implemented soon.
45 |
46 | - Auto refresh browser on changes
47 | - Improve Service Worker cache strategy
48 | - Pre-Render to static HTML
49 |
50 | ## LazyLoading ChunkLoadError on localhost
51 |
52 | On localhost you may experience the following error when LazyLoading while switching routes:
53 |
54 | ```
55 | Uncaught (in promise) ChunkLoadError: Loading chunk
56 | ```
57 |
58 | This is related to the disabled browser option:
59 |
60 | ```
61 | Allow invalid certificates for resources loaded from localhost.
62 | ```
63 |
64 | **Fix for Google Chrome**
65 |
66 | In the Chrome address bar, type [chrome://flags/#allow-insecure-localhost](chrome://flags/#allow-insecure-localhost) and **enable** the option.
67 |
68 | **Fix for Firefox**
69 |
70 | No supported option to disable for localhost only.
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nano-jsx-template",
3 | "version": "0.0.8",
4 | "description": "Nano JSX Template using Isomorphic JSX.",
5 | "scripts": {
6 | "start": "npm run clean && npm run dev",
7 | "serve": "node dist/server.bundle.js",
8 | "build": "npm run clean && npm-run-all --parallel prod:*",
9 | "dev": "webpack --config webpack/webpack.client.dev.cjs && webpack --config webpack/webpack.server.cjs && npm-run-all --parallel dev:*",
10 | "dev:nodemon": "nodemon --watch src --ext css,scss,sass,js,ts,tsx,webmanifest --watch dist dist/server.bundle.js",
11 | "dev:webpack-client": "webpack --config webpack/webpack.client.dev.cjs --watch",
12 | "dev:webpack-server": "webpack --config webpack/webpack.server.cjs --watch",
13 | "prod:webpack-client": "webpack --config webpack/webpack.client.prod.cjs",
14 | "prod:webpack-server": "webpack --config webpack/webpack.server.prod.cjs",
15 | "postinstall": "npm run build",
16 | "clean": "rimraf dist",
17 | "format:check": "prettier --check src/**/*",
18 | "format": "prettier --write src/**/*"
19 | },
20 | "keywords": [],
21 | "author": "Yannick Deubel (https://github.com/yandeu)",
22 | "license": "MIT",
23 | "engines": {
24 | "node": ">=14"
25 | },
26 | "dependencies": {
27 | "compression": "^1.7.4",
28 | "express": "^4.17.2",
29 | "nano-jsx": "^0.0.34"
30 | },
31 | "devDependencies": {
32 | "@types/compression": "^1.7.2",
33 | "@types/node-fetch": "^2.5.8",
34 | "@yandeu/prettier-config": "^0.0.3",
35 | "autoprefixer": "^10.4.0",
36 | "copy-webpack-plugin": "^11.0.0",
37 | "css-loader": "^6.5.1",
38 | "nodemon": "^2.0.15",
39 | "npm-run-all": "^4.1.5",
40 | "null-loader": "^4.0.1",
41 | "postcss-loader": "^7.0.1",
42 | "prettier": "^2.5.1",
43 | "resolve-typescript-plugin": "^1.2.0",
44 | "rimraf": "^3.0.2",
45 | "sass": "^1.45.1",
46 | "sass-loader": "^13.1.0",
47 | "style-loader": "^3.3.1",
48 | "ts-loader": "^9.2.6",
49 | "typescript": "^4.8.4",
50 | "webpack": "^5.65.0",
51 | "webpack-cli": "^4.9.1",
52 | "webpack-manifest-plugin": "^5.0.0",
53 | "webpack-merge": "^5.8.0",
54 | "webpack-node-externals": "^3.0.0"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require('autoprefixer')]
3 | }
4 |
--------------------------------------------------------------------------------
/public/img/pwa/icons-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanojsx/template/94dcbabafe23a0c676a410a3a3d7313f563bdf88/public/img/pwa/icons-192.png
--------------------------------------------------------------------------------
/public/img/pwa/icons-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanojsx/template/94dcbabafe23a0c676a410a3a3d7313f563bdf88/public/img/pwa/icons-512.png
--------------------------------------------------------------------------------
/public/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Nano JSX",
3 | "name": "Nano JSX Application",
4 | "description": "A simple PWA using nano-jsx",
5 | "icons": [
6 | {
7 | "src": "/public/img/pwa/icons-192.png",
8 | "type": "image/png",
9 | "sizes": "192x192"
10 | },
11 | {
12 | "src": "/public/img/pwa/icons-512.png",
13 | "type": "image/png",
14 | "sizes": "512x512"
15 | }
16 | ],
17 | "start_url": "/",
18 | "background_color": "#3367D6",
19 | "display": "standalone",
20 | "scope": "/",
21 | "theme_color": "#3367D6"
22 | }
23 |
--------------------------------------------------------------------------------
/public/sw.js:
--------------------------------------------------------------------------------
1 | importScripts(
2 | 'https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js'
3 | )
4 |
5 | const { registerRoute } = workbox.routing
6 | const { CacheFirst, StaleWhileRevalidate } = workbox.strategies
7 | const { ExpirationPlugin } = workbox.expiration
8 |
9 | registerRoute(
10 | ({ request }) => request.destination === 'style',
11 | new StaleWhileRevalidate({
12 | cacheName: 'css-cache'
13 | })
14 | )
15 |
16 | registerRoute(
17 | ({ request }) => request.destination === 'document',
18 | new StaleWhileRevalidate({
19 | cacheName: 'document-cache'
20 | })
21 | )
22 |
23 | registerRoute(
24 | ({ request }) => request.destination === 'script',
25 | new StaleWhileRevalidate({
26 | cacheName: 'script-cache'
27 | })
28 | )
29 |
30 | registerRoute(
31 | // Cache image files.
32 | ({ request }) => request.destination === 'image',
33 | // Use the cache if it's available.
34 | new CacheFirst({
35 | // Use a custom cache name.
36 | cacheName: 'image-cache',
37 | plugins: [
38 | new ExpirationPlugin({
39 | // Cache only 20 images.
40 | maxEntries: 20,
41 | // Cache for a maximum of a week.
42 | maxAgeSeconds: 7 * 24 * 60 * 60
43 | })
44 | ]
45 | })
46 | )
47 |
--------------------------------------------------------------------------------
/src/client/home.hydrate.tsx:
--------------------------------------------------------------------------------
1 | import { h, hydrate } from 'nano-jsx/lib/core.js'
2 | import { printVersion } from 'nano-jsx/lib/helpers.js'
3 | import TodoList from '../components/TodoList.js'
4 |
5 | const main = async () => {
6 | hydrate(, document.getElementById('todo-list'))
7 |
8 | // example of a lazy loaded module
9 | window.addEventListener(
10 | 'click',
11 | () =>
12 | import('../components/LazyExample/LazyExample.js').then(({ default: LazyComponent }) => {
13 | const html = hydrate()
14 | const homePage = document.getElementById('homePage')
15 | homePage?.appendChild(html)
16 | }),
17 | { once: true }
18 | )
19 |
20 | // print the nano-jsx version in the console
21 | printVersion()
22 | }
23 |
24 | main()
25 |
--------------------------------------------------------------------------------
/src/colors.scss:
--------------------------------------------------------------------------------
1 | $padding: 1em 2em;
2 | $primary-color: #ff4e6a;
3 | $font: #363636;
4 |
--------------------------------------------------------------------------------
/src/components/LazyExample/LazyExample.client.module.scss:
--------------------------------------------------------------------------------
1 | .lazy-example {
2 | margin-top: 3em;
3 | border: 1px red solid;
4 |
5 | i {
6 | color: #2e19ab;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/LazyExample/LazyExample.client.scss:
--------------------------------------------------------------------------------
1 | #lazy-example {
2 | p {
3 | color: blue;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/LazyExample/LazyExample.tsx:
--------------------------------------------------------------------------------
1 | import { h } from 'nano-jsx/lib/core.js'
2 |
3 | // you don't need "withStyles" if you include your css in the client bundle
4 | // import { withStyles } from 'nano-jsx/lib/withStyles'
5 |
6 | // import normal styling (ending with .client.scss)
7 | import './LazyExample.client.scss'
8 |
9 | // import css modules styling (ending with .client.module.scss)
10 | import className from './LazyExample.client.module.scss'
11 |
12 | // see home.hydrate.tsx to see how to lazy load (code splitting with dynamic Imports) this component
13 | const LazyExample = () => {
14 | console.log('I am LazyExample')
15 | return (
16 |
17 |
18 | LazyExample
19 |
20 |
21 |
22 | This text and its styling are lazy loaded (in another .js file) and will appear once you click on the page.
23 |
24 |
25 |