Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/jsx2mp-runtime/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"env": {
"test": {
"plugins": ["@babel/plugin-transform-modules-commonjs"]
}
}
}
8 changes: 7 additions & 1 deletion packages/jsx2mp-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,26 @@
],
"scripts": {
"build": "cross-env NODE_ENV=production rollup -c rollup.config.js",
"prepublishOnly": "npm run build"
"prepublishOnly": "npm run build",
"test": "jest --coverage --testPathIgnorePatterns=__modules__"
},
"author": "Rax Team",
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-transform-modules-commonjs": "^7.16.8",
"@babel/preset-env": "^7.4.5",
"@babel/types": "^7.8.3",
"cross-env": "^5.2.0",
"jest": "^27.4.7",
"rollup": "^1.13.1",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-cleanup": "^3.1.1",
"rollup-plugin-filesize": "^6.1.0",
"rollup-plugin-replace": "^2.2.0",
"universal-env": "^3.0.0"
},
"jest": {
"collectCoverage": true
}
}
85 changes: 85 additions & 0 deletions packages/jsx2mp-runtime/src/__tests__/arrayDiff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const { diffData } = require('../diff');

describe('Array diff', () => {
it('add item to array', () => {
expect(
diffData({ list: [{ a: 1 }, { a: 2 }] }, { list: [{ a: 1 }] })
).toStrictEqual({
'list[1]': { a: 2 },
});
});

it('diff with null/undefined', () => {
expect(diffData({ list: [{ a: 1 }, { a: 2 }] }, null)).toStrictEqual({
list: [{ a: 1 }, { a: 2 }],
});
});

it('delete item from array', () => {
expect(
diffData({ list: [{ a: 1 }] }, { list: [{ a: 1 }, { a: 2 }] })
).toStrictEqual({
list: [{ a: 1 }],
});
});

it('modify item of array', () => {
expect(diffData({ list: [{ a: 1 }] }, { list: [{ a: 2 }] })).toStrictEqual({
'list[0].a': 1,
});
});

it('modify item of array', () => {
expect(diffData({ list: [{ a: 1 }] }, { list: [{ a: 2 }] })).toStrictEqual({
'list[0].a': 1,
});
});

it('modify item of array & update props', () => {
expect(
diffData({ list: [{ a: 1 }], a: 1 }, { list: [{ a: 2 }], a: 2 })
).toStrictEqual({
'list[0].a': 1,
a: 1,
});
});

it('complex ', () => {
expect(
diffData(
{
a: 1,
b: 2,
c: 'str',
d: { e: [2, { a: 4 }, 5] },
f: true,
h: [1],
g: { a: [1, 2], j: 111 },
},
{
a: [],
b: 'aa',
c: 3,
d: { e: [3, { a: 3 }] },
f: false,
h: [1, 2],
g: { a: [1, 1, 1], i: 'delete' },
k: 'del',
}
)
).toStrictEqual({
a: 1,
b: 2,
c: 'str',
'd.e[0]': 2,
'd.e[1].a': 4,
'd.e[2]': 5,
f: true,
h: [1],
'g.a': [1, 2],
'g.j': 111,
'g.i': null,
k: null,
});
});
});
2 changes: 1 addition & 1 deletion packages/jsx2mp-runtime/src/__tests__/createStyle.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import createStyle from '../createStyle';
const createStyle = require('../createStyle');

describe('Create style', () => {
describe('Types', () => {
Expand Down
80 changes: 80 additions & 0 deletions packages/jsx2mp-runtime/src/__tests__/propsDiff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const { diffData } = require('../diff');

describe('Props diff', () => {
it('same props', () => {
expect(diffData({ a: 1 }, { a: 1 })).toStrictEqual({});
});

it('update props', () => {
expect(diffData({ a: 1 }, { a: 2 })).toStrictEqual({
a: 1,
});
});

it('delete props', () => {
expect(diffData({ a: 1 }, { a: 1, b: 2 })).toStrictEqual({
b: null,
});
});

it('add props', () => {
expect(diffData({ a: 1, b: 2 }, { a: 1 })).toStrictEqual({
b: 2,
});
});

it('update deep props', () => {
expect(
diffData(
{ user: { name: 'dnt', age: 18 } },
{ user: { name: 'dnt', age: 20 } }
)
).toStrictEqual({
'user.age': 18,
});
});

it('update deep props', () => {
expect(
diffData(
{ user: { name: 'dnt2', age: 18 } },
{ user: { name: 'dnt', age: 20 } }
)
).toStrictEqual({
'user.age': 18,
'user.name': 'dnt2',
});
});

it('add deep props', () => {
expect(
diffData({ user: { name: 'dnt', age: 18 } }, { user: { name: 'dnt' } })
).toStrictEqual({
'user.age': 18,
});
});

it('add deep props', () => {
expect(
diffData(
{ user: { name: 'dnt', age: 18, sex: 1 } },
{ user: { name: 'dnt' } }
)
).toStrictEqual({
'user.age': 18,
'user.sex': 1,
});
});

it('delete deep props', () => {
expect(
diffData(
{ user: { name: 'dnt' } },
{ user: { name: 'dnt', age: 18, sex: 1 } }
)
).toStrictEqual({
'user.age': null,
'user.sex': null,
});
});
});
14 changes: 11 additions & 3 deletions packages/jsx2mp-runtime/src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
import apiCore from './adapter/getNativeAPI';
import attachRef from './adapter/attachRef';
import Event from './events';
import { diffData } from './diff';

const event = new Event();

Expand Down Expand Up @@ -401,7 +402,14 @@ export default class Component {
);
} else if (isDifferentData(currentData[key], data[key])) {
if (isPlainObject(data[key])) {
normalData[key] = Object.assign({}, currentData[key], data[key]);
// normalData[key] = Object.assign({}, currentData[key], data[key]);
// find the different path from data and currentData
try {
const patch = diffData({ [key]: data[key] }, { [key]: currentData[key] });
Object.assign(normalData, patch);
} catch (err) {
normalData[key] = Object.assign({}, currentData[key], data[key]);
}
} else {
normalData[key] = data[key] === undefined ? null : data[key]; // Make undefined value compatible with Alibaba MiniApp incase that data is not sync in render and worker thread
}
Expand Down Expand Up @@ -486,8 +494,8 @@ function isAppendArray(prev, next) {
// Only concern about list append case
if (next.length === 0) return false;
if (prev.length === 0) return true;
// When item's type is object, they have differrent reference, so should use shallowEqual
return next.length > prev.length && next.slice(0, prev.length).every((val, index) => shallowEqual(prev[index], val));
// Use diffData replace shallowEqual
return next.length > prev.length && next.slice(0, prev.length).every((val, index) => Object.keys(diffData(prev[index], val)).length===0);
}

function isDifferentData(prevData, nextData) {
Expand Down
116 changes: 116 additions & 0 deletions packages/jsx2mp-runtime/src/diff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
const ARRAYTYPE = '[object Array]';
const OBJECTTYPE = '[object Object]';
const FUNCTIONTYPE = '[object Function]';

export function diffData(current, previous) {
const result = {};
if (!previous) return current;
_diff(current, previous, '', result);
return result;
}

function _diff(current, previous, path, result) {
if (current === previous) return;
const rootCurrentType = getType(current);
const rootPreType = getType(previous);
if (rootCurrentType == OBJECTTYPE) {
const $current = { ...current };
if (rootPreType === OBJECTTYPE) {
for (let key in previous) {
const currentValue = $current[key];
if (currentValue === undefined) {
$current[key] = null;
}
}
}
if (
rootPreType != OBJECTTYPE ||
(Object.keys($current).length < Object.keys(previous).length &&
path !== '')
) {
setResult(result, path, $current);
} else {
for (let key in $current) {
const currentValue = $current[key];
const preValue = previous[key];
const currentType = getType(currentValue);
const preType = getType(preValue);
if (currentType != ARRAYTYPE && currentType != OBJECTTYPE) {
if (currentValue !== previous[key]) {
setResult(result, concatPathAndKey(path, key), currentValue);
}
} else if (currentType == ARRAYTYPE) {
if (preType != ARRAYTYPE) {
setResult(result, concatPathAndKey(path, key), currentValue);
} else {
if (currentValue.length < preValue.length) {
setResult(result, concatPathAndKey(path, key), currentValue);
} else {
currentValue.forEach((item, index) => {
_diff(
item,
preValue[index],
concatPathAndKey(path, key) + '[' + index + ']',
result
);
});
}
}
} else if (currentType == OBJECTTYPE) {
const $currentValue = { ...currentValue };
if (preType === OBJECTTYPE) {
for (let key in preValue) {
const currentItem = $currentValue[key];
if (currentItem === undefined) {
$currentValue[key] = null;
}
}
}
if (
preType != OBJECTTYPE ||
Object.keys($currentValue).length < Object.keys(preValue).length
) {
setResult(result, concatPathAndKey(path, key), $currentValue);
} else {
for (let subKey in $currentValue) {
const realPath =
concatPathAndKey(path, key) +
(subKey.includes('.') ? `['${subKey}']` : `.${subKey}`);
_diff($currentValue[subKey], preValue[subKey], realPath, result);
}
}
}
}
}
} else if (rootCurrentType == ARRAYTYPE) {
if (rootPreType != ARRAYTYPE) {
setResult(result, path, current);
} else {
if (current.length < previous.length) {
setResult(result, path, current);
} else {
current.forEach((item, index) => {
_diff(item, previous[index], path + '[' + index + ']', result);
});
}
}
} else {
setResult(result, path, current);
}
}

function concatPathAndKey(path, key) {
return key.includes('.')
? path + `['${key}']`
: (path == '' ? '' : path + '.') + key;
}

function setResult(result, k, v) {
if (getType(v) != FUNCTIONTYPE) {
result[k] = v;
}
}

function getType(obj) {
return Object.prototype.toString.call(obj);
}