mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +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-border: oklch(0.92 0.004 286.32);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
|
||||
--tw-border-style: solid;
|
||||
}
|
||||
|
||||
@theme {
|
||||
@@ -105,7 +103,7 @@
|
||||
--color-chart-5: var(--chart-5);
|
||||
}
|
||||
|
||||
#searchChat-container{
|
||||
#searchChat-container {
|
||||
/* Map tokens directly; they are oklch(...) or other full values */
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
|
||||
183
tsup.config.ts
183
tsup.config.ts
@@ -1,12 +1,18 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
import { writeFileSync, readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
||||
import { join, resolve } from 'path';
|
||||
import postcss from 'postcss';
|
||||
import tailwindcssPostcss from '@tailwindcss/postcss';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import { defineConfig } from "tsup";
|
||||
import {
|
||||
writeFileSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
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(
|
||||
readFileSync(join(__dirname, 'package.json'), 'utf-8')
|
||||
readFileSync(join(__dirname, "package.json"), "utf-8")
|
||||
);
|
||||
|
||||
function walk(dir: string): string[] {
|
||||
@@ -29,8 +35,8 @@ function hasTauriRefs(content: string): boolean {
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/pages/web/index.tsx'],
|
||||
format: ['esm'],
|
||||
entry: ["src/pages/web/index.tsx"],
|
||||
format: ["esm"],
|
||||
dts: true,
|
||||
splitting: true,
|
||||
sourcemap: false,
|
||||
@@ -38,74 +44,73 @@ export default defineConfig({
|
||||
treeshake: true,
|
||||
minify: true,
|
||||
env: {
|
||||
BUILD_TARGET: 'web',
|
||||
NODE_ENV: 'production',
|
||||
BUILD_TARGET: "web",
|
||||
NODE_ENV: "production",
|
||||
},
|
||||
external: [
|
||||
'react',
|
||||
'react-dom',
|
||||
],
|
||||
external: ["react", "react-dom"],
|
||||
esbuildOptions(options) {
|
||||
options.bundle = true;
|
||||
options.platform = 'browser';
|
||||
options.platform = "browser";
|
||||
// Enable Tailwind v4 CSS import resolution using the "style" condition
|
||||
// so that `@import "tailwindcss";` in CSS can be resolved by esbuild.
|
||||
// See: https://tailwindcss.com/docs/installation#bundlers
|
||||
(options as any).conditions = ["style", "browser", "module", "default"];
|
||||
options.loader = {
|
||||
'.css': 'css',
|
||||
'.scss': 'css',
|
||||
'.svg': 'dataurl',
|
||||
'.png': 'dataurl',
|
||||
'.jpg': 'dataurl',
|
||||
},
|
||||
options.alias = {
|
||||
'@': resolve(__dirname, './src')
|
||||
}
|
||||
(options.loader = {
|
||||
".css": "css",
|
||||
".scss": "css",
|
||||
".svg": "dataurl",
|
||||
".png": "dataurl",
|
||||
".jpg": "dataurl",
|
||||
}),
|
||||
(options.alias = {
|
||||
"@": resolve(__dirname, "./src"),
|
||||
});
|
||||
options.external = [
|
||||
'@tauri-apps/api',
|
||||
'@tauri-apps/plugin-*',
|
||||
'tauri-plugin-*',
|
||||
"@tauri-apps/api",
|
||||
"@tauri-apps/plugin-*",
|
||||
"tauri-plugin-*",
|
||||
];
|
||||
options.treeShaking = true;
|
||||
options.define = {
|
||||
'process.env.BUILD_TARGET': '"web"',
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
'process.env.DEBUG': 'false',
|
||||
'process.env.IS_DEV': 'false',
|
||||
'process.env.VERSION': `"${projectPackageJson.version}"`,
|
||||
"process.env.BUILD_TARGET": '"web"',
|
||||
"process.env.NODE_ENV": '"production"',
|
||||
"process.env.DEBUG": "false",
|
||||
"process.env.IS_DEV": "false",
|
||||
"process.env.VERSION": `"${projectPackageJson.version}"`,
|
||||
};
|
||||
options.pure = ['console.log'];
|
||||
options.target = 'es2020';
|
||||
options.legalComments = 'none';
|
||||
options.pure = ["console.log"];
|
||||
options.target = "es2020";
|
||||
options.legalComments = "none";
|
||||
options.ignoreAnnotations = false;
|
||||
},
|
||||
esbuildPlugins: [
|
||||
{
|
||||
name: 'jsx-import-source',
|
||||
name: "jsx-import-source",
|
||||
setup(build) {
|
||||
build.initialOptions.jsx = 'automatic';
|
||||
build.initialOptions.jsxImportSource = 'react';
|
||||
build.initialOptions.jsx = "automatic";
|
||||
build.initialOptions.jsxImportSource = "react";
|
||||
},
|
||||
},
|
||||
],
|
||||
outDir: 'out/search-chat',
|
||||
outDir: "out/search-chat",
|
||||
|
||||
async onSuccess() {
|
||||
const outDir = join(__dirname, 'out/search-chat');
|
||||
const files = walk(outDir).filter(f => /\.(m?js|cjs)$/i.test(f));
|
||||
const tauriFiles = files.filter(f => {
|
||||
const content = readFileSync(f, 'utf-8');
|
||||
const outDir = join(__dirname, "out/search-chat");
|
||||
const files = walk(outDir).filter((f) => /\.(m?js|cjs)$/i.test(f));
|
||||
const tauriFiles = files.filter((f) => {
|
||||
const content = readFileSync(f, "utf-8");
|
||||
return hasTauriRefs(content);
|
||||
});
|
||||
|
||||
if (tauriFiles.length) {
|
||||
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(
|
||||
readFileSync(join(__dirname, 'package.json'), 'utf-8')
|
||||
readFileSync(join(__dirname, "package.json"), "utf-8")
|
||||
);
|
||||
|
||||
const packageJson = {
|
||||
@@ -117,57 +122,51 @@ export default defineConfig({
|
||||
types: "index.d.ts",
|
||||
dependencies: projectPackageJson.dependencies as Record<string, string>,
|
||||
peerDependencies: {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
react: "^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 =>
|
||||
dep.includes('@tauri-apps') ||
|
||||
dep.includes('tauri-plugin') ||
|
||||
noNeedDeps.includes(dep)
|
||||
const noNeedDeps = ["dotenv", "uuid", "wavesurfer.js"];
|
||||
|
||||
const tauriDeps = Object.keys(packageJson.dependencies).filter(
|
||||
(dep) =>
|
||||
dep.includes("@tauri-apps") ||
|
||||
dep.includes("tauri-plugin") ||
|
||||
noNeedDeps.includes(dep)
|
||||
);
|
||||
tauriDeps.forEach(dep => {
|
||||
tauriDeps.forEach((dep) => {
|
||||
delete packageJson.dependencies[dep];
|
||||
});
|
||||
|
||||
writeFileSync(
|
||||
join(__dirname, 'out/search-chat/package.json'),
|
||||
join(__dirname, "out/search-chat/package.json"),
|
||||
JSON.stringify(packageJson, null, 2)
|
||||
);
|
||||
|
||||
try {
|
||||
const readmePath = join(__dirname, 'src/pages/web/README.md');
|
||||
const readmeContent = readFileSync(readmePath, 'utf-8');
|
||||
const readmePath = join(__dirname, "src/pages/web/README.md");
|
||||
const readmeContent = readFileSync(readmePath, "utf-8");
|
||||
writeFileSync(
|
||||
join(__dirname, 'out/search-chat/README.md'),
|
||||
join(__dirname, "out/search-chat/README.md"),
|
||||
readmeContent
|
||||
);
|
||||
} 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
|
||||
// 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.
|
||||
try {
|
||||
const cssInPath = join(__dirname, 'src/main.css');
|
||||
const cssOutPath = join(__dirname, 'out/search-chat/index.css');
|
||||
const cssIn = readFileSync(cssInPath, 'utf-8');
|
||||
const cssInPath = join(__dirname, "src/main.css");
|
||||
const cssOutPath = join(__dirname, "out/search-chat/index.css");
|
||||
const cssIn = readFileSync(cssInPath, "utf-8");
|
||||
|
||||
const result = await 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
|
||||
const assetRegex = /url\((['"])\/assets\/([^'"\)]+)\1\)/g;
|
||||
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)) {
|
||||
console.warn(`[build:web] Asset not found: ${srcAssetPath}`);
|
||||
return `url(${quote}/assets/${file}${quote})`;
|
||||
}
|
||||
try {
|
||||
const buffer = readFileSync(srcAssetPath);
|
||||
const ext = file.split('.').pop()?.toLowerCase() ?? 'png';
|
||||
const mime = ext === 'svg'
|
||||
? 'image/svg+xml'
|
||||
: ext === 'jpg' || ext === 'jpeg'
|
||||
? 'image/jpeg'
|
||||
: 'image/png';
|
||||
const base64 = buffer.toString('base64');
|
||||
const ext = file.split(".").pop()?.toLowerCase() ?? "png";
|
||||
const mime =
|
||||
ext === "svg"
|
||||
? "image/svg+xml"
|
||||
: ext === "jpg" || ext === "jpeg"
|
||||
? "image/jpeg"
|
||||
: "image/png";
|
||||
const base64 = buffer.toString("base64");
|
||||
const dataUrl = `data:${mime};base64,${base64}`;
|
||||
return `url(${quote}${dataUrl}${quote})`;
|
||||
} catch (err) {
|
||||
@@ -207,7 +207,18 @@ export default defineConfig({
|
||||
|
||||
writeFileSync(cssOutPath, rewrittenCss);
|
||||
} 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