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/*"],