Files
objdiff-web/rsbuild.config.ts
2025-03-19 12:01:55 -06:00

181 lines
4.3 KiB
TypeScript

import fs from 'node:fs';
import type { ServerResponse } from 'node:http';
import path from 'node:path';
import {
type RequestHandler,
type RsbuildConfig,
defineConfig,
} from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginTypeCheck } from '@rsbuild/plugin-type-check';
import { pluginTypedCSSModules } from '@rsbuild/plugin-typed-css-modules';
// Standalone web configuration.
const webConfig: RsbuildConfig = {
source: {
entry: {
index: './webview/index.tsx',
},
},
html: {
scriptLoading: 'module',
title: 'objdiff',
},
plugins: [pluginReact(), pluginTypeCheck(), pluginTypedCSSModules()],
dev: {
setupMiddlewares: [
(middlewares, _server) => {
middlewares.unshift(apiMiddleware);
return middlewares;
},
],
},
};
// VS Code extension configuration.
const extensionConfig: RsbuildConfig = {
environments: {
extension: {
source: {
entry: {
extension: './src/extension.ts',
},
},
output: {
target: 'node',
distPath: {
root: 'dist',
},
externals: ['vscode'],
legalComments: 'none',
},
},
webview: {
source: {
entry: {
index: './webview/index.tsx',
},
},
output: {
target: 'web',
distPath: {
root: 'dist/webview',
},
// VS Code webviews don't have easy access to resources,
// (especially if the extension is running on web) so we
// simply inline everything into the HTML.
dataUriLimit: 1000000000,
inlineScripts: true,
inlineStyles: true,
legalComments: 'none',
},
html: {
scriptLoading: 'module',
title: 'objdiff',
},
plugins: [
pluginReact({
fastRefresh: false,
}),
pluginTypedCSSModules(),
],
},
},
// Ensure that we never split chunks. Both the extension and
// the webview must be self-contained files.
performance: {
chunkSplit: {
strategy: 'all-in-one',
},
},
// Enable async TypeScript type checking.
plugins: [pluginTypeCheck()],
// We can't use async chunks for aforementioned reasons.
// Disabling them ensures that all code is bundled together
// even if dynamic imports are used.
tools: {
rspack: {
output: {
asyncChunks: false,
},
},
},
// Disable HMR and live reload. Neither the extension nor the
// webview can communicate with the rsbuild dev server.
dev: {
hmr: false,
liveReload: false,
},
// Disable copying the public directory. This is only useful
// for the web configuration.
server: {
publicDir: {
copyOnBuild: false,
},
},
};
const buildType = process.env.BUILD_TYPE;
let config: RsbuildConfig;
if (buildType === 'extension') {
config = extensionConfig;
} else {
config = webConfig;
}
export default defineConfig(config);
const PROJECT_ROOT = '../prime';
// Mock API middleware for development.
const apiMiddleware: RequestHandler = (req, res, next) => {
// Permit cross-origin embedding for decomp.me.
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
if (!req.url || !req.headers.host || req.method !== 'GET') {
return next();
}
const url = new URL(req.url, `http://${req.headers.host}`);
if (!url) {
return next();
}
if (url.pathname === '/api/get') {
const file = url.searchParams.get('path');
if (file) {
const filepath = path.join(PROJECT_ROOT, file);
if (filepath.startsWith(PROJECT_ROOT)) {
return sendFile(res, filepath, 'application/octet-stream');
}
}
}
return next();
};
// Send a file as a response.
function sendFile(
res: ServerResponse,
path: string,
contentType: string,
): void {
const stream = fs.createReadStream(path);
stream.on('error', (err) => {
if (res.headersSent) {
throw err;
}
let statusCode = 500;
if ((err as any).code === 'ENOENT') {
statusCode = 404;
}
res.writeHead(statusCode, {
'Content-Type': 'application/json',
});
res.end(JSON.stringify({ error: err.message }));
});
stream.on('ready', () => {
res.writeHead(200, {
'Content-Type': contentType,
});
});
stream.pipe(res);
}