mirror of https://github.com/alibaba/ice.git
				
				
				
			Feat/plugin rax compat (#262)
* feat: support inlineStyle option for plugin rax compat * fix: update pnpm lock yaml * refactor: rax-compat create-element and plugin rule * chore: remove some lint problem * chore: update pnpm lock * chore: update pnpm * feat: add warn for inline style
This commit is contained in:
		
							parent
							
								
									e42a9ba67a
								
							
						
					
					
						commit
						59aa271de1
					
				|  | @ -1,15 +1,22 @@ | ||||||
| # @ice/plugin-rax-compat | # @ice/plugin-rax-compat | ||||||
| 
 | 
 | ||||||
| ice plugin for migrate `rax-app` project to `ice`. | ICE plugin for migrating `rax-app` project into `ICE`. | ||||||
| 
 | 
 | ||||||
| ## Usage | ## Usage | ||||||
| 
 | 
 | ||||||
| add plugin in `ice.config.ts`: | add plugin in `ice.config.ts`: | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| import raxCompat from '@ice/plugin-rax-compat'; | import compatRax from '@ice/plugin-rax-compat'; | ||||||
| 
 | 
 | ||||||
| export default { | export default defineConfig({ | ||||||
|   plugins: [raxCompat()], |   plugins: [compatRax(options)], | ||||||
| } | }); | ||||||
| ``` | ``` | ||||||
|  | 
 | ||||||
|  | ## Options | ||||||
|  | 
 | ||||||
|  | - inlineStyle:  | ||||||
|  |   - Enable stylesheet loader to import css file. | ||||||
|  |   - default to `false` | ||||||
|  |    | ||||||
|  |  | ||||||
|  | @ -17,10 +17,13 @@ | ||||||
|     "!esm/**/*.map" |     "!esm/**/*.map" | ||||||
|   ], |   ], | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "rax-compat": "^0.1.0" |     "consola": "^2.15.3", | ||||||
|  |     "rax-compat": "^0.1.0", | ||||||
|  |     "stylesheet-loader": "^0.9.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@ice/types": "^1.0.0" |     "@ice/types": "^1.0.0", | ||||||
|  |     "@types/webpack": "^5.28.0" | ||||||
|   }, |   }, | ||||||
|   "repository": { |   "repository": { | ||||||
|     "type": "http", |     "type": "http", | ||||||
|  |  | ||||||
|  | @ -1,26 +1,71 @@ | ||||||
| import { createRequire } from 'module'; | import { createRequire } from 'module'; | ||||||
| import type { Plugin } from '@ice/types'; | import type { Plugin } from '@ice/types'; | ||||||
|  | import type { RuleSetRule } from 'webpack'; | ||||||
|  | import consola from 'consola'; | ||||||
| 
 | 
 | ||||||
| const require = createRequire(import.meta.url); | const require = createRequire(import.meta.url); | ||||||
| 
 | 
 | ||||||
| const plugin: Plugin = ({ onGetConfig }) => { | const alias = { | ||||||
|   onGetConfig((config) => { |   // Add rax compat packages.
 | ||||||
|     Object.assign(config.alias, { |   rax: require.resolve('rax-compat'), | ||||||
|       // Add rax compat packages.
 |   'rax-children': require.resolve('rax-compat/children'), | ||||||
|       rax: require.resolve('rax-compat'), |   'rax-clone-element': require.resolve('rax-compat/clone-element'), | ||||||
|       'rax-children': require.resolve('rax-compat/children'), |   'rax-create-class': require.resolve('rax-compat/create-class'), | ||||||
|       'rax-clone-element': require.resolve('rax-compat/clone-element'), |   'rax-create-factory': require.resolve('rax-compat/create-factory'), | ||||||
|       'rax-create-class': require.resolve('rax-compat/create-class'), |   'rax-create-portal': require.resolve('rax-compat/create-portal'), | ||||||
|       'rax-create-factory': require.resolve('rax-compat/create-factory'), |   'rax-find-dom-node': require.resolve('rax-compat/find-dom-node'), | ||||||
|       'rax-create-portal': require.resolve('rax-compat/create-portal'), |   'rax-is-valid-element': require.resolve('rax-compat/is-valid-element'), | ||||||
|       'rax-find-dom-node': require.resolve('rax-compat/find-dom-node'), |   'rax-unmount-component-at-node': require.resolve('rax-compat/unmount-component-at-node'), | ||||||
|       'rax-is-valid-element': require.resolve('rax-compat/is-valid-element'), |  | ||||||
|       'rax-unmount-component-at-node': require.resolve('rax-compat/unmount-component-at-node'), |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default () => ({ | const ruleSetStylesheet = { | ||||||
|  |   test: /\.css$/i, | ||||||
|  |   use: [ | ||||||
|  |     { | ||||||
|  |       loader: require.resolve('stylesheet-loader'), | ||||||
|  |       options: {}, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function getPlugin(options: CompatRaxOptions): Plugin { | ||||||
|  |   return ({ onGetConfig }) => { | ||||||
|  |     onGetConfig((config) => { | ||||||
|  |       Object.assign(config.alias, alias); | ||||||
|  |       if (options.inlineStyle) { | ||||||
|  |         consola.warn('[WARN] Enabling inline style is not recommended.'); | ||||||
|  |         consola.warn('       It is recommended to use CSS modules (as default). Only allow old projects to migrate and use.'); | ||||||
|  |         config.configureWebpack ??= []; | ||||||
|  |         config.configureWebpack.unshift((config) => { | ||||||
|  |           const { rules } = config.module || {}; | ||||||
|  |           if (Array.isArray(rules)) { | ||||||
|  |             for (let i = 0, l = rules.length; i < l; i++) { | ||||||
|  |               const rule: RuleSetRule | any = rules[i]; | ||||||
|  |               // Find the css rule, that default to CSS Modules.
 | ||||||
|  |               if (rule.test && rule.test.source.indexOf('.css') > -1) { | ||||||
|  |                 rule.test = /\.module\.css$/i; | ||||||
|  |                 rules[i] = { | ||||||
|  |                   test: /\.css$/i, | ||||||
|  |                   oneOf: [ | ||||||
|  |                     rule, | ||||||
|  |                     ruleSetStylesheet, | ||||||
|  |                   ], | ||||||
|  |                 }; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           return config; | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface CompatRaxOptions { | ||||||
|  |   inlineStyle?: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default (options: CompatRaxOptions | void) => ({ | ||||||
|   name: '@ice/plugin-rax-compat', |   name: '@ice/plugin-rax-compat', | ||||||
|   plugin, |   plugin: getPlugin(options || {}), | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -5,9 +5,9 @@ import type { | ||||||
|   ReactNode, |   ReactNode, | ||||||
|   RefObject, |   RefObject, | ||||||
| } from 'react'; | } from 'react'; | ||||||
| import { createElement as _createElement, useEffect, useRef, forwardRef } from 'react'; | import { createElement as _createElement, useEffect, forwardRef } from 'react'; | ||||||
| import { setupAppear } from 'appear-polyfill'; | import { setupAppear } from 'appear-polyfill'; | ||||||
| import { cached } from 'style-unit'; | import { cached, convertUnit } from 'style-unit'; | ||||||
| import { isFunction, isObject, isNumber } from './type'; | import { isFunction, isObject, isNumber } from './type'; | ||||||
| 
 | 
 | ||||||
| let appearSetup = false; | let appearSetup = false; | ||||||
|  | @ -18,8 +18,6 @@ function setupAppearOnce() { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const hasOwn = {}.hasOwnProperty; |  | ||||||
| 
 |  | ||||||
| // https://github.com/alibaba/rax/blob/master/packages/driver-dom/src/index.js
 | // https://github.com/alibaba/rax/blob/master/packages/driver-dom/src/index.js
 | ||||||
| // opacity -> opa
 | // opacity -> opa
 | ||||||
| // fontWeight -> ntw
 | // fontWeight -> ntw
 | ||||||
|  | @ -55,19 +53,23 @@ export function createElement<P extends { | ||||||
|   type: FunctionComponent<P>, |   type: FunctionComponent<P>, | ||||||
|   props?: Attributes & P | null, |   props?: Attributes & P | null, | ||||||
|   ...children: ReactNode[]): ReactElement { |   ...children: ReactNode[]): ReactElement { | ||||||
|   const { children: propsChildren, onAppear, onDisappear } = props || {}; |  | ||||||
|   const rest = Object.assign({}, props); |   const rest = Object.assign({}, props); | ||||||
|  |   const { children: propsChildren, onAppear, onDisappear } = rest; | ||||||
|   delete rest.children; |   delete rest.children; | ||||||
|   delete rest.onAppear; |   delete rest.onAppear; | ||||||
|   delete rest.onDisappear; |   delete rest.onDisappear; | ||||||
| 
 | 
 | ||||||
|   // Compat for style unit.
 |   // Compat for style unit.
 | ||||||
|   rest.style = compatStyle(rest.style); |   const compatStyleProps = compatStyle(rest.style); | ||||||
|  |   if (compatStyleProps) { | ||||||
|  |     rest.style = compatStyleProps; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   rest.ref = props.ref || useRef(null); |   // Create backend element.
 | ||||||
|   const args = [type, rest as Attributes & P | null, propsChildren]; |   const args = [type, rest, propsChildren]; | ||||||
|   let element: any = _createElement.apply(null, args.concat(children)); |   let element: any = _createElement.apply(null, args.concat(children)); | ||||||
|   // Polyfill onAppear and onDisappear.
 | 
 | ||||||
|  |   // Polyfill for appear and disappear event.
 | ||||||
|   if (isFunction(onAppear) || isFunction(onDisappear)) { |   if (isFunction(onAppear) || isFunction(onDisappear)) { | ||||||
|     setupAppearOnce(); |     setupAppearOnce(); | ||||||
|     element = _createElement(forwardRef(AppearOrDisappear), { |     element = _createElement(forwardRef(AppearOrDisappear), { | ||||||
|  | @ -82,19 +84,24 @@ export function createElement<P extends { | ||||||
| 
 | 
 | ||||||
| const isDimensionalProp = cached((prop: string) => !NON_DIMENSIONAL_REG.test(prop)); | const isDimensionalProp = cached((prop: string) => !NON_DIMENSIONAL_REG.test(prop)); | ||||||
| 
 | 
 | ||||||
| // Convert numeric value into rpx.
 | // Convert unit as driver-dom does.
 | ||||||
| // eg. width: 2px ->
 | // https://github.com/alibaba/rax/blob/master/packages/driver-dom/src/index.js#L346
 | ||||||
| function compatStyle(style?: object): any { | function compatStyle<S = object>(style?: S): S | void { | ||||||
|   if (isObject(style)) { |   if (isObject(style)) { | ||||||
|     const result = {}; |     // Do not modify the original style object, copy results to another plain object.
 | ||||||
|  |     const result = Object.create(null); | ||||||
|     for (let key in style) { |     for (let key in style) { | ||||||
|       if (hasOwn.call(style, key)) { |       const value = style[key]; | ||||||
|         // @ts-ignore
 |       if (isNumber(value) && isDimensionalProp(key)) { | ||||||
|         if (isNumber(style[key]) && isDimensionalProp(key)) result[key] = `${style[key]}rpx`; |         // Transform rpx to vw.
 | ||||||
|  |         result[key] = convertUnit(`${value}rpx`); | ||||||
|  |       } else { | ||||||
|  |         result[key] = convertUnit(value); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
|  |   return style; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Appear HOC Component.
 | // Appear HOC Component.
 | ||||||
|  | @ -106,6 +113,7 @@ function AppearOrDisappear(props: any, ref: RefObject<EventTarget>) { | ||||||
| 
 | 
 | ||||||
|   function listen(eventName: string, handler: EventListenerOrEventListenerObject) { |   function listen(eventName: string, handler: EventListenerOrEventListenerObject) { | ||||||
|     if (isFunction(handler) && ref != null) { |     if (isFunction(handler) && ref != null) { | ||||||
|  |       // eslint-disable-next-line react-hooks/rules-of-hooks
 | ||||||
|       useEffect(() => { |       useEffect(() => { | ||||||
|         const { current } = ref; |         const { current } = ref; | ||||||
|         if (current != null) { |         if (current != null) { | ||||||
|  |  | ||||||
							
								
								
									
										3875
									
								
								pnpm-lock.yaml
								
								
								
								
							
							
						
						
									
										3875
									
								
								pnpm-lock.yaml
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -38,11 +38,12 @@ async function checkVersionExists(pkg: string, version: string, distTag: string) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getVersionPrefix(version): string { | export function getVersionPrefix(version): string { | ||||||
|   return isNaN(version[0]) ? version[0] : ''; |   return Number.isNaN(version[0]) ? version[0] : ''; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getPackageInfos(distTag = ''): Promise<IPackageInfo[]> { | export async function getPackageInfos(distTag = ''): Promise<IPackageInfo[]> { | ||||||
|   const packageInfos: IPackageInfo[] = []; |   const packageInfos: IPackageInfo[] = []; | ||||||
|  |   // eslint-disable-next-line no-negated-condition
 | ||||||
|   if (!existsSync(TARGET_DIRECTORY)) { |   if (!existsSync(TARGET_DIRECTORY)) { | ||||||
|     console.log(`[ERROR] Directory ${TARGET_DIRECTORY} not exist!`); |     console.log(`[ERROR] Directory ${TARGET_DIRECTORY} not exist!`); | ||||||
|   } else { |   } else { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue