diff --git a/.gitignore b/.gitignore
index eb79dd5..700344c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
node_modules
.idea
+public
+dist
\ No newline at end of file
diff --git a/assets/config.json b/assets/config.json
new file mode 100644
index 0000000..5c18915
--- /dev/null
+++ b/assets/config.json
@@ -0,0 +1,10 @@
+{
+ "bundles": {
+ "app": "js/app.js",
+ "layout": "sass/layout.scss",
+ "error": "sass/error.scss",
+ "logo": "img/logo.svg",
+ "logo_png": "img/logox128.png",
+ "logo_png_xxl": "img/logox1024.png"
+ }
+}
\ No newline at end of file
diff --git a/assets/img/logo.svg b/assets/img/logo.svg
new file mode 100644
index 0000000..e81b818
--- /dev/null
+++ b/assets/img/logo.svg
@@ -0,0 +1,71 @@
+
+
diff --git a/assets/img/logox1024.png b/assets/img/logox1024.png
new file mode 100644
index 0000000..f5894c0
Binary files /dev/null and b/assets/img/logox1024.png differ
diff --git a/assets/img/logox128.png b/assets/img/logox128.png
new file mode 100644
index 0000000..a1252a5
Binary files /dev/null and b/assets/img/logox128.png differ
diff --git a/assets/js/app.js b/assets/js/app.js
new file mode 100644
index 0000000..fc7c539
--- /dev/null
+++ b/assets/js/app.js
@@ -0,0 +1,6 @@
+import './external_links';
+import './message_icons';
+
+import '../sass/app.scss';
+
+console.log('Hello world!');
\ No newline at end of file
diff --git a/assets/js/external_links.js b/assets/js/external_links.js
new file mode 100644
index 0000000..7108c4e
--- /dev/null
+++ b/assets/js/external_links.js
@@ -0,0 +1,9 @@
+import feather from "feather-icons";
+
+document.addEventListener('DOMContentLoaded', () => {
+ document.querySelectorAll('a[target="_blank"]').forEach(el => {
+ el.innerHTML += ``;
+ });
+
+ feather.replace();
+});
\ No newline at end of file
diff --git a/assets/js/message_icons.js b/assets/js/message_icons.js
new file mode 100644
index 0000000..b625f55
--- /dev/null
+++ b/assets/js/message_icons.js
@@ -0,0 +1,21 @@
+import feather from "feather-icons";
+
+document.addEventListener('DOMContentLoaded', () => {
+ const messageTypeToIcon = {
+ info: 'info',
+ success: 'check',
+ warning: 'alert-triangle',
+ error: 'x-circle',
+ question: 'help-circle',
+ };
+ document.querySelectorAll('.message').forEach(el => {
+ const type = el.dataset['type'];
+ const icon = el.querySelector('.icon');
+ const svgContainer = document.createElement('div');
+ svgContainer.innerHTML = feather.icons[messageTypeToIcon[type]].toSvg();
+ el.insertBefore(svgContainer.firstChild, icon);
+ icon.remove();
+ });
+
+ feather.replace();
+});
\ No newline at end of file
diff --git a/assets/sass/_fonts.scss b/assets/sass/_fonts.scss
new file mode 100644
index 0000000..6c26b55
--- /dev/null
+++ b/assets/sass/_fonts.scss
@@ -0,0 +1,81 @@
+/* vietnamese */
+@font-face {
+ font-family: 'Nunito Sans';
+ font-style: normal;
+ font-weight: 300;
+ font-display: swap;
+ src: local('Nunito Sans Light'), local('NunitoSans-Light'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8WAc5iU1EQVg.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Nunito Sans';
+ font-style: normal;
+ font-weight: 300;
+ font-display: swap;
+ src: local('Nunito Sans Light'), local('NunitoSans-Light'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8WAc5jU1EQVg.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Nunito Sans';
+ font-style: normal;
+ font-weight: 300;
+ font-display: swap;
+ src: local('Nunito Sans Light'), local('NunitoSans-Light'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8WAc5tU1E.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Nunito Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: local('Nunito Sans Regular'), local('NunitoSans-Regular'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe0qMImSLYBIv1o4X1M8cceyI9tScg.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Nunito Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: local('Nunito Sans Regular'), local('NunitoSans-Regular'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe0qMImSLYBIv1o4X1M8ccezI9tScg.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Nunito Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: local('Nunito Sans Regular'), local('NunitoSans-Regular'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe0qMImSLYBIv1o4X1M8cce9I9s.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Nunito Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: local('Nunito Sans Bold'), local('NunitoSans-Bold'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8GBs5iU1EQVg.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Nunito Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: local('Nunito Sans Bold'), local('NunitoSans-Bold'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8GBs5jU1EQVg.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Nunito Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: local('Nunito Sans Bold'), local('NunitoSans-Bold'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8GBs5tU1E.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
diff --git a/assets/sass/_vars.scss b/assets/sass/_vars.scss
new file mode 100644
index 0000000..5c7b523
--- /dev/null
+++ b/assets/sass/_vars.scss
@@ -0,0 +1,23 @@
+$primary: #1e2932;
+$primaryForeground: #f0f0f0;
+$secondary: #00766c;
+$secondaryForeground: $primaryForeground;
+
+$backgroundColor: $primary;
+$defaultTextColor: $primaryForeground;
+
+$info: #49fb;
+$infoText: darken($info, 42%);
+$infoColor: desaturate($infoText, 50%);
+
+$success: #5f5b;
+$successText: darken($success, 45%);
+$successColor: desaturate($successText, 50%);
+
+$warning: #fc0b;
+$warningText: darken($warning, 30%);
+$warningColor: desaturate($warningText, 50%);
+
+$error: #f00b;
+$errorText: darken($error, 30%);
+$errorColor: desaturate($errorText, 50%);
diff --git a/assets/sass/app.scss b/assets/sass/app.scss
new file mode 100644
index 0000000..933fea1
--- /dev/null
+++ b/assets/sass/app.scss
@@ -0,0 +1 @@
+@import "layout";
\ No newline at end of file
diff --git a/assets/sass/error.scss b/assets/sass/error.scss
new file mode 100644
index 0000000..d36ad0b
--- /dev/null
+++ b/assets/sass/error.scss
@@ -0,0 +1,90 @@
+@import "layout";
+
+header, footer {
+ margin: 0;
+ padding: 0;
+ height: 0;
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ .messages {
+ margin-bottom: 32px;
+ }
+
+ .error-code {
+ font-size: 36px;
+ }
+
+ .error-message {
+ font-size: 32px;
+ }
+
+ .error-instructions {
+ margin-top: 32px;
+ font-size: 20px;
+ }
+
+ nav {
+ margin-top: 32px;
+ }
+
+ &::before {
+ content: "Oops";
+ position: absolute;
+ z-index: -1;
+
+ font-size: #{'min(50vh, 40vw)'};
+ opacity: 0.025;
+ }
+}
+
+.contact {
+ text-align: center;
+ padding: 8px;
+}
+
+.logo {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ margin-top: 24px;
+ text-align: center;
+
+ a {
+ position: relative;
+ padding: 16px;
+
+ color: $defaultTextColor;
+
+ &:hover {
+ color: #fff;
+
+ &::before {
+ opacity: 0.2;
+ }
+ }
+
+ &::before {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+
+ background-image: url(../img/logo.svg);
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 64px;
+
+ opacity: 0.075;
+ filter: contrast(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/assets/sass/layout.scss b/assets/sass/layout.scss
new file mode 100644
index 0000000..0eb03a5
--- /dev/null
+++ b/assets/sass/layout.scss
@@ -0,0 +1,512 @@
+@import "vars";
+@import 'fonts';
+
+* {
+ box-sizing: border-box;
+}
+
+html, body {
+ height: 100%;
+}
+
+body {
+ display: flex;
+ flex-direction: column;
+
+ margin: 0;
+ font-family: "Nunito Sans", sans-serif;
+ font-size: 16px;
+
+ color: $defaultTextColor;
+ background-color: $backgroundColor;
+}
+
+header {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+
+ $headerHeight: 64px;
+ height: $headerHeight;
+ line-height: $headerHeight;
+
+ $headerBackground: darken($primary, 1.75%);
+ background-color: $headerBackground;
+
+ .logo {
+ display: flex;
+ flex-direction: row;
+
+ padding: 0 24px 0 16px;
+ font-size: 32px;
+ color: $defaultTextColor;
+
+ &:hover {
+ color: lighten($defaultTextColor, 10%);
+ }
+
+ img {
+ width: $headerHeight;
+ height: $headerHeight;
+ margin-right: 16px;
+ }
+ }
+
+ nav ul {
+ display: flex;
+ flex-direction: row;
+ margin: 0;
+ padding: 0;
+
+ font-size: 20px;
+
+ li {
+ list-style: none;
+
+ a, span {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ height: 64px;
+ padding: 0 24px;
+
+ .feather {
+ --icon-size: 24px;
+ margin-right: 10px;
+ }
+ }
+
+ a:hover {
+ background-color: #fff1;
+ }
+ }
+ }
+}
+
+footer {
+ padding: 8px;
+ margin-top: 8px;
+ text-align: center;
+ background-color: darken($primary, 3%);
+}
+
+main {
+ flex: 1;
+ padding: 8px;
+}
+
+h1 {
+ text-align: center;
+ font-size: 32px;
+
+ & + p {
+ text-align: center;
+ font-size: 20px;
+ }
+}
+
+h1, h2 {
+ font-weight: 100;
+}
+
+h3, h4 {
+ font-weight: 300;
+}
+
+section > h2, .panel > h2 {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ position: relative;
+ text-align: center;
+ margin-top: 16px;
+
+ &::before, &::after {
+ content: "";
+ flex: 1;
+ margin: 0 32px;
+ height: 0;
+ border-bottom: 1px solid $defaultTextColor;
+ opacity: 0.2;
+ }
+}
+
+section > hr, .panel > hr {
+ border: 0;
+ border-bottom: 1px solid $defaultTextColor;
+ opacity: 0.2;
+
+ margin: 8px 32px;
+}
+
+a {
+ color: $secondary;
+ text-decoration: none;
+
+ &:hover {
+ color: lighten($secondary, 10%);
+ }
+
+ .feather.feather-external-link {
+ --icon-size: 16px;
+ margin-left: 4px;
+ margin-top: -3px;
+ }
+}
+
+form {
+ padding: 8px 16px;
+ text-align: center;
+
+ .form-field {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ margin: 16px auto;
+
+ label {
+ position: absolute;
+ left: 8px;
+ top: 20px;
+ user-select: none;
+ font-size: 16px;
+ opacity: 0.75;
+
+ transition-property: top, font-size;
+ transition-duration: 150ms;
+ transition-timing-function: ease-out;
+
+ cursor: text;
+ }
+
+ input, select, .input-group {
+ border: 0;
+ border-bottom: 2px solid #0008;
+ color: $defaultTextColor;
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 3px 3px 0 0;
+ font-size: 16px;
+
+ &:focus, &:not([value=""]) {
+ ~ label {
+ top: 8px;
+ font-size: 14px;
+ }
+ }
+ }
+
+ input, select, .form-display {
+ display: block;
+ padding: 32px 8px 8px 8px;
+ width: 100%;
+ }
+
+ select {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+
+ &::-ms-expand {
+ display: none;
+ }
+
+ & + .feather {
+ position: absolute;
+ z-index: -1;
+ right: 8px;
+ bottom: 8px;
+
+ transition: transform 150ms ease-out;
+ }
+
+ // Temporary
+ &:focus + .feather {
+ transform: rotateX(180deg);
+ }
+ }
+
+ input[type=color] {
+ height: calc(32px + 8px + 32px);
+ }
+
+ &.inline {
+ flex-direction: row;
+
+ input[type=checkbox] {
+ text-align: left;
+ width: min-content;
+ height: min-content;
+
+ & ~ label {
+ position: static;
+ display: inline;
+ padding-left: 8px;
+ font-size: 16px;
+ }
+ }
+ }
+
+ .input-group {
+ display: flex;
+ flex-shrink: 1;
+ flex-direction: row;
+
+ div {
+ position: relative;
+ flex: 1;
+
+ input {
+ width: 100%;
+ margin-top: 24px;
+ padding-top: 8px;
+ border: 0;
+ background: transparent;
+ }
+
+ > input + * {
+ position: absolute;
+ top: 32px;
+ right: 28px;
+ user-select: none;
+ text-align: right;
+ }
+ }
+ }
+
+ .error, .hint {
+ padding: 2px;
+ text-align: left;
+ font-size: 14px;
+
+ .feather {
+ --icon-size: 14px;
+ }
+ }
+
+ .error {
+ color: $error;
+ }
+ }
+}
+
+button, .button {
+ display: inline-flex;
+ margin: 8px;
+ padding: 12px 16px;
+ border: 0;
+ border-radius: 5px;
+ cursor: pointer;
+
+ text-transform: uppercase;
+ font-size: 16px;
+ font-weight: bolder;
+
+ line-height: 16px;
+
+ .feather {
+ --icon-size: 16px;
+ margin-right: 8px;
+ }
+
+ &, &.primary {
+ color: $primaryForeground;
+ background-color: $secondary;
+
+ &:hover {
+ background-color: lighten($secondary, 10%);
+ }
+ }
+
+ &.info {
+ background-color: $infoColor;
+ }
+
+ &.success {
+ background-color: $successColor;
+ }
+
+ &.warning {
+ background-color: $warningColor;
+ }
+
+ &.error, &.danger {
+ background-color: $errorColor;
+
+ &:hover {
+ background-color: lighten($errorColor, 10%);
+ }
+ }
+
+ &.transparent {
+ background-color: transparent;
+ }
+
+ &:hover {
+ color: $primaryForeground;
+ }
+}
+
+.data-table {
+ width: 100%;
+ text-align: left;
+ border-collapse: collapse;
+
+ th, td {
+ padding: 8px;
+ }
+
+ th {
+ border-bottom: 1px solid #ffffff17;
+ }
+
+ tr:nth-child(even) {
+ background-color: #ffffff08;
+ }
+
+ tr:hover {
+ background-color: #ffffff18;
+ }
+
+ thead tr:hover {
+ background-color: transparent;
+ }
+}
+
+// ---
+// --- Layout helpers
+// ---
+.center {
+ text-align: center;
+}
+
+.container {
+ padding: 0 16px;
+ max-width: 632px;
+ margin: 0 auto;
+}
+
+.panel {
+ margin: 16px 0;
+ padding: 8px;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ background-color: rgba(255, 255, 255, 0.03);
+ border-radius: 5px;
+
+ p {
+ margin: 16px 8px;
+ }
+}
+
+.sub-panel {
+ margin: 32px -18px;
+ padding: 1px 16px;
+ border: 2px solid rgba(255, 255, 255, 0.05);
+ border-radius: 5px;
+
+ input, select, .input-group {
+ border-radius: 5px !important;
+ border-width: 0 !important;
+ }
+}
+
+
+// ---
+// --- Feather
+// ---
+.feather {
+ flex-shrink: 0;
+ --icon-size: 24px;
+ width: var(--icon-size);
+ height: var(--icon-size);
+ stroke: currentColor;
+ stroke-width: 2;
+ stroke-linecap: square;
+ stroke-linejoin: miter;
+ fill: none;
+ vertical-align: middle;
+}
+
+// ---
+// --- Helper classes
+// ---
+.message {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ padding: 8px 16px;
+
+ border-radius: 5px;
+
+ .feather {
+ --icon-size: 24px;
+ margin-right: 8px;
+ }
+
+ &:not(&-discreet) {
+ background-color: #fff5;
+
+ &[data-type=info], &[data-type=question] {
+ background-color: $infoColor;
+ }
+
+ &[data-type=success] {
+ background-color: $successColor;
+ }
+
+ &[data-type=warning] {
+ background-color: $warningColor;
+ }
+
+ &[data-type=error] {
+ background-color: $errorColor;
+ }
+ }
+
+ &-discreet {
+ opacity: 0.75;
+
+ .feather {
+ --icon-size: 20px;
+ }
+ }
+}
+
+.messages .message:not(:last-child) {
+ margin-bottom: 8px;
+}
+
+.container > .messages:first-child {
+ margin-top: 16px;
+}
+
+.copyable-text {
+ display: flex;
+ flex-direction: row;
+ margin: 8px;
+
+ background-color: darken($backgroundColor, 2%);
+ border-radius: 5px;
+ overflow: hidden;
+
+ .title {
+ padding: 8px;
+ }
+
+ .content {
+ overflow: hidden;
+ white-space: nowrap;
+ padding: 8px;
+ }
+
+ .copy-button {
+ margin: 0;
+ padding: 0;
+ border-radius: 0;
+
+ .feather {
+ --icon-size: 20px;
+ margin: 8px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 9ce3150..d088e21 100644
--- a/package.json
+++ b/package.json
@@ -2,19 +2,48 @@
"name": "aldap",
"version": "0.1.0",
"description": "Authentication LDAP server",
- "main": "index.js",
"repository": "git@gitlab.com:ArisuOngaku/aldap.git",
"author": "Alice Gaudon ",
- "license": "MIT",
"private": true,
+ "main": "dist/main.js",
+ "scripts": {
+ "test": "jest --verbose --runInBand",
+ "dist-webpack": "webpack --mode production",
+ "dist": "tsc && npm run dist-webpack",
+ "dev": "concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon dist/main.js\" \"webpack --watch --mode development\" \"maildev\"",
+ "start": "yarn dist && node dist/main.js"
+ },
"devDependencies": {
+ "@babel/core": "^7.9.0",
+ "@babel/preset-env": "^7.9.5",
+ "@types/config": "^0.0.36",
+ "@types/express": "^4.17.6",
"@types/node": "^13.13.2",
+ "babel-loader": "^8.1.0",
+ "concurrently": "^5.1.0",
+ "css-loader": "^3.5.2",
+ "feather-icons": "^4.28.0",
+ "file-loader": "^6.0.0",
+ "imagemin": "^7.0.1",
+ "imagemin-gifsicle": "^7.0.0",
+ "imagemin-mozjpeg": "^8.0.0",
+ "imagemin-pngquant": "^8.0.0",
+ "imagemin-svgo": "^7.1.0",
+ "img-loader": "^3.0.1",
"jest": "^25.4.0",
+ "mini-css-extract-plugin": "^0.9.0",
+ "node-sass": "^4.14.0",
+ "nodemon": "^2.0.3",
+ "sass-loader": "^8.0.2",
"ts-jest": "^25.4.0",
"typescript": "^3.8.3",
+ "uglifyjs-webpack-plugin": "^2.2.0",
+ "webpack": "^4.43.0",
+ "webpack-cli": "^3.3.11",
"wms-core": "^0.2.0"
},
"dependencies": {
- "config": "^3.3.1"
+ "config": "^3.3.1",
+ "express": "^4.17.1"
}
}
diff --git a/public/.gitkeep b/public/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..478541b
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,86 @@
+const path = require('path');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
+
+const dev = process.env.NODE_ENV === 'development';
+
+const userConfig = require('./assets/config.json');
+for (const b in userConfig.bundles) {
+ if (userConfig.bundles.hasOwnProperty(b)) {
+ userConfig.bundles[b] = `./assets/${userConfig.bundles[b]}`;
+ }
+}
+
+const config = {
+ entry: userConfig.bundles,
+ output: {
+ path: path.resolve(__dirname, 'public/js'),
+ filename: '[name].js'
+ },
+ devtool: dev ? 'eval-source-map' : undefined,
+ module: {
+ rules: [
+ {
+ test: /\.js$/i,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env'],
+ }
+ }
+ ]
+ },
+ {
+ test: /\.s[ac]ss$/i,
+ use: [
+ {
+ loader: MiniCssExtractPlugin.loader,
+ options: {
+ publicPath: '/',
+ }
+ },
+ 'css-loader',
+ 'sass-loader',
+ ]
+ },
+ {
+ test: /\.(woff2?|eot|ttf|otf)$/i,
+ use: 'file-loader?name=../fonts/[name].[ext]',
+ },
+ {
+ test: /\.(png|jpe?g|gif|svg)$/i,
+ use: [
+ 'file-loader?name=../img/[name].[ext]',
+ {
+ loader: 'img-loader',
+ options: {
+ enabled: !dev,
+ plugins: [
+ require('imagemin-gifsicle')({}),
+ require('imagemin-mozjpeg')({}),
+ require('imagemin-pngquant')({}),
+ require('imagemin-svgo')({}),
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ },
+ plugins: [
+ new MiniCssExtractPlugin({
+ filename: '../css/[name].css',
+ }),
+ ]
+};
+
+if (!dev) {
+ config.optimization = {
+ minimizer: [
+ new UglifyJSPlugin(),
+ ]
+ };
+}
+
+module.exports = config;
\ No newline at end of file