Migrate web builds from Vue CLI to Vite
This commit changes the web application's build, transpilation and minification process from Vue CLI to Vite. This shift paves the way for a full migration to Vite as the primary build tool (#230). Configuration changes: - `.vscode/extensions.json`: Update recommended plugins, replacing unmaintained ones with official recommendations. - Legacy browser support: - Use `@vitejs/plugin-legacy` to transpile for older browsers. - Remove `core-js` dependency and `babel.config.cjs` configuration as they're now handled by the legacy plugin. - Delete `@babel/preset-typescript` and `@babel/preset-typescript` dependencies as legacy plugin handles babel dependencies by default. - Add `terser` dependency that's used by the legacy plugin for minification, as per Vite's official documentation. - `tsconfig.json`: - Remove obsolete `webpack-env` types. - Add `"resolveJsonModule": true` to be able to read JSON files in right way. - Use correct casing as configuration values. - Simplify `lib` to align with Vite and Vue starter configuration. - Add `"skipLibCheck": true` as `npm run build` now runs `tsc` which fails on inconsistent typings inside `node_modules` due to npm's weak dependency resoultion. - PostCSS: - Add `autoprefixer` as dependency, no longer installed by Vue CLI. - Epxlicitly added `postcss` as dependency to anticipate potential peer dependency changes. - Remove related `@vue/cli` dependencies. - Remove `sass-loader` as Vite has native CSS preprocessing support. - Run integration tests with `jsdom` environment so `window` object can be used. Client-side changes: - Abstract build tool specific environment variable population. Environment variables were previously populated by Vue CLI and now by Vite but not having an abstraction caused issues. This abstraction solves build errors and allows easier future migrations and testing. - Change Vue CLI-specific `~@` aliases to `@` to be able to compile with Vite. - Update types in LiquorTree to satisfy `tsc`. - Remove Vue CLI-specific workaround from `src/presentation/main.ts`. Restructuring: - Move `public/` to `presentation/` to align with the layered structure, which was not possible with Vue CLI. - Move `index.html` to web root instead of having it inside `public/` to align with official recommended structure. - Move logic shared by both integration and unit tests to `tests/shared`. - Move logo creation script to `scripts/` and its npm command to include `build` to align with rest of the structure.
This commit is contained in:
@@ -7,15 +7,17 @@ import MacOsData from '@/application/collections/macos.yaml';
|
||||
import LinuxData from '@/application/collections/linux.yaml';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { Application } from '@/domain/Application';
|
||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
||||
import { ViteAppMetadata } from '@/infrastructure/Metadata/Vite/ViteAppMetadata';
|
||||
import { parseCategoryCollection } from './CategoryCollectionParser';
|
||||
|
||||
export function parseApplication(
|
||||
parser: CategoryCollectionParserType = parseCategoryCollection,
|
||||
processEnv: NodeJS.ProcessEnv = process.env,
|
||||
parser = parseCategoryCollection,
|
||||
metadata: IAppMetadata = new ViteAppMetadata(),
|
||||
collectionsData = PreParsedCollections,
|
||||
): IApplication {
|
||||
validateCollectionsData(collectionsData);
|
||||
const information = parseProjectInformation(processEnv);
|
||||
const information = parseProjectInformation(metadata);
|
||||
const collections = collectionsData.map((collection) => parser(collection, information));
|
||||
const app = new Application(information, collections);
|
||||
return app;
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
||||
import { Version } from '@/domain/Version';
|
||||
|
||||
export function parseProjectInformation(
|
||||
environment: NodeJS.ProcessEnv | VueAppEnvironment,
|
||||
metadata: IAppMetadata,
|
||||
): IProjectInformation {
|
||||
const version = new Version(environment[VueAppEnvironmentKeys.VUE_APP_VERSION]);
|
||||
const version = new Version(
|
||||
metadata.version,
|
||||
);
|
||||
return new ProjectInformation(
|
||||
environment[VueAppEnvironmentKeys.VUE_APP_NAME],
|
||||
metadata.name,
|
||||
version,
|
||||
environment[VueAppEnvironmentKeys.VUE_APP_SLOGAN],
|
||||
environment[VueAppEnvironmentKeys.VUE_APP_REPOSITORY_URL],
|
||||
environment[VueAppEnvironmentKeys.VUE_APP_HOMEPAGE_URL],
|
||||
metadata.slogan,
|
||||
metadata.repositoryUrl,
|
||||
metadata.homepageUrl,
|
||||
);
|
||||
}
|
||||
|
||||
export const VueAppEnvironmentKeys = {
|
||||
VUE_APP_VERSION: 'VUE_APP_VERSION',
|
||||
VUE_APP_NAME: 'VUE_APP_NAME',
|
||||
VUE_APP_SLOGAN: 'VUE_APP_SLOGAN',
|
||||
VUE_APP_REPOSITORY_URL: 'VUE_APP_REPOSITORY_URL',
|
||||
VUE_APP_HOMEPAGE_URL: 'VUE_APP_HOMEPAGE_URL',
|
||||
} as const;
|
||||
|
||||
export type VueAppEnvironment = {
|
||||
[K in keyof typeof VueAppEnvironmentKeys]: string;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
// eslint-disable-next-line camelcase
|
||||
import child_process from 'child_process';
|
||||
import { Environment } from '@/application/Environment/Environment';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
13
src/infrastructure/Metadata/IAppMetadata.ts
Normal file
13
src/infrastructure/Metadata/IAppMetadata.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Represents essential metadata about the application.
|
||||
*
|
||||
* Designed to decouple the process of retrieving metadata
|
||||
* (e.g., from the build environment) from the rest of the application.
|
||||
*/
|
||||
export interface IAppMetadata {
|
||||
readonly version: string;
|
||||
readonly name: string;
|
||||
readonly slogan: string;
|
||||
readonly repositoryUrl: string;
|
||||
readonly homepageUrl: string;
|
||||
}
|
||||
29
src/infrastructure/Metadata/Vite/ViteAppMetadata.ts
Normal file
29
src/infrastructure/Metadata/Vite/ViteAppMetadata.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { IAppMetadata } from '../IAppMetadata';
|
||||
|
||||
/**
|
||||
* Provides the application's metadata using Vite's environment variables.
|
||||
*/
|
||||
export class ViteAppMetadata implements IAppMetadata {
|
||||
// Ensure the use of import.meta.env prefix for the following properties.
|
||||
// Vue will replace these statically during production builds.
|
||||
|
||||
public get version(): string {
|
||||
return import.meta.env.VITE_APP_VERSION;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return import.meta.env.VITE_APP_NAME;
|
||||
}
|
||||
|
||||
public get slogan(): string {
|
||||
return import.meta.env.VITE_APP_SLOGAN;
|
||||
}
|
||||
|
||||
public get repositoryUrl(): string {
|
||||
return import.meta.env.VITE_APP_REPOSITORY_URL;
|
||||
}
|
||||
|
||||
public get homepageUrl(): string {
|
||||
return import.meta.env.VITE_APP_HOMEPAGE_URL;
|
||||
}
|
||||
}
|
||||
8
src/infrastructure/Metadata/Vite/ViteEnvironmentKeys.ts
Normal file
8
src/infrastructure/Metadata/Vite/ViteEnvironmentKeys.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// Only variables prefixed with VITE_ are exposed to Vite-processed code
|
||||
export const VITE_ENVIRONMENT_KEYS = {
|
||||
VERSION: 'VITE_APP_VERSION',
|
||||
NAME: 'VITE_APP_NAME',
|
||||
SLOGAN: 'VITE_APP_SLOGAN',
|
||||
REPOSITORY_URL: 'VITE_APP_REPOSITORY_URL',
|
||||
HOMEPAGE_URL: 'VITE_APP_HOMEPAGE_URL',
|
||||
} as const;
|
||||
11
src/infrastructure/Metadata/Vite/vite-env.d.ts
vendored
Normal file
11
src/infrastructure/Metadata/Vite/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
import { VITE_ENVIRONMENT_KEYS } from './ViteEnvironmentKeys';
|
||||
|
||||
export type ViteEnvironmentVariables = {
|
||||
readonly [K in keyof typeof VITE_ENVIRONMENT_KEYS]: string;
|
||||
};
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ViteEnvironmentVariables
|
||||
}
|
||||
3
src/presentation/README.md
Normal file
3
src/presentation/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# presentation
|
||||
|
||||
See [`presentation.md`](./../../docs/presentation.md)
|
||||
@@ -1,17 +1,19 @@
|
||||
// https://google-webfonts-helper.herokuapp.com/fonts
|
||||
|
||||
@use "@/presentation/assets/styles/vite-path" as *;
|
||||
|
||||
/* slabo-27px-regular - latin-ext_latin */
|
||||
@font-face {
|
||||
font-family: 'Slabo 27px';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.eot'); /* IE9 Compat Modes */
|
||||
src: url('#{$base-assets-path}/fonts/slabo-27px-v6-latin-ext_latin-regular.eot'); /* IE9 Compat Modes */
|
||||
src: local('Slabo 27px'), local('Slabo27px-Regular'),
|
||||
url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.svg#Slabo27px') format('svg'); /* Legacy iOS */
|
||||
url('#{$base-assets-path}/fonts/slabo-27px-v6-latin-ext_latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('#{$base-assets-path}/fonts/slabo-27px-v6-latin-ext_latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('#{$base-assets-path}/fonts/slabo-27px-v6-latin-ext_latin-regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('#{$base-assets-path}/fonts/slabo-27px-v6-latin-ext_latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('#{$base-assets-path}/fonts/slabo-27px-v6-latin-ext_latin-regular.svg#Slabo27px') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* yesteryear-regular - latin */
|
||||
@@ -19,13 +21,13 @@
|
||||
font-family: 'Yesteryear';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.eot'); /* IE9 Compat Modes */
|
||||
src: url('#{$base-assets-path}/fonts/yesteryear-v8-latin-regular.eot'); /* IE9 Compat Modes */
|
||||
src: local('Yesteryear'), local('Yesteryear-Regular'),
|
||||
url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.svg#Yesteryear') format('svg'); /* Legacy iOS */
|
||||
url('#{$base-assets-path}/fonts/yesteryear-v8-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('#{$base-assets-path}/fonts/yesteryear-v8-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('#{$base-assets-path}/fonts/yesteryear-v8-latin-regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('#{$base-assets-path}/fonts/yesteryear-v8-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('#{$base-assets-path}/fonts/yesteryear-v8-latin-regular.svg#Yesteryear') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
$font-normal : 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@use "@/presentation/assets/styles/colors" as *;
|
||||
@use "@/presentation/assets/styles/fonts" as *;
|
||||
@use "@/presentation/assets/styles/mixins" as *;
|
||||
@use "@/presentation/assets/styles/vite-path" as *;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
|
||||
4
src/presentation/assets/styles/_vite-path.scss
Normal file
4
src/presentation/assets/styles/_vite-path.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
// Define paths specific to Vite's resolution system.
|
||||
// Vite uses the "@" symbol to resolve its aliases for styles.
|
||||
|
||||
$base-assets-path: "@/presentation/assets/";
|
||||
@@ -1,8 +1,8 @@
|
||||
import ace from 'ace-builds';
|
||||
|
||||
/*
|
||||
Following is here because `import 'ace-builds/webpack-resolver';` does not work with webpack 5.
|
||||
Related issue: https://github.com/ajaxorg/ace-builds/issues/211, PR: https://github.com/ajaxorg/ace-builds/pull/221
|
||||
Following is here because `import 'ace-builds/esm-resolver' imports all unused functionality
|
||||
when built with Vite (`npm run build`).
|
||||
*/
|
||||
|
||||
import 'ace-builds/src-noconflict/theme-github';
|
||||
|
||||
@@ -69,6 +69,8 @@ declare module 'liquor-tree' {
|
||||
matcher(query: string, node: ILiquorTreeExistingNode): boolean;
|
||||
}
|
||||
|
||||
const LiquorTree: PluginObject<Vue>;
|
||||
interface LiquorTreeVueComponent extends PluginObject<Vue> {
|
||||
install(Vue: VueConstructor<Vue>, options?: unknown);
|
||||
}
|
||||
export default LiquorTree;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ $text-size: 0.75em; // Lower looks bad on Firefox
|
||||
Use mask element instead of content/background-image etc.
|
||||
This way we can apply current font color to it to match the theme
|
||||
*/
|
||||
mask: url(~@/presentation/assets/icons/external-link.svg) no-repeat 50% 50%;
|
||||
mask: url(@/presentation/assets/icons/external-link.svg) no-repeat 50% 50%;
|
||||
mask-size: cover;
|
||||
content: '';
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
@node:unchecked="nodeSelected($event)"
|
||||
ref="liquorTree"
|
||||
>
|
||||
<span class="tree-text" slot-scope="{ node }">
|
||||
<NodeContent :data="convertExistingToNode(node)" />
|
||||
</span>
|
||||
<template v-slot:default="{ node }">
|
||||
<span class="tree-text">
|
||||
<NodeContent :data="convertExistingToNode(node)" />
|
||||
</span>
|
||||
</template>
|
||||
</LiquorTree>
|
||||
</span>
|
||||
<span v-else>Nooo 😢</span>
|
||||
@@ -192,4 +194,3 @@ async function tryUntilDefined<T>(
|
||||
return value;
|
||||
}
|
||||
</script>
|
||||
./Node/INodeContent
|
||||
|
||||
@@ -7,6 +7,7 @@ import fetch from 'cross-fetch';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { Version } from '@/domain/Version';
|
||||
import { ViteAppMetadata } from '@/infrastructure/Metadata/Vite/ViteAppMetadata';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { UpdateProgressBar } from './UpdateProgressBar';
|
||||
|
||||
@@ -28,7 +29,7 @@ export async function handleManualUpdate(info: UpdateInfo) {
|
||||
}
|
||||
|
||||
function getTargetProject(targetVersion: string) {
|
||||
const existingProject = parseProjectInformation(process.env);
|
||||
const existingProject = parseProjectInformation(new ViteAppMetadata());
|
||||
const targetProject = new ProjectInformation(
|
||||
existingProject.name,
|
||||
new Version(targetVersion),
|
||||
|
||||
38
src/presentation/index.html
Normal file
38
src/presentation/index.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Privacy is sexy 🍑🍆 - Enforce privacy & security on Windows, macOS and Linux</title>
|
||||
<meta name="robots" content="index,follow" />
|
||||
<meta name="description"
|
||||
content="Web tool to generate scripts for enforcing privacy & security best-practices such as stopping data collection of Windows and different softwares on it." />
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<style>
|
||||
#javascriptDisabled {
|
||||
background: #eceef1;
|
||||
margin: 5rem auto;
|
||||
max-width: 800px;
|
||||
font-size: 7px;
|
||||
padding: 3rem;
|
||||
border: 1px solid#333a45;
|
||||
font-size: 1.5rem;
|
||||
line-height: 150%;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
||||
}
|
||||
</style>
|
||||
<div id="javascriptDisabled">
|
||||
<h1>Problem loading page</h1>
|
||||
<p>The page does not work without JavaScript enabled. Please enable it to use privacy.sexy. There's no shady stuff
|
||||
as 100% of the website is open source.</p>
|
||||
</div>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,16 +1,10 @@
|
||||
import Vue from 'vue';
|
||||
import { buildContext } from '@/application/Context/ApplicationContextFactory';
|
||||
import App from './components/App.vue';
|
||||
import { ApplicationBootstrapper } from './bootstrapping/ApplicationBootstrapper';
|
||||
|
||||
buildContext().then(() => {
|
||||
// hack workaround to solve running tests through
|
||||
// Vue CLI throws 'Top-level-await is only supported in EcmaScript Modules'
|
||||
// once migrated to vite, remove buildContext() call from here and use top-level-await
|
||||
new ApplicationBootstrapper()
|
||||
.bootstrap(Vue);
|
||||
new ApplicationBootstrapper()
|
||||
.bootstrap(Vue);
|
||||
|
||||
new Vue({
|
||||
render: (h) => h(App),
|
||||
}).$mount('#app');
|
||||
});
|
||||
new Vue({
|
||||
render: (h) => h(App),
|
||||
}).$mount('#app');
|
||||
|
||||
BIN
src/presentation/public/favicon.ico
Normal file
BIN
src/presentation/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 353 KiB |
BIN
src/presentation/public/icon.png
Normal file
BIN
src/presentation/public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
23
src/presentation/public/index.html
Normal file
23
src/presentation/public/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<style>
|
||||
#javascriptDisabled {
|
||||
background:#eceef1;
|
||||
margin: 5rem auto;
|
||||
max-width: 800px;
|
||||
font-size: 7px;
|
||||
padding: 3rem;
|
||||
border: 1px solid#333a45;
|
||||
font-size: 1.5rem;
|
||||
line-height: 150%;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
||||
}
|
||||
</style>
|
||||
<div id="javascriptDisabled">
|
||||
<h1>Problem loading page</h1>
|
||||
<p>The page does not work without JavaScript enabled. Please enable it to use privacy.sexy. There's no shady stuff as 100% of the website is open source.</p>
|
||||
</div>
|
||||
</noscript>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
2
src/presentation/public/robots.txt
Normal file
2
src/presentation/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
Reference in New Issue
Block a user