import * as path from "https://deno.land/std@0.83.0/path/mod.ts"; import postcss from "./postcss/postcss.js"; const parse$3 = (url) => new URL(url); /** * Make a map and return a function for checking if a key * is in that map. * IMPORTANT: all calls of this function must be prefixed with * \/\*#\_\_PURE\_\_\*\/ * So that rollup can tree-shake them if necessary. */ function makeMap(str, expectsLowerCase) { const map = Object.create(null); const list = str.split(","); for (let i = 0; i < list.length; i++) { map[list[i]] = true; } return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val]; } /** * dev only flag -> name mapping */ const PatchFlagNames = { [1 /* TEXT */]: `TEXT`, [2 /* CLASS */]: `CLASS`, [4 /* STYLE */]: `STYLE`, [8 /* PROPS */]: `PROPS`, [16 /* FULL_PROPS */]: `FULL_PROPS`, [32 /* HYDRATE_EVENTS */]: `HYDRATE_EVENTS`, [64 /* STABLE_FRAGMENT */]: `STABLE_FRAGMENT`, [128 /* KEYED_FRAGMENT */]: `KEYED_FRAGMENT`, [256 /* UNKEYED_FRAGMENT */]: `UNKEYED_FRAGMENT`, [512 /* NEED_PATCH */]: `NEED_PATCH`, [1024 /* DYNAMIC_SLOTS */]: `DYNAMIC_SLOTS`, [2048 /* DEV_ROOT_FRAGMENT */]: `DEV_ROOT_FRAGMENT`, [-1 /* HOISTED */]: `HOISTED`, [-2 /* BAIL */]: `BAIL`, }; /** * Dev only */ const slotFlagsText = { [1 /* STABLE */]: "STABLE", [2 /* DYNAMIC */]: "DYNAMIC", [3 /* FORWARDED */]: "FORWARDED", }; const GLOBALS_WHITE_LISTED = "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI," + "decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array," + "Object,Boolean,String,RegExp,Map,Set,JSON,Intl"; const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED); const range = 2; function generateCodeFrame(source, start = 0, end = source.length) { const lines = source.split(/\r?\n/); let count = 0; const res = []; for (let i = 0; i < lines.length; i++) { count += lines[i].length + 1; if (count >= start) { for (let j = i - range; j <= i + range || end > count; j++) { if (j < 0 || j >= lines.length) continue; const line = j + 1; res.push( `${line}${" ".repeat(Math.max(3 - String(line).length, 0))}| ${ lines[j] }` ); const lineLength = lines[j].length; if (j === i) { // push underline const pad = start - (count - lineLength) + 1; const length = Math.max( 1, end > count ? lineLength - pad : end - start ); res.push(` | ` + " ".repeat(pad) + "^".repeat(length)); } else if (j > i) { if (end > count) { const length = Math.max(Math.min(end - count, lineLength), 1); res.push(` | ` + "^".repeat(length)); } count += lineLength + 1; } } break; } } return res.join("\n"); } /** * On the client we only need to offer special cases for boolean attributes that * have different names from their corresponding dom properties: * - itemscope -> N/A * - allowfullscreen -> allowFullscreen * - formnovalidate -> formNoValidate * - ismap -> isMap * - nomodule -> noModule * - novalidate -> noValidate * - readonly -> readOnly */ const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`; /** * The full list is needed during SSR to produce the correct initial markup. */ const isBooleanAttr = /*#__PURE__*/ makeMap( specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,` + `loop,open,required,reversed,scoped,seamless,` + `checked,muted,multiple,selected` ); const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/; const attrValidationCache = {}; function isSSRSafeAttrName(name) { if (attrValidationCache.hasOwnProperty(name)) { return attrValidationCache[name]; } const isUnsafe = unsafeAttrCharRE.test(name); if (isUnsafe) { console.error(`unsafe attribute name: ${name}`); } return (attrValidationCache[name] = !isUnsafe); } const propsToAttrMap = { acceptCharset: "accept-charset", className: "class", htmlFor: "for", httpEquiv: "http-equiv", }; /** * CSS properties that accept plain numbers */ const isNoUnitNumericStyleProp = /*#__PURE__*/ makeMap( `animation-iteration-count,border-image-outset,border-image-slice,` + `border-image-width,box-flex,box-flex-group,box-ordinal-group,column-count,` + `columns,flex,flex-grow,flex-positive,flex-shrink,flex-negative,flex-order,` + `grid-row,grid-row-end,grid-row-span,grid-row-start,grid-column,` + `grid-column-end,grid-column-span,grid-column-start,font-weight,line-clamp,` + `line-height,opacity,order,orphans,tab-size,widows,z-index,zoom,` + // SVG `fill-opacity,flood-opacity,stop-opacity,stroke-dasharray,stroke-dashoffset,` + `stroke-miterlimit,stroke-opacity,stroke-width` ); /** * Known attributes, this is used for stringification of runtime static nodes * so that we don't stringify bindings that cannot be set from HTML. * Don't also forget to allow `data-*` and `aria-*`! * Generated from https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes */ const isKnownAttr = /*#__PURE__*/ makeMap( `accept,accept-charset,accesskey,action,align,allow,alt,async,` + `autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,` + `border,buffered,capture,challenge,charset,checked,cite,class,code,` + `codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,` + `coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,` + `disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,` + `formaction,formenctype,formmethod,formnovalidate,formtarget,headers,` + `height,hidden,high,href,hreflang,http-equiv,icon,id,importance,integrity,` + `ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,` + `manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,` + `open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,` + `referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,` + `selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,` + `start,step,style,summary,tabindex,target,title,translate,type,usemap,` + `value,width,wrap` ); function normalizeStyle(value) { if (isArray(value)) { const res = {}; for (let i = 0; i < value.length; i++) { const item = value[i]; const normalized = normalizeStyle( isString(item) ? parseStringStyle(item) : item ); if (normalized) { for (const key in normalized) { res[key] = normalized[key]; } } } return res; } else if (isObject(value)) { return value; } } const listDelimiterRE = /;(?![^(]*\))/g; const propertyDelimiterRE = /:(.+)/; function parseStringStyle(cssText) { const ret = {}; cssText.split(listDelimiterRE).forEach((item) => { if (item) { const tmp = item.split(propertyDelimiterRE); tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim()); } }); return ret; } function stringifyStyle(styles) { let ret = ""; if (!styles) { return ret; } for (const key in styles) { const value = styles[key]; const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key); if ( isString(value) || (typeof value === "number" && isNoUnitNumericStyleProp(normalizedKey)) ) { // only render valid values ret += `${normalizedKey}:${value};`; } } return ret; } function normalizeClass(value) { let res = ""; if (isString(value)) { res = value; } else if (isArray(value)) { for (let i = 0; i < value.length; i++) { res += normalizeClass(value[i]) + " "; } } else if (isObject(value)) { for (const name in value) { if (value[name]) { res += name + " "; } } } return res.trim(); } // These tag configs are shared between compiler-dom and runtime-dom, so they // https://developer.mozilla.org/en-US/docs/Web/HTML/Element const HTML_TAGS = "html,body,base,head,link,meta,style,title,address,article,aside,footer," + "header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption," + "figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code," + "data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup," + "time,u,var,wbr,area,audio,map,track,video,embed,object,param,source," + "canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td," + "th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup," + "option,output,progress,select,textarea,details,dialog,menu," + "summary,template,blockquote,iframe,tfoot"; // https://developer.mozilla.org/en-US/docs/Web/SVG/Element const SVG_TAGS = "svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile," + "defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer," + "feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap," + "feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR," + "feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset," + "fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter," + "foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask," + "mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern," + "polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol," + "text,textPath,title,tspan,unknown,use,view"; const VOID_TAGS = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr"; const isHTMLTag = /*#__PURE__*/ makeMap(HTML_TAGS); const isSVGTag = /*#__PURE__*/ makeMap(SVG_TAGS); const isVoidTag = /*#__PURE__*/ makeMap(VOID_TAGS); const escapeRE = /["'&<>]/; function escapeHtml(string) { const str = "" + string; const match = escapeRE.exec(str); if (!match) { return str; } let html = ""; let escaped; let index; let lastIndex = 0; for (index = match.index; index < str.length; index++) { switch (str.charCodeAt(index)) { case 34: // " escaped = """; break; case 38: // & escaped = "&"; break; case 39: // ' escaped = "'"; break; case 60: // < escaped = "<"; break; case 62: // > escaped = ">"; break; default: continue; } if (lastIndex !== index) { html += str.substring(lastIndex, index); } lastIndex = index + 1; html += escaped; } return lastIndex !== index ? html + str.substring(lastIndex, index) : html; } /** * For converting {{ interpolation }} values to displayed strings. * @private */ const toDisplayString = (val) => { return val == null ? "" : isObject(val) ? JSON.stringify(val, replacer, 2) : String(val); }; const replacer = (_key, val) => { if (isMap(val)) { return { [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val]) => { entries[`${key} =>`] = val; return entries; }, {}), }; } else if (isSet(val)) { return { [`Set(${val.size})`]: [...val.values()], }; } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) { return String(val); } return val; }; /** * List of @babel/parser plugins that are used for template expression * transforms and SFC script transforms. By default we enable proposals slated * for ES2020. This will need to be updated as the spec moves forward. * Full list at https://babeljs.io/docs/en/next/babel-parser#plugins */ const babelParserDefaultPlugins = [ "bigInt", "optionalChaining", "nullishCoalescingOperator", ]; const EMPTY_OBJ = Object.freeze({}); const EMPTY_ARR = Object.freeze([]); const NOOP = () => {}; /** * Always return false. */ const NO = () => false; const onRE = /^on[^a-z]/; const isOn = (key) => onRE.test(key); const extend = Object.assign; const hasOwnProperty = Object.prototype.hasOwnProperty; const hasOwn = (val, key) => hasOwnProperty.call(val, key); const isArray = Array.isArray; const isMap = (val) => toTypeString(val) === "[object Map]"; const isSet = (val) => toTypeString(val) === "[object Set]"; const isFunction = (val) => typeof val === "function"; const isString = (val) => typeof val === "string"; const isSymbol = (val) => typeof val === "symbol"; const isObject = (val) => val !== null && typeof val === "object"; const objectToString = Object.prototype.toString; const toTypeString = (value) => objectToString.call(value); const isPlainObject = (val) => toTypeString(val) === "[object Object]"; const isReservedProp = /*#__PURE__*/ makeMap( // the leading comma is intentional so empty string "" is also included ",key,ref," + "onVnodeBeforeMount,onVnodeMounted," + "onVnodeBeforeUpdate,onVnodeUpdated," + "onVnodeBeforeUnmount,onVnodeUnmounted" ); const cacheStringFunction = (fn) => { const cache = Object.create(null); return (str) => { const hit = cache[str]; return hit || (cache[str] = fn(str)); }; }; const camelizeRE = /-(\w)/g; /** * @private */ const camelize = cacheStringFunction((str) => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }); const hyphenateRE = /\B([A-Z])/g; /** * @private */ const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, "-$1").toLowerCase() ); /** * @private */ const capitalize = cacheStringFunction( (str) => str.charAt(0).toUpperCase() + str.slice(1) ); /** * @private */ const toHandlerKey = cacheStringFunction((str) => str ? `on${capitalize(str)}` : `` ); function defaultOnError(error) { throw error; } function createCompilerError(code, loc, messages, additionalMessage) { const msg = (messages || errorMessages)[code] + (additionalMessage || ``); const error = new SyntaxError(String(msg)); error.code = code; error.loc = loc; return error; } const errorMessages = { // parse errors [0 /* ABRUPT_CLOSING_OF_EMPTY_COMMENT */]: "Illegal comment.", [1 /* CDATA_IN_HTML_CONTENT */]: "CDATA section is allowed only in XML context.", [2 /* DUPLICATE_ATTRIBUTE */]: "Duplicate attribute.", [3 /* END_TAG_WITH_ATTRIBUTES */]: "End tag cannot have attributes.", [4 /* END_TAG_WITH_TRAILING_SOLIDUS */]: "Illegal '/' in tags.", [5 /* EOF_BEFORE_TAG_NAME */]: "Unexpected EOF in tag.", [6 /* EOF_IN_CDATA */]: "Unexpected EOF in CDATA section.", [7 /* EOF_IN_COMMENT */]: "Unexpected EOF in comment.", [8 /* EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT */]: "Unexpected EOF in script.", [9 /* EOF_IN_TAG */]: "Unexpected EOF in tag.", [10 /* INCORRECTLY_CLOSED_COMMENT */]: "Incorrectly closed comment.", [11 /* INCORRECTLY_OPENED_COMMENT */]: "Incorrectly opened comment.", [12 /* INVALID_FIRST_CHARACTER_OF_TAG_NAME */]: "Illegal tag name. Use '<' to print '<'.", [13 /* MISSING_ATTRIBUTE_VALUE */]: "Attribute value was expected.", [14 /* MISSING_END_TAG_NAME */]: "End tag name was expected.", [15 /* MISSING_WHITESPACE_BETWEEN_ATTRIBUTES */]: "Whitespace was expected.", [16 /* NESTED_COMMENT */]: "Unexpected ' isRef(x) ? x.value = y : x = y const rVal = parent.right; const rExp = rawExp.slice(rVal.start - 1, rVal.end - 1); const rExpString = stringifyExpression( processExpression(createSimpleExpression(rExp, false), context) ); return `${context.helperString(IS_REF)}(${raw})${ context.isTS ? ` //@ts-ignore\n` : `` } ? ${raw}.value = ${rExpString} : ${raw}`; } else if (isUpdateArg) { // make id replace parent in the code range so the raw update operator // is removed id.start = parent.start; id.end = parent.end; const { prefix: isPrefix, operator } = parent; const prefix = isPrefix ? operator : ``; const postfix = isPrefix ? `` : operator; // let binding. // x++ --> isRef(a) ? a.value++ : a++ return `${context.helperString(IS_REF)}(${raw})${ context.isTS ? ` //@ts-ignore\n` : `` } ? ${prefix}${raw}.value${postfix} : ${prefix}${raw}${postfix}`; } else if (isDestructureAssignment) { // TODO // let binding in a destructure assignment - it's very tricky to // handle both possible cases here without altering the original // structure of the code, so we just assume it's not a ref here // for now return raw; } else { return `${context.helperString(UNREF)}(${raw})`; } } else if (type === "props" /* PROPS */) { // use __props which is generated by compileScript so in ts mode // it gets correct type return `__props.${raw}`; } } else { if (type && type.startsWith("setup")) { // setup bindings in non-inline mode return `$setup.${raw}`; } else if (type) { return `$${type}.${raw}`; } } // fallback to ctx return `_ctx.${raw}`; }; // fast path if expression is a simple identifier. const rawExp = node.content; // bail constant on parens (function invocation) and dot (member access) const bailConstant = rawExp.indexOf(`(`) > -1 || rawExp.indexOf(".") > 0; if (isSimpleIdentifier(rawExp)) { const isScopeVarReference = context.identifiers[rawExp]; const isAllowedGlobal = isGloballyWhitelisted(rawExp); const isLiteral = isLiteralWhitelisted(rawExp); if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) { // const bindings exposed from setup can be skipped for patching but // cannot be hoisted to module scope if (bindingMetadata[node.content] === "setup-const" /* SETUP_CONST */) { node.constType = 1 /* CAN_SKIP_PATCH */; } node.content = rewriteIdentifier(rawExp); } else if (!isScopeVarReference) { if (isLiteral) { node.constType = 3 /* CAN_STRINGIFY */; } else { node.constType = 2 /* CAN_HOIST */; } } return node; } let ast; // exp needs to be parsed differently: // 1. Multiple inline statements (v-on, with presence of `;`): parse as raw // exp, but make sure to pad with spaces for consistent ranges // 2. Expressions: wrap with parens (for e.g. object expressions) // 3. Function arguments (v-for, v-slot): place in a function argument position const source = asRawStatements ? ` ${rawExp} ` : `(${rawExp})${asParams ? `=>{}` : ``}`; try { ast = parse_1(source, { plugins: [...context.expressionPlugins, ...babelParserDefaultPlugins], }).program; } catch (e) { context.onError( createCompilerError( 43 /* X_INVALID_EXPRESSION */, node.loc, undefined, e.message ) ); return node; } const ids = []; const knownIds = Object.create(context.identifiers); const isDuplicate = (node) => ids.some((id) => id.start === node.start); const parentStack = []; walk$1(ast, { enter(node, parent) { parent && parentStack.push(parent); if (node.type === "Identifier") { if (!isDuplicate(node)) { const needPrefix = shouldPrefix(node, parent, parentStack); if (!knownIds[node.name] && needPrefix) { if (isStaticProperty(parent) && parent.shorthand) { // property shorthand like { foo }, we need to add the key since // we rewrite the value node.prefix = `${node.name}: `; } node.name = rewriteIdentifier(node.name, parent, node); ids.push(node); } else if (!isStaticPropertyKey(node, parent)) { // The identifier is considered constant unless it's pointing to a // scope variable (a v-for alias, or a v-slot prop) if (!(needPrefix && knownIds[node.name]) && !bailConstant) { node.isConstant = true; } // also generate sub-expressions for other identifiers for better // source map support. (except for property keys which are static) ids.push(node); } } } else if (isFunction$1(node)) { // walk function expressions and add its arguments to known identifiers // so that we don't prefix them node.params.forEach((p) => walk$1(p, { enter(child, parent) { if ( child.type === "Identifier" && // do not record as scope variable if is a destructured key !isStaticPropertyKey(child, parent) && // do not record if this is a default value // assignment of a destructured variable !( parent && parent.type === "AssignmentPattern" && parent.right === child ) ) { const { name } = child; if (node.scopeIds && node.scopeIds.has(name)) { return; } if (name in knownIds) { knownIds[name]++; } else { knownIds[name] = 1; } (node.scopeIds || (node.scopeIds = new Set())).add(name); } }, }) ); } }, leave(node, parent) { parent && parentStack.pop(); if (node !== ast.body[0].expression && node.scopeIds) { node.scopeIds.forEach((id) => { knownIds[id]--; if (knownIds[id] === 0) { delete knownIds[id]; } }); } }, }); // We break up the compound expression into an array of strings and sub // expressions (for identifiers that have been prefixed). In codegen, if // an ExpressionNode has the `.children` property, it will be used instead of // `.content`. const children = []; ids.sort((a, b) => a.start - b.start); ids.forEach((id, i) => { // range is offset by -1 due to the wrapping parens when parsed const start = id.start - 1; const end = id.end - 1; const last = ids[i - 1]; const leadingText = rawExp.slice(last ? last.end - 1 : 0, start); if (leadingText.length || id.prefix) { children.push(leadingText + (id.prefix || ``)); } const source = rawExp.slice(start, end); children.push( createSimpleExpression( id.name, false, { source, start: advancePositionWithClone(node.loc.start, source, start), end: advancePositionWithClone(node.loc.start, source, end), }, id.isConstant ? 3 /* CAN_STRINGIFY */ : 0 /* NOT_CONSTANT */ ) ); if (i === ids.length - 1 && end < rawExp.length) { children.push(rawExp.slice(end)); } }); let ret; if (children.length) { ret = createCompoundExpression(children, node.loc); } else { ret = node; ret.constType = bailConstant ? 0 /* NOT_CONSTANT */ : 3 /* CAN_STRINGIFY */; } ret.identifiers = Object.keys(knownIds); return ret; } const isFunction$1 = (node) => { return /Function(?:Expression|Declaration)$|Method$/.test(node.type); }; const isStaticProperty = (node) => node && (node.type === "ObjectProperty" || node.type === "ObjectMethod") && !node.computed; const isStaticPropertyKey = (node, parent) => isStaticProperty(parent) && parent.key === node; function shouldPrefix(id, parent, parentStack) { // declaration id if ( (parent.type === "VariableDeclarator" || parent.type === "ClassDeclaration") && parent.id === id ) { return false; } if (isFunction$1(parent)) { // function decalration/expression id if (parent.id === id) { return false; } // params list if (parent.params.includes(id)) { return false; } } // property key // this also covers object destructure pattern if (isStaticPropertyKey(id, parent)) { return false; } // non-assignment array destructure pattern if ( parent.type === "ArrayPattern" && !isInDestructureAssignment(parent, parentStack) ) { return false; } // member expression property if ( (parent.type === "MemberExpression" || parent.type === "OptionalMemberExpression") && parent.property === id && !parent.computed ) { return false; } // is a special keyword but parsed as identifier if (id.name === "arguments") { return false; } // skip whitelisted globals if (isGloballyWhitelisted(id.name)) { return false; } // special case for webpack compilation if (id.name === "require") { return false; } return true; } function isInDestructureAssignment(parent, parentStack) { if ( parent && (parent.type === "ObjectProperty" || parent.type === "ArrayPattern") ) { let i = parentStack.length; while (i--) { const p = parentStack[i]; if (p.type === "AssignmentExpression") { return true; } else if (p.type !== "ObjectProperty" && !p.type.endsWith("Pattern")) { break; } } } return false; } function stringifyExpression(exp) { if (isString(exp)) { return exp; } else if (exp.type === 4 /* SIMPLE_EXPRESSION */) { return exp.content; } else { return exp.children.map(stringifyExpression).join(""); } } const transformIf = createStructuralDirectiveTransform( /^(if|else|else-if)$/, (node, dir, context) => { return processIf(node, dir, context, (ifNode, branch, isRoot) => { // #1587: We need to dynamically increment the key based on the current // node's sibling nodes, since chained v-if/else branches are // rendered at the same depth const siblings = context.parent.children; let i = siblings.indexOf(ifNode); let key = 0; while (i-- >= 0) { const sibling = siblings[i]; if (sibling && sibling.type === 9 /* IF */) { key += sibling.branches.length; } } // Exit callback. Complete the codegenNode when all children have been // transformed. return () => { if (isRoot) { ifNode.codegenNode = createCodegenNodeForBranch(branch, key, context); } else { // attach this branch's codegen node to the v-if root. const parentCondition = getParentCondition(ifNode.codegenNode); parentCondition.alternate = createCodegenNodeForBranch( branch, key + ifNode.branches.length - 1, context ); } }; }); } ); // target-agnostic transform used for both Client and SSR function processIf(node, dir, context, processCodegen) { if (dir.name !== "else" && (!dir.exp || !dir.exp.content.trim())) { const loc = dir.exp ? dir.exp.loc : node.loc; context.onError( createCompilerError(27 /* X_V_IF_NO_EXPRESSION */, dir.loc) ); dir.exp = createSimpleExpression(`true`, false, loc); } if (context.prefixIdentifiers && dir.exp) { // dir.exp can only be simple expression because vIf transform is applied // before expression transform. dir.exp = processExpression(dir.exp, context); } if (dir.name === "if") { const branch = createIfBranch(node, dir); const ifNode = { type: 9 /* IF */, loc: node.loc, branches: [branch], }; context.replaceNode(ifNode); if (processCodegen) { return processCodegen(ifNode, branch, true); } } else { // locate the adjacent v-if const siblings = context.parent.children; const comments = []; let i = siblings.indexOf(node); while (i-- >= -1) { const sibling = siblings[i]; if (sibling && sibling.type === 3 /* COMMENT */) { context.removeNode(sibling); comments.unshift(sibling); continue; } if ( sibling && sibling.type === 2 /* TEXT */ && !sibling.content.trim().length ) { context.removeNode(sibling); continue; } if (sibling && sibling.type === 9 /* IF */) { // move the node to the if node's branches context.removeNode(); const branch = createIfBranch(node, dir); if (comments.length) { branch.children = [...comments, ...branch.children]; } // check if user is forcing same key on different branches { const key = branch.userKey; if (key) { sibling.branches.forEach(({ userKey }) => { if (isSameKey(userKey, key)) { context.onError( createCompilerError( 28 /* X_V_IF_SAME_KEY */, branch.userKey.loc ) ); } }); } } sibling.branches.push(branch); const onExit = processCodegen && processCodegen(sibling, branch, false); // since the branch was removed, it will not be traversed. // make sure to traverse here. traverseNode(branch, context); // call on exit if (onExit) onExit(); // make sure to reset currentNode after traversal to indicate this // node has been removed. context.currentNode = null; } else { context.onError( createCompilerError(29 /* X_V_ELSE_NO_ADJACENT_IF */, node.loc) ); } break; } } } function createIfBranch(node, dir) { return { type: 10 /* IF_BRANCH */, loc: node.loc, condition: dir.name === "else" ? undefined : dir.exp, children: node.tagType === 3 /* TEMPLATE */ && !findDir(node, "for") ? node.children : [node], userKey: findProp(node, `key`), }; } function createCodegenNodeForBranch(branch, keyIndex, context) { if (branch.condition) { return createConditionalExpression( branch.condition, createChildrenCodegenNode(branch, keyIndex, context), // make sure to pass in asBlock: true so that the comment node call // closes the current block. createCallExpression(context.helper(CREATE_COMMENT), ['"v-if"', "true"]) ); } else { return createChildrenCodegenNode(branch, keyIndex, context); } } function createChildrenCodegenNode(branch, keyIndex, context) { const { helper } = context; const keyProperty = createObjectProperty( `key`, createSimpleExpression(`${keyIndex}`, false, locStub, 2 /* CAN_HOIST */) ); const { children } = branch; const firstChild = children[0]; const needFragmentWrapper = children.length !== 1 || firstChild.type !== 1; /* ELEMENT */ if (needFragmentWrapper) { if (children.length === 1 && firstChild.type === 11 /* FOR */) { // optimize away nested fragments when child is a ForNode const vnodeCall = firstChild.codegenNode; injectProp(vnodeCall, keyProperty, context); return vnodeCall; } else { return createVNodeCall( context, helper(FRAGMENT), createObjectExpression([keyProperty]), children, 64 /* STABLE_FRAGMENT */ + ` /* ${PatchFlagNames[64 /* STABLE_FRAGMENT */]} */`, undefined, undefined, true, false, branch.loc ); } } else { const vnodeCall = firstChild.codegenNode; // Change createVNode to createBlock. if (vnodeCall.type === 13 /* VNODE_CALL */) { vnodeCall.isBlock = true; helper(OPEN_BLOCK); helper(CREATE_BLOCK); } // inject branch key injectProp(vnodeCall, keyProperty, context); return vnodeCall; } } function isSameKey(a, b) { if (!a || a.type !== b.type) { return false; } if (a.type === 6 /* ATTRIBUTE */) { if (a.value.content !== b.value.content) { return false; } } else { // directive const exp = a.exp; const branchExp = b.exp; if (exp.type !== branchExp.type) { return false; } if ( exp.type !== 4 /* SIMPLE_EXPRESSION */ || exp.isStatic !== branchExp.isStatic || exp.content !== branchExp.content ) { return false; } } return true; } function getParentCondition(node) { while (true) { if (node.type === 19 /* JS_CONDITIONAL_EXPRESSION */) { if (node.alternate.type === 19 /* JS_CONDITIONAL_EXPRESSION */) { node = node.alternate; } else { return node; } } else if (node.type === 20 /* JS_CACHE_EXPRESSION */) { node = node.value; } } } const transformFor = createStructuralDirectiveTransform( "for", (node, dir, context) => { const { helper } = context; return processFor(node, dir, context, (forNode) => { // create the loop render function expression now, and add the // iterator on exit after all children have been traversed const renderExp = createCallExpression(helper(RENDER_LIST), [ forNode.source, ]); const keyProp = findProp(node, `key`); const keyProperty = keyProp ? createObjectProperty( `key`, keyProp.type === 6 /* ATTRIBUTE */ ? createSimpleExpression(keyProp.value.content, true) : keyProp.exp ) : null; if (context.prefixIdentifiers && keyProperty) { // #2085 process :key expression needs to be processed in order for it // to behave consistently for