You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

525 lines
18 KiB

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createStructuralDirectiveTransform = exports.createTransformContext = exports.traverseChildren = exports.traverseNode = exports.transform = exports.isScopedSlotVFor = exports.isVForScope = exports.isVIfScope = exports.isRootScope = void 0;
const shared_1 = require("@vue/shared");
const types_1 = require("@babel/types");
const compiler_core_1 = require("@vue/compiler-core");
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
const identifier_1 = __importDefault(require("./identifier"));
const runtimeHelpers_1 = require("./runtimeHelpers");
const ast_1 = require("./ast");
const utils_1 = require("./transforms/utils");
const codegen_1 = require("./codegen");
function isRootScope(scope) {
return !isVIfScope(scope) && !isVForScope(scope);
}
exports.isRootScope = isRootScope;
function isVIfScope(scope) {
return (!!scope.condition ||
scope.name === 'else');
}
exports.isVIfScope = isVIfScope;
function isVForScope(scope) {
return !!scope.source;
}
exports.isVForScope = isVForScope;
function isScopedSlotVFor({ source }) {
if (source.type !== 8 /* NodeTypes.COMPOUND_EXPRESSION */) {
return false;
}
const first = source.children[0];
return (first.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ &&
first.content.includes(utils_1.SCOPED_SLOT_IDENTIFIER));
}
exports.isScopedSlotVFor = isScopedSlotVFor;
function transform(root, options) {
const context = createTransformContext(root, options);
findRootNode(root, context);
traverseNode(root, context);
root.renderData = createRenderDataExpr(context.scope.properties, context);
// finalize meta information
root.helpers = new Set([...context.helpers.keys()]);
root.components = [...context.components];
root.imports = context.imports;
root.cached = context.cached;
return context;
}
exports.transform = transform;
function findRootNode(root, context) {
const children = root.children.filter((node) => node.type === 1 /* NodeTypes.ELEMENT */ && node.tag !== 'template');
if (children.length === 1) {
context.rootNode = children[0];
}
}
function traverseNode(node, context) {
context.currentNode = node;
// apply transform plugins
const { nodeTransforms } = context;
const exitFns = [];
for (let i = 0; i < nodeTransforms.length; i++) {
const onExit = nodeTransforms[i](node, context);
if (onExit) {
if ((0, shared_1.isArray)(onExit)) {
exitFns.push(...onExit);
}
else {
exitFns.push(onExit);
}
}
if (!context.currentNode) {
// node was removed
return;
}
else {
// node may have been replaced
node = context.currentNode;
}
}
switch (node.type) {
case 3 /* NodeTypes.COMMENT */:
// context.helper(CREATE_COMMENT)
break;
case 5 /* NodeTypes.INTERPOLATION */:
context.helper(compiler_core_1.TO_DISPLAY_STRING);
break;
// for container types, further traverse downwards
case 9 /* NodeTypes.IF */:
for (let i = 0; i < node.branches.length; i++) {
traverseNode(node.branches[i], context);
}
break;
case 10 /* NodeTypes.IF_BRANCH */:
case 11 /* NodeTypes.FOR */:
case 1 /* NodeTypes.ELEMENT */:
case 0 /* NodeTypes.ROOT */:
traverseChildren(node, context);
break;
}
// exit transforms
context.currentNode = node;
let i = exitFns.length;
while (i--) {
exitFns[i]();
}
}
exports.traverseNode = traverseNode;
function traverseChildren(parent, context) {
let i = 0;
const nodeRemoved = () => {
i--;
};
for (; i < parent.children.length; i++) {
const child = parent.children[i];
if ((0, shared_1.isString)(child))
continue;
context.parent = parent;
context.childIndex = i;
context.onNodeRemoved = nodeRemoved;
traverseNode(child, context);
}
}
exports.traverseChildren = traverseChildren;
function defaultOnError(error) {
throw error;
}
function defaultOnWarn(msg) {
console.warn(`[Vue warn] ${msg.message}`);
}
function createTransformContext(rootNode, { root = '', filename = '', isTS = false, inline = false, hashId = null, scopeId = null, filters = [], bindingCssVars = [], bindingMetadata = shared_1.EMPTY_OBJ, cacheHandlers = false, prefixIdentifiers = false, skipTransformIdentifier = false, renderDataSpread = false, nodeTransforms = [], directiveTransforms = {}, miniProgram = {
class: {
array: true,
},
slot: {
fallbackContent: false,
dynamicSlotNames: true,
},
directive: '',
}, isBuiltInComponent = shared_1.NOOP, isCustomElement = shared_1.NOOP, expressionPlugins = [], onError = defaultOnError, onWarn = defaultOnWarn, }) {
const rootScope = {
id: new identifier_1.default(),
identifiers: [],
properties: [],
parent: null,
};
function findVIfParentScope() {
for (let i = scopes.length - 1; i >= 0; i--) {
const scope = scopes[i];
if (isVForScope(scope) || isRootScope(scope)) {
return scope;
}
}
return rootScope;
}
function createScope(id, initScope) {
return (0, shared_1.extend)({
id,
properties: [],
parent: scopes[scopes.length - 1],
get identifiers() {
return Object.keys(identifiers);
},
}, initScope);
}
const vueIds = [];
const identifiers = Object.create(null);
const scopes = [rootScope];
const miniProgramComponents = (0, uni_cli_shared_1.findMiniProgramUsingComponents)({
filename,
componentsDir: miniProgram.component?.dir,
inputDir: root,
});
// const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
const context = {
// options
// 暂不提供根据文件名生成递归组件
selfName: '',
miniProgram,
isTS,
inline,
hashId,
scopeId,
filters,
bindingCssVars,
bindingMetadata,
cacheHandlers,
prefixIdentifiers,
nodeTransforms,
directiveTransforms,
expressionPlugins,
skipTransformIdentifier,
renderDataSpread,
isBuiltInComponent,
isCustomElement,
onError,
onWarn,
// state
parent: null,
childIndex: 0,
helpers: new Map(),
components: new Set(),
imports: [],
bindingComponents: Object.create(null),
cached: 0,
identifiers,
scope: rootScope,
scopes: {
vFor: 0,
vueId: 0,
},
get currentScope() {
return scopes[scopes.length - 1];
},
currentNode: rootNode,
vueIds,
get currentVueId() {
return vueIds[vueIds.length - 1];
},
inVOnce: false,
get inVFor() {
let parent = scopes[scopes.length - 1];
while (parent) {
if (isVForScope(parent) && !isScopedSlotVFor(parent)) {
return true;
}
parent = parent.parent;
}
return false;
},
// methods
getScopeIndex(scope) {
return scopes.indexOf(scope);
},
popScope() {
return scopes.pop();
},
addVIfScope(initScope) {
const vIfScope = createScope(scopes[scopes.length - 1].id, (0, shared_1.extend)(initScope, { parentScope: findVIfParentScope() }));
scopes.push(vIfScope);
return vIfScope;
},
addVForScope(initScope) {
const vForScope = createScope(new identifier_1.default(), initScope);
scopes.push(vForScope);
return vForScope;
},
helper(name) {
const count = context.helpers.get(name) || 0;
context.helpers.set(name, count + 1);
return name;
},
removeHelper(name) {
const count = context.helpers.get(name);
if (count) {
const currentCount = count - 1;
if (!currentCount) {
context.helpers.delete(name);
}
else {
context.helpers.set(name, currentCount);
}
}
},
helperString(name) {
return `_${compiler_core_1.helperNameMap[context.helper(name)]}`;
},
replaceNode(node) {
context.parent.children[context.childIndex] = context.currentNode = node;
},
removeNode(node) {
if (!context.parent) {
throw new Error(`Cannot remove root node.`);
}
const list = context.parent.children;
const removalIndex = node
? list.indexOf(node)
: context.currentNode
? context.childIndex
: -1;
/* istanbul ignore if */
if (removalIndex < 0) {
throw new Error(`node being removed is not a child of current parent`);
}
if (!node || node === context.currentNode) {
// current node removed
context.currentNode = null;
context.onNodeRemoved();
}
else {
// sibling node removed
if (context.childIndex > removalIndex) {
context.childIndex--;
context.onNodeRemoved();
}
}
context.parent.children.splice(removalIndex, 1);
},
onNodeRemoved: () => { },
addIdentifiers(exp) {
if ((0, shared_1.isString)(exp)) {
addId(exp);
}
else if (exp.identifiers) {
exp.identifiers.forEach(addId);
}
else if (exp.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */) {
addId(exp.content);
}
},
removeIdentifiers(exp) {
if ((0, shared_1.isString)(exp)) {
removeId(exp);
}
else if (exp.identifiers) {
exp.identifiers.forEach(removeId);
}
else if (exp.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */) {
removeId(exp.content);
}
},
cache(exp, isVNode = false) {
return createCacheExpression(context.cached++, exp, isVNode);
},
isMiniProgramComponent(name) {
return miniProgramComponents[name];
},
rootNode: null,
};
function addId(id) {
const { identifiers } = context;
if (identifiers[id] === undefined) {
identifiers[id] = 0;
}
identifiers[id]++;
}
function removeId(id) {
context.identifiers[id]--;
}
return context;
}
exports.createTransformContext = createTransformContext;
function createCacheExpression(index, value, isVNode = false) {
return {
type: 20 /* NodeTypes.JS_CACHE_EXPRESSION */,
index,
value,
isVNode,
loc: compiler_core_1.locStub,
};
}
function createStructuralDirectiveTransform(name, fn) {
const matches = (0, shared_1.isString)(name)
? (n) => n === name
: (n) => name.test(n);
return (node, context) => {
if (node.type === 1 /* NodeTypes.ELEMENT */) {
const { props } = node;
// structural directive transforms are not concerned with slots
// as they are handled separately in vSlot.ts
// if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) {
// return
// }
const exitFns = [];
for (let i = 0; i < props.length; i++) {
const prop = props[i];
if (prop.type === 7 /* NodeTypes.DIRECTIVE */ && matches(prop.name)) {
// structural directives are removed to avoid infinite recursion
// also we remove them *before* applying so that it can further
// traverse itself in case it moves the node around
props.splice(i, 1);
i--;
const onExit = fn(node, prop, context);
if (onExit)
exitFns.push(onExit);
}
}
return exitFns;
}
};
}
exports.createStructuralDirectiveTransform = createStructuralDirectiveTransform;
function createRenderDataExpr(properties, context) {
const objExpr = (0, ast_1.createObjectExpression)(properties);
if (!hasSpreadElement(objExpr)) {
return objExpr;
}
// filters: ['test']
// v-if="text.aa()"
if (context.filters.length) {
transformFilterObjectSpreadExpr(objExpr, context);
}
if (context.renderDataSpread) {
return objExpr;
}
return transformObjectSpreadExpr(objExpr, context);
}
function hasSpreadElement(expr) {
return expr.properties.some((prop) => {
if ((0, types_1.isSpreadElement)(prop)) {
return true;
}
else {
const returnStatement = parseReturnStatement(prop);
if (returnStatement) {
return hasSpreadElement(returnStatement.argument);
}
}
});
}
// 目前硬编码识别 _f,应该读取 context.helperString
const returnObjExprMap = {
_f: 1,
_w: 0, // _w(()=>{return {}})
};
function parseReturnStatement(prop) {
if ((0, types_1.isObjectProperty)(prop) &&
(0, types_1.isCallExpression)(prop.value) &&
(0, types_1.isIdentifier)(prop.value.callee)) {
const { name } = prop.value.callee;
if ((0, shared_1.hasOwn)(returnObjExprMap, name)) {
return prop.value.arguments[returnObjExprMap[name]].body.body[0];
}
}
}
function transformObjectPropertyExpr(prop, context) {
// vFor,withScopedSlot
const returnStatement = parseReturnStatement(prop);
if (returnStatement) {
const objExpr = returnStatement.argument;
if (hasSpreadElement(objExpr)) {
returnStatement.argument = transformObjectSpreadExpr(objExpr, context);
}
}
return prop;
}
function transformObjectSpreadExpr(objExpr, context) {
const properties = objExpr.properties;
const args = [];
let objExprProperties = [];
properties.forEach((prop) => {
if ((0, types_1.isObjectProperty)(prop)) {
objExprProperties.push(transformObjectPropertyExpr(prop, context));
}
else {
if (objExprProperties.length) {
args.push((0, types_1.objectExpression)(objExprProperties));
}
args.push(transformConditionalExpression(prop.argument, context));
objExprProperties = [];
}
});
if (objExprProperties.length) {
args.push((0, types_1.objectExpression)(objExprProperties));
}
if (args.length === 1) {
return args[0];
}
return (0, types_1.callExpression)((0, types_1.identifier)(context.helperString(runtimeHelpers_1.EXTEND)), args);
}
function transformConditionalExpression(expr, context) {
const { consequent, alternate } = expr;
if ((0, types_1.isObjectExpression)(consequent) && hasSpreadElement(consequent)) {
expr.consequent = transformObjectSpreadExpr(consequent, context);
}
if ((0, types_1.isObjectExpression)(alternate)) {
if (hasSpreadElement(alternate)) {
expr.alternate = transformObjectSpreadExpr(alternate, context);
}
}
else if ((0, types_1.isConditionalExpression)(alternate)) {
transformConditionalExpression(alternate, context);
}
return expr;
}
function transformFilterObjectSpreadExpr(objExpr, context) {
const properties = objExpr.properties;
properties.forEach((prop) => {
if ((0, types_1.isObjectProperty)(prop)) {
transformFilterObjectPropertyExpr(prop, context);
}
else {
prop.argument = transformFilterConditionalExpression(prop.argument, context);
}
});
}
function transformFilterObjectPropertyExpr(prop, context) {
// vFor, withScopedSlot
const returnStatement = parseReturnStatement(prop);
if (returnStatement) {
const objExpr = returnStatement.argument;
if (hasSpreadElement(objExpr)) {
transformFilterObjectSpreadExpr(objExpr, context);
}
}
}
function transformFilterConditionalExpression(expr, context) {
const { test, consequent, alternate } = expr;
if ((0, types_1.isObjectExpression)(consequent) && hasSpreadElement(consequent)) {
transformFilterObjectSpreadExpr(consequent, context);
}
if ((0, types_1.isObjectExpression)(alternate)) {
if (hasSpreadElement(alternate)) {
transformFilterObjectSpreadExpr(alternate, context);
}
}
else if ((0, types_1.isConditionalExpression)(alternate)) {
expr.alternate = transformFilterConditionalExpression(alternate, context);
}
const testCode = (0, codegen_1.genBabelExpr)(test);
// filter test
if (context.filters.find((filter) => testCode.includes(filter + '.'))) {
// test.aa() ? {a:1} : {b:2} => {...{a:1},...{b:2}}
const properties = [];
if (!(0, types_1.isObjectExpression)(consequent) || consequent.properties.length) {
properties.push((0, types_1.spreadElement)(consequent));
}
if (!(0, types_1.isObjectExpression)(expr.alternate) ||
expr.alternate.properties.length) {
properties.push((0, types_1.spreadElement)(expr.alternate));
}
return (0, types_1.objectExpression)(properties);
}
return expr;
}