Feat: vite & upgraded dependencies (#100)

* On every route change, update local variables from query params

* ResultSection is keyed to query to force re-render

* Resolved lint warnings

* replace webpack w/ vite

* update all imports with alias @ and scss

* vite environment variables, also typed

* upgraded eslint, defined new rules & added ignore comments

* resolved linting issues

* moved index.html to project root

* updated dockerfile w/ build stage before runtime image definition

* sign drone config
This commit is contained in:
2026-02-23 20:53:19 +01:00
committed by GitHub
parent fb3b4c8f7d
commit 8e586811ec
74 changed files with 3007 additions and 10582 deletions

View File

@@ -25,7 +25,7 @@ steps:
path: /cache path: /cache
- name: Frontend install - name: Frontend install
image: node:18.2.0 image: node:24.13.1
commands: commands:
- node -v - node -v
- yarn --version - yarn --version
@@ -42,8 +42,14 @@ steps:
- name: cache - name: cache
path: /cache path: /cache
- name: Lint project using eslint
image: node:24.13.1
commands:
- yarn lint
failure: ignore
- name: Frontend build - name: Frontend build
image: node:18.2.0 image: node:24.13.1
commands: commands:
- yarn build - yarn build
environment: environment:
@@ -56,12 +62,6 @@ steps:
SEASONED_DOMAIN: SEASONED_DOMAIN:
from_secret: SEASONED_DOMAIN from_secret: SEASONED_DOMAIN
- name: Lint project using eslint
image: node:18.2.0
commands:
- yarn lint
failure: ignore
- name: Build and publish docker image - name: Build and publish docker image
image: plugins/docker image: plugins/docker
settings: settings:
@@ -105,3 +105,8 @@ trigger:
include: include:
- push - push
# - pull_request # - pull_request
---
kind: signature
hmac: 6f10b2871d2bd6b5cd26ddf72796325991ba211ba1eb62b657baf993e9d549c8
...

View File

@@ -1,4 +1,3 @@
SEASONED_API= SEASONED_API=http://localhost:31459
ELASTIC= ELASTIC_URL=http://elastic.local:9200/tmdb-movies-shows
ELASTIC_INDEX=shows,movies ELASTIC_API_KEY=
SEASONED_DOMAIN=

View File

@@ -1,31 +0,0 @@
{
"root": true,
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"@vue/eslint-config-airbnb",
"plugin:vue/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
"rules": {
"vue/no-v-model-argument": "off",
"no-underscore-dangle": "off",
"vue/multi-word-component-names": "off",
"no-shadow": "off",
"@typescript-eslint/no-shadow": ["error"],
},
"settings": {
"import/resolver": {
webpack: {
config: "./webpack.config.js"
}
}
}
}

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@ src/config.json
# Build directory # Build directory
dist/ dist/
lib/
# Node packages # Node packages
node_modules/ node_modules/

View File

@@ -1,7 +1,39 @@
FROM nginx:latest FROM node:24.13.1 AS build
COPY public /usr/share/nginx/html # Set the working directory for the build stage
WORKDIR /app
# Install dependencies
COPY package.json yarn.lock .
RUN yarn install --frozen-lockfile
# Copy source files that the build depends on
COPY index.html .
COPY public/ public/
COPY src/ src/
COPY tsconfig.json vite.config.ts .
ARG SEASONED_API=http://localhost:31459
ENV VITE_SEASONED_API=$SEASONED_API
ARG ELASTIC_URL=http://elastic.local:9200/tmdb-movies-shows
ENV VITE_ELASTIC_URL=$ELASTIC_URL
ARG ELASTIC_API_KEY=
ENV VITE_ELASTIC_API_KEY=$ELASTIC_API_KEY
RUN yarn build
FROM nginx:1.29.5
# Copy the static build from the previous stage
COPY index.html /usr/share/nginx/html
COPY public/ /usr/share/nginx/html
COPY --from=build /app/dist /usr/share/nginx/html
# Copy nginx config file
COPY nginx.conf /etc/nginx/conf.d/default.conf.template COPY nginx.conf /etc/nginx/conf.d/default.conf.template
# Manual entrypoint after nginx substring
COPY docker-entrypoint.sh /docker-entrypoint.d/05-docker-entrypoint.sh COPY docker-entrypoint.sh /docker-entrypoint.d/05-docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.d/05-docker-entrypoint.sh RUN chmod +x /docker-entrypoint.d/05-docker-entrypoint.sh

67
eslint.config.mjs Normal file
View File

@@ -0,0 +1,67 @@
import path from "node:path";
import { includeIgnoreFile } from "@eslint/compat";
import js from "@eslint/js";
import { defineConfig } from "eslint/config";
import { configs, plugins } from "eslint-config-airbnb-extended";
import { rules as prettierConfigRules } from "eslint-config-prettier";
import prettierPlugin from "eslint-plugin-prettier";
const CUSTOM_RULES = {
"vue/no-v-model-argument": "off",
"no-underscore-dangle": "off",
"vue/multi-word-component-names": "off",
"no-shadow": "off",
"@typescript-eslint/no-shadow": ["error"]
};
const gitignorePath = path.resolve(".", ".gitignore");
// ESLint recommended config
const jsConfig = defineConfig([
{
name: "js/config",
...js.configs.recommended
},
plugins.stylistic,
plugins.importX,
...configs.base.recommended // Airbnb base recommended config
]);
// Node & Airbnb recommended config
const nodeConfig = defineConfig([plugins.node, ...configs.node.recommended]);
// Typescript & Airbnb base TS config
const typescriptConfig = defineConfig([
plugins.typescriptEslint,
...configs.base.typescript
]);
// Prettier config
const prettierConfig = defineConfig([
{
name: "prettier/plugin/config",
plugins: {
prettier: prettierPlugin
}
},
{
name: "prettier/config",
rules: {
...prettierConfigRules,
"prettier/prettier": "error"
}
}
]);
export default defineConfig([
// Ignore files and folders listed in .gitignore
includeIgnoreFile(gitignorePath),
...jsConfig,
...nodeConfig,
...typescriptConfig,
...prettierConfig,
{
rules: CUSTOM_RULES
}
]);

View File

@@ -24,7 +24,9 @@
<meta name="theme-color" content="#081c24" /> <meta name="theme-color" content="#081c24" />
</head> </head>
<body> <body>
<div id="entry"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body> </body>
<script <script

View File

@@ -2,7 +2,7 @@ server {
listen 5000 default_server; listen 5000 default_server;
listen [::]:5000 default_server; listen [::]:5000 default_server;
server_name $SEASONED_DOMAIN; server_name _;
root /usr/share/nginx/html; root /usr/share/nginx/html;
gzip on; gzip on;

View File

@@ -5,57 +5,31 @@
"author": "Kevin Midboe", "author": "Kevin Midboe",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "NODE_ENV=development webpack server", "dev": "NODE_ENV=development vite",
"build": "yarn build:ts && yarn build:webpack", "build": "yarn vite build",
"build:ts": "tsc --project tsconfig.json", "clean": "rm -r dist 2> /dev/null; rm public/index.html 2> /dev/null; rm -r lib 2> /dev/null",
"build:webpack": "NODE_ENV=production webpack-cli build --progress",
"postbuild": "cp public/dist/index.html public/index.html",
"clean": "rm -r public/dist 2> /dev/null; rm public/index.html 2> /dev/null; rm -r lib 2> /dev/null",
"start": "echo 'Start using docker, consult README'", "start": "echo 'Start using docker, consult README'",
"lint": "eslint src --ext .ts,.vue", "lint": "eslint src --ext .ts,.vue",
"docs": "documentation build src/api.ts -f html -o docs/api && documentation build src/api.ts -f md -o docs/api.md" "docs": "documentation build src/api.ts -f html -o docs/api && documentation build src/api.ts -f md -o docs/api.md"
}, },
"dependencies": { "dependencies": {
"chart.js": "3.9.1", "chart.js": "3.9.1",
"connect-history-api-fallback": "2.0.0", "vue": "3.5.28",
"dotenv": "^16.0.1", "vue-router": "5.0.3",
"express": "4.18.1", "vuex": "4.1.0"
"vue": "3.2.37",
"vue-router": "4.1.3",
"vuex": "4.0.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.18.10", "@eslint/compat": "^2.0.2",
"@babel/plugin-transform-runtime": "7.18.10", "@eslint/js": "^10.0.1",
"@babel/preset-env": "7.18.10", "@types/node": "^25.3.0",
"@babel/runtime": "7.18.9", "@vitejs/plugin-vue": "^5.2.1",
"@types/express": "4.17.13", "eslint": "^10.0.1",
"@types/node": "18.6.1", "eslint-config-airbnb-extended": "^3.0.1",
"@typescript-eslint/eslint-plugin": "5.33.0", "eslint-config-prettier": "^10.1.8",
"@typescript-eslint/parser": "5.33.0", "eslint-plugin-prettier": "^5.5.5",
"@vue/cli": "5.0.8", "prettier": "^3.8.1",
"@vue/cli-service": "5.0.8",
"@vue/eslint-config-airbnb": "6.0.0",
"babel-loader": "8.2.5",
"css-loader": "6.7.1",
"documentation": "13.2.5",
"eslint": "8.21.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-vue": "9.3.0",
"eslint-plugin-vuejs-accessibility": "1.2.0",
"file-loader": "6.2.0",
"html-webpack-plugin": "5.5.0",
"prettier": "2.7.1",
"sass": "1.54.3", "sass": "1.54.3",
"sass-loader": "13.0.2", "typescript": "5.9.3",
"terser-webpack-plugin": "5.3.3", "vite": "^6.0.3"
"ts-loader": "9.3.1",
"typescript": "4.7.4",
"vue-loader": "17.0.0",
"webpack": "5.74.0",
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.9.3"
} }
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<div id="app"> <div id="content">
<!-- Header and hamburger navigation --> <!-- Header and hamburger navigation -->
<NavigationHeader class="header" /> <NavigationHeader class="header" />
@@ -28,13 +28,13 @@
</script> </script>
<style lang="scss"> <style lang="scss">
@import "src/scss/main"; @import "scss/main";
@import "src/scss/media-queries"; @import "scss/media-queries";
#app { #content {
display: grid; display: grid;
grid-template-rows: var(--header-size); grid-template-rows: var(--header-size);
grid-template-columns: var(--header-size) 1fr; grid-template-columns: var(--header-size) 100%;
@include mobile { @include mobile {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@@ -58,6 +58,7 @@
.content { .content {
display: grid; display: grid;
width: calc(100% - var(--header-size));
grid-column: 2 / 3; grid-column: 2 / 3;
grid-row: 2; grid-row: 2;
z-index: 5; z-index: 5;

View File

@@ -1,11 +1,13 @@
/* eslint-disable n/no-unsupported-features/node-builtins */
import { IList, IMediaCredits, IPersonCredits } from "./interfaces/IList"; import { IList, IMediaCredits, IPersonCredits } from "./interfaces/IList";
import type { import type {
IRequestStatusResponse, IRequestStatusResponse,
IRequestSubmitResponse IRequestSubmitResponse
} from "./interfaces/IRequestResponse"; } from "./interfaces/IRequestResponse";
const { ELASTIC, ELASTIC_INDEX, ELASTIC_APIKEY } = process.env; const API_HOSTNAME = import.meta.env.VITE_SEASONED_API;
const API_HOSTNAME = window.location.origin; const ELASTIC_URL = import.meta.env.VITE_ELASTIC_URL;
const ELASTIC_API_KEY = import.meta.env.VITE_ELASTIC_API_KEY;
// - - - TMDB - - - // - - - TMDB - - -
@@ -334,7 +336,11 @@ const register = (username, password) => {
}); });
}; };
const login = (username, password, throwError = false) => { const login = async (
username: string,
password: string,
throwError = false
) => {
const url = new URL("/api/v1/user/login", API_HOSTNAME); const url = new URL("/api/v1/user/login", API_HOSTNAME);
const options = { const options = {
method: "POST", method: "POST",
@@ -351,7 +357,7 @@ const login = (username, password, throwError = false) => {
}); });
}; };
const logout = (throwError = false) => { const logout = async (throwError = false) => {
const url = new URL("/api/v1/user/logout", API_HOSTNAME); const url = new URL("/api/v1/user/logout", API_HOSTNAME);
const options = { method: "POST" }; const options = { method: "POST" };
@@ -472,8 +478,9 @@ const getEmoji = async () => {
* @param {string} query * @param {string} query
* @returns {object} List of movies and shows matching query * @returns {object} List of movies and shows matching query
*/ */
const elasticSearchMoviesAndShows = (query: string, count = 22) => {
const url = new URL(`${ELASTIC_INDEX}/_search`, ELASTIC); const elasticSearchMoviesAndShows = (query, count = 22) => {
const url = new URL(`${ELASTIC_URL}/_search`);
const body = { const body = {
sort: [{ popularity: { order: "desc" } }, "_score"], sort: [{ popularity: { order: "desc" } }, "_score"],
@@ -521,8 +528,8 @@ const elasticSearchMoviesAndShows = (query: string, count = 22) => {
const options = { const options = {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `ApiKey ${ELASTIC_APIKEY}`, "Content-Type": "application/json",
"Content-Type": "application/json" Authorization: `ApiKey ${ELASTIC_API_KEY}`
}, },
body: JSON.stringify(body) body: JSON.stringify(body)
}; };

View File

@@ -11,7 +11,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import CastListItem from "src/components/CastListItem.vue"; import { defineProps } from "vue";
import CastListItem from "@/components/CastListItem.vue";
import type { import type {
IMovie, IMovie,
IShow, IShow,

View File

@@ -100,8 +100,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
header { header {
width: 100%; width: 100%;

View File

@@ -47,9 +47,9 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
@import "src/scss/main"; @import "scss/main";
header { header {
width: 100%; width: 100%;

View File

@@ -46,7 +46,7 @@
let _type: MediaTypes; let _type: MediaTypes;
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
params.forEach((value, key) => { params.forEach((_, key) => {
if ( if (
key !== MediaTypes.Movie && key !== MediaTypes.Movie &&
key !== MediaTypes.Show && key !== MediaTypes.Show &&
@@ -90,8 +90,8 @@
</script> </script>
<style lang="scss"> <style lang="scss">
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
.movie-popup { .movie-popup {
position: fixed; position: fixed;

View File

@@ -34,8 +34,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/media-queries"; @import "scss/media-queries";
@import "src/scss/main"; @import "scss/main";
.no-results { .no-results {
width: 100%; width: 100%;

View File

@@ -111,9 +111,9 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
@import "src/scss/main"; @import "scss/main";
.movie-item { .movie-item {
padding: 15px; padding: 15px;

View File

@@ -172,7 +172,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/media-queries"; @import "scss/media-queries";
.resultSection { .resultSection {
background-color: var(--background-color); background-color: var(--background-color);

View File

@@ -29,12 +29,11 @@ Searches Elasticsearch for results based on changes to `query`.
--> -->
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from "vue";
import { useStore } from "vuex";
import IconMovie from "@/icons/IconMovie.vue";
import IconShow from "@/icons/IconShow.vue";
import IconPerson from "@/icons/IconPerson.vue";
import type { Ref } from "vue"; import type { Ref } from "vue";
import { ref, watch, defineProps } from "vue";
import { useStore } from "vuex";
import IconMovie from "../../icons/IconMovie.vue";
import IconShow from "../../icons/IconShow.vue";
import { elasticSearchMoviesAndShows } from "../../api"; import { elasticSearchMoviesAndShows } from "../../api";
import { MediaTypes } from "../../interfaces/IList"; import { MediaTypes } from "../../interfaces/IList";
import type { import type {
@@ -161,9 +160,9 @@ Searches Elasticsearch for results based on changes to `query`.
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
@import "src/scss/main"; @import "scss/main";
$sizes: 22; $sizes: 22;
@for $i from 0 through $sizes { @for $i from 0 through $sizes {
@@ -241,7 +240,9 @@ Searches Elasticsearch for results based on changes to `query`.
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
transition: color 0.1s ease, fill 0.4s ease; transition:
color 0.1s ease,
fill 0.4s ease;
span { span {
overflow-x: hidden; overflow-x: hidden;

View File

@@ -61,8 +61,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
.spacer { .spacer {
@include mobile-only { @include mobile-only {

View File

@@ -37,7 +37,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/media-queries"; @import "scss/media-queries";
.navigation-link { .navigation-link {
display: grid; display: grid;
@@ -47,8 +47,13 @@
padding: 1rem 0.15rem; padding: 1rem 0.15rem;
text-align: center; text-align: center;
background-color: var(--background-color-secondary); background-color: var(--background-color-secondary);
transition: transform 0.3s ease, color 0.3s ease, stoke 0.3s ease, transition:
fill 0.3s ease, background-color 0.5s ease; transform 0.3s ease,
color 0.3s ease,
stoke 0.3s ease,
fill 0.3s ease,
background-color 0.5s ease;
transition: all 0.3s ease;
&:hover { &:hover {
transform: scale(1.05); transform: scale(1.05);

View File

@@ -77,7 +77,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/media-queries"; @import "scss/media-queries";
.navigation-icons { .navigation-icons {
display: grid; display: grid;

View File

@@ -55,13 +55,13 @@ the `query`.
--> -->
<script setup lang="ts"> <script setup lang="ts">
import type { Ref } from "vue";
import { ref, computed } from "vue"; import { ref, computed } from "vue";
import { useStore } from "vuex"; import { useStore } from "vuex";
import { useRouter, useRoute } from "vue-router"; import { useRouter, useRoute } from "vue-router";
import AutocompleteDropdown from "@/components/header/AutocompleteDropdown.vue"; import AutocompleteDropdown from "./AutocompleteDropdown.vue";
import IconSearch from "@/icons/IconSearch.vue"; import IconSearch from "../../icons/IconSearch.vue";
import IconClose from "@/icons/IconClose.vue"; import IconClose from "../../icons/IconClose.vue";
import type { Ref } from "vue";
import type { MediaTypes } from "../../interfaces/IList"; import type { MediaTypes } from "../../interfaces/IList";
import { IAutocompleteResult } from "../../interfaces/IAutocompleteSearch"; import { IAutocompleteResult } from "../../interfaces/IAutocompleteSearch";
@@ -98,13 +98,9 @@ import { IAutocompleteResult } from "../../interfaces/IAutocompleteSearch";
query.value = decodeURIComponent(params.get("query")); query.value = decodeURIComponent(params.get("query"));
} }
const { ELASTIC, ELASTIC_APIKEY } = process.env; const ELASTIC_URL = import.meta.env.VITE_ELASTIC_URL;
if ( const ELASTIC_API_KEY = import.meta.env.VITE_ELASTIC_API_KEY;
ELASTIC === undefined || if (!ELASTIC_URL || !ELASTIC_API_KEY) {
ELASTIC === "" ||
ELASTIC_APIKEY === undefined ||
ELASTIC_APIKEY === ""
) {
disabled.value = true; disabled.value = true;
} }
@@ -184,9 +180,9 @@ import { IAutocompleteResult } from "../../interfaces/IAutocompleteSearch";
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
@import "src/scss/main"; @import "scss/main";
.close-icon { .close-icon {
position: absolute; position: absolute;

View File

@@ -24,7 +24,7 @@
</script> </script>
<style lang="scss"> <style lang="scss">
@import "src/scss/media-queries"; @import "scss/media-queries";
li.sidebar-list-element { li.sidebar-list-element {
display: flex; display: flex;

View File

@@ -27,7 +27,6 @@
const overflow: Ref<boolean> = ref(false); const overflow: Ref<boolean> = ref(false);
const descriptionElement: Ref<HTMLElement> = ref(null); const descriptionElement: Ref<HTMLElement> = ref(null);
// eslint-disable-next-line no-undef
function removeElements(elems: NodeListOf<Element>) { function removeElements(elems: NodeListOf<Element>) {
elems.forEach(el => el.remove()); elems.forEach(el => el.remove());
} }
@@ -67,7 +66,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/media-queries"; @import "scss/media-queries";
.movie-description { .movie-description {
font-weight: 300; font-weight: 300;

View File

@@ -17,7 +17,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/media-queries"; @import "scss/media-queries";
.movie-detail { .movie-detail {
margin-bottom: 20px; margin-bottom: 20px;

View File

@@ -169,21 +169,20 @@
import { useStore } from "vuex"; import { useStore } from "vuex";
// import img from "@/directives/v-image"; // import img from "@/directives/v-image";
import IconProfile from "@/icons/IconProfile.vue"; import IconProfile from "../../icons/IconProfile.vue";
import IconThumbsUp from "@/icons/IconThumbsUp.vue"; import IconThumbsUp from "../../icons/IconThumbsUp.vue";
import IconThumbsDown from "@/icons/IconThumbsDown.vue"; import IconThumbsDown from "../../icons/IconThumbsDown.vue";
import IconInfo from "@/icons/IconInfo.vue"; import IconInfo from "../../icons/IconInfo.vue";
import IconRequest from "@/icons/IconRequest.vue"; import IconRequest from "../../icons/IconRequest.vue";
import IconRequested from "@/icons/IconRequested.vue"; import IconRequested from "../../icons/IconRequested.vue";
import IconBinoculars from "@/icons/IconBinoculars.vue"; import IconBinoculars from "../../icons/IconBinoculars.vue";
import IconPlay from "@/icons/IconPlay.vue"; import IconPlay from "../../icons/IconPlay.vue";
import TorrentList from "@/components/torrent/TruncatedTorrentResults.vue"; import TorrentList from "../torrent/TruncatedTorrentResults.vue";
import CastList from "@/components/CastList.vue"; import CastList from "../CastList.vue";
import Detail from "@/components/popup/Detail.vue"; import Detail from "./Detail.vue";
import ActionButton from "@/components/popup/ActionButton.vue"; import ActionButton from "./ActionButton.vue";
import Description from "@/components/popup/Description.vue"; import Description from "./Description.vue";
import LoadingPlaceholder from "@/components/ui/LoadingPlaceholder.vue"; import LoadingPlaceholder from "../ui/LoadingPlaceholder.vue";
import type { Ref } from "vue";
import type { import type {
IMovie, IMovie,
IShow, IShow,
@@ -342,10 +341,10 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/loading-placeholder"; @import "scss/loading-placeholder";
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
@import "src/scss/main"; @import "scss/main";
header { header {
$duration: 0.2s; $duration: 0.2s;

View File

@@ -165,10 +165,10 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/loading-placeholder"; @import "scss/loading-placeholder";
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
@import "src/scss/main"; @import "scss/main";
section.person { section.person {
overflow: hidden; overflow: hidden;

View File

@@ -85,7 +85,7 @@
try { try {
validate(); validate();
} catch (error) { } catch (error) {
console.log("not valid!"); // eslint-disable-line no-console console.log("not valid! error:", error); // eslint-disable-line no-console
} }
// const body: ResetPasswordPayload = { // const body: ResetPasswordPayload = {

View File

@@ -102,9 +102,9 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
@import "src/scss/elements"; @import "scss/elements";
h2 { h2 {
font-size: 20px; font-size: 20px;
@@ -115,13 +115,8 @@
margin: 1rem 0; margin: 1rem 0;
} }
.container {
background-color: $background-color;
}
.no-results { .no-results {
display: flex; display: flex;
padding-bottom: 2rem;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;

View File

@@ -155,9 +155,9 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
@import "src/scss/elements"; @import "scss/elements";
table { table {
border-spacing: 0; border-spacing: 0;

View File

@@ -25,8 +25,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { ref, defineProps, computed } from "vue";
import TorrentSearchResults from "@/components/torrent/TorrentSearchResults.vue"; import TorrentSearchResults from "@/components/torrent/TorrentSearchResults.vue";
import SeasonedButton from "@/components/ui/SeasonedButton.vue"; import SeasonedButton from "@/components/ui/SeasonedButton.vue";
import IconArrowDown from "@/icons/IconArrowDown.vue"; import IconArrowDown from "@/icons/IconArrowDown.vue";

View File

@@ -23,7 +23,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/media-queries"; @import "scss/media-queries";
.nav__hamburger { .nav__hamburger {
display: block; display: block;

View File

@@ -22,7 +22,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
.loader { .loader {
display: flex; display: flex;

View File

@@ -20,5 +20,5 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/loading-placeholder"; @import "scss/loading-placeholder";
</style> </style>

View File

@@ -23,8 +23,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
button { button {
display: inline-block; display: inline-block;
@@ -42,7 +42,10 @@
background: $background-color-secondary; background: $background-color-secondary;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
transition: background 0.5s ease, color 0.5s ease, border-color 0.5s ease; transition:
background 0.5s ease,
color 0.5s ease,
border-color 0.5s ease;
@include desktop { @include desktop {
font-size: 0.8rem; font-size: 0.8rem;

View File

@@ -74,8 +74,8 @@
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
.group { .group {
display: flex; display: flex;

View File

@@ -64,8 +64,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
.fade-active { .fade-active {
transition: opacity 0.4s; transition: opacity 0.4s;

View File

@@ -33,7 +33,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
$background: $background-ui; $background: $background-ui;
$background-selected: $background-color-secondary; $background-selected: $background-color-secondary;

View File

@@ -1,3 +1,4 @@
/*
let setValue = function(el, binding) { let setValue = function(el, binding) {
let value = binding.value; let value = binding.value;
let dateArray = value.split('-'); let dateArray = value.split('-');
@@ -13,3 +14,4 @@ module.exports = {
setValue(el, binding); setValue(el, binding);
} }
} }
*/

View File

@@ -1,5 +1,6 @@
let setValue = function(el, binding) { /*
let img = new Image(); const setValue = function(el, binding) {
const img = new Image();
img.src = binding.value; img.src = binding.value;
img.onload = function() { img.onload = function() {
@@ -10,10 +11,11 @@ let setValue = function(el, binding) {
module.exports = { module.exports = {
isLiteral: true, isLiteral: true,
bind(el, binding){ bind(el, binding) {
setValue(el, binding); setValue(el, binding);
}, },
update(el, binding){ update(el, binding) {
setValue(el, binding); setValue(el, binding);
} }
} };
*/

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-use-before-define */
import { MediaTypes } from "./IList"; import { MediaTypes } from "./IList";
export interface IAutocompleteResult { export interface IAutocompleteResult {

View File

@@ -1,5 +1,3 @@
/* eslint-disable no-use-before-define */
export enum GraphTypes { export enum GraphTypes {
Plays = "plays", Plays = "plays",
Duration = "duration" Duration = "duration"
@@ -12,12 +10,12 @@ export enum GraphValueTypes {
export interface IGraphDataset { export interface IGraphDataset {
name: string; name: string;
data: Array<number>; data: number[];
} }
export interface IGraphData { export interface IGraphData {
labels: Array<string>; labels: string[];
series: Array<IGraphDataset>; series: IGraphDataset[];
} }
export interface IGraphResponse { export interface IGraphResponse {

View File

@@ -67,7 +67,7 @@ export interface IMovie {
backdrop: string; backdrop: string;
release_date: string | Date; release_date: string | Date;
rating: number; rating: number;
genres: Array<MovieGenres>; genres: MovieGenres[];
production_status: MovieProductionStatus; production_status: MovieProductionStatus;
tagline: string; tagline: string;
runtime: number; runtime: number;
@@ -88,9 +88,9 @@ export interface IShow {
seasons?: number; seasons?: number;
episodes?: number; episodes?: number;
popularity?: number; popularity?: number;
genres?: Array<ShowGenres>; genres?: ShowGenres[];
production_status?: string; production_status?: string;
runtime?: Array<number>; runtime?: number[];
exists_in_plex?: boolean; exists_in_plex?: boolean;
type: MediaTypes.Show; type: MediaTypes.Show;
} }
@@ -135,19 +135,19 @@ export interface ICrew {
} }
export interface IMediaCredits { export interface IMediaCredits {
cast: Array<ICast>; cast: ICast[];
crew: Array<ICrew>; crew: ICrew[];
id: number; id: number;
} }
export interface IPersonCredits { export interface IPersonCredits {
cast: Array<IMovie | IShow>; cast: (IMovie | IShow)[];
crew: Array<ICrew>; crew: ICrew[];
id: number; id: number;
type?: string; type?: string;
} }
export type ListResults = Array<IMovie | IShow | IPerson | IRequest>; export type ListResults = (IMovie | IShow | IPerson | IRequest)[];
export interface IList { export interface IList {
results: ListResults; results: ListResults;

View File

@@ -1,7 +1,7 @@
export default interface INavigationIcon { export default interface INavigationIcon {
title: string; title: string;
route: string; route: string;
icon: any; // eslint-disable-line @typescript-eslint/no-explicit-any icon: any;
requiresAuth?: boolean; requiresAuth?: boolean;
useStroke?: boolean; useStroke?: boolean;
} }

View File

@@ -1,6 +1,6 @@
import type ITorrent from "./ITorrent"; import type ITorrent from "./ITorrent";
export default interface IStateTorrent { export default interface IStateTorrent {
results: Array<ITorrent>; results: ITorrent[];
resultCount: number | null; resultCount: number | null;
} }

View File

@@ -7,5 +7,5 @@ export default interface ITorrent {
seed: string; seed: string;
leech: string; leech: string;
url: string | null; url: string | null;
release_type: Array<string>; release_type: string[];
} }

View File

@@ -3,8 +3,7 @@ import router from "./routes";
import store from "./store"; import store from "./store";
import Toast from "./plugins/Toast"; import Toast from "./plugins/Toast";
// eslint-disable-next-line @typescript-eslint/no-var-requires import App from "./App.vue";
const App = require("./App.vue").default;
store.dispatch("darkmodeModule/findAndSetDarkmodeSupported"); store.dispatch("darkmodeModule/findAndSetDarkmodeSupported");
store.dispatch("user/initUserFromCookie"); store.dispatch("user/initUserFromCookie");
@@ -14,4 +13,5 @@ const app = createApp(App);
app.use(router); app.use(router);
app.use(store); app.use(store);
app.use(Toast); app.use(Toast);
app.mount("#entry");
app.mount("#app");

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import IStateDarkmode from "../interfaces/IStateDarkmode"; import IStateDarkmode from "../interfaces/IStateDarkmode";
const state: IStateDarkmode = { const state: IStateDarkmode = {
@@ -10,9 +11,7 @@ export default {
namespaced: true, namespaced: true,
state, state,
getters: { getters: {
darkmodeSupported: (state: IStateDarkmode) => { darkmodeSupported: (state: IStateDarkmode) => state.darkmodeSupported
return state.darkmodeSupported;
}
}, },
mutations: { mutations: {
SET_DARKMODE_SUPPORT: ( SET_DARKMODE_SUPPORT: (

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import type IStateDocumentTitle from "../interfaces/IStateDocumentTitle"; import type IStateDocumentTitle from "../interfaces/IStateDocumentTitle";
const capitalize = (string: string) => { const capitalize = (string: string) => {
@@ -26,7 +27,7 @@ const state: IStateDocumentTitle = {
title: undefined title: undefined
}; };
/* eslint-disable @typescript-eslint/no-shadow, no-return-assign */ /* eslint-disable @typescript-eslint/no-shadow */
export default { export default {
namespaced: true, namespaced: true,
state, state,

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import type IStateHamburger from "../interfaces/IStateHamburger"; import type IStateHamburger from "../interfaces/IStateHamburger";
const state: IStateHamburger = { const state: IStateHamburger = {

View File

@@ -1,15 +1,16 @@
/* eslint-disable no-param-reassign */
import { MediaTypes } from "../interfaces/IList"; import { MediaTypes } from "../interfaces/IList";
import type { IStatePopup, IPopupQuery } from "../interfaces/IStatePopup"; import type { IStatePopup, IPopupQuery } from "../interfaces/IStatePopup";
/* eslint-disable-next-line import/no-cycle */ /* eslint-disable-next-line import-x/no-cycle */
import router from "../routes"; import router from "../routes";
const removeIncludedQueryParams = (params, key) => { const removeIncludedQueryParams = (params: URLSearchParams, key: string) => {
if (params.has(key)) params.delete(key); if (params.has(key)) params.delete(key);
return params; return params;
}; };
function paramsToObject(entries) { function paramsToObject(entries: Iterator<[string, string]>) {
const result = {}; const result = {};
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
for (const [key, value] of entries) { for (const [key, value] of entries) {
@@ -65,7 +66,7 @@ export default {
actions: { actions: {
open: ({ commit }, { id, type }: { id: number; type: MediaTypes }) => { open: ({ commit }, { id, type }: { id: number; type: MediaTypes }) => {
if (!Number.isNaN(id)) { if (!Number.isNaN(id)) {
id = Number(id); /* eslint-disable-line no-param-reassign */ id = Number(id);
} }
commit("SET_OPEN", { id, type }); commit("SET_OPEN", { id, type });

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import type ITorrent from "../interfaces/ITorrent"; import type ITorrent from "../interfaces/ITorrent";
import type IStateTorrent from "../interfaces/IStateTorrent"; import type IStateTorrent from "../interfaces/IStateTorrent";
@@ -11,16 +12,12 @@ export default {
namespaced: true, namespaced: true,
state, state,
getters: { getters: {
results: (state: IStateTorrent) => { results: (state: IStateTorrent) => state.results,
return state.results; resultCount: (state: IStateTorrent) => state.resultCount
},
resultCount: (state: IStateTorrent) => {
return state.resultCount;
}
}, },
mutations: { mutations: {
SET_RESULTS: (state: IStateTorrent, results: Array<ITorrent>) => { SET_RESULTS: (state: IStateTorrent, results: ITorrent[]) => {
state.results = results; state.results = results;
}, },
SET_RESULT_COUNT: (state: IStateTorrent, count: number) => { SET_RESULT_COUNT: (state: IStateTorrent, count: number) => {
@@ -32,7 +29,7 @@ export default {
} }
}, },
actions: { actions: {
setResults({ commit }, results: Array<ITorrent>) { setResults({ commit }, results: ITorrent[]) {
commit("SET_RESULTS", results); commit("SET_RESULTS", results);
}, },
setResultCount({ commit }, count: number) { setResultCount({ commit }, count: number) {

View File

@@ -28,8 +28,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables.scss";
@import "src/scss/media-queries"; @import "scss/media-queries";
.button { .button {
font-size: 1.2rem; font-size: 1.2rem;

View File

@@ -141,7 +141,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
.wrapper { .wrapper {
padding: 2rem; padding: 2rem;

View File

@@ -118,8 +118,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
.button--group { .button--group {
display: flex; display: flex;

View File

@@ -131,7 +131,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
section { section {
padding: 1.3rem; padding: 1.3rem;

View File

@@ -89,7 +89,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/media-queries"; @import "scss/media-queries";
.filter { .filter {
margin-top: 0.5rem; margin-top: 0.5rem;

View File

@@ -48,8 +48,8 @@
</script> </script>
<style lang="scss"> <style lang="scss">
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
.settings { .settings {
padding: 3rem; padding: 3rem;

View File

@@ -112,7 +112,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/scss/variables"; @import "scss/variables";
section { section {
padding: 1.3rem; padding: 1.3rem;

View File

@@ -94,7 +94,9 @@
background-color: white; background-color: white;
border-radius: 3px; border-radius: 3px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.17), 0 2px 4px 0 rgba(0, 0, 0, 0.08); box-shadow:
0 4px 8px 0 rgba(0, 0, 0, 0.17),
0 2px 4px 0 rgba(0, 0, 0, 0.08);
padding: 0.5rem; padding: 0.5rem;
margin: 1rem 2rem 1rem 0.71rem; margin: 1rem 2rem 1rem 0.71rem;
// max-width: calc(100% - 3rem); // max-width: calc(100% - 3rem);

View File

@@ -1,7 +1,7 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import type { RouteRecordRaw, RouteLocationNormalized } from "vue-router"; import type { RouteRecordRaw, RouteLocationNormalized } from "vue-router";
/* eslint-disable-next-line import/no-cycle */ /* eslint-disable-next-line import-x/no-cycle */
import store from "./store"; import store from "./store";
declare global { declare global {
@@ -10,7 +10,7 @@ declare global {
} }
} }
const routes: Array<RouteRecordRaw> = [ const routes: RouteRecordRaw[] = [
{ {
name: "home", name: "home",
path: "/", path: "/",
@@ -99,7 +99,6 @@ const loggedIn = () => store.getters["user/loggedIn"];
const hasPlexAccount = () => store.getters["user/plexUserId"] !== null; const hasPlexAccount = () => store.getters["user/plexUserId"] !== null;
const hamburgerIsOpen = () => store.getters["hamburger/isOpen"]; const hamburgerIsOpen = () => store.getters["hamburger/isOpen"];
/* eslint-disable @typescript-eslint/no-explicit-any */
router.beforeEach( router.beforeEach(
(to: RouteLocationNormalized, from: RouteLocationNormalized, next: any) => { (to: RouteLocationNormalized, from: RouteLocationNormalized, next: any) => {
store.dispatch("documentTitle/updateTitle", to.name); store.dispatch("documentTitle/updateTitle", to.name);

View File

@@ -1,5 +1,5 @@
@import "src/scss/variables"; @import "scss/variables";
@import "src/scss/media-queries"; @import "scss/media-queries";
.filter { .filter {
margin: 1rem; margin: 1rem;

View File

@@ -1,4 +1,4 @@
@import "src/scss/variables"; @import "scss/variables";
// Loading placeholder styling // Loading placeholder styling
@mixin nth-children($points...) { @mixin nth-children($points...) {

View File

@@ -1,4 +1,4 @@
@import "src/scss/variables"; @import "scss/variables";
.noselect { .noselect {
-webkit-touch-callout: none; /* iOS Safari */ -webkit-touch-callout: none; /* iOS Safari */

View File

@@ -1,6 +1,6 @@
// Colors // Colors
// @import "./media-queries"; // @import "./media-queries";
@import "src/scss/media-queries"; @import "scss/media-queries";
:root { :root {
color-scheme: light; color-scheme: light;

View File

@@ -6,7 +6,7 @@ import torrentModule from "./modules/torrentModule";
import user from "./modules/user"; import user from "./modules/user";
import hamburger from "./modules/hamburger"; import hamburger from "./modules/hamburger";
/* eslint-disable-next-line import/no-cycle */ /* eslint-disable-next-line import-x/no-cycle */
import popup from "./modules/popup"; import popup from "./modules/popup";
const store = createStore({ const store = createStore({

View File

@@ -14,9 +14,7 @@ export const parseJwt = (token: string) => {
const jsonPayload = decodeURIComponent( const jsonPayload = decodeURIComponent(
atob(base64) atob(base64)
.split("") .split("")
.map(c => { .map(c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
return `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`;
})
.join("") .join("")
); );
@@ -62,10 +60,10 @@ export function focusOnNextElement(elementEvent: KeyboardEvent): void {
} }
} }
export function humanMinutes(minutes) { export function humanMinutes(minutes: number[] | number) {
if (minutes instanceof Array) { if (minutes instanceof Array) {
/* eslint-disable-next-line prefer-destructuring, no-param-reassign */ /* eslint-disable-next-line no-param-reassign */
minutes = minutes[0]; [minutes] = minutes;
} }
const hours = Math.floor(minutes / 60); const hours = Math.floor(minutes / 60);
@@ -93,7 +91,7 @@ export function setUrlQueryParameter(parameter: string, value: string): void {
const url = `${window.location.protocol}//${window.location.hostname}${ const url = `${window.location.protocol}//${window.location.hostname}${
window.location.port ? `:${window.location.port}` : "" window.location.port ? `:${window.location.port}` : ""
}${window.location.pathname}${params.toString().length ? `?${params}` : ""}`; }${ndow.location.pathname}${params.toString().length ? `?${params}` : ""}`;
window.history.pushState({}, "search", url); window.history.pushState({}, "search", url);
} }

9
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
interface ImportMetaEnv {
readonly VITE_SEASONED_API: string;
readonly VITE_ELASTIC_URL: string;
readonly VITE_ELASTIC_API_KEY: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@@ -12,7 +12,8 @@
"outDir": "lib", "outDir": "lib",
"baseUrl": "/", "baseUrl": "/",
"paths": { "paths": {
"@": ["src"] "@/*": ["./src/*"],
"scss/*": ["./src/scss/*"]
} }
}, },
"include": [ "include": [

14
vite.config.ts Normal file
View File

@@ -0,0 +1,14 @@
import path from "path";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vite.dev/config/
export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
scss: path.resolve(__dirname, "./src/scss")
}
},
plugins: [vue()]
});

View File

@@ -1,157 +0,0 @@
const path = require("path");
const webpack = require("webpack");
const sass = require("sass");
const HTMLWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const TerserPlugin = require("terser-webpack-plugin");
const dotenv = require("dotenv").config({ path: "./.env" });
const dotenvExample = require("dotenv").config({ path: "./.env.example" });
const sourcePath = path.resolve(__dirname, "src");
const indexFile = path.join(sourcePath, "index.html");
const javascriptEntry = path.join(sourcePath, "main.ts");
const publicPath = path.resolve(__dirname, "public");
const isProd = process.env.NODE_ENV === "production";
const variables = dotenv.parsed || dotenvExample.parsed;
// Merge inn all process.env values that match dotenv keys
Object.keys(process.env).forEach(key => {
if (key in variables) {
variables[key] = process.env[key];
}
});
module.exports = {
mode: process.env.NODE_ENV,
context: publicPath,
entry: javascriptEntry,
output: {
path: `${publicPath}/dist/`,
publicPath: "/dist/",
filename: "[name].[contenthash].js",
clean: true
},
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
},
exclude: /node_modules/
},
{
test: /\.vue$/,
use: ["vue-loader"]
},
{
test: /\.ts$/,
loader: "ts-loader",
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/]
}
},
{
test: /\.scss$/,
use: [
"vue-style-loader",
// isProd ? MiniCssExtractPlugin.loader : "vue-style-loader",
"css-loader",
"sass-loader"
]
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: "file-loader",
options: {
name: "[name].[ext]?[hash]"
}
}
]
},
plugins: [
new VueLoaderPlugin(),
new HTMLWebpackPlugin({
template: indexFile,
filename: "index.html",
minify: isProd
}),
new webpack.DefinePlugin({
"process.env": JSON.stringify(variables)
})
],
resolve: {
extensions: [".js", ".ts", ".vue", ".json", ".scss"],
alias: {
vue: "@vue/runtime-dom",
"@": path.resolve(__dirname, "src"),
src: path.resolve(__dirname, "src"),
assets: `${publicPath}/assets`,
components: path.resolve(__dirname, "src/components")
}
},
devtool: "source-map",
performance: {
hints: false
},
optimization: {
splitChunks: {
chunks: "all",
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1];
// npm package names are URL-safe, but some servers don't like @ symbols
return `npm.${packageName.replace("@", "")}`;
}
}
}
},
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
sourceMap: true
// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
}
})
]
},
devServer: {
static: publicPath,
historyApiFallback: {
index: "/dist/index.html"
},
compress: true,
hot: true,
port: 8080
}
};
if (isProd) {
module.exports.mode = "production";
module.exports.devtool = false;
module.exports.performance.hints = "warning";
}
// enable proxy for anything that hits /Api
// View README or update src/config.ts:SEASONED_API_URL
const { SEASONED_API } = process.env;
if (SEASONED_API) {
module.exports.devServer.proxy = {
"/api": {
target: SEASONED_API,
changeOrigin: true
}
};
}

12741
yarn.lock

File diff suppressed because it is too large Load Diff