Browse Source

initial commit

master
Anthony Hinsinger 3 years ago
commit
fb97dc15f8
  1. 4
      .dockerignore
  2. 1
      .env.example
  3. 1
      .eslintignore
  4. 4
      .gitignore
  5. 23
      Dockerfile
  6. 15981
      package-lock.json
  7. 70
      package.json
  8. 9
      src/index.ejs
  9. 5
      src/server/router.ts
  10. 18
      src/server/server-dev.ts
  11. 16
      src/server/server.ts
  12. 97
      src/web/hooks/css.ts
  13. 19
      src/web/main.scss
  14. 36
      src/web/main.ts
  15. 20
      src/web/typings.d.ts
  16. 9
      tsconfig.server.json
  17. 10
      tsconfig.web.json
  18. 22
      webpack.server.common.js
  19. 15
      webpack.server.development.js
  20. 15
      webpack.server.production.js
  21. 51
      webpack.web.common.js
  22. 13
      webpack.web.development.js
  23. 10
      webpack.web.production.js

4
.dockerignore

@ -0,0 +1,4 @@
dist
node_modules
.env
.env.*

1
.env.example

@ -0,0 +1 @@
BASE_URL=https://portal.mydomain.com

1
.eslintignore

@ -0,0 +1 @@
**/*.js

4
.gitignore vendored

@ -0,0 +1,4 @@
.env
dist
node_modules
yarn-error.log

23
Dockerfile

@ -0,0 +1,23 @@
# syntax=docker/dockerfile:1
FROM node:18-alpine AS builder
WORKDIR /app
COPY . .
RUN npm install
ENV NODE_ENV=production
RUN npm run build
FROM node:18-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm install
COPY --from=builder /app/dist .
CMD ["node", "./server/server.js"]

15981
package-lock.json generated

File diff suppressed because it is too large Load Diff

70
package.json

@ -0,0 +1,70 @@
{
"name": "vpn-portal",
"version": "1.0.0",
"description": "Portal for openvpn auth and profiles",
"scripts": {
"build": "run-s build:server build:web",
"build:server": "webpack --config webpack.server.production.js",
"build:web": "webpack --config webpack.web.production.js",
"build:server:once": "webpack --config webpack.server.development.js",
"dev:server": "npm run build:server:once && run-p nodemon:prod watch:server",
"watch:server": "webpack --config webpack.server.development.js --watch",
"nodemon:prod": "nodemon dist/server/server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@webcomponents/webcomponentsjs": "^2.7.0",
"express": "^4.18.2",
"haunted": "^5.0.0",
"lit-html": "^2.6.1"
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/webpack-dev-middleware": "^5.3.0",
"@types/webpack-hot-middleware": "^2.25.6",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3",
"dotenv-webpack": "^8.0.1",
"eslint": "^8.33.0",
"extract-loader": "^5.1.0",
"html-webpack-plugin": "^5.5.0",
"node-sass": "^8.0.0",
"nodemon": "^2.0.20",
"npm-run-all": "^4.1.5",
"sass-loader": "^13.2.0",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-middleware": "^6.0.1",
"webpack-hot-middleware": "^2.25.3",
"webpack-merge": "^5.8.0",
"webpack-node-externals": "^3.0.0"
},
"nodemonConfig": {
"exec": "node -r dotenv/config",
"watch": [
"dist/server"
]
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": 0
}
}
}

9
src/index.ejs

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title><%= htmlWebpackPlugin.options.title %></title>
<script src="webcomponents-loader.js"></script>
</head>
<body></body>
</html>

5
src/server/router.ts

@ -0,0 +1,5 @@
import express from "express";
const router = express.Router();
export default router;

18
src/server/server-dev.ts

@ -0,0 +1,18 @@
import express from "express";
import router from "./router";
import webpack from "webpack";
import webpackDevMiddleware from "webpack-dev-middleware";
import webpackHotMiddleware from "webpack-hot-middleware";
import webpackConfig from "../../webpack.web.development.js";
const app = express();
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler));
app.use(webpackHotMiddleware(compiler));
app.use(router);
app.listen(8080, () => {
console.log("Server started");
});

16
src/server/server.ts

@ -0,0 +1,16 @@
import express from "express";
import router from "./router";
import path from "path";
const app = express();
app.use(express.static(path.join(__dirname, "..", "web")));
app.use(router);
const server = app.listen(8080, () => {
console.log("Server started");
});
process.on("SIGTERM", () => {
server.close();
});

97
src/web/hooks/css.ts

@ -0,0 +1,97 @@
import { unsafeCSS, supportsAdoptingStyleSheets, CSSResult } from "lit-element";
import { useState, useEffect, useLayoutEffect, useMemo, useRef } from "haunted";
export function useStylesheet(initialStyles: CSSResult[] | CSSResult) {
const [needsShim, setNeedsShim] = useState(false);
const userStyles = useRef(initialStyles);
const styles = useMemo(() => {
let _styles: CSSResult[];
if (Array.isArray(userStyles.current)) {
// De-duplicate styles preserving the _last_ instance in the set.
// This is a performance optimization to avoid duplicated styles that can
// occur especially when composing via subclassing.
// The last item is kept to try to preserve the cascade order with the
// assumption that it's most important that last added styles override
// previous styles.
const addStyles = (styles: CSSResult[], set: Set<CSSResult>) =>
styles.reduceRight(
(set, s) =>
// Note: On IE set.add() does not return the set
Array.isArray(s) ? addStyles(s, set) : (set.add(s), set),
set
);
// Array.from does not work on Set in IE, otherwise return
// Array.from(addStyles(userStyles, new Set<CSSResult>())).reverse()
const set = addStyles(userStyles.current, new Set<CSSResult>());
const styles: CSSResult[] = [];
set.forEach((v) => styles.unshift(v));
_styles = styles;
} else {
_styles = userStyles.current === undefined ? [] : [userStyles.current];
}
// Ensure that there are no invalid CSSStyleSheet instances here. They are
// invalid in two conditions.
// (1) the sheet is non-constructible (`sheet` of a HTMLStyleElement), but
// this is impossible to check except via .replaceSync or use
// (2) the ShadyCSS polyfill is enabled (:. supportsAdoptingStyleSheets is
// false)
return _styles.map((s) => {
if (s instanceof CSSStyleSheet && !supportsAdoptingStyleSheets) {
// Flatten the cssText from the passed constructible stylesheet (or
// undetectable non-constructible stylesheet). The user might have
// expected to update their stylesheets over time, but the alternative
// is a crash.
const cssText: string = Array.prototype.slice
.call(s.cssRules)
.reduce((css: string, rule: CSSRule) => css + rule.cssText, "");
return unsafeCSS(cssText);
}
return s;
});
}, [userStyles.current]);
useLayoutEffect(
function () {
const elm = this.host as Element;
// There are three separate cases here based on Shadow DOM support.
// (1) shadowRoot polyfilled: use ShadyCSS
// (2) shadowRoot.adoptedStyleSheets available: use it
// (3) shadowRoot.adoptedStyleSheets polyfilled: append styles after
// rendering
if (
window["ShadyCSS"] !== undefined &&
!window["ShadyCSS"].nativeShadow
) {
window["ShadyCSS"].ScopingShim.prepareAdoptedCssText(
styles.map((s) => (s instanceof CSSResult ? s.cssText : "")),
elm.localName
);
} else if (supportsAdoptingStyleSheets) {
elm.shadowRoot!.adoptedStyleSheets = styles.map((s) =>
s instanceof CSSStyleSheet ? s : s.styleSheet!
);
} else {
setNeedsShim(true);
}
},
[styles]
);
useEffect(
function () {
const elm = this.host as Element;
if (needsShim) {
styles.forEach((s) => {
const style = document.createElement("style");
style.textContent = s.cssText;
elm.shadowRoot!.appendChild(style);
});
setNeedsShim(false);
}
},
[needsShim, styles]
);
}

19
src/web/main.scss

@ -0,0 +1,19 @@
:host {
font-family: Ubuntu, sans-serif;
display: block;
border: solid 1px black;
padding: 15px;
}
h1 {
color: #77216f;
}
button {
border-radius: 4px;
padding: 8px;
font-size: 16px;
font-weight: bold;
border: 0;
background-color: #aea79f;
}

36
src/web/main.ts

@ -0,0 +1,36 @@
import { component, useState } from "haunted";
import { html, render } from "lit-html";
import { css, unsafeCSS } from "lit-element";
import { useStylesheet } from "./hooks/css";
import style from "./main.scss";
const CSS = css`
${unsafeCSS(style)}
`;
if (module["hot"]) {
module["hot"].accept(() => {
window.location.reload();
});
}
const AppComp = () => {
useStylesheet(CSS);
const [counter, setCounter] = useState(0);
return html`
<h1>My App</h1>
<p>Value = ${counter}</p>
<button
@click=${() => {
setCounter(counter + 1);
}}
>
increment
</button>
`;
};
customElements.define("my-app", component(AppComp));
render(html`<my-app></my-app>`, document.body);

20
src/web/typings.d.ts vendored

@ -0,0 +1,20 @@
// typings.d.ts
declare module '*.scss' {
const content: string;
export default content;
}
declare module '*.png' {
const content: string;
export default content;
}
declare module '*.jpg' {
const content: string;
export default content;
}
declare module '*.gif' {
const content: string;
export default content;
}

9
tsconfig.server.json

@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"removeComments": true,
"esModuleInterop": true
},
"include": ["src/server", "src/shared"]
}

10
tsconfig.web.json

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"removeComments": true,
"esModuleInterop": true
},
"include": ["src/web", "src/shared"]
}

22
webpack.server.common.js

@ -0,0 +1,22 @@
const path = require("path");
module.exports = {
module: {
rules: [
{
exclude: [/node_modules/],
test: /\.ts$/,
loader: "ts-loader",
options: { configFile: "tsconfig.server.json" },
},
],
},
output: {
filename: "server.js",
path: path.resolve(__dirname, "dist", "server"),
},
resolve: {
extensions: [".ts", ".js"],
},
target: "node",
};

15
webpack.server.development.js

@ -0,0 +1,15 @@
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const { merge } = require("webpack-merge");
const nodeExternals = require("webpack-node-externals");
const path = require("path");
const webpack = require("webpack");
const common = require("./webpack.server.common.js");
module.exports = merge(common, {
devtool: "inline-source-map",
entry: [path.join(__dirname, "src/server/server-dev.ts")],
externals: [ nodeExternals(), ],
mode: "development",
plugins: [new CleanWebpackPlugin()],
});

15
webpack.server.production.js

@ -0,0 +1,15 @@
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const { merge } = require("webpack-merge");
const nodeExternals = require("webpack-node-externals");
const path = require("path");
const common = require("./webpack.server.common.js");
module.exports = merge(common, {
devtool: "source-map",
node: { __dirname: false },
entry: [path.join(__dirname, "src/server/server.ts")],
externals: [nodeExternals()],
mode: "production",
plugins: [new CleanWebpackPlugin()],
});

51
webpack.web.common.js

@ -0,0 +1,51 @@
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const Dotenv = require("dotenv-webpack");
module.exports = {
target: "web",
module: {
rules: [
{
exclude: [/node_modules/],
test: /\.ts$/,
loader: "ts-loader",
options: { configFile: "tsconfig.web.json" },
},
{
test: /\.(css|scss)$/,
use: ["css-loader", "sass-loader"],
},
{
test: /\.(png|jpg|gif)$/,
type: "asset/resource",
},
],
},
output: {
filename: "bundle.[name].js",
path: path.resolve(__dirname, "dist", "web"),
},
resolve: {
extensions: [".ts", ".js"],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./src/index.ejs",
title: "My App",
}),
new CopyWebpackPlugin({
patterns: [
"./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js",
{
from: "./node_modules/@webcomponents/webcomponentsjs/bundles",
to: "bundles",
},
],
}),
new Dotenv(),
],
};

13
webpack.web.development.js

@ -0,0 +1,13 @@
const { merge, } = require("webpack-merge");
const webpack = require("webpack");
const common = require("./webpack.web.common.js");
module.exports = merge(common, {
devtool: "inline-source-map",
entry: ["webpack-hot-middleware/client?timeout=1000&reload=true", "./src/web/main.ts"],
mode: "development",
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
});

10
webpack.web.production.js

@ -0,0 +1,10 @@
const { merge } = require("webpack-merge");
const path = require("path");
const common = require("./webpack.web.common.js");
module.exports = merge(common, {
// devtool: "source-map",
entry: [path.join(__dirname, "src/web/main.ts")],
mode: "production",
});
Loading…
Cancel
Save