├── .eslintrc
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── config.yml
├── config
│ ├── codeql.yml
│ └── snyk.yml
└── workflows
│ ├── browser.yml
│ ├── codeql.yml
│ ├── deno.yml
│ ├── eslint.yml
│ ├── nodejs.yml
│ ├── prettier.yml
│ └── snyk.yml
├── .gitignore
├── .npmignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── bundles
├── nano.core.min.js
├── nano.full.min.js
├── nano.slim.min.js
└── nano.ui.min.js
├── deno.dev.tsx
├── deno_lib
├── LICENSE
├── README.md
├── component.ts
├── components
│ ├── helmet.ts
│ ├── img.ts
│ ├── index.ts
│ ├── link.ts
│ ├── router.ts
│ ├── suspense.ts
│ └── visible.ts
├── context.ts
├── core.ts
├── customElementsMode.ts
├── fragment.ts
├── helpers.ts
├── hooks
│ ├── index.ts
│ └── useState.ts
├── htm.ts
├── htm
│ ├── LICENSE
│ ├── README.md
│ ├── build.ts
│ ├── constants.ts
│ └── index.ts
├── index.ts
├── jsx-runtime
│ └── index.ts
├── jsx.ts
├── lazy.ts
├── mod.ts
├── regexDom.ts
├── ssr.ts
├── state.ts
├── store.ts
├── types.ts
├── ui
│ ├── README.md
│ ├── _config.ts
│ ├── _helpers.ts
│ ├── appBar.ts
│ ├── banner.ts
│ ├── button.ts
│ ├── dialog.ts
│ ├── fab.ts
│ ├── icon.ts
│ ├── index.ts
│ ├── list.ts
│ ├── menu.ts
│ ├── navigation.ts
│ ├── sheet.ts
│ ├── snackbar.ts
│ ├── tabs.ts
│ └── toolbar.ts
├── version.ts
└── withStyles.ts
├── dev
├── bundled-core.html
├── bundled-dev.html
├── dev.html
├── font-awesome
│ ├── LICENSE
│ ├── ellipsis-v-solid.svg
│ ├── heart-solid.svg
│ ├── home-solid.svg
│ └── user-solid.svg
├── img
│ └── placeholder.png
└── ui.html
├── jest.config.json
├── package-lock.json
├── package.json
├── readme
├── logo-drawing.svg
├── nano-jsx-logo-512.png
├── nano-jsx-logo-dark.svg
├── nano-jsx-logo.png
├── nano-jsx-logo.svg
└── thumbnail.png
├── scripts
├── browserTest
│ ├── README.md
│ ├── index.mjs
│ ├── mime.mjs
│ ├── nyc.mjs
│ ├── requestListener.mjs
│ ├── tester.ts
│ └── tsconfig.json
└── packageType.mjs
├── src
├── bundles
│ ├── bundle.core.ts
│ ├── bundle.full.ts
│ ├── bundle.slim.ts
│ └── bundle.ui.ts
├── component.ts
├── components
│ ├── helmet.ts
│ ├── img.ts
│ ├── index.ts
│ ├── link.ts
│ ├── router.ts
│ ├── suspense.ts
│ └── visible.ts
├── context.ts
├── core.ts
├── customElementsMode.ts
├── dev
│ ├── components
│ │ └── isomorphic.tsx
│ ├── dev.tsx
│ └── devSSR.tsx
├── fragment.ts
├── helpers.ts
├── hooks
│ ├── index.ts
│ └── useState.ts
├── htm.ts
├── htm
│ ├── LICENSE
│ ├── README.md
│ ├── build.ts
│ ├── constants.ts
│ └── index.ts
├── index.ts
├── jsx-runtime
│ └── index.ts
├── jsx.ts
├── lazy.ts
├── mod.ts
├── regexDom.ts
├── ssr.ts
├── state.ts
├── store.ts
├── types.ts
├── ui
│ ├── README.md
│ ├── _config.ts
│ ├── _helpers.ts
│ ├── appBar.ts
│ ├── banner.ts
│ ├── button.ts
│ ├── dialog.ts
│ ├── fab.ts
│ ├── icon.ts
│ ├── index.ts
│ ├── list.ts
│ ├── menu.ts
│ ├── navigation.ts
│ ├── sheet.ts
│ ├── snackbar.ts
│ ├── tabs.ts
│ └── toolbar.ts
├── version.ts
└── withStyles.ts
├── test.jsx-runtime
└── nodejs
│ ├── client.test.tsx
│ └── server.tsx
├── test
├── browser
│ ├── className.html
│ ├── component.test.html
│ ├── dangerous.test.html
│ ├── fragment.test.html
│ ├── link.test.html
│ ├── router.test.html
│ ├── simple.test.html
│ ├── svg.test.html
│ ├── webComponent.html
│ └── withStyles.test.html
├── deno
│ ├── deno.test.tsx
│ ├── nanossr.test.tsx
│ └── svg.test.tsx
├── helpers.mjs
├── nodejs
│ ├── children.test.tsx
│ ├── className.test.tsx
│ ├── component.test.tsx
│ ├── components
│ │ ├── helmet.ssr.test.tsx
│ │ ├── helmet.test.tsx
│ │ ├── img.test.tsx
│ │ ├── link.test.tsx
│ │ └── visible.test.tsx
│ ├── context.ssr.test.tsx
│ ├── context.test.tsx
│ ├── customElementsMode.ssr.test.tsx
│ ├── customElementsMode.test.tsx
│ ├── e2e
│ │ ├── assets
│ │ │ ├── index.html
│ │ │ └── toggle.html
│ │ └── core.test.tsx
│ ├── events.ssr.test.tsx
│ ├── events.test.tsx
│ ├── exeptions.test.tsx
│ ├── fetchAPI.test.tsx
│ ├── fragment.test.tsx
│ ├── functionLess.test.tsx
│ ├── helpers.ts
│ ├── hooks
│ │ └── useState.test.tsx
│ ├── htm.test.ts
│ ├── hydrateLazy.test.tsx
│ ├── jsx-runtime.test.tsx
│ ├── lifecycles.test.tsx
│ ├── nestedRenderOutputs.test.tsx
│ ├── no-jsx.test.tsx
│ ├── parentElement.test.tsx
│ ├── partialHydration.test.tsx
│ ├── props.test.tsx
│ ├── ref.test.tsx
│ ├── regexDom.ssr.test.tsx
│ ├── renderNothing.test.tsx
│ ├── replaceParent.test.tsx
│ ├── router.ssr.test.tsx
│ ├── router.test.tsx
│ ├── ssr.test.tsx
│ ├── state.test.tsx
│ ├── store.ssr.test.tsx
│ ├── store.test.tsx
│ ├── stringArray.test.tsx
│ ├── style.test.tsx
│ ├── suspense.ssr.test.tsx
│ ├── suspense.test.tsx
│ ├── svg.ssr.test.tsx
│ ├── svg.test.tsx
│ └── withStyles.test.tsx
└── scripts.mjs
├── tsconfig.deno.json
├── tsconfig.esm.json
├── tsconfig.json
├── tsconfig.test.json
├── tsconfig.test.jsx-runtime.json
└── webpack
├── webpack.bundle.dev.js
├── webpack.bundle.instrumented.js
├── webpack.bundle.prod.js
└── webpack.dev.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "@yandeu/eslint-config",
4 | "prettier"
5 | ],
6 | "globals": {
7 | "globalThis": "off"
8 | },
9 | "rules": {
10 | "no-undef": "off",
11 | "sort-imports": "off"
12 | }
13 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: yandeu
2 |
--------------------------------------------------------------------------------
/.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/config/codeql.yml:
--------------------------------------------------------------------------------
1 | paths:
2 | - src
3 |
--------------------------------------------------------------------------------
/.github/config/snyk.yml:
--------------------------------------------------------------------------------
1 | paths:
2 | - src
3 |
--------------------------------------------------------------------------------
/.github/workflows/browser.yml:
--------------------------------------------------------------------------------
1 | # syntax: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
2 | name: Browser
3 |
4 | on:
5 | push:
6 | paths-ignore:
7 | - '**.md'
8 | - '.vscode/**'
9 | - 'bundles/**'
10 | - 'dev/**'
11 | - 'readme/**'
12 | pull_request:
13 | paths-ignore:
14 | - '**.md'
15 | - '.vscode/**'
16 | - 'bundles/**'
17 | - 'dev/**'
18 | - 'readme/**'
19 |
20 | jobs:
21 | build:
22 | runs-on: ubuntu-latest
23 |
24 | name: Browser
25 |
26 | steps:
27 | - name: Checkout Repository
28 | uses: actions/checkout@v4
29 |
30 | - name: NodeJS
31 | uses: actions/setup-node@v4
32 | with:
33 | node-version: '20'
34 |
35 | - name: Install
36 | run: |
37 | rm -f package.json
38 | rm -f package-lock.json
39 | npm init -y
40 | npm i -D rimraf typescript puppeteer
41 | npm i -D webpack webpack-cli ts-loader nyc coverage-istanbul-loader
42 |
43 | - name: Prepare
44 | run: |
45 | node_modules/.bin/rimraf .nyc_output coverage
46 | node_modules/.bin/tsc -p tsconfig.json
47 | node_modules/.bin/tsc -p scripts/browserTest/tsconfig.json
48 | node_modules/.bin/webpack -c webpack/webpack.bundle.instrumented.js
49 |
50 | - name: Test
51 | run: |
52 | node scripts/browserTest/index.mjs --coverage
53 |
54 | - name: Coverage
55 | run: |
56 | node_modules/.bin/nyc report --exclude "src/{bundles,dev,htm,ui}/**" --exclude "src/mod.ts" --reporter=text --reporter=html --reporter=lcov
57 |
58 | - name: Upload coverage to Codecov
59 | if: ${{ github.ref == 'refs/heads/master' }}
60 | uses: codecov/codecov-action@v2
61 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: 'CodeQL'
2 |
3 | on:
4 | push:
5 | paths:
6 | - 'src/**'
7 | - '.github/**'
8 | pull_request:
9 | paths:
10 | - 'src/**'
11 | - '.github/**'
12 |
13 | jobs:
14 | analyze:
15 | name: Analyze
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - name: Checkout repository
20 | uses: actions/checkout@v4
21 |
22 | - name: Initialize CodeQL
23 | uses: github/codeql-action/init@v2
24 | with:
25 | languages: typescript
26 | config-file: ./.github/config/codeql.yml
27 |
28 | - name: Perform CodeQL Analysis
29 | uses: github/codeql-action/analyze@v2
30 |
--------------------------------------------------------------------------------
/.github/workflows/deno.yml:
--------------------------------------------------------------------------------
1 | # syntax: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
2 | name: Deno
3 |
4 | on:
5 | push:
6 | paths-ignore:
7 | - '**.md'
8 | - '.vscode/**'
9 | - 'bundles/**'
10 | - 'dev/**'
11 | - 'readme/**'
12 | pull_request:
13 | paths-ignore:
14 | - '**.md'
15 | - '.vscode/**'
16 | - 'bundles/**'
17 | - 'dev/**'
18 | - 'readme/**'
19 |
20 | jobs:
21 | build:
22 | runs-on: ubuntu-latest
23 |
24 | strategy:
25 | matrix:
26 | deno: ['v1.x']
27 | node-version: ['20.x']
28 |
29 | name: Deno ${{ matrix.deno }}
30 |
31 | steps:
32 | - name: Checkout repository
33 | uses: actions/checkout@v4
34 |
35 | # install node to run denoify.land
36 | - name: Use Node.js ${{ matrix.node-version }}
37 | uses: actions/setup-node@v4
38 | with:
39 | node-version: ${{ matrix.node-version }}
40 |
41 | # install deno to run the test
42 | - name: Setup Deno
43 | uses: denolib/setup-deno@v2
44 | with:
45 | deno-version: ${{ matrix.deno }}
46 |
47 | - name: Install Dependencies
48 | run: |
49 | rm -f package.json
50 | rm -f package-lock.json
51 | npm init -y
52 | npm i -D typescript
53 | npm i -D denoify
54 | npm i -D rimraf
55 |
56 | - name: Denoify
57 | run: |
58 | node_modules/.bin/rimraf deno_lib
59 | node_modules/.bin/denoify
60 | node_modules/.bin/rimraf deno_lib/bundles deno_lib/dev
61 |
62 | - name: Run Tests
63 | run: |
64 | deno --version
65 | deno test -c tsconfig.deno.json test/deno/**
66 |
--------------------------------------------------------------------------------
/.github/workflows/eslint.yml:
--------------------------------------------------------------------------------
1 | # syntax: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
2 | name: ESLint
3 |
4 | on: [push, pull_request]
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 |
10 | name: Linting
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: '20'
17 | - run: rm -f package.json
18 | - run: rm -f package-lock.json
19 | - run: npm init -y
20 | - run: npm i -D @yandeu/eslint-config
21 | - run: npm i -D eslint
22 | - run: node node_modules/.bin/eslint src --ext .ts,.tsx
23 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | # syntax: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
2 | name: NodeJS
3 |
4 | on:
5 | push:
6 | paths-ignore:
7 | - '**.md'
8 | - '.vscode/**'
9 | - 'bundles/**'
10 | - 'dev/**'
11 | - 'readme/**'
12 | pull_request:
13 | paths-ignore:
14 | - '**.md'
15 | - '.vscode/**'
16 | - 'bundles/**'
17 | - 'dev/**'
18 | - 'readme/**'
19 |
20 | jobs:
21 | build:
22 | runs-on: ubuntu-latest
23 |
24 | strategy:
25 | matrix:
26 | node-version: [18.x, 20.x, 22.x]
27 |
28 | name: Node ${{ matrix.node-version }}
29 |
30 | steps:
31 | - name: Checkout repository
32 | uses: actions/checkout@v4
33 |
34 | - name: Use Node.js ${{ matrix.node-version }}
35 | uses: actions/setup-node@v4
36 | with:
37 | node-version: ${{ matrix.node-version }}
38 |
39 | - name: Install Dependencies
40 | run: npm install
41 |
42 | - name: Build Packages
43 | run: npm run build
44 |
45 | - name: Bundle Package
46 | run: npm run bundle
47 |
48 | - name: Run Tests
49 | run: npm test
50 |
51 | - name: Upload coverage to Codecov
52 | if: ${{ github.ref == 'refs/heads/master' }}
53 | uses: codecov/codecov-action@v2
54 |
--------------------------------------------------------------------------------
/.github/workflows/prettier.yml:
--------------------------------------------------------------------------------
1 | # syntax: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
2 | name: Prettier
3 |
4 | on: [push, pull_request]
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 |
10 | name: Format
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: '20'
17 | - run: rm -f package.json
18 | - run: rm -f package-lock.json
19 | - run: npm init -y
20 | - run: npm i -D @yandeu/prettier-config
21 | - run: npm i -D prettier
22 | - run: node node_modules/.bin/prettier --check src
23 |
--------------------------------------------------------------------------------
/.github/workflows/snyk.yml:
--------------------------------------------------------------------------------
1 | name: Snyk
2 |
3 | on:
4 | push:
5 | paths:
6 | - 'src/**'
7 | - '.github/**'
8 | pull_request:
9 | paths:
10 | - 'src/**'
11 | - '.github/**'
12 |
13 | jobs:
14 | snyk:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@master
18 | - name: Run Snyk to check for vulnerabilities
19 | uses: snyk/actions/node@master
20 | continue-on-error: true # To make sure that SARIF upload gets called
21 | env:
22 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
23 | with:
24 | args: --sarif-file-output=snyk.sarif
25 | - name: Upload result to GitHub Code Scanning
26 | uses: github/codeql-action/upload-sarif@v2
27 | continue-on-error: true
28 | with:
29 | sarif_file: snyk.sarif
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.nyc_output
2 | /bundles/nano.dev.min.js
3 | /bundles/nano.instrumented.min.js
4 | /bundles/nano.instrumented.min.js.map
5 | /coverage
6 | /debug.log
7 | /dev/dev.js
8 | /esm
9 | /lib
10 | /node_modules
11 | /test/**/*.js
12 | /test.jsx-runtime/**/*.js
13 | /scripts/browserTest/tester.js
14 | /deno.lock
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | !/.npmrc
4 | !/LICENSE
5 | !/README.md
6 | !/bundles
7 | !/esm
8 | !/lib
9 | !/package.json
10 | !/readme
11 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | *.html
2 | *.json
3 | *.md
4 | *.min.js
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | "@yandeu/prettier-config"
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Yannick Deubel (https://github.com/yandeu); Project Url: https://github.com/nanojsx/nano
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 |
--------------------------------------------------------------------------------
/deno.dev.tsx:
--------------------------------------------------------------------------------
1 | import { h, Helmet, renderSSR, Component } from './deno_lib/mod.ts'
2 | import { Application, Router } from 'https://deno.land/x/oak@v10.2.1/mod.ts'
3 |
4 | const comments = ['Comment One', 'Comment Two']
5 |
6 | class Comments extends Component {
7 | render() {
8 | return (
9 |
10 | {this.props.comments.map((comment: any) => {
11 | return {comment}
12 | })}
13 |
14 | )
15 | }
16 | }
17 |
18 | const App = () => (
19 |
20 |
21 | Nano JSX SSR
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
Comments
30 |
33 |
34 | )
35 |
36 | const ssr = renderSSR( )
37 | const { body, head, footer } = Helmet.SSR(ssr)
38 |
39 | console.log(body)
40 |
41 | const html = `
42 |
43 |
44 |
45 |
46 |
47 | ${head.join('\n')}
48 |
49 |
50 | ${body}
51 | ${footer.join('\n')}
52 |
53 | `
54 |
55 | const router = new Router()
56 | router.get('/', context => {
57 | context.response.body = html
58 | })
59 |
60 | const app = new Application()
61 | app.use(router.routes())
62 | app.use(router.allowedMethods())
63 |
64 | app.addEventListener('listen', ({ port }) => {
65 | console.log(`Listening on: http://localhost:${port}`)
66 | })
67 |
68 | await app.listen({ port: 5000 })
69 |
--------------------------------------------------------------------------------
/deno_lib/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Yannick Deubel (https://github.com/yandeu); Project Url: https://github.com/nanojsx/nano
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 |
--------------------------------------------------------------------------------
/deno_lib/components/img.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.ts'
2 | import { h, strToHash } from '../core.ts'
3 |
4 | interface Props {
5 | [key: string]: any
6 | src: string
7 | height?: number | string
8 | width?: number | string
9 | lazy?: boolean
10 | placeholder?: any
11 | }
12 |
13 | /**
14 | * A useful Image component
15 | * Add {
20 | constructor(props: Props) {
21 | super(props)
22 |
23 | const { src, key } = props
24 |
25 | // id has to be unique
26 | this.id = `${strToHash(src)}-${strToHash(JSON.stringify(props))}`
27 | if (key) this.id += `key-${key}`
28 |
29 | // this could also be done in willMount()
30 | if (!this.state) this.setState({ isLoaded: false, image: undefined })
31 | }
32 |
33 | didMount() {
34 | const { lazy = true, placeholder, children, key, ref, ...rest } = this.props
35 |
36 | if (typeof lazy === 'boolean' && lazy === false) return
37 |
38 | const observer = new IntersectionObserver(
39 | (entries, observer) => {
40 | entries.forEach(entry => {
41 | if (entry.isIntersecting) {
42 | observer.disconnect()
43 | this.state.image = h('img', { ...rest }) as HTMLImageElement
44 | if (this.state.image.complete) {
45 | this.state.isLoaded = true
46 | this.update()
47 | } else {
48 | this.state.image.onload = () => {
49 | this.state.isLoaded = true
50 | this.update()
51 | }
52 | }
53 | }
54 | })
55 | },
56 | { threshold: [0, 1] }
57 | )
58 | observer.observe(this.elements[0])
59 | }
60 | render() {
61 | const { src, placeholder, children, lazy = true, key, ref, ...rest } = this.props
62 |
63 | // return the img tag if not lazy loaded
64 | if (typeof lazy === 'boolean' && lazy === false) {
65 | this.state.image = h('img', { src, ...rest }) as HTMLImageElement
66 | return this.state.image
67 | }
68 |
69 | // if it is visible and loaded, show the image
70 | if (this.state.isLoaded) {
71 | return this.state.image
72 | // if the placeholder is an image src
73 | } else if (placeholder && typeof placeholder === 'string') {
74 | return h('img', { src: placeholder, ...rest })
75 | // if the placeholder is an JSX element
76 | } else if (placeholder && typeof placeholder === 'function') {
77 | return placeholder()
78 | } else {
79 | // render a simple box
80 | const style: Record = {}
81 | if (rest.width) style.width = `${rest.width}px`
82 | if (rest.height) style.height = `${rest.height}px`
83 | const { width, height, ...others } = rest
84 | return h('div', { style, ...others })
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/deno_lib/components/index.ts:
--------------------------------------------------------------------------------
1 | export { Helmet } from './helmet.ts'
2 | export { Img } from './img.ts'
3 | export { Link } from './link.ts'
4 | export * as Router from './router.ts'
5 | export { Suspense } from './suspense.ts'
6 | export { Visible } from './visible.ts'
7 |
--------------------------------------------------------------------------------
/deno_lib/components/suspense.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.ts'
2 | import { isSSR, strToHash } from '../core.ts'
3 |
4 | interface Props {
5 | fallback: any
6 | cache?: boolean
7 | [key: string]: any
8 | }
9 |
10 | export class Suspense extends Component {
11 | ready = false
12 |
13 | constructor(props: Props) {
14 | super(props)
15 |
16 | // get props promises in ...rest
17 | const { children, fallback, cache = false, ...rest } = this.props
18 |
19 | // stringify ...rest
20 | const str = JSON.stringify(rest, function (_key, val) {
21 | if (typeof val === 'function') return `${val}` // implicitly `toString` it
22 | return val
23 | })
24 |
25 | // create unique id based on ...rest
26 | this.id = strToHash(JSON.stringify(str))
27 | }
28 |
29 | async didMount() {
30 | // get props promises in ...rest
31 | const { children, fallback, cache = false, ...rest } = this.props
32 |
33 | // set initial state to []
34 | if (cache) this.initState = {}
35 |
36 | // check if we already cached the results in this.state
37 | if (this.loadFromCache(cache)) return
38 |
39 | // resolve the promises
40 | const promises = Object.values(rest).map(p => p())
41 | const resolved = await Promise.all(promises)
42 |
43 | // prepare data
44 | const data = this.prepareData(rest, resolved, cache)
45 |
46 | // add data to children
47 | this.addDataToChildren(data)
48 |
49 | // update the component
50 | this.ready = true
51 | this.update()
52 | }
53 |
54 | ssr() {
55 | // get props promises in ...rest
56 | const { children, fallback, cache = false, ...rest } = this.props
57 |
58 | // execute the functions
59 | const functions = Object.values(rest).map(p => p())
60 |
61 | // prepare data
62 | const data = this.prepareData(rest, functions, false)
63 |
64 | // add data to children
65 | this.addDataToChildren(data)
66 | }
67 |
68 | loadFromCache(cache: boolean) {
69 | const hasCachedProps = this.state && cache && Object.keys(this.state).length > 0
70 |
71 | if (hasCachedProps) {
72 | this.addDataToChildren(this.state)
73 | this.ready = true
74 | }
75 |
76 | return hasCachedProps
77 | }
78 |
79 | prepareData(rest: any, fnc: Function[], cache: boolean) {
80 | const data = Object.keys(rest).reduce((obj, item, index) => {
81 | if (cache) this.state = { ...this.state, [item]: fnc[index] }
82 | return {
83 | ...obj,
84 | [item]: fnc[index]
85 | }
86 | }, {})
87 | return data
88 | }
89 |
90 | addDataToChildren(data: any) {
91 | // add data as props to children
92 | this.props.children.forEach((child: any) => {
93 | if (child.props) child.props = { ...child.props, ...data }
94 | })
95 | }
96 |
97 | render() {
98 | if (!isSSR()) {
99 | const { cache = false } = this.props
100 | this.loadFromCache(cache)
101 | return !this.ready ? this.props.fallback : this.props.children
102 | } else {
103 | this.ssr()
104 | return this.props.children
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/deno_lib/components/visible.ts:
--------------------------------------------------------------------------------
1 | import { h, render } from '../core.ts'
2 | import { Component } from '../component.ts'
3 |
4 | export class Visible extends Component {
5 | isVisible = false
6 |
7 | didMount() {
8 | const observer = new IntersectionObserver(
9 | (entries, observer) => {
10 | entries.forEach(entry => {
11 | if (entry.isIntersecting) {
12 | observer.disconnect()
13 | this.isVisible = true
14 | this.update()
15 | }
16 | })
17 | },
18 | { threshold: [0, 1] }
19 | )
20 | observer.observe(this.elements[0])
21 | }
22 |
23 | render() {
24 | if (!this.isVisible) {
25 | return h('div', { 'data-visible': false, visibility: 'hidden' })
26 | } else {
27 | if (this.props.onVisible) this.props.onVisible()
28 | return render(this.props.component || this.props.children[0])
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/deno_lib/context.ts:
--------------------------------------------------------------------------------
1 | export const createContext = (ctx: any) => {
2 | let _ctx = ctx
3 | return {
4 | Provider: (props: any) => {
5 | if (props.value) _ctx = props.value
6 | return props.children
7 | },
8 | Consumer: (props: any) => {
9 | return { component: props.children[0](_ctx), props: { ...props, context: _ctx } }
10 | },
11 | get: () => _ctx,
12 | set: (ctx: any) => (_ctx = ctx)
13 | }
14 | }
15 |
16 | export const useContext = (ctx: any) => {
17 | const _ctx = ctx
18 | if (_ctx && typeof _ctx.get === 'function') {
19 | return _ctx.get()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/deno_lib/fragment.ts:
--------------------------------------------------------------------------------
1 | export const Fragment = (props: any) => {
2 | return props.children
3 | }
4 |
--------------------------------------------------------------------------------
/deno_lib/helpers.ts:
--------------------------------------------------------------------------------
1 | import { VERSION } from './version.ts'
2 |
3 | /** Creates a new Task using setTimeout() */
4 | export const task = (task: () => void) => setTimeout(task, 0)
5 |
6 | export const nodeToString = (node: Node) => {
7 | const tmpNode = document.createElement('div')
8 | tmpNode.appendChild(node.cloneNode(true))
9 | return tmpNode.innerHTML
10 | }
11 |
12 | export const detectSSR = (): boolean => {
13 | // @ts-ignore
14 | const isDeno = typeof Deno !== 'undefined'
15 | const hasWindow = typeof window !== 'undefined' ? true : false
16 | return (typeof _nano !== 'undefined' && _nano.isSSR) || isDeno || !hasWindow
17 | }
18 |
19 | function isDescendant(desc: ParentNode | null, root: Node): boolean {
20 | // @ts-ignore
21 | return !!desc && (desc === root || isDescendant(desc.parentNode, root))
22 | }
23 |
24 | // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
25 | export const onNodeRemove = (element: HTMLElement, callback: () => void) => {
26 | let observer = new MutationObserver(mutationsList => {
27 | mutationsList.forEach(mutation => {
28 | mutation.removedNodes.forEach(removed => {
29 | if (isDescendant(element, removed)) {
30 | callback()
31 | if (observer) {
32 | // allow garbage collection
33 | observer.disconnect()
34 | // @ts-ignore
35 | observer = undefined
36 | }
37 | }
38 | })
39 | })
40 | })
41 | observer.observe(document, {
42 | childList: true,
43 | subtree: true
44 | })
45 | return observer
46 | }
47 |
48 | // https://stackoverflow.com/a/6234804
49 | export const escapeHtml = (unsafe: string) => {
50 | if (unsafe && typeof unsafe === 'string')
51 | return unsafe
52 | .replace(/&/g, '&')
53 | .replace(//g, '>')
55 | .replace(/"/g, '"')
56 | .replace(/'/g, ''')
57 | return unsafe
58 | }
59 |
60 | export const printVersion = () => {
61 | const info = `Powered by nano JSX v${VERSION}`
62 | console.log(
63 | `%c %c %c %c %c ${info} %c http://nanojsx.io`,
64 | 'background: #ff0000',
65 | 'background: #ffff00',
66 | 'background: #00ff00',
67 | 'background: #00ffff',
68 | 'color: #fff; background: #000000;',
69 | 'background: none'
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/deno_lib/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { useState, getState, setState } from './useState.ts'
2 |
--------------------------------------------------------------------------------
/deno_lib/hooks/useState.ts:
--------------------------------------------------------------------------------
1 | import { _state } from '../state.ts'
2 |
3 | export const useState = (state: T, id: string): readonly [T, (state: T) => void] => {
4 | const s = {
5 | setState(state: T) {
6 | if (state !== null) _state.set(id, state)
7 | },
8 | get state(): T {
9 | return _state.get(id)
10 | }
11 | }
12 |
13 | if (!_state.has(id)) _state.set(id, state)
14 |
15 | return [s.state, s.setState]
16 | }
17 |
18 | export const getState = (id: string) => {
19 | return _state.get(id)
20 | }
21 |
22 | export const setState = (id: string, state: any) => {
23 | return _state.set(id, state)
24 | }
25 |
--------------------------------------------------------------------------------
/deno_lib/htm.ts:
--------------------------------------------------------------------------------
1 | import htm from './htm/index.ts'
2 | export default htm
3 |
--------------------------------------------------------------------------------
/deno_lib/htm/README.md:
--------------------------------------------------------------------------------
1 | All files in this folder are copied from version 3.0.4 of htm from https://github.com/developit/htm and ported to TypeScript to make it compatible with Deno.
2 |
3 | Please take a look at the [LICENSE](./LICENSE) file.
4 |
--------------------------------------------------------------------------------
/deno_lib/htm/constants.ts:
--------------------------------------------------------------------------------
1 | export const MINI = false
2 |
--------------------------------------------------------------------------------
/deno_lib/htm/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | import { MINI } from './constants.ts'
15 | import { build, evaluate } from './build.ts'
16 |
17 | const CACHES = new Map()
18 |
19 | const regular = function (this: HTMLElement, statics: TemplateStringsArray) {
20 | let tmp = CACHES.get(this)
21 | if (!tmp) {
22 | tmp = new Map()
23 | CACHES.set(this, tmp)
24 | }
25 | tmp = evaluate(this, tmp.get(statics) || (tmp.set(statics, (tmp = build(statics))), tmp), arguments, [])
26 | return tmp.length > 1 ? tmp : tmp[0]
27 | }
28 |
29 | // export as htm
30 | export default MINI ? build : regular
31 |
--------------------------------------------------------------------------------
/deno_lib/index.ts:
--------------------------------------------------------------------------------
1 | // core
2 | export { h, render, hydrate, tick } from './core.ts'
3 | export type { FC } from './core.ts'
4 |
5 | // component
6 | export { Component } from './component.ts'
7 |
8 | // built-in Components
9 | export * from './components/index.ts'
10 |
11 | // export some defaults (Nano)
12 | import { h, render, hydrate, isSSR } from './core.ts'
13 | import { renderSSR } from './ssr.ts'
14 | export default { h, render, hydrate, renderSSR, isSSR }
15 |
16 | // other
17 | export { isSSR }
18 | export { jsx } from './jsx.ts'
19 | export { hydrateLazy } from './lazy.ts'
20 | export { nodeToString, task } from './helpers.ts'
21 | export { renderSSR } from './ssr.ts'
22 | export { Fragment } from './fragment.ts'
23 | export { Store } from './store.ts'
24 | export { createContext, useContext } from './context.ts'
25 | export { withStyles } from './withStyles.ts'
26 | export { defineAsCustomElements } from './customElementsMode.ts'
27 |
28 | // version
29 | export { printVersion } from './helpers.ts'
30 | export { VERSION } from './version.ts'
31 |
--------------------------------------------------------------------------------
/deno_lib/jsx-runtime/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-const */
2 | export { Fragment } from '../fragment.ts'
3 | import { h } from '../core.ts'
4 |
5 | const createNode: (type: any, props: any, key: string, source?: string, self?: string) => any = function (type, props) {
6 | let { children = [], ..._props } = props
7 | if (!Array.isArray(children)) children = [children]
8 | return h(type, _props, ...children)
9 | }
10 |
11 | export { createNode as jsx }
12 | export { createNode as jsxs }
13 | export { createNode as jsxDev }
14 |
--------------------------------------------------------------------------------
/deno_lib/jsx.ts:
--------------------------------------------------------------------------------
1 | import { h } from './core.ts'
2 | import htm from './htm.ts'
3 | const jsx = htm.bind(h)
4 |
5 | export { jsx }
6 |
--------------------------------------------------------------------------------
/deno_lib/lazy.ts:
--------------------------------------------------------------------------------
1 | import { hydrate, h } from './core.ts'
2 | import { Visible } from './components/visible.ts'
3 |
4 | export const hydrateLazy = (component: any, parent: HTMLElement | null = null, removeChildNodes = true) => {
5 | const c = h(Visible, null, component)
6 | return hydrate(c, parent, removeChildNodes)
7 | }
8 |
--------------------------------------------------------------------------------
/deno_lib/mod.ts:
--------------------------------------------------------------------------------
1 | export * from './index.ts'
2 |
--------------------------------------------------------------------------------
/deno_lib/ssr.ts:
--------------------------------------------------------------------------------
1 | import { isSSR, render } from './core.ts'
2 | import { documentSSR } from './regexDom.ts'
3 | import { _state } from './state.ts'
4 | import { detectSSR } from './helpers.ts'
5 |
6 | // functions that should only be available on the server-side
7 | const ssrTricks = {
8 | isWebComponent: (tagNameOrComponent: any) => {
9 | return (
10 | typeof tagNameOrComponent === 'string' &&
11 | tagNameOrComponent.includes('-') &&
12 | _nano.customElements.has(tagNameOrComponent)
13 | )
14 | },
15 | renderWebComponent: (tagNameOrComponent: any, props: any, children: any, _render: any) => {
16 | const customElement = _nano.customElements.get(tagNameOrComponent)
17 | const component = _render({ component: customElement, props: { ...props, children: children } })
18 | // get the html tag and the innerText from string
19 | // match[1]: HTMLTag
20 | // match[2]: innerText
21 | const match = component.toString().match(/^<(?[a-z]+)>(.*)<\/\k>$/)
22 | if (match) {
23 | const element = document.createElement(match[1])
24 | element.innerText = match[2]
25 |
26 | // eslint-disable-next-line no-inner-declarations
27 | function replacer(match: string, p1: string, _offset: number, _string: string): string {
28 | return match.replace(p1, '')
29 | }
30 | // remove events like onClick from DOM
31 | element.innerText = element.innerText.replace(/<\w+[^>]*(\s(on\w*)="[^"]*")/gm, replacer)
32 |
33 | return element
34 | } else {
35 | return null
36 | }
37 | }
38 | }
39 |
40 | const initGlobalVar = () => {
41 | const isSSR = detectSSR() === true ? true : undefined
42 | const location = { pathname: '/' }
43 | const document = isSSR ? documentSSR() : window.document
44 | globalThis._nano = { isSSR, location, document, customElements: new Map(), ssrTricks }
45 | }
46 | initGlobalVar()
47 |
48 | export const initSSR = (pathname: string = '/') => {
49 | _nano.location = { pathname }
50 | globalThis.document = _nano.document = isSSR() ? documentSSR() : window.document
51 | }
52 |
53 | export const renderSSR = (component: any, options: { pathname?: string; clearState?: boolean } = {}) => {
54 | const { pathname, clearState = true } = options
55 |
56 | initSSR(pathname)
57 | if (clearState) _state.clear()
58 |
59 | const tmp = render(component, null, true) as string | string[]
60 | if (Array.isArray(tmp)) return tmp.join('')
61 | else return Array.from(tmp).join('')
62 | }
63 |
64 | export const clearState = () => {
65 | _state.clear()
66 | }
67 |
--------------------------------------------------------------------------------
/deno_lib/state.ts:
--------------------------------------------------------------------------------
1 | /** Holds the state of the whole application. */
2 | export const _state: Map = new Map()
3 |
4 | /** Clears the state of the whole application. */
5 | export const _clearState = () => {
6 | _state.clear()
7 | }
8 |
--------------------------------------------------------------------------------
/deno_lib/store.ts:
--------------------------------------------------------------------------------
1 | import { isSSR } from './core.ts'
2 |
3 | export class Store {
4 | private _state: S
5 | private _prevState: S
6 | private _listeners: Map = new Map()
7 | private _storage: 'memory' | 'local' | 'session'
8 | private _id: string
9 |
10 | /**
11 | * Create your own Store.
12 | * @param defaultState Pass the initial State.
13 | * @param name The name of the Store (only required if you persist the state in localStorage or sessionStorage).
14 | * @param storage Pass 'memory', 'local' or 'session'.
15 | */
16 | constructor(defaultState: Object, name: string = '', storage: 'memory' | 'local' | 'session' = 'memory') {
17 | if (isSSR()) storage = 'memory'
18 |
19 | this._id = name
20 | this._storage = storage
21 |
22 | this._state = this._prevState = defaultState as any
23 |
24 | if (storage === 'memory' || !storage) return
25 |
26 | const Storage = storage === 'local' ? localStorage : sessionStorage
27 |
28 | // get/set initial state of Storage
29 | const item = Storage.getItem(this._id)
30 | if (item) {
31 | this._state = this._prevState = JSON.parse(item)
32 | } else Storage.setItem(this._id, JSON.stringify(defaultState))
33 | }
34 |
35 | private persist(newState: S) {
36 | if (this._storage === 'memory') return
37 | const Storage = this._storage === 'local' ? localStorage : sessionStorage
38 | Storage.setItem(this._id, JSON.stringify(newState))
39 | }
40 |
41 | /** Clears the state of the whole store. */
42 | public clear() {
43 | // @ts-ignore
44 | this._state = this._prevState = undefined
45 |
46 | if (this._storage === 'local') localStorage.removeItem(this._id)
47 | else if (this._storage === 'session') sessionStorage.removeItem(this._id)
48 | }
49 |
50 | public setState(newState: S) {
51 | this.state = newState
52 | }
53 |
54 | public set state(newState: S) {
55 | this._prevState = this._state
56 | this._state = newState
57 |
58 | this.persist(newState)
59 |
60 | this._listeners.forEach(fnc => {
61 | fnc(this._state, this._prevState)
62 | })
63 | }
64 |
65 | public get state(): S {
66 | return this._state
67 | }
68 |
69 | public use() {
70 | const id = Math.random().toString(36).substring(2, 9)
71 | const _this = this
72 | return {
73 | get state(): S {
74 | return _this.state
75 | },
76 | setState: (newState: S) => {
77 | this.state = newState
78 | },
79 | subscribe: (fnc: (newState: S, prevState: S) => void) => {
80 | this._listeners.set(id, fnc)
81 | },
82 | cancel: () => {
83 | if (this._listeners.has(id)) this._listeners.delete(id)
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/deno_lib/types.ts:
--------------------------------------------------------------------------------
1 | // fixes an issue in std@0.80.0 (deno)
2 | // interface ReadableStream {
3 | // getIterator(): any
4 | // }
5 |
6 | declare global {
7 | export var _nano: {
8 | document: Document
9 | isSSR: true | undefined
10 | location: { pathname: string }
11 | customElements: Map
12 | ssrTricks: {
13 | isWebComponent: (tagNameOrComponent: any) => boolean
14 | renderWebComponent: (tagNameOrComponent: any, props: any, children: any, _render: any) => any
15 | }
16 | }
17 |
18 | export namespace Deno {}
19 |
20 | export namespace JSX {
21 | interface IntrinsicElements {
22 | [elemName: string]: any
23 | }
24 | interface ElementClass {
25 | render: any
26 | }
27 | interface ElementChildrenAttribute {
28 | children: any
29 | }
30 | }
31 | }
32 | // This export keeps the backward compatibility with the module resolution system in deno < 1.23 version.
33 | export {}
34 |
--------------------------------------------------------------------------------
/deno_lib/ui/README.md:
--------------------------------------------------------------------------------
1 | # UI Elements
2 |
3 | These ui elements should look very similar to the [Material UI Components](https://material.io/components).
4 |
5 | Feel free to improve the elements or add new once.
6 |
--------------------------------------------------------------------------------
/deno_lib/ui/_config.ts:
--------------------------------------------------------------------------------
1 | import { strToHash } from '../core.ts'
2 |
3 | export const boxShadow = `
4 | -webkit-box-shadow: 0px 3px 5px -1px rgba(0,0,0,0.2), 0px 6px 10px 0px rgba(0,0,0,0.14), 0px 1px 18px 0px rgba(0,0,0,0.12);
5 | -moz-box-shadow: 0px 3px 5px -1px rgba(0,0,0,0.2), 0px 6px 10px 0px rgba(0,0,0,0.14), 0px 1px 18px 0px rgba(0,0,0,0.12);
6 | box-shadow: 0px 3px 5px -1px rgba(0,0,0,0.2), 0px 6px 10px 0px rgba(0,0,0,0.14), 0px 1px 18px 0px rgba(0,0,0,0.12);
7 | `
8 |
9 | export const userSelect = `
10 | -webkit-touch-callout:none;
11 | -webkit-user-select:none;
12 | -khtml-user-select:none;
13 | -moz-user-select:none;
14 | -ms-user-select:none;
15 | user-select:none;
16 | -webkit-tap-highlight-color:rgba(0,0,0,0);
17 | `
18 |
19 | export const rippleEffect = (rippleClr: string, hoverClr: string) => {
20 | const rippleClass = `ripple-${strToHash(rippleClr + hoverClr)}`
21 |
22 | const styles = `
23 | .${rippleClass} {
24 | background-position: center;
25 | transition: background 0.8s;
26 | }
27 |
28 | .${rippleClass}:hover {
29 | background: ${hoverClr} radial-gradient(circle, transparent 1%, ${hoverClr} 1%) center/15000%;
30 | }
31 |
32 | .${rippleClass}:focus {
33 | background: ${hoverClr} radial-gradient(circle, transparent 1%, ${hoverClr} 1%) center/15000%;
34 | }
35 |
36 | .${rippleClass}:active {
37 | background-color: ${rippleClr};
38 | background-size: 100%;
39 | transition: background 0s;
40 | }`
41 |
42 | return {
43 | styles,
44 | class: rippleClass
45 | }
46 | }
47 |
48 | export const zIndex = {
49 | button: 'unset;',
50 | banner: '50;',
51 | bar: '100;',
52 | navigation: '100;',
53 | fab: '200;',
54 | sheet: '300;',
55 | menu: '400;',
56 | snackbar: '500;',
57 | dialog: '600;'
58 | }
59 |
--------------------------------------------------------------------------------
/deno_lib/ui/_helpers.ts:
--------------------------------------------------------------------------------
1 | import { h } from '../core.ts'
2 |
3 | // https://gist.github.com/renancouto/4675192
4 | export const lightenColor = (color: string, percent: number) => {
5 | const num = parseInt(color.replace('#', ''), 16),
6 | amt = Math.round(2.55 * percent),
7 | R = (num >> 16) + amt,
8 | B = ((num >> 8) & 0x00ff) + amt,
9 | G = (num & 0x0000ff) + amt
10 | return `#${(
11 | 0x1000000 +
12 | (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
13 | (B < 255 ? (B < 1 ? 0 : B) : 255) * 0x100 +
14 | (G < 255 ? (G < 1 ? 0 : G) : 255)
15 | )
16 | .toString(16)
17 | .slice(1)}`
18 | }
19 |
20 | export const addStylesToHead = (styles: string, hash: string) => {
21 | const el = document.querySelector(`[data-css-hash*="${hash}"]`)
22 | if (!el) {
23 | const styleElement = h('style', { 'data-css-hash': hash }, styles)
24 | document.head.appendChild(styleElement)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/deno_lib/ui/banner.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.ts'
2 | import { h } from '../core.ts'
3 | import { Button } from './button.ts'
4 | import { zIndex } from './_config.ts'
5 |
6 | interface BannerAction {
7 | name: string
8 | id?: string | number
9 | color?: string
10 | }
11 |
12 | interface BannerActionEvent {
13 | action: string
14 | }
15 |
16 | interface BannerProps {
17 | title?: string
18 | body?: string
19 | actions?: BannerAction[]
20 | onAction?: () => BannerActionEvent
21 | parentId?: string
22 | sticky?: number
23 | }
24 |
25 | export class Banner extends Component {
26 | defaultActionColor = '#6200EE'
27 |
28 | render() {
29 | const {
30 | sticky,
31 | body = 'There was a problem processing a transaction on your credit card.',
32 | actions = [
33 | { name: 'fix it', color: this.defaultActionColor },
34 | { name: 'learn more', color: this.defaultActionColor }
35 | ]
36 | } = this.props
37 |
38 | const stickyStyles = sticky
39 | ? `
40 | position: -webkit-sticky;
41 | position: sticky;
42 | top: ${sticky}px;`
43 | : ''
44 |
45 | const styles = {
46 | container: `
47 | margin: -16px -16px 16px -16px;
48 | ${stickyStyles}
49 | z-index: ${zIndex.banner}
50 | `,
51 | banner: `
52 |
53 | display: flex;
54 | justify-content: space-between;
55 | flex-wrap: wrap;
56 |
57 |
58 | background: white;
59 | border-bottom: 1px rgb(0 0 0 / 0.12) solid;
60 | min-height: 54px;`,
61 | body: `
62 | color: #000000;
63 | padding: 16px;
64 | padding-bottom: 8px;
65 | font-size: 16px;
66 | line-height: 1.5em;`,
67 | actions: `
68 | margin: 0;
69 | padding: 8px;
70 | display: flex;
71 | flex-direction: row;
72 | align-items: flex-end;
73 | margin-left: auto;
74 | flex-wrap: wrap;
75 | justify-content: flex-end;
76 | `,
77 | action: `
78 | margin-bottom: 0px;
79 | margin-left: 10px;`
80 | }
81 |
82 | const actionsArray = actions.map((action: any) => {
83 | return h(
84 | Button,
85 | {
86 | text: true,
87 | color: action.color || this.defaultActionColor,
88 | style: styles.action,
89 | // style: `color: ${action.color || this.defaultActionColor}`,
90 | onClick: () => {
91 | // callback({ name: action.name, id: action.id })
92 | // this.remove()
93 | }
94 | },
95 | action.name.toUpperCase()
96 | )
97 | })
98 |
99 | const bodyElement = h('div', { style: styles.body }, body)
100 | const actionsElement = h('div', { style: styles.actions }, actionsArray)
101 | const bannerElement = h('div', { style: styles.banner }, bodyElement, actionsElement)
102 |
103 | return h('div', { style: styles.container }, bannerElement)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/deno_lib/ui/button.ts:
--------------------------------------------------------------------------------
1 | import { h, strToHash } from '../core.ts'
2 | import { boxShadow, rippleEffect, userSelect, zIndex } from './_config.ts'
3 | import { addStylesToHead, lightenColor } from './_helpers.ts'
4 | import { Icon } from './icon.ts'
5 |
6 | export const Button = (props: {
7 | outlined?: boolean
8 | text?: boolean
9 | style?: string
10 | icon?: string
11 | [key: string]: any
12 | }) => {
13 | const {
14 | children,
15 | outlined = false,
16 | text = false,
17 | background = '#6200ee',
18 | color = '#ffffff',
19 | style = '',
20 | class: className = '',
21 | icon,
22 | ...rest
23 | } = props
24 |
25 | const normal = !(outlined || text)
26 |
27 | const bg = normal ? background : '#ffffff'
28 | const clr = normal ? color : background
29 | const hoverClr = normal ? lightenColor(bg, 10) : lightenColor(bg, -10)
30 | const rippleClr = normal ? lightenColor(bg, 50) : lightenColor(background, 50)
31 | const cssHash = strToHash(outlined.toString() + text.toString() + bg + clr + style)
32 |
33 | const ripple = rippleEffect(rippleClr, hoverClr)
34 |
35 | const styles = `
36 | .nano_jsx_button-${cssHash} {
37 | color: ${clr};
38 | background: ${bg};
39 | border-radius: 4px;
40 | display: inline-flex;
41 | font-size: 14px;
42 | padding: 10px 16px;
43 | margin: 0px 0px 1em 0px;
44 | text-align: center;
45 | cursor: pointer;
46 |
47 | ${userSelect}
48 |
49 |
50 | z-index: ${zIndex.button}
51 |
52 | ${boxShadow}
53 |
54 | border: none;
55 | text-transform: uppercase;
56 | box-shadow: 0 0 4px #999;
57 | outline: none;
58 | }
59 |
60 | ${ripple.styles}
61 | `
62 |
63 | addStylesToHead(styles, cssHash)
64 |
65 | let customStyles = ''
66 | if (outlined || text) {
67 | customStyles += 'padding-top: 9px; padding-bottom: 9px; '
68 | customStyles += '-webkit-box-shadow: none; -moz-box-shadow: none; box-shadow none; '
69 | if (outlined) customStyles += `border: 1px ${clr} solid; `
70 | }
71 | customStyles += style
72 |
73 | return h(
74 | 'button',
75 | { class: `nano_jsx_button-${cssHash} ${ripple.class} ${className}`, style: customStyles, ...rest },
76 | icon ? h(Icon, { style: 'margin-left: -4px; margin-right: 8px; width: 14px; height: 14px;' }, icon) : null,
77 | children
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/deno_lib/ui/fab.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.ts'
2 | import { h, strToHash } from '../core.ts'
3 | import { boxShadow, userSelect, zIndex } from './_config.ts'
4 | import { addStylesToHead } from './_helpers.ts'
5 |
6 | interface FabProps {
7 | onClick?: (e: MouseEvent) => void
8 | offsetY?: number
9 | center?: boolean
10 | left?: boolean
11 | extended?: boolean
12 | mini?: boolean
13 | background?: string
14 | color?: string
15 | }
16 |
17 | export class Fab extends Component {
18 | render() {
19 | const {
20 | background = '#6200EE',
21 | color = 'white',
22 | extended = false,
23 | mini = false,
24 | center = false,
25 | left = false,
26 | onClick = () => {}
27 | } = this.props
28 |
29 | const height = mini ? 40 : extended ? 48 : 56
30 | const cssHash = strToHash(extended.toString() + mini.toString() + center.toString() + left.toString())
31 | const className = `fab-container-${cssHash}`
32 |
33 | const styles = `
34 | .${className} {
35 | ${mini ? 'width: 40px;' : extended ? 'padding: 0px 12px;' : 'width: 56px;'}
36 | height: ${height}px;
37 | position: fixed;
38 | background: ${background};
39 | border-radius: 36px;
40 | display: flex;
41 | align-items: center;
42 | justify-content: center;
43 | color: ${color};
44 | cursor: pointer;
45 |
46 | z-index: ${zIndex.fab}
47 | bottom: ${this.props.offsetY ? 16 + this.props.offsetY : 16}px;
48 | ${left ? 'left: 16px;' : 'right: 16px;'}
49 | ${center ? 'transform: translateX(50%); right: 50%;' : ''}
50 | ${boxShadow}
51 | ${userSelect}
52 | }
53 | `
54 |
55 | addStylesToHead(styles, cssHash)
56 |
57 | const { children } = this.props as any
58 |
59 | return h('div', { class: className, onClick: (e: MouseEvent) => onClick(e) }, children)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/deno_lib/ui/icon.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.ts'
2 | import { h, strToHash } from '../core.ts'
3 | import { addStylesToHead } from './_helpers.ts'
4 |
5 | interface IconProps {
6 | src: string
7 | active?: boolean
8 | color?: string
9 | style?: string
10 | size?: number
11 | onClick?: (e: MouseEvent) => void
12 | }
13 |
14 | export class Icon extends Component {
15 | cssHash: string
16 |
17 | didUnmount() {
18 | // not sure if I want to remove the css, since there might be another Icon with the same styles.
19 | // const el = document.querySelector(`[data-css-hash*="${this.cssHash}"]`)
20 | // if (el) el.remove()
21 | }
22 |
23 | render() {
24 | const { src, size = 16, active = true, color = '#6204EE', style = '', ...rest } = this.props
25 |
26 | // @ts-ignore
27 | const children = this.props.children
28 |
29 | this.cssHash = strToHash(active + color + size.toString())
30 |
31 | const colors = {
32 | active: color,
33 | inactive: '#00000070'
34 | }
35 |
36 | const styles = `
37 | i.icon-${this.cssHash} {
38 | width: ${size}px;
39 | height: ${size}px;
40 | display: inline-block;
41 | content: '';
42 |
43 | /*-webkit-mask: url(YOUR_SVG_URL) no-repeat 50% 50%;
44 | mask: url(YOUR_SVG_URL) no-repeat 50% 50%;*/
45 |
46 | -webkit-mask-size: cover;
47 | mask-size: cover;
48 |
49 | background-color: ${colors.active};
50 | }
51 |
52 | i.icon-${this.cssHash}.icon_inactive-${this.cssHash} {
53 | background-color: ${colors.inactive};
54 | }
55 | `
56 |
57 | addStylesToHead(styles, this.cssHash)
58 |
59 | // const iconStyle = `-webkit-mask: url(/dev/font-awesome/ellipsis-v-solid.svg) no-repeat 50% 50%;mask: url(/dev/font-awesome/ellipsis-v-solid.svg) no-repeat 50% 50%;`
60 | const iconStyle = `-webkit-mask: url(${src || children}) no-repeat 50% 50%; mask: url(${
61 | src || children
62 | }) no-repeat 50% 50%;`
63 |
64 | const classes = [`icon-${this.cssHash}`]
65 | if (!active) classes.push(`icon_inactive-${this.cssHash}`)
66 |
67 | const icon = h('i', { class: classes.join(' '), ...rest, style: iconStyle + style })
68 |
69 | return icon
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/deno_lib/ui/index.ts:
--------------------------------------------------------------------------------
1 | export { AppBar } from '../ui/appBar.ts'
2 | export { Toolbar } from '../ui/toolbar.ts'
3 | export { Navigation, NavigationAction } from '../ui/navigation.ts'
4 | export { Button } from '../ui/button.ts'
5 | export { Dialog } from '../ui/dialog.ts'
6 | export { Fab } from '../ui/fab.ts'
7 | export { Icon } from '../ui/icon.ts'
8 | export { List, ListItem } from '../ui/list.ts'
9 | export { Menu } from '../ui/menu.ts'
10 | export { Snackbar } from '../ui/snackbar.ts'
11 | export { Tabs, Tab } from '../ui/tabs.ts'
12 | export { Sheet } from '../ui/sheet.ts'
13 |
--------------------------------------------------------------------------------
/deno_lib/ui/list.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.ts'
2 | import { h, strToHash } from '../core.ts'
3 | import { Icon } from './icon.ts'
4 | import { addStylesToHead } from './_helpers.ts'
5 |
6 | interface ListProps {
7 | small?: boolean
8 | children?: any
9 | }
10 |
11 | interface ListItemProps {
12 | onClick?: Function
13 | icon?: string
14 | avatar?: string
15 | square?: string
16 | image?: string
17 | children?: any
18 | }
19 |
20 | export class ListItem extends Component {
21 | render() {
22 | const { props: p } = this
23 | const { onClick = () => {} } = p
24 |
25 | const adjustedMargin = 'margin-right: 16px;'
26 |
27 | const icon = p.icon ? h(Icon, { size: 20, style: 'margin-right: 32px;', src: p.icon }) : null
28 | const avatar = p.avatar
29 | ? h('img', { src: p.avatar, width: 40, height: 40, style: `border-radius: 20px; ${adjustedMargin}` })
30 | : null
31 | const square = p.square ? h('img', { src: p.square, width: 56, height: 56, style: adjustedMargin }) : null
32 | const image = p.image
33 | ? h('img', { src: p.image, width: 100, height: 56, style: 'margin-left: -16px; margin-right: 16px;' })
34 | : null
35 | const text = h('span', null, p.children)
36 |
37 | // additional style for the list item
38 | let style = ''
39 | if (p.icon || p.avatar) style += 'min-height: 56px; '
40 | if (p.square || p.image) style += 'min-height: 72px; '
41 |
42 | return h('li', { style, onClick }, icon, avatar, square, image, text)
43 | }
44 | }
45 |
46 | export class List extends Component {
47 | cssHash: string
48 |
49 | render() {
50 | const { small = false } = this.props
51 |
52 | this.cssHash = strToHash(`List${small.toString()}`)
53 |
54 | const styles = `
55 | .list-${this.cssHash} ul {
56 | margin: 0px;
57 | padding: 8px 16px;
58 | }
59 |
60 | .list-${this.cssHash} ul li {
61 | list-style: none;
62 | min-height: ${small ? 32 : 46}px;
63 | display: flex;
64 | align-items: center;
65 | margin: 0px -16px;
66 | padding: 0px 16px;
67 | cursor: pointer;
68 | }
69 |
70 | .list-${this.cssHash} ul li span {
71 | font-size: 16px;
72 | }
73 |
74 | .list-${this.cssHash} ul li:hover {
75 | background:#00000010
76 | }
77 | `
78 |
79 | addStylesToHead(styles, this.cssHash)
80 |
81 | const ul = h('ul', null, this.props.children)
82 | return h('div', { class: `list-${this.cssHash}` }, ul)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/deno_lib/ui/menu.ts:
--------------------------------------------------------------------------------
1 | import { h, removeAllChildNodes, render } from '../core.ts'
2 | import { boxShadow, zIndex } from './_config.ts'
3 | import { addStylesToHead } from './_helpers.ts'
4 |
5 | interface MenuOptions {
6 | position: { x: number; y: number }
7 | list: any
8 | }
9 |
10 | export class Menu {
11 | defaultParentId = 'menu_items_container'
12 | cssHash = Math.random().toString(36).substring(2)
13 |
14 | // didUnmount() {
15 | // const el = document.querySelector(`[data-css-hash*="${this.cssHash}"]`)
16 | // if (el) el.remove()
17 | // }
18 |
19 | private getParentElement(id: string) {
20 | // delete all other
21 | const others = document.querySelectorAll(`[id^="${this.defaultParentId}"]`)
22 | others.forEach(e => {
23 | e.remove()
24 | })
25 |
26 | let el = document.getElementById(`${this.defaultParentId}-${id}`)
27 | if (!el) {
28 | el = document.createElement('div')
29 | el.id = `${this.defaultParentId}-${id}`
30 | }
31 |
32 | removeAllChildNodes(el)
33 | document.body.appendChild(el)
34 |
35 | return el
36 | }
37 |
38 | close() {
39 | removeAllChildNodes(this.getParentElement(this.cssHash))
40 | }
41 |
42 | open(menuOptions: MenuOptions) {
43 | const { position, list } = menuOptions
44 |
45 | // check in which corner the menu appears and adjust fixed position.
46 | const left = position.x < window.innerWidth / 2 ? 'left' : 'right'
47 | const top = position.y < window.innerHeight / 2 ? 'top' : 'bottom'
48 |
49 | const styles = `
50 |
51 | #menu_items_background-${this.cssHash} {
52 | width: 100vw;
53 | height: 100vh;
54 | background: transparent;
55 | position: fixed;
56 | top: 0;
57 | left: 0;
58 | z-index: ${zIndex.menu}
59 | }
60 |
61 | #menu_items_list-${this.cssHash} {
62 | position: fixed;
63 | background: white;
64 |
65 | border-radius: 4px;
66 | min-width: 112px;
67 |
68 | ${top}: ${position.y > window.innerHeight / 2 ? window.innerHeight - position.y : position.y}px;
69 | ${left}: ${position.x > window.innerWidth / 2 ? window.innerWidth - position.x : position.x}px;
70 |
71 | z-index: ${zIndex.menu}
72 |
73 | ${boxShadow}
74 | }
75 |
76 | `
77 | // remove old styles
78 | const el = document.querySelector(`[data-css-hash*="${this.cssHash}"]`)
79 | if (el) el.remove()
80 |
81 | // add new styles
82 | addStylesToHead(styles, this.cssHash)
83 |
84 | const itemsList = h('div', { id: `menu_items_list-${this.cssHash}` }, list)
85 | const itemsBg = h('div', { onClick: () => this.close(), id: `menu_items_background-${this.cssHash}` }, itemsList)
86 |
87 | itemsList.addEventListener('click', (e: Event) => e.stopPropagation())
88 |
89 | this.getParentElement(this.cssHash).appendChild(render(itemsBg))
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/deno_lib/version.ts:
--------------------------------------------------------------------------------
1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | export const VERSION = '0.1.0'
3 |
--------------------------------------------------------------------------------
/deno_lib/withStyles.ts:
--------------------------------------------------------------------------------
1 | import { h } from './core.ts'
2 | import { Component } from './component.ts'
3 | import { Fragment } from './fragment.ts'
4 | import { Helmet } from './components/helmet.ts'
5 |
6 | interface ObjectHasToString {
7 | toString: () => string
8 | }
9 | type Styles = string
10 | type FunctionReturnsString = () => string
11 | type WithStylesType = Styles | ObjectHasToString | FunctionReturnsString
12 |
13 | export const withStyles: any =
14 | (...styles: WithStylesType[]) =>
15 | (WrappedComponent: any) => {
16 | return class extends Component {
17 | render() {
18 | const { children, ...rest } = this.props
19 |
20 | const helmets: any[] = []
21 | styles.forEach(style => {
22 | if (typeof style === 'string') {
23 | helmets.push(h(Helmet, null, h('style', null, style)))
24 | } else if (typeof style === 'function') {
25 | const _style = style()
26 | if (typeof _style === 'string') {
27 | helmets.push(h(Helmet, null, h('style', null, _style)))
28 | }
29 | } else if (typeof style === 'object') {
30 | const _style = style.toString?.()
31 | if (typeof _style === 'string') {
32 | helmets.push(h(Helmet, null, h('style', null, _style)))
33 | }
34 | }
35 | })
36 |
37 | const component =
38 | children && children.length > 0
39 | ? h(WrappedComponent, { ...rest }, children)
40 | : h(WrappedComponent, { ...this.props })
41 |
42 | return h(Fragment, null, ...helmets, component)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/dev/bundled-core.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/dev/bundled-dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
21 |
22 |
23 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/dev/dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/dev/font-awesome/LICENSE:
--------------------------------------------------------------------------------
1 | https://fontawesome.com/license
--------------------------------------------------------------------------------
/dev/font-awesome/ellipsis-v-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev/font-awesome/heart-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev/font-awesome/home-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev/font-awesome/user-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev/img/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanojsx/nano/b1abb6ad02274105ce003eeb821da11716988199/dev/img/placeholder.png
--------------------------------------------------------------------------------
/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectCoverage": true,
3 | "collectCoverageFrom": [
4 | "lib/**/*.js",
5 | "!lib/{bundles,dev,htm,ui}/**",
6 | "!lib/mod.js"
7 | ],
8 | "maxConcurrency": 2,
9 | "maxWorkers": 2,
10 | "testEnvironment": "jsdom"
11 | }
--------------------------------------------------------------------------------
/readme/nano-jsx-logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanojsx/nano/b1abb6ad02274105ce003eeb821da11716988199/readme/nano-jsx-logo-512.png
--------------------------------------------------------------------------------
/readme/nano-jsx-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanojsx/nano/b1abb6ad02274105ce003eeb821da11716988199/readme/nano-jsx-logo.png
--------------------------------------------------------------------------------
/readme/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanojsx/nano/b1abb6ad02274105ce003eeb821da11716988199/readme/thumbnail.png
--------------------------------------------------------------------------------
/scripts/browserTest/README.md:
--------------------------------------------------------------------------------
1 | # browserTest
2 |
3 | This package is used for testing Nano JSX in the browser.
4 |
--------------------------------------------------------------------------------
/scripts/browserTest/mime.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import { extname } from 'path'
4 |
5 | export const mime = fileName => {
6 | switch (extname(fileName)) {
7 | case '.css':
8 | return 'text/css'
9 | case '.js':
10 | return 'application/javascript'
11 | case '.html':
12 | return 'text/html'
13 | case '.json':
14 | return 'application/json'
15 | case '.jpg':
16 | case '.jpeg':
17 | return 'image/jpeg'
18 | case '.png':
19 | return 'image/png'
20 | case '.svg':
21 | case '.svgz':
22 | return 'image/svg+xml'
23 | default:
24 | return 'text/plain'
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/scripts/browserTest/nyc.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import { spawn } from 'child_process'
4 |
5 | const isWin = process.platform === 'win32'
6 |
7 | export const NYC = {
8 | instrument: (filePath, res) => {
9 | if (isWin) spawn('powershell.exe', ['npx', 'nyc', 'instrument', filePath]).stdout.pipe(res)
10 | else spawn('npx', ['nyc', 'instrument', filePath]).stdout.pipe(res)
11 | },
12 | report: () => {
13 | if (isWin) spawn('powershell.exe', 'npx nyc report --reporter=text'.split(' '), { stdio: 'inherit' })
14 | else spawn('npx', 'nyc report --reporter=text'.split(' '), { stdio: 'inherit' })
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/scripts/browserTest/requestListener.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import path from 'path'
4 | import { mime } from './mime.mjs'
5 | import { readFile } from 'fs/promises'
6 | import { dirname, join } from 'path'
7 | import { fileURLToPath } from 'url'
8 | import { NYC } from './nyc.mjs'
9 |
10 | const __filename = fileURLToPath(import.meta.url)
11 | const __dirname = dirname(__filename)
12 |
13 | const testerJS = await readFile(join(__dirname, './tester.js'), { encoding: 'utf-8' })
14 |
15 | export const totalPasses = [0, 0]
16 |
17 | /** @type {Array<{text:string, id: number, type?: string}>} */
18 | let _messages = []
19 |
20 | const sendMessages = () => {
21 | _messages
22 | .sort((a, b) => a.id - b.id)
23 | .forEach(m => {
24 | if (m.type === 'end') {
25 | const match = m.text.match(/(\d+)\/(\d+).passing/m)
26 | if (match) {
27 | totalPasses[0] += parseInt(match[1])
28 | totalPasses[1] += parseInt(match[2])
29 | }
30 | }
31 |
32 | // log the text (with 2 spaces more)
33 | console.log(
34 | m.text
35 | .split('\n')
36 | .map(l => ` ${l}`)
37 | .join('\n')
38 | )
39 | })
40 |
41 | _messages = []
42 | }
43 |
44 | const queueMessages = message => {
45 | _messages.push(message)
46 |
47 | setTimeout(() => {
48 | sendMessages()
49 | }, 100)
50 | }
51 |
52 | export const requestListener = ({ serve, collectCoverage }) => {
53 | return async (req, res) => {
54 | if (req.method === 'POST') {
55 | let data = ''
56 | req.on('data', chunk => {
57 | data += chunk
58 | })
59 | req.on('end', () => {
60 | const message = JSON.parse(data)
61 | queueMessages(message)
62 | res.end()
63 | })
64 | return
65 | }
66 |
67 | if (req.method === 'GET') {
68 | try {
69 | const filePath = path.join(path.resolve(), req.url)
70 | const contentType = mime(req.url)
71 |
72 | // tester.js
73 | if (req.url === '/tester.js') {
74 | return res.writeHead(200, { 'Content-Type': contentType }).end(testerJS)
75 | }
76 | // Will instrument all javascript files not containing "instrumented"
77 | else if (collectCoverage && contentType === 'application/javascript' && !/instrumented/.test(filePath)) {
78 | const myOptions = ''
79 | res.writeHead(200, { 'Content-Type': contentType })
80 | NYC.instrument(filePath, res)
81 | return
82 | }
83 | // else
84 | else {
85 | const file = await readFile(filePath, { encoding: 'utf-8' })
86 | if (!file) return res.writeHead(404).end()
87 | return res.writeHead(200, { 'Content-Type': contentType }).end(file)
88 | }
89 | } catch (err) {
90 | if (!err.message.endsWith(".ico'")) {
91 | console.log('Error:', err.message)
92 | }
93 | return res.writeHead(500).end()
94 | }
95 | }
96 |
97 | return res.writeHead(400).end()
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/scripts/browserTest/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "module": "UMD",
5 |
6 | "noImplicitAny": false,
7 |
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "typeRoots": []
11 | },
12 | "include": ["./**/*.ts"]
13 | }
14 |
--------------------------------------------------------------------------------
/scripts/packageType.mjs:
--------------------------------------------------------------------------------
1 | import { writeFile } from 'fs/promises'
2 |
3 | const pkgModule = {
4 | type: 'module'
5 | }
6 |
7 | const pkgCommonjs = {
8 | type: 'commonjs'
9 | }
10 |
11 | await writeFile('esm/package.json', JSON.stringify(pkgModule, null, 2), { encoding: 'utf-8' })
12 | await writeFile('lib/package.json', JSON.stringify(pkgCommonjs, null, 2), { encoding: 'utf-8' })
13 |
--------------------------------------------------------------------------------
/src/bundles/bundle.core.ts:
--------------------------------------------------------------------------------
1 | // core
2 | import { h, render } from '../core.js'
3 |
4 | export default {
5 | render,
6 | h
7 | }
8 |
--------------------------------------------------------------------------------
/src/bundles/bundle.full.ts:
--------------------------------------------------------------------------------
1 | // core
2 | import { h, hydrate, removeAllChildNodes, render, tick } from '../core.js'
3 | import { hydrateLazy } from '../lazy.js'
4 | import { nodeToString, task } from '../helpers.js'
5 |
6 | // useful tools
7 | import { Component } from '../component.js'
8 | import { Fragment } from '../fragment.js'
9 | import { Store } from '../store.js'
10 | import { createContext } from '../context.js'
11 | import { withStyles } from '../withStyles.js'
12 |
13 | // built-in components
14 | import { Helmet } from '../components/helmet.js'
15 | import { Link } from '../components/link.js'
16 | import { Img } from '../components/img.js'
17 | import { Visible } from '../components/visible.js'
18 | import * as Router from '../components/router.js'
19 |
20 | // customElement
21 | import { defineAsCustomElements } from '../customElementsMode.js'
22 |
23 | // tagged templates
24 | import { jsx } from '../jsx.js'
25 |
26 | // ui
27 | import * as UI from '../ui/index.js'
28 |
29 | export default {
30 | Component,
31 | Fragment,
32 | Helmet,
33 | Img,
34 | Link,
35 | Router,
36 | Store,
37 | UI,
38 | Visible,
39 | createContext,
40 | h,
41 | hydrate,
42 | hydrateLazy,
43 | jsx,
44 | nodeToString,
45 | removeAllChildNodes,
46 | render,
47 | task,
48 | tick,
49 | withStyles,
50 | defineAsCustomElements
51 | }
52 |
53 | // version
54 | export { printVersion } from '../helpers.js'
55 | export { VERSION } from '../version.js'
56 |
--------------------------------------------------------------------------------
/src/bundles/bundle.slim.ts:
--------------------------------------------------------------------------------
1 | // core
2 | import { h, render, tick } from '../core.js'
3 | import { task } from '../helpers.js'
4 |
5 | // useful tools
6 | import { Component } from '../component.js'
7 | import { Fragment } from '../fragment.js'
8 | import { Store } from '../store.js'
9 | import { createContext } from '../context.js'
10 | import { withStyles } from '../withStyles.js'
11 | import * as Router from '../components/router.js'
12 |
13 | // tagged templates
14 | import { jsx } from '../jsx.js'
15 |
16 | export default {
17 | Component,
18 | Fragment,
19 | Router,
20 | Store,
21 | createContext,
22 | h,
23 | jsx,
24 | render,
25 | task,
26 | tick,
27 | withStyles
28 | }
29 |
30 | // version
31 | export { printVersion } from '../helpers.js'
32 | export { VERSION } from '../version.js'
33 |
--------------------------------------------------------------------------------
/src/bundles/bundle.ui.ts:
--------------------------------------------------------------------------------
1 | // UI Elements
2 | import { AppBar } from '../ui/appBar.js'
3 | import { Toolbar } from '../ui/toolbar.js'
4 | import { Navigation, NavigationAction } from '../ui/navigation.js'
5 | import { Button } from '../ui/button.js'
6 | import { Dialog } from '../ui/dialog.js'
7 | import { Fab } from '../ui/fab.js'
8 | import { Icon } from '../ui/icon.js'
9 | import { List, ListItem } from '../ui/list.js'
10 | import { Menu } from '../ui/menu.js'
11 | import { Snackbar } from '../ui/snackbar.js'
12 |
13 | export default {
14 | AppBar,
15 | Toolbar,
16 | Navigation,
17 | NavigationAction,
18 | Button,
19 | Dialog,
20 | Fab,
21 | Icon,
22 | List,
23 | ListItem,
24 | Menu,
25 | Snackbar
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/img.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.js'
2 | import { h, strToHash } from '../core.js'
3 |
4 | interface Props {
5 | [key: string]: any
6 | src: string
7 | height?: number | string
8 | width?: number | string
9 | lazy?: boolean
10 | placeholder?: any
11 | }
12 |
13 | /**
14 | * A useful Image component
15 | * Add {
20 | constructor(props: Props) {
21 | super(props)
22 |
23 | const { src, key } = props
24 |
25 | // id has to be unique
26 | this.id = `${strToHash(src)}-${strToHash(JSON.stringify(props))}`
27 | if (key) this.id += `key-${key}`
28 |
29 | // this could also be done in willMount()
30 | if (!this.state) this.setState({ isLoaded: false, image: undefined })
31 | }
32 |
33 | didMount() {
34 | const { lazy = true, placeholder, children, key, ref, ...rest } = this.props
35 |
36 | if (typeof lazy === 'boolean' && lazy === false) return
37 |
38 | const observer = new IntersectionObserver(
39 | (entries, observer) => {
40 | entries.forEach(entry => {
41 | if (entry.isIntersecting) {
42 | observer.disconnect()
43 | this.state.image = h('img', { ...rest }) as HTMLImageElement
44 | if (this.state.image.complete) {
45 | this.state.isLoaded = true
46 | this.update()
47 | } else {
48 | this.state.image.onload = () => {
49 | this.state.isLoaded = true
50 | this.update()
51 | }
52 | }
53 | }
54 | })
55 | },
56 | { threshold: [0, 1] }
57 | )
58 | observer.observe(this.elements[0])
59 | }
60 | render() {
61 | const { src, placeholder, children, lazy = true, key, ref, ...rest } = this.props
62 |
63 | // return the img tag if not lazy loaded
64 | if (typeof lazy === 'boolean' && lazy === false) {
65 | this.state.image = h('img', { src, ...rest }) as HTMLImageElement
66 | return this.state.image
67 | }
68 |
69 | // if it is visible and loaded, show the image
70 | if (this.state.isLoaded) {
71 | return this.state.image
72 | // if the placeholder is an image src
73 | } else if (placeholder && typeof placeholder === 'string') {
74 | return h('img', { src: placeholder, ...rest })
75 | // if the placeholder is an JSX element
76 | } else if (placeholder && typeof placeholder === 'function') {
77 | return placeholder()
78 | } else {
79 | // render a simple box
80 | const style: Record = {}
81 | if (rest.width) style.width = `${rest.width}px`
82 | if (rest.height) style.height = `${rest.height}px`
83 | const { width, height, ...others } = rest
84 | return h('div', { style, ...others })
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { Helmet } from './helmet.js'
2 | export { Img } from './img.js'
3 | export { Link } from './link.js'
4 | export * as Router from './router.js'
5 | export { Suspense } from './suspense.js'
6 | export { Visible } from './visible.js'
7 |
--------------------------------------------------------------------------------
/src/components/suspense.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.js'
2 | import { isSSR, strToHash } from '../core.js'
3 |
4 | interface Props {
5 | fallback: any
6 | cache?: boolean
7 | [key: string]: any
8 | }
9 |
10 | export class Suspense extends Component {
11 | ready = false
12 |
13 | constructor(props: Props) {
14 | super(props)
15 |
16 | // get props promises in ...rest
17 | const { children, fallback, cache = false, ...rest } = this.props
18 |
19 | // stringify ...rest
20 | const str = JSON.stringify(rest, function (_key, val) {
21 | if (typeof val === 'function') return `${val}` // implicitly `toString` it
22 | return val
23 | })
24 |
25 | // create unique id based on ...rest
26 | this.id = strToHash(JSON.stringify(str))
27 | }
28 |
29 | async didMount() {
30 | // get props promises in ...rest
31 | const { children, fallback, cache = false, ...rest } = this.props
32 |
33 | // set initial state to []
34 | if (cache) this.initState = {}
35 |
36 | // check if we already cached the results in this.state
37 | if (this.loadFromCache(cache)) return
38 |
39 | // resolve the promises
40 | const promises = Object.values(rest).map(p => p())
41 | const resolved = await Promise.all(promises)
42 |
43 | // prepare data
44 | const data = this.prepareData(rest, resolved, cache)
45 |
46 | // add data to children
47 | this.addDataToChildren(data)
48 |
49 | // update the component
50 | this.ready = true
51 | this.update()
52 | }
53 |
54 | ssr() {
55 | // get props promises in ...rest
56 | const { children, fallback, cache = false, ...rest } = this.props
57 |
58 | // execute the functions
59 | const functions = Object.values(rest).map(p => p())
60 |
61 | // prepare data
62 | const data = this.prepareData(rest, functions, false)
63 |
64 | // add data to children
65 | this.addDataToChildren(data)
66 | }
67 |
68 | loadFromCache(cache: boolean) {
69 | const hasCachedProps = this.state && cache && Object.keys(this.state).length > 0
70 |
71 | if (hasCachedProps) {
72 | this.addDataToChildren(this.state)
73 | this.ready = true
74 | }
75 |
76 | return hasCachedProps
77 | }
78 |
79 | prepareData(rest: any, fnc: Function[], cache: boolean) {
80 | const data = Object.keys(rest).reduce((obj, item, index) => {
81 | if (cache) this.state = { ...this.state, [item]: fnc[index] }
82 | return {
83 | ...obj,
84 | [item]: fnc[index]
85 | }
86 | }, {})
87 | return data
88 | }
89 |
90 | addDataToChildren(data: any) {
91 | // add data as props to children
92 | this.props.children.forEach((child: any) => {
93 | if (child.props) child.props = { ...child.props, ...data }
94 | })
95 | }
96 |
97 | render() {
98 | if (!isSSR()) {
99 | const { cache = false } = this.props
100 | this.loadFromCache(cache)
101 | return !this.ready ? this.props.fallback : this.props.children
102 | } else {
103 | this.ssr()
104 | return this.props.children
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/components/visible.ts:
--------------------------------------------------------------------------------
1 | import { h, render } from '../core.js'
2 | import { Component } from '../component.js'
3 |
4 | export class Visible extends Component {
5 | isVisible = false
6 |
7 | didMount() {
8 | const observer = new IntersectionObserver(
9 | (entries, observer) => {
10 | entries.forEach(entry => {
11 | if (entry.isIntersecting) {
12 | observer.disconnect()
13 | this.isVisible = true
14 | this.update()
15 | }
16 | })
17 | },
18 | { threshold: [0, 1] }
19 | )
20 | observer.observe(this.elements[0])
21 | }
22 |
23 | render() {
24 | if (!this.isVisible) {
25 | return h('div', { 'data-visible': false, visibility: 'hidden' })
26 | } else {
27 | if (this.props.onVisible) this.props.onVisible()
28 | return render(this.props.component || this.props.children[0])
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/context.ts:
--------------------------------------------------------------------------------
1 | export const createContext = (ctx: any) => {
2 | let _ctx = ctx
3 | return {
4 | Provider: (props: any) => {
5 | if (props.value) _ctx = props.value
6 | return props.children
7 | },
8 | Consumer: (props: any) => {
9 | return { component: props.children[0](_ctx), props: { ...props, context: _ctx } }
10 | },
11 | get: () => _ctx,
12 | set: (ctx: any) => (_ctx = ctx)
13 | }
14 | }
15 |
16 | export const useContext = (ctx: any) => {
17 | const _ctx = ctx
18 | if (_ctx && typeof _ctx.get === 'function') {
19 | return _ctx.get()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/dev/components/isomorphic.tsx:
--------------------------------------------------------------------------------
1 | import { h, FC } from '../../core.js'
2 | import { Component } from '../../component.js'
3 |
4 | /**
5 | * This Component work on the client and the server.
6 | * (Make your you prefetch the static getData() and pass it as "data" props in SSR)
7 | */
8 |
9 | // simply mocks a server fetch and returns an array of comments
10 | const fetchMock = (): Promise =>
11 | new Promise(resolve => setTimeout(() => resolve(['comment_one', 'comment_two']), 500))
12 |
13 | // the comments component
14 | interface CommentsProps {
15 | comments: string[]
16 | }
17 | const Comments: FC = ({ comments }) => {
18 | console.log(comments)
19 | return (
20 |
21 | {comments.map(d => (
22 | {d}
23 | ))}
24 |
25 | )
26 | }
27 |
28 | // const Bla = () => {
29 | // console.log('BLA')
30 | // return asdf
31 | // }
32 |
33 | // the app component
34 | class App extends Component {
35 | // this static method can be calles before the componend is rendered in SSR mode
36 | static async getData() {
37 | // get some data from your server
38 | return await fetchMock()
39 | }
40 |
41 | async didMount() {
42 | // will re-render the component and pass the result of getData()
43 | const data = await App.getData()
44 | this.update(data)
45 | }
46 |
47 | render(data: string[]) {
48 | // this.props.data will be defined if in SSR mode
49 | data = data || this.props.data
50 |
51 | // console.log(Nano.h(Bla, null))
52 | if (data) return
53 | //
54 | // this will be shown while loading on the client side
55 | else return loading...
56 | }
57 | }
58 |
59 | export { App }
60 |
--------------------------------------------------------------------------------
/src/dev/dev.tsx:
--------------------------------------------------------------------------------
1 | import { h, render } from '../core.js'
2 | import { Component } from '../component.js'
3 |
4 | class App extends Component {
5 | render() {
6 | return Nano JSX App
7 | }
8 | }
9 |
10 | render( , document.getElementById('root'))
11 |
--------------------------------------------------------------------------------
/src/dev/devSSR.tsx:
--------------------------------------------------------------------------------
1 | import 'source-map-support/register'
2 |
3 | import { h } from '../core.js'
4 | import { renderSSR } from '../ssr.js'
5 | import { Component } from '../component.js'
6 | import { Helmet } from '../components/helmet.js'
7 |
8 | // @ts-ignore
9 | import fs from 'fs'
10 | // @ts-ignore
11 | import { join } from 'path'
12 | // @ts-ignore
13 | import http from 'http'
14 |
15 | class App extends Component {
16 | render() {
17 | return Nano JSX App
18 | }
19 | }
20 |
21 | const app = renderSSR( )
22 | const { body, head, footer } = Helmet.SSR(app)
23 |
24 | const html = `
25 |
26 |
27 |
28 |
29 |
34 | ${head.join('\n')}
35 |
36 |
37 |
38 | ${body}
39 |
40 |
41 | ${footer.join('\n')}
42 |
43 | `
44 |
45 | // minify
46 | // html = html.replace(/[\s]+/gm, ' ')
47 |
48 | http
49 | .createServer((req: any, res: any) => {
50 | const { url } = req
51 |
52 | if (/\.html$/.test(url)) return res.end(html)
53 |
54 | // @ts-ignore
55 | const path = join(__dirname, '../../', url)
56 |
57 | fs.readFile(path, (err: any, data: any) => {
58 | if (err) {
59 | res.writeHead(404)
60 | return res.end(data)
61 | }
62 | const type = /\.png$/.test(url) ? 'image/png' : 'image/svg+xml'
63 | res.setHeader('Content-Type', type)
64 | return res.end(data)
65 | })
66 | })
67 | .listen(8080, () => console.log('open http://localhost:8080/index.html in your browser'))
68 |
--------------------------------------------------------------------------------
/src/fragment.ts:
--------------------------------------------------------------------------------
1 | export const Fragment = (props: any) => {
2 | return props.children
3 | }
4 |
--------------------------------------------------------------------------------
/src/helpers.ts:
--------------------------------------------------------------------------------
1 | import { VERSION } from './version.js'
2 |
3 | /** Creates a new Task using setTimeout() */
4 | export const task = (task: () => void) => setTimeout(task, 0)
5 |
6 | export const nodeToString = (node: Node) => {
7 | const tmpNode = document.createElement('div')
8 | tmpNode.appendChild(node.cloneNode(true))
9 | return tmpNode.innerHTML
10 | }
11 |
12 | export const detectSSR = (): boolean => {
13 | // @ts-ignore
14 | const isDeno = typeof Deno !== 'undefined'
15 | const hasWindow = typeof window !== 'undefined' ? true : false
16 | return (typeof _nano !== 'undefined' && _nano.isSSR) || isDeno || !hasWindow
17 | }
18 |
19 | function isDescendant(desc: ParentNode | null, root: Node): boolean {
20 | // @ts-ignore
21 | return !!desc && (desc === root || isDescendant(desc.parentNode, root))
22 | }
23 |
24 | // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
25 | export const onNodeRemove = (element: HTMLElement, callback: () => void) => {
26 | let observer = new MutationObserver(mutationsList => {
27 | mutationsList.forEach(mutation => {
28 | mutation.removedNodes.forEach(removed => {
29 | if (isDescendant(element, removed)) {
30 | callback()
31 | if (observer) {
32 | // allow garbage collection
33 | observer.disconnect()
34 | // @ts-ignore
35 | observer = undefined
36 | }
37 | }
38 | })
39 | })
40 | })
41 | observer.observe(document, {
42 | childList: true,
43 | subtree: true
44 | })
45 | return observer
46 | }
47 |
48 | // https://stackoverflow.com/a/6234804
49 | export const escapeHtml = (unsafe: string) => {
50 | if (unsafe && typeof unsafe === 'string')
51 | return unsafe
52 | .replace(/&/g, '&')
53 | .replace(//g, '>')
55 | .replace(/"/g, '"')
56 | .replace(/'/g, ''')
57 | return unsafe
58 | }
59 |
60 | export const printVersion = () => {
61 | const info = `Powered by nano JSX v${VERSION}`
62 | console.log(
63 | `%c %c %c %c %c ${info} %c http://nanojsx.io`,
64 | 'background: #ff0000',
65 | 'background: #ffff00',
66 | 'background: #00ff00',
67 | 'background: #00ffff',
68 | 'color: #fff; background: #000000;',
69 | 'background: none'
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { useState, getState, setState } from './useState.js'
2 |
--------------------------------------------------------------------------------
/src/hooks/useState.ts:
--------------------------------------------------------------------------------
1 | import { _state } from '../state.js'
2 |
3 | export const useState = (state: T, id: string): readonly [T, (state: T) => void] => {
4 | const s = {
5 | setState(state: T) {
6 | if (state !== null) _state.set(id, state)
7 | },
8 | get state(): T {
9 | return _state.get(id)
10 | }
11 | }
12 |
13 | if (!_state.has(id)) _state.set(id, state)
14 |
15 | return [s.state, s.setState]
16 | }
17 |
18 | export const getState = (id: string) => {
19 | return _state.get(id)
20 | }
21 |
22 | export const setState = (id: string, state: any) => {
23 | return _state.set(id, state)
24 | }
25 |
--------------------------------------------------------------------------------
/src/htm.ts:
--------------------------------------------------------------------------------
1 | import htm from './htm/index.js'
2 | export default htm
3 |
--------------------------------------------------------------------------------
/src/htm/README.md:
--------------------------------------------------------------------------------
1 | All files in this folder are copied from version 3.0.4 of htm from https://github.com/developit/htm and ported to TypeScript to make it compatible with Deno.
2 |
3 | Please take a look at the [LICENSE](./LICENSE) file.
4 |
--------------------------------------------------------------------------------
/src/htm/constants.ts:
--------------------------------------------------------------------------------
1 | export const MINI = false
2 |
--------------------------------------------------------------------------------
/src/htm/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | import { MINI } from './constants.js'
15 | import { build, evaluate } from './build.js'
16 |
17 | const CACHES = new Map()
18 |
19 | const regular = function (this: HTMLElement, statics: TemplateStringsArray) {
20 | let tmp = CACHES.get(this)
21 | if (!tmp) {
22 | tmp = new Map()
23 | CACHES.set(this, tmp)
24 | }
25 | tmp = evaluate(this, tmp.get(statics) || (tmp.set(statics, (tmp = build(statics))), tmp), arguments, [])
26 | return tmp.length > 1 ? tmp : tmp[0]
27 | }
28 |
29 | // export as htm
30 | export default MINI ? build : regular
31 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // core
2 | export { h, render, hydrate, tick } from './core.js'
3 | export type { FC } from './core.js'
4 |
5 | // component
6 | export { Component } from './component.js'
7 |
8 | // built-in Components
9 | export * from './components/index.js'
10 |
11 | // export some defaults (Nano)
12 | import { h, render, hydrate, isSSR } from './core.js'
13 | import { renderSSR } from './ssr.js'
14 | export default { h, render, hydrate, renderSSR, isSSR }
15 |
16 | // other
17 | export { isSSR }
18 | export { jsx } from './jsx.js'
19 | export { hydrateLazy } from './lazy.js'
20 | export { nodeToString, task } from './helpers.js'
21 | export { renderSSR } from './ssr.js'
22 | export { Fragment } from './fragment.js'
23 | export { Store } from './store.js'
24 | export { createContext, useContext } from './context.js'
25 | export { withStyles } from './withStyles.js'
26 | export { defineAsCustomElements } from './customElementsMode.js'
27 |
28 | // version
29 | export { printVersion } from './helpers.js'
30 | export { VERSION } from './version.js'
31 |
--------------------------------------------------------------------------------
/src/jsx-runtime/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-const */
2 | export { Fragment } from '../fragment.js'
3 | import { h } from '../core.js'
4 |
5 | const createNode: (type: any, props: any, key: string, source?: string, self?: string) => any = function (type, props) {
6 | let { children = [], ..._props } = props
7 | if (!Array.isArray(children)) children = [children]
8 | return h(type, _props, ...children)
9 | }
10 |
11 | export { createNode as jsx }
12 | export { createNode as jsxs }
13 | export { createNode as jsxDev }
14 |
--------------------------------------------------------------------------------
/src/jsx.ts:
--------------------------------------------------------------------------------
1 | import { h } from './core.js'
2 | import htm from './htm.js'
3 | const jsx = htm.bind(h)
4 |
5 | export { jsx }
6 |
--------------------------------------------------------------------------------
/src/lazy.ts:
--------------------------------------------------------------------------------
1 | import { hydrate, h } from './core.js'
2 | import { Visible } from './components/visible.js'
3 |
4 | export const hydrateLazy = (component: any, parent: HTMLElement | null = null, removeChildNodes = true) => {
5 | const c = h(Visible, null, component)
6 | return hydrate(c, parent, removeChildNodes)
7 | }
8 |
--------------------------------------------------------------------------------
/src/mod.ts:
--------------------------------------------------------------------------------
1 | export * from './index.js'
2 |
--------------------------------------------------------------------------------
/src/ssr.ts:
--------------------------------------------------------------------------------
1 | import { isSSR, render } from './core.js'
2 | import { documentSSR } from './regexDom.js'
3 | import { _state } from './state.js'
4 | import { detectSSR } from './helpers.js'
5 |
6 | // functions that should only be available on the server-side
7 | const ssrTricks = {
8 | isWebComponent: (tagNameOrComponent: any) => {
9 | return (
10 | typeof tagNameOrComponent === 'string' &&
11 | tagNameOrComponent.includes('-') &&
12 | _nano.customElements.has(tagNameOrComponent)
13 | )
14 | },
15 | renderWebComponent: (tagNameOrComponent: any, props: any, children: any, _render: any) => {
16 | const customElement = _nano.customElements.get(tagNameOrComponent)
17 | const component = _render({ component: customElement, props: { ...props, children: children } })
18 | // get the html tag and the innerText from string
19 | // match[1]: HTMLTag
20 | // match[2]: innerText
21 | const match = component.toString().match(/^<(?[a-z]+)>(.*)<\/\k>$/)
22 | if (match) {
23 | const element = document.createElement(match[1])
24 | element.innerText = match[2]
25 |
26 | // eslint-disable-next-line no-inner-declarations
27 | function replacer(match: string, p1: string, _offset: number, _string: string): string {
28 | return match.replace(p1, '')
29 | }
30 | // remove events like onClick from DOM
31 | element.innerText = element.innerText.replace(/<\w+[^>]*(\s(on\w*)="[^"]*")/gm, replacer)
32 |
33 | return element
34 | } else {
35 | return null
36 | }
37 | }
38 | }
39 |
40 | const initGlobalVar = () => {
41 | const isSSR = detectSSR() === true ? true : undefined
42 | const location = { pathname: '/' }
43 | const document = isSSR ? documentSSR() : window.document
44 | globalThis._nano = { isSSR, location, document, customElements: new Map(), ssrTricks }
45 | }
46 | initGlobalVar()
47 |
48 | export const initSSR = (pathname: string = '/') => {
49 | _nano.location = { pathname }
50 | globalThis.document = _nano.document = isSSR() ? documentSSR() : window.document
51 | }
52 |
53 | export const renderSSR = (component: any, options: { pathname?: string; clearState?: boolean } = {}) => {
54 | const { pathname, clearState = true } = options
55 |
56 | initSSR(pathname)
57 | if (clearState) _state.clear()
58 |
59 | const tmp = render(component, null, true) as string | string[]
60 | if (Array.isArray(tmp)) return tmp.join('')
61 | else return Array.from(tmp).join('')
62 | }
63 |
64 | export const clearState = () => {
65 | _state.clear()
66 | }
67 |
--------------------------------------------------------------------------------
/src/state.ts:
--------------------------------------------------------------------------------
1 | /** Holds the state of the whole application. */
2 | export const _state: Map = new Map()
3 |
4 | /** Clears the state of the whole application. */
5 | export const _clearState = () => {
6 | _state.clear()
7 | }
8 |
--------------------------------------------------------------------------------
/src/store.ts:
--------------------------------------------------------------------------------
1 | import { isSSR } from './core.js'
2 |
3 | export class Store {
4 | private _state: S
5 | private _prevState: S
6 | private _listeners: Map = new Map()
7 | private _storage: 'memory' | 'local' | 'session'
8 | private _id: string
9 |
10 | /**
11 | * Create your own Store.
12 | * @param defaultState Pass the initial State.
13 | * @param name The name of the Store (only required if you persist the state in localStorage or sessionStorage).
14 | * @param storage Pass 'memory', 'local' or 'session'.
15 | */
16 | constructor(defaultState: Object, name: string = '', storage: 'memory' | 'local' | 'session' = 'memory') {
17 | if (isSSR()) storage = 'memory'
18 |
19 | this._id = name
20 | this._storage = storage
21 |
22 | this._state = this._prevState = defaultState as any
23 |
24 | if (storage === 'memory' || !storage) return
25 |
26 | const Storage = storage === 'local' ? localStorage : sessionStorage
27 |
28 | // get/set initial state of Storage
29 | const item = Storage.getItem(this._id)
30 | if (item) {
31 | this._state = this._prevState = JSON.parse(item)
32 | } else Storage.setItem(this._id, JSON.stringify(defaultState))
33 | }
34 |
35 | private persist(newState: S) {
36 | if (this._storage === 'memory') return
37 | const Storage = this._storage === 'local' ? localStorage : sessionStorage
38 | Storage.setItem(this._id, JSON.stringify(newState))
39 | }
40 |
41 | /** Clears the state of the whole store. */
42 | public clear() {
43 | // @ts-ignore
44 | this._state = this._prevState = undefined
45 |
46 | if (this._storage === 'local') localStorage.removeItem(this._id)
47 | else if (this._storage === 'session') sessionStorage.removeItem(this._id)
48 | }
49 |
50 | public setState(newState: S) {
51 | this.state = newState
52 | }
53 |
54 | public set state(newState: S) {
55 | this._prevState = this._state
56 | this._state = newState
57 |
58 | this.persist(newState)
59 |
60 | this._listeners.forEach(fnc => {
61 | fnc(this._state, this._prevState)
62 | })
63 | }
64 |
65 | public get state(): S {
66 | return this._state
67 | }
68 |
69 | public use() {
70 | const id = Math.random().toString(36).substring(2, 9)
71 | const _this = this
72 | return {
73 | get state(): S {
74 | return _this.state
75 | },
76 | setState: (newState: S) => {
77 | this.state = newState
78 | },
79 | subscribe: (fnc: (newState: S, prevState: S) => void) => {
80 | this._listeners.set(id, fnc)
81 | },
82 | cancel: () => {
83 | if (this._listeners.has(id)) this._listeners.delete(id)
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | // fixes an issue in std@0.80.0 (deno)
2 | // interface ReadableStream {
3 | // getIterator(): any
4 | // }
5 |
6 | declare global {
7 | export var _nano: {
8 | document: Document
9 | isSSR: true | undefined
10 | location: { pathname: string }
11 | customElements: Map
12 | ssrTricks: {
13 | isWebComponent: (tagNameOrComponent: any) => boolean
14 | renderWebComponent: (tagNameOrComponent: any, props: any, children: any, _render: any) => any
15 | }
16 | }
17 |
18 | export namespace Deno {}
19 |
20 | export namespace JSX {
21 | interface IntrinsicElements {
22 | [elemName: string]: any
23 | }
24 | interface ElementClass {
25 | render: any
26 | }
27 | interface ElementChildrenAttribute {
28 | children: any
29 | }
30 | }
31 | }
32 | // This export keeps the backward compatibility with the module resolution system in deno < 1.23 version.
33 | export {}
34 |
--------------------------------------------------------------------------------
/src/ui/README.md:
--------------------------------------------------------------------------------
1 | # UI Elements
2 |
3 | These ui elements should look very similar to the [Material UI Components](https://material.io/components).
4 |
5 | Feel free to improve the elements or add new once.
6 |
--------------------------------------------------------------------------------
/src/ui/_config.ts:
--------------------------------------------------------------------------------
1 | import { strToHash } from '../core.js'
2 |
3 | export const boxShadow = `
4 | -webkit-box-shadow: 0px 3px 5px -1px rgba(0,0,0,0.2), 0px 6px 10px 0px rgba(0,0,0,0.14), 0px 1px 18px 0px rgba(0,0,0,0.12);
5 | -moz-box-shadow: 0px 3px 5px -1px rgba(0,0,0,0.2), 0px 6px 10px 0px rgba(0,0,0,0.14), 0px 1px 18px 0px rgba(0,0,0,0.12);
6 | box-shadow: 0px 3px 5px -1px rgba(0,0,0,0.2), 0px 6px 10px 0px rgba(0,0,0,0.14), 0px 1px 18px 0px rgba(0,0,0,0.12);
7 | `
8 |
9 | export const userSelect = `
10 | -webkit-touch-callout:none;
11 | -webkit-user-select:none;
12 | -khtml-user-select:none;
13 | -moz-user-select:none;
14 | -ms-user-select:none;
15 | user-select:none;
16 | -webkit-tap-highlight-color:rgba(0,0,0,0);
17 | `
18 |
19 | export const rippleEffect = (rippleClr: string, hoverClr: string) => {
20 | const rippleClass = `ripple-${strToHash(rippleClr + hoverClr)}`
21 |
22 | const styles = `
23 | .${rippleClass} {
24 | background-position: center;
25 | transition: background 0.8s;
26 | }
27 |
28 | .${rippleClass}:hover {
29 | background: ${hoverClr} radial-gradient(circle, transparent 1%, ${hoverClr} 1%) center/15000%;
30 | }
31 |
32 | .${rippleClass}:focus {
33 | background: ${hoverClr} radial-gradient(circle, transparent 1%, ${hoverClr} 1%) center/15000%;
34 | }
35 |
36 | .${rippleClass}:active {
37 | background-color: ${rippleClr};
38 | background-size: 100%;
39 | transition: background 0s;
40 | }`
41 |
42 | return {
43 | styles,
44 | class: rippleClass
45 | }
46 | }
47 |
48 | export const zIndex = {
49 | button: 'unset;',
50 | banner: '50;',
51 | bar: '100;',
52 | navigation: '100;',
53 | fab: '200;',
54 | sheet: '300;',
55 | menu: '400;',
56 | snackbar: '500;',
57 | dialog: '600;'
58 | }
59 |
--------------------------------------------------------------------------------
/src/ui/_helpers.ts:
--------------------------------------------------------------------------------
1 | import { h } from '../core.js'
2 |
3 | // https://gist.github.com/renancouto/4675192
4 | export const lightenColor = (color: string, percent: number) => {
5 | const num = parseInt(color.replace('#', ''), 16),
6 | amt = Math.round(2.55 * percent),
7 | R = (num >> 16) + amt,
8 | B = ((num >> 8) & 0x00ff) + amt,
9 | G = (num & 0x0000ff) + amt
10 | return `#${(
11 | 0x1000000 +
12 | (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
13 | (B < 255 ? (B < 1 ? 0 : B) : 255) * 0x100 +
14 | (G < 255 ? (G < 1 ? 0 : G) : 255)
15 | )
16 | .toString(16)
17 | .slice(1)}`
18 | }
19 |
20 | export const addStylesToHead = (styles: string, hash: string) => {
21 | const el = document.querySelector(`[data-css-hash*="${hash}"]`)
22 | if (!el) {
23 | const styleElement = h('style', { 'data-css-hash': hash }, styles)
24 | document.head.appendChild(styleElement)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/ui/banner.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.js'
2 | import { h } from '../core.js'
3 | import { Button } from './button.js'
4 | import { zIndex } from './_config.js'
5 |
6 | interface BannerAction {
7 | name: string
8 | id?: string | number
9 | color?: string
10 | }
11 |
12 | interface BannerActionEvent {
13 | action: string
14 | }
15 |
16 | interface BannerProps {
17 | title?: string
18 | body?: string
19 | actions?: BannerAction[]
20 | onAction?: () => BannerActionEvent
21 | parentId?: string
22 | sticky?: number
23 | }
24 |
25 | export class Banner extends Component {
26 | defaultActionColor = '#6200EE'
27 |
28 | render() {
29 | const {
30 | sticky,
31 | body = 'There was a problem processing a transaction on your credit card.',
32 | actions = [
33 | { name: 'fix it', color: this.defaultActionColor },
34 | { name: 'learn more', color: this.defaultActionColor }
35 | ]
36 | } = this.props
37 |
38 | const stickyStyles = sticky
39 | ? `
40 | position: -webkit-sticky;
41 | position: sticky;
42 | top: ${sticky}px;`
43 | : ''
44 |
45 | const styles = {
46 | container: `
47 | margin: -16px -16px 16px -16px;
48 | ${stickyStyles}
49 | z-index: ${zIndex.banner}
50 | `,
51 | banner: `
52 |
53 | display: flex;
54 | justify-content: space-between;
55 | flex-wrap: wrap;
56 |
57 |
58 | background: white;
59 | border-bottom: 1px rgb(0 0 0 / 0.12) solid;
60 | min-height: 54px;`,
61 | body: `
62 | color: #000000;
63 | padding: 16px;
64 | padding-bottom: 8px;
65 | font-size: 16px;
66 | line-height: 1.5em;`,
67 | actions: `
68 | margin: 0;
69 | padding: 8px;
70 | display: flex;
71 | flex-direction: row;
72 | align-items: flex-end;
73 | margin-left: auto;
74 | flex-wrap: wrap;
75 | justify-content: flex-end;
76 | `,
77 | action: `
78 | margin-bottom: 0px;
79 | margin-left: 10px;`
80 | }
81 |
82 | const actionsArray = actions.map((action: any) => {
83 | return h(
84 | Button,
85 | {
86 | text: true,
87 | color: action.color || this.defaultActionColor,
88 | style: styles.action,
89 | // style: `color: ${action.color || this.defaultActionColor}`,
90 | onClick: () => {
91 | // callback({ name: action.name, id: action.id })
92 | // this.remove()
93 | }
94 | },
95 | action.name.toUpperCase()
96 | )
97 | })
98 |
99 | const bodyElement = h('div', { style: styles.body }, body)
100 | const actionsElement = h('div', { style: styles.actions }, actionsArray)
101 | const bannerElement = h('div', { style: styles.banner }, bodyElement, actionsElement)
102 |
103 | return h('div', { style: styles.container }, bannerElement)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/ui/button.ts:
--------------------------------------------------------------------------------
1 | import { h, strToHash } from '../core.js'
2 | import { boxShadow, rippleEffect, userSelect, zIndex } from './_config.js'
3 | import { addStylesToHead, lightenColor } from './_helpers.js'
4 | import { Icon } from './icon.js'
5 |
6 | export const Button = (props: {
7 | outlined?: boolean
8 | text?: boolean
9 | style?: string
10 | icon?: string
11 | [key: string]: any
12 | }) => {
13 | const {
14 | children,
15 | outlined = false,
16 | text = false,
17 | background = '#6200ee',
18 | color = '#ffffff',
19 | style = '',
20 | class: className = '',
21 | icon,
22 | ...rest
23 | } = props
24 |
25 | const normal = !(outlined || text)
26 |
27 | const bg = normal ? background : '#ffffff'
28 | const clr = normal ? color : background
29 | const hoverClr = normal ? lightenColor(bg, 10) : lightenColor(bg, -10)
30 | const rippleClr = normal ? lightenColor(bg, 50) : lightenColor(background, 50)
31 | const cssHash = strToHash(outlined.toString() + text.toString() + bg + clr + style)
32 |
33 | const ripple = rippleEffect(rippleClr, hoverClr)
34 |
35 | const styles = `
36 | .nano_jsx_button-${cssHash} {
37 | color: ${clr};
38 | background: ${bg};
39 | border-radius: 4px;
40 | display: inline-flex;
41 | font-size: 14px;
42 | padding: 10px 16px;
43 | margin: 0px 0px 1em 0px;
44 | text-align: center;
45 | cursor: pointer;
46 |
47 | ${userSelect}
48 |
49 |
50 | z-index: ${zIndex.button}
51 |
52 | ${boxShadow}
53 |
54 | border: none;
55 | text-transform: uppercase;
56 | box-shadow: 0 0 4px #999;
57 | outline: none;
58 | }
59 |
60 | ${ripple.styles}
61 | `
62 |
63 | addStylesToHead(styles, cssHash)
64 |
65 | let customStyles = ''
66 | if (outlined || text) {
67 | customStyles += 'padding-top: 9px; padding-bottom: 9px; '
68 | customStyles += '-webkit-box-shadow: none; -moz-box-shadow: none; box-shadow none; '
69 | if (outlined) customStyles += `border: 1px ${clr} solid; `
70 | }
71 | customStyles += style
72 |
73 | return h(
74 | 'button',
75 | { class: `nano_jsx_button-${cssHash} ${ripple.class} ${className}`, style: customStyles, ...rest },
76 | icon ? h(Icon, { style: 'margin-left: -4px; margin-right: 8px; width: 14px; height: 14px;' }, icon) : null,
77 | children
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/src/ui/fab.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.js'
2 | import { h, strToHash } from '../core.js'
3 | import { boxShadow, userSelect, zIndex } from './_config.js'
4 | import { addStylesToHead } from './_helpers.js'
5 |
6 | interface FabProps {
7 | onClick?: (e: MouseEvent) => void
8 | offsetY?: number
9 | center?: boolean
10 | left?: boolean
11 | extended?: boolean
12 | mini?: boolean
13 | background?: string
14 | color?: string
15 | }
16 |
17 | export class Fab extends Component {
18 | render() {
19 | const {
20 | background = '#6200EE',
21 | color = 'white',
22 | extended = false,
23 | mini = false,
24 | center = false,
25 | left = false,
26 | onClick = () => {}
27 | } = this.props
28 |
29 | const height = mini ? 40 : extended ? 48 : 56
30 | const cssHash = strToHash(extended.toString() + mini.toString() + center.toString() + left.toString())
31 | const className = `fab-container-${cssHash}`
32 |
33 | const styles = `
34 | .${className} {
35 | ${mini ? 'width: 40px;' : extended ? 'padding: 0px 12px;' : 'width: 56px;'}
36 | height: ${height}px;
37 | position: fixed;
38 | background: ${background};
39 | border-radius: 36px;
40 | display: flex;
41 | align-items: center;
42 | justify-content: center;
43 | color: ${color};
44 | cursor: pointer;
45 |
46 | z-index: ${zIndex.fab}
47 | bottom: ${this.props.offsetY ? 16 + this.props.offsetY : 16}px;
48 | ${left ? 'left: 16px;' : 'right: 16px;'}
49 | ${center ? 'transform: translateX(50%); right: 50%;' : ''}
50 | ${boxShadow}
51 | ${userSelect}
52 | }
53 | `
54 |
55 | addStylesToHead(styles, cssHash)
56 |
57 | const { children } = this.props as any
58 |
59 | return h('div', { class: className, onClick: (e: MouseEvent) => onClick(e) }, children)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/ui/icon.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.js'
2 | import { h, strToHash } from '../core.js'
3 | import { addStylesToHead } from './_helpers.js'
4 |
5 | interface IconProps {
6 | src: string
7 | active?: boolean
8 | color?: string
9 | style?: string
10 | size?: number
11 | onClick?: (e: MouseEvent) => void
12 | }
13 |
14 | export class Icon extends Component {
15 | cssHash: string
16 |
17 | didUnmount() {
18 | // not sure if I want to remove the css, since there might be another Icon with the same styles.
19 | // const el = document.querySelector(`[data-css-hash*="${this.cssHash}"]`)
20 | // if (el) el.remove()
21 | }
22 |
23 | render() {
24 | const { src, size = 16, active = true, color = '#6204EE', style = '', ...rest } = this.props
25 |
26 | // @ts-ignore
27 | const children = this.props.children
28 |
29 | this.cssHash = strToHash(active + color + size.toString())
30 |
31 | const colors = {
32 | active: color,
33 | inactive: '#00000070'
34 | }
35 |
36 | const styles = `
37 | i.icon-${this.cssHash} {
38 | width: ${size}px;
39 | height: ${size}px;
40 | display: inline-block;
41 | content: '';
42 |
43 | /*-webkit-mask: url(YOUR_SVG_URL) no-repeat 50% 50%;
44 | mask: url(YOUR_SVG_URL) no-repeat 50% 50%;*/
45 |
46 | -webkit-mask-size: cover;
47 | mask-size: cover;
48 |
49 | background-color: ${colors.active};
50 | }
51 |
52 | i.icon-${this.cssHash}.icon_inactive-${this.cssHash} {
53 | background-color: ${colors.inactive};
54 | }
55 | `
56 |
57 | addStylesToHead(styles, this.cssHash)
58 |
59 | // const iconStyle = `-webkit-mask: url(/dev/font-awesome/ellipsis-v-solid.svg) no-repeat 50% 50%;mask: url(/dev/font-awesome/ellipsis-v-solid.svg) no-repeat 50% 50%;`
60 | const iconStyle = `-webkit-mask: url(${src || children}) no-repeat 50% 50%; mask: url(${
61 | src || children
62 | }) no-repeat 50% 50%;`
63 |
64 | const classes = [`icon-${this.cssHash}`]
65 | if (!active) classes.push(`icon_inactive-${this.cssHash}`)
66 |
67 | const icon = h('i', { class: classes.join(' '), ...rest, style: iconStyle + style })
68 |
69 | return icon
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/ui/index.ts:
--------------------------------------------------------------------------------
1 | export { AppBar } from '../ui/appBar.js'
2 | export { Toolbar } from '../ui/toolbar.js'
3 | export { Navigation, NavigationAction } from '../ui/navigation.js'
4 | export { Button } from '../ui/button.js'
5 | export { Dialog } from '../ui/dialog.js'
6 | export { Fab } from '../ui/fab.js'
7 | export { Icon } from '../ui/icon.js'
8 | export { List, ListItem } from '../ui/list.js'
9 | export { Menu } from '../ui/menu.js'
10 | export { Snackbar } from '../ui/snackbar.js'
11 | export { Tabs, Tab } from '../ui/tabs.js'
12 | export { Sheet } from '../ui/sheet.js'
13 |
--------------------------------------------------------------------------------
/src/ui/list.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '../component.js'
2 | import { h, strToHash } from '../core.js'
3 | import { Icon } from './icon.js'
4 | import { addStylesToHead } from './_helpers.js'
5 |
6 | interface ListProps {
7 | small?: boolean
8 | children?: any
9 | }
10 |
11 | interface ListItemProps {
12 | onClick?: Function
13 | icon?: string
14 | avatar?: string
15 | square?: string
16 | image?: string
17 | children?: any
18 | }
19 |
20 | export class ListItem extends Component {
21 | render() {
22 | const { props: p } = this
23 | const { onClick = () => {} } = p
24 |
25 | const adjustedMargin = 'margin-right: 16px;'
26 |
27 | const icon = p.icon ? h(Icon, { size: 20, style: 'margin-right: 32px;', src: p.icon }) : null
28 | const avatar = p.avatar
29 | ? h('img', { src: p.avatar, width: 40, height: 40, style: `border-radius: 20px; ${adjustedMargin}` })
30 | : null
31 | const square = p.square ? h('img', { src: p.square, width: 56, height: 56, style: adjustedMargin }) : null
32 | const image = p.image
33 | ? h('img', { src: p.image, width: 100, height: 56, style: 'margin-left: -16px; margin-right: 16px;' })
34 | : null
35 | const text = h('span', null, p.children)
36 |
37 | // additional style for the list item
38 | let style = ''
39 | if (p.icon || p.avatar) style += 'min-height: 56px; '
40 | if (p.square || p.image) style += 'min-height: 72px; '
41 |
42 | return h('li', { style, onClick }, icon, avatar, square, image, text)
43 | }
44 | }
45 |
46 | export class List extends Component {
47 | cssHash: string
48 |
49 | render() {
50 | const { small = false } = this.props
51 |
52 | this.cssHash = strToHash(`List${small.toString()}`)
53 |
54 | const styles = `
55 | .list-${this.cssHash} ul {
56 | margin: 0px;
57 | padding: 8px 16px;
58 | }
59 |
60 | .list-${this.cssHash} ul li {
61 | list-style: none;
62 | min-height: ${small ? 32 : 46}px;
63 | display: flex;
64 | align-items: center;
65 | margin: 0px -16px;
66 | padding: 0px 16px;
67 | cursor: pointer;
68 | }
69 |
70 | .list-${this.cssHash} ul li span {
71 | font-size: 16px;
72 | }
73 |
74 | .list-${this.cssHash} ul li:hover {
75 | background:#00000010
76 | }
77 | `
78 |
79 | addStylesToHead(styles, this.cssHash)
80 |
81 | const ul = h('ul', null, this.props.children)
82 | return h('div', { class: `list-${this.cssHash}` }, ul)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/ui/menu.ts:
--------------------------------------------------------------------------------
1 | import { h, removeAllChildNodes, render } from '../core.js'
2 | import { boxShadow, zIndex } from './_config.js'
3 | import { addStylesToHead } from './_helpers.js'
4 |
5 | interface MenuOptions {
6 | position: { x: number; y: number }
7 | list: any
8 | }
9 |
10 | export class Menu {
11 | defaultParentId = 'menu_items_container'
12 | cssHash = Math.random().toString(36).substring(2)
13 |
14 | // didUnmount() {
15 | // const el = document.querySelector(`[data-css-hash*="${this.cssHash}"]`)
16 | // if (el) el.remove()
17 | // }
18 |
19 | private getParentElement(id: string) {
20 | // delete all other
21 | const others = document.querySelectorAll(`[id^="${this.defaultParentId}"]`)
22 | others.forEach(e => {
23 | e.remove()
24 | })
25 |
26 | let el = document.getElementById(`${this.defaultParentId}-${id}`)
27 | if (!el) {
28 | el = document.createElement('div')
29 | el.id = `${this.defaultParentId}-${id}`
30 | }
31 |
32 | removeAllChildNodes(el)
33 | document.body.appendChild(el)
34 |
35 | return el
36 | }
37 |
38 | close() {
39 | removeAllChildNodes(this.getParentElement(this.cssHash))
40 | }
41 |
42 | open(menuOptions: MenuOptions) {
43 | const { position, list } = menuOptions
44 |
45 | // check in which corner the menu appears and adjust fixed position.
46 | const left = position.x < window.innerWidth / 2 ? 'left' : 'right'
47 | const top = position.y < window.innerHeight / 2 ? 'top' : 'bottom'
48 |
49 | const styles = `
50 |
51 | #menu_items_background-${this.cssHash} {
52 | width: 100vw;
53 | height: 100vh;
54 | background: transparent;
55 | position: fixed;
56 | top: 0;
57 | left: 0;
58 | z-index: ${zIndex.menu}
59 | }
60 |
61 | #menu_items_list-${this.cssHash} {
62 | position: fixed;
63 | background: white;
64 |
65 | border-radius: 4px;
66 | min-width: 112px;
67 |
68 | ${top}: ${position.y > window.innerHeight / 2 ? window.innerHeight - position.y : position.y}px;
69 | ${left}: ${position.x > window.innerWidth / 2 ? window.innerWidth - position.x : position.x}px;
70 |
71 | z-index: ${zIndex.menu}
72 |
73 | ${boxShadow}
74 | }
75 |
76 | `
77 | // remove old styles
78 | const el = document.querySelector(`[data-css-hash*="${this.cssHash}"]`)
79 | if (el) el.remove()
80 |
81 | // add new styles
82 | addStylesToHead(styles, this.cssHash)
83 |
84 | const itemsList = h('div', { id: `menu_items_list-${this.cssHash}` }, list)
85 | const itemsBg = h('div', { onClick: () => this.close(), id: `menu_items_background-${this.cssHash}` }, itemsList)
86 |
87 | itemsList.addEventListener('click', (e: Event) => e.stopPropagation())
88 |
89 | this.getParentElement(this.cssHash).appendChild(render(itemsBg))
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/version.ts:
--------------------------------------------------------------------------------
1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | export const VERSION = '0.1.0'
3 |
--------------------------------------------------------------------------------
/src/withStyles.ts:
--------------------------------------------------------------------------------
1 | import { h } from './core.js'
2 | import { Component } from './component.js'
3 | import { Fragment } from './fragment.js'
4 | import { Helmet } from './components/helmet.js'
5 |
6 | interface ObjectHasToString {
7 | toString: () => string
8 | }
9 | type Styles = string
10 | type FunctionReturnsString = () => string
11 | type WithStylesType = Styles | ObjectHasToString | FunctionReturnsString
12 |
13 | export const withStyles: any =
14 | (...styles: WithStylesType[]) =>
15 | (WrappedComponent: any) => {
16 | return class extends Component {
17 | render() {
18 | const { children, ...rest } = this.props
19 |
20 | const helmets: any[] = []
21 | styles.forEach(style => {
22 | if (typeof style === 'string') {
23 | helmets.push(h(Helmet, null, h('style', null, style)))
24 | } else if (typeof style === 'function') {
25 | const _style = style()
26 | if (typeof _style === 'string') {
27 | helmets.push(h(Helmet, null, h('style', null, _style)))
28 | }
29 | } else if (typeof style === 'object') {
30 | const _style = style.toString?.()
31 | if (typeof _style === 'string') {
32 | helmets.push(h(Helmet, null, h('style', null, _style)))
33 | }
34 | }
35 | })
36 |
37 | const component =
38 | children && children.length > 0
39 | ? h(WrappedComponent, { ...rest }, children)
40 | : h(WrappedComponent, { ...this.props })
41 |
42 | return h(Fragment, null, ...helmets, component)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test.jsx-runtime/nodejs/client.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, Component, Helmet, isSSR } from '../../lib/index.js'
2 | import { initSSR } from '../../lib/ssr.js'
3 | import { wait, nodeToString } from '../../test/nodejs/helpers.js'
4 |
5 | const spy = jest.spyOn(global.console, 'error')
6 |
7 | describe('jsx-runtime (client-side)', () => {
8 | test('should render without errors', async () => {
9 | const Root = () => (
10 |
11 |
Hello
12 |
13 | )
14 | const html = render(Root)
15 | await wait()
16 | expect(nodeToString(html)).toBe('
Hello ')
17 | expect(spy).not.toHaveBeenCalled()
18 | })
19 |
20 | test('should render without errors', async () => {
21 | const Root = () => (
22 |
23 |
24 | TITLE
25 |
26 |
27 |
Hello
28 |
29 | )
30 | render(Root, document.body)
31 | await wait()
32 | expect(nodeToString(document.head)).toBe(
33 | 'TITLE '
34 | )
35 | expect(nodeToString(document.body)).toBe('
Hello ')
36 | expect(spy).not.toHaveBeenCalled()
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/test.jsx-runtime/nodejs/server.tsx:
--------------------------------------------------------------------------------
1 | import { renderSSR, Component, Helmet, isSSR } from '../../lib/index.js'
2 | import { detectSSR } from '../../lib/helpers.js'
3 | import { initSSR } from '../../lib/ssr.js'
4 |
5 | // I don't use jest here because it generated too many unexpected errors.
6 |
7 | let hasErrors = false
8 | const tests = []
9 |
10 | const expect = one => {
11 | return {
12 | toBe: two => {
13 | const match = one === two
14 | if (match === false) {
15 | hasErrors = true
16 | console.log(`> ERROR: "${one} does NOT match "${two}"`)
17 | } else {
18 | console.log('> ok')
19 | }
20 | return match
21 | }
22 | }
23 | }
24 |
25 | const test = fnc => {
26 | tests.push(fnc)
27 | }
28 |
29 | test(() => {
30 | const Root = () => (
31 |
32 |
Hello
33 |
34 | )
35 | initSSR('/')
36 | const html = renderSSR(() => )
37 | expect(html).toBe('
Hello ')
38 | })
39 |
40 | test(() => {
41 | const Root = () => (
42 |
43 |
44 | TITLE
45 |
46 |
47 |
Hello
48 |
49 | )
50 | const html = renderSSR( )
51 | const { body, head } = Helmet.SSR(html)
52 | expect(head[0]).toBe('TITLE ')
53 | expect(body).toBe('
Hello ')
54 | })
55 |
56 | // Children as Component
57 | test(() => {
58 | const Child = () => Hello
59 | const Root = (props: any) => {props.children}
60 | const html = renderSSR(
61 |
62 |
63 |
64 | )
65 | expect(html).toBe('
Hello ')
66 | })
67 |
68 | // Children as Props
69 | test(() => {
70 | const Child = () => Hello
71 | const Root = (props: any) => {props.children}
72 | const html = renderSSR( )
73 | expect(html).toBe('
Hello ')
74 | })
75 |
76 | // Children as Props (Array)
77 | test(() => {
78 | const Child = () => Hello
79 | const Root = (props: any) => {props.children}
80 | const html = renderSSR( )
81 | expect(html).toBe('
Hello ')
82 | })
83 |
84 | for (const test of tests) {
85 | test()
86 | }
87 |
88 | if (hasErrors) {
89 | process.exit(1)
90 | } else {
91 | process.exit(0)
92 | }
93 |
--------------------------------------------------------------------------------
/test/browser/className.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/browser/component.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/test/browser/dangerous.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/test/browser/fragment.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/test/browser/simple.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/test/browser/svg.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/browser/webComponent.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
34 |
35 |
36 |
37 | invisible
38 | John Doe
39 |
40 |
41 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/test/browser/withStyles.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/test/deno/deno.test.tsx:
--------------------------------------------------------------------------------
1 | import { h, Helmet, renderSSR, Component } from '../../deno_lib/mod.ts'
2 | import { assertEquals } from 'https://deno.land/std@0.115.1/testing/asserts.ts'
3 |
4 | Deno.test('should render without errors', () => {
5 | const comments = ['Comment One', 'Comment Two']
6 |
7 | class Comments extends Component {
8 | render() {
9 | return (
10 |
11 | {this.props.comments.map((comment: any) => {
12 | return {comment}
13 | })}
14 |
15 | )
16 | }
17 | }
18 |
19 | const App = () => (
20 |
21 |
22 | Nano JSX SSR
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
Comments
31 |
34 |
35 | )
36 |
37 | const ssr = renderSSR( )
38 | const { body, head, footer } = Helmet.SSR(ssr)
39 |
40 | assertEquals(
41 | body,
42 | '
Comments '
43 | )
44 | assertEquals(head.join('\n'), 'Nano JSX SSR ')
45 | assertEquals(footer.join('\n'), '')
46 | })
47 |
--------------------------------------------------------------------------------
/test/deno/nanossr.test.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | ///
3 | ///
4 | ///
5 | ///
6 |
7 | /**
8 | * test for https://crux.land/nanossr@0.0.1 and https://dash.deno.com/playground/example-nanossr
9 | */
10 |
11 | import { assertStringIncludes } from 'https://deno.land/std@0.115.1/testing/asserts.ts'
12 |
13 | import { h, FC } from '../../deno_lib/mod.ts'
14 | import { tw } from 'https://cdn.skypack.dev/twind'
15 |
16 | import { Helmet, renderSSR as nanoRender } from '../../deno_lib/mod.ts'
17 | import { setup } from 'https://cdn.skypack.dev/twind'
18 | import { getStyleTag, virtualSheet } from 'https://cdn.skypack.dev/twind/sheets'
19 | import typography from 'https://cdn.skypack.dev/@twind/typography'
20 |
21 | let SHEET_SINGLETON: any = null
22 | function sheet(twOptions = {}) {
23 | return SHEET_SINGLETON ?? (SHEET_SINGLETON = setupSheet(twOptions))
24 | }
25 |
26 | // Setup TW sheet singleton
27 | function setupSheet(twOptions: Record) {
28 | const sheet = virtualSheet()
29 | setup({ ...twOptions, sheet, plugins: { ...typography() } })
30 | return sheet
31 | }
32 |
33 | interface MakeHtml {
34 | body: string
35 | head: string[]
36 | footer: string[]
37 | styleTag: string
38 | }
39 | const html = ({ body, head, footer, styleTag }: MakeHtml): string => `
40 |
41 |
42 |
43 |
44 |
45 | ${head.join('\n')}
46 | ${styleTag}
47 |
48 |
49 | ${body}
50 | ${footer.join('\n')}
51 |
52 |
53 | `
54 |
55 | export function ssr(render: CallableFunction, options?: any) {
56 | sheet(options?.tw ?? {}).reset()
57 | const app = nanoRender(render())
58 | const { body, head, footer } = Helmet.SSR(app)
59 | const styleTag = getStyleTag(sheet())
60 | return /*new Response*/ html({ body, head, footer, styleTag }) //, { headers: { 'content-type': 'text/html' } }
61 | }
62 |
63 | const Hello: FC = props => (
64 |
65 |
Hello {props.name}!
66 |
67 | )
68 |
69 | // console.log('Listening on http://localhost:8080')
70 | // await listenAndServe(':8080', req => {
71 | // const url = new URL(req.url)
72 | // const name = url.searchParams.get('name') ?? 'world'
73 | // return ssr(() => )
74 | // })
75 |
76 | Deno.test('should render without errors', () => {
77 | const res = ssr(() => )
78 |
79 | // simply test some string outputs
80 | assertStringIncludes(res, '')
81 | assertStringIncludes(res, 'Hello deno!')
82 | assertStringIncludes(res, '
58 | )
59 |
60 | expect(head[0]).toContain(
61 | '@import url("https://fonts.googleapis.com/css?family=Montserrat:400,700|Roboto:100,300,400"'
62 | )
63 | expect(head[0]).toContain('')
31 | expect(spy).not.toHaveBeenCalled()
32 | })
33 |
34 | // fails in jest, but works in the browser :/
35 | xtest('should render without errors', async () => {
36 | class AppA extends Component {
37 | render() {
38 | return (
39 |
42 | )
43 | }
44 | }
45 |
46 | const App = withStyles('CSS1', () => 'CSS2', { toString: () => 'CSS3' })(AppA)
47 |
48 | Nano.render( , document.body)
49 |
50 | await wait()
51 |
52 | expect(document.head.innerHTML).toBe('')
53 | expect(spy).not.toHaveBeenCalled()
54 | })
55 |
56 | test('should not escape webfonts and single quotes', async () => {
57 | class AppA extends Component {
58 | render() {
59 | return {this.props.children}
60 | }
61 | }
62 |
63 | const App = withStyles(`
64 | @import url("https://fonts.googleapis.com/css?family=Montserrat:400,700|Roboto:100,300,400");
65 |
66 | body {
67 | font-family: 'Montserrat', 'Roboto', sans-serif;
68 | }
69 | `)(AppA)
70 |
71 | Nano.render(
72 |
73 | with styles
74 | ,
75 | document.body
76 | )
77 |
78 | await wait()
79 | expect(multiLineToSingleLine(document.head.innerHTML)).toBe(
80 | ""
81 | )
82 | expect(spy).not.toHaveBeenCalled()
83 | })
84 |
--------------------------------------------------------------------------------
/test/scripts.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import { readFile, readdir, writeFile } from 'fs/promises'
4 | import { resolve } from 'path'
5 |
6 | const args = process.argv.splice(2)
7 | const arg0 = args[0]
8 |
9 | async function* getFiles(dir) {
10 | const dirents = await readdir(dir, { withFileTypes: true })
11 | for (const dirent of dirents) {
12 | const res = resolve(dir, dirent.name)
13 | if (dirent.isDirectory()) {
14 | yield* getFiles(res)
15 | } else {
16 | yield res
17 | }
18 | }
19 | }
20 |
21 | if (arg0 === 'add-jest-environment') {
22 | const str = '/**\n* @jest-environment node\n*/\n\n'
23 |
24 | for await (const f of getFiles('test')) {
25 | if (/ssr\.test\.js/.test(f) || /e2e\/core\.test\.js/.test(f)) {
26 | const file = await readFile(f, { encoding: 'utf-8' })
27 | await writeFile(f, str + file, { encoding: 'utf-8' })
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tsconfig.deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"],
4 | "jsxFactory": "h",
5 | "jsxFragmentFactory": "Fragment"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./esm",
5 | "target": "ES2018",
6 | "module": "ES2015"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./lib",
4 | "rootDir": "./src",
5 |
6 | "target": "ES2018",
7 | "module": "commonjs",
8 |
9 | "jsx": "react",
10 | "jsxFactory": "h",
11 |
12 | "declaration": true,
13 | "declarationMap": true,
14 | "sourceMap": true,
15 |
16 | "esModuleInterop": true,
17 | "moduleResolution": "node",
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "skipLibCheck": true,
21 | "strict": true,
22 | "strictPropertyInitialization": false
23 | },
24 | "include": ["src/**/*"],
25 | "exclude": ["node_module"]
26 | }
27 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "",
5 | "rootDir": "",
6 |
7 | "jsx": "react",
8 | "jsxFactory": "Nano.h",
9 |
10 | "declaration": false,
11 | "declarationMap": false,
12 | "sourceMap": false,
13 |
14 | "alwaysStrict": false,
15 | "esModuleInterop": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "noUnusedLocals": false,
18 | "noUnusedParameters": false
19 | },
20 | "include": ["test/nodejs/**/*"],
21 | "exclude": ["node_modules", "**/*.spec.ts", "test/deno/**", "test/browser/**"]
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.test.jsx-runtime.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "",
4 | "rootDir": "",
5 |
6 | "target": "ES2018",
7 | "module": "commonjs",
8 |
9 | "jsx": "react-jsx",
10 | "jsxImportSource": "../../lib",
11 |
12 | "skipLibCheck": true
13 | },
14 | "include": ["test.jsx-runtime/**/*"],
15 | "exclude": ["node_modules", "**/*.spec.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/webpack/webpack.bundle.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | mode: 'development',
5 | stats: 'errors-warnings',
6 | devtool: 'inline-source-map',
7 | entry: './lib/bundles/bundle.full.js',
8 | output: {
9 | filename: 'nano.dev.min.js',
10 | path: path.resolve(__dirname, '../bundles'),
11 | library: 'nanoJSX',
12 | libraryExport: 'default'
13 | },
14 | resolve: {
15 | extensions: ['.js']
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/webpack/webpack.bundle.instrumented.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | mode: 'development',
5 | stats: 'errors-warnings',
6 | devtool: 'source-map',
7 | entry: './lib/bundles/bundle.full.js',
8 | output: {
9 | filename: 'nano.instrumented.min.js',
10 | path: path.resolve(__dirname, '../bundles'),
11 | library: 'nanoJSX',
12 | libraryExport: 'default'
13 | },
14 | resolve: {
15 | extensions: ['.js']
16 | },
17 | module: {
18 | rules: [
19 | { test: /\.jsx?$/, use: ['coverage-istanbul-loader'] }
20 | // { test: /\.tsx?$/, use: ['coverage-istanbul-loader', 'ts-loader'] }
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/webpack/webpack.bundle.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | mode: 'production',
5 | stats: 'errors-warnings',
6 | entry: {
7 | core: './lib/bundles/bundle.core.js',
8 | slim: './lib/bundles/bundle.slim.js',
9 | full: './lib/bundles/bundle.full.js',
10 | ui: './lib/bundles/bundle.ui.js'
11 | },
12 | output: {
13 | filename: 'nano.[name].min.js',
14 | path: path.resolve(__dirname, '../bundles'),
15 | library: 'nanoJSX',
16 | libraryExport: 'default'
17 | },
18 | resolve: {
19 | extensions: ['.js']
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/webpack/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | mode: 'development',
5 | stats: 'errors-warnings',
6 | devtool: 'inline-source-map',
7 | entry: './lib/dev/dev.js',
8 | output: {
9 | filename: 'dev.js',
10 | path: path.resolve(__dirname, '../dev')
11 | },
12 | resolve: {
13 | extensions: ['.js']
14 | }
15 | // module: {
16 | // rules: [{ test: /\.tsx?$/, loader: 'ts-loader' }]
17 | // }
18 | }
19 |
--------------------------------------------------------------------------------