2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-10-09 16:26:44 +00:00

chore: update modman vite setup to support dev server

This commit is contained in:
Jan Laupetin
2025-10-06 21:19:02 +02:00
parent 047fabb546
commit 4f24b9ce86
7 changed files with 164 additions and 96 deletions

View File

@@ -37,7 +37,7 @@ function ModMan:project()
}
includedirs {
"%{prj.location}"
"%{wks.location}/src/ModMan"
}
filter { "system:linux", "action:gmake" }

View File

@@ -18,6 +18,8 @@
namespace
{
constexpr auto LOCALHOST_PREFIX = "http://localhost:";
std::unordered_map<std::string, UiFile> assetLookup;
std::string WideStringToString(const std::wstring& wideString)
@@ -95,6 +97,13 @@ namespace
const auto uri = WideStringToString(wUri);
bool fileFound = false;
#ifdef _DEBUG
// Allow dev server access
if (uri.starts_with(LOCALHOST_PREFIX))
return S_OK;
#endif
if (uri.starts_with(edge::URL_PREFIX))
{
const auto asset = uri.substr(std::char_traits<char>::length(edge::URL_PREFIX) - 1);

View File

@@ -1,6 +1,6 @@
#pragma once
#include "ui/modmanui.h"
#include "Web/ViteAssets.h"
#include <string>
#include <unordered_map>

View File

@@ -2,6 +2,7 @@
#include "webview/webview.h"
#pragma warning(pop)
#include "Web/ViteAssets.h"
#include "Web/Edge/AssetHandlerEdge.h"
#include "Web/Gtk/AssetHandlerGtk.h"
@@ -58,12 +59,19 @@ int main()
#if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE)
edge::InstallCustomProtocolHandler(w);
w.navigate(edge::URL_PREFIX + "index.html"s);
constexpr auto urlPrefix = edge::URL_PREFIX;
#elif defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK)
gtk::InstallCustomProtocolHandler(w);
w.navigate(gtk::URL_PREFIX + "index.html"s);
constexpr auto urlPrefix = gtk::URL_PREFIX;
#else
#error Unsupported platform
#endif
#ifdef _DEBUG
w.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", urlPrefix));
#else
w.navigate(std::format("{}index.html", urlPrefix));
#endif
w.run();
}
catch (const webview::exception& e)

View File

@@ -1,84 +1,88 @@
import type { Plugin, ViteDevServer } from "vite";
import type { OutputOptions, OutputBundle, OutputAsset, OutputChunk } from "rollup";
import type { Plugin } from "vite";
import type { OutputAsset, OutputChunk } from "rollup";
import path from "node:path";
import fs from "node:fs";
function createTransformedTextSource(varName: string, previousSource: string) {
const str = [...previousSource]
.map((v) => `0x${v.charCodeAt(0).toString(16).padStart(2, "0")}`)
.join(", ");
return `#pragma once
static inline const unsigned char ${varName}[] {
${str}
};
`;
}
type MinimalOutputAsset = Pick<OutputAsset, "type" | "fileName" | "source">;
type MinimalOutputChunk = Pick<OutputChunk, "type" | "fileName" | "code">;
type MinimalOutputBundle = Record<string, MinimalOutputAsset | MinimalOutputChunk>;
function createVarName(fileName: string) {
return fileName.replaceAll(".", "_").toUpperCase();
}
function transformAsset(asset: OutputAsset) {
const varName = createVarName(asset.names[0]);
function transformAsset(asset: MinimalOutputAsset) {
const varName = createVarName(asset.fileName);
let bytes: string;
if (typeof asset.source === "string") {
asset.source = createTransformedTextSource(varName, asset.source);
bytes = [...asset.source].map((v) => String(v.charCodeAt(0))).join(",");
} else {
const str = [...asset.source].map((v) => `0x${v.toString(16).padStart(2, "0")}`).join(", ");
asset.source = `#pragma once
static inline const unsigned char ${varName}[] {
${str}
};
`;
bytes = [...asset.source].map((v) => String(v)).join(",");
}
return varName;
return `constexpr const unsigned char ${varName}[] {${bytes}};
`;
}
function transformChunk(chunk: OutputChunk) {
function transformChunk(chunk: MinimalOutputChunk) {
const varName = createVarName(chunk.fileName);
chunk.code = createTransformedTextSource(varName, chunk.code);
return varName;
const bytes = [...chunk.code].map((v) => String(v.charCodeAt(0))).join(",");
return `constexpr const unsigned char ${varName}[] {${bytes}};
`;
}
export function headerTransformationPlugin(): Plugin {
return {
name: "header-transformation",
apply: "build",
generateBundle(options: OutputOptions, bundle: OutputBundle, isWrite: boolean) {
const includesStr: string[] = [`#include "index.html.h"`];
const uiFilesStr: string[] = [
`{ "index.html", INDEX_HTML, std::extent_v<decltype(INDEX_HTML)> }`,
];
function writeHeader(
bundle: MinimalOutputBundle,
outputDir?: string,
options?: HeaderTransformationPluginOptions,
devServerPort?: number,
) {
const outputPath = options?.outputPath ?? path.join(outputDir ?? "dist", "ViteAssets.h");
const outputPathParentDir = path.dirname(outputPath);
for (const curBundle of Object.values(bundle)) {
let varName: string;
if (curBundle.type === "asset") {
varName = transformAsset(curBundle);
} else {
varName = transformChunk(curBundle);
}
fs.mkdirSync(outputPathParentDir, { recursive: true });
includesStr.push(`#include "${curBundle.fileName}.h"`);
uiFilesStr.push(
`{ "${curBundle.fileName}", ${varName}, std::extent_v<decltype(${varName})> }`,
);
const fd = fs.openSync(outputPath, "w");
const includeFileEnumeration = options?.includeFileEnumeration ?? true;
curBundle.fileName = `${curBundle.fileName}.h`;
}
fs.writeSync(
fd,
`#pragma once
this.emitFile({
type: "asset",
fileName: "modmanui.h",
source: `#pragma once
`,
);
${includesStr.join("\n")}
#include <cstdlib>
if (includeFileEnumeration) {
fs.writeSync(
fd,
`#include <cstdlib>
#include <type_traits>
`,
);
}
fs.writeSync(
fd,
`constexpr auto VITE_DEV_SERVER = ${devServerPort ? "true" : "false"};
constexpr auto VITE_DEV_SERVER_PORT = ${devServerPort ? String(devServerPort) : "-1"};
`,
);
for (const curBundle of Object.values(bundle)) {
if (curBundle.type === "asset") {
fs.writeSync(fd, transformAsset(curBundle));
} else {
fs.writeSync(fd, transformChunk(curBundle));
}
}
if (includeFileEnumeration) {
fs.writeSync(
fd,
`
struct UiFile
{
const char* filename;
@@ -87,36 +91,81 @@ struct UiFile
};
static inline const UiFile MOD_MAN_UI_FILES[] {
${uiFilesStr.join(",\n")}
`,
);
let index = 0;
for (const curBundle of Object.values(bundle)) {
const fileName = curBundle.fileName;
const varName = createVarName(fileName);
let prefix = " ";
if (index > 0) {
prefix = `,
`;
}
fs.writeSync(
fd,
`${prefix}{ "${fileName}", ${varName}, std::extent_v<decltype(${varName})> }`,
);
index++;
}
fs.writeSync(
fd,
`
};
`,
);
fs.closeSync(fd);
}
}
export interface HeaderTransformationPluginOptions {
outputPath?: string;
includeFileEnumeration?: boolean;
}
export default function headerTransformationPlugin(
options?: HeaderTransformationPluginOptions,
): Plugin {
let writeServerActive = false;
let writeBundleActive = false;
return {
name: "vite-plugin-header-transformation",
enforce: "post",
config(_userOptions, env) {
if (env.command === "serve") {
writeServerActive = true;
} else {
writeBundleActive = true;
}
},
configureServer(server) {
if (!writeServerActive) {
return;
}
server.httpServer?.once("listening", () => {
writeHeader(
{
// We need at least one array entry for MSVC
dummyfile: { type: "chunk", fileName: "dummyfile", code: "dummy" },
},
server.config.build.outDir,
options,
server.config.server.port,
);
});
},
transformIndexHtml(
html: string,
ctx: {
path: string;
filename: string;
server?: ViteDevServer;
bundle?: OutputBundle;
chunk?: OutputChunk;
},
) {
html = html.replaceAll("index.js.h", "index.js");
html = createTransformedTextSource(createVarName("index.html"), html);
ctx.filename = `${ctx.filename}.h`;
return html;
},
writeBundle(options, bundle) {
for (const curBundle of Object.values(bundle)) {
if (curBundle.fileName === "index.html" && curBundle.type === "asset") {
const outputFilePath = path.join(options.dir!, curBundle.fileName);
fs.renameSync(outputFilePath, outputFilePath + ".h");
curBundle.fileName += ".h";
}
writeBundle(outputOptions, bundle) {
if (!writeBundleActive) {
return;
}
writeHeader(bundle, outputOptions.dir, options);
},
};
}

View File

@@ -1,7 +1,6 @@
export interface NativeMethods{
greet: (name: string) => Promise<string>;
export interface NativeMethods {
greet: (name: string) => Promise<string>;
}
// @ts-expect-error
// @ts-expect-error Typescript expects this to be an error, it is not here though
export const nativeMethods: NativeMethods = window as NativeMethods;

View File

@@ -3,12 +3,11 @@ import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueDevTools from "vite-plugin-vue-devtools";
import { headerTransformationPlugin } from "./build/HeaderTransformationPlugin";
import headerTransformationPlugin from "./build/HeaderTransformationPlugin";
// https://vite.dev/config/
export default defineConfig({
build: {
outDir: "../../build/src/ModMan/ui",
copyPublicDir: false,
emptyOutDir: true,
rollupOptions: {
@@ -19,14 +18,18 @@ export default defineConfig({
},
},
},
plugins: [vue(), vueDevTools(), headerTransformationPlugin()],
plugins: [
vue(),
vueDevTools(),
headerTransformationPlugin({
outputPath: fileURLToPath(
new URL("../../build/src/ModMan/Web/ViteAssets.h", import.meta.url),
),
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
server: {
port: 1420,
strictPort: true,
},
});