├── template
├── public
│ ├── footer.html
│ ├── header.html
│ ├── examples
│ │ ├── search.html
│ │ ├── list.html
│ │ ├── counter.html
│ │ ├── icon-list.html
│ │ ├── bind.html
│ │ ├── form.html
│ │ ├── article.html
│ │ ├── fragment.html
│ │ ├── edit.html
│ │ └── data-show.html
│ ├── style.css
│ ├── index.html
│ ├── base.js
│ └── app.html
├── .gitignore
├── server
│ ├── error.ts
│ ├── env.ts
│ ├── main.ts
│ └── api.ts
├── tsconfig.json
├── package.json
└── README.md
├── .npmignore
├── .gitignore
├── scripts
├── size.sh
├── update-hash.sh
├── hash.sh
└── du.js
├── tsconfig.json
├── cli.ts
├── package.json
├── LICENSE
└── README.md
/template/public/footer.html:
--------------------------------------------------------------------------------
1 | this is footer (loaded from footer.html)
--------------------------------------------------------------------------------
/template/public/header.html:
--------------------------------------------------------------------------------
1 | this is header (loaded form header.html)
--------------------------------------------------------------------------------
/template/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | package-lock.json
3 | pnpm-lock.yaml
4 | yarn.lock
5 | *.tgz
6 | dist/
7 | .env
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .dccache
2 | *.tgz
3 | pnpm-lock.yaml
4 | cli.ts
5 | scripts/
6 | tsconfig.json
7 | !template/tsconfig.json
8 | tsconfig.tsbuildinfo
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | package-lock.json
3 | pnpm-lock.yaml
4 | yarn.lock
5 | *.min.js
6 | *.gz
7 | *.tgz
8 | dist/
9 | tsconfig.tsbuildinfo
10 | cli.js
11 | base.js
12 |
--------------------------------------------------------------------------------
/template/server/error.ts:
--------------------------------------------------------------------------------
1 | export class HttpError extends Error {
2 | status?: number
3 | constructor(public statusCode: number, message: string) {
4 | super(message)
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/size.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | set -o pipefail
4 |
5 | cd template/public
6 | npx --yes esbuild base.js --minify > base.min.js
7 | gzip -f -k base.min.js
8 |
9 | ../../scripts/du.js base.*
10 |
--------------------------------------------------------------------------------
/scripts/update-hash.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | set -o pipefail
4 | ./scripts/size.sh
5 | cp template/public/base.js .
6 | cp template/public/base.min.js .
7 | ./scripts/hash.sh base.js >> README.md
8 | ./scripts/hash.sh base.min.js >> README.md
9 |
--------------------------------------------------------------------------------
/template/server/env.ts:
--------------------------------------------------------------------------------
1 | import { config } from 'dotenv'
2 | import populateEnv from 'populate-env'
3 |
4 | config()
5 |
6 | export let env = {
7 | NODE_ENV: 'development',
8 | PORT: 8100,
9 | }
10 |
11 | populateEnv(env, { mode: 'halt' })
12 |
--------------------------------------------------------------------------------
/template/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "commonjs",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "outDir": "dist",
10 | "incremental": true
11 | }
12 | }
--------------------------------------------------------------------------------
/template/public/examples/search.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "commonjs",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "incremental": true
10 | },
11 | "include": [
12 | "cli.ts"
13 | ]
14 | }
--------------------------------------------------------------------------------
/scripts/hash.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # generate integrity attribute
3 | set -e
4 | set -o pipefail
5 | hash=$(shasum -b -a 384 $1 | awk '{print $1}' | xxd -r -p | base64)
6 | echo ""
11 |
--------------------------------------------------------------------------------
/scripts/du.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // using readFile instead of stat to be compatible to MacOS
4 | let { readFileSync } = require('fs')
5 | let { argv } = process
6 |
7 | let files = argv.slice(2)
8 |
9 | let maxFileLength = Math.max(0, ...files.map(file => file.length))
10 |
11 | for (let file of files) {
12 | let size = readFileSync(file).length
13 | let padding = ' '.repeat(maxFileLength - file.length)
14 | let line = file + padding + '\t' + size
15 | if (size >= 1024) {
16 | line += `\t(${(size / 1024).toFixed(1)} KB)`
17 | }
18 | console.log(line)
19 | }
20 |
--------------------------------------------------------------------------------
/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-server",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "ts-node-dev server/main.ts",
8 | "prod": "NODE_ENV=production node dist/main.js",
9 | "test": "tsc --noEmit",
10 | "minify": "esbuild public/base.js --minify > public/base.min.js",
11 | "tsc": "tsc -p .",
12 | "build": "run-p minify tsc"
13 | },
14 | "keywords": [],
15 | "author": "",
16 | "license": "ISC",
17 | "devDependencies": {
18 | "@types/express": "^4.17.14",
19 | "@types/node": "^18.7.23",
20 | "esbuild": "^0.15.9",
21 | "npm-run-all": "^4.1.5",
22 | "ts-node": "^10.9.1",
23 | "ts-node-dev": "^2.0.0",
24 | "typescript": "^4.8.4"
25 | },
26 | "dependencies": {
27 | "cast.ts": "^1.5.1",
28 | "dotenv": "^16.0.2",
29 | "express": "^4.18.1",
30 | "listening-on": "^2.0.9",
31 | "populate-env": "^2.0.0"
32 | }
33 | }
--------------------------------------------------------------------------------
/template/public/style.css:
--------------------------------------------------------------------------------
1 | @media (prefers-color-scheme: dark) {
2 | body {
3 | background-color: #222;
4 | color: #eee;
5 | }
6 | a {
7 | color: #cc0;
8 | }
9 | a:visited {
10 | color: #0c0;
11 | }
12 | button[disabled] {
13 | color: #000;
14 | }
15 | }
16 |
17 | [hidden] {
18 | display: none !important;
19 | }
20 |
21 | .error {
22 | border: 1px solid red;
23 | padding: 0.5rem;
24 | width: fit-content;
25 | }
26 |
27 | .highlight {
28 | font-weight: bold;
29 | }
30 |
31 | article img {
32 | width: 200px;
33 | height: 200px;
34 | }
35 |
36 | img.small {
37 | width: 64px;
38 | height: 64px;
39 | }
40 |
41 | ul.tags {
42 | padding: 0;
43 | }
44 | ul.tags li {
45 | list-style: none;
46 | display: inline-block;
47 | padding: 0.5rem;
48 | border-radius: 1.5rem;
49 | font-size: 0.8rem;
50 | }
51 | @media (prefers-color-scheme: dark) {
52 | ul.tags li {
53 | background-color: #555;
54 | }
55 | }
56 | @media (prefers-color-scheme: light) {
57 | ul.tags li {
58 | background-color: #ccc;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/template/public/examples/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Array Value Demo
8 |
13 |
14 |
15 | Array Value Demo
16 |
17 |
18 |
19 |
23 |
24 |
25 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/cli.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { readFileSync, writeFileSync } from 'fs'
3 | import { copyTemplate, getDest, hasExec } from 'npm-init-helper'
4 | import { basename, join } from 'path'
5 |
6 | async function main() {
7 | let srcDir = join(__dirname, 'template')
8 | let dest = await getDest()
9 | await copyTemplate({
10 | srcDir,
11 | dest,
12 | updatePackageJson: true,
13 | verbose: true,
14 | })
15 | let name = basename(dest)
16 | if (name !== 'my-app') {
17 | // update project name in README.md
18 | let file = join(dest, 'README.md')
19 | let text = readFileSync(file)
20 | .toString()
21 | .replace(/my-app/g, name)
22 | writeFileSync(file, text)
23 | }
24 | console.log(
25 | `
26 | Done.
27 |
28 | Get started by typing:
29 |
30 | cd ${dest}
31 | `.trim(),
32 | )
33 |
34 | if (hasExec('pnpm')) {
35 | console.log(` pnpm i`)
36 | console.log(` npm run dev`)
37 | } else if (hasExec('yarn')) {
38 | console.log(` yarn install`)
39 | console.log(` yarn dev`)
40 | } else {
41 | console.log(` npm i`)
42 | console.log(` npm run dev`)
43 | }
44 | }
45 | main().catch(e => console.error(e))
46 |
--------------------------------------------------------------------------------
/template/public/examples/counter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/template/public/examples/icon-list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Icon List
7 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/template/public/examples/bind.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | bind demo
8 |
13 |
14 |
15 |
16 |
renderData without bind
17 |
20 |
21 |
22 |
renderTemplate with bind
23 |
24 |
25 |
26 |
renderTemplate without bind
27 |
28 |
29 |
30 | title:
31 |
32 |
33 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/template/server/main.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { print } from 'listening-on'
3 | import { join } from 'path'
4 | import { env } from './env'
5 | import { readFileSync, readdirSync } from 'fs'
6 | import { api } from './api'
7 | import { HttpError } from './error'
8 |
9 | let app = express()
10 |
11 | if (
12 | env.NODE_ENV == 'production' &&
13 | readdirSync('public').includes('base.min.js')
14 | ) {
15 | let base_min_js = readFileSync(join('public', 'base.min.js'))
16 | app.get('/base.js', (req, res) => {
17 | res.contentType('text/javascript')
18 | res.end(base_min_js)
19 | })
20 | }
21 |
22 | app.use(express.static('public'))
23 | app.use(express.static('public/examples'))
24 | app.use(express.json())
25 | app.use(express.urlencoded({ extended: false }))
26 |
27 | app.use(api)
28 |
29 | let errorHandler: express.ErrorRequestHandler = (
30 | error: HttpError,
31 | req,
32 | res,
33 | next,
34 | ) => {
35 | if (!(error instanceof HttpError)) {
36 | console.error(error)
37 | }
38 | res.status(error.status || error.statusCode || 500)
39 | if (req.header('Sec-Fetch-Mode') == 'navigate') {
40 | res.end(String(error))
41 | } else {
42 | res.json({ error: String(error) })
43 | }
44 | }
45 | app.use(errorHandler)
46 |
47 | let port = env.PORT
48 | app.listen(port, () => {
49 | print(port)
50 | })
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "data-template",
3 | "version": "1.10.1",
4 | "description": "Lightweight and minimal HTML template helpers powered by native DOM",
5 | "main": "base.js",
6 | "bin": {
7 | "data-template": "cli.js"
8 | },
9 | "scripts": {
10 | "test": "tsc --noEmit",
11 | "build": "run-p tsc base",
12 | "base": "bash scripts/size.sh && cp template/public/base.*js .",
13 | "tsc": "tsc -p ."
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/beenotung/data-template.git"
18 | },
19 | "keywords": [
20 | "html",
21 | "template",
22 | "native",
23 | "dom",
24 | "data",
25 | "dataset",
26 | "attribute",
27 | "render",
28 | "lightweight",
29 | "minimal",
30 | "ajax",
31 | "html-template",
32 | "data-template",
33 | "data-attribute",
34 | "data-binding"
35 | ],
36 | "author": "",
37 | "license": "BSD-2-Clause",
38 | "bugs": {
39 | "url": "https://github.com/beenotung/data-template/issues"
40 | },
41 | "homepage": "https://github.com/beenotung/data-template#readme",
42 | "devDependencies": {
43 | "@types/node": "^16.18.52",
44 | "npm-run-all": "^4.1.5",
45 | "ts-node": "^10.9.1",
46 | "typescript": "^5.2.2"
47 | },
48 | "dependencies": {
49 | "npm-init-helper": "^1.5.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright (c) [2022], [Beeno Tung (Tung Cheung Leong)]
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/template/public/examples/form.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
33 |
34 |
35 |
36 |
46 |
--------------------------------------------------------------------------------
/template/README.md:
--------------------------------------------------------------------------------
1 | # my-app
2 |
3 | Powered by [data-template](https://github.com/beenotung/data-template)
4 |
5 | ## Get Started
6 |
7 | ### Install dependencies
8 |
9 | Run: `npm install`
10 |
11 | Tips, you can also use below alternative installers:
12 |
13 | - `pnpm i`
14 | - `yarn install`
15 | - `slnpm`
16 |
17 | ### Start development server
18 |
19 | Run: `npm run dev`
20 |
21 | You will see output like below:
22 |
23 | ```
24 | listening on http://localhost:8100
25 | listening on http://127.0.0.1:8100 (lo)
26 | listening on http://192.168.1.2:8100 (wlp3s0)
27 | ```
28 |
29 | Then you can open http://localhost:8100 with a browser
30 |
31 | The port number may be changed by the `PORT` variable in the `.env` file
32 |
33 | ### Deploy production server
34 |
35 | 1. Run `npm run build` to compile the typescript project and minify the `base.js`
36 | 2. Run `npm start` to start the node.js server
37 |
38 | If you have installed pm2, you can start the server with: `pm2 start --name my-app dist/main.js`
39 |
40 | In the production mode, the server will enable compression (e.g. gzip) when the client supports it.
41 |
42 | It will also use the minified `base.min.js` when requested `base.min`, so you don't have to change the `src` attribute in the `
41 |
48 |