keycloak_theme/src/vite-plugin/vite-plugin.ts

218 lines
6.8 KiB
TypeScript

import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
import type { Plugin } from "vite";
import {
WELL_KNOWN_DIRECTORY_BASE_NAME,
VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES
} from "../bin/shared/constants";
import { id } from "tsafe/id";
import { rm } from "../bin/tools/fs.rm";
import { copyKeycloakResourcesToPublic } from "../bin/shared/copyKeycloakResourcesToPublic";
import { assert } from "tsafe/assert";
import {
getBuildContext,
type BuildContext,
type BuildOptions,
type ResolvedViteConfig
} from "../bin/shared/buildContext";
import MagicString from "magic-string";
import { generateKcGenTs } from "../bin/shared/generateKcGenTs";
export namespace keycloakify {
export type Params = BuildOptions & {
postBuild?: (buildContext: Omit<BuildContext, "bundler">) => Promise<void>;
};
}
export function keycloakify(params: keycloakify.Params) {
const { postBuild, ...buildOptions } = params;
let projectDirPath: string | undefined = undefined;
let urlPathname: string | undefined = undefined;
let buildDirPath: string | undefined = undefined;
let command: "build" | "serve" | undefined = undefined;
let shouldGenerateSourcemap: boolean | undefined = undefined;
const plugin = {
name: "keycloakify",
configResolved: async resolvedConfig => {
shouldGenerateSourcemap = resolvedConfig.build.sourcemap !== false;
run_post_build_script_case: {
const envValue =
process.env[VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RUN_POST_BUILD_SCRIPT];
if (envValue === undefined) {
break run_post_build_script_case;
}
const { buildContext, resourcesDirPath } = JSON.parse(envValue) as {
buildContext: BuildContext;
resourcesDirPath: string;
};
process.chdir(resourcesDirPath);
await postBuild?.(buildContext);
process.exit(0);
}
command = resolvedConfig.command;
projectDirPath = resolvedConfig.root;
urlPathname = (() => {
let out = resolvedConfig.env.BASE_URL;
if (
out.startsWith(".") &&
command === "build" &&
resolvedConfig.envPrefix?.includes("STORYBOOK_") !== true
) {
throw new Error(
[
`BASE_URL=${out} is not supported By Keycloakify. Use an absolute path instead.`,
`If this is a problem, please open an issue at https://github.com/keycloakify/keycloakify/issues/new`
].join("\n")
);
}
if (out === undefined) {
return undefined;
}
if (!out.startsWith("/")) {
out = "/" + out;
}
if (!out.endsWith("/")) {
out += "/";
}
return out;
})();
buildDirPath = pathJoin(projectDirPath, resolvedConfig.build.outDir);
resolve_vite_config_case: {
const envValue =
process.env[VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RESOLVE_VITE_CONFIG];
if (envValue === undefined) {
break resolve_vite_config_case;
}
console.log(VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RESOLVE_VITE_CONFIG);
console.log(
JSON.stringify(
id<ResolvedViteConfig>({
publicDir: pathRelative(
projectDirPath,
resolvedConfig.publicDir
),
assetsDir: resolvedConfig.build.assetsDir,
buildDir: resolvedConfig.build.outDir,
urlPathname,
buildOptions
})
)
);
process.exit(0);
}
const buildContext = getBuildContext({
cliCommandOptions: {
projectDirPath
}
});
copyKeycloakResourcesToPublic({ buildContext }),
await generateKcGenTs({ buildContext });
},
transform: (code, id) => {
assert(command !== undefined);
assert(shouldGenerateSourcemap !== undefined);
if (command !== "build") {
return;
}
assert(projectDirPath !== undefined);
{
const isWithinSourceDirectory = id.startsWith(
pathJoin(projectDirPath, "src") + pathSep
);
if (!isWithinSourceDirectory) {
return;
}
}
{
const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx");
const isTypeScriptFile = id.endsWith(".ts") || id.endsWith(".tsx");
if (!isTypeScriptFile && !isJavascriptFile) {
return;
}
}
const transformedCode = new MagicString(code);
transformedCode.replaceAll(
/import\.meta\.env(?:(?:\.BASE_URL)|(?:\["BASE_URL"\]))/g,
[
`(`,
`(window.kcContext === undefined || import.meta.env.MODE === "development")?`,
`"${urlPathname ?? "/"}":`,
`(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/")`,
`)`
].join("")
);
if (!transformedCode.hasChanged()) {
return;
}
if (!shouldGenerateSourcemap) {
return transformedCode.toString();
}
const map = transformedCode.generateMap({
source: id,
includeContent: true,
hires: true
});
return {
code: transformedCode.toString(),
map: map.toString()
};
},
closeBundle: async () => {
assert(command !== undefined);
if (command !== "build") {
return;
}
assert(buildDirPath !== undefined);
await rm(
pathJoin(
buildDirPath,
WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES
),
{
recursive: true,
force: true
}
);
}
} satisfies Plugin;
return plugin as any;
}