UNPKG

@kdujs/compiler-dom

Version:
507 lines (492 loc) 16.2 kB
/** * @kdujs/compiler-dom v3.4.27 * (c) 2021-present NKDuy * @license MIT **/ import { registerRuntimeHelpers, createSimpleExpression, createCompilerError, createObjectProperty, getConstantType, createCallExpression, TO_DISPLAY_STRING, transformModel as transformModel$1, findProp, hasDynamicKeyKBind, findDir, isStaticArgOf, transformOn as transformOn$1, isStaticExp, createCompoundExpression, checkCompatEnabled, noopDirectiveTransform, baseCompile, baseParse } from '@kdujs/compiler-core'; export * from '@kdujs/compiler-core'; import { isVoidTag, isHTMLTag, isSVGTag, isMathMLTag, parseStringStyle, capitalize, makeMap, extend } from '@kdujs/shared'; const K_MODEL_RADIO = Symbol(!!(process.env.NODE_ENV !== "production") ? `kModelRadio` : ``); const K_MODEL_CHECKBOX = Symbol(!!(process.env.NODE_ENV !== "production") ? `kModelCheckbox` : ``); const K_MODEL_TEXT = Symbol(!!(process.env.NODE_ENV !== "production") ? `kModelText` : ``); const K_MODEL_SELECT = Symbol(!!(process.env.NODE_ENV !== "production") ? `kModelSelect` : ``); const K_MODEL_DYNAMIC = Symbol(!!(process.env.NODE_ENV !== "production") ? `kModelDynamic` : ``); const K_ON_WITH_MODIFIERS = Symbol(!!(process.env.NODE_ENV !== "production") ? `kOnModifiersGuard` : ``); const K_ON_WITH_KEYS = Symbol(!!(process.env.NODE_ENV !== "production") ? `kOnKeysGuard` : ``); const K_SHOW = Symbol(!!(process.env.NODE_ENV !== "production") ? `kShow` : ``); const TRANSITION = Symbol(!!(process.env.NODE_ENV !== "production") ? `Transition` : ``); const TRANSITION_GROUP = Symbol(!!(process.env.NODE_ENV !== "production") ? `TransitionGroup` : ``); registerRuntimeHelpers({ [K_MODEL_RADIO]: `kModelRadio`, [K_MODEL_CHECKBOX]: `kModelCheckbox`, [K_MODEL_TEXT]: `kModelText`, [K_MODEL_SELECT]: `kModelSelect`, [K_MODEL_DYNAMIC]: `kModelDynamic`, [K_ON_WITH_MODIFIERS]: `withModifiers`, [K_ON_WITH_KEYS]: `withKeys`, [K_SHOW]: `kShow`, [TRANSITION]: `Transition`, [TRANSITION_GROUP]: `TransitionGroup` }); let decoder; function decodeHtmlBrowser(raw, asAttr = false) { if (!decoder) { decoder = document.createElement("div"); } if (asAttr) { decoder.innerHTML = `<div foo="${raw.replace(/"/g, "&quot;")}">`; return decoder.children[0].getAttribute("foo"); } else { decoder.innerHTML = raw; return decoder.textContent; } } const parserOptions = { parseMode: "html", isVoidTag, isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag), isPreTag: (tag) => tag === "pre", decodeEntities: decodeHtmlBrowser , isBuiltInComponent: (tag) => { if (tag === "Transition" || tag === "transition") { return TRANSITION; } else if (tag === "TransitionGroup" || tag === "transition-group") { return TRANSITION_GROUP; } }, // https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher getNamespace(tag, parent, rootNamespace) { let ns = parent ? parent.ns : rootNamespace; if (parent && ns === 2) { if (parent.tag === "annotation-xml") { if (tag === "svg") { return 1; } if (parent.props.some( (a) => a.type === 6 && a.name === "encoding" && a.value != null && (a.value.content === "text/html" || a.value.content === "application/xhtml+xml") )) { ns = 0; } } else if (/^m(?:[ions]|text)$/.test(parent.tag) && tag !== "mglyph" && tag !== "malignmark") { ns = 0; } } else if (parent && ns === 1) { if (parent.tag === "foreignObject" || parent.tag === "desc" || parent.tag === "title") { ns = 0; } } if (ns === 0) { if (tag === "svg") { return 1; } if (tag === "math") { return 2; } } return ns; } }; const transformStyle = (node) => { if (node.type === 1) { node.props.forEach((p, i) => { if (p.type === 6 && p.name === "style" && p.value) { node.props[i] = { type: 7, name: `bind`, arg: createSimpleExpression(`style`, true, p.loc), exp: parseInlineCSS(p.value.content, p.loc), modifiers: [], loc: p.loc }; } }); } }; const parseInlineCSS = (cssText, loc) => { const normalized = parseStringStyle(cssText); return createSimpleExpression( JSON.stringify(normalized), false, loc, 3 ); }; function createDOMCompilerError(code, loc) { return createCompilerError( code, loc, !!(process.env.NODE_ENV !== "production") || false ? DOMErrorMessages : void 0 ); } const DOMErrorCodes = { "X_K_HTML_NO_EXPRESSION": 53, "53": "X_K_HTML_NO_EXPRESSION", "X_K_HTML_WITH_CHILDREN": 54, "54": "X_K_HTML_WITH_CHILDREN", "X_K_TEXT_NO_EXPRESSION": 55, "55": "X_K_TEXT_NO_EXPRESSION", "X_K_TEXT_WITH_CHILDREN": 56, "56": "X_K_TEXT_WITH_CHILDREN", "X_K_MODEL_ON_INVALID_ELEMENT": 57, "57": "X_K_MODEL_ON_INVALID_ELEMENT", "X_K_MODEL_ARG_ON_ELEMENT": 58, "58": "X_K_MODEL_ARG_ON_ELEMENT", "X_K_MODEL_ON_FILE_INPUT_ELEMENT": 59, "59": "X_K_MODEL_ON_FILE_INPUT_ELEMENT", "X_K_MODEL_UNNECESSARY_VALUE": 60, "60": "X_K_MODEL_UNNECESSARY_VALUE", "X_K_SHOW_NO_EXPRESSION": 61, "61": "X_K_SHOW_NO_EXPRESSION", "X_TRANSITION_INVALID_CHILDREN": 62, "62": "X_TRANSITION_INVALID_CHILDREN", "X_IGNORED_SIDE_EFFECT_TAG": 63, "63": "X_IGNORED_SIDE_EFFECT_TAG", "__EXTEND_POINT__": 64, "64": "__EXTEND_POINT__" }; const DOMErrorMessages = { [53]: `k-html is missing expression.`, [54]: `k-html will override element children.`, [55]: `k-text is missing expression.`, [56]: `k-text will override element children.`, [57]: `k-model can only be used on <input>, <textarea> and <select> elements.`, [58]: `k-model argument is not supported on plain elements.`, [59]: `k-model cannot be used on file inputs since they are read-only. Use a k-on:change listener instead.`, [60]: `Unnecessary value binding used alongside k-model. It will interfere with k-model's behavior.`, [61]: `k-show is missing expression.`, [62]: `<Transition> expects exactly one child element or component.`, [63]: `Tags with side effect (<script> and <style>) are ignored in client component templates.` }; const transformKHtml = (dir, node, context) => { const { exp, loc } = dir; if (!exp) { context.onError( createDOMCompilerError(53, loc) ); } if (node.children.length) { context.onError( createDOMCompilerError(54, loc) ); node.children.length = 0; } return { props: [ createObjectProperty( createSimpleExpression(`innerHTML`, true, loc), exp || createSimpleExpression("", true) ) ] }; }; const transformKText = (dir, node, context) => { const { exp, loc } = dir; if (!exp) { context.onError( createDOMCompilerError(55, loc) ); } if (node.children.length) { context.onError( createDOMCompilerError(56, loc) ); node.children.length = 0; } return { props: [ createObjectProperty( createSimpleExpression(`textContent`, true), exp ? getConstantType(exp, context) > 0 ? exp : createCallExpression( context.helperString(TO_DISPLAY_STRING), [exp], loc ) : createSimpleExpression("", true) ) ] }; }; const transformModel = (dir, node, context) => { const baseResult = transformModel$1(dir, node, context); if (!baseResult.props.length || node.tagType === 1) { return baseResult; } if (dir.arg) { context.onError( createDOMCompilerError( 58, dir.arg.loc ) ); } function checkDuplicatedValue() { const value = findDir(node, "bind"); if (value && isStaticArgOf(value.arg, "value")) { context.onError( createDOMCompilerError( 60, value.loc ) ); } } const { tag } = node; const isCustomElement = context.isCustomElement(tag); if (tag === "input" || tag === "textarea" || tag === "select" || isCustomElement) { let directiveToUse = K_MODEL_TEXT; let isInvalidType = false; if (tag === "input" || isCustomElement) { const type = findProp(node, `type`); if (type) { if (type.type === 7) { directiveToUse = K_MODEL_DYNAMIC; } else if (type.value) { switch (type.value.content) { case "radio": directiveToUse = K_MODEL_RADIO; break; case "checkbox": directiveToUse = K_MODEL_CHECKBOX; break; case "file": isInvalidType = true; context.onError( createDOMCompilerError( 59, dir.loc ) ); break; default: !!(process.env.NODE_ENV !== "production") && checkDuplicatedValue(); break; } } } else if (hasDynamicKeyKBind(node)) { directiveToUse = K_MODEL_DYNAMIC; } else { !!(process.env.NODE_ENV !== "production") && checkDuplicatedValue(); } } else if (tag === "select") { directiveToUse = K_MODEL_SELECT; } else { !!(process.env.NODE_ENV !== "production") && checkDuplicatedValue(); } if (!isInvalidType) { baseResult.needRuntime = context.helper(directiveToUse); } } else { context.onError( createDOMCompilerError( 57, dir.loc ) ); } baseResult.props = baseResult.props.filter( (p) => !(p.key.type === 4 && p.key.content === "modelValue") ); return baseResult; }; const isEventOptionModifier = /* @__PURE__ */ makeMap(`passive,once,capture`); const isNonKeyModifier = /* @__PURE__ */ makeMap( // event propagation management `stop,prevent,self,ctrl,shift,alt,meta,exact,middle` ); const maybeKeyModifier = /* @__PURE__ */ makeMap("left,right"); const isKeyboardEvent = /* @__PURE__ */ makeMap( `onkeyup,onkeydown,onkeypress`, true ); const resolveModifiers = (key, modifiers, context, loc) => { const keyModifiers = []; const nonKeyModifiers = []; const eventOptionModifiers = []; for (let i = 0; i < modifiers.length; i++) { const modifier = modifiers[i]; if (modifier === "native" && checkCompatEnabled( "COMPILER_K_ON_NATIVE", context, loc )) { eventOptionModifiers.push(modifier); } else if (isEventOptionModifier(modifier)) { eventOptionModifiers.push(modifier); } else { if (maybeKeyModifier(modifier)) { if (isStaticExp(key)) { if (isKeyboardEvent(key.content)) { keyModifiers.push(modifier); } else { nonKeyModifiers.push(modifier); } } else { keyModifiers.push(modifier); nonKeyModifiers.push(modifier); } } else { if (isNonKeyModifier(modifier)) { nonKeyModifiers.push(modifier); } else { keyModifiers.push(modifier); } } } } return { keyModifiers, nonKeyModifiers, eventOptionModifiers }; }; const transformClick = (key, event) => { const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === "onclick"; return isStaticClick ? createSimpleExpression(event, true) : key.type !== 4 ? createCompoundExpression([ `(`, key, `) === "onClick" ? "${event}" : (`, key, `)` ]) : key; }; const transformOn = (dir, node, context) => { return transformOn$1(dir, node, context, (baseResult) => { const { modifiers } = dir; if (!modifiers.length) return baseResult; let { key, value: handlerExp } = baseResult.props[0]; const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(key, modifiers, context, dir.loc); if (nonKeyModifiers.includes("right")) { key = transformClick(key, `onContextmenu`); } if (nonKeyModifiers.includes("middle")) { key = transformClick(key, `onMouseup`); } if (nonKeyModifiers.length) { handlerExp = createCallExpression(context.helper(K_ON_WITH_MODIFIERS), [ handlerExp, JSON.stringify(nonKeyModifiers) ]); } if (keyModifiers.length && // if event name is dynamic, always wrap with keys guard (!isStaticExp(key) || isKeyboardEvent(key.content))) { handlerExp = createCallExpression(context.helper(K_ON_WITH_KEYS), [ handlerExp, JSON.stringify(keyModifiers) ]); } if (eventOptionModifiers.length) { const modifierPostfix = eventOptionModifiers.map(capitalize).join(""); key = isStaticExp(key) ? createSimpleExpression(`${key.content}${modifierPostfix}`, true) : createCompoundExpression([`(`, key, `) + "${modifierPostfix}"`]); } return { props: [createObjectProperty(key, handlerExp)] }; }); }; const transformShow = (dir, node, context) => { const { exp, loc } = dir; if (!exp) { context.onError( createDOMCompilerError(61, loc) ); } return { props: [], needRuntime: context.helper(K_SHOW) }; }; const transformTransition = (node, context) => { if (node.type === 1 && node.tagType === 1) { const component = context.isBuiltInComponent(node.tag); if (component === TRANSITION) { return () => { if (!node.children.length) { return; } if (hasMultipleChildren(node)) { context.onError( createDOMCompilerError( 62, { start: node.children[0].loc.start, end: node.children[node.children.length - 1].loc.end, source: "" } ) ); } const child = node.children[0]; if (child.type === 1) { for (const p of child.props) { if (p.type === 7 && p.name === "show") { node.props.push({ type: 6, name: "persisted", nameLoc: node.loc, value: void 0, loc: node.loc }); } } } }; } } }; function hasMultipleChildren(node) { const children = node.children = node.children.filter( (c) => c.type !== 3 && !(c.type === 2 && !c.content.trim()) ); const child = children[0]; return children.length !== 1 || child.type === 11 || child.type === 9 && child.branches.some(hasMultipleChildren); } const ignoreSideEffectTags = (node, context) => { if (node.type === 1 && node.tagType === 0 && (node.tag === "script" || node.tag === "style")) { !!(process.env.NODE_ENV !== "production") && context.onError( createDOMCompilerError( 63, node.loc ) ); context.removeNode(); } }; const DOMNodeTransforms = [ transformStyle, ...!!(process.env.NODE_ENV !== "production") ? [transformTransition] : [] ]; const DOMDirectiveTransforms = { cloak: noopDirectiveTransform, html: transformKHtml, text: transformKText, model: transformModel, // override compiler-core on: transformOn, // override compiler-core show: transformShow }; function compile(src, options = {}) { return baseCompile( src, extend({}, parserOptions, options, { nodeTransforms: [ // ignore <script> and <tag> // this is not put inside DOMNodeTransforms because that list is used // by compiler-ssr to generate knode fallback branches ignoreSideEffectTags, ...DOMNodeTransforms, ...options.nodeTransforms || [] ], directiveTransforms: extend( {}, DOMDirectiveTransforms, options.directiveTransforms || {} ), transformHoist: null }) ); } function parse(template, options = {}) { return baseParse(template, extend({}, parserOptions, options)); } export { DOMDirectiveTransforms, DOMErrorCodes, DOMErrorMessages, DOMNodeTransforms, K_MODEL_CHECKBOX, K_MODEL_DYNAMIC, K_MODEL_RADIO, K_MODEL_SELECT, K_MODEL_TEXT, K_ON_WITH_KEYS, K_ON_WITH_MODIFIERS, K_SHOW, TRANSITION, TRANSITION_GROUP, compile, createDOMCompilerError, parse, parserOptions, transformStyle };