From d44a74710125cd5f77f9f5299b4923b9067fb986 Mon Sep 17 00:00:00 2001 From: mantou132 <709922234@qq.com> Date: Thu, 7 Dec 2023 11:33:09 +0800 Subject: [PATCH] [duoyun-ui] Export React component --- .eslintignore | 1 + .github/workflows/duoyun-ui-publish.yml | 1 + packages/duoyun-ui/.gitignore | 1 + packages/duoyun-ui/package.json | 3 + .../duoyun-ui/src/elements/cascader-picker.ts | 8 +- .../duoyun-ui/src/elements/color-picker.ts | 4 +- .../duoyun-ui/src/elements/date-picker.ts | 4 +- .../src/elements/date-range-picker.ts | 4 +- .../duoyun-ui/src/elements/file-picker.ts | 4 +- packages/duoyun-ui/src/elements/picker.ts | 4 +- packages/duoyun-ui/src/elements/select.ts | 12 +- packages/duoyun-ui/src/elements/tabs.ts | 4 +- packages/duoyun-ui/src/elements/text-mask.ts | 2 +- .../duoyun-ui/src/elements/time-picker.ts | 4 +- packages/gem-port/.gitignore | 1 + packages/gem-port/package.json | 41 ++++++ packages/gem-port/src/index.ts | 125 ++++++++++++++++++ packages/gem-port/tsconfig.json | 8 ++ packages/gem/src/lib/utils.ts | 2 +- 19 files changed, 213 insertions(+), 20 deletions(-) create mode 100644 packages/gem-port/.gitignore create mode 100644 packages/gem-port/package.json create mode 100644 packages/gem-port/src/index.ts create mode 100644 packages/gem-port/tsconfig.json diff --git a/.eslintignore b/.eslintignore index 274f10de..48cee302 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ packages/*/dist +packages/*/react packages/*/bin packages/*/coverage packages/*/extension diff --git a/.github/workflows/duoyun-ui-publish.yml b/.github/workflows/duoyun-ui-publish.yml index d3578170..524642f4 100644 --- a/.github/workflows/duoyun-ui-publish.yml +++ b/.github/workflows/duoyun-ui-publish.yml @@ -19,6 +19,7 @@ jobs: node-version: 18.x - run: yarn + - run: yarn --cwd packages/duoyun-ui build:react - uses: JS-DevTools/npm-publish@v1 with: diff --git a/packages/duoyun-ui/.gitignore b/packages/duoyun-ui/.gitignore index bd489b71..602dbf5e 100644 --- a/packages/duoyun-ui/.gitignore +++ b/packages/duoyun-ui/.gitignore @@ -3,6 +3,7 @@ # Compiler output /lib/ /elements/ +/react/ /locales/ /coverage/ diff --git a/packages/duoyun-ui/package.json b/packages/duoyun-ui/package.json index 3ed12f8f..4144a6c1 100644 --- a/packages/duoyun-ui/package.json +++ b/packages/duoyun-ui/package.json @@ -4,11 +4,13 @@ "description": "WebComponts UI", "exports": { "./elements/*": "./elements/*.js", + "./react/*": "./react/*.js", "./lib/*": "./lib/*.js", "./locales/*": "./locales/*.js" }, "files": [ "/elements/", + "/react/", "/lib/", "/locales/" ], @@ -16,6 +18,7 @@ "docs": "node scripts/hack-gbp-example && gem-book docs --plugin node_modules/gbp-example-hack", "docs:remote": "gem-book docs --plugin example", "build:docs": "gem-book docs --build --plugin example", + "build:react": "gem-port src/elements", "clean": "node -e \"fs.readdirSync('src').map(dir => require('rimraf').sync(dir))\"", "build": "yarn clean && tsc -p tsconfig.build.json", "start": "yarn build --watch", diff --git a/packages/duoyun-ui/src/elements/cascader-picker.ts b/packages/duoyun-ui/src/elements/cascader-picker.ts index bbbbe3fe..9e84d1b8 100644 --- a/packages/duoyun-ui/src/elements/cascader-picker.ts +++ b/packages/duoyun-ui/src/elements/cascader-picker.ts @@ -133,12 +133,14 @@ export class DuoyunCascaderPickElement extends GemElement implements BasePickerE ${isEmpty ? this.placeholder : this.multiple - ? this.#renderMultipleValue(this.value as (string | number)[][]) - : this.#renderValue(this.value as (string | number)[])} + ? this.#renderMultipleValue(this.value as (string | number)[][]) + : this.#renderValue(this.value as (string | number)[])} `; }; - showPicker = () => this.#onOpen(); + showPicker() { + this.#onOpen(); + } } diff --git a/packages/duoyun-ui/src/elements/color-picker.ts b/packages/duoyun-ui/src/elements/color-picker.ts index 92996297..66909990 100644 --- a/packages/duoyun-ui/src/elements/color-picker.ts +++ b/packages/duoyun-ui/src/elements/color-picker.ts @@ -89,7 +89,7 @@ export class DuoyunColorPickElement extends GemElement implements BasePickerElem `; }; - showPicker = () => { + showPicker() { this.popoverRef.element?.click(); - }; + } } diff --git a/packages/duoyun-ui/src/elements/date-picker.ts b/packages/duoyun-ui/src/elements/date-picker.ts index 81ece605..d234cd78 100644 --- a/packages/duoyun-ui/src/elements/date-picker.ts +++ b/packages/duoyun-ui/src/elements/date-picker.ts @@ -174,5 +174,7 @@ export class DuoyunDatePickElement extends GemElement implements BasePickerEleme `; }; - showPicker = () => this.#onOpen(); + showPicker() { + this.#onOpen(); + } } diff --git a/packages/duoyun-ui/src/elements/date-range-picker.ts b/packages/duoyun-ui/src/elements/date-range-picker.ts index 2c7283c0..13e4be89 100644 --- a/packages/duoyun-ui/src/elements/date-range-picker.ts +++ b/packages/duoyun-ui/src/elements/date-range-picker.ts @@ -177,5 +177,7 @@ export class DuoyunDateRangePickElement extends GemElement implements BasePicker `; }; - showPicker = () => this.#onOpen(); + showPicker() { + this.#onOpen(); + } } diff --git a/packages/duoyun-ui/src/elements/file-picker.ts b/packages/duoyun-ui/src/elements/file-picker.ts index 22e9977f..975eea45 100644 --- a/packages/duoyun-ui/src/elements/file-picker.ts +++ b/packages/duoyun-ui/src/elements/file-picker.ts @@ -240,7 +240,7 @@ export class DuoyunFilePickElement extends GemElement implements BasePickerEleme `; }; - showPicker = () => { + showPicker() { this.inputRef.element!.click(); - }; + } } diff --git a/packages/duoyun-ui/src/elements/picker.ts b/packages/duoyun-ui/src/elements/picker.ts index 83772c66..0a469549 100644 --- a/packages/duoyun-ui/src/elements/picker.ts +++ b/packages/duoyun-ui/src/elements/picker.ts @@ -178,5 +178,7 @@ export class DuoyunPickElement extends GemElement implements BasePickerElement { `; }; - showPicker = () => this.#onOpen(); + showPicker() { + this.#onOpen(); + } } diff --git a/packages/duoyun-ui/src/elements/select.ts b/packages/duoyun-ui/src/elements/select.ts index 36f6d248..0e75e3ac 100644 --- a/packages/duoyun-ui/src/elements/select.ts +++ b/packages/duoyun-ui/src/elements/select.ts @@ -166,8 +166,8 @@ export class DuoyunSelectElement extends GemElement implements BasePicker return this.multiple && Array.isArray(this.value) ? this.value : isNotNullish(this.value) - ? [this.value] - : undefined; + ? [this.value] + : undefined; } constructor() { @@ -397,8 +397,8 @@ export class DuoyunSelectElement extends GemElement implements BasePicker ` : this.renderTag - ? this.renderTag(this.#valueOptions![index]) - : label, + ? this.renderTag(this.#valueOptions![index]) + : label, )} ` : this.#valueOptions![0].label} @@ -447,5 +447,7 @@ export class DuoyunSelectElement extends GemElement implements BasePicker `; }; - showPicker = () => this.#open(); + showPicker() { + this.#open(); + } } diff --git a/packages/duoyun-ui/src/elements/tabs.ts b/packages/duoyun-ui/src/elements/tabs.ts index 0ab337b4..fe16fe7b 100644 --- a/packages/duoyun-ui/src/elements/tabs.ts +++ b/packages/duoyun-ui/src/elements/tabs.ts @@ -121,7 +121,7 @@ export class DuoyunTabsElement extends GemElement { return html`
${this.data.map(({ value, tab, icon, getContent }, index) => { - const isCurrent = (value ?? index) === this.value; + const isCurrent: boolean = (value ?? index) === this.value; if (isCurrent) currentContent = getContent?.() || ''; return html`
this.#onOpen(); + showPicker() { + this.#onOpen(); + } } diff --git a/packages/gem-port/.gitignore b/packages/gem-port/.gitignore new file mode 100644 index 00000000..65776c32 --- /dev/null +++ b/packages/gem-port/.gitignore @@ -0,0 +1 @@ +/bin/ \ No newline at end of file diff --git a/packages/gem-port/package.json b/packages/gem-port/package.json new file mode 100644 index 00000000..9a4b3202 --- /dev/null +++ b/packages/gem-port/package.json @@ -0,0 +1,41 @@ +{ + "name": "gem-port", + "version": "0.0.1", + "description": "Export React component", + "keywords": [ + "gem", + "react", + "generator" + ], + "bin": { + "gem-port": "bin/index.js" + }, + "files": [ + "/bin/" + ], + "scripts": { + "build": "esbuild ./src/index.ts --outdir=./bin --bundle --platform=node --minify --sourcemap --external:typescript", + "start": "yarn build --watch", + "prepublishOnly": "yarn build" + }, + "dependencies": { + "@gemjs/config": "^1.6.11", + "commander": "^7.2.0", + "gem-analyzer": "^1.7.0", + "ts-morph": "^13.0.0", + "typescript": "^4.5.0" + }, + "devDependencies": { + }, + "author": "mantou132", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/mantou132/gem.git", + "directory": "packages/gem-port" + }, + "bugs": { + "url": "https://github.com/mantou132/gem/issues" + }, + "homepage": "https://github.com/mantou132/gem#readme" +} diff --git a/packages/gem-port/src/index.ts b/packages/gem-port/src/index.ts new file mode 100644 index 00000000..5b4e6302 --- /dev/null +++ b/packages/gem-port/src/index.ts @@ -0,0 +1,125 @@ +#!/usr/bin/env node + +import path from 'path'; +import { readdirSync, readFileSync, statSync } from 'fs'; + +import { getElements } from 'gem-analyzer'; +import { Project } from 'ts-morph'; +import * as ts from 'typescript'; +import program from 'commander'; + +import { name, description, version } from '../package.json'; + +const cliOptions = { + outDir: './', +}; + +function createReactSourceFile(elementFilePath: string, outDir: string) { + const content = readFileSync(elementFilePath, { encoding: 'utf-8' }); + const project = new Project({ useInMemoryFileSystem: true }); + const file = project.createSourceFile(elementFilePath, content); + const basename = path.basename(elementFilePath, path.extname(elementFilePath)); + // FIXME + const relativePath = path.relative(path.resolve(outDir), path.dirname(elementFilePath)).replace('src/', ''); + return Object.fromEntries( + getElements(file).map(({ name: tag, constructorName, properties, methods, events }) => { + const componentName = constructorName.replace('Element', ''); + const componentPropsName = `${componentName}Props`; + const componentMethodsName = `${componentName}Methods`; + return [ + componentName + '.tsx', + ` + import React, { ForwardRefExoticComponent, HTMLAttributes, RefAttributes, forwardRef, useImperativeHandle, useRef } from 'react'; + import { TemplateResult } from '@mantou/gem/lib/element'; + import { ${constructorName} } from '${relativePath}/${basename}'; + export { ${constructorName} }; + + export type ${componentPropsName} = HTMLAttributes & RefAttributes<${constructorName}> & { + ${properties + .map(({ name, attribute, reactive }) => + !reactive ? '' : [name, attribute ? 'string' : 'any'].join('?:'), + ) + .join(';')} + ${events.map((event) => [`on${event}`, `(arg: CustomEvent) => any`].join('?:')).join(';')} + }; + + export type ${componentMethodsName} = { + ${methods.map(({ name }) => [name, `typeof ${constructorName}.prototype.${name}`].join(': ')).join(';')} + } + + declare global { + namespace JSX { + interface IntrinsicElements { + '${tag}': ${componentPropsName}; + } + } + } + + const ${componentName}: ForwardRefExoticComponent & RefAttributes<${componentMethodsName}>> = forwardRef<${componentMethodsName}, ${componentPropsName}>(function (props, ref): JSX.Element { + const elementRef = useRef<${constructorName}>(null); + useImperativeHandle(ref, () => { + return { + ${methods + .map( + ({ name }) => ` + ${name}(...args) { + elementRef.current?.${name}(...args) + } + `, + ) + .join(',')} + }; + }, []); + return <${tag} ref={elementRef} {...props}>; + }) + + export default ${componentName}; + `, + ]; + }), + ); +} + +function createSourceFiles(elementsDir: string, outDir: string) { + const fileSystem: Record = {}; + readdirSync(elementsDir).forEach((filename) => { + const elementFilePath = path.resolve(elementsDir, filename); + if (statSync(elementFilePath).isFile()) { + // if (!elementFilePath.includes('color-pick') && !elementFilePath.includes('card')) return; + Object.assign(fileSystem, createReactSourceFile(elementFilePath, outDir)); + } + }); + return fileSystem; +} + +function compile(elementsDir: string): void { + const outDir = path.resolve(cliOptions.outDir, 'react'); + const options: ts.CompilerOptions = { + jsx: ts.JsxEmit.React, + target: ts.ScriptTarget.ES2020, + declaration: true, + outDir, + }; + const fileSystem = createSourceFiles(elementsDir, outDir); + const host = ts.createCompilerHost(options); + const originReadFile = host.readFile; + host.readFile = (filename: string) => { + if (filename in fileSystem) return fileSystem[filename]; + return originReadFile(filename); + }; + const program = ts.createProgram(Object.keys(fileSystem), options, host); + program.emit(); +} + +program + .name(name) + .description(description) + .version(version, '-v, --version') + .option('-o, --outdir ', `specify out dir`, (outdir: string) => (cliOptions.outDir = outdir), cliOptions.outDir) + .arguments('') + .action((dir: string) => { + compile(dir); + process.exit(0); + }); + +program.parse(process.argv).outputHelp(); diff --git a/packages/gem-port/tsconfig.json b/packages/gem-port/tsconfig.json new file mode 100644 index 00000000..12e6e5d3 --- /dev/null +++ b/packages/gem-port/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@gemjs/config/tsconfig", + "compilerOptions": { + "module": "commonjs" + }, + "include": ["src"], + "exclude": [] +} diff --git a/packages/gem/src/lib/utils.ts b/packages/gem/src/lib/utils.ts index 2bef5ab3..9a1369b6 100644 --- a/packages/gem/src/lib/utils.ts +++ b/packages/gem/src/lib/utils.ts @@ -245,7 +245,7 @@ export interface StyledKeyValuePair { // https://bugzilla.mozilla.org/show_bug.cgi?id=1520690 // https://bugs.webkit.org/show_bug.cgi?id=228684 export let useNativeCSSStyleSheet = true; -let CSSStyleSheet = window.CSSStyleSheet; +let CSSStyleSheet = globalThis.CSSStyleSheet; try { new (CSSStyleSheet as any)(); } catch {