mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 19:47:43 +01:00
feat: support for extracting css variables
This commit is contained in:
39
scripts/buildWebAfter.ts
Normal file
39
scripts/buildWebAfter.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
|
import { join, dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const extractCssVars = () => {
|
||||||
|
const filePath = join(__dirname, "../out/search-chat/index.css");
|
||||||
|
|
||||||
|
const cssContent = readFileSync(filePath, "utf-8");
|
||||||
|
|
||||||
|
const vars: Record<string, string> = {};
|
||||||
|
|
||||||
|
const propertyBlockRegex = /@property\s+(--[\w-]+)\s*\{([\s\S]*?)\}/g;
|
||||||
|
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
while ((match = propertyBlockRegex.exec(cssContent))) {
|
||||||
|
const [, varName, body] = match;
|
||||||
|
|
||||||
|
const initialValueMatch = /initial-value\s*:\s*([^;]+);/.exec(body);
|
||||||
|
|
||||||
|
if (initialValueMatch) {
|
||||||
|
vars[varName] = initialValueMatch[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssVarsBlock =
|
||||||
|
`.coco-container {\n` +
|
||||||
|
Object.entries(vars)
|
||||||
|
.map(([k, v]) => ` ${k}: ${v};`)
|
||||||
|
.join("\n") +
|
||||||
|
`\n}\n`;
|
||||||
|
|
||||||
|
writeFileSync(filePath, `${cssContent}\n${cssVarsBlock}`, "utf-8");
|
||||||
|
};
|
||||||
|
|
||||||
|
extractCssVars();
|
||||||
@@ -73,8 +73,6 @@
|
|||||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
||||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
--sidebar-border: oklch(0.92 0.004 286.32);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
|
||||||
--tw-border-style: solid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
@@ -105,7 +103,7 @@
|
|||||||
--color-chart-5: var(--chart-5);
|
--color-chart-5: var(--chart-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#searchChat-container{
|
#searchChat-container {
|
||||||
/* Map tokens directly; they are oklch(...) or other full values */
|
/* Map tokens directly; they are oklch(...) or other full values */
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
|
|||||||
183
tsup.config.ts
183
tsup.config.ts
@@ -1,12 +1,18 @@
|
|||||||
import { defineConfig } from 'tsup';
|
import { defineConfig } from "tsup";
|
||||||
import { writeFileSync, readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
import {
|
||||||
import { join, resolve } from 'path';
|
writeFileSync,
|
||||||
import postcss from 'postcss';
|
readFileSync,
|
||||||
import tailwindcssPostcss from '@tailwindcss/postcss';
|
readdirSync,
|
||||||
import autoprefixer from 'autoprefixer';
|
statSync,
|
||||||
|
existsSync,
|
||||||
|
} from "fs";
|
||||||
|
import { join, resolve } from "path";
|
||||||
|
import postcss from "postcss";
|
||||||
|
import tailwindcssPostcss from "@tailwindcss/postcss";
|
||||||
|
import autoprefixer from "autoprefixer";
|
||||||
|
|
||||||
const projectPackageJson = JSON.parse(
|
const projectPackageJson = JSON.parse(
|
||||||
readFileSync(join(__dirname, 'package.json'), 'utf-8')
|
readFileSync(join(__dirname, "package.json"), "utf-8")
|
||||||
);
|
);
|
||||||
|
|
||||||
function walk(dir: string): string[] {
|
function walk(dir: string): string[] {
|
||||||
@@ -29,8 +35,8 @@ function hasTauriRefs(content: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
entry: ['src/pages/web/index.tsx'],
|
entry: ["src/pages/web/index.tsx"],
|
||||||
format: ['esm'],
|
format: ["esm"],
|
||||||
dts: true,
|
dts: true,
|
||||||
splitting: true,
|
splitting: true,
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
@@ -38,74 +44,73 @@ export default defineConfig({
|
|||||||
treeshake: true,
|
treeshake: true,
|
||||||
minify: true,
|
minify: true,
|
||||||
env: {
|
env: {
|
||||||
BUILD_TARGET: 'web',
|
BUILD_TARGET: "web",
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: "production",
|
||||||
},
|
},
|
||||||
external: [
|
external: ["react", "react-dom"],
|
||||||
'react',
|
|
||||||
'react-dom',
|
|
||||||
],
|
|
||||||
esbuildOptions(options) {
|
esbuildOptions(options) {
|
||||||
options.bundle = true;
|
options.bundle = true;
|
||||||
options.platform = 'browser';
|
options.platform = "browser";
|
||||||
// Enable Tailwind v4 CSS import resolution using the "style" condition
|
// Enable Tailwind v4 CSS import resolution using the "style" condition
|
||||||
// so that `@import "tailwindcss";` in CSS can be resolved by esbuild.
|
// so that `@import "tailwindcss";` in CSS can be resolved by esbuild.
|
||||||
// See: https://tailwindcss.com/docs/installation#bundlers
|
// See: https://tailwindcss.com/docs/installation#bundlers
|
||||||
(options as any).conditions = ["style", "browser", "module", "default"];
|
(options as any).conditions = ["style", "browser", "module", "default"];
|
||||||
options.loader = {
|
(options.loader = {
|
||||||
'.css': 'css',
|
".css": "css",
|
||||||
'.scss': 'css',
|
".scss": "css",
|
||||||
'.svg': 'dataurl',
|
".svg": "dataurl",
|
||||||
'.png': 'dataurl',
|
".png": "dataurl",
|
||||||
'.jpg': 'dataurl',
|
".jpg": "dataurl",
|
||||||
},
|
}),
|
||||||
options.alias = {
|
(options.alias = {
|
||||||
'@': resolve(__dirname, './src')
|
"@": resolve(__dirname, "./src"),
|
||||||
}
|
});
|
||||||
options.external = [
|
options.external = [
|
||||||
'@tauri-apps/api',
|
"@tauri-apps/api",
|
||||||
'@tauri-apps/plugin-*',
|
"@tauri-apps/plugin-*",
|
||||||
'tauri-plugin-*',
|
"tauri-plugin-*",
|
||||||
];
|
];
|
||||||
options.treeShaking = true;
|
options.treeShaking = true;
|
||||||
options.define = {
|
options.define = {
|
||||||
'process.env.BUILD_TARGET': '"web"',
|
"process.env.BUILD_TARGET": '"web"',
|
||||||
'process.env.NODE_ENV': '"production"',
|
"process.env.NODE_ENV": '"production"',
|
||||||
'process.env.DEBUG': 'false',
|
"process.env.DEBUG": "false",
|
||||||
'process.env.IS_DEV': 'false',
|
"process.env.IS_DEV": "false",
|
||||||
'process.env.VERSION': `"${projectPackageJson.version}"`,
|
"process.env.VERSION": `"${projectPackageJson.version}"`,
|
||||||
};
|
};
|
||||||
options.pure = ['console.log'];
|
options.pure = ["console.log"];
|
||||||
options.target = 'es2020';
|
options.target = "es2020";
|
||||||
options.legalComments = 'none';
|
options.legalComments = "none";
|
||||||
options.ignoreAnnotations = false;
|
options.ignoreAnnotations = false;
|
||||||
},
|
},
|
||||||
esbuildPlugins: [
|
esbuildPlugins: [
|
||||||
{
|
{
|
||||||
name: 'jsx-import-source',
|
name: "jsx-import-source",
|
||||||
setup(build) {
|
setup(build) {
|
||||||
build.initialOptions.jsx = 'automatic';
|
build.initialOptions.jsx = "automatic";
|
||||||
build.initialOptions.jsxImportSource = 'react';
|
build.initialOptions.jsxImportSource = "react";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
outDir: 'out/search-chat',
|
outDir: "out/search-chat",
|
||||||
|
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
const outDir = join(__dirname, 'out/search-chat');
|
const outDir = join(__dirname, "out/search-chat");
|
||||||
const files = walk(outDir).filter(f => /\.(m?js|cjs)$/i.test(f));
|
const files = walk(outDir).filter((f) => /\.(m?js|cjs)$/i.test(f));
|
||||||
const tauriFiles = files.filter(f => {
|
const tauriFiles = files.filter((f) => {
|
||||||
const content = readFileSync(f, 'utf-8');
|
const content = readFileSync(f, "utf-8");
|
||||||
return hasTauriRefs(content);
|
return hasTauriRefs(content);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tauriFiles.length) {
|
if (tauriFiles.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Build output contains Tauri references:\n${tauriFiles.map(f => ` - ${f}`).join('\n')}`
|
`Build output contains Tauri references:\n${tauriFiles
|
||||||
|
.map((f) => ` - ${f}`)
|
||||||
|
.join("\n")}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const projectPackageJson = JSON.parse(
|
const projectPackageJson = JSON.parse(
|
||||||
readFileSync(join(__dirname, 'package.json'), 'utf-8')
|
readFileSync(join(__dirname, "package.json"), "utf-8")
|
||||||
);
|
);
|
||||||
|
|
||||||
const packageJson = {
|
const packageJson = {
|
||||||
@@ -117,57 +122,51 @@ export default defineConfig({
|
|||||||
types: "index.d.ts",
|
types: "index.d.ts",
|
||||||
dependencies: projectPackageJson.dependencies as Record<string, string>,
|
dependencies: projectPackageJson.dependencies as Record<string, string>,
|
||||||
peerDependencies: {
|
peerDependencies: {
|
||||||
"react": "^18.0.0",
|
react: "^18.0.0",
|
||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0",
|
||||||
|
},
|
||||||
|
sideEffects: ["*.css", "*.scss"],
|
||||||
|
publishConfig: {
|
||||||
|
access: "public",
|
||||||
|
registry: "https://registry.npmjs.org/",
|
||||||
},
|
},
|
||||||
"sideEffects": [
|
|
||||||
"*.css",
|
|
||||||
"*.scss"
|
|
||||||
],
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public",
|
|
||||||
"registry": "https://registry.npmjs.org/"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const noNeedDeps = [
|
|
||||||
"dotenv",
|
|
||||||
"uuid",
|
|
||||||
"wavesurfer.js",
|
|
||||||
]
|
|
||||||
|
|
||||||
const tauriDeps = Object.keys(packageJson.dependencies).filter(dep =>
|
const noNeedDeps = ["dotenv", "uuid", "wavesurfer.js"];
|
||||||
dep.includes('@tauri-apps') ||
|
|
||||||
dep.includes('tauri-plugin') ||
|
const tauriDeps = Object.keys(packageJson.dependencies).filter(
|
||||||
noNeedDeps.includes(dep)
|
(dep) =>
|
||||||
|
dep.includes("@tauri-apps") ||
|
||||||
|
dep.includes("tauri-plugin") ||
|
||||||
|
noNeedDeps.includes(dep)
|
||||||
);
|
);
|
||||||
tauriDeps.forEach(dep => {
|
tauriDeps.forEach((dep) => {
|
||||||
delete packageJson.dependencies[dep];
|
delete packageJson.dependencies[dep];
|
||||||
});
|
});
|
||||||
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(__dirname, 'out/search-chat/package.json'),
|
join(__dirname, "out/search-chat/package.json"),
|
||||||
JSON.stringify(packageJson, null, 2)
|
JSON.stringify(packageJson, null, 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const readmePath = join(__dirname, 'src/pages/web/README.md');
|
const readmePath = join(__dirname, "src/pages/web/README.md");
|
||||||
const readmeContent = readFileSync(readmePath, 'utf-8');
|
const readmeContent = readFileSync(readmePath, "utf-8");
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(__dirname, 'out/search-chat/README.md'),
|
join(__dirname, "out/search-chat/README.md"),
|
||||||
readmeContent
|
readmeContent
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to copy README.md:', error);
|
console.error("Failed to copy README.md:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure Tailwind v4 directives (@import, @source, @apply, @theme) are compiled
|
// Ensure Tailwind v4 directives (@import, @source, @apply, @theme) are compiled
|
||||||
// into a final CSS for the library consumer. Esbuild doesn't process Tailwind,
|
// into a final CSS for the library consumer. Esbuild doesn't process Tailwind,
|
||||||
// so we run PostCSS with Tailwind + Autoprefixer here to produce index.css.
|
// so we run PostCSS with Tailwind + Autoprefixer here to produce index.css.
|
||||||
try {
|
try {
|
||||||
const cssInPath = join(__dirname, 'src/main.css');
|
const cssInPath = join(__dirname, "src/main.css");
|
||||||
const cssOutPath = join(__dirname, 'out/search-chat/index.css');
|
const cssOutPath = join(__dirname, "out/search-chat/index.css");
|
||||||
const cssIn = readFileSync(cssInPath, 'utf-8');
|
const cssIn = readFileSync(cssInPath, "utf-8");
|
||||||
|
|
||||||
const result = await postcss([
|
const result = await postcss([
|
||||||
// Use the Tailwind v4 PostCSS plugin from @tailwindcss/postcss
|
// Use the Tailwind v4 PostCSS plugin from @tailwindcss/postcss
|
||||||
@@ -183,20 +182,21 @@ export default defineConfig({
|
|||||||
// This fixes consumer bundlers failing to resolve "/assets/*.png" from node_modules
|
// This fixes consumer bundlers failing to resolve "/assets/*.png" from node_modules
|
||||||
const assetRegex = /url\((['"])\/assets\/([^'"\)]+)\1\)/g;
|
const assetRegex = /url\((['"])\/assets\/([^'"\)]+)\1\)/g;
|
||||||
const rewrittenCss = result.css.replace(assetRegex, (_m, quote, file) => {
|
const rewrittenCss = result.css.replace(assetRegex, (_m, quote, file) => {
|
||||||
const srcAssetPath = join(__dirname, 'src/assets', file);
|
const srcAssetPath = join(__dirname, "src/assets", file);
|
||||||
if (!existsSync(srcAssetPath)) {
|
if (!existsSync(srcAssetPath)) {
|
||||||
console.warn(`[build:web] Asset not found: ${srcAssetPath}`);
|
console.warn(`[build:web] Asset not found: ${srcAssetPath}`);
|
||||||
return `url(${quote}/assets/${file}${quote})`;
|
return `url(${quote}/assets/${file}${quote})`;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const buffer = readFileSync(srcAssetPath);
|
const buffer = readFileSync(srcAssetPath);
|
||||||
const ext = file.split('.').pop()?.toLowerCase() ?? 'png';
|
const ext = file.split(".").pop()?.toLowerCase() ?? "png";
|
||||||
const mime = ext === 'svg'
|
const mime =
|
||||||
? 'image/svg+xml'
|
ext === "svg"
|
||||||
: ext === 'jpg' || ext === 'jpeg'
|
? "image/svg+xml"
|
||||||
? 'image/jpeg'
|
: ext === "jpg" || ext === "jpeg"
|
||||||
: 'image/png';
|
? "image/jpeg"
|
||||||
const base64 = buffer.toString('base64');
|
: "image/png";
|
||||||
|
const base64 = buffer.toString("base64");
|
||||||
const dataUrl = `data:${mime};base64,${base64}`;
|
const dataUrl = `data:${mime};base64,${base64}`;
|
||||||
return `url(${quote}${dataUrl}${quote})`;
|
return `url(${quote}${dataUrl}${quote})`;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -207,7 +207,18 @@ export default defineConfig({
|
|||||||
|
|
||||||
writeFileSync(cssOutPath, rewrittenCss);
|
writeFileSync(cssOutPath, rewrittenCss);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to compile Tailwind CSS with PostCSS:', error);
|
console.error("Failed to compile Tailwind CSS with PostCSS:", error);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
try {
|
||||||
|
console.log("[build:web] Executing buildWebAfter.ts script...");
|
||||||
|
await import("./scripts/buildWebAfter.ts");
|
||||||
|
console.log("[build:web] buildWebAfter.ts script executed successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"[build:web] Failed to execute buildWebAfter.ts script:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user