Skip to content
New issue

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

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

Already on GitHub? Sign in 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;
}