├── .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 |
--------------------------------------------------------------------------------
/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 | ;
--------------------------------------------------------------------------------