From ec34490202752b6567f60c245159be816cfb177d Mon Sep 17 00:00:00 2001 From: huangxuan Date: Tue, 2 Apr 2024 09:51:58 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(core):=20=E5=AF=BC=E5=87=BAversion?= =?UTF-8?q?=EF=BC=8C=E5=85=BC=E5=AE=B9mobx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/inula/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/inula/src/index.ts b/packages/inula/src/index.ts index 116ab1f2..33a3eaba 100644 --- a/packages/inula/src/index.ts +++ b/packages/inula/src/index.ts @@ -72,6 +72,8 @@ import { import { syncUpdates as flushSync } from './renderer/TreeBuilder'; import { toRaw } from './inulax/proxy/ProxyHandler'; +const version = __VERSION__; + const Inula = { Children, createRef, @@ -122,9 +124,9 @@ const Inula = { Profiler, StrictMode, Suspense, + version, }; -export const version = __VERSION__; export { Children, createRef, @@ -178,6 +180,7 @@ export { Profiler, StrictMode, Suspense, + version, }; export * from './types'; From ebfe1eceb997895bf63c66e45d2c1a39b9d18e08 Mon Sep 17 00:00:00 2001 From: huangxuan Date: Tue, 2 Apr 2024 10:47:31 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat(core):=20=E9=99=90=E5=88=B6dangerously?= =?UTF-8?q?SetInnerHTML=20API=E7=94=9F=E6=95=88=E7=9A=84=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E5=87=8F=E5=B0=91XSS=E6=94=BB=E5=87=BB=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inula/__tests__/DomTest/Attribute.test.js | 12 +++++++ .../inula/src/dom/validators/ValidateProps.ts | 31 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/packages/inula/__tests__/DomTest/Attribute.test.js b/packages/inula/__tests__/DomTest/Attribute.test.js index 27e5becb..a216bc5b 100644 --- a/packages/inula/__tests__/DomTest/Attribute.test.js +++ b/packages/inula/__tests__/DomTest/Attribute.test.js @@ -95,4 +95,16 @@ describe('Dom Attribute', () => { Inula.render(
, container); }).not.toThrow(); }); + + it('dangerouslySetInnerHTML和children同时设置,只渲染children', () => { + Inula.act(() => { + Inula.render( +
+ 123 +
, + container + ); + }); + expect(container.innerHTML).toBe('
123
'); + }); }); diff --git a/packages/inula/src/dom/validators/ValidateProps.ts b/packages/inula/src/dom/validators/ValidateProps.ts index 2a6b27e1..18cf48c3 100644 --- a/packages/inula/src/dom/validators/ValidateProps.ts +++ b/packages/inula/src/dom/validators/ValidateProps.ts @@ -17,6 +17,25 @@ import { getPropDetails, PROPERTY_TYPE, PropDetails } from './PropertiesData'; const INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/; +const voidTagElements = [ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'keygen', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr', + 'menuitem', +]; + // 是内置元素 export function isNativeElement(tagName: string, props: Record) { return !tagName.includes('-') && props.is === undefined; @@ -108,6 +127,18 @@ export function validateProps(type, props) { throw new Error('style should be a object.'); } + // 对于没有children的元素,设置dangerouslySetInnerHTML不生效 + if (voidTagElements.includes(type)) { + if (props.dangerouslySetInnerHTML != null) { + delete props.dangerouslySetInnerHTML; + } + } + + // dangerouslySetInnerHTML和children同时设置,只渲染children + if (props.dangerouslySetInnerHTML != null && props.children != null) { + delete props.dangerouslySetInnerHTML; + } + if (isDev) { // 校验属性 const invalidProps = Object.keys(props).filter(key => !isValidProp(type, key, props[key])); From 78f4bce57cab6d6858491a68834d3671e8ac399d Mon Sep 17 00:00:00 2001 From: huangxuan Date: Tue, 2 Apr 2024 10:52:22 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix(router):=20inula-router=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E5=8C=B9=E9=85=8D=E8=A7=84=E5=88=99=E5=85=BC=E5=AE=B9?= =?UTF-8?q?react-router=EF=BC=9BHashHistory=20hash=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E4=B8=8D=E5=90=88=E6=B3=95=E6=97=B6=E9=87=8D=E5=AE=9A=E5=90=91?= =?UTF-8?q?=E8=87=B3=E5=90=88=E6=B3=95URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inula-router/src/history/hashHistory.ts | 7 ++ .../router/matcher/__tests__/parser.test.ts | 10 ++- .../inula-router/src/router/matcher/parser.ts | 70 +++++++++---------- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/packages/inula-router/src/history/hashHistory.ts b/packages/inula-router/src/history/hashHistory.ts index 6deb725e..8ce51b0a 100644 --- a/packages/inula-router/src/history/hashHistory.ts +++ b/packages/inula-router/src/history/hashHistory.ts @@ -57,6 +57,13 @@ export function createHashHistory(option: HashHistoryOptio const pathDecoder = addHeadSlash; const pathEncoder = hashType === 'slash' ? addHeadSlash : stripHeadSlash; + const startLocation = getHashContent(window.location.href); + const encodeLocation = pathEncoder(startLocation); + // 初始化hash格式不合法时会重定向 + if (startLocation !== encodeLocation) { + window.location.replace(stripHash(window.location.href) + '#' + encodeLocation); + } + function getLocation() { let hashPath = pathDecoder(getHashContent(window.location.hash)); if (basename) { diff --git a/packages/inula-router/src/router/matcher/__tests__/parser.test.ts b/packages/inula-router/src/router/matcher/__tests__/parser.test.ts index 91493cf6..e2a779d7 100644 --- a/packages/inula-router/src/router/matcher/__tests__/parser.test.ts +++ b/packages/inula-router/src/router/matcher/__tests__/parser.test.ts @@ -121,7 +121,13 @@ describe('parser test', () => { it('url without end slash match wildcard', function () { const parser = createPathParser('/about/', { strictMode: false }); const matched = parser.parse('/about'); - expect(matched).toBeNull(); + expect(matched).toStrictEqual({ + path: '/about/', + url: '/about', + score: [10], + isExact: true, + param: {}, + }); }); it('url without end slash match wildcard (strictMode)', function () { @@ -259,7 +265,7 @@ describe('parser test', () => { }); it('dynamic param with complex regexp pattern', () => { - const parser = createPathParser('/detail/:action([\\da-z]+)', { exact: true }); + const parser = createPathParser('/detail/:action([\\da-z]+)', { exact: true, caseSensitive: true }); const res = parser.parse('/detail/a123'); expect(res).toEqual({ isExact: true, diff --git a/packages/inula-router/src/router/matcher/parser.ts b/packages/inula-router/src/router/matcher/parser.ts index 9ea88fa7..1ddbbb5c 100644 --- a/packages/inula-router/src/router/matcher/parser.ts +++ b/packages/inula-router/src/router/matcher/parser.ts @@ -97,12 +97,14 @@ export function createPathParser

(pathname: string, option: ParserOp const token = tokens[tokenIdx]; const nextToken = tokens[tokenIdx + 1]; switch (token.type) { - case TokenType.Delimiter: - { - const hasOptional = lookToNextDelimiter(tokenIdx + 1); - pattern += `/${hasOptional ? '?' : ''}`; - } + case TokenType.Delimiter: { + // 该分隔符后有可选参数则该分割符在匹配时是可选的 + const hasOptional = lookToNextDelimiter(tokenIdx + 1); + // 该分割符为最后一个且strictMode===false时,该分隔符在匹配时是可选的 + const isSlashOptional = nextToken === undefined && !strictMode; + pattern += `/${hasOptional || isSlashOptional ? '?' : ''}`; break; + } case TokenType.Static: pattern += token.value.replace(REGEX_CHARS_RE, '\\$&'); if (nextToken && nextToken.type === TokenType.Pattern) { @@ -112,32 +114,31 @@ export function createPathParser

(pathname: string, option: ParserOp } scores.push(MatchScore.static); break; - case TokenType.Param: - { - // 动态参数支持形如/:param、/:param*、/:param?、/:param(\\d+)的形式 - let paramRegexp = ''; - if (nextToken) { - switch (nextToken.type) { - case TokenType.LBracket: - // 跳过当前Token和左括号 - tokenIdx += 2; - while (tokens[tokenIdx].type !== TokenType.RBracket) { - paramRegexp += tokens[tokenIdx].value; - tokenIdx++; - } - paramRegexp = `(${paramRegexp})`; - break; - case TokenType.Pattern: + case TokenType.Param: { + // 动态参数支持形如/:param、/:param*、/:param?、/:param(\\d+)的形式 + let paramRegexp = ''; + if (nextToken) { + switch (nextToken.type) { + case TokenType.LBracket: + // 跳过当前Token和左括号 + tokenIdx += 2; + while (tokens[tokenIdx].type !== TokenType.RBracket) { + paramRegexp += tokens[tokenIdx].value; tokenIdx++; - paramRegexp += `(${nextToken.value === '*' ? '.*' : BASE_PARAM_PATTERN})${nextToken.value}`; - break; - } + } + paramRegexp = `(${paramRegexp})`; + break; + case TokenType.Pattern: + tokenIdx++; + paramRegexp += `(${nextToken.value === '*' ? '.*' : BASE_PARAM_PATTERN})${nextToken.value}`; + break; } - pattern += paramRegexp ? `(?:${paramRegexp})` : `(${BASE_PARAM_PATTERN})`; - keys.push(token.value); - scores.push(MatchScore.param); } + pattern += paramRegexp ? `(?:${paramRegexp})` : `(${BASE_PARAM_PATTERN})`; + keys.push(token.value); + scores.push(MatchScore.param); break; + } case TokenType.WildCard: keys.push(token.value); pattern += `((?:${BASE_PARAM_PATTERN})${onlyHasWildCard ? '?' : ''}(?:/(?:${BASE_PARAM_PATTERN}))*)`; @@ -215,16 +216,15 @@ export function createPathParser

(pathname: string, option: ParserOp } path += params[token.value]; break; - case TokenType.WildCard: - { - const wildCard = params['*']; - if (wildCard instanceof Array) { - path += wildCard.join('/'); - } else { - path += wildCard; - } + case TokenType.WildCard: { + const wildCard = params['*']; + if (wildCard instanceof Array) { + path += wildCard.join('/'); + } else { + path += wildCard; } break; + } case TokenType.Delimiter: path += token.value; break; From aa4984f99764a5767077d1674c00c83470c92a51 Mon Sep 17 00:00:00 2001 From: huangxuan Date: Tue, 2 Apr 2024 10:53:25 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat(router):=20=E4=BF=AE=E5=A4=8DHashRoute?= =?UTF-8?q?r=20push=E4=B8=8E=E5=BD=93=E5=89=8D=E9=A1=B5=E9=9D=A2=E7=9B=B8?= =?UTF-8?q?=E5=90=8C=E7=9A=84URL=E6=97=B6=E9=A1=B5=E9=9D=A2=E4=B8=8D?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/inula-router/src/history/baseHistory.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/inula-router/src/history/baseHistory.ts b/packages/inula-router/src/history/baseHistory.ts index 87451567..0b1bffa0 100644 --- a/packages/inula-router/src/history/baseHistory.ts +++ b/packages/inula-router/src/history/baseHistory.ts @@ -66,7 +66,9 @@ export function getBaseHistory( Object.assign(historyProps, nextState); } historyProps.length = browserHistory.length; - const args = { location: historyProps.location, action: historyProps.action }; + // 避免location饮用相同时setState不触发 + const location = Object.assign({}, historyProps.location); + const args = { location: location, action: historyProps.action }; transitionManager.notifyListeners(args); }; } From 4a825cec8891ab93ea2ab8f3120cd2b15e9aefa7 Mon Sep 17 00:00:00 2001 From: huangxuan Date: Tue, 2 Apr 2024 11:02:48 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix(inulax):=20=E4=BF=AE=E5=A4=8D=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86=E5=99=A8=E8=A7=A6=E5=8F=91=E7=B1=BB?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E9=87=8D=E6=96=B0=E6=B8=B2=E6=9F=93=EF=BC=8C?= =?UTF-8?q?shouldComponentUpdate=E4=B8=8D=E7=94=9F=E6=95=88=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/inula/src/renderer/render/ClassComponent.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/inula/src/renderer/render/ClassComponent.ts b/packages/inula/src/renderer/render/ClassComponent.ts index 2792425c..ec1a4857 100644 --- a/packages/inula/src/renderer/render/ClassComponent.ts +++ b/packages/inula/src/renderer/render/ClassComponent.ts @@ -145,7 +145,12 @@ export function captureRender(processing: VNode): VNode | null { processUpdates(processing, inst, nextProps); // 如果 props, state, context 都没有变化且 isForceUpdate 为 false则不需要更新 - shouldUpdate = oldProps !== processing.props || inst.state !== processing.state || processing.isForceUpdate; + shouldUpdate = + oldProps !== processing.props || + inst.state !== processing.state || + processing.isForceUpdate || + // 响应式状态管理器中的值变化,需要更新 + processing.isStoreChange; if (shouldUpdate) { // derivedStateFromProps会修改nextState,因此需要调用 @@ -167,7 +172,7 @@ export function captureRender(processing: VNode): VNode | null { } // 如果捕获了 error,必须更新 const isCatchError = (processing.flags & DidCapture) === DidCapture; - shouldUpdate = isCatchError || shouldUpdate || processing.isStoreChange; + shouldUpdate = isCatchError || shouldUpdate; // 更新ref markRef(processing); From 0375ed95fc4a959e7cfe148429392330b75f8303 Mon Sep 17 00:00:00 2001 From: huangxuan Date: Tue, 2 Apr 2024 11:08:16 +0800 Subject: [PATCH 6/6] =?UTF-8?q?fix(core):=20=E4=BF=AE=E5=A4=8Dantd=20Tree?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/inula/src/renderer/TreeBuilder.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/inula/src/renderer/TreeBuilder.ts b/packages/inula/src/renderer/TreeBuilder.ts index c29126fb..c7a76bee 100644 --- a/packages/inula/src/renderer/TreeBuilder.ts +++ b/packages/inula/src/renderer/TreeBuilder.ts @@ -200,6 +200,10 @@ function getChildByIndex(vNode: VNode, idx: number) { // 从多个更新节点中,计算出开始节点。即:找到最近的共同的父辈节点 export function calcStartUpdateVNode(treeRoot: VNode) { const toUpdateNodes = Array.from(treeRoot.toUpdateNodes!); + // 所有待更新元素的parent为null说明该node的父元素已经被卸载,应该从根节点发起更新 + if (toUpdateNodes.every(node => node.parent === null)) { + return treeRoot; + } if (toUpdateNodes.length === 0) { return treeRoot;