fix(sandbox): prevent css causing full app refresh (#1869)

This commit is contained in:
Sam Potts
2026-07-24 16:36:08 -07:00
committed by GitHub
parent cc76fd03f5
commit 5cb93c1701
5 changed files with 60 additions and 33 deletions
+52 -13
View File
@@ -1,4 +1,4 @@
import { globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { existsSync, globSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { resolveImports } from './resolve-css-imports.ts';
import type { BuildPlugin } from './types.ts';
@@ -6,29 +6,68 @@ import type { BuildPlugin } from './types.ts';
interface CopyCssPluginOptions {
skinsDir: string;
outDir: string;
inline?: boolean;
rebuild?: boolean;
}
export function copyCssPlugin(options: CopyCssPluginOptions): BuildPlugin {
const { skinsDir, outDir } = options;
const { skinsDir, outDir, inline = true, rebuild = true } = options;
let poll: ReturnType<typeof setInterval> | undefined;
let state = new Map<string, string>();
function getCssFiles() {
return new Set([...globSync('src/**/*.css'), ...globSync(join(skinsDir, '**/*.css'))]);
}
function getState() {
return new Map(
[...getCssFiles()].map((file) => {
const { mtimeMs, size } = statSync(file);
return [file, `${mtimeMs}:${size}`];
})
);
}
function writeCss() {
for (const file of globSync('src/**/*.css')) {
const content = readFileSync(file, 'utf-8');
const output = inline ? resolveImports(content, dirname(file), skinsDir) : content;
const outFile = join(outDir, file.replace(/^src\//, ''));
if (existsSync(outFile) && readFileSync(outFile, 'utf-8') === output) continue;
mkdirSync(dirname(outFile), { recursive: true });
writeFileSync(outFile, output);
}
}
function checkCss() {
try {
const next = getState();
if (next.size === state.size && [...next].every(([file, value]) => state.get(file) === value)) return;
writeCss();
state = next;
} catch (error) {
console.error(error);
}
}
return {
name: 'copy-css',
buildStart() {
for (const file of globSync('src/**/*.css')) {
this.addWatchFile(file);
if (!rebuild && process.argv.includes('--watch')) {
if (!poll) {
state = getState();
poll = setInterval(checkCss, 100);
}
return;
}
for (const file of globSync(join(skinsDir, '**/*.css'))) {
for (const file of getCssFiles()) {
this.addWatchFile(file);
}
},
writeBundle() {
for (const file of globSync('src/**/*.css')) {
const content = readFileSync(file, 'utf-8');
const resolved = resolveImports(content, dirname(file), skinsDir);
const outFile = join(outDir, file.replace(/^src\//, ''));
mkdirSync(dirname(outFile), { recursive: true });
writeFileSync(outFile, resolved);
}
writeBundle: writeCss,
closeWatcher() {
clearInterval(poll);
},
};
}
+1
View File
@@ -14,4 +14,5 @@ export interface BuildPlugin {
load?: (this: void, id: string) => { code: string; moduleSideEffects: boolean } | null;
buildStart?: (this: { addWatchFile: (file: string) => void }) => void;
writeBundle?: (this: void) => void;
closeWatcher?: (this: void) => void;
}
+1 -1
View File
@@ -28,7 +28,7 @@
"dev:site": "turbo run dev --filter=site...",
"dev": "turbo run dev",
"dev:packages": "turbo run dev --filter='./packages/*'",
"dev:sandbox": "turbo run dev --filter=@videojs/sandbox...",
"dev:sandbox": "turbo run build --filter=@videojs/sandbox^... && turbo run dev --filter=@videojs/sandbox... --only",
"lint": "biome check .",
"lint:fix": "biome check . --write",
"lint:fix:file": "biome check --write",
+1 -1
View File
@@ -26,7 +26,7 @@ const createConfig = (mode: PackageBuildMode): UserConfig => ({
alias: {
'@': new URL('./src', import.meta.url).pathname,
},
plugins: [copyCssPlugin({ skinsDir, outDir: `dist/${mode}` })],
plugins: [copyCssPlugin({ skinsDir, outDir: `dist/${mode}`, rebuild: false })],
});
export default defineConfig(packageBuildModes.map((mode) => createConfig(mode)));
+5 -18
View File
@@ -1,8 +1,12 @@
import { globSync } from 'node:fs';
import { resolve } from 'node:path';
import type { UserConfig } from 'tsdown';
import { defineConfig } from 'tsdown';
import { copyCssPlugin } from '../../build/plugins/copy-css-plugin.ts';
import { type PackageBuildMode, packageBuildConfig, packageBuildModes } from '../../build/tsdown.ts';
const skinsDir = resolve('src');
const entries = Object.fromEntries(
globSync('src/**/*.tailwind.ts').map((file) => {
const key = file.replace('src/', '').replace('.ts', '');
@@ -13,24 +17,7 @@ const entries = Object.fromEntries(
const createConfig = (mode: PackageBuildMode): UserConfig => ({
...packageBuildConfig(mode, 'browser'),
entry: entries,
copy: [
{
from: 'src/**/*.css',
to: `dist/${mode}`,
flatten: false,
},
],
plugins: [
{
name: 'watch-css',
buildStart() {
const cssFiles = globSync('src/**/*.css');
for (const file of cssFiles) {
this.addWatchFile(file);
}
},
},
],
plugins: [copyCssPlugin({ skinsDir, outDir: `dist/${mode}`, inline: false, rebuild: false })],
});
export default defineConfig(packageBuildModes.map((mode) => createConfig(mode)));