diff --git a/.fatherrc.js b/.fatherrc.js index 4ddbafd..16a4ddf 100644 --- a/.fatherrc.js +++ b/.fatherrc.js @@ -2,4 +2,5 @@ import { defineConfig } from 'father'; export default defineConfig({ plugins: ['@rc-component/father-plugin'], -}); \ No newline at end of file + esm: { ignores: ['src/cli/**'] } +}); diff --git a/README.md b/README.md index 6c56fe9..fa6f666 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ npm install @ant-design/static-style-extract ## Usage ```tsx | pure -import extractStyle from `@ant-design/static-style-extract`; +import { extractStyle } from `@ant-design/static-style-extract`; const cssText = extractStyle(); // :where(.css-bAMboOo).ant-btn ... @@ -41,7 +41,7 @@ const cssText = extractStyle(); // :where(.css-bAMboOo).ant-btn ... use with custom theme ```tsx | pure -import extractStyle from `@ant-design/static-style-extract`; +import { extractStyle } from `@ant-design/static-style-extract`; const cssText = extractStyle(); // :where(.css-bAMboOo).ant-btn ... @@ -52,6 +52,57 @@ const cssText = extractStyle((node) => ( )); ``` +use command line + +```bash +npx @ant-design/static-style-extract@latest -i your-theme.tsx +``` + +
+ your-theme.tsx example + +```tsx | pure +import * as React from 'react'; +import { ConfigProvider } from 'antd'; + +const testGreenColor = '#008000'; +const testRedColor = '#ff0000'; + +// Not a React component (Pure function) +export default (node) => ( + <> + + {node} + + + + {node} + + + +) +``` + +
+ ## Example http://localhost:8000 diff --git a/bin/index.js b/bin/index.js new file mode 100644 index 0000000..5288390 --- /dev/null +++ b/bin/index.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../lib/cli/index.js'); diff --git a/docs/examples/_example.tsx b/docs/examples/_example.tsx new file mode 100644 index 0000000..d1bf350 --- /dev/null +++ b/docs/examples/_example.tsx @@ -0,0 +1,41 @@ +/** + * coped from https://ant.design/docs/react/server-side-rendering-cn + * > npx @ant-design/static-style-extract@latest -i ./docs/examples/_example.tsx + */ +import * as React from 'react'; +import { ConfigProvider } from 'antd'; + +const testGreenColor = '#008000'; +const testRedColor = '#ff0000'; + +// Not a React component (Pure function) +export default (node) => ( + <> + + {node} + + + + {node} + + + +) \ No newline at end of file diff --git a/package.json b/package.json index 9ad7a48..de33751 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,18 @@ "url": "https://github.com/ant-design/static-style-extract/issues" }, "files": [ + "bin", "es", "lib" ], "license": "MIT", "main": "./lib/index", "module": "./es/index", + "bin": "./bin/index.js", "scripts": { "start": "dumi dev", "build": "dumi build", + "dev": "father dev", "compile": "father build", "prepublishOnly": "npm run compile && np --yolo --no-publish", "lint": "eslint src/ docs/examples/ --ext .tsx,.ts,.jsx,.js", @@ -46,14 +49,19 @@ "cross-env": "^7.0.1", "dumi": "^2.1.0", "eslint": "^7.0.0", + "execa": "5", "father": "^4.0.0", "less": "^3.10.3", + "nanoid": "3", "np": "^6.2.0", + "picocolors": "^1.0.0", "rc-test": "^7.0.13", "react": "^18.0.0", "react-dom": "^18.0.0", "regenerator-runtime": "^0.13.7", - "typescript": "^4.0.0" + "tsx": "^4.6.2", + "typescript": "^4.0.0", + "yargs": "^17.7.2" }, "dependencies": { "@ant-design/cssinjs": "^1.8.1", diff --git a/src/cli/constants.ts b/src/cli/constants.ts new file mode 100644 index 0000000..01a1d2f --- /dev/null +++ b/src/cli/constants.ts @@ -0,0 +1,18 @@ +import c from 'picocolors' +import pkg from '../../package.json' + +export const ARROW = c.cyan('→') +export const CHECK = c.green('✔') +export const CROSS = c.red('✘') +export const WARN = c.yellow('ℹ') +export const PREFIX = 'antd' + +export const cssinjsVersion = pkg.dependencies['@ant-design/cssinjs']; +export const version = pkg.version; +export const name = pkg.name; + +export const example = ` +import * as React from 'react'; +import { ConfigProvider } from 'antd'; +export default (node) => React.createElement(ConfigProvider, null, node); +`.trim(); diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..eca9e5e --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,55 @@ +import process from 'process' +import c from 'picocolors' +import { hideBin } from 'yargs/helpers' +import yargs from 'yargs' +import { CROSS, version, name } from './constants' +import { run } from './run' + +function header() { + console.log(`\n${c.green(`${name} `)}${c.dim(`v${version}`)}`) +} + +const instance = yargs(hideBin(process.argv)) + .scriptName(name) + .usage('') + .command( + '*', + 'Generate static css', + args => args + .option('output', { + alias: 'o', + description: 'Output file path', + type: 'string', + default: 'antd.min.css' + }) + .option('input', { + alias: 'i', + description: 'Input file path', + type: 'string' + }) + .option('overwrite', { + alias: 'w', + description: 'Overwrite existing files', + type: 'boolean' + }) + .help(), + async (args) => { + header() + console.log() + try { + await run(args) + } + catch (error) { + console.error(c.inverse(c.red(' Failed to generate '))) + console.error(c.red(`${CROSS} ${String(error)}`)) + process.exit(1) + } + }, + ) + .showHelpOnFail(false) + .alias('h', 'help') + .version('version', version) + .alias('v', 'version') + +// eslint-disable-next-line @typescript-eslint/no-unused-expressions +instance.help().argv \ No newline at end of file diff --git a/src/cli/run.ts b/src/cli/run.ts new file mode 100644 index 0000000..15650e5 --- /dev/null +++ b/src/cli/run.ts @@ -0,0 +1,76 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import c from 'picocolors' +import { CHECK, WARN, PREFIX } from './constants' +import { nanoid } from 'nanoid' +import execa from 'execa'; + +export interface CliOptions { + /** + * @default `antd.min.css` + */ + output?: string; + input?: string; + overwrite?: boolean; +} + +const tsxPath = require.resolve('tsx/cli'); +const coreFilePath = path.join(__dirname, '..'); + +export async function run(options: CliOptions = {}) { + const { output = "antd.min.css", input = '', overwrite } = options; + const OVERWRITE = !!process.env.ALLAY_OVERWRITE /** Can be used for CI */ || overwrite; + + const cwd = process.cwd() + + const outputFilePath = path.join(cwd, output); + const inputFilePath = path.join(cwd, input); + + const userInputFileExits = input.length > 0 && fs.existsSync(inputFilePath) + + if (input.length > 0 && !userInputFileExits) { + console.log(c.yellow(`${WARN} ${input.length ? c.bold(input) : 'input'} is not exists.`)); + } + + if (fs.existsSync(outputFilePath) && !OVERWRITE) { + throw new Error(`${output} is already exists.`); + } + + const outputDir = path.dirname(outputFilePath); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + // ====== Generate temp file ====== + const id = nanoid().replaceAll('-', '_'); + const fileExt = userInputFileExits ? path.extname(inputFilePath) : '.js'; + const internalFileName = `.temp_${PREFIX}_${id}${fileExt}`, + internalVariable = `${PREFIX}_${id}`; + let internalFileContent = ''; + + if (userInputFileExits) { + internalFileContent += `import ${internalVariable} from ${JSON.stringify(inputFilePath)};\n` + } else { + internalFileContent += `const ${internalVariable} = void 0;\n` + } + + internalFileContent += ` +import { extractStyle } from ${JSON.stringify(coreFilePath)}; +import fs from 'fs'; +fs.writeFileSync(${JSON.stringify(outputFilePath)}, extractStyle(${internalVariable})); +`.trim(); + + const tmpFilePath = path.join(os.tmpdir(), internalFileName); + const symlinkPath = path.join(path.dirname(inputFilePath), internalFileName); + fs.writeFileSync(tmpFilePath, internalFileContent, { encoding: 'utf-8', mode: 0o777 }); + fs.symlinkSync(tmpFilePath, symlinkPath, 'file'); + + execa.node(tsxPath, [symlinkPath]) + .then(() => { + console.log(c.green(`${CHECK} ${c.bold(output)} is generated.`)); + }) + .finally(() => { + fs.unlinkSync(symlinkPath); + }) +} diff --git a/tsconfig.json b/tsconfig.json index 9a34f39..1936eb2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, "paths": { "@/*": ["src/*"], "@@/*": [".dumi/tmp/*"],