Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

refactor: rework list #1379

Merged
merged 12 commits into from
Sep 18, 2019
47 changes: 41 additions & 6 deletions packages/jsx-compiler/src/modules/__tests__/jsx-plus.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,57 @@
const { _transformCondition, _transformList, _transformFragment } = require('../jsx-plus');
const {
_transformCondition,
_transformList,
_transformFragment,
} = require('../jsx-plus');
const { parseExpression } = require('../../parser');
const genExpression = require('../../codegen/genExpression');
const adapter = require('../../adapter');

describe('Directives', () => {
describe('list', () => {
it('simple', () => {
const ast = parseExpression(`
<View x-for={val in array}>{val}</View>
`);
_transformList(ast, adapter);
expect(genExpression(ast)).toEqual(`<View a:for={array.map((val, index) => {
const code = `
<View x-for={val in array}>{val}</View>
`;
const ast = parseExpression(code);
_transformList(ast, code, adapter);
expect(genExpression(ast))
.toEqual(`<View a:for={array.map((val, index) => {
return {
val: val,
index: index
};
})} a:for-item="val" a:for-index="index">{val}</View>`);
});

it('nested', () => {
const code = `
<View x-for={item in array}>
<View x-for={item2 in item}>
{item2}
</View>
</View>
`;
const ast = parseExpression(code);
_transformList(ast, code, adapter);
expect(genExpression(ast))
.toEqual(`<View a:for={array.map((item, index) => {
item = item.map((item2, index) => {
return {
item2: item2,
index: index
};
});
return {
item: item,
index: index
};
})} a:for-item="item" a:for-index="index">
<View a:for={item} a:for-item="item2" a:for-index="index">
{item2}
</View>
</View>`);
});
});

describe('condition', () => {
Expand Down
123 changes: 72 additions & 51 deletions packages/jsx-compiler/src/modules/jsx-plus.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const t = require('@babel/types');
const traverse = require('../utils/traverseNodePath');
const hasListItem = require('../utils/hasListItem');
const CodeError = require('../utils/CodeError');
const genExpression = require('../codegen/genExpression');

const directiveIf = 'x-if';
const directiveElseif = 'x-elseif';
Expand Down Expand Up @@ -87,48 +90,8 @@ function transformDirectiveCondition(ast, adapter) {
});
}

function transformDirectiveList(ast, adapter) {
function transformDirectiveList(ast, code, adapter) {
traverse(ast, {
JSXElement: {
exit(path) {
const { node } = path;
const { attributes } = node.openingElement;
if (node.__jsxlist && !node.__jsxlist.generated) {
const { args, iterValue } = node.__jsxlist;
path.traverse({
Identifier(innerPath) {
const { node } = innerPath;
if (args.find(arg => arg.name === node.name)
) {
node.__listItem = {
jsxplus: true,
item: args[0].name
};
}
}
});
attributes.push(
t.jsxAttribute(
t.jsxIdentifier(adapter.for),
t.jsxExpressionContainer(iterValue)
)
);
args.forEach((arg, index) => {
attributes.push(
t.jsxAttribute(
t.jsxIdentifier([adapter.forItem, adapter.forIndex][index]),
t.stringLiteral(arg.name)
)
);
// Mark skip ids.
const skipIds = node.skipIds = node.skipIds || new Map();
skipIds.set(arg.name, true);
});

node.__jsxlist.generated = true;
}
}
},
JSXAttribute(path) {
const { node } = path;
if (t.isJSXIdentifier(node.name, { name: 'x-for' })) {
Expand Down Expand Up @@ -163,24 +126,42 @@ function transformDirectiveList(ast, adapter) {
}
const parentJSXEl = path.findParent(p => p.isJSXElement());
// Transform x-for iterValue to map function
const properties = [
t.objectProperty(params[0], params[0]),
t.objectProperty(params[1], params[1])
];
const loopFnBody = t.blockStatement([
t.returnStatement(
t.objectExpression([
t.objectProperty(params[0], params[0]),
t.objectProperty(params[1], params[1])
])
t.objectExpression(properties)
)
]);
const mapCallExpression = t.callExpression(
t.memberExpression(iterValue, t.identifier('map')),
[
t.arrowFunctionExpression(params, loopFnBody)
]);
if (hasListItem(iterValue)) {
let parentList;
if (t.isMemberExpression(iterValue)) {
parentList = iterValue.object.__listItem.parentList;
} else if (t.isIdentifier(iterValue)) {
parentList = iterValue.__listItem.parentList;
}
if (parentList) {
parentList.loopFnBody.body.unshift(t.expressionStatement(t.assignmentExpression('=', iterValue, mapCallExpression)));
} else {
throw new CodeError(code, iterValue, iterValue.loc, 'Nested x-for list only supports MemberExpression and Identifier,like x-for={item.list} or x-for={item}.');
}
} else {
iterValue = mapCallExpression;
}
parentJSXEl.node.__jsxlist = {
args: params,
iterValue: t.callExpression(
t.memberExpression(iterValue, t.identifier('map')),
[
t.arrowFunctionExpression(params, loopFnBody)
]),
iterValue,
loopFnBody,
jsxplus: true
};
transformListJSXElement(path.parentPath.parentPath, adapter);
path.remove();
}
}
Expand All @@ -200,11 +181,51 @@ function transformComponentFragment(ast) {
});
return null;
}

function transformListJSXElement(path, adapter) {
const { node } = path;
const { attributes } = node.openingElement;
if (node.__jsxlist && !node.__jsxlist.generated) {
const { args, iterValue } = node.__jsxlist;
path.traverse({
Identifier(innerPath) {
const innerNode = innerPath.node;
if (args.find(arg => arg.name === innerNode.name)
) {
innerNode.__listItem = {
jsxplus: true,
item: args[0].name,
parentList: node.__jsxlist
};
}
}
});
attributes.push(
t.jsxAttribute(
t.jsxIdentifier(adapter.for),
t.jsxExpressionContainer(iterValue)
)
);
args.forEach((arg, index) => {
attributes.push(
t.jsxAttribute(
t.jsxIdentifier([adapter.forItem, adapter.forIndex][index]),
t.stringLiteral(arg.name)
)
);
// Mark skip ids.
const skipIds = node.skipIds = node.skipIds || new Map();
skipIds.set(arg.name, true);
});

node.__jsxlist.generated = true;
}
}
module.exports = {
parse(parsed, code, options) {
if (parsed.renderFunctionPath) {
transformDirectiveCondition(parsed.templateAST, options.adapter);
transformDirectiveList(parsed.templateAST, options.adapter);
transformDirectiveList(parsed.templateAST, code, options.adapter);
}
},
_transformList: transformDirectiveList,
Expand Down
13 changes: 2 additions & 11 deletions packages/jsx-compiler/src/modules/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const traverse = require('../utils/traverseNodePath');

const TEMPLATE_AST = 'templateAST';
const DynamicBinding = require('../utils/DynamicBinding');
const hasListItem = require('../utils/hasListItem');

/**
* Transform style object.
Expand Down Expand Up @@ -34,17 +35,7 @@ function transformStyle(ast) {
function shouldReplace(path) {
const { node } = path;
if (t.isJSXExpressionContainer(node.value) && node.name.name === 'style') {
let shouldReplace = true;
// List item has been replaced in list module
traverse(node.value.expression, {
Identifier(innerPath) {
if (innerPath.node.__listItem) {
shouldReplace = false;
innerPath.stop();
}
}
});
return shouldReplace;
return !hasListItem(node.value.expression);
}
return false;
}
Expand Down
15 changes: 15 additions & 0 deletions packages/jsx-compiler/src/utils/hasListItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const traverse = require('./traverseNodePath');

module.exports = function hasListItem(ast) {
let hasListItem = false;
// List item has been replaced in list module
traverse(ast, {
Identifier(innerPath) {
if (innerPath.node.__listItem) {
hasListItem = true;
innerPath.stop();
}
}
});
return hasListItem;
};
3 changes: 2 additions & 1 deletion packages/jsx2mp-runtime/src/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { cycles as appCycles } from './app';
import Component from './component';
import { ON_SHOW, ON_HIDE, ON_PAGE_SCROLL, ON_SHARE_APP_MESSAGE, ON_REACH_BOTTOM, ON_PULL_DOWN_REFRESH } from './cycles';
import { setComponentInstance, getComponentProps } from './updater';
import { setComponentInstance, getComponentProps, executeCallbacks } from './updater';
import { getComponentLifecycle } from '@@ADAPTER@@';

const GET_DERIVED_STATE_FROM_PROPS = 'getDerivedStateFromProps';
Expand Down Expand Up @@ -62,6 +62,7 @@ function getComponentCycles(Klass) {
if (this[PROPS].hasOwnProperty('__tagId')) {
const componentId = this[PROPS].__tagId;
setComponentInstance(componentId, this.instance);
executeCallbacks();
}

if (GET_DERIVED_STATE_FROM_PROPS in Klass) {
Expand Down
30 changes: 25 additions & 5 deletions packages/jsx2mp-runtime/src/updater.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const propsMap = {
};
const componentIntances = {};

const updateChildPropsCallbacks = [];

export function setComponentInstance(instanceId, instance) {
componentIntances[instanceId] = instance;
Expand All @@ -23,11 +24,30 @@ export function removeComponentProps(tagId) {

export function updateChildProps(trigger, instanceId, nextProps) {
const targetComponent = componentIntances[instanceId];
if (trigger && targetComponent) {
if (trigger) {
// Create a new object reference.
propsMap[instanceId] = targetComponent.props = Object.assign({}, targetComponent.props, nextProps);
nextTick(() => {
targetComponent._updateComponent();
});
if (targetComponent) {
propsMap[instanceId] = Object.assign(
{},
targetComponent.props,
nextProps,
);
nextTick(() => {
Object.assign(targetComponent.props, propsMap[instanceId]);
targetComponent._updateComponent();
});
} else {
/**
* updateChildProps may execute before setComponentInstance
*/
updateChildPropsCallbacks.push(
updateChildProps.bind(null, trigger, instanceId, nextProps),
);
}
}
}

export function executeCallbacks() {
updateChildPropsCallbacks.forEach(callback => callback());
updateChildPropsCallbacks.length = 0;
}