vusion-api
Version:
Vusion Node.js API
389 lines • 18.1 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ASTNodeInfo = void 0;
const compiler = __importStar(require("vue-template-compiler"));
const babel = __importStar(require("@babel/core"));
const generator_1 = __importDefault(require("@babel/generator"));
// import * as prettier from 'prettier';
const shared_1 = require("../utils/shared");
class ASTNodeInfo {
constructor(node, parent, route = '') {
this.node = node;
this.parent = parent;
this.route = route;
}
remove() {
const index = this.parent.children.indexOf(this.node);
if (~index)
this.parent.children.splice(index, 1);
else if (this.parent.scopedSlots && this.parent.scopedSlots[this.node.slotTarget] === this.node)
delete this.parent.scopedSlots[this.node.slotTarget];
}
}
exports.ASTNodeInfo = ASTNodeInfo;
;
/**
* 模板 AST 处理器
* 该 class 可以在两端(node, browser)运行
*/
class TemplateHandler {
constructor(code = '', options) {
this.code = code;
this.ast = this.parse(code);
this.options = Object.assign({
tabLength: 4,
startLevel: 0,
}, options);
}
parse(code) {
const compilerOptions = {
preserveWhitespace: false,
outputSourceRange: true,
};
return compiler.compile(code, compilerOptions).ast;
}
generate(options) {
options = Object.assign({}, this.options, options);
// @TODO: 暂时没有很好的 generate
const tabs = ' '.repeat(options.tabLength * options.startLevel);
return tabs + this.generateElement(this.ast, options.startLevel, options) + '\n';
// return this.code;
}
generateElement(el, level, options) {
const tabs = ' '.repeat(options.tabLength * level);
const insideTabs = ' '.repeat(options.tabLength * (level + 1));
let shouldFormat = true;
const children = [].concat(el.children, !el.scopedSlots ? [] : Object.keys(el.scopedSlots).map((key) => el.scopedSlots[key]));
// 需要处理 v-else-if 和 v-else 的情况
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.ifConditions) {
child.ifConditions.forEach((condition) => {
if (condition.block !== child) {
children.splice(i + 1, 0, condition.block);
i++;
}
});
}
}
const content = children.map((node) => {
let text = '';
if (node.type === 1)
text = (shouldFormat ? '\n' + insideTabs : '') + this.generateElement(node, level + 1, options);
else if (node.type === 2)
text = node.text;
else if (node.type === 3)
text = node.text;
else
console.log(node);
shouldFormat = node.type === 1;
return text;
}).join('');
if (!content)
shouldFormat = false;
const attrs = Object.keys(el.attrsMap).map((key) => {
if (key.startsWith('vusion-'))
return '';
const value = el.attrsMap[key];
if (value === '') {
// const attr = (el as any).rawAttrsMap[key];
// if (attr && attr.end - attr.start === key.length)
return key;
}
return `${key}="${value}"`;
});
let attrsLength = 0;
let attrsString = '';
attrs.forEach((attr) => {
if (!attr)
return;
if (attrsLength >= 120 || attr.length >= 120) {
attrsString += '\n' + tabs + ' '.repeat(3); // ' '.repeat(el.tag.length + 1);
attrsLength = 0;
}
attrsString += ' ' + attr;
attrsLength += attr.length;
});
return `<${el.tag}${attrs.length ? attrsString : ''}>` + content + (shouldFormat ? '\n' + tabs : '') + `</${el.tag}>`;
}
traverse(func) {
let queue = [];
queue = queue.concat(new ASTNodeInfo(this.ast, null, ''));
let nodeInfo;
while ((nodeInfo = queue.shift())) {
if (nodeInfo.node.type === 1) {
const parent = nodeInfo.node;
const children = [].concat(parent.children, !parent.scopedSlots ? [] : Object.keys(parent.scopedSlots).map((key) => parent.scopedSlots[key]));
queue.push(...children.map((node, index) => new ASTNodeInfo(node, parent, nodeInfo.route + '/' + index)));
}
const result = func(nodeInfo);
if (result !== undefined)
return result;
}
}
/**
* 根据路径查找子节点
* @param nodePath 节点路径,/1/2 表示根节点的第1个子节点的第2个子节点
* @param node 查找的起始节点
* @examples
* - findByNodePath('', root) 指根节点本身
* - findByNodePath('/', root) 指根节点本身
* - findByNodePath('/0', root) 指第0个子节点
* - findByNodePath('/2/1', root) 指第2个子节点的第1个子节点
*/
findByNodePath(nodePath, node) {
if (nodePath[0] === '/') // 这个里边相对和绝对是一样的
nodePath = nodePath.slice(1);
const arr = nodePath.split('/');
if (!nodePath || !arr.length)
return node;
else {
const parent = node;
const children = [].concat(parent.children, !parent.scopedSlots ? [] : Object.keys(parent.scopedSlots).map((key) => parent.scopedSlots[key]));
return this.findByNodePath(arr.slice(1).join('/'), children[+arr[0]]);
}
}
findByRoute(route, node) {
return this.findByNodePath(route, node);
}
/**
* 该函数处理一个试用阶段
* @param position
*/
findByPosition(position) {
if (typeof position === 'object') {
let pos = 0;
const lines = this.code.split('\n');
for (let i = 0; i < position.line - 1; i++)
pos += lines[i].length + 1;
pos += position.character - this.options.tabLength * this.options.startLevel;
position = pos;
}
let found;
this.traverse((nodeInfo) => {
const node = nodeInfo.node;
if (node.start <= position && position < node.end)
found = node;
});
if (!found)
return this.ast;
return found;
}
/**
* 将另一个 that 的模板合并到当前模板中
* @param that 另一个 TemplateHandler
* @param route 插入的节点路径,最后一位表示节点位置,为空表示最后,比如 /1/2/1 表示插入到根节点的第1个子节点的第2个子节点的第1个位置
* - merge(that, '') 指根节点本身
* - merge(that, '/') 指根节点本身
* - merge(that, '/0') 指第0个子节点
* - merge(that, '/2/1') 指第2个子节点的第1个子节点
* - merge(that, '/2/') 指第2个子节点的最后
* @param replacements 需要跟着替换的样式和变量
*/
merge(that, route, replacements) {
replacements = replacements || {};
replacements.ref = {};
const thisRefKeys = new Set();
this.traverse((nodeInfo) => {
if (nodeInfo.node.type !== 1)
return;
if (nodeInfo.node.attrsMap.ref)
thisRefKeys.add(nodeInfo.node.attrsMap.ref);
});
that.traverse((nodeInfo) => {
if (nodeInfo.node.type !== 1)
return;
const ref = nodeInfo.node.attrsMap.ref;
if (ref) {
const newRef = (0, shared_1.uniqueInMap)(ref, thisRefKeys);
if (newRef !== ref)
replacements['ref'][ref] = newRef;
nodeInfo.node.attrsMap.ref = newRef;
}
});
/**
* Replacements
*/
{
const classKeys = Object.keys(replacements['class'] || {});
// @TODO: 'directives', 'filters'
const identifierMap = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, replacements['props']), replacements['data']), replacements['computed']), replacements['methods']), replacements['data2']), replacements['logic']);
const identifierKeys = Object.keys(identifierMap);
function fix(expr) {
const ast = babel.parse('const __RESULT__ = ' + expr, {
filename: 'file.js',
});
let changed = false;
// 替换是个小概率事件,而且主要是替换 Block,因此不用考虑太多情况
babel.traverse(ast, {
Identifier(nodeInfo) {
if (nodeInfo.parent.type === 'MemberExpression' && nodeInfo.parent.object.type === 'Identifier' && nodeInfo.parent.object.name === '$refs') {
if (replacements['ref'][nodeInfo.node.name]) {
nodeInfo.node.name = replacements['ref'][nodeInfo.node.name];
changed = true;
}
}
if (nodeInfo.parent.type === 'MemberExpression' && nodeInfo.parent.object.type !== 'ThisExpression' && nodeInfo.parent.property === nodeInfo.node)
return nodeInfo.skip();
if (identifierMap[nodeInfo.node.name]) {
nodeInfo.node.name = identifierMap[nodeInfo.node.name];
changed = true;
}
},
Function(nodeInfo) {
nodeInfo.skip();
},
});
return changed ? (0, generator_1.default)(ast.program.body[0].declarations[0].init, { concise: true }).code : expr;
}
// @TODO: v-for 内部作用域的问题
// @TODO: classBinding, styleBinding
that.traverse((nodeInfo) => {
if (nodeInfo.node.type === 1) {
const node = nodeInfo.node;
if (classKeys.length && node.classBinding) {
classKeys.forEach((key) => {
node.attrsMap[':class'] = node.classBinding = node.classBinding
.replace(new RegExp(`(\\$style\\.)${key}(?![-_a-zA-Z0-9])|(\\$style\\[['"])${key}(?![-_a-zA-Z0-9])(['"]\\])`, 'g'), (m, $1, $2, $3) => {
if ($1)
return $1 + replacements['class'][key];
else
return $2 + replacements['class'][key] + $3;
});
});
}
if (identifierKeys.length) {
/* attrsList 里有绑定属性、事件和指令,没有 v-if, v-for 和 class */
node.attrsList.forEach((attr, index) => {
if (attr.name.startsWith(':') || attr.name.startsWith('@') || attr.name.startsWith('v-')) {
const newExpr = fix(attr.value);
if (newExpr !== attr.value) {
attr.value = newExpr;
node.attrsMap[attr.name] = newExpr;
let pureName;
if (attr.name.startsWith(':'))
pureName = attr.name.slice(1);
else if (attr.name.startsWith('v-bind:'))
pureName = attr.name.slice(7);
if (pureName) {
const realAttr = node.attrs.find((realAttr) => realAttr.name === pureName);
realAttr && (realAttr.value = newExpr);
return;
}
if (attr.name.startsWith('@'))
pureName = attr.name.slice(1);
if (attr.name.startsWith('v-on:'))
pureName = attr.name.slice(5);
if (pureName) {
const event = node.events && node.events[pureName];
event && (event.value = newExpr);
const nativeEvent = node.nativeEvents && node.nativeEvents[pureName];
nativeEvent && (nativeEvent.value = newExpr);
return;
}
if (attr.name.startsWith('v-')) {
const directive = node.directives.find((directive) => directive.rawName === attr.name);
if (directive) {
directive.value = newExpr;
if (directive.name === 'model') {
node.model.value = `(${newExpr})`;
node.model.expression = `"${newExpr}"`;
node.model.callback; // = `"${newExpr}"`;
}
}
}
}
}
});
if (node.if) {
const newExpr = fix(node.if);
if (newExpr !== node.if) {
node.if = newExpr;
node.ifConditions[0].exp = newExpr;
node.attrsMap['v-if'] = newExpr;
}
}
if (node.elseif) {
const newExpr = fix(node.elseif);
if (newExpr !== node.elseif) {
node.elseif = newExpr;
node.attrsMap['v-else-if'] = newExpr;
}
}
if (node.for) {
const newExpr = fix(node.for);
if (newExpr !== node.for) {
node.for = newExpr;
node.attrsMap['v-for'] = node.attrsMap['v-for'].replace(/(\s+(?:in|of)\s+)(.+)$/, (m, $1) => $1 + newExpr);
}
}
}
}
else if (nodeInfo.node.type === 2) {
const node = nodeInfo.node;
let changed = false;
const text = node.tokens.map((token) => {
if (typeof token !== 'string') {
const newExpr = fix(token['@binding']);
if (newExpr !== token['@binding']) {
token['@binding'] = newExpr;
changed = true;
}
return `{{ ${token['@binding']} }}`;
}
else
return token;
}).join('');
if (changed)
node.text = text;
}
});
}
let el;
let index = 0;
if (typeof route === 'string') {
if (route[0] === '/') // 这个里边相对和绝对是一样的
route = route.slice(1);
const arr = route.split('/');
const last = arr[arr.length - 1];
const parentNodePath = arr.slice(0, -1).join('/');
el = this.findByNodePath(parentNodePath, this.ast);
index = last === undefined || last === '' ? el.children.length : +last;
}
else {
el = this.findByPosition(route);
index = el.children.length;
}
if (!el.children)
throw new Error(`Not an element node! route: ${route}`);
el.children.splice(index, 0, that.ast);
}
}
exports.default = TemplateHandler;
//# sourceMappingURL=TemplateHandler.js.map
;