Skip to content

Commit

Permalink
jsx: vue mode
Browse files Browse the repository at this point in the history
  • Loading branch information
andrei-markeev committed Jul 1, 2018
1 parent 950593b commit 56b951f
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 56 deletions.
3 changes: 2 additions & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ namespace ts {
type: createMapFromTemplate({
"preserve": JsxEmit.Preserve,
"react-native": JsxEmit.ReactNative,
"react": JsxEmit.React
"react": JsxEmit.React,
"vue": JsxEmit.Vue
}),
paramType: Diagnostics.KIND,
showInSimplifiedHelpView: true,
Expand Down
78 changes: 47 additions & 31 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3205,16 +3205,16 @@ namespace ts {
);
}

function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) {
function createSynthesizedIdentifier(name: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) {
// To ensure the emit resolver can properly resolve the namespace, we need to
// treat this identifier as if it were a source tree node by clearing the `Synthesized`
// flag and setting a parent node.
const react = createIdentifier(reactNamespace || "React");
react.flags &= ~NodeFlags.Synthesized;
const ident = createIdentifier(name);
ident.flags &= ~NodeFlags.Synthesized;
// Set the parent that is in parse tree
// this makes sure that parent chain is intact for checker to traverse complete scope tree
react.parent = getParseTreeNode(parent);
return react;
ident.parent = getParseTreeNode(parent);
return ident;
}

function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
Expand All @@ -3225,75 +3225,91 @@ namespace ts {
return createPropertyAccess(left, right);
}
else {
return createReactNamespace(idText(jsxFactory), parent);
return createSynthesizedIdentifier(idText(jsxFactory), parent);
}
}

function createJsxFactoryExpression(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
function createJsxFactoryExpression(jsxFactoryEntity: EntityName | undefined, jsxMode: JsxEmit, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
return jsxFactoryEntity ?
createJsxFactoryExpressionFromEntityName(jsxFactoryEntity, parent) :
createJsxCreateElement(jsxMode, reactNamespace, parent);
}

function createJsxCreateElement(jsxMode: JsxEmit, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) {
return jsxMode === JsxEmit.Vue ?
createSynthesizedIdentifier("h", parent) :
createPropertyAccess(
createReactNamespace(reactNamespace, parent),
createSynthesizedIdentifier(reactNamespace || "React", parent),
"createElement"
);
}

export function createExpressionForJsxElement(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, tagName: Expression, props: Expression, children: ReadonlyArray<Expression>, parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression {
export function createExpressionForJsxElement(jsxFactoryEntity: EntityName | undefined, jsxMode: JsxEmit, reactNamespace: string, tagName: Expression, props: Expression, children: ReadonlyArray<Expression>, parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression {
const argumentsList = [tagName];
if (props) {
argumentsList.push(props);
}

if (children && children.length > 0) {
if (!props) {
argumentsList.push(createNull());
if (jsxMode === JsxEmit.Vue) {
argumentsList.push(createArrayLiteral(children));
}
else {
if (!props) {
argumentsList.push(createNull());
}

if (children.length > 1) {
for (const child of children) {
startOnNewLine(child);
argumentsList.push(child);
if (children.length > 1) {
for (const child of children) {
startOnNewLine(child);
argumentsList.push(child);
}
}
else {
argumentsList.push(children[0]);
}
}
else {
argumentsList.push(children[0]);
}
}

return setTextRange(
createCall(
createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement),
createJsxFactoryExpression(jsxFactoryEntity, jsxMode, reactNamespace, parentElement),
/*typeArguments*/ undefined,
argumentsList
),
location
);
}

export function createExpressionForJsxFragment(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, children: ReadonlyArray<Expression>, parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression {
export function createExpressionForJsxFragment(jsxFactoryEntity: EntityName | undefined, jsxMode: JsxEmit, reactNamespace: string, children: ReadonlyArray<Expression>, parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression {
const tagName = createPropertyAccess(
createReactNamespace(reactNamespace, parentElement),
createSynthesizedIdentifier(reactNamespace || "React", parentElement),
"Fragment"
);

const argumentsList = [<Expression>tagName];
argumentsList.push(createNull());
if (jsxMode === JsxEmit.Vue) {
argumentsList.push(createArrayLiteral(children));
}
else {
argumentsList.push(createNull());

if (children && children.length > 0) {
if (children.length > 1) {
for (const child of children) {
startOnNewLine(child);
argumentsList.push(child);
if (children && children.length > 0) {
if (children.length > 1) {
for (const child of children) {
startOnNewLine(child);
argumentsList.push(child);
}
}
else {
argumentsList.push(children[0]);
}
}
else {
argumentsList.push(children[0]);
}
}

return setTextRange(
createCall(
createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement),
createJsxFactoryExpression(jsxFactoryEntity, jsxMode, reactNamespace, parentElement),
/*typeArguments*/ undefined,
argumentsList
),
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace ts {

transformers.push(transformTypeScript);

if (jsx === JsxEmit.React) {
if (jsx === JsxEmit.React || jsx === JsxEmit.Vue) {
transformers.push(transformJsx);
}

Expand Down
125 changes: 103 additions & 22 deletions src/compiler/transformers/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,36 +88,20 @@ namespace ts {
const tagName = getTagName(node);
let objectProperties: Expression | undefined;
const attrs = node.attributes.properties;
if (attrs.length === 0) {
if (compilerOptions.jsx === JsxEmit.Vue) {
objectProperties = createVueProperties(tagName, attrs);
}
else if (attrs.length === 0) {
// When there are no attributes, React wants "null"
objectProperties = createNull();
}
else {
// Map spans of JsxAttribute nodes into object literals and spans
// of JsxSpreadAttribute nodes into expressions.
const segments = flatten<Expression | ObjectLiteralExpression>(
spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
? map(attrs, transformJsxSpreadAttributeToExpression)
: createObjectLiteral(map(attrs, transformJsxAttributeToObjectLiteralElement))
)
);

if (isJsxSpreadAttribute(attrs[0])) {
// We must always emit at least one object literal before a spread
// argument.
segments.unshift(createObjectLiteral());
}

// Either emit one big object literal (no spread attribs), or
// a call to the __assign helper.
objectProperties = singleOrUndefined(segments);
if (!objectProperties) {
objectProperties = createAssignHelper(context, segments);
}
objectProperties = createReactProperties(attrs);
}

const element = createExpressionForJsxElement(
context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
compilerOptions.jsx!,
compilerOptions.reactNamespace!, // TODO: GH#18217
tagName,
objectProperties,
Expand All @@ -133,9 +117,106 @@ namespace ts {
return element;
}

function createVueProperties(tagName: Expression, attrs: NodeArray<JsxAttributeLike>) {
// it is a custom element if it contains a hypen or it is not a string literal
// Vue treats DOM elements and custom elements differently
let isCustomElement = true;
if (tagName.kind === SyntaxKind.StringLiteral && (<StringLiteral>tagName).text.indexOf("-") === -1) {
isCustomElement = false;
}

return createObjectLiteral(spanMap(
attrs,
attr => getVuePropertyKind(isCustomElement, attr),
createVuePropertyAssignment
));
}

function getVuePropertyKind(isCustomElement: boolean, attr: JsxAttributeLike) {
if (isJsxSpreadAttribute(attr)) {
/*
const expression = transformJsxSpreadAttributeToExpression(attr);
const type = expression.contextualType;
if (type && type.symbol.members && type.symbol.members.size > 0)
console.log(type.symbol.members);
*/
throw new Error("Not implemented");
}

const name = idText(attr.name);

if (["class", "style", "slot", "key", "ref"].indexOf(name) > -1) {
return name;
}
else if (name.indexOf("v-") === 0) {
return "directives";
}
else if (name.indexOf("on") === 0) {
return "on";
}
else {
return isCustomElement
? "props"
: (["value", "innerHTML"].indexOf(name) > -1 ? "domProps" : "attrs");
}
}

function createVuePropertyAssignment(attrs: JsxAttribute[], key: string) {
if (["class", "style", "slot", "key", "ref"].indexOf(key) > -1) {
return transformJsxAttributeToObjectLiteralElement(attrs[0]);
}
else if (key === "directives") {
return createPropertyAssignment(key, createArrayLiteral(map(attrs, createVueDirectiveObjectLiteral)));
}
else {
return createPropertyAssignment(key, createObjectLiteral(map(attrs, transformVueAttributeToObjectLiteralElement)));
}
}

function createVueDirectiveObjectLiteral(attr: JsxAttribute) {
const name = idText(attr.name).substr(2);
return createObjectLiteral([
createPropertyAssignment("name", createStringLiteral(name)),
createPropertyAssignment("value", transformJsxAttributeInitializer(attr.initializer))
]);
}

function transformVueAttributeToObjectLiteralElement(attr: JsxAttribute) {
let name = idText(attr.name);
if (name.indexOf("on") === 0) {
name = name.substr(2);
}
const propName = name.indexOf("-") > -1 ? createStringLiteral(name) : name;
const expression = transformJsxAttributeInitializer(attr.initializer);
return createPropertyAssignment(propName, expression);
}

function createReactProperties(attrs: NodeArray<JsxAttributeLike>) {
let objectProperties: Expression | undefined;
// Map spans of JsxAttribute nodes into object literals and spans
// of JsxSpreadAttribute nodes into expressions.
const segments = flatten<Expression | ObjectLiteralExpression>(spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
? map(attrs, transformJsxSpreadAttributeToExpression)
: createObjectLiteral(map(attrs, transformJsxAttributeToObjectLiteralElement))));
if (isJsxSpreadAttribute(attrs[0])) {
// We must always emit at least one object literal before a spread
// argument.
segments.unshift(createObjectLiteral());
}
// Either emit one big object literal (no spread attribs), or
// a call to the __assign helper.
objectProperties = singleOrUndefined(segments);
if (!objectProperties) {
objectProperties = createAssignHelper(context, segments);
}
return objectProperties;
}


function visitJsxOpeningFragment(node: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, isChild: boolean, location: TextRange) {
const element = createExpressionForJsxFragment(
context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
compilerOptions.jsx!,
compilerOptions.reactNamespace!, // TODO: GH#18217
mapDefined(children, transformJsxChildToExpression),
node,
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4413,7 +4413,8 @@ namespace ts {
None = 0,
Preserve = 1,
React = 2,
ReactNative = 3
ReactNative = 3,
Vue = 4
}

export const enum NewLineKind {
Expand Down

0 comments on commit 56b951f

Please sign in to comment.