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:
ZeroLing 2022-06-20 15:28:01 +08:00 committed by GitHub
parent e42a9ba67a
commit 59aa271de1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 18379 additions and 1472 deletions

View File

@ -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`

View File

@ -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",

View File

@ -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 || {}),
}); });

View File

@ -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) {

File diff suppressed because it is too large Load Diff

15828
pnpm-lock.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 {