├── .npmrc ├── .eslintignore ├── .gitignore ├── app ├── res │ ├── favicon.ico │ ├── quickpage-logo.png │ └── quickpage-logo.svg ├── typings │ └── main.d.ts ├── jsconfig.json ├── pages │ └── home │ │ ├── index.js │ │ └── style.scss ├── index.html ├── main.view.js ├── lib │ ├── RouterExtension.js │ └── Router.js ├── main.js ├── themes │ ├── light.js │ ├── black.js │ ├── default.js │ └── index.js ├── main.scss └── .eslintrc.json ├── dist ├── app │ ├── favicon.ico │ ├── quickpage-logo.png │ ├── index.html │ ├── app_pages_home_index_js.css │ ├── main.css │ ├── app_pages_home_index_js.chunk.js │ ├── app_themes_light_js.chunk.js │ └── app_themes_black_js.chunk.js └── server │ └── main.cjs ├── .hintrc ├── postcss.config.js ├── jsconfig.json ├── .vscode ├── settings.json ├── config.js └── dev-server.js ├── .babelrc ├── .eslintrc.json ├── server └── main.js ├── typings └── index.d.ts ├── LICENSE ├── package.json ├── readme.md └── webpack.config.js /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | typings -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | !public/index.html 4 | .env 5 | .DS_Store -------------------------------------------------------------------------------- /app/res/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deadlyjack/quickpage/HEAD/app/res/favicon.ico -------------------------------------------------------------------------------- /dist/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deadlyjack/quickpage/HEAD/dist/app/favicon.ico -------------------------------------------------------------------------------- /app/res/quickpage-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deadlyjack/quickpage/HEAD/app/res/quickpage-logo.png -------------------------------------------------------------------------------- /dist/app/quickpage-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deadlyjack/quickpage/HEAD/dist/app/quickpage-logo.png -------------------------------------------------------------------------------- /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ], 5 | "hints": { 6 | "compat-api/css": "off" 7 | } 8 | } -------------------------------------------------------------------------------- /app/typings/main.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare const app: HTMLBodyElement; 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | import autoprefixer from 'autoprefixer'; 2 | 3 | export default { 4 | plugins: [ 5 | autoprefixer({}), 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "paths": { 5 | "*": [ 6 | "*" 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /app/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "app", 4 | "paths": { 5 | "*": [ 6 | "*" 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "foxbiz", 4 | "linebreak", 5 | "locationchange", 6 | "onnavigate", 7 | "plusplus", 8 | "Quickpage" 9 | ] 10 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | "html-tag-js/jsx/jsx-to-tag.js", 7 | "html-tag-js/jsx/syntax-parser.js", 8 | [ 9 | "@babel/transform-runtime", 10 | { 11 | "regenerator": true 12 | } 13 | ] 14 | ], 15 | "compact": false 16 | } -------------------------------------------------------------------------------- /app/pages/home/index.js: -------------------------------------------------------------------------------- 1 | import './style.scss'; 2 | 3 | function home() { 4 | const count = <>0; 5 | const onclick = () => { ++count.value; }; 6 | 7 | return
8 | 9 | Count: {count} times clicked 10 | 11 | 12 |
; 13 | } 14 | 15 | export default home; 16 | -------------------------------------------------------------------------------- /dist/app/index.html: -------------------------------------------------------------------------------- 1 | Quickpage
-------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Quickpage 9 | 10 | 11 | 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "airbnb-base" 9 | ], 10 | "parserOptions": { 11 | "ecmaVersion": 13, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "no-use-before-define": "off", 16 | "no-param-reassign": "off", 17 | "no-plusplus": "off", 18 | "import/no-extraneous-dependencies": "off", 19 | "lines-between-class-members": "off", 20 | "operator-linebreak": "off", 21 | "comma-dangle": [ 22 | 2, 23 | "always-multiline" 24 | ] 25 | } 26 | } -------------------------------------------------------------------------------- /app/pages/home/style.scss: -------------------------------------------------------------------------------- 1 | #home { 2 | height: 100%; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | flex-direction: column; 7 | 8 | .counter { 9 | margin: 20px; 10 | } 11 | 12 | button { 13 | border-radius: 4px; 14 | height: 40px; 15 | width: 120px; 16 | border: none; 17 | background: var(--button-background); 18 | color: var(--button-text); 19 | text-transform: uppercase; 20 | box-shadow: 2px 2px 4px 0 rgba($color: #000000, $alpha: 0.5); 21 | 22 | &:hover { 23 | background: var(--button-hover); 24 | } 25 | 26 | &:active { 27 | transition: all 100ms ease; 28 | transform: scale(0.9); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { readFileSync, writeFileSync } from 'fs'; 3 | 4 | const __dirname = process.cwd(); 5 | const arg = process.argv[2]; 6 | const babelrcPath = resolve(__dirname, '../.babelrc'); 7 | 8 | let babelrc; 9 | 10 | try { 11 | babelrc = readFileSync(babelrcPath, 'utf8'); 12 | if (babelrc) babelrc = JSON.parse(babelrc); 13 | } catch (error) { 14 | babelrc = null; 15 | } 16 | 17 | if (arg === 'd') { 18 | if (babelrc) babelrc.compact = false; 19 | } else if (arg === 'p') { 20 | if (babelrc) babelrc.compact = true; 21 | } 22 | 23 | if (babelrc) { 24 | babelrc = JSON.stringify(babelrc, undefined, 2); 25 | writeFileSync(babelrcPath, babelrc, 'utf8'); 26 | } 27 | process.exit(0); 28 | -------------------------------------------------------------------------------- /app/main.view.js: -------------------------------------------------------------------------------- 1 | import './main.scss'; 2 | 3 | /** 4 | * View component for the main page. 5 | * @param {Object} props 6 | * @param {string} props.appName 7 | * @param {Array<{ href: string, text: string }>} props.routes 8 | * @param {(e:InputEvent) => void} props.onThemeChange 9 | */ 10 | export default ({ appName, routes, onThemeChange }) =>
11 |
12 | {appName} 13 | 16 | 21 |
22 |
23 |
; 24 | -------------------------------------------------------------------------------- /server/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import express from 'express'; 3 | import { env } from 'process'; 4 | import { resolve } from 'path'; 5 | import { config } from 'dotenv'; 6 | import { existsSync } from 'fs'; 7 | 8 | config(); 9 | const app = express(); 10 | const { PORT = 3000 } = env; 11 | const currentDir = process.cwd(); 12 | 13 | app.use(express.json()); 14 | 15 | app.get('/:filename', (req, res, next) => { 16 | const file = resolve(currentDir, `dist/app/${req.params.filename}`); 17 | if (existsSync(file)) { 18 | res.sendFile(file); 19 | return; 20 | } 21 | next(); 22 | }); 23 | 24 | app.get('*', (req, res) => { 25 | res.sendFile(resolve(currentDir, 'dist/app/index.html')); 26 | }); 27 | 28 | app.listen(PORT, () => { 29 | console.log(`Server started at http://localhost:${PORT}`); 30 | }); 31 | -------------------------------------------------------------------------------- /app/lib/RouterExtension.js: -------------------------------------------------------------------------------- 1 | export default class RouterExtension { 2 | #base = ''; 3 | #routes = {}; 4 | #beforeNavigate = []; 5 | constructor(base) { 6 | this.#base = base; 7 | } 8 | 9 | /** 10 | * Add route 11 | * @param {string} path 12 | * @param {NavigationCallback} callback 13 | */ 14 | add(path, callback) { 15 | path = this.#base + (path.startsWith('/') ? path : `/${path}`); 16 | // remove duplicate slashes 17 | path = path.replace(/\/+/g, '/'); 18 | this.#routes[path] = callback; 19 | } 20 | 21 | beforeNavigate(callback) { 22 | this.#beforeNavigate.push({ 23 | callback, 24 | path: this.#base, 25 | }); 26 | } 27 | 28 | get routes() { 29 | return { 30 | ...this.#routes, 31 | }; 32 | } 33 | 34 | get beforeNavigateCallbacks() { 35 | return [ 36 | ...this.#beforeNavigate, 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | import 'core-js'; 2 | import 'html-tag-js/dist/polyfill'; 3 | 4 | import './main.scss'; 5 | import 'res/favicon.ico'; 6 | 7 | import Router from 'lib/Router'; 8 | import themes from './themes'; 9 | import MainView from './main.view'; 10 | 11 | window.onload = () => { 12 | themes.use('dark'); 13 | 14 | const routes = [ 15 | { href: 'https://foxbiz.io', text: 'Foxbiz' }, 16 | { href: 'https://github.com/deadlyjack/quickpage', text: 'GitHub' }, 17 | ]; 18 | 19 | app.content = themes.use(e.target.value)} appName="Quickpage" routes={routes} />; 20 | const main = app.get('main'); 21 | 22 | Router.add('/:filename(index.html?)?', async () => { 23 | const { default: Home } = await import('./pages/home'); 24 | main.content = ; 25 | }); 26 | 27 | Router.add('*', () => { 28 | main.innerHTML = `Cannot get ${window.location.pathname}`; 29 | }); 30 | 31 | Router.listen(); 32 | }; 33 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace JSX { 4 | interface IntrinsicElements { 5 | [el: string]: HTMLElement; 6 | } 7 | } 8 | 9 | interface Router { 10 | add(path: String, callback: () => void): void; 11 | listen(): void; 12 | navigate(url: String): void; 13 | /** 14 | * Add event listener to router. 15 | * @param event Name of event to add listener to 16 | * @param listener Callback function 17 | */ 18 | on( 19 | event: 'navigate', 20 | listener: (url: String, changed: Boolean) => void, 21 | ): void; 22 | /** 23 | * Remove event listener to router. 24 | * @param event Name of event to add listener to 25 | * @param listener Callback function 26 | */ 27 | off( 28 | event: 'navigate', 29 | listener: (url: String, changed: Boolean) => void, 30 | ): void; 31 | onnavigate(url: String): void; 32 | } 33 | 34 | declare const app: HTMLDivElement; 35 | declare const main: HTMLDivElement; 36 | -------------------------------------------------------------------------------- /app/themes/light.js: -------------------------------------------------------------------------------- 1 | import defaultTheme from './default'; 2 | 3 | const primary = '#ffffff'; 4 | const primaryText = '#221100'; 5 | const secondary = '#ffffff'; 6 | const secondaryText = '#112200'; 7 | 8 | export default { 9 | ...defaultTheme, 10 | name: 'Light', 11 | primary, 12 | primaryText, 13 | secondary, 14 | secondaryText, 15 | primaryActiveText: '#3399ff', 16 | buttonBackground: '#3399ff', 17 | buttonText: '#ffffff', 18 | popupBackground: '#ffffff', 19 | popupText: '#000000', 20 | popupBorder: 'rgba(0, 0, 0, 0.5)', 21 | popupActiveBackground: secondary, 22 | popupActiveText: '#3399ff', 23 | popupIcon: '#3399ff', 24 | danger: '#ff3325', 25 | dangerActive: '#661105', 26 | dangerText: '#ffffff', 27 | error: '#ff9966', 28 | success: '#339966', 29 | border: 'solid 1px rgba(0,0,0,0.4)', 30 | input: '#ffffff', 31 | inputText: '#333366', 32 | inputLabel: primary, 33 | inputBorder: `solid 1px ${primaryText}`, 34 | inputLabelText: primaryText, 35 | type: 'light', 36 | }; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ajit Kumar 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. -------------------------------------------------------------------------------- /app/main.scss: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | height: 100%; 4 | width: 100%; 5 | margin: 0; 6 | background-color: var(--primary); 7 | color: var(--primary-text); 8 | font-family: Arial, Helvetica, sans-serif; 9 | } 10 | 11 | header { 12 | display: flex; 13 | align-items: center; 14 | box-sizing: border-box; 15 | position: sticky; 16 | top: 0; 17 | width: 100%; 18 | background-color: var(--secondary); 19 | color: var(--secondary-text); 20 | border-bottom: var(--border); 21 | 22 | nav { 23 | padding: 0 10px; 24 | 25 | a { 26 | color: var(--secondary-text); 27 | display: inline-flex; 28 | text-decoration: none; 29 | margin-right: 10px; 30 | border-radius: 4px; 31 | height: 30px; 32 | padding: 0 10px; 33 | align-items: center; 34 | 35 | &:hover { 36 | background-color: rgba($color: #000000, $alpha: 0.2); 37 | } 38 | } 39 | } 40 | 41 | .logo { 42 | background-image: url(res/quickpage-logo.png); 43 | font-size: 2rem; 44 | height: 3rem; 45 | padding: 0.5rem; 46 | line-height: 3rem; 47 | background-size: cover; 48 | background-repeat: no-repeat; 49 | background-position: center -87px; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/themes/black.js: -------------------------------------------------------------------------------- 1 | import defaultTheme from './default'; 2 | 3 | const primary = '#000000'; 4 | const primaryText = '#FAF0E6'; 5 | const secondary = '#000000'; 6 | const secondaryText = '#FAF0E6'; 7 | 8 | export default { 9 | ...defaultTheme, 10 | name: 'Black', 11 | primary, 12 | primaryText, 13 | secondary, 14 | secondaryText, 15 | primaryActiveText: '#3399ff', 16 | buttonBackground: '#3399ff', 17 | buttonText: '#ffffff', 18 | popupBackground: '#121212', 19 | popupText: '#FFFFFF', 20 | popupBorder: 'rgba(255, 255, 255, 0.5)', 21 | popupActiveBackground: secondary, 22 | popupActiveText: '#FFD700', 23 | popupIcon: '#FFFFFF', 24 | danger: '#ff3325', 25 | dangerBorder: 'solid 1px #ff3325', 26 | dangerBackground: 'linear-gradient(to bottom, #662212, #ff3325)', 27 | dangerActive: '#661105', 28 | dangerText: '#ffffff', 29 | error: '#ff9966', 30 | success: '#339966', 31 | shadow: 'none', 32 | border: 'solid 1px rgba(255,255,255,0.4)', 33 | borderRadius: '6px', 34 | input: '#000000', 35 | inputText: '#ffffff', 36 | inputPlaceholder: '#ffffff88', 37 | inputLabel: secondary, 38 | inputBorder: 'solid 1px rgba(255,255,255,0.4)', 39 | inputLabelText: secondaryText, 40 | type: 'dark', 41 | }; 42 | -------------------------------------------------------------------------------- /app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "jsx" 4 | ], 5 | "env": { 6 | "browser": true, 7 | "es2021": true, 8 | "node": true 9 | }, 10 | "extends": [ 11 | "airbnb-base" 12 | ], 13 | "parserOptions": { 14 | "ecmaVersion": 13, 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "jsx": true 18 | } 19 | }, 20 | "rules": { 21 | "jsx/mark-used-vars": "error", 22 | "jsx/no-undef": "error", 23 | "no-use-before-define": "off", 24 | "no-param-reassign": "off", 25 | "no-plusplus": "off", 26 | "lines-between-class-members": "off", 27 | "operator-linebreak": "off", 28 | "default-case": "off", 29 | "comma-dangle": [ 30 | 2, 31 | "always-multiline" 32 | ] 33 | }, 34 | "globals": { 35 | "main": "readonly", 36 | "app": "readonly", 37 | "db": "readonly", 38 | "tag": "readonly" 39 | }, 40 | "settings": { 41 | "import/resolver": { 42 | "typescript": {}, 43 | "node": { 44 | "paths": [ 45 | "app" 46 | ] 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app/themes/default.js: -------------------------------------------------------------------------------- 1 | const primary = '#0E2954'; 2 | const primaryText = '#FAF0E6'; 3 | const secondary = '#1F6E8C'; 4 | const secondaryText = '#FAF0E6'; 5 | 6 | export default { 7 | name: 'Dark', 8 | primary, 9 | primaryText, 10 | secondary, 11 | secondaryText, 12 | primaryActiveText: '#3399ff', 13 | buttonBackground: '#3399ff', 14 | buttonHover: '#0066cc', 15 | buttonText: '#ffffff', 16 | popupBackground: '#363062', 17 | popupText: '#FFFFFF', 18 | popupBorder: 'rgba(0, 0, 0, 0.5)', 19 | popupActiveBackground: secondary, 20 | popupActiveText: '#FFD700', 21 | popupIcon: '#FFFFFF', 22 | danger: '#ff3325', 23 | dangerBorder: 'solid 1px #ff3325', 24 | dangerBackground: '#ff3325', 25 | dangerActive: '#661105', 26 | dangerText: '#ffffff', 27 | error: '#ff9966', 28 | success: '#339966', 29 | successBorder: 'solid 1px #339966', 30 | successBackground: '#339966', 31 | successActive: '#1f664f', 32 | successText: '#ffffff', 33 | shadow: '0 0 4px 0 rgba(0,0,0,0.5)', 34 | border: 'solid 1px rgba(0,0,0,0.4)', 35 | borderRadius: '4px', 36 | input: '#ffffff', 37 | inputText: '#333366', 38 | inputPlaceholder: '#00000088', 39 | inputLabel: secondary, 40 | inputBorder: 'solid 1px #ffffff', 41 | inputLabelText: secondaryText, 42 | type: 'dark', 43 | }; 44 | -------------------------------------------------------------------------------- /.vscode/dev-server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable no-console */ 3 | // eslint-disable-next-line import/no-extraneous-dependencies 4 | import open from 'open'; 5 | import { exec } from 'child_process'; 6 | import { env } from 'process'; 7 | import { config } from 'dotenv'; 8 | 9 | config(); 10 | const { PORT = 3000 } = env; 11 | const inspect = process.argv.includes('--inspect'); 12 | let serverStarted = false; 13 | 14 | const configProcess = exec('node .vscode/config.js d', processHandler); 15 | configProcess.on('exit', build); 16 | 17 | function build() { 18 | const buildProcess = exec(`webpack --watch --mode development`, processHandler); 19 | buildProcess.stdout.on('data', writeStdout); 20 | buildProcess.stderr.on('data', writeStderr); 21 | } 22 | 23 | function start() { 24 | const nodemonProcess = exec(`nodemon ${inspect ? '--inspect' : ''} --watch server server/main -q`, processHandler); 25 | nodemonProcess.stdout.on('data', writeStdout); 26 | nodemonProcess.stderr.on('data', writeStderr); 27 | open(`http://localhost:${PORT}`); 28 | } 29 | 30 | function processHandler(err) { 31 | if (err) console.error(err); 32 | } 33 | 34 | function writeStdout(data) { 35 | console.log(data.trim()); 36 | if (!serverStarted && /webpack \d+\.\d+\.\d+ compiled .*successfully.*/.test(data)) { 37 | serverStarted = true; 38 | start(); 39 | } 40 | } 41 | 42 | function writeStderr(data) { 43 | console.error(data); 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quickpage", 3 | "type": "module", 4 | "version": "1.0.1", 5 | "description": "Quickpage, a simple template for creating PWAs and SPAs with Frontend routing and JSX along with Express server.", 6 | "main": "dist/server/main.js", 7 | "author": "Ajit Kumar", 8 | "license": "MIT", 9 | "engines": { 10 | "node": ">=12.16.1" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.24.9", 14 | "@babel/plugin-transform-runtime": "^7.24.7", 15 | "@babel/preset-env": "^7.24.8", 16 | "autoprefixer": "^10.4.19", 17 | "babel-cli": "^6.26.0", 18 | "babel-loader": "^9.1.3", 19 | "css-loader": "^7.1.2", 20 | "eslint": "^8.57.0", 21 | "eslint-config-airbnb-base": "^15.0.0", 22 | "eslint-import-resolver-typescript": "^3.6.1", 23 | "eslint-plugin-import": "^2.29.1", 24 | "eslint-plugin-jsx": "^0.1.0", 25 | "file-loader": "^6.2.0", 26 | "html-webpack-plugin": "^5.6.0", 27 | "mini-css-extract-plugin": "^2.9.0", 28 | "nodemon": "^3.1.4", 29 | "open": "^10.1.0", 30 | "postcss": "^8.4.39", 31 | "postcss-loader": "^8.1.1", 32 | "sass": "^1.77.8", 33 | "sass-loader": "^15.0.0", 34 | "style-loader": "^4.0.0", 35 | "terser-webpack-plugin": "^5.3.10", 36 | "url-loader": "^4.1.1", 37 | "webpack": "5.93.0", 38 | "webpack-cli": "5.1.4" 39 | }, 40 | "dependencies": { 41 | "core-js": "^3.37.1", 42 | "dotenv": "^16.4.5", 43 | "express": "^4.19.2", 44 | "html-tag-js": "^1.5.1" 45 | }, 46 | "scripts": { 47 | "config-build": "node .vscode/config.js", 48 | "build": "yarn config-build d && webpack --progress --mode development", 49 | "build-release": "yarn config-build p && webpack --progress --mode production", 50 | "start": "node ./server/main.js", 51 | "start-dev": "node .vscode/dev-server.js" 52 | }, 53 | "browserslist": "cover 100%,not android < 5" 54 | } -------------------------------------------------------------------------------- /dist/app/app_pages_home_index_js.css: -------------------------------------------------------------------------------- 1 | /*!**************************************************************************************************************************************************************!*\ 2 | !*** css ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./app/pages/home/style.scss ***! 3 | \**************************************************************************************************************************************************************/ 4 | #home { 5 | height: 100%; 6 | display: -webkit-box; 7 | display: -webkit-flex; 8 | display: -moz-box; 9 | display: flex; 10 | -webkit-box-align: center; 11 | -webkit-align-items: center; 12 | -moz-box-align: center; 13 | align-items: center; 14 | -webkit-box-pack: center; 15 | -webkit-justify-content: center; 16 | -moz-box-pack: center; 17 | justify-content: center; 18 | -webkit-box-orient: vertical; 19 | -webkit-box-direction: normal; 20 | -webkit-flex-direction: column; 21 | -moz-box-orient: vertical; 22 | -moz-box-direction: normal; 23 | flex-direction: column; 24 | } 25 | #home .counter { 26 | margin: 20px; 27 | } 28 | #home button { 29 | border-radius: 4px; 30 | height: 40px; 31 | width: 120px; 32 | border: none; 33 | background: var(--button-background); 34 | color: var(--button-text); 35 | text-transform: uppercase; 36 | -webkit-box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.5); 37 | box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.5); 38 | } 39 | #home button:hover { 40 | background: var(--button-hover); 41 | } 42 | #home button:active { 43 | -webkit-transition: all 100ms ease; 44 | -moz-transition: all 100ms ease; 45 | transition: all 100ms ease; 46 | -webkit-transform: scale(0.9); 47 | -moz-transform: scale(0.9); 48 | -ms-transform: scale(0.9); 49 | transform: scale(0.9); 50 | } 51 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Quickpage 2 | 3 | Quickpage is a GitHub template for creating single-page-application with front-end routing and JSX. See demo at official page . 4 | 5 | ## Usage 6 | 7 | Create github repository using this template. 8 | 9 | ## Documentation 10 | 11 | ### Start server 12 | 13 | To start the server run the following bash command 14 | 15 | ```bash 16 | : yarn start 17 | ``` 18 | 19 | To start the dev-server run the following bash command 20 | 21 | ```bash 22 | : yarn start-dev 23 | ``` 24 | 25 | ### Build the application 26 | 27 | ```bash 28 | : yarn build-release 29 | ``` 30 | 31 | The server uses 'NodeJs' and 'ExpressJs' for serving files. You can edit the server src code in `server` directory. 32 | 33 | ### Routing 34 | 35 | ```javascript 36 | import Router from 'lib/Router'; 37 | ``` 38 | 39 | Add routes. 40 | 41 | ```javascript 42 | Router.add('/home', (params, queries) => { 43 | // render home 44 | }); 45 | ``` 46 | 47 | Start route. 48 | 49 | ```javascript 50 | Router.listen(); 51 | ``` 52 | 53 | #### Create separate routing page 54 | 55 | Create a router page 56 | 57 | ```bash 58 | touch adminRouter.js 59 | ``` 60 | 61 | Initialize router page. 62 | 63 | ```javascript 64 | // adminRouter.js 65 | import Router from 'lib/RouterExtension'; 66 | 67 | const router = new Router('/admin'); 68 | 69 | // routes 70 | 71 | export default router; 72 | ``` 73 | 74 | Add middle function to filter routes. 75 | 76 | ```javascript 77 | Router.beforeNavigate((url, next) => { 78 | // url -> current url 79 | // next -> callback function 80 | // call next function to proceed 81 | }); 82 | ``` 83 | 84 | Add a route. 85 | 86 | ```javascript 87 | Router.add('home', (params, queries) => { 88 | // render '/base-route/home' 89 | }); 90 | ``` 91 | 92 | Add router to main Router. 93 | 94 | ```javascript 95 | import adminRouter from './adminRouter'; 96 | import Router from 'lib/Router'; 97 | 98 | Router.use(adminRouter); 99 | Router.listen(); 100 | ``` 101 | -------------------------------------------------------------------------------- /dist/app/main.css: -------------------------------------------------------------------------------- 1 | /*!**************************************************************************************************************************************************!*\ 2 | !*** css ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./app/main.scss ***! 3 | \**************************************************************************************************************************************************/ 4 | body, 5 | html { 6 | height: 100%; 7 | width: 100%; 8 | margin: 0; 9 | background-color: var(--primary); 10 | color: var(--primary-text); 11 | font-family: Arial, Helvetica, sans-serif; 12 | } 13 | 14 | header { 15 | display: -webkit-box; 16 | display: -webkit-flex; 17 | display: -moz-box; 18 | display: flex; 19 | -webkit-box-align: center; 20 | -webkit-align-items: center; 21 | -moz-box-align: center; 22 | align-items: center; 23 | -webkit-box-sizing: border-box; 24 | -moz-box-sizing: border-box; 25 | box-sizing: border-box; 26 | position: -webkit-sticky; 27 | position: sticky; 28 | top: 0; 29 | width: 100%; 30 | background-color: var(--secondary); 31 | color: var(--secondary-text); 32 | border-bottom: var(--border); 33 | } 34 | header nav { 35 | padding: 0 10px; 36 | } 37 | header nav a { 38 | color: var(--secondary-text); 39 | display: -webkit-inline-box; 40 | display: -webkit-inline-flex; 41 | display: -moz-inline-box; 42 | display: inline-flex; 43 | text-decoration: none; 44 | margin-right: 10px; 45 | border-radius: 4px; 46 | height: 30px; 47 | padding: 0 10px; 48 | -webkit-box-align: center; 49 | -webkit-align-items: center; 50 | -moz-box-align: center; 51 | align-items: center; 52 | } 53 | header nav a:hover { 54 | background-color: rgba(0, 0, 0, 0.2); 55 | } 56 | header .logo { 57 | background-image: url(/quickpage-logo.png); 58 | font-size: 2rem; 59 | height: 3rem; 60 | padding: 0.5rem; 61 | line-height: 3rem; 62 | background-size: cover; 63 | background-repeat: no-repeat; 64 | background-position: center -87px; 65 | } 66 | -------------------------------------------------------------------------------- /app/themes/index.js: -------------------------------------------------------------------------------- 1 | import defaultTheme from './default'; 2 | 3 | /** 4 | * @typedef {Map.} Theme 5 | */ 6 | 7 | /** @type {HTMLStyleElement} */ 8 | let themeStyleEl = null; 9 | 10 | export default { 11 | /** 12 | * Get theme by name 13 | * @param {string} name 14 | * @returns {Promise} 15 | */ 16 | async get(name) { 17 | name = name.toLowerCase(); 18 | let theme = defaultTheme; 19 | switch (name) { 20 | case 'light': { 21 | const themeModule = await import('./light'); 22 | theme = themeModule.default; 23 | break; 24 | } 25 | 26 | case 'black': { 27 | const themeModule = await import('./black'); 28 | theme = themeModule.default; 29 | break; 30 | } 31 | 32 | default: { 33 | const themeModule = await import('./default'); 34 | theme = themeModule.default; 35 | break; 36 | } 37 | } 38 | 39 | return new Map(Object.entries(theme)); 40 | }, 41 | 42 | /** 43 | * Applies the specified theme by updating the CSS variables in the document. 44 | * @param {string} name - The name of the theme to apply. 45 | * @param {HTMLStyleElement} [style] - The style element to update with the theme's CSS. 46 | * @returns {Promise} A promise that resolves when the theme is applied. 47 | */ 48 | async use(name, style) { 49 | const theme = await this.get(name); 50 | const css = jsonToCssVariables(theme); 51 | if (!themeStyleEl) { 52 | themeStyleEl = style || document.createElement('style'); 53 | } 54 | 55 | if (!style) style = themeStyleEl; 56 | style.innerHTML = css; 57 | if (!style.isConnected) { 58 | document.head.appendChild(style); 59 | } 60 | }, 61 | /** 62 | * Get the list of available themes. 63 | * 64 | * @returns {string[]} The list of themes. 65 | */ 66 | get list() { 67 | return ['Light', 'Dark', 'Black']; 68 | }, 69 | /** 70 | * Checks if a theme name exists in the list of themes. 71 | * @param {string} name - The name of the theme to check. 72 | * @returns {boolean} - Returns true if the theme exists, false otherwise. 73 | */ 74 | has(name) { 75 | return this.list.includes(name); 76 | }, 77 | }; 78 | 79 | /** 80 | * Converts a JSON object to CSS variables. 81 | * 82 | * @param {Map} json - The JSON object containing key-value pairs. 83 | * @returns {string} - The CSS variables as a string. 84 | */ 85 | function jsonToCssVariables(json) { 86 | let theme = ''; 87 | Array.from(json.keys()).forEach((color) => { 88 | const cssVar = color.replace(/[A-Z]/g, ($) => `-${$.toLowerCase()}`); 89 | theme += `--${cssVar}: ${json.get(color)};`; 90 | }); 91 | return `:root{${theme}}`; 92 | } 93 | -------------------------------------------------------------------------------- /dist/app/app_pages_home_index_js.chunk.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* 3 | * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). 4 | * This devtool is neither made for production nor for readable output files. 5 | * It uses "eval()" calls to create a separate source file in the browser devtools. 6 | * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) 7 | * or disable the default devtool with "devtool: false". 8 | * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). 9 | */ 10 | (self["webpackChunkquickpage"] = self["webpackChunkquickpage"] || []).push([["app_pages_home_index_js"],{ 11 | 12 | /***/ "./app/pages/home/index.js": 13 | /*!*********************************!*\ 14 | !*** ./app/pages/home/index.js ***! 15 | \*********************************/ 16 | /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { 17 | 18 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var html_tag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! html-tag-js */ \"./node_modules/html-tag-js/dist/tag.js\");\n/* harmony import */ var html_tag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(html_tag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _style_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./style.scss */ \"./app/pages/home/style.scss\");\n\n\nfunction home() {\n var count = html_tag_js__WEBPACK_IMPORTED_MODULE_0___default().use(0);\n var onclick = function onclick() {\n ++count.value;\n };\n return html_tag_js__WEBPACK_IMPORTED_MODULE_0___default()(\"section\", {\n id: 'home',\n children: [\"\\n \", html_tag_js__WEBPACK_IMPORTED_MODULE_0___default()(\"span\", {\n className: 'counter',\n children: [\"\\n Count: \", html_tag_js__WEBPACK_IMPORTED_MODULE_0___default()(\"strong\", {\n children: [count]\n }), \" times clicked\\n \"]\n }), \"\\n \", html_tag_js__WEBPACK_IMPORTED_MODULE_0___default()(\"button\", {\n onclick: onclick,\n type: 'button',\n children: [\"Click\"]\n }), \"\\n \"]\n });\n}\n/* harmony default export */ __webpack_exports__[\"default\"] = (home);\n\n//# sourceURL=webpack://quickpage/./app/pages/home/index.js?"); 19 | 20 | /***/ }), 21 | 22 | /***/ "./app/pages/home/style.scss": 23 | /*!***********************************!*\ 24 | !*** ./app/pages/home/style.scss ***! 25 | \***********************************/ 26 | /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { 27 | 28 | eval("__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://quickpage/./app/pages/home/style.scss?"); 29 | 30 | /***/ }) 31 | 32 | }]); -------------------------------------------------------------------------------- /dist/app/app_themes_light_js.chunk.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* 3 | * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). 4 | * This devtool is neither made for production nor for readable output files. 5 | * It uses "eval()" calls to create a separate source file in the browser devtools. 6 | * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) 7 | * or disable the default devtool with "devtool: false". 8 | * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). 9 | */ 10 | (self["webpackChunkquickpage"] = self["webpackChunkquickpage"] || []).push([["app_themes_light_js"],{ 11 | 12 | /***/ "./app/themes/light.js": 13 | /*!*****************************!*\ 14 | !*** ./app/themes/light.js ***! 15 | \*****************************/ 16 | /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { 17 | 18 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/defineProperty */ \"./node_modules/@babel/runtime/helpers/esm/defineProperty.js\");\n/* harmony import */ var _default__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./default */ \"./app/themes/default.js\");\n\nfunction ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }\nfunction _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0,_babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }\n\nvar primary = '#ffffff';\nvar primaryText = '#221100';\nvar secondary = '#ffffff';\nvar secondaryText = '#112200';\n/* harmony default export */ __webpack_exports__[\"default\"] = (_objectSpread(_objectSpread({}, _default__WEBPACK_IMPORTED_MODULE_1__[\"default\"]), {}, {\n name: 'Light',\n primary: primary,\n primaryText: primaryText,\n secondary: secondary,\n secondaryText: secondaryText,\n primaryActiveText: '#3399ff',\n buttonBackground: '#3399ff',\n buttonText: '#ffffff',\n popupBackground: '#ffffff',\n popupText: '#000000',\n popupBorder: 'rgba(0, 0, 0, 0.5)',\n popupActiveBackground: secondary,\n popupActiveText: '#3399ff',\n popupIcon: '#3399ff',\n danger: '#ff3325',\n dangerActive: '#661105',\n dangerText: '#ffffff',\n error: '#ff9966',\n success: '#339966',\n border: 'solid 1px rgba(0,0,0,0.4)',\n input: '#ffffff',\n inputText: '#333366',\n inputLabel: primary,\n inputBorder: \"solid 1px \".concat(primaryText),\n inputLabelText: primaryText,\n type: 'light'\n}));\n\n//# sourceURL=webpack://quickpage/./app/themes/light.js?"); 19 | 20 | /***/ }) 21 | 22 | }]); -------------------------------------------------------------------------------- /dist/app/app_themes_black_js.chunk.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* 3 | * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). 4 | * This devtool is neither made for production nor for readable output files. 5 | * It uses "eval()" calls to create a separate source file in the browser devtools. 6 | * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) 7 | * or disable the default devtool with "devtool: false". 8 | * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). 9 | */ 10 | (self["webpackChunkquickpage"] = self["webpackChunkquickpage"] || []).push([["app_themes_black_js"],{ 11 | 12 | /***/ "./app/themes/black.js": 13 | /*!*****************************!*\ 14 | !*** ./app/themes/black.js ***! 15 | \*****************************/ 16 | /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { 17 | 18 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/defineProperty */ \"./node_modules/@babel/runtime/helpers/esm/defineProperty.js\");\n/* harmony import */ var _default__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./default */ \"./app/themes/default.js\");\n\nfunction ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }\nfunction _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0,_babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }\n\nvar primary = '#000000';\nvar primaryText = '#FAF0E6';\nvar secondary = '#000000';\nvar secondaryText = '#FAF0E6';\n/* harmony default export */ __webpack_exports__[\"default\"] = (_objectSpread(_objectSpread({}, _default__WEBPACK_IMPORTED_MODULE_1__[\"default\"]), {}, {\n name: 'Black',\n primary: primary,\n primaryText: primaryText,\n secondary: secondary,\n secondaryText: secondaryText,\n primaryActiveText: '#3399ff',\n buttonBackground: '#3399ff',\n buttonText: '#ffffff',\n popupBackground: '#121212',\n popupText: '#FFFFFF',\n popupBorder: 'rgba(255, 255, 255, 0.5)',\n popupActiveBackground: secondary,\n popupActiveText: '#FFD700',\n popupIcon: '#FFFFFF',\n danger: '#ff3325',\n dangerBorder: 'solid 1px #ff3325',\n dangerBackground: 'linear-gradient(to bottom, #662212, #ff3325)',\n dangerActive: '#661105',\n dangerText: '#ffffff',\n error: '#ff9966',\n success: '#339966',\n shadow: 'none',\n border: 'solid 1px rgba(255,255,255,0.4)',\n borderRadius: '6px',\n input: '#000000',\n inputText: '#ffffff',\n inputPlaceholder: '#ffffff88',\n inputLabel: secondary,\n inputBorder: 'solid 1px rgba(255,255,255,0.4)',\n inputLabelText: secondaryText,\n type: 'dark'\n}));\n\n//# sourceURL=webpack://quickpage/./app/themes/black.js?"); 19 | 20 | /***/ }) 21 | 22 | }]); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 3 | import TerserPlugin from 'terser-webpack-plugin'; 4 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 5 | 6 | const PUBLIC = resolve(process.cwd(), 'dist/app'); 7 | const SERVER = resolve(process.cwd(), 'dist/server'); 8 | 9 | export default (env, options) => { 10 | const { mode } = options; 11 | 12 | const config = { 13 | resolve: { 14 | modules: ['node_modules', 'app'], 15 | }, 16 | stats: 'minimal', 17 | watchOptions: { 18 | ignored: [ 19 | '**/node_modules', 20 | '**/dist', 21 | '**/tools', 22 | ], 23 | }, 24 | mode, 25 | entry: { 26 | main: './app/main.js', 27 | }, 28 | output: { 29 | path: PUBLIC, 30 | filename: '[name].min.js', 31 | chunkFilename: '[name].chunk.js', 32 | publicPath: '/', 33 | assetModuleFilename: '[name][ext]', 34 | clean: true, 35 | }, 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.module.(sa|sc|c)ss$/, 40 | use: [ 41 | 'raw-loader', 42 | 'postcss-loader', 43 | 'sass-loader', 44 | ], 45 | }, 46 | { 47 | test: /\.jsx?$/, 48 | type: 'javascript/auto', 49 | exclude: /(node_modules)/, 50 | use: [ 51 | 'html-tag-js/jsx/tag-loader.js', 52 | 'babel-loader', 53 | ], 54 | resolve: { 55 | fullySpecified: false, 56 | }, 57 | }, 58 | { 59 | test: /(? 2 | 3 | 4 | image/svg+xml 28 | 29 | 30 | 31 | 32 | 33 | 34 | 78 | 84 | 92 | 98 | 99 | -------------------------------------------------------------------------------- /app/lib/Router.js: -------------------------------------------------------------------------------- 1 | class Router { 2 | #customEvent = new CustomEvent('locationchange'); 3 | #routes = {}; 4 | #beforeNavigate = {}; 5 | #lastPath = '/'; 6 | #on = { 7 | navigate: [], 8 | }; 9 | #allCallbacks = []; 10 | #currentUrl = ''; 11 | /** 12 | * Add listener for navigate event, called when navigation is done 13 | * @type {() => void} 14 | */ 15 | onnavigate = null; 16 | 17 | constructor() { 18 | this.reload = this.reload.bind(this); 19 | this.navigate = this.navigate.bind(this); 20 | this.add = this.add.bind(this); 21 | this.listen = this.listen.bind(this); 22 | this.on = this.on.bind(this); 23 | this.off = this.off.bind(this); 24 | this.use = this.use.bind(this); 25 | this.loadUrl = this.loadUrl.bind(this); 26 | } 27 | 28 | /** 29 | * @typedef {( 30 | * params: Map, 31 | * queries: Map 32 | * ) => void} NavigationCallback 33 | */ 34 | 35 | /** 36 | * Add route to router 37 | * @param {string} path path to listen 38 | * @param {NavigationCallback} callback callback to call when path is matched 39 | */ 40 | add(path, callback) { 41 | this.#routes[path] = callback; 42 | } 43 | 44 | /** 45 | * Navigates to the specified URL or the current location pathname. 46 | * @param {string} url - The URL to navigate to. 47 | * If not provided, the current location pathname will be used. 48 | */ 49 | navigate(url) { 50 | const { location } = window; 51 | url = typeof url === 'string' ? url : location.pathname; 52 | url = url.toLowerCase(); 53 | const allCallbacks = []; 54 | this.#currentUrl = url; 55 | 56 | Object.keys(this.#beforeNavigate).forEach((path) => { 57 | if (url.startsWith(path)) { 58 | const callbacks = this.#beforeNavigate[path]; 59 | if (Array.isArray(callbacks)) { 60 | allCallbacks.push(...callbacks); 61 | } 62 | } 63 | }); 64 | 65 | allCallbacks.push((currUrl, next, forceParams) => { 66 | this.#navigate(currUrl, forceParams); 67 | }); 68 | 69 | this.#allCallbacks = [...allCallbacks]; 70 | Router.#callWithNext(allCallbacks, url); 71 | } 72 | 73 | /** 74 | * Load url, this method calls the added callbacks 75 | * @param {string} url 76 | * @param {Map} forceParams 77 | */ 78 | #navigate(url, forceParams) { 79 | const routes = Object.keys(this.#routes); 80 | 81 | for (let i = 0; i < routes.length; ++i) { 82 | const path = routes[i]; 83 | try { 84 | const route = this.#routes[path]; 85 | const [params, queries] = Router.#execRoute(path, url); 86 | const changed = this.#lastPath !== path; 87 | this.#lastPath = path; 88 | route(forceParams ?? params, queries); 89 | 90 | if (typeof this.onnavigate === 'function') { 91 | this.onnavigate(url, changed); 92 | } 93 | 94 | this.#on.navigate.forEach((listener) => listener(url, changed)); 95 | break; 96 | } catch (error) { 97 | // not matched 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * Add listener for navigate event 104 | */ 105 | listen() { 106 | const { location } = window; 107 | this.navigate(location.pathname); 108 | document.addEventListener('locationchange', () => this.navigate()); 109 | document.body.addEventListener('click', this.#listenForAnchor.bind(this)); 110 | window.addEventListener('popstate', () => { 111 | document.dispatchEvent(this.#customEvent); 112 | }); 113 | } 114 | 115 | /** 116 | * Add event listener 117 | * @param {'navigate'} event 118 | * @param {function(string):void} callback 119 | */ 120 | on(event, callback) { 121 | if (event in this.#on) { 122 | this.#on[event].push(callback); 123 | } 124 | } 125 | 126 | /** 127 | * Removes event listener 128 | * @param {'navigate'} event 129 | * @param {function(string):void} callback 130 | */ 131 | off(event, callback) { 132 | if (event in this.#on) { 133 | this.#on[event].splice(this.#on[event].indexOf(callback), 1); 134 | } 135 | } 136 | 137 | /** 138 | * 139 | * @param {import('./RouterExtension').default} router 140 | */ 141 | use(router) { 142 | const { routes, beforeNavigateCallbacks } = router; 143 | Object.keys(routes).forEach((path) => { 144 | this.add(path, routes[path]); 145 | }); 146 | 147 | beforeNavigateCallbacks.forEach(({ path, callback }) => { 148 | if (!this.#beforeNavigate[path]) this.#beforeNavigate[path] = []; 149 | this.#beforeNavigate[path].push(callback); 150 | }); 151 | } 152 | 153 | /** 154 | * Recursively call callbacks when one of them calls next 155 | * @param {Array} callbacks 156 | * @param {string} url 157 | * @param {Map} forceParams 158 | */ 159 | static #callWithNext(callbacks, url, forceParams) { 160 | const callback = callbacks.shift(); 161 | if (callback) { 162 | callback(url, next, forceParams); 163 | } 164 | 165 | function next() { 166 | this.#callWithNext(callbacks, url, forceParams); 167 | } 168 | } 169 | 170 | /** 171 | * Test if given path matches the given route 172 | * @param {string} route route to be tested on 173 | * @param {string} path path to test 174 | */ 175 | static #execRoute(route, path) { 176 | // if path starts with : then it is a param 177 | // if param ends with ? then it is optional 178 | // if param pattern is 'param(path1|path2)' then value can be path1 or path2 179 | // if param pattern is 'param(path1|path2)?' then value can be path1 or path2 or empty 180 | // if route is * then it is a wildcard 181 | const queryString = window.location.search.substring(1); 182 | 183 | const params = {}; 184 | const queries = {}; 185 | const routeSegments = route.split('/'); 186 | const pathSegments = path.split('/'); 187 | 188 | queryString?.split('&').forEach((get) => { 189 | const [key, value] = get.split('='); 190 | queries[decodeURIComponent(key)] = decodeURIComponent(value); 191 | }); 192 | 193 | const len = Math.max(routeSegments.length, pathSegments.length); 194 | 195 | for (let i = 0; i < len; ++i) { 196 | const routeSegment = routeSegments[i]; 197 | const pathSegment = pathSegments[i]; 198 | 199 | if (routeSegment === undefined) { 200 | return null; 201 | } 202 | 203 | if (routeSegment === '*') { 204 | return [params, queries]; // wildcard 205 | } 206 | 207 | if (routeSegment.startsWith(':')) { 208 | const IS_OPTIONAL = routeSegment.endsWith('?'); 209 | const IS_ALLOWED = IS_OPTIONAL && !pathSegment; 210 | const cleanRouteSegment = IS_OPTIONAL 211 | ? routeSegment.slice(1, -1) 212 | : routeSegment.slice(1); 213 | const key = cleanRouteSegment.replace(/\(.*\)$/, ''); 214 | const execValue = /\((.+)\)/.exec(cleanRouteSegment); 215 | if (Array.isArray(execValue)) { 216 | const regex = new RegExp(execValue[1]); 217 | if (IS_ALLOWED || regex.test(pathSegment)) { 218 | params[key] = pathSegment; 219 | } else { 220 | return null; 221 | } 222 | } else if (IS_ALLOWED || pathSegment) { 223 | params[key] = pathSegment; 224 | } else { 225 | return null; 226 | } 227 | } else if (routeSegment !== pathSegment) { 228 | return null; 229 | } 230 | } 231 | return [params, queries]; 232 | } 233 | 234 | /** 235 | * Listens for click event on anchor tag 236 | * @param {MouseEvent} e 237 | * @returns 238 | */ 239 | #listenForAnchor(e) { 240 | const $el = e.target; 241 | 242 | if (!($el instanceof HTMLAnchorElement)) return; 243 | if ($el.target === '_blank') return; 244 | 245 | e.preventDefault(); 246 | 247 | /** 248 | * @type {string} 249 | */ 250 | const href = $el.getAttribute('href'); 251 | this.loadUrl(href); 252 | } 253 | 254 | /** 255 | * Loads the specified URL and updates the browser's history. 256 | * If the URL has a protocol of 'mailto', 'tel', or 'sms', it will be opened directly. 257 | * If the URL is not from the same site, it will be opened in a new window. 258 | * If the URL is different from the current URL, 259 | * it will update the browser's history and dispatch a custom event. 260 | * @param {string} href - The URL to load. 261 | */ 262 | loadUrl(href) { 263 | const [protocol] = href.split(':'); 264 | if (['mailto', 'tel', 'sms'].includes(protocol)) { 265 | window.location.href = href; 266 | return; 267 | } 268 | 269 | const { location, history } = window; 270 | const thisSite = new RegExp( 271 | `(^https?://(www.)?${location.hostname}(/.*)?)|(^/)`, 272 | ); 273 | if (!thisSite.test(href)) { 274 | window.location.href = href; 275 | } 276 | 277 | const currentUrl = location.pathname + location.search; 278 | if (href !== currentUrl) { 279 | history.pushState(history.state, document.title, href); 280 | document.dispatchEvent(this.#customEvent); 281 | } 282 | } 283 | 284 | /** 285 | * Reload current page 286 | * @param {Map} [forceParams] params to force 287 | */ 288 | reload(forceParams = null) { 289 | const callbacks = [...this.#allCallbacks]; 290 | Router.#callWithNext(callbacks, this.#currentUrl, forceParams); 291 | } 292 | 293 | static setUrl(path) { 294 | const { history } = window; 295 | history.pushState(history.state, document.title, path); 296 | } 297 | } 298 | 299 | export default new Router(); 300 | -------------------------------------------------------------------------------- /dist/server/main.cjs: -------------------------------------------------------------------------------- 1 | /* 2 | * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). 3 | * This devtool is neither made for production nor for readable output files. 4 | * It uses "eval()" calls to create a separate source file in the browser devtools. 5 | * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) 6 | * or disable the default devtool with "devtool: false". 7 | * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). 8 | */ 9 | /******/ (() => { // webpackBootstrap 10 | /******/ var __webpack_modules__ = ({ 11 | 12 | /***/ "./node_modules/dotenv/lib/main.js": 13 | /*!*****************************************!*\ 14 | !*** ./node_modules/dotenv/lib/main.js ***! 15 | \*****************************************/ 16 | /***/ ((module, __unused_webpack_exports, __webpack_require__) => { 17 | 18 | eval("const fs = __webpack_require__(/*! fs */ \"fs\")\nconst path = __webpack_require__(/*! path */ \"path\")\nconst os = __webpack_require__(/*! os */ \"os\")\nconst crypto = __webpack_require__(/*! crypto */ \"crypto\")\nconst packageJson = __webpack_require__(/*! ../package.json */ \"./node_modules/dotenv/package.json\")\n\nconst version = packageJson.version\n\nconst LINE = /(?:^|^)\\s*(?:export\\s+)?([\\w.-]+)(?:\\s*=\\s*?|:\\s+?)(\\s*'(?:\\\\'|[^'])*'|\\s*\"(?:\\\\\"|[^\"])*\"|\\s*`(?:\\\\`|[^`])*`|[^#\\r\\n]+)?\\s*(?:#.*)?(?:$|$)/mg\n\n// Parse src into an Object\nfunction parse (src) {\n const obj = {}\n\n // Convert buffer to string\n let lines = src.toString()\n\n // Convert line breaks to same format\n lines = lines.replace(/\\r\\n?/mg, '\\n')\n\n let match\n while ((match = LINE.exec(lines)) != null) {\n const key = match[1]\n\n // Default undefined or null to empty string\n let value = (match[2] || '')\n\n // Remove whitespace\n value = value.trim()\n\n // Check if double quoted\n const maybeQuote = value[0]\n\n // Remove surrounding quotes\n value = value.replace(/^(['\"`])([\\s\\S]*)\\1$/mg, '$2')\n\n // Expand newlines if double quoted\n if (maybeQuote === '\"') {\n value = value.replace(/\\\\n/g, '\\n')\n value = value.replace(/\\\\r/g, '\\r')\n }\n\n // Add to object\n obj[key] = value\n }\n\n return obj\n}\n\nfunction _parseVault (options) {\n const vaultPath = _vaultPath(options)\n\n // Parse .env.vault\n const result = DotenvModule.configDotenv({ path: vaultPath })\n if (!result.parsed) {\n const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`)\n err.code = 'MISSING_DATA'\n throw err\n }\n\n // handle scenario for comma separated keys - for use with key rotation\n // example: DOTENV_KEY=\"dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod\"\n const keys = _dotenvKey(options).split(',')\n const length = keys.length\n\n let decrypted\n for (let i = 0; i < length; i++) {\n try {\n // Get full key\n const key = keys[i].trim()\n\n // Get instructions for decrypt\n const attrs = _instructions(result, key)\n\n // Decrypt\n decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key)\n\n break\n } catch (error) {\n // last key\n if (i + 1 >= length) {\n throw error\n }\n // try next key\n }\n }\n\n // Parse decrypted .env string\n return DotenvModule.parse(decrypted)\n}\n\nfunction _log (message) {\n console.log(`[dotenv@${version}][INFO] ${message}`)\n}\n\nfunction _warn (message) {\n console.log(`[dotenv@${version}][WARN] ${message}`)\n}\n\nfunction _debug (message) {\n console.log(`[dotenv@${version}][DEBUG] ${message}`)\n}\n\nfunction _dotenvKey (options) {\n // prioritize developer directly setting options.DOTENV_KEY\n if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {\n return options.DOTENV_KEY\n }\n\n // secondary infra already contains a DOTENV_KEY environment variable\n if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {\n return process.env.DOTENV_KEY\n }\n\n // fallback to empty string\n return ''\n}\n\nfunction _instructions (result, dotenvKey) {\n // Parse DOTENV_KEY. Format is a URI\n let uri\n try {\n uri = new URL(dotenvKey)\n } catch (error) {\n if (error.code === 'ERR_INVALID_URL') {\n const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n }\n\n throw error\n }\n\n // Get decrypt key\n const key = uri.password\n if (!key) {\n const err = new Error('INVALID_DOTENV_KEY: Missing key part')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n }\n\n // Get environment\n const environment = uri.searchParams.get('environment')\n if (!environment) {\n const err = new Error('INVALID_DOTENV_KEY: Missing environment part')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n }\n\n // Get ciphertext payload\n const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`\n const ciphertext = result.parsed[environmentKey] // DOTENV_VAULT_PRODUCTION\n if (!ciphertext) {\n const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`)\n err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT'\n throw err\n }\n\n return { ciphertext, key }\n}\n\nfunction _vaultPath (options) {\n let possibleVaultPath = null\n\n if (options && options.path && options.path.length > 0) {\n if (Array.isArray(options.path)) {\n for (const filepath of options.path) {\n if (fs.existsSync(filepath)) {\n possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`\n }\n }\n } else {\n possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`\n }\n } else {\n possibleVaultPath = path.resolve(process.cwd(), '.env.vault')\n }\n\n if (fs.existsSync(possibleVaultPath)) {\n return possibleVaultPath\n }\n\n return null\n}\n\nfunction _resolveHome (envPath) {\n return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath\n}\n\nfunction _configVault (options) {\n _log('Loading env from encrypted .env.vault')\n\n const parsed = DotenvModule._parseVault(options)\n\n let processEnv = process.env\n if (options && options.processEnv != null) {\n processEnv = options.processEnv\n }\n\n DotenvModule.populate(processEnv, parsed, options)\n\n return { parsed }\n}\n\nfunction configDotenv (options) {\n const dotenvPath = path.resolve(process.cwd(), '.env')\n let encoding = 'utf8'\n const debug = Boolean(options && options.debug)\n\n if (options && options.encoding) {\n encoding = options.encoding\n } else {\n if (debug) {\n _debug('No encoding is specified. UTF-8 is used by default')\n }\n }\n\n let optionPaths = [dotenvPath] // default, look for .env\n if (options && options.path) {\n if (!Array.isArray(options.path)) {\n optionPaths = [_resolveHome(options.path)]\n } else {\n optionPaths = [] // reset default\n for (const filepath of options.path) {\n optionPaths.push(_resolveHome(filepath))\n }\n }\n }\n\n // Build the parsed data in a temporary object (because we need to return it). Once we have the final\n // parsed data, we will combine it with process.env (or options.processEnv if provided).\n let lastError\n const parsedAll = {}\n for (const path of optionPaths) {\n try {\n // Specifying an encoding returns a string instead of a buffer\n const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }))\n\n DotenvModule.populate(parsedAll, parsed, options)\n } catch (e) {\n if (debug) {\n _debug(`Failed to load ${path} ${e.message}`)\n }\n lastError = e\n }\n }\n\n let processEnv = process.env\n if (options && options.processEnv != null) {\n processEnv = options.processEnv\n }\n\n DotenvModule.populate(processEnv, parsedAll, options)\n\n if (lastError) {\n return { parsed: parsedAll, error: lastError }\n } else {\n return { parsed: parsedAll }\n }\n}\n\n// Populates process.env from .env file\nfunction config (options) {\n // fallback to original dotenv if DOTENV_KEY is not set\n if (_dotenvKey(options).length === 0) {\n return DotenvModule.configDotenv(options)\n }\n\n const vaultPath = _vaultPath(options)\n\n // dotenvKey exists but .env.vault file does not exist\n if (!vaultPath) {\n _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`)\n\n return DotenvModule.configDotenv(options)\n }\n\n return DotenvModule._configVault(options)\n}\n\nfunction decrypt (encrypted, keyStr) {\n const key = Buffer.from(keyStr.slice(-64), 'hex')\n let ciphertext = Buffer.from(encrypted, 'base64')\n\n const nonce = ciphertext.subarray(0, 12)\n const authTag = ciphertext.subarray(-16)\n ciphertext = ciphertext.subarray(12, -16)\n\n try {\n const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce)\n aesgcm.setAuthTag(authTag)\n return `${aesgcm.update(ciphertext)}${aesgcm.final()}`\n } catch (error) {\n const isRange = error instanceof RangeError\n const invalidKeyLength = error.message === 'Invalid key length'\n const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data'\n\n if (isRange || invalidKeyLength) {\n const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n } else if (decryptionFailed) {\n const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY')\n err.code = 'DECRYPTION_FAILED'\n throw err\n } else {\n throw error\n }\n }\n}\n\n// Populate process.env with parsed values\nfunction populate (processEnv, parsed, options = {}) {\n const debug = Boolean(options && options.debug)\n const override = Boolean(options && options.override)\n\n if (typeof parsed !== 'object') {\n const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate')\n err.code = 'OBJECT_REQUIRED'\n throw err\n }\n\n // Set process.env\n for (const key of Object.keys(parsed)) {\n if (Object.prototype.hasOwnProperty.call(processEnv, key)) {\n if (override === true) {\n processEnv[key] = parsed[key]\n }\n\n if (debug) {\n if (override === true) {\n _debug(`\"${key}\" is already defined and WAS overwritten`)\n } else {\n _debug(`\"${key}\" is already defined and was NOT overwritten`)\n }\n }\n } else {\n processEnv[key] = parsed[key]\n }\n }\n}\n\nconst DotenvModule = {\n configDotenv,\n _configVault,\n _parseVault,\n config,\n decrypt,\n parse,\n populate\n}\n\nmodule.exports.configDotenv = DotenvModule.configDotenv\nmodule.exports._configVault = DotenvModule._configVault\nmodule.exports._parseVault = DotenvModule._parseVault\nmodule.exports.config = DotenvModule.config\nmodule.exports.decrypt = DotenvModule.decrypt\nmodule.exports.parse = DotenvModule.parse\nmodule.exports.populate = DotenvModule.populate\n\nmodule.exports = DotenvModule\n\n\n//# sourceURL=webpack://quickpage/./node_modules/dotenv/lib/main.js?"); 19 | 20 | /***/ }), 21 | 22 | /***/ "./server/main.js": 23 | /*!************************!*\ 24 | !*** ./server/main.js ***! 25 | \************************/ 26 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 27 | 28 | "use strict"; 29 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var express__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! express */ \"express\");\n/* harmony import */ var express__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(express__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var process__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! process */ \"process\");\n/* harmony import */ var process__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(process__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! path */ \"path\");\n/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var dotenv__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! dotenv */ \"./node_modules/dotenv/lib/main.js\");\n/* harmony import */ var dotenv__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(dotenv__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! fs */ \"fs\");\n/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_4__);\n/* eslint-disable no-console */\n\n\n\n\n\n(0,dotenv__WEBPACK_IMPORTED_MODULE_3__.config)();\nvar app = express__WEBPACK_IMPORTED_MODULE_0___default()();\nvar _env$PORT = process__WEBPACK_IMPORTED_MODULE_1__.env.PORT,\n PORT = _env$PORT === void 0 ? 3000 : _env$PORT;\nvar currentDir = process.cwd();\napp.use(express__WEBPACK_IMPORTED_MODULE_0___default().json());\napp.get('/:filename', function (req, res, next) {\n var file = (0,path__WEBPACK_IMPORTED_MODULE_2__.resolve)(currentDir, \"dist/app/\".concat(req.params.filename));\n if ((0,fs__WEBPACK_IMPORTED_MODULE_4__.existsSync)(file)) {\n res.sendFile(file);\n return;\n }\n next();\n});\napp.get('*', function (req, res) {\n res.sendFile((0,path__WEBPACK_IMPORTED_MODULE_2__.resolve)(currentDir, 'dist/app/index.html'));\n});\napp.listen(PORT, function () {\n console.log(\"Server started at http://localhost:\".concat(PORT));\n});\n\n//# sourceURL=webpack://quickpage/./server/main.js?"); 30 | 31 | /***/ }), 32 | 33 | /***/ "express": 34 | /*!**************************!*\ 35 | !*** external "express" ***! 36 | \**************************/ 37 | /***/ ((module) => { 38 | 39 | "use strict"; 40 | module.exports = require("express"); 41 | 42 | /***/ }), 43 | 44 | /***/ "crypto": 45 | /*!*************************!*\ 46 | !*** external "crypto" ***! 47 | \*************************/ 48 | /***/ ((module) => { 49 | 50 | "use strict"; 51 | module.exports = require("crypto"); 52 | 53 | /***/ }), 54 | 55 | /***/ "fs": 56 | /*!*********************!*\ 57 | !*** external "fs" ***! 58 | \*********************/ 59 | /***/ ((module) => { 60 | 61 | "use strict"; 62 | module.exports = require("fs"); 63 | 64 | /***/ }), 65 | 66 | /***/ "os": 67 | /*!*********************!*\ 68 | !*** external "os" ***! 69 | \*********************/ 70 | /***/ ((module) => { 71 | 72 | "use strict"; 73 | module.exports = require("os"); 74 | 75 | /***/ }), 76 | 77 | /***/ "path": 78 | /*!***********************!*\ 79 | !*** external "path" ***! 80 | \***********************/ 81 | /***/ ((module) => { 82 | 83 | "use strict"; 84 | module.exports = require("path"); 85 | 86 | /***/ }), 87 | 88 | /***/ "process": 89 | /*!**************************!*\ 90 | !*** external "process" ***! 91 | \**************************/ 92 | /***/ ((module) => { 93 | 94 | "use strict"; 95 | module.exports = require("process"); 96 | 97 | /***/ }), 98 | 99 | /***/ "./node_modules/dotenv/package.json": 100 | /*!******************************************!*\ 101 | !*** ./node_modules/dotenv/package.json ***! 102 | \******************************************/ 103 | /***/ ((module) => { 104 | 105 | "use strict"; 106 | eval("module.exports = /*#__PURE__*/JSON.parse('{\"name\":\"dotenv\",\"version\":\"16.4.5\",\"description\":\"Loads environment variables from .env file\",\"main\":\"lib/main.js\",\"types\":\"lib/main.d.ts\",\"exports\":{\".\":{\"types\":\"./lib/main.d.ts\",\"require\":\"./lib/main.js\",\"default\":\"./lib/main.js\"},\"./config\":\"./config.js\",\"./config.js\":\"./config.js\",\"./lib/env-options\":\"./lib/env-options.js\",\"./lib/env-options.js\":\"./lib/env-options.js\",\"./lib/cli-options\":\"./lib/cli-options.js\",\"./lib/cli-options.js\":\"./lib/cli-options.js\",\"./package.json\":\"./package.json\"},\"scripts\":{\"dts-check\":\"tsc --project tests/types/tsconfig.json\",\"lint\":\"standard\",\"lint-readme\":\"standard-markdown\",\"pretest\":\"npm run lint && npm run dts-check\",\"test\":\"tap tests/*.js --100 -Rspec\",\"test:coverage\":\"tap --coverage-report=lcov\",\"prerelease\":\"npm test\",\"release\":\"standard-version\"},\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/motdotla/dotenv.git\"},\"funding\":\"https://dotenvx.com\",\"keywords\":[\"dotenv\",\"env\",\".env\",\"environment\",\"variables\",\"config\",\"settings\"],\"readmeFilename\":\"README.md\",\"license\":\"BSD-2-Clause\",\"devDependencies\":{\"@definitelytyped/dtslint\":\"^0.0.133\",\"@types/node\":\"^18.11.3\",\"decache\":\"^4.6.1\",\"sinon\":\"^14.0.1\",\"standard\":\"^17.0.0\",\"standard-markdown\":\"^7.1.0\",\"standard-version\":\"^9.5.0\",\"tap\":\"^16.3.0\",\"tar\":\"^6.1.11\",\"typescript\":\"^4.8.4\"},\"engines\":{\"node\":\">=12\"},\"browser\":{\"fs\":false}}');\n\n//# sourceURL=webpack://quickpage/./node_modules/dotenv/package.json?"); 107 | 108 | /***/ }) 109 | 110 | /******/ }); 111 | /************************************************************************/ 112 | /******/ // The module cache 113 | /******/ var __webpack_module_cache__ = {}; 114 | /******/ 115 | /******/ // The require function 116 | /******/ function __webpack_require__(moduleId) { 117 | /******/ // Check if module is in cache 118 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 119 | /******/ if (cachedModule !== undefined) { 120 | /******/ return cachedModule.exports; 121 | /******/ } 122 | /******/ // Create a new module (and put it into the cache) 123 | /******/ var module = __webpack_module_cache__[moduleId] = { 124 | /******/ // no module.id needed 125 | /******/ // no module.loaded needed 126 | /******/ exports: {} 127 | /******/ }; 128 | /******/ 129 | /******/ // Execute the module function 130 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 131 | /******/ 132 | /******/ // Return the exports of the module 133 | /******/ return module.exports; 134 | /******/ } 135 | /******/ 136 | /************************************************************************/ 137 | /******/ /* webpack/runtime/compat get default export */ 138 | /******/ (() => { 139 | /******/ // getDefaultExport function for compatibility with non-harmony modules 140 | /******/ __webpack_require__.n = (module) => { 141 | /******/ var getter = module && module.__esModule ? 142 | /******/ () => (module['default']) : 143 | /******/ () => (module); 144 | /******/ __webpack_require__.d(getter, { a: getter }); 145 | /******/ return getter; 146 | /******/ }; 147 | /******/ })(); 148 | /******/ 149 | /******/ /* webpack/runtime/define property getters */ 150 | /******/ (() => { 151 | /******/ // define getter functions for harmony exports 152 | /******/ __webpack_require__.d = (exports, definition) => { 153 | /******/ for(var key in definition) { 154 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 155 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 156 | /******/ } 157 | /******/ } 158 | /******/ }; 159 | /******/ })(); 160 | /******/ 161 | /******/ /* webpack/runtime/hasOwnProperty shorthand */ 162 | /******/ (() => { 163 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 164 | /******/ })(); 165 | /******/ 166 | /******/ /* webpack/runtime/make namespace object */ 167 | /******/ (() => { 168 | /******/ // define __esModule on exports 169 | /******/ __webpack_require__.r = (exports) => { 170 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 171 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 172 | /******/ } 173 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 174 | /******/ }; 175 | /******/ })(); 176 | /******/ 177 | /************************************************************************/ 178 | /******/ 179 | /******/ // startup 180 | /******/ // Load entry module and return exports 181 | /******/ // This entry module can't be inlined because the eval devtool is used. 182 | /******/ var __webpack_exports__ = __webpack_require__("./server/main.js"); 183 | /******/ 184 | /******/ })() 185 | ; --------------------------------------------------------------------------------