c41a8e2b23
## Summary Now that Twenty has fully migrated from Emotion to Linaria, the theme system has been simplified to remove unnecessary complexity that existed only to support the old runtime injection pattern. ### What changed - **Deleted** `generateThemeConstants.ts` script and the entire `generated/` directory — no more auto-generation - **Added** `theme-light.css` and `theme-dark.css`: static CSS files with 991 custom properties each, scoped under `.light` and `.dark` selectors respectively - **Moved** `themeCssVariables.ts` out of `generated/` and hand-maintain it as a static `as const` object of `var(--t-*)` references (Linaria can statically evaluate these at build time) - **Extracted** numeric constants (`MOBILE_VIEWPORT`, `ICON_SIZES`, `ICON_STROKES`) into a new `constants.ts` — CSS variables can't be used in media queries or as numeric icon size props - **Simplified** `ThemeContextProvider`: removed `ThemeCssVariableInjectorEffect` entirely; now uses a single `useLayoutEffect` to toggle `.light`/`.dark` class on `<html>` - **Added** `class="light"` to `index.html` as default to prevent FOUC before React hydration ### Why The previous setup maintained a dual system: JS theme objects (`THEME_LIGHT`/`THEME_DARK`) used at runtime, plus a generation script that produced CSS variable entry arrays, which were then injected into the DOM by `ThemeCssVariableInjectorEffect`. With Linaria, theme values only need to be CSS custom properties — the JS objects were redundant. This PR removes ~250 lines of infrastructure while keeping the same theming capabilities.
154 lines
4.2 KiB
TypeScript
154 lines
4.2 KiB
TypeScript
import react from '@vitejs/plugin-react-swc';
|
|
import wyw from '@wyw-in-js/vite';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { createWywProfilingPlugin } from 'twenty-shared/vite';
|
|
import { defineConfig } from 'vite';
|
|
import checker from 'vite-plugin-checker';
|
|
import dts, { type PluginOptions } from 'vite-plugin-dts';
|
|
import svgr from 'vite-plugin-svgr';
|
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
|
|
type Checkers = Parameters<typeof checker>[0];
|
|
|
|
import packageJson from './package.json';
|
|
|
|
const entries = Object.keys(packageJson.exports)
|
|
.filter((el) => !el.endsWith('.css'))
|
|
.map((module) => `src/${module}/index.ts`);
|
|
|
|
const entryFileNames = (chunk: any, extension: 'cjs' | 'mjs') => {
|
|
if (!chunk.isEntry) {
|
|
throw new Error(
|
|
`Should never occurs, encountered a non entry chunk ${chunk.facadeModuleId}`,
|
|
);
|
|
}
|
|
|
|
const splitFaceModuleId = chunk.facadeModuleId?.split('/');
|
|
if (splitFaceModuleId === undefined) {
|
|
throw new Error(
|
|
`Should never occurs splitFaceModuleId is undefined ${chunk.facadeModuleId}`,
|
|
);
|
|
}
|
|
|
|
const moduleDirectory = splitFaceModuleId[splitFaceModuleId?.length - 2];
|
|
if (moduleDirectory === 'src') {
|
|
return `${chunk.name}.${extension}`;
|
|
}
|
|
return `${moduleDirectory}.${extension}`;
|
|
};
|
|
|
|
export default defineConfig(({ command }) => {
|
|
const isBuildCommand = command === 'build';
|
|
|
|
const tsConfigPath = isBuildCommand
|
|
? path.resolve(__dirname, './tsconfig.lib.json')
|
|
: path.resolve(__dirname, './tsconfig.json');
|
|
|
|
const checkersConfig: Checkers = {
|
|
typescript: {
|
|
tsconfigPath: tsConfigPath,
|
|
},
|
|
};
|
|
|
|
const dtsConfig: PluginOptions = {
|
|
entryRoot: 'src',
|
|
tsconfigPath: tsConfigPath,
|
|
};
|
|
|
|
return {
|
|
resolve: {
|
|
alias: {
|
|
'@ui/': path.resolve(__dirname, 'src') + '/',
|
|
'@assets/': path.resolve(__dirname, 'src/assets') + '/',
|
|
},
|
|
},
|
|
css: {
|
|
modules: {
|
|
localsConvention: 'camelCaseOnly',
|
|
},
|
|
},
|
|
optimizeDeps: {
|
|
exclude: ['../../node_modules/.vite', '../../node_modules/.cache'],
|
|
},
|
|
root: __dirname,
|
|
cacheDir: '../../node_modules/.vite/packages/twenty-ui',
|
|
assetsInclude: ['src/**/*.svg'],
|
|
plugins: [
|
|
react(),
|
|
tsconfigPaths({
|
|
root: __dirname,
|
|
projects: ['tsconfig.json'],
|
|
}),
|
|
svgr(),
|
|
dts(dtsConfig),
|
|
checker(checkersConfig),
|
|
createWywProfilingPlugin(
|
|
wyw({
|
|
include: [path.resolve(__dirname, 'src') + '/**/*.{ts,tsx}'],
|
|
babelOptions: {
|
|
presets: ['@babel/preset-typescript', '@babel/preset-react'],
|
|
},
|
|
}),
|
|
),
|
|
{
|
|
name: 'copy-theme-css',
|
|
closeBundle() {
|
|
const themeCssFiles = ['theme-light.css', 'theme-dark.css'];
|
|
for (const file of themeCssFiles) {
|
|
fs.copyFileSync(
|
|
path.resolve(__dirname, `src/theme-constants/${file}`),
|
|
path.resolve(__dirname, `dist/${file}`),
|
|
);
|
|
}
|
|
},
|
|
},
|
|
],
|
|
build: {
|
|
cssCodeSplit: false,
|
|
minify: 'esbuild',
|
|
sourcemap: false,
|
|
emptyOutDir: false,
|
|
outDir: './dist',
|
|
reportCompressedSize: true,
|
|
commonjsOptions: {
|
|
transformMixedEsModules: true,
|
|
interopDefault: true,
|
|
defaultIsModuleExports: true,
|
|
requireReturnsDefault: 'auto',
|
|
},
|
|
lib: {
|
|
entry: ['src/index.ts', ...entries],
|
|
name: 'twenty-ui',
|
|
},
|
|
rollupOptions: {
|
|
external: Object.keys(packageJson.dependencies || {}),
|
|
output: [
|
|
{
|
|
assetFileNames: 'style.css',
|
|
globals: {
|
|
react: 'React',
|
|
'react-dom': 'ReactDOM',
|
|
},
|
|
format: 'es',
|
|
entryFileNames: (chunk) => entryFileNames(chunk, 'mjs'),
|
|
},
|
|
{
|
|
assetFileNames: 'style.css',
|
|
format: 'cjs',
|
|
globals: {
|
|
react: 'React',
|
|
'react-dom': 'ReactDOM',
|
|
},
|
|
interop: 'auto',
|
|
esModule: true,
|
|
exports: 'named',
|
|
entryFileNames: (chunk) => entryFileNames(chunk, 'cjs'),
|
|
},
|
|
],
|
|
},
|
|
},
|
|
logLevel: 'error',
|
|
};
|
|
});
|