Skip to content

Commit

Permalink
chore: improve tailwindcss
Browse files Browse the repository at this point in the history
  • Loading branch information
sorrycc committed Nov 12, 2024
1 parent 3beb69f commit 3f41d9a
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 99 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ $ pnpm preview
- `tnf dev`: Start the development server.
- `tnf generate/g <type> <name>`: Generate a new page (or component and other types in the future).
- `tnf preview`: Preview the product after building the project.
- `tnf sync`: Sync the project to the temporary directory.
- `tnf sync --mode=<mode>`: Sync the project to the temporary directory.

## API

Expand Down
2 changes: 1 addition & 1 deletion examples/tailwindcss/src/pages/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Link, Outlet, createRootRoute } from '@umijs/tnf/router';
export const Route = createRootRoute({
component: () => (
<>
<div>Hello "__root"!</div>
<div className="text-2xl font-bold">Hello "__root"!</div>
<ul>
<li>
<Link to="/">Home</Link>
Expand Down
14 changes: 3 additions & 11 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,29 @@ import chokidar from 'chokidar';
import path from 'pathe';
import type { Config } from './config';
import { FRAMEWORK_NAME } from './constants';
import { generateTailwindcss } from './fishkit/tailwindcss';
import { sync } from './sync';

export async function build({
cwd,
config,
devMakoConfig,
watch,
mode,
}: {
cwd: string;
config?: Config;
devMakoConfig?: BuildParams['config'];
watch?: boolean;
mode: 'development' | 'production';
}) {
const tmpPath = path.join(cwd, `src/.${FRAMEWORK_NAME}`);

let tailwindcssPath: string;
if (config?.tailwindcss) {
tailwindcssPath = await generateTailwindcss({
cwd,
tmpPath,
config: config?.tailwindcss,
});
}

const doSync = async () => {
await sync({
cwd,
tmpPath,
config,
tailwindcssPath,
mode,
});
};

Expand Down
8 changes: 5 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,21 @@ async function run(cwd: string) {
case 'build':
const { build } = await import('./build.js');
return build({
cwd,
config: await loadConfig({ cwd }),
cwd,
mode: 'production',
});
case 'dev':
const { dev } = await import('./dev.js');
return dev({
cwd,
config: await loadConfig({ cwd }),
cwd,
});
case 'preview':
const { preview } = await import('./preview.js');
return preview({
cwd,
config: await loadConfig({ cwd }),
cwd,
});
case 'generate':
case 'g':
Expand All @@ -58,6 +59,7 @@ async function run(cwd: string) {
cwd,
tmpPath,
config: await loadConfig({ cwd }),
mode: argv.mode || 'development',
});
default:
throw new Error(`Unknown command: ${cmd}`);
Expand Down
1 change: 1 addition & 0 deletions src/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export async function dev(opts: DevOpts) {
...opts,
config: opts.config,
devMakoConfig,
mode: 'development',
watch: true,
});
}
215 changes: 135 additions & 80 deletions src/fishkit/tailwindcss.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,134 @@
import assert from 'assert';
import { type ChildProcess, spawn } from 'child_process';
import { existsSync, writeFileSync } from 'fs';
import { mkdirSync, rmSync } from 'fs-extra';
import { createRequire } from 'module';
import { dirname, join } from 'path';
import fs from 'fs-extra';
import module from 'module';
import path from 'pathe';
import type { Config } from '../config';

const CHECK_INTERVAL = 300;
const CHECK_TIMEOUT_UNIT_SECOND = 5;
// 常量配置
const CONSTANTS = {
CHECK_INTERVAL_MS: 300,
CHECK_TIMEOUT_SECONDS: 5,
TAILWIND_VERSION: '3',
} as const;

// Tailwind 配置类型
interface TailwindConfig {
content: string[];
[key: string]: unknown;
}

// 生成 Tailwindcss 的选项接口
interface GenerateTailwindcssOpts {
cwd: string;
tmpPath: string;
config: Config['tailwindcss'];
mode: 'development' | 'production';
}

function getTailwindBinPath(opts: { cwd: string }) {
const require = createRequire(process.cwd());
const pkgPath = require.resolve('tailwindcss/package.json', {
paths: [opts.cwd],
});
const tailwindPath = require(pkgPath).bin['tailwind'];
return join(dirname(pkgPath), tailwindPath);
// 生成文件的选项接口
interface GenerateFileOpts {
binPath: string;
inputPath: string;
outputPath: string;
configPath: string;
mode: 'development' | 'production';
}

let tailwind: ChildProcess;
let tailwindProcess: ChildProcess;

export async function generateTailwindcss(opts: GenerateTailwindcssOpts) {
const cwd = opts.cwd;
const binPath = getTailwindBinPath({ cwd });
if (existsSync(join(opts.tmpPath, '../.tailwindcss'))) {
rmSync(join(opts.tmpPath, '../.tailwindcss'), { recursive: true });
/**
* 获取 Tailwind CLI 的二进制文件路径
*/
function getTailwindBinPath(opts: { cwd: string }): string {
const require = module.createRequire(process.cwd());
let pkgPath: string;

try {
pkgPath = require.resolve('tailwindcss/package.json', {
paths: [opts.cwd],
});
} catch (error) {
throw new Error('tailwindcss not found, please install it first');
}
const inputPath = join(
opts.tmpPath,
'../.tailwindcss/tailwindDirectives.css',

const pkg = require(pkgPath);
const version = pkg.version;

assert(
version.startsWith(CONSTANTS.TAILWIND_VERSION),
`tailwindcss version must be ${CONSTANTS.TAILWIND_VERSION}.x`,
);
const outputPath = join(opts.tmpPath, '../.tailwindcss/tailwind.css');
const configPath = join(opts.tmpPath, '../.tailwindcss/tailwind.config.js');
const customConfig = opts.config;
const defaultTailwindcssConfig = {

return path.join(path.dirname(pkgPath), pkg.bin.tailwind);
}

/**
* 生成 Tailwind CSS 文件
*/
export async function generateTailwindcss(
opts: GenerateTailwindcssOpts,
): Promise<string> {
const { cwd, tmpPath, config, mode } = opts;
const rootPath = path.join(tmpPath, 'tailwindcss');

// 设置文件路径
const paths = {
input: path.join(rootPath, 'tailwindDirectives.css'),
output: path.join(rootPath, 'tailwind.css'),
config: path.join(rootPath, 'tailwind.config.js'),
};

// 默认 Tailwind 配置
const defaultConfig: TailwindConfig = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx}',
'./src/components/**/*.{js,ts,jsx,tsx}',
],
};
const mergedTailwindcssConfig = {
...defaultTailwindcssConfig,
...customConfig,
};
if (!existsSync(inputPath)) {
mkdirSync(dirname(inputPath), { recursive: true });
}

writeFileSync(
inputPath,
`@tailwind base;
const mergedConfig = { ...defaultConfig, ...config };

// 确保目录存在
fs.mkdirpSync(path.dirname(paths.input));

// 写入 Tailwind 指令文件
fs.writeFileSync(
paths.input,
`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
`.trim(),
);

writeFileSync(
configPath,
`export default ${JSON.stringify(mergedTailwindcssConfig, null, 2)}`,
// 写入配置文件
fs.writeFileSync(
paths.config,
`export default ${JSON.stringify(mergedConfig, null, 2)}`,
);

await generateFile({ binPath, inputPath, outputPath, configPath });
// 生成 CSS 文件
await generateFile({
binPath: getTailwindBinPath({ cwd }),
inputPath: paths.input,
outputPath: paths.output,
configPath: paths.config,
mode,
});

return outputPath;
return paths.output;
}

async function generateFile(opts: {
binPath: string;
inputPath: string;
outputPath: string;
configPath: string;
}) {
const { binPath, inputPath, outputPath, configPath } = opts;
const isProduction = process.env.HMR === 'none';
return new Promise((resolve) => {
tailwind = spawn(
/**
* 生成 Tailwind CSS 文件并监听变化
*/
async function generateFile(opts: GenerateFileOpts): Promise<void> {
const { binPath, inputPath, outputPath, configPath, mode } = opts;
const isProduction = mode === 'production';

return new Promise((resolve, reject) => {
tailwindProcess = spawn(
binPath,
[
'-i',
Expand All @@ -88,42 +137,48 @@ async function generateFile(opts: {
outputPath,
'-c',
configPath,
isProduction ? '' : '--watch',
...(isProduction ? [] : ['--watch']),
],
{
stdio: 'inherit',
},
{ stdio: 'inherit' },
);

tailwind.on('error', (err) => {
console.error('tailwindcss service encounter an error: ' + err);
tailwindProcess.on('error', (error) => {
console.error('Tailwind CSS service encountered an error:', error);
reject(error);
});

console.log('tailwindcss service started');
tailwind.on('close', () => {
console.log('tailwindcss service closed');
resolve(void 0);
console.log('Tailwind CSS service started');

tailwindProcess.on('close', () => {
console.log('Tailwind CSS service closed');
resolve();
});

if (!isProduction) {
// wait for generatedPath to be created by interval
const interval = setInterval(() => {
if (existsSync(outputPath)) {
clearInterval(interval);
resolve(void 0);
}
}, CHECK_INTERVAL);
watchOutputFile(outputPath, resolve);
}

// throw error if not generated after 5s
const timer = setTimeout(() => {
if (!existsSync(outputPath)) {
clearInterval(timer);
console.error(
`tailwindcss generate failed after ${CHECK_TIMEOUT_UNIT_SECOND} seconds, please check your tailwind.css and tailwind.config.js`,
);
process.exit(1);
}
}, CHECK_TIMEOUT_UNIT_SECOND * 1000);
});
}

/**
* 监听输出文件的生成
*/
function watchOutputFile(outputPath: string, resolve: () => void): void {
const interval = setInterval(() => {
if (fs.existsSync(outputPath)) {
clearInterval(interval);
resolve();
}
}, CONSTANTS.CHECK_INTERVAL_MS);

// 设置超时检查
setTimeout(() => {
if (!fs.existsSync(outputPath)) {
clearInterval(interval);
throw new Error(
`Tailwind CSS generation failed after ${CONSTANTS.CHECK_TIMEOUT_SECONDS} seconds. ` +
'Please check your tailwind.css and tailwind.config.js',
);
}
}, CONSTANTS.CHECK_TIMEOUT_SECONDS * 1000);
}
Loading

0 comments on commit 3f41d9a

Please sign in to comment.