@progress/kendo-ui
Version:
This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.
165 lines (141 loc) • 4.7 kB
JavaScript
;
const { transformTemplateStringProperties, parseKendoTemplate, buildArrowFunctionString, buildComplexArrowFunctionString } = require('../_shared.cjs');
function getAttrValue(attrs, name) {
const m = attrs.match(new RegExp(`(?:^|\\s)${name}\\s*=\\s*["']([^"']*)["']`, 'i'));
return m ? m[1] : null;
}
function isKendoTemplateScript(attrs) {
const type = getAttrValue(attrs, 'type');
if (type === null) {
return false;
}
const lower = type.toLowerCase();
return lower === 'text/x-kendo-template' || lower === 'text/x-kendo-tmpl';
}
function isJQuerySelectorCall(node) {
return (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(node.callee.name === '$' || node.callee.name === 'jQuery') &&
node.arguments.length === 1 &&
(node.arguments[0].type === 'StringLiteral' ||
(node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string'))
);
}
function getIdFromJQuerySelector(node) {
if (!isJQuerySelectorCall(node)) {
return null;
}
const val = node.arguments[0].value;
if (!val.startsWith('#') || val.length < 2) {
return null;
}
if (/\s/.test(val)) {
return null;
}
return val.slice(1);
}
function buildTemplateMap(source) {
const map = new Map();
const re = /<script([^>]*)>([\s\S]*?)<\/script>/gi;
let m;
while ((m = re.exec(source)) !== null) {
const attrs = m[1];
if (!isKendoTemplateScript(attrs)) {
continue;
}
const id = getAttrValue(attrs, 'id');
if (!id) {
continue;
}
map.set(id, m[2].trim().replace(/\r\n/g, '\n').replace(/\r/g, '\n'));
}
return map;
}
function processScriptContent(content, j, templateMap, usedIds) {
let root;
try {
root = j(content);
} catch {
return content;
}
let changed = false;
root.find(j.CallExpression, {
callee: { type: 'MemberExpression', property: { name: 'html' } },
}).filter(path => {
const obj = path.node.callee.object;
const id = getIdFromJQuerySelector(obj);
return id !== null && templateMap.has(id);
}).forEach(path => {
const id = getIdFromJQuerySelector(path.node.callee.object);
const templateContent = templateMap.get(id);
j(path).replaceWith(j.stringLiteral(templateContent));
usedIds.add(id);
changed = true;
});
if (!changed) {return content;}
transformTemplateStringProperties(root, j);
return root.toSource({ quote: 'single' });
}
function removeTemplateScriptTag(source, id) {
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp(
`[ \\t]*<script[^>]*\\bid\\s*=\\s*["']${escaped}["'][^>]*>[\\s\\S]*?<\\/script>\\r?\\n?`,
'gi'
);
return source.replace(re, '');
}
function toValidIdentifier(id) {
return id.replace(/[-_\s]+(.)/g, (_, ch) => ch.toUpperCase()).replace(/[^a-zA-Z0-9$]/g, '_').replace(/^([0-9])/, '_$1');
}
function convertTemplateToVarDeclaration(source, id, content) {
const tokens = content.includes('#') ? parseKendoTemplate(content) : [{ type: 'text', value: content }];
if (!tokens) {
return source;
}
let arrowFnStr;
if (tokens.some(t => t.type === 'code')) {
arrowFnStr = buildComplexArrowFunctionString(tokens);
} else {
arrowFnStr = buildArrowFunctionString(tokens);
}
const varName = toValidIdentifier(id);
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp(
`[ \\t]*<script[^>]*\\bid\\s*=\\s*["']${escaped}["'][^>]*>[\\s\\S]*?<\\/script>\\r?\\n?`,
'gi'
);
return source.replace(re, `<script>\nvar ${varName} = ${arrowFnStr};\n</script>\n`);
}
module.exports = function transformer(fileInfo, api) {
if (!/\.html?$/i.test(fileInfo.path)) {
return undefined;
}
const source = fileInfo.source;
const templateMap = buildTemplateMap(source);
if (templateMap.size === 0) {return undefined;}
const j = api.jscodeshift;
const usedIds = new Set();
let changed = false;
let result = source.replace(/<script([^>]*)>([\s\S]*?)<\/script>/gi, (match, attrs, content) => {
if (/\bsrc\b/.test(attrs)) {return match;}
if (isKendoTemplateScript(attrs)) {return match;}
const transformed = processScriptContent(content, j, templateMap, usedIds);
if (transformed !== content) {
changed = true;
return `<script${attrs}>${transformed}</script>`;
}
return match;
});
for (const id of usedIds) {
result = removeTemplateScriptTag(result, id);
}
for (const [id, content] of templateMap) {
if (usedIds.has(id)) {
continue;
}
result = convertTemplateToVarDeclaration(result, id, content);
changed = true;
}
return changed ? result : undefined;
};