Match-id-c4c9af2f57d85de8d763757eebf4f9e8c0d1a595

This commit is contained in:
* 2023-02-17 18:18:13 +08:00
commit c1aeba5e70
22 changed files with 308 additions and 58 deletions

View File

@ -20,7 +20,7 @@ if [ -n "${releaseVersion}" ] ; then
cd umd
# umd生产包多暴露全局名HorizonDOM
# 以解决webpack的externals react-dom和react都指向Horizon时,webpack随机使用key名造成源码交付问题
sed -i '$a window.HorizonDOM = window.Horizon;' horizon.production.js
sed -i '$a window.HorizonDOM = window.Horizon;' horizon.production.min.js
cd -
# 写入新版本号

View File

@ -22,8 +22,8 @@ steps:
- checkout:
path: horizon-core
- gitlab:
url: https://szv-open.codehub.huawei.com/innersource/shanhai/wutong/react/horizon-test.git
branch: one_tree_dev
url: https://szv-open.codehub.huawei.com/innersource/fenghuang/horizon/horizon-test.git
branch: master
path: horizon-test
BUILD:
- build_execute:

View File

@ -1,3 +1,25 @@
## 0.0.38 (2023-02-01)
- **core**: 增加flushSync接口
## 0.0.37 (2023-01-31)
- **core**: 增加jsxs方法
## 0.0.36 (2023-01-30)
- **core**: #100 horizon从上层页面透传到iframe页面里使用创建的dom元素instanceof HTMLElement为false
## 0.0.35 (2023-01-28)
- **core**: 在 cloneDeep JSXElement 的时候会出现死循环
## 0.0.34 (2023-01-19)
- **core**: #95 新增jsx接口
- **core**: #96 #97 fix testing-library 的UT错误
## 0.0.33 (2023-01-11)
- **horizonX-devtool**: 修复IE中报错
## 0.0.32 (2023-01-04)
- **CI**: 生成态输出文件改为horiozn.producion.min.js
## 0.0.26 (2022-11-09)
- **CI**: 包信息同步CMC

View File

@ -19,3 +19,4 @@
declare var isDev: boolean;
declare var isTest: boolean;
declare const __VERSION__: string;
declare var setImmediate: Function;

View File

@ -42,9 +42,6 @@ import {
useState,
useDebugValue,
} from './src/renderer/hooks/HookExternal';
import { asyncUpdates } from './src/renderer/TreeBuilder';
import { callRenderQueueImmediate } from './src/renderer/taskExecutor/RenderQueue';
import { runAsyncEffects } from './src/renderer/submit/HookEffectHandler';
import {
isContextProvider,
isContextConsumer,
@ -59,13 +56,7 @@ import {
import { createStore, useStore, clearStore } from './src/horizonx/store/StoreHandler';
import * as reduxAdapter from './src/horizonx/adapters/redux';
import { watch } from './src/horizonx/proxy/watch';
// act用于测试作用是如果fun触发了刷新包含了异步刷新可以保证在act后面的代码是在刷新完成后才执行。
const act = fun => {
asyncUpdates(fun);
callRenderQueueImmediate();
runAsyncEffects();
};
import { act } from './src/external/TestUtil';
import {
render,
@ -75,6 +66,8 @@ import {
unmountComponentAtNode,
} from './src/dom/DOMExternal';
import { syncUpdates as flushSync } from './src/renderer/TreeBuilder';
const Horizon = {
Children,
createRef,
@ -107,6 +100,7 @@ const Horizon = {
findDOMNode,
unmountComponentAtNode,
act,
flushSync,
createStore,
useStore,
clearStore,
@ -156,6 +150,7 @@ export {
findDOMNode,
unmountComponentAtNode,
act,
flushSync,
// 状态管理器HorizonX接口
createStore,
useStore,

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import {
TYPE_FRAGMENT as Fragment,
} from './src/external/JSXElementType';
import { jsx, jsx as jsxs } from './src/external/JSXElement';
export {
jsx,
jsxs,
Fragment
};

View File

@ -16,7 +16,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/horizon.production.js');
module.exports = require('./cjs/horizon.production.min.js');
} else {
module.exports = require('./cjs/horizon.development.js');
}

View File

@ -4,7 +4,7 @@
"keywords": [
"horizon"
],
"version": "0.0.26",
"version": "0.0.38",
"homepage": "",
"bugs": "",
"main": "index.js",

View File

@ -16,7 +16,7 @@
import { saveVNode, updateVNodeProps } from './DOMInternalKeys';
import { createDom } from './utils/DomCreator';
import { getSelectionInfo, resetSelectionRange, SelectionData } from './SelectionRangeHandler';
import { shouldAutoFocus } from './utils/Common';
import { isDocument, shouldAutoFocus } from './utils/Common';
import { NSS } from './utils/DomCreator';
import { adjustStyleValue } from './DOMPropertiesHandler/StyleHandler';
import type { VNode } from '../renderer/Types';
@ -26,6 +26,7 @@ import { isNativeElement, validateProps } from './validators/ValidateProps';
import { watchValueChange } from './valueHandler/ValueChangeHandler';
import { DomComponent, DomText } from '../renderer/vnode/VNodeTags';
import { updateCommonProp } from './DOMPropertiesHandler/UpdateCommonProp';
import {getCurrentRoot} from '../renderer/RootStack';
export type Props = Record<string, any> & {
autoFocus?: boolean;
@ -70,7 +71,12 @@ export function resetAfterSubmit(): void {
// 创建 DOM 对象
export function newDom(tagName: string, props: Props, parentNamespace: string, vNode: VNode): Element {
const dom: Element = createDom(tagName, parentNamespace);
// document取值于treeRoot对应的DOM的ownerDocument。
// 解决在iframe中使用top的horizon时horizon在创建DOM时用到的document并不是iframe的document而是top中的document的问题。
const rootDom = getCurrentRoot().realNode;
const doc = isDocument(rootDom) ? rootDom : rootDom.ownerDocument;
const dom: Element = createDom(tagName, parentNamespace, doc);
// 将 vNode 节点挂到 DOM 对象上
saveVNode(vNode, dom);
// 将属性挂到 DOM 对象上

View File

@ -20,15 +20,15 @@ export const NSS = {
};
// 创建DOM元素
export function createDom(tagName: string, parentNamespace: string): Element {
export function createDom(tagName: string, parentNamespace: string, doc: Document): Element {
let dom: Element;
const selfNamespace = NSS[tagName] || NSS.html;
const ns = parentNamespace !== NSS.html ? parentNamespace : selfNamespace;
if (ns !== NSS.html) {
dom = document.createElementNS(ns, tagName);
dom = doc.createElementNS(ns, tagName);
} else {
dom = document.createElement(tagName);
dom = doc.createElement(tagName);
}
return dom;
}

View File

@ -25,10 +25,10 @@ import { Source } from '../renderer/Types';
* props
*/
export function JSXElement(type, key, ref, vNode, props, source: Source | null) {
return {
const ele = {
// 元素标识符
vtype: TYPE_COMMON_ELEMENT,
src: isDev ? source : null,
src: null,
// 属于元素的内置属性
type: type,
@ -37,8 +37,27 @@ export function JSXElement(type, key, ref, vNode, props, source: Source | null)
props: props,
// 所属的class组件
belongClassVNode: vNode,
belongClassVNode: null,
};
// 在 cloneDeep JSXElement 的时候会出现死循环需要设置belongClassVNode的enumerable为false
Object.defineProperty(ele, 'belongClassVNode', {
configurable: false,
enumerable: false,
value: vNode,
});
if (isDev) {
// 为了test判断两个 JSXElement 对象是否相等时忽略src属性需要设置src的enumerable为false
Object.defineProperty(ele, 'src', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
}
return ele;
}
function isValidKey(key) {
@ -107,3 +126,12 @@ export function cloneElement(element, setting, ...children) {
export function isValidElement(element) {
return !!(element && element.vtype === TYPE_COMMON_ELEMENT);
}
// 兼容高版本的babel编译方式
export function jsx(type, setting, key) {
if (setting.key === undefined && key !== undefined) {
setting.key = key;
}
return buildElement(false, type, setting, []);
}

74
libs/horizon/src/external/TestUtil.ts vendored Normal file
View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import {asyncUpdates} from '../renderer/TreeBuilder';
import {callRenderQueueImmediate} from '../renderer/taskExecutor/RenderQueue';
import {runAsyncEffects} from '../renderer/submit/HookEffectHandler';
import {isPromise} from '../renderer/ErrorHandler';
interface Thenable {
then(resolve: (val?: any) => void, reject: (err: any) => void): void;
}
// act用于测试作用是如果fun触发了刷新包含了异步刷新可以保证在act后面的代码是在刷新完成后才执行。
function act(fun: () => void | Thenable): Thenable {
const funRet = asyncUpdates(fun);
callRenderQueue();
// 如果fun返回的是Promise
if (isPromise(funRet)) {
// testing-library会返回Promise
return {
then(resolve, reject) {
funRet.then(
() => {
if (typeof setImmediate === 'function') {
// 通过setImmediate回调用于等待业务的setTimeout完成
setImmediate(() => {
callRenderQueue();
resolve();
});
} else {
callRenderQueue();
resolve();
}
},
err => {
reject(err);
},
);
},
};
} else {
return {
then(resolve) {
resolve();
},
};
}
}
function callRenderQueue() {
callRenderQueueImmediate();
runAsyncEffects();
// effects可能产生刷新任务这里再执行一次
callRenderQueueImmediate();
}
export {
act
};

View File

@ -53,6 +53,43 @@ export type ReduxMiddleware = (
type Reducer = (state: any, action: ReduxAction) => any;
function mergeData(state, data) {
console.log('merging data', { state, data });
if (!data) {
console.log('!data');
state.stateWrapper = data;
return;
}
if (Array.isArray(data) && Array.isArray(state?.stateWrapper)) {
console.log('data is array');
state.stateWrapper.length = data.length;
data.forEach((item, idx) => {
if (item != state.stateWrapper[idx]) {
state.stateWrapper[idx] = item;
}
});
return;
}
if (typeof data === 'object' && typeof state?.stateWrapper === 'object') {
console.log('data is object');
Object.keys(state.stateWrapper).forEach(key => {
if (!data.hasOwnProperty(key)) delete state.stateWrapper[key];
});
Object.entries(data).forEach(([key, value]) => {
if (state.stateWrapper[key] !== value) {
state.stateWrapper[key] = value;
}
});
return;
}
console.log('data is primitive or type mismatch');
state.stateWrapper = data;
}
export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): ReduxStoreHandler {
const store = createStoreX({
id: 'defaultStore',
@ -69,6 +106,7 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?):
if (result === undefined) {
return;
} // NOTE: reducer should never return undefined, in this case, do not change state
// mergeData(state,result);
state.stateWrapper = result;
},
},
@ -77,6 +115,10 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?):
},
})();
// store.$subscribe(()=>{
// console.log('changed');
// });
const result = {
reducer,
getState: function () {

View File

@ -175,7 +175,11 @@ window.addEventListener('message', messageEvent => {
}
// executes store action
<<<<<<< HEAD
if (messageEvent.data.payload.type === 'horizonx executue action') {
=======
if (messageEvent.data?.payload?.type === 'horizonx executue action') {
>>>>>>> master
const data = messageEvent.data.payload.data;
const store = getStore(data.storeId);
if (!store?.[data.action]) return;

View File

@ -72,7 +72,7 @@ function createClassErrorUpdate(vNode: VNode, error: any): Update {
}
return update;
}
function isPromise(error: any): error is PromiseType<any> {
export function isPromise(error: any): error is PromiseType<any> {
return error !== null && typeof error === 'object' && typeof error.then === 'function';
}
// 处理capture和bubble阶段抛出的错误

View File

@ -14,6 +14,7 @@
*/
import { VNode } from './vnode/VNode';
const currentRootStack: VNode[] = [];
export function getCurrentRoot() {
return currentRootStack[currentRootStack.length - 1];

View File

@ -77,3 +77,5 @@ export type Source = {
fileName: string;
lineNumber: number;
};
export type Callback = () => void;

View File

@ -13,7 +13,7 @@
* See the Mulan PSL v2 for more details.
*/
import type { VNode } from './Types';
import type { VNode, Callback } from './Types';
import { FlagUtils, ShouldCapture } from './vnode/VNodeFlags';
export type Update = {
@ -22,8 +22,6 @@ export type Update = {
callback: Callback | null;
};
export type Callback = () => any;
export type Updates = Array<Update> | null;
export enum UpdateState {

View File

@ -13,6 +13,8 @@
* See the Mulan PSL v2 for more details.
*/
import {Callback} from '../Types';
/**
* Component的api setState和forceUpdate在实例生成阶段实现
*/
@ -29,7 +31,7 @@ class Component<P, S, C> {
this.context = context;
}
setState(state: S) {
setState(state: S, callback?: Callback) {
if (isDev) {
console.error('Cant not call `this.setState` in the constructor of class component, it will do nothing');
}

View File

@ -19,7 +19,9 @@
let isMessageLoopRunning = false;
let browserCallback = null;
const { port1, port2 } = new MessageChannel();
let port1 = null;
let port2 = null;
let isTestRuntime = false;
export function isOverTime() {
return false;
@ -41,21 +43,38 @@ const callRenderTasks = () => {
browserCallback = null;
} else {
// 还有task继续调用
port2.postMessage(null);
asyncCall();
}
} catch (error) {
port2.postMessage(null);
asyncCall();
throw error;
}
};
port1.onmessage = callRenderTasks;
if (typeof MessageChannel === 'function') {
const mc = new MessageChannel();
port1 = mc.port1;
port1.onmessage = callRenderTasks;
port2 = mc.port2;
} else {
// 测试环境没有 MessageChannel
isTestRuntime = true;
}
function asyncCall() {
if (isTestRuntime) {
setTimeout(callRenderTasks, 0);
} else {
port2.postMessage(null);
}
}
export function requestBrowserCallback(callback) {
browserCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
port2.postMessage(null);
asyncCall();
}
}

View File

@ -35,11 +35,11 @@
"@babel/plugin-transform-object-super": "7.16.7",
"@babel/plugin-transform-parameters": "7.16.7",
"@babel/plugin-transform-react-jsx": "7.16.7",
"@babel/plugin-transform-react-jsx-source": "^7.16.7",
"@babel/plugin-transform-runtime": "7.16.7",
"@babel/plugin-transform-shorthand-properties": "7.16.7",
"@babel/plugin-transform-spread": "7.16.7",
"@babel/plugin-transform-template-literals": "7.16.7",
"@babel/plugin-transform-react-jsx-source": "^7.16.7",
"@babel/preset-env": "7.16.7",
"@babel/preset-typescript": "7.16.7",
"@rollup/plugin-babel": "^5.3.1",
@ -67,5 +67,8 @@
"engines": {
"node": ">=10.x",
"npm": ">=7.x"
},
"dependencies": {
"ejs": "^3.1.8"
}
}

View File

@ -38,44 +38,58 @@ if (!fs.existsSync(outDir)) {
const outputResolve = (...p) => path.resolve(outDir, ...p);
const isDev = (mode) => {
return mode === 'development';
}
const getBasicPlugins = (mode) => {
return [
nodeResolve({
extensions,
modulesOnly: true,
}),
babel({
exclude: 'node_modules/**',
configFile: path.join(__dirname, '../../babel.config.js'),
babelHelpers: 'runtime',
extensions,
}),
replace({
values: {
'process.env.NODE_ENV': `"${mode}"`,
isDev: isDev(mode).toString(),
isTest: false,
__VERSION__: `"${horizonVersion}"`,
},
preventAssignment: true,
}),
];
}
function getOutputName(mode) {
return mode === 'production' ? `horizon.${mode}.min.js` : `horizon.${mode}.js`;
}
function genConfig(mode) {
const isDev = mode === 'development';
const sourcemap = isDev ? 'inline' : false;
const sourcemap = isDev(mode) ? 'inline' : false;
return {
input: path.resolve(libDir, 'index.ts'),
output: [
{
file: outputResolve('cjs', `horizon.${mode}.js`),
file: outputResolve('cjs', getOutputName(mode)),
sourcemap,
format: 'cjs',
},
{
file: outputResolve('umd', `horizon.${mode}.js`),
file: outputResolve('umd', getOutputName(mode)),
sourcemap,
name: 'Horizon',
format: 'umd',
},
],
plugins: [
nodeResolve({
extensions,
modulesOnly: true,
}),
babel({
exclude: 'node_modules/**',
configFile: path.join(__dirname, '../../babel.config.js'),
babelHelpers: 'runtime',
extensions,
}),
replace({
values: {
'process.env.NODE_ENV': `"${mode}"`,
isDev: isDev.toString(),
isTest: false,
__VERSION__: `"${horizonVersion}"`,
},
preventAssignment: true,
}),
...getBasicPlugins(mode),
execute('npm run build-types'),
mode === 'production' && terser(),
copy([
@ -92,4 +106,18 @@ function genConfig(mode) {
};
}
export default [genConfig('development'), genConfig('production')];
function genJSXRuntimeConfig(mode) {
return {
input: path.resolve(libDir, 'jsx-runtime.ts'),
output: [
{
file: outputResolve('jsx-runtime.js'),
format: 'cjs',
}
],
plugins: [
...getBasicPlugins(mode)
]
};
}
export default [genConfig('development'), genConfig('production'), genJSXRuntimeConfig('')];