web: Fix issues surrounding Vite/ESBuild types.

This commit is contained in:
Teffen Ellis
2025-05-19 00:42:19 +02:00
parent 248fcd5d7f
commit 7c69add264
15 changed files with 139 additions and 82 deletions

View File

@ -3,13 +3,11 @@
* @import { StorybookConfig } from "@storybook/web-components-vite"; * @import { StorybookConfig } from "@storybook/web-components-vite";
* @import { InlineConfig, Plugin } from "vite"; * @import { InlineConfig, Plugin } from "vite";
*/ */
import { cwd } from "process"; import { createBundleDefinitions } from "@goauthentik/web/scripts/esbuild/environment";
import postcssLit from "rollup-plugin-postcss-lit"; import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from "vite-tsconfig-paths";
const NODE_ENV = process.env.NODE_ENV || "development"; const CSSImportPattern = /import [\w$]+ from .+\.(css)/g;
const CSSImportPattern = /import [\w\$]+ from .+\.(css)/g;
const JavaScriptFilePattern = /\.m?(js|ts|tsx)$/; const JavaScriptFilePattern = /\.m?(js|ts|tsx)$/;
/** /**
@ -54,11 +52,7 @@ const config = {
*/ */
const mergedConfig = { const mergedConfig = {
...config, ...config,
define: { define: createBundleDefinitions(),
"process.env.NODE_ENV": JSON.stringify(NODE_ENV),
"process.env.CWD": JSON.stringify(cwd()),
"process.env.AK_API_BASE_PATH": JSON.stringify(process.env.AK_API_BASE_PATH || ""),
},
plugins: [inlineCSSPlugin, ...plugins, postcssLit(), tsconfigPaths()], plugins: [inlineCSSPlugin, ...plugins, postcssLit(), tsconfigPaths()],
}; };

View File

@ -21,7 +21,7 @@ const log = console.debug.bind(console, logPrefix);
* ESBuild may tree-shake it out of production builds. * ESBuild may tree-shake it out of production builds.
* *
* ```ts * ```ts
* if (process.env.NODE_ENV === "development") { * if (import.meta.env.NODE_ENV=== "development") {
* await import("@goauthentik/esbuild-plugin-live-reload/client") * await import("@goauthentik/esbuild-plugin-live-reload/client")
* .catch(() => console.warn("Failed to import watcher")) * .catch(() => console.warn("Failed to import watcher"))
* } * }

View File

@ -4,15 +4,20 @@
export {}; export {};
declare global { declare global {
interface ImportMeta { /**
readonly env: { * Environment variables injected by ESBuild.
*/
interface ImportMetaEnv {
/** /**
* The injected watcher URL for ESBuild. * The injected watcher URL for ESBuild.
* This is used for live reloading in development mode. * This is used for live reloading in development mode.
* *
* @format url * @format url
*/ */
ESBUILD_WATCHER_URL: string; readonly ESBUILD_WATCHER_URL?: string;
}; }
interface ImportMeta {
readonly env: ImportMetaEnv;
} }
} }

View File

@ -1,16 +0,0 @@
/**
* @file Constants for JavaScript and TypeScript files.
*/
/// <reference types="../../types/global.js" />
/**
* The current Node.js environment, defaulting to "development" when not set.
*
* Note, this should only be used during the build process.
*
* If you need to check the environment at runtime, use `process.env.NODE_ENV` to
* ensure that module tree-shaking works correctly.
*
*/
export const NodeEnvironment = process.env.NODE_ENV || "development";

View File

@ -1,6 +1,20 @@
/** /**
* @file Utility functions for building and copying files. * @file Utility functions for working with environment variables.
*/ */
/// <reference types="./types/global.js" />
//#region Constants
/**
* The current Node.js environment, defaulting to "development" when not set.
*
* Note, this should only be used during the build process.
*
* If you need to check the environment at runtime, use `process.env.NODE_ENV` to
* ensure that module tree-shaking works correctly.
*
*/
export const NodeEnvironment = process.env.NODE_ENV || "development";
/** /**
* A source environment variable, which can be a string, number, boolean, null, or undefined. * A source environment variable, which can be a string, number, boolean, null, or undefined.
@ -14,19 +28,26 @@
* @typedef {T extends string ? `"${T}"` : T} JSONify * @typedef {T extends string ? `"${T}"` : T} JSONify
*/ */
//#endregion
//#region Utilities
/** /**
* Given an object of environment variables, returns a new object with the same keys and values, but * Given an object of environment variables, returns a new object with the same keys and values, but
* with the values serialized as strings. * with the values serialized as strings.
* *
* @template {Record<string, EnvironmentVariable>} EnvRecord * @template {Record<string, EnvironmentVariable>} EnvRecord
* @template {string} [Prefix='process.env.'] * @template {string} [Prefix='import.meta.env.']
* *
* @param {EnvRecord} input * @param {EnvRecord} input
* @param {Prefix} [prefix='process.env.'] * @param {Prefix} [prefix='import.meta.env.']
* *
* @returns {{[K in keyof EnvRecord as `${Prefix}${K}`]: JSONify<EnvRecord[K]>}} * @returns {{[K in keyof EnvRecord as `${Prefix}${K}`]: JSONify<EnvRecord[K]>}}
*/ */
export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ ("process.env.")) { export function serializeEnvironmentVars(
input,
prefix = /** @type {Prefix} */ ("import.meta.env."),
) {
/** /**
* @type {Record<string, string>} * @type {Record<string, string>}
*/ */
@ -40,3 +61,5 @@ export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ (
return /** @type {any} */ (env); return /** @type {any} */ (env);
} }
//#endregion

View File

@ -1,7 +1,6 @@
/// <reference types="./types/global.js" /> /// <reference types="./types/global.js" />
export * from "./paths.js"; export * from "./paths.js";
export * from "./constants.js"; export * from "./environment.js";
export * from "./build.js";
export * from "./version.js"; export * from "./version.js";
export * from "./scripting.js"; export * from "./scripting.js";

View File

@ -1,3 +1,4 @@
/// <reference types="../types/esbuild.js" />
/** /**
* @file ESBuild script for building the authentik web UI. * @file ESBuild script for building the authentik web UI.
* *
@ -9,7 +10,6 @@ import {
NodeEnvironment, NodeEnvironment,
readBuildIdentifier, readBuildIdentifier,
resolvePackage, resolvePackage,
serializeEnvironmentVars,
} from "@goauthentik/monorepo"; } from "@goauthentik/monorepo";
import { DistDirectory, DistDirectoryName, EntryPoint, PackageRoot } from "@goauthentik/web/paths"; import { DistDirectory, DistDirectoryName, EntryPoint, PackageRoot } from "@goauthentik/web/paths";
import { deepmerge } from "deepmerge-ts"; import { deepmerge } from "deepmerge-ts";
@ -20,15 +20,10 @@ import * as fs from "node:fs/promises";
import * as path from "node:path"; import * as path from "node:path";
import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs"; import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs";
import { createBundleDefinitions } from "./esbuild/environment.mjs";
const logPrefix = "[Build]"; const logPrefix = "[Build]";
const definitions = serializeEnvironmentVars({
NODE_ENV: NodeEnvironment,
CWD: process.cwd(),
AK_API_BASE_PATH: process.env.AK_API_BASE_PATH,
});
const patternflyPath = resolvePackage("@patternfly/patternfly"); const patternflyPath = resolvePackage("@patternfly/patternfly");
/** /**
@ -86,7 +81,7 @@ const BASE_ESBUILD_OPTIONS = {
root: MonoRepoRoot, root: MonoRepoRoot,
}), }),
], ],
define: definitions, define: createBundleDefinitions(),
format: "esm", format: "esm",
logOverride: { logOverride: {
/** /**

View File

@ -0,0 +1,29 @@
/**
* @file ESBuild environment utilities.
*/
import { AuthentikVersion, NodeEnvironment, serializeEnvironmentVars } from "@goauthentik/monorepo";
/**
* Creates a mapping of environment variables to their respective runtime constants.
*/
export function createBundleDefinitions() {
const SerializedNodeEnvironment = /** @type {`"development"` | `"production"`} */ (
JSON.stringify(NodeEnvironment)
);
/**
* @satisfies {Record<ESBuildImportEnvKey, string>}
*/
const envRecord = {
AK_VERSION: AuthentikVersion,
AK_API_BASE_PATH: process.env.AK_API_BASE_PATH ?? "",
};
return {
...serializeEnvironmentVars(envRecord),
// We need to explicitly set this for NPM packages that use `process`
// to determine their environment.
"process.env.NODE_ENV": SerializedNodeEnvironment,
"import.meta.env.NODE_ENV": SerializedNodeEnvironment,
};
}

View File

@ -43,7 +43,7 @@ import {
renderSidebarItems, renderSidebarItems,
} from "./AdminSidebar.js"; } from "./AdminSidebar.js";
if (process.env.NODE_ENV === "development") { if (import.meta.env.NODE_ENV === "development") {
await import("@goauthentik/esbuild-plugin-live-reload/client"); await import("@goauthentik/esbuild-plugin-live-reload/client");
} }

View File

@ -223,7 +223,7 @@ export function inspectStyleSheetTree(element: ReactiveElement): InspectedStyleS
}; };
} }
if (process.env.NODE_ENV === "development") { if (import.meta.env.NODE_ENV === "development") {
Object.assign(window, { Object.assign(window, {
inspectStyleSheetTree, inspectStyleSheetTree,
serializeStyleSheet, serializeStyleSheet,

View File

@ -14,6 +14,6 @@ import "@goauthentik/flow/stages/password/PasswordStage";
// end of stage import // end of stage import
if (process.env.NODE_ENV === "development") { if (import.meta.env.NODE_ENV === "development") {
await import("@goauthentik/esbuild-plugin-live-reload/client"); await import("@goauthentik/esbuild-plugin-live-reload/client");
} }

View File

@ -43,7 +43,7 @@ import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import { CurrentBrand, EventsApi, SessionUser } from "@goauthentik/api"; import { CurrentBrand, EventsApi, SessionUser } from "@goauthentik/api";
if (process.env.NODE_ENV === "development") { if (import.meta.env.NODE_ENV === "development") {
await import("@goauthentik/esbuild-plugin-live-reload/client"); await import("@goauthentik/esbuild-plugin-live-reload/client");
} }

39
web/types/esbuild.d.ts vendored Normal file
View File

@ -0,0 +1,39 @@
/**
* @file Import meta environment variables available via ESBuild.
*/
export {};
declare global {
interface ESBuildImportEnv {
/**
* The authentik version injected by ESBuild during build time.
*
* @format semver
*/
readonly AK_VERSION: string;
/**
* @todo Determine where this is used and if it is needed,
* give it a better name.
* @deprecated
*/
readonly AK_API_BASE_PATH: string;
}
type ESBuildImportEnvKey = keyof ESBuildImportEnv;
/**
* Environment variables injected by ESBuild.
*/
interface ImportMetaEnv extends ESBuildImportEnv {
/**
* An environment variable used to determine
* whether Node.js is running in production mode.
*/
readonly NODE_ENV: "development" | "production";
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
}

View File

@ -1,5 +1,5 @@
/** /**
* @file Environment variables available via ESBuild. * @file Global variables provided by Node.js
*/ */
declare module "module" { declare module "module" {
@ -14,8 +14,8 @@ declare module "module" {
* const relativeDirname = dirname(fileURLToPath(import.meta.url)); * const relativeDirname = dirname(fileURLToPath(import.meta.url));
* ``` * ```
*/ */
// eslint-disable-next-line no-var
var __dirname: string; const __dirname: string;
} }
} }
@ -23,13 +23,16 @@ declare module "process" {
global { global {
namespace NodeJS { namespace NodeJS {
interface ProcessEnv { interface ProcessEnv {
CWD: string; /**
* Node environment, if any.
*/
readonly NODE_ENV?: "development" | "production";
/** /**
* @todo Determine where this is used and if it is needed, * @todo Determine where this is used and if it is needed,
* give it a better name. * give it a better name.
* @deprecated * @deprecated
*/ */
AK_API_BASE_PATH: string; readonly AK_API_BASE_PATH?: string;
} }
} }
} }

View File

@ -1,17 +1,15 @@
import replace from "@rollup/plugin-replace"; /// <reference types="@wdio/browser-runner" />
import { browser } from "@wdio/globals"; import { browser } from "@wdio/globals";
import type { Options } from "@wdio/types"; import type { Options } from "@wdio/types";
import path from "path"; import path from "node:path";
import { cwd } from "process"; import { fileURLToPath } from "node:url";
import { fileURLToPath } from "url"; import { createBundleDefinitions } from "scripts/esbuild/environment.mjs";
import type { UserConfig } from "vite"; import type { InlineConfig } from "vite";
import litCss from "vite-plugin-lit-css"; import litCSS from "vite-plugin-lit-css";
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from "vite-tsconfig-paths";
const __dirname = fileURLToPath(new URL(".", import.meta.url)); const __dirname = fileURLToPath(new URL(".", import.meta.url));
const isProdBuild = process.env.NODE_ENV === "production";
const apiBasePath = process.env.AK_API_BASE_PATH || "";
const runHeadless = process.env.CI !== undefined; const runHeadless = process.env.CI !== undefined;
const testSafari = process.env.WDIO_TEST_SAFARI !== undefined; const testSafari = process.env.WDIO_TEST_SAFARI !== undefined;
@ -72,21 +70,9 @@ export const config: Options.Testrunner = {
runner: [ runner: [
"browser", "browser",
{ {
viteConfig: (userConfig: UserConfig = { plugins: [] }) => ({ viteConfig: {
...userConfig, define: createBundleDefinitions(),
plugins: [ plugins: [litCSS(), tsconfigPaths()],
litCss(),
replace({
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development",
),
"process.env.CWD": JSON.stringify(cwd()),
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
"preventAssignment": true,
}),
...(userConfig?.plugins ?? []),
tsconfigPaths(),
],
resolve: { resolve: {
alias: { alias: {
"@goauthentik/admin": path.resolve(__dirname, "src/admin"), "@goauthentik/admin": path.resolve(__dirname, "src/admin"),
@ -101,7 +87,7 @@ export const config: Options.Testrunner = {
"@goauthentik/user": path.resolve(__dirname, "src/user"), "@goauthentik/user": path.resolve(__dirname, "src/user"),
}, },
}, },
}), } satisfies InlineConfig,
}, },
], ],