UNPKG

@zakijs/plugin-compiler-alipay

Version:

mor complier plugin for alipay mini program

372 lines 15.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.templateProcessorToOther = void 0; const utils_1 = require("@morjs/utils"); const templateEvents_1 = require("./templateEvents"); const templateTags_1 = require("./templateTags"); /** * 自定义 template 处理 * 处理 支付宝转 其他小程序的兼容性 */ exports.templateProcessorToOther = { onNode(node, options) { processComponentCompatible(node, options); if (node.content && node.content.length) { node.content = node.content.map(function (value) { // 处理函数调用 return processAttrFuncionCall( // 处理模版字符串 processTemplateString(value)); }); } }, onNodeExit(node, options, context) { processEventProxy(node, options, context); }, onNodeAttr(attrName, node, options, context) { // polyfills // 针对 native tags if (node.attrs[attrName] === '' && node.tag && (0, templateTags_1.isNativeTag)(node.tag)) { ; node.attrs[attrName] = true; } // a:else 检查 processAElseCheck(attrName, node, options); if (node.attrs[attrName]) { // 方法调用转换支持 node.attrs[attrName] = processAttrFuncionCall( // 模版字符串处理 processTemplateString(node.attrs[attrName])); } // 事件处理 processEventsAttributes(attrName, node, options, context); // 初始 style 对象支持 processStyleAttrObject(attrName, node, context); if (node.tag && (0, templateTags_1.isNativeTag)(node.tag)) { // 属性映射及替换 const replaceAttrName = (0, templateEvents_1.getNativePropName)(node.tag, attrName); if (replaceAttrName !== attrName) { node.attrs[replaceAttrName] = node.attrs[attrName]; delete node.attrs[attrName]; } } else { // 非 native 的样式兼容 // NOTE: 需要了解原因 if (attrName === 'style') { node.attrs['morStyle'] = node.attrs['style']; delete node.attrs['style']; } } if (typeof node.attrs[attrName] === 'string' && /\n/.test(node.attrs[attrName])) { // 其他平台 attr 值 不支持换行 node.attrs[attrName] = node.attrs[attrName].replace(/\n/g, '').trim(); } } }; // 事件代理名称 const PROXY_EVENT_NAME = '$morEventHandlerProxy'; const EVENT_HANDLER_NAME = 'data-mor-event-handlers'; const PROXY_DISABLE_EVENT_NAME = '$morDisableScrollProxy'; /** * 将时间代理存储到 node 节点上 */ function processEventProxy(node, options, context) { if (context.morHandlersMap && Object.keys(context.morHandlersMap).length) { node.attrs[EVENT_HANDLER_NAME] = Buffer.from(JSON.stringify(context.morHandlersMap)).toString('base64'); delete context.morHandlersMap; } } /** * 处理事件转换 */ function processEventsAttributes(attrName, node, options, context) { // 如果 tag 不存在, 则跳过 if (!node.tag) return; const morHandlersMap = context.morHandlersMap || {}; // 判断是否是 onTap 等以 on 开头的事件属性 if ((0, templateEvents_1.isEventAttr)(attrName)) { // eventName 是去掉 on 且把事件名首字母改成小写 // 比如 onTouchStart 将会得到touchStart const eventName = (0, templateEvents_1.getEventName)(attrName); const isNativeEventName = (0, templateEvents_1.isNativeEvent)(eventName, node.tag); // 组件的原生事件不走代理模式 if (isNativeEventName) { const newAttr = `bind:${(0, templateEvents_1.getNativeEventName)(eventName, node.tag)}`; node.attrs[newAttr] = node.attrs[attrName]; } else { const newAttr = `bind:${eventName}`; node.attrs[newAttr] = PROXY_EVENT_NAME; morHandlersMap[eventName] = node.attrs[attrName]; } delete node.attrs[attrName]; } // catchTap 等以 catch 开头的事件属性 else if ((0, templateEvents_1.isCatchEventAttr)(attrName)) { const eventName = (0, templateEvents_1.getEventName)(attrName); // catch 暂且理解为只有原生事件, 后续需要提供更好的兼容 const newAttr = `catch:${(0, templateEvents_1.getNativeEventName)(eventName, node.tag)}`; node.attrs[newAttr] = node.attrs[attrName]; delete node.attrs[attrName]; } // 支持 ref 引用传递 else if (attrName === 'ref') { node.attrs['bind:ref'] = PROXY_EVENT_NAME; morHandlersMap.ref = node.attrs[attrName]; delete node.attrs[attrName]; } context.morHandlersMap = morHandlersMap; } /** * 检查 a:else 有效性 */ function processAElseCheck(attrName, node, options) { if (attrName === 'a:else') { const value = node.attrs[attrName]; if (value !== '' && value !== true) { utils_1.logger.warn('a:else 不能有业务逻辑,请直接使用 a:else 即可\n' + `文件路径: ${options.fileInfo.path}`); } node.attrs[attrName] = true; } } /** * 处理组件兼容性 */ function processComponentCompatible(node, options) { // text 标签兼容 if (node.tag === 'text' && node.attrs && node.attrs['number-of-lines']) { const numberOfLines = node.attrs['number-of-lines']; const lines = Number(numberOfLines); let appendStyle; if ((isNaN(lines) && typeof numberOfLines === 'string' && numberOfLines.startsWith('{{')) || lines >= 1) { // 这里的处理和支付宝的做法保持一致,单行文本也是用这种方式处理 appendStyle = 'overflow:hidden;text-overflow:ellipsis;' + `display:-webkit-box;-webkit-line-clamp:${isNaN(lines) ? numberOfLines : lines};-webkit-box-orient:vertical;user-select:none;`; } else { utils_1.logger.warn('text 组件的 number-of-lines 属性请设置为 >=1 的数字\n' + `文件路径: ${options.fileInfo.path}`); } // 样式兼容 if (appendStyle) { if (node.attrs.style) { node.attrs.style = `${appendStyle}${node.attrs.style}`; } else { node.attrs.style = appendStyle; } } /** * TODO: 后面看下其他端是否也有同样的问题 * 在微信下,如果文本内容有换行符会导致缩略文本的样式异常 **/ if (node.content && node.content.length === 1 && typeof node.content[0] === 'string') { node.content[0] = node.content[0].replace(/\n/g, ''); } delete node.attrs['number-of-lines']; } // button 标签兼容 if (node.tag === 'button' && node.attrs) { // 授权相关 if (node.attrs['open-type'] === 'getAuthorize') { if (!node.attrs['onGetAuthorize']) { utils_1.logger.warn('button 组件, 用户授权相关需要设置 onGetAuthorize 回调\n' + `文件地址: ${options.fileInfo.path}`); } // 手机号码授权 if (node.attrs.scope === 'phoneNumber') { node.attrs['open-type'] = 'getPhoneNumber'; node.attrs['bind:getphonenumber'] = node.attrs['onGetAuthorize']; delete node.attrs['onGetAuthorize']; } // 用户信息授权 if (node.attrs.scope === 'userInfo') { node.attrs['open-type'] = 'getUserInfo'; node.attrs['bind:getuserinfo'] = node.attrs['onGetAuthorize']; delete node.attrs['onGetAuthorize']; } } } // 通过 catchtouchmove 来实现阻止 view 滚动 if (node.tag === 'view' && node.attrs && // view 上面有 disable-scroll 属性 'disable-scroll' in node.attrs && // view 维度的禁用 !('data-mor-transform-disable' in node.attrs)) { if ('catchTouchMove' in node.attrs) { utils_1.logger.warn('view 的 disable-scroll 属性抹平需要用到 catchTouchMove 事件, 当前文件中被占用\n' + `文件路径: ${options.fileInfo.path}`); } else { const value = node.attrs['disable-scroll']; const reg = /\{\{([\w\W]*?)\}\}/m; node.attrs['data-disable-scroll'] = node.attrs['disable-scroll']; // 如果是 {{variable}} 的类型 if (reg.test(value)) { const match = reg.exec(value); if (match) { node.attrs['catchTouchMove'] = `{{(${match[1]}) ? '${PROXY_DISABLE_EVENT_NAME}' : ''}}`; } else { utils_1.logger.warn('view 的 disable-scroll 属性匹配出错\n' + `文件路径: ${options.fileInfo.path}`); } } else { node.attrs['catchTouchMove'] = value === 'true' ? PROXY_DISABLE_EVENT_NAME : ''; } delete node.attrs['disable-scroll']; } } } function unicodeEscapeRegexp() { return /\\u([a-f0-9]{4})/gi; } /** * 判断是否有转义的 unicode 字符 * @param content - 内容 * @returns 是否有转义的 unicode 字符 */ function hasEscapedUnicode(content) { return unicodeEscapeRegexp().test(content); } /** * 处理 模版字符串 * 兼容支付宝小程序模版字符串语法:{{`xxx ${a}`}} 会被转化为 {{'xxx' + a}} */ function processTemplateString(content) { if (typeof content === 'string') { if ((content.indexOf('{{') < content.indexOf('`') && content.indexOf('`') < content.indexOf('}}')) || /\{\{[\w\W]*?`[\w\W]*?`[\w\W]*?\}\}/.test(content)) { return content.replace(/\{\{([\w\W]*?)\}\}/g, (_, matchStr) => { let es5Code = utils_1.typescript .transpileModule(matchStr, { compilerOptions: { module: utils_1.typescript.ModuleKind.None, target: utils_1.typescript.ScriptTarget.ES5, noImplicitUseStrict: true } }) .outputText.replace(/;\n/, ''); // 处理双引号包裹单引号的问题 // 由于后续的逻辑中会将双引号全部替换为单引号 // 这里将单引号进行转义处理 if (es5Code.includes('"') && es5Code.includes("'")) { es5Code = es5Code.replace(/"([^"]*)"/g, function (s) { return s.replace(/([^\\])?'/g, "$1\\'"); }); } es5Code = es5Code.replace(/"/g, "'").replace(/'\\''/g, "'\"'"); // 判断是否存在 cjk 字符被转换为了 \uxxxx // 判断条件为: 转换前不符合 UNICODE_ESCAPE_REGEXP 规则 // 转换后符合 UNICODE_ESCAPE_REGEXP 规则 if (!hasEscapedUnicode(matchStr) && hasEscapedUnicode(es5Code)) { // 修复 ts 处理 tempalte literal 时会将中文字符转换为 \uxxxx 的问题 // 将其恢复为中文字符, 避免 *xml 渲染时显示错误 es5Code = es5Code.replace(unicodeEscapeRegexp(), function (_, hex) { return String.fromCharCode(parseInt(hex, 16)); }); } return `{{${es5Code}}}`; }); } } return content; } /** * 处理 style 属性中的对象 * 增加 {{ height: '20rpx' }} 转换支持,将其转换为 {{morSjs.s({ height: '20rpx' })}} */ function processStyleAttrObject(attrName, node, context) { if (attrName === 'style' && typeof node.attrs[attrName] === 'string' && /^ *\{\{[\n ]*[a-zA-Z0-9$_]+ *\:[\w\W]+\}\} *$/.test(node.attrs[attrName])) { node.attrs[attrName] = node.attrs[attrName].replace(/\{\{([\w\W]*?)\}\}/g, (_, matchStr) => { // 标记为需要注入 sjs 对象支持 context.injectSjsObjectSupport = true; return `{{morSjs.s({${matchStr}})}}`; }); } } // 支持的函数调用 const SUPPORT_FUNCTION_CALL_NAMES = [ 'toLowerCase', 'toUpperCase', 'slice', 'includes', 'toString', 'indexOf' ]; // 判断是否包含符合条件的调用方式 const FUNCTION_CALL_REGEXP = new RegExp(`(\\.(${SUPPORT_FUNCTION_CALL_NAMES.join('|')})\\(|typeof )`); /** * 处理属性中的方法调用,支持: * - typeof a === 'string' => morSjs.toType(a) === 'string' * - a.toLowerCase() => morSjs.toLowerCase(a) * - a.toUpperCase() => morSjs.toUpperCase(a) * - a.slice(0,1) => morSjs.slice(a, 0, 1) * - a.includes(b) => morSjs.includes(a, b) * - a.indexOf(b) => morSjs.indexOf(a, b) * - a.toString() => morSjs.toString(a) */ function processAttrFuncionCall(value) { if (!value) return value; if (typeof value !== 'string') return value; if (!/{{(.*?)}}/.test(value)) return value; if (!FUNCTION_CALL_REGEXP.test(value)) return value; return value.replace(/{{(.*?)}}/g, function (matchStr, captureStr) { if (!FUNCTION_CALL_REGEXP.test(matchStr)) return matchStr; utils_1.typescript.transpileModule(captureStr, { compilerOptions: { module: utils_1.typescript.ModuleKind.None, target: utils_1.typescript.ScriptTarget.Latest, noImplicitUseStrict: true }, transformers: { before: [ (0, utils_1.tsTransformerFactory)(function (node) { var _a, _b; // 处理 typeof if (utils_1.typescript.isTypeOfExpression(node)) { captureStr = captureStr.replace(node.getText(), `morSjs.toType(${node.expression.getFullText().trim()})`); } // 处理函数调用 if (utils_1.typescript.isCallExpression(node) && utils_1.typescript.isPropertyAccessExpression(node.expression)) { const functionName = (_b = (_a = node.expression .getChildAt(node.expression.getChildCount() - 1)) === null || _a === void 0 ? void 0 : _a.getText) === null || _b === void 0 ? void 0 : _b.call(_a); if (SUPPORT_FUNCTION_CALL_NAMES.includes(functionName)) { const arg1 = node.expression .getText() .replace(new RegExp(`\\.${functionName}$`), ''); const allArgs = [arg1].concat(node.arguments.map((arg) => arg.getText())); captureStr = captureStr.replace(node.getText(), `morSjs.${functionName}(${allArgs.join(',')})`); } } return node; }) ] } }); return `{{${captureStr}}}`; }); } //# sourceMappingURL=templateProcessorToOther.js.map