Skip to content

Commit e3e5d75

Browse files
committed
Implement favicon inlining in Vite configuration and enhance npx command for serving files; support for hash routing added in main.tsx
1 parent e008cc7 commit e3e5d75

3 files changed

Lines changed: 58 additions & 57 deletions

File tree

npx/run-claude-artifact.js

Lines changed: 32 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,9 @@ function run(cmd, args, options = {}) {
1414
});
1515
}
1616

17-
// Build using a temporary Vite config that always enables single-file output
17+
// Build using the template's single-file config
1818
async function buildWithSingleFileConfig(repoDir) {
19-
const configFileName = 'vite.single.config.ts';
20-
const configPath = path.join(repoDir, configFileName);
21-
const configContents = `import { defineConfig, mergeConfig } from 'vite';
22-
import baseConfig from './vite.config';
23-
import { viteSingleFile } from 'vite-plugin-singlefile';
24-
25-
export default defineConfig((env) => {
26-
const base = typeof baseConfig === 'function' ? (baseConfig)(env) : baseConfig;
27-
return mergeConfig(base, {
28-
plugins: [...(base.plugins || []), viteSingleFile()]
29-
});
30-
});
31-
`;
32-
33-
await fs.promises.writeFile(configPath, configContents, 'utf8');
34-
// Ensure vite-plugin-singlefile is available in the cloned template
35-
const pluginPath = path.join(repoDir, 'node_modules', 'vite-plugin-singlefile');
36-
if (!fs.existsSync(pluginPath)) {
37-
await run('npm', ['install', 'vite-plugin-singlefile@^2.3.0'], { cwd: repoDir });
38-
}
39-
try {
40-
await run('node_modules/.bin/vite', ['build', '-c', configFileName], { cwd: repoDir });
41-
} finally {
42-
try {
43-
await fs.promises.unlink(configPath);
44-
} catch (_) {
45-
// ignore cleanup errors
46-
}
47-
}
19+
await run('node_modules/.bin/vite', ['build', '-c', 'vite.single.config.ts'], { cwd: repoDir });
4820
}
4921

5022
// Deploy build output to specified directory
@@ -260,41 +232,52 @@ Notes:
260232

261233
// Function to serve files using npx serve
262234
async function serveFile(filePath, isSingleFile = false, fileName = null) {
263-
const serveDir = filePath; // filePath is already the directory to serve
264-
const displayPath = isSingleFile && fileName ? `${filePath}/${fileName}` : filePath;
265-
console.log(`🌐 Serving ${isSingleFile ? 'single file' : 'directory'}: ${displayPath}`);
235+
let serveDir = filePath;
236+
let url = 'http://localhost:3000';
237+
let tmpDir = null;
238+
239+
if (isSingleFile && fileName) {
240+
// Copy single-file to temp dir as index.html
241+
tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'artifact-view-'));
242+
const target = path.join(tmpDir, 'index.html');
243+
await fs.promises.copyFile(path.join(filePath, fileName), target);
244+
serveDir = tmpDir;
245+
url = 'http://localhost:3000/';
246+
console.log(`🌐 Serving single file via temp dir: ${target}`);
247+
} else {
248+
console.log(`🌐 Serving directory: ${filePath}`);
249+
}
266250

267-
const serveArgs = ['serve', '-s', '-L', serveDir];
268-
const serveProcess = spawn('npx', serveArgs, {
269-
stdio: 'inherit'
270-
});
251+
// Drop -s so serve does not single-page rewrite; we want 404s in multipage, and index.html at / in single-file temp
252+
const serveArgs = ['serve', '-L', serveDir];
253+
const serveProcess = spawn('npx', serveArgs, { stdio: 'inherit' });
271254

272255
// Wait for server to start before opening browser
273-
const url = isSingleFile && fileName ? `http://localhost:3000/${fileName}` : 'http://localhost:3000';
274256
console.log(`🚀 Opening browser at: ${url}`);
275-
276-
// Give server time to start
277257
await new Promise(resolve => setTimeout(resolve, 3000));
278258

279-
// Try to open browser (cross-platform)
280259
try {
281-
const openCmd = process.platform === 'darwin' ? 'open' :
282-
process.platform === 'win32' ? 'start' : 'xdg-open';
260+
const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
283261
await run(openCmd, [url]);
284-
} catch (error) {
262+
} catch {
285263
console.log(`ℹ️ Please open your browser and navigate to: ${url}`);
286264
}
287265

288-
process.on('SIGINT', () => {
266+
process.on('SIGINT', async () => {
289267
console.log(`${os.EOL}🛑 Stopping server...`);
290268
serveProcess.kill('SIGINT');
269+
if (tmpDir) {
270+
try { await fs.promises.rm(tmpDir, { recursive: true, force: true }); } catch {}
271+
}
291272
process.exit(0);
292273
});
293274

294-
// Wait for the process to exit
295-
await new Promise((resolve) => {
296-
serveProcess.on('exit', resolve);
297-
});
275+
await new Promise((resolve) => { serveProcess.on('exit', resolve); });
276+
277+
// Cleanup temp dir after server exits
278+
if (tmpDir) {
279+
try { await fs.promises.rm(tmpDir, { recursive: true, force: true }); } catch {}
280+
}
298281
}
299282

300283
async function main() {

src/main.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom/client';
3-
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
3+
import { RouterProvider, createBrowserRouter, createHashRouter } from 'react-router-dom';
44
import routes from 'virtual:generated-pages-react';
55
import Layout from './components/layout';
66
import './index.css';
77

8-
const router = createBrowserRouter(
9-
routes.map((route) => ({
10-
...route,
11-
element: <Layout>{route.element}</Layout>,
12-
}))
13-
);
8+
const useHash = typeof window !== 'undefined' && window.location?.protocol === 'file:';
9+
const mkRoutes = routes.map((route) => ({
10+
...route,
11+
element: <Layout>{route.element}</Layout>,
12+
}));
13+
const router = useHash ? createHashRouter(mkRoutes) : createBrowserRouter(mkRoutes);
1414

1515
ReactDOM.createRoot(document.getElementById('root')!).render(
1616
<React.StrictMode>

vite.single.config.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
import { defineConfig, mergeConfig } from 'vite';
22
import baseConfig from './vite.config';
33
import { viteSingleFile } from 'vite-plugin-singlefile';
4+
import fs from 'node:fs';
5+
import path from 'node:path';
6+
7+
const inlineFaviconPost = {
8+
name: 'inline-favicon-post',
9+
transformIndexHtml: {
10+
order: 'post' as const,
11+
handler(html: string) {
12+
const icoPath = path.resolve(__dirname, 'public/favicon.ico');
13+
if (!fs.existsSync(icoPath)) return html;
14+
const icoBase64 = fs.readFileSync(icoPath).toString('base64');
15+
const tag = `<link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/x-icon;base64,${icoBase64}\">`;
16+
return html.includes('rel="icon"')
17+
? html.replace(/<link[^>]*rel=["'](?:icon|shortcut icon)["'][^>]*>/, tag)
18+
: html.replace('</head>', `${tag}\n</head>`);
19+
}
20+
}
21+
};
422

523
export default defineConfig((env) => {
624
const base = typeof baseConfig === 'function' ? (baseConfig as any)(env) : (baseConfig as any);
725
return mergeConfig(base, {
8-
plugins: [...(base.plugins || []), viteSingleFile()]
26+
plugins: [...(base.plugins || []), viteSingleFile(), inlineFaviconPost]
927
});
1028
});
1129

0 commit comments

Comments
 (0)