Created
November 11, 2019 01:53
-
-
Save barata0/375efb0ec2d2942d9531fad100b776d5 to your computer and use it in GitHub Desktop.
vue 3 global js vue next preview
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Vue = (function (exports) { | |
'use strict'; | |
// 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]; | |
} | |
// Patch flags are optimization hints generated by the compiler. | |
// when a block with dynamicChildren is encountered during diff, the algorithm | |
// enters "optimized mode". In this mode, we know that the vdom is produced by | |
// a render function generated by the compiler, so the algorithm only needs to | |
// handle updates explicitly marked by these patch flags. | |
// runtime object for public consumption | |
const PublicPatchFlags = { | |
TEXT: 1 /* TEXT */, | |
CLASS: 2 /* CLASS */, | |
STYLE: 4 /* STYLE */, | |
PROPS: 8 /* PROPS */, | |
NEED_PATCH: 32 /* NEED_PATCH */, | |
FULL_PROPS: 16 /* FULL_PROPS */, | |
KEYED_FRAGMENT: 64 /* KEYED_FRAGMENT */, | |
UNKEYED_FRAGMENT: 128 /* UNKEYED_FRAGMENT */, | |
DYNAMIC_SLOTS: 256 /* DYNAMIC_SLOTS */, | |
BAIL: -1 /* BAIL */ | |
}; | |
// dev only flag -> name mapping | |
const PatchFlagNames = { | |
[1 /* TEXT */]: `TEXT`, | |
[2 /* CLASS */]: `CLASS`, | |
[4 /* STYLE */]: `STYLE`, | |
[8 /* PROPS */]: `PROPS`, | |
[32 /* NEED_PATCH */]: `NEED_PATCH`, | |
[16 /* FULL_PROPS */]: `FULL_PROPS`, | |
[64 /* KEYED_FRAGMENT */]: `KEYED_FRAGMENT`, | |
[128 /* UNKEYED_FRAGMENT */]: `UNKEYED_FRAGMENT`, | |
[256 /* DYNAMIC_SLOTS */]: `DYNAMIC_SLOTS`, | |
[-1 /* BAIL */]: `BAIL` | |
}; | |
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; | |
res.push(`${j + 1}${' '.repeat(3 - String(j + 1).length)}| ${lines[j]}`); | |
const lineLength = lines[j].length; | |
if (j === i) { | |
// push underline | |
const pad = start - (count - lineLength) + 1; | |
const length = end > count ? lineLength - pad : end - start; | |
res.push(` | ` + ' '.repeat(pad) + '^'.repeat(length)); | |
} | |
else if (j > i) { | |
if (end > count) { | |
const length = Math.min(end - count, lineLength); | |
res.push(` | ` + '^'.repeat(length)); | |
} | |
count += lineLength + 1; | |
} | |
} | |
break; | |
} | |
} | |
return res.join('\n'); | |
} | |
// 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,menuitem,' + | |
'summary,content,element,shadow,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,lineGradient,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 EMPTY_OBJ = Object.freeze({}) | |
; | |
const EMPTY_ARR = []; | |
const NOOP = () => { }; | |
/** | |
* Always return false. | |
*/ | |
const NO = () => false; | |
const isOn = (key) => key[0] === 'o' && key[1] === 'n'; | |
const extend = (a, b) => { | |
for (const key in b) { | |
a[key] = b[key]; | |
} | |
return a; | |
}; | |
const hasOwnProperty = Object.prototype.hasOwnProperty; | |
const hasOwn = (val, key) => hasOwnProperty.call(val, key); | |
const isArray = Array.isArray; | |
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'; | |
function isPromise(val) { | |
return isObject(val) && isFunction(val.then) && isFunction(val.catch); | |
} | |
const objectToString = Object.prototype.toString; | |
const toTypeString = (value) => objectToString.call(value); | |
function toRawType(value) { | |
return toTypeString(value).slice(8, -1); | |
} | |
const isPlainObject = (val) => toTypeString(val) === '[object Object]'; | |
const isReservedProp = /*#__PURE__*/ makeMap('key,ref,' + | |
'onVnodeBeforeMount,onVnodeMounted,' + | |
'onVnodeBeforeUpdate,onVnodeUpdated,' + | |
'onVnodeBeforeUnmount,onVnodeUnmounted'); | |
const camelizeRE = /-(\w)/g; | |
const camelize = (str) => { | |
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : '')); | |
}; | |
const hyphenateRE = /\B([A-Z])/g; | |
const hyphenate = (str) => { | |
return str.replace(hyphenateRE, '-$1').toLowerCase(); | |
}; | |
const capitalize = (str) => { | |
return str.charAt(0).toUpperCase() + str.slice(1); | |
}; | |
// compare whether a value has changed, accounting for NaN. | |
const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue); | |
function defaultOnError(error) { | |
throw error; | |
} | |
function createCompilerError(code, loc, messages) { | |
const msg = (messages || errorMessages)[code] ; | |
const locInfo = loc ? ` (${loc.start.line}:${loc.start.column})` : ``; | |
const error = new SyntaxError(msg + locInfo); | |
error.code = code; | |
error.loc = loc; | |
return error; | |
} | |
const errorMessages = { | |
// parse errors | |
[0 /* ABRUPT_CLOSING_OF_EMPTY_COMMENT */]: 'Illegal comment.', | |
[1 /* ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE */]: 'Illegal numeric character reference: invalid character.', | |
[2 /* CDATA_IN_HTML_CONTENT */]: 'CDATA section is allowed only in XML context.', | |
[3 /* CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE */]: 'Illegal numeric character reference: too big.', | |
[4 /* CONTROL_CHARACTER_REFERENCE */]: 'Illegal numeric character reference: control character.', | |
[5 /* DUPLICATE_ATTRIBUTE */]: 'Duplicate attribute.', | |
[6 /* END_TAG_WITH_ATTRIBUTES */]: 'End tag cannot have attributes.', | |
[7 /* END_TAG_WITH_TRAILING_SOLIDUS */]: "Illegal '/' in tags.", | |
[8 /* EOF_BEFORE_TAG_NAME */]: 'Unexpected EOF in tag.', | |
[9 /* EOF_IN_CDATA */]: 'Unexpected EOF in CDATA section.', | |
[10 /* EOF_IN_COMMENT */]: 'Unexpected EOF in comment.', | |
[11 /* EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT */]: 'Unexpected EOF in script.', | |
[12 /* EOF_IN_TAG */]: 'Unexpected EOF in tag.', | |
[13 /* INCORRECTLY_CLOSED_COMMENT */]: 'Incorrectly closed comment.', | |
[14 /* INCORRECTLY_OPENED_COMMENT */]: 'Incorrectly opened comment.', | |
[15 /* INVALID_FIRST_CHARACTER_OF_TAG_NAME */]: "Illegal tag name. Use '<' to print '<'.", | |
[16 /* MISSING_ATTRIBUTE_VALUE */]: 'Attribute value was expected.', | |
[17 /* MISSING_END_TAG_NAME */]: 'End tag name was expected.', | |
[18 /* MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE */]: 'Semicolon was expected.', | |
[19 /* MISSING_WHITESPACE_BETWEEN_ATTRIBUTES */]: 'Whitespace was expected.', | |
[20 /* NESTED_COMMENT */]: "Unexpected '<!--' in comment.", | |
[21 /* NONCHARACTER_CHARACTER_REFERENCE */]: 'Illegal numeric character reference: non character.', | |
[22 /* NULL_CHARACTER_REFERENCE */]: 'Illegal numeric character reference: null character.', | |
[23 /* SURROGATE_CHARACTER_REFERENCE */]: 'Illegal numeric character reference: non-pair surrogate.', | |
[24 /* UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME */]: 'Attribute name cannot contain U+0022 ("), U+0027 (\'), and U+003C (<).', | |
[25 /* UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE */]: 'Unquoted attribute value cannot contain U+0022 ("), U+0027 (\'), U+003C (<), U+003D (=), and U+0060 (`).', | |
[26 /* UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME */]: "Attribute name cannot start with '='.", | |
[28 /* UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME */]: "'<?' is allowed only in XML context.", | |
[29 /* UNEXPECTED_SOLIDUS_IN_TAG */]: "Illegal '/' in tags.", | |
[30 /* UNKNOWN_NAMED_CHARACTER_REFERENCE */]: 'Unknown entity name.', | |
// Vue-specific parse errors | |
[31 /* X_INVALID_END_TAG */]: 'Invalid end tag.', | |
[32 /* X_MISSING_END_TAG */]: 'End tag was not found.', | |
[33 /* X_MISSING_INTERPOLATION_END */]: 'Interpolation end sign was not found.', | |
[34 /* X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END */]: 'End bracket for dynamic directive argument was not found. ' + | |
'Note that dynamic directive argument cannot contain spaces.', | |
// transform errors | |
[35 /* X_V_IF_NO_EXPRESSION */]: `v-if/v-else-if is missing expression.`, | |
[36 /* X_V_ELSE_NO_ADJACENT_IF */]: `v-else/v-else-if has no adjacent v-if.`, | |
[37 /* X_V_FOR_NO_EXPRESSION */]: `v-for is missing expression.`, | |
[38 /* X_V_FOR_MALFORMED_EXPRESSION */]: `v-for has invalid expression.`, | |
[39 /* X_V_BIND_NO_EXPRESSION */]: `v-bind is missing expression.`, | |
[40 /* X_V_ON_NO_EXPRESSION */]: `v-on is missing expression.`, | |
[41 /* X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET */]: `Unexpected custom directive on <slot> outlet.`, | |
[42 /* X_V_SLOT_NAMED_SLOT_ON_COMPONENT */]: `Named v-slot on component. ` + | |
`Named slots should use <template v-slot> syntax nested inside the component.`, | |
[43 /* X_V_SLOT_MIXED_SLOT_USAGE */]: `Mixed v-slot usage on both the component and nested <template>.` + | |
`The default slot should also use <template> syntax when there are other ` + | |
`named slots to avoid scope ambiguity.`, | |
[44 /* X_V_SLOT_DUPLICATE_SLOT_NAMES */]: `Duplicate slot names found. `, | |
[45 /* X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN */]: `Extraneous children found when component has explicit slots. ` + | |
`These children will be ignored.`, | |
[46 /* X_V_SLOT_MISPLACED */]: `v-slot can only be used on components or <template> tags.`, | |
[47 /* X_V_MODEL_NO_EXPRESSION */]: `v-model is missing expression.`, | |
[48 /* X_V_MODEL_MALFORMED_EXPRESSION */]: `v-model value must be a valid JavaScript member expression.`, | |
[49 /* X_V_MODEL_ON_SCOPE_VARIABLE */]: `v-model cannot be used on v-for or v-slot scope variables because they are not writable.`, | |
[50 /* X_INVALID_EXPRESSION */]: `Invalid JavaScript expression.`, | |
// generic errors | |
[51 /* X_PREFIX_ID_NOT_SUPPORTED */]: `"prefixIdentifiers" option is not supported in this build of compiler.`, | |
[52 /* X_MODULE_MODE_NOT_SUPPORTED */]: `ES module mode is not supported in this build of compiler.` | |
}; | |
// AST Utilities --------------------------------------------------------------- | |
// Some expressions, e.g. sequence and conditional expressions, are never | |
// associated with template nodes, so their source locations are just a stub. | |
// Container types like CompoundExpression also don't need a real location. | |
const locStub = { | |
source: '', | |
start: { line: 1, column: 1, offset: 0 }, | |
end: { line: 1, column: 1, offset: 0 } | |
}; | |
function createArrayExpression(elements, loc = locStub) { | |
return { | |
type: 16 /* JS_ARRAY_EXPRESSION */, | |
loc, | |
elements | |
}; | |
} | |
function createObjectExpression(properties, loc = locStub) { | |
return { | |
type: 14 /* JS_OBJECT_EXPRESSION */, | |
loc, | |
properties | |
}; | |
} | |
function createObjectProperty(key, value) { | |
return { | |
type: 15 /* JS_PROPERTY */, | |
loc: locStub, | |
key: isString(key) ? createSimpleExpression(key, true) : key, | |
value | |
}; | |
} | |
function createSimpleExpression(content, isStatic, loc = locStub, isConstant = false) { | |
return { | |
type: 4 /* SIMPLE_EXPRESSION */, | |
loc, | |
isConstant, | |
content, | |
isStatic | |
}; | |
} | |
function createCompoundExpression(children, loc = locStub) { | |
return { | |
type: 8 /* COMPOUND_EXPRESSION */, | |
loc, | |
children | |
}; | |
} | |
function createCallExpression(callee, args = [], loc = locStub) { | |
return { | |
type: 13 /* JS_CALL_EXPRESSION */, | |
loc, | |
callee, | |
arguments: args | |
}; | |
} | |
function createFunctionExpression(params, returns, newline = false, loc = locStub) { | |
return { | |
type: 17 /* JS_FUNCTION_EXPRESSION */, | |
params, | |
returns, | |
newline, | |
loc | |
}; | |
} | |
function createSequenceExpression(expressions) { | |
return { | |
type: 18 /* JS_SEQUENCE_EXPRESSION */, | |
expressions, | |
loc: locStub | |
}; | |
} | |
function createConditionalExpression(test, consequent, alternate) { | |
return { | |
type: 19 /* JS_CONDITIONAL_EXPRESSION */, | |
test, | |
consequent, | |
alternate, | |
loc: locStub | |
}; | |
} | |
function createCacheExpression(index, value, isVNode = false) { | |
return { | |
type: 20 /* JS_CACHE_EXPRESSION */, | |
index, | |
value, | |
isVNode, | |
loc: locStub | |
}; | |
} | |
const FRAGMENT = Symbol( `Fragment` ); | |
const PORTAL = Symbol( `Portal` ); | |
const SUSPENSE = Symbol( `Suspense` ); | |
const KEEP_ALIVE = Symbol( `KeepAlive` ); | |
const OPEN_BLOCK = Symbol( `openBlock` ); | |
const CREATE_BLOCK = Symbol( `createBlock` ); | |
const CREATE_VNODE = Symbol( `createVNode` ); | |
const CREATE_COMMENT = Symbol( `createCommentVNode` ); | |
const CREATE_TEXT = Symbol( `createTextVNode` ); | |
const RESOLVE_COMPONENT = Symbol( `resolveComponent` ); | |
const RESOLVE_DYNAMIC_COMPONENT = Symbol( `resolveDynamicComponent` ); | |
const RESOLVE_DIRECTIVE = Symbol( `resolveDirective` ); | |
const WITH_DIRECTIVES = Symbol( `withDirectives` ); | |
const RENDER_LIST = Symbol( `renderList` ); | |
const RENDER_SLOT = Symbol( `renderSlot` ); | |
const CREATE_SLOTS = Symbol( `createSlots` ); | |
const TO_STRING = Symbol( `toString` ); | |
const MERGE_PROPS = Symbol( `mergeProps` ); | |
const TO_HANDLERS = Symbol( `toHandlers` ); | |
const CAMELIZE = Symbol( `camelize` ); | |
const SET_BLOCK_TRACKING = Symbol( `setBlockTracking` ); | |
// Name mapping for runtime helpers that need to be imported from 'vue' in | |
// generated code. Make sure these are correctly exported in the runtime! | |
// Using `any` here because TS doesn't allow symbols as index type. | |
const helperNameMap = { | |
[FRAGMENT]: `Fragment`, | |
[PORTAL]: `Portal`, | |
[SUSPENSE]: `Suspense`, | |
[KEEP_ALIVE]: `KeepAlive`, | |
[OPEN_BLOCK]: `openBlock`, | |
[CREATE_BLOCK]: `createBlock`, | |
[CREATE_VNODE]: `createVNode`, | |
[CREATE_COMMENT]: `createCommentVNode`, | |
[CREATE_TEXT]: `createTextVNode`, | |
[RESOLVE_COMPONENT]: `resolveComponent`, | |
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`, | |
[RESOLVE_DIRECTIVE]: `resolveDirective`, | |
[WITH_DIRECTIVES]: `withDirectives`, | |
[RENDER_LIST]: `renderList`, | |
[RENDER_SLOT]: `renderSlot`, | |
[CREATE_SLOTS]: `createSlots`, | |
[TO_STRING]: `toString`, | |
[MERGE_PROPS]: `mergeProps`, | |
[TO_HANDLERS]: `toHandlers`, | |
[CAMELIZE]: `camelize`, | |
[SET_BLOCK_TRACKING]: `setBlockTracking` | |
}; | |
function registerRuntimeHelpers(helpers) { | |
Object.getOwnPropertySymbols(helpers).forEach(s => { | |
helperNameMap[s] = helpers[s]; | |
}); | |
} | |
const nonIdentifierRE = /^\d|[^\$\w]/; | |
const isSimpleIdentifier = (name) => !nonIdentifierRE.test(name); | |
const memberExpRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[[^\]]+\])*$/; | |
const isMemberExpression = (path) => memberExpRE.test(path); | |
function getInnerRange(loc, offset, length) { | |
assert(offset <= loc.source.length); | |
const source = loc.source.substr(offset, length); | |
const newLoc = { | |
source, | |
start: advancePositionWithClone(loc.start, loc.source, offset), | |
end: loc.end | |
}; | |
if (length != null) { | |
assert(offset + length <= loc.source.length); | |
newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length); | |
} | |
return newLoc; | |
} | |
function advancePositionWithClone(pos, source, numberOfCharacters = source.length) { | |
return advancePositionWithMutation({ ...pos }, source, numberOfCharacters); | |
} | |
// advance by mutation without cloning (for performance reasons), since this | |
// gets called a lot in the parser | |
function advancePositionWithMutation(pos, source, numberOfCharacters = source.length) { | |
let linesCount = 0; | |
let lastNewLinePos = -1; | |
for (let i = 0; i < numberOfCharacters; i++) { | |
if (source.charCodeAt(i) === 10 /* newline char code */) { | |
linesCount++; | |
lastNewLinePos = i; | |
} | |
} | |
pos.offset += numberOfCharacters; | |
pos.line += linesCount; | |
pos.column = | |
lastNewLinePos === -1 | |
? pos.column + numberOfCharacters | |
: Math.max(1, numberOfCharacters - lastNewLinePos); | |
return pos; | |
} | |
function assert(condition, msg) { | |
/* istanbul ignore if */ | |
if (!condition) { | |
throw new Error(msg || `unexpected compiler condition`); | |
} | |
} | |
function findDir(node, name, allowEmpty = false) { | |
for (let i = 0; i < node.props.length; i++) { | |
const p = node.props[i]; | |
if (p.type === 7 /* DIRECTIVE */ && | |
(allowEmpty || p.exp) && | |
(isString(name) ? p.name === name : name.test(p.name))) { | |
return p; | |
} | |
} | |
} | |
function findProp(node, name, dynamicOnly = false) { | |
for (let i = 0; i < node.props.length; i++) { | |
const p = node.props[i]; | |
if (p.type === 6 /* ATTRIBUTE */) { | |
if (dynamicOnly) | |
continue; | |
if (p.name === name && p.value) { | |
return p; | |
} | |
} | |
else if (p.name === 'bind' && | |
p.arg && | |
p.arg.type === 4 /* SIMPLE_EXPRESSION */ && | |
p.arg.isStatic && | |
p.arg.content === name && | |
p.exp) { | |
return p; | |
} | |
} | |
} | |
function createBlockExpression(blockExp, context) { | |
return createSequenceExpression([ | |
createCallExpression(context.helper(OPEN_BLOCK)), | |
blockExp | |
]); | |
} | |
function isVSlot(p) { | |
return p.type === 7 /* DIRECTIVE */ && p.name === 'slot'; | |
} | |
function isTemplateNode(node) { | |
return (node.type === 1 /* ELEMENT */ && node.tagType === 3 /* TEMPLATE */); | |
} | |
function isSlotOutlet(node) { | |
return node.type === 1 /* ELEMENT */ && node.tagType === 2 /* SLOT */; | |
} | |
function injectProp(node, prop, context) { | |
let propsWithInjection; | |
const props = node.callee === RENDER_SLOT ? node.arguments[2] : node.arguments[1]; | |
if (props == null || isString(props)) { | |
propsWithInjection = createObjectExpression([prop]); | |
} | |
else if (props.type === 13 /* JS_CALL_EXPRESSION */) { | |
// merged props... add ours | |
// only inject key to object literal if it's the first argument so that | |
// if doesn't override user provided keys | |
const first = props.arguments[0]; | |
if (!isString(first) && first.type === 14 /* JS_OBJECT_EXPRESSION */) { | |
first.properties.unshift(prop); | |
} | |
else { | |
props.arguments.unshift(createObjectExpression([prop])); | |
} | |
propsWithInjection = props; | |
} | |
else if (props.type === 14 /* JS_OBJECT_EXPRESSION */) { | |
props.properties.unshift(prop); | |
propsWithInjection = props; | |
} | |
else { | |
// single v-bind with expression, return a merged replacement | |
propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [ | |
createObjectExpression([prop]), | |
props | |
]); | |
} | |
if (node.callee === RENDER_SLOT) { | |
node.arguments[2] = propsWithInjection; | |
} | |
else { | |
node.arguments[1] = propsWithInjection; | |
} | |
} | |
function toValidAssetId(name, type) { | |
return `_${type}_${name.replace(/[^\w]/g, '_')}`; | |
} | |
// Portal and Fragment are native types, not components | |
const isBuiltInComponent = /*#__PURE__*/ makeMap(`suspense,keep-alive,keepalive,transition`, true); | |
const defaultParserOptions = { | |
delimiters: [`{{`, `}}`], | |
getNamespace: () => 0 /* HTML */, | |
getTextMode: () => 0 /* DATA */, | |
isVoidTag: NO, | |
isPreTag: NO, | |
isCustomElement: NO, | |
namedCharacterReferences: { | |
'gt;': '>', | |
'lt;': '<', | |
'amp;': '&', | |
'apos;': "'", | |
'quot;': '"' | |
}, | |
onError: defaultOnError | |
}; | |
function parse(content, options = {}) { | |
const context = createParserContext(content, options); | |
const start = getCursor(context); | |
return { | |
type: 0 /* ROOT */, | |
children: parseChildren(context, 0 /* DATA */, []), | |
helpers: [], | |
components: [], | |
directives: [], | |
hoists: [], | |
cached: 0, | |
codegenNode: undefined, | |
loc: getSelection(context, start) | |
}; | |
} | |
function createParserContext(content, options) { | |
return { | |
options: { | |
...defaultParserOptions, | |
...options | |
}, | |
column: 1, | |
line: 1, | |
offset: 0, | |
originalSource: content, | |
source: content, | |
maxCRNameLength: Object.keys(options.namedCharacterReferences || | |
defaultParserOptions.namedCharacterReferences).reduce((max, name) => Math.max(max, name.length), 0), | |
inPre: false | |
}; | |
} | |
function parseChildren(context, mode, ancestors) { | |
const parent = last(ancestors); | |
const ns = parent ? parent.ns : 0 /* HTML */; | |
const nodes = []; | |
while (!isEnd(context, mode, ancestors)) { | |
assert(context.source.length > 0); | |
const s = context.source; | |
let node = undefined; | |
if (!context.inPre && startsWith(s, context.options.delimiters[0])) { | |
// '{{' | |
node = parseInterpolation(context, mode); | |
} | |
else if (mode === 0 /* DATA */ && s[0] === '<') { | |
// https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state | |
if (s.length === 1) { | |
emitError(context, 8 /* EOF_BEFORE_TAG_NAME */, 1); | |
} | |
else if (s[1] === '!') { | |
// https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state | |
if (startsWith(s, '<!--')) { | |
node = parseComment(context); | |
} | |
else if (startsWith(s, '<!DOCTYPE')) { | |
// Ignore DOCTYPE by a limitation. | |
node = parseBogusComment(context); | |
} | |
else if (startsWith(s, '<![CDATA[')) { | |
if (ns !== 0 /* HTML */) { | |
node = parseCDATA(context, ancestors); | |
} | |
else { | |
emitError(context, 2 /* CDATA_IN_HTML_CONTENT */); | |
node = parseBogusComment(context); | |
} | |
} | |
else { | |
emitError(context, 14 /* INCORRECTLY_OPENED_COMMENT */); | |
node = parseBogusComment(context); | |
} | |
} | |
else if (s[1] === '/') { | |
// https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state | |
if (s.length === 2) { | |
emitError(context, 8 /* EOF_BEFORE_TAG_NAME */, 2); | |
} | |
else if (s[2] === '>') { | |
emitError(context, 17 /* MISSING_END_TAG_NAME */, 2); | |
advanceBy(context, 3); | |
continue; | |
} | |
else if (/[a-z]/i.test(s[2])) { | |
emitError(context, 31 /* X_INVALID_END_TAG */); | |
parseTag(context, 1 /* End */, parent); | |
continue; | |
} | |
else { | |
emitError(context, 15 /* INVALID_FIRST_CHARACTER_OF_TAG_NAME */, 2); | |
node = parseBogusComment(context); | |
} | |
} | |
else if (/[a-z]/i.test(s[1])) { | |
node = parseElement(context, ancestors); | |
} | |
else if (s[1] === '?') { | |
emitError(context, 28 /* UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME */, 1); | |
node = parseBogusComment(context); | |
} | |
else { | |
emitError(context, 15 /* INVALID_FIRST_CHARACTER_OF_TAG_NAME */, 1); | |
} | |
} | |
if (!node) { | |
node = parseText(context, mode); | |
} | |
if (isArray(node)) { | |
for (let i = 0; i < node.length; i++) { | |
pushNode(nodes, node[i]); | |
} | |
} | |
else { | |
pushNode(nodes, node); | |
} | |
} | |
// Whitespace management for more efficient output | |
// (same as v2 whitespance: 'condense') | |
let removedWhitespace = false; | |
if (mode !== 2 /* RAWTEXT */ && | |
(!parent || !context.options.isPreTag(parent.tag))) { | |
for (let i = 0; i < nodes.length; i++) { | |
const node = nodes[i]; | |
if (node.type === 2 /* TEXT */) { | |
if (!node.content.trim()) { | |
const prev = nodes[i - 1]; | |
const next = nodes[i + 1]; | |
// If: | |
// - the whitespace is the first or last node, or: | |
// - the whitespace is adjacent to a comment, or: | |
// - the whitespace is between two elements AND contains newline | |
// Then the whitespace is ignored. | |
if (!prev || | |
!next || | |
prev.type === 3 /* COMMENT */ || | |
next.type === 3 /* COMMENT */ || | |
(prev.type === 1 /* ELEMENT */ && | |
next.type === 1 /* ELEMENT */ && | |
/[\r\n]/.test(node.content))) { | |
removedWhitespace = true; | |
nodes[i] = null; | |
} | |
else { | |
// Otherwise, condensed consecutive whitespace inside the text down to | |
// a single space | |
node.content = ' '; | |
} | |
} | |
else { | |
node.content = node.content.replace(/\s+/g, ' '); | |
} | |
} | |
} | |
} | |
return removedWhitespace ? nodes.filter(node => node !== null) : nodes; | |
} | |
function pushNode(nodes, node) { | |
if (node.type === 2 /* TEXT */) { | |
const prev = last(nodes); | |
// Merge if both this and the previous node are text and those are | |
// consecutive. This happens for cases like "a < b". | |
if (prev && | |
prev.type === 2 /* TEXT */ && | |
prev.loc.end.offset === node.loc.start.offset) { | |
prev.content += node.content; | |
prev.loc.end = node.loc.end; | |
prev.loc.source += node.loc.source; | |
return; | |
} | |
} | |
nodes.push(node); | |
} | |
function parseCDATA(context, ancestors) { | |
assert(last(ancestors) == null || last(ancestors).ns !== 0 /* HTML */); | |
assert(startsWith(context.source, '<![CDATA[')); | |
advanceBy(context, 9); | |
const nodes = parseChildren(context, 3 /* CDATA */, ancestors); | |
if (context.source.length === 0) { | |
emitError(context, 9 /* EOF_IN_CDATA */); | |
} | |
else { | |
assert(startsWith(context.source, ']]>')); | |
advanceBy(context, 3); | |
} | |
return nodes; | |
} | |
function parseComment(context) { | |
assert(startsWith(context.source, '<!--')); | |
const start = getCursor(context); | |
let content; | |
// Regular comment. | |
const match = /--(\!)?>/.exec(context.source); | |
if (!match) { | |
content = context.source.slice(4); | |
advanceBy(context, context.source.length); | |
emitError(context, 10 /* EOF_IN_COMMENT */); | |
} | |
else { | |
if (match.index <= 3) { | |
emitError(context, 0 /* ABRUPT_CLOSING_OF_EMPTY_COMMENT */); | |
} | |
if (match[1]) { | |
emitError(context, 13 /* INCORRECTLY_CLOSED_COMMENT */); | |
} | |
content = context.source.slice(4, match.index); | |
// Advancing with reporting nested comments. | |
const s = context.source.slice(0, match.index); | |
let prevIndex = 1, nestedIndex = 0; | |
while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) { | |
advanceBy(context, nestedIndex - prevIndex + 1); | |
if (nestedIndex + 4 < s.length) { | |
emitError(context, 20 /* NESTED_COMMENT */); | |
} | |
prevIndex = nestedIndex + 1; | |
} | |
advanceBy(context, match.index + match[0].length - prevIndex + 1); | |
} | |
return { | |
type: 3 /* COMMENT */, | |
content, | |
loc: getSelection(context, start) | |
}; | |
} | |
function parseBogusComment(context) { | |
assert(/^<(?:[\!\?]|\/[^a-z>])/i.test(context.source)); | |
const start = getCursor(context); | |
const contentStart = context.source[1] === '?' ? 1 : 2; | |
let content; | |
const closeIndex = context.source.indexOf('>'); | |
if (closeIndex === -1) { | |
content = context.source.slice(contentStart); | |
advanceBy(context, context.source.length); | |
} | |
else { | |
content = context.source.slice(contentStart, closeIndex); | |
advanceBy(context, closeIndex + 1); | |
} | |
return { | |
type: 3 /* COMMENT */, | |
content, | |
loc: getSelection(context, start) | |
}; | |
} | |
function parseElement(context, ancestors) { | |
assert(/^<[a-z]/i.test(context.source)); | |
// Start tag. | |
const wasInPre = context.inPre; | |
const parent = last(ancestors); | |
const element = parseTag(context, 0 /* Start */, parent); | |
const isPreBoundary = context.inPre && !wasInPre; | |
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) { | |
return element; | |
} | |
// Children. | |
ancestors.push(element); | |
const mode = context.options.getTextMode(element.tag, element.ns); | |
const children = parseChildren(context, mode, ancestors); | |
ancestors.pop(); | |
element.children = children; | |
// End tag. | |
if (startsWithEndTagOpen(context.source, element.tag)) { | |
parseTag(context, 1 /* End */, parent); | |
} | |
else { | |
emitError(context, 32 /* X_MISSING_END_TAG */); | |
if (context.source.length === 0 && element.tag.toLowerCase() === 'script') { | |
const first = children[0]; | |
if (first && startsWith(first.loc.source, '<!--')) { | |
emitError(context, 11 /* EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT */); | |
} | |
} | |
} | |
element.loc = getSelection(context, element.loc.start); | |
if (isPreBoundary) { | |
context.inPre = false; | |
} | |
return element; | |
} | |
/** | |
* Parse a tag (E.g. `<div id=a>`) with that type (start tag or end tag). | |
*/ | |
function parseTag(context, type, parent) { | |
assert(/^<\/?[a-z]/i.test(context.source)); | |
assert(type === (startsWith(context.source, '</') ? 1 /* End */ : 0 /* Start */)); | |
// Tag open. | |
const start = getCursor(context); | |
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source); | |
const tag = match[1]; | |
const ns = context.options.getNamespace(tag, parent); | |
advanceBy(context, match[0].length); | |
advanceSpaces(context); | |
// save current state in case we need to re-parse attributes with v-pre | |
const cursor = getCursor(context); | |
const currentSource = context.source; | |
// Attributes. | |
let props = parseAttributes(context, type); | |
// check v-pre | |
if (!context.inPre && | |
props.some(p => p.type === 7 /* DIRECTIVE */ && p.name === 'pre')) { | |
context.inPre = true; | |
// reset context | |
extend(context, cursor); | |
context.source = currentSource; | |
// re-parse attrs and filter out v-pre itself | |
props = parseAttributes(context, type).filter(p => p.name !== 'v-pre'); | |
} | |
// Tag close. | |
let isSelfClosing = false; | |
if (context.source.length === 0) { | |
emitError(context, 12 /* EOF_IN_TAG */); | |
} | |
else { | |
isSelfClosing = startsWith(context.source, '/>'); | |
if (type === 1 /* End */ && isSelfClosing) { | |
emitError(context, 7 /* END_TAG_WITH_TRAILING_SOLIDUS */); | |
} | |
advanceBy(context, isSelfClosing ? 2 : 1); | |
} | |
let tagType = 0 /* ELEMENT */; | |
if (!context.inPre && !context.options.isCustomElement(tag)) { | |
if (context.options.isNativeTag) { | |
if (!context.options.isNativeTag(tag)) | |
tagType = 1 /* COMPONENT */; | |
} | |
else if (isBuiltInComponent(tag) || /^[A-Z]/.test(tag)) { | |
tagType = 1 /* COMPONENT */; | |
} | |
if (tag === 'slot') { | |
tagType = 2 /* SLOT */; | |
} | |
else if (tag === 'template') { | |
tagType = 3 /* TEMPLATE */; | |
} | |
} | |
return { | |
type: 1 /* ELEMENT */, | |
ns, | |
tag, | |
tagType, | |
props, | |
isSelfClosing, | |
children: [], | |
loc: getSelection(context, start), | |
codegenNode: undefined // to be created during transform phase | |
}; | |
} | |
function parseAttributes(context, type) { | |
const props = []; | |
const attributeNames = new Set(); | |
while (context.source.length > 0 && | |
!startsWith(context.source, '>') && | |
!startsWith(context.source, '/>')) { | |
if (startsWith(context.source, '/')) { | |
emitError(context, 29 /* UNEXPECTED_SOLIDUS_IN_TAG */); | |
advanceBy(context, 1); | |
advanceSpaces(context); | |
continue; | |
} | |
if (type === 1 /* End */) { | |
emitError(context, 6 /* END_TAG_WITH_ATTRIBUTES */); | |
} | |
const attr = parseAttribute(context, attributeNames); | |
if (type === 0 /* Start */) { | |
props.push(attr); | |
} | |
if (/^[^\t\r\n\f />]/.test(context.source)) { | |
emitError(context, 19 /* MISSING_WHITESPACE_BETWEEN_ATTRIBUTES */); | |
} | |
advanceSpaces(context); | |
} | |
return props; | |
} | |
function parseAttribute(context, nameSet) { | |
assert(/^[^\t\r\n\f />]/.test(context.source)); | |
// Name. | |
const start = getCursor(context); | |
const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source); | |
const name = match[0]; | |
if (nameSet.has(name)) { | |
emitError(context, 5 /* DUPLICATE_ATTRIBUTE */); | |
} | |
nameSet.add(name); | |
if (name[0] === '=') { | |
emitError(context, 26 /* UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME */); | |
} | |
{ | |
const pattern = /["'<]/g; | |
let m; | |
while ((m = pattern.exec(name)) !== null) { | |
emitError(context, 24 /* UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME */, m.index); | |
} | |
} | |
advanceBy(context, name.length); | |
// Value | |
let value = undefined; | |
if (/^[\t\r\n\f ]*=/.test(context.source)) { | |
advanceSpaces(context); | |
advanceBy(context, 1); | |
advanceSpaces(context); | |
value = parseAttributeValue(context); | |
if (!value) { | |
emitError(context, 16 /* MISSING_ATTRIBUTE_VALUE */); | |
} | |
} | |
const loc = getSelection(context, start); | |
if (!context.inPre && /^(v-|:|@|#)/.test(name)) { | |
const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec(name); | |
let arg; | |
if (match[2]) { | |
const startOffset = name.split(match[2], 2).shift().length; | |
const loc = getSelection(context, getNewPosition(context, start, startOffset), getNewPosition(context, start, startOffset + match[2].length)); | |
let content = match[2]; | |
let isStatic = true; | |
if (content.startsWith('[')) { | |
isStatic = false; | |
if (!content.endsWith(']')) { | |
emitError(context, 34 /* X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END */); | |
} | |
content = content.substr(1, content.length - 2); | |
} | |
arg = { | |
type: 4 /* SIMPLE_EXPRESSION */, | |
content, | |
isStatic, | |
isConstant: isStatic, | |
loc | |
}; | |
} | |
if (value && value.isQuoted) { | |
const valueLoc = value.loc; | |
valueLoc.start.offset++; | |
valueLoc.start.column++; | |
valueLoc.end = advancePositionWithClone(valueLoc.start, value.content); | |
valueLoc.source = valueLoc.source.slice(1, -1); | |
} | |
return { | |
type: 7 /* DIRECTIVE */, | |
name: match[1] || | |
(startsWith(name, ':') | |
? 'bind' | |
: startsWith(name, '@') | |
? 'on' | |
: 'slot'), | |
exp: value && { | |
type: 4 /* SIMPLE_EXPRESSION */, | |
content: value.content, | |
isStatic: false, | |
// Treat as non-constant by default. This can be potentially set to | |
// true by `transformExpression` to make it eligible for hoisting. | |
isConstant: false, | |
loc: value.loc | |
}, | |
arg, | |
modifiers: match[3] ? match[3].substr(1).split('.') : [], | |
loc | |
}; | |
} | |
return { | |
type: 6 /* ATTRIBUTE */, | |
name, | |
value: value && { | |
type: 2 /* TEXT */, | |
content: value.content, | |
loc: value.loc | |
}, | |
loc | |
}; | |
} | |
function parseAttributeValue(context) { | |
const start = getCursor(context); | |
let content; | |
const quote = context.source[0]; | |
const isQuoted = quote === `"` || quote === `'`; | |
if (isQuoted) { | |
// Quoted value. | |
advanceBy(context, 1); | |
const endIndex = context.source.indexOf(quote); | |
if (endIndex === -1) { | |
content = parseTextData(context, context.source.length, 4 /* ATTRIBUTE_VALUE */); | |
} | |
else { | |
content = parseTextData(context, endIndex, 4 /* ATTRIBUTE_VALUE */); | |
advanceBy(context, 1); | |
} | |
} | |
else { | |
// Unquoted | |
const match = /^[^\t\r\n\f >]+/.exec(context.source); | |
if (!match) { | |
return undefined; | |
} | |
let unexpectedChars = /["'<=`]/g; | |
let m; | |
while ((m = unexpectedChars.exec(match[0])) !== null) { | |
emitError(context, 25 /* UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE */, m.index); | |
} | |
content = parseTextData(context, match[0].length, 4 /* ATTRIBUTE_VALUE */); | |
} | |
return { content, isQuoted, loc: getSelection(context, start) }; | |
} | |
function parseInterpolation(context, mode) { | |
const [open, close] = context.options.delimiters; | |
assert(startsWith(context.source, open)); | |
const closeIndex = context.source.indexOf(close, open.length); | |
if (closeIndex === -1) { | |
emitError(context, 33 /* X_MISSING_INTERPOLATION_END */); | |
return undefined; | |
} | |
const start = getCursor(context); | |
advanceBy(context, open.length); | |
const innerStart = getCursor(context); | |
const innerEnd = getCursor(context); | |
const rawContentLength = closeIndex - open.length; | |
const rawContent = context.source.slice(0, rawContentLength); | |
const preTrimContent = parseTextData(context, rawContentLength, mode); | |
const content = preTrimContent.trim(); | |
const startOffset = preTrimContent.indexOf(content); | |
if (startOffset > 0) { | |
advancePositionWithMutation(innerStart, rawContent, startOffset); | |
} | |
const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); | |
advancePositionWithMutation(innerEnd, rawContent, endOffset); | |
advanceBy(context, close.length); | |
return { | |
type: 5 /* INTERPOLATION */, | |
content: { | |
type: 4 /* SIMPLE_EXPRESSION */, | |
isStatic: false, | |
// Set `isConstant` to false by default and will decide in transformExpression | |
isConstant: false, | |
content, | |
loc: getSelection(context, innerStart, innerEnd) | |
}, | |
loc: getSelection(context, start) | |
}; | |
} | |
function parseText(context, mode) { | |
assert(context.source.length > 0); | |
const [open] = context.options.delimiters; | |
// TODO could probably use some perf optimization | |
const endIndex = Math.min(...[ | |
context.source.indexOf('<', 1), | |
context.source.indexOf(open, 1), | |
mode === 3 /* CDATA */ ? context.source.indexOf(']]>') : -1, | |
context.source.length | |
].filter(n => n !== -1)); | |
assert(endIndex > 0); | |
const start = getCursor(context); | |
const content = parseTextData(context, endIndex, mode); | |
return { | |
type: 2 /* TEXT */, | |
content, | |
loc: getSelection(context, start) | |
}; | |
} | |
/** | |
* Get text data with a given length from the current location. | |
* This translates HTML entities in the text data. | |
*/ | |
function parseTextData(context, length, mode) { | |
if (mode === 2 /* RAWTEXT */ || mode === 3 /* CDATA */) { | |
const text = context.source.slice(0, length); | |
advanceBy(context, length); | |
return text; | |
} | |
// DATA or RCDATA. Entity decoding required. | |
const end = context.offset + length; | |
let text = ''; | |
while (context.offset < end) { | |
const head = /&(?:#x?)?/i.exec(context.source); | |
if (!head || context.offset + head.index >= end) { | |
const remaining = end - context.offset; | |
text += context.source.slice(0, remaining); | |
advanceBy(context, remaining); | |
break; | |
} | |
// Advance to the "&". | |
text += context.source.slice(0, head.index); | |
advanceBy(context, head.index); | |
if (head[0] === '&') { | |
// Named character reference. | |
let name = '', value = undefined; | |
if (/[0-9a-z]/i.test(context.source[1])) { | |
for (let length = context.maxCRNameLength; !value && length > 0; --length) { | |
name = context.source.substr(1, length); | |
value = context.options.namedCharacterReferences[name]; | |
} | |
if (value) { | |
const semi = name.endsWith(';'); | |
if (mode === 4 /* ATTRIBUTE_VALUE */ && | |
!semi && | |
/[=a-z0-9]/i.test(context.source[1 + name.length] || '')) { | |
text += '&'; | |
text += name; | |
advanceBy(context, 1 + name.length); | |
} | |
else { | |
text += value; | |
advanceBy(context, 1 + name.length); | |
if (!semi) { | |
emitError(context, 18 /* MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE */); | |
} | |
} | |
} | |
else { | |
emitError(context, 30 /* UNKNOWN_NAMED_CHARACTER_REFERENCE */); | |
text += '&'; | |
text += name; | |
advanceBy(context, 1 + name.length); | |
} | |
} | |
else { | |
text += '&'; | |
advanceBy(context, 1); | |
} | |
} | |
else { | |
// Numeric character reference. | |
const hex = head[0] === '&#x'; | |
const pattern = hex ? /^&#x([0-9a-f]+);?/i : /^&#([0-9]+);?/; | |
const body = pattern.exec(context.source); | |
if (!body) { | |
text += head[0]; | |
emitError(context, 1 /* ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE */); | |
advanceBy(context, head[0].length); | |
} | |
else { | |
// https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state | |
let cp = Number.parseInt(body[1], hex ? 16 : 10); | |
if (cp === 0) { | |
emitError(context, 22 /* NULL_CHARACTER_REFERENCE */); | |
cp = 0xfffd; | |
} | |
else if (cp > 0x10ffff) { | |
emitError(context, 3 /* CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE */); | |
cp = 0xfffd; | |
} | |
else if (cp >= 0xd800 && cp <= 0xdfff) { | |
emitError(context, 23 /* SURROGATE_CHARACTER_REFERENCE */); | |
cp = 0xfffd; | |
} | |
else if ((cp >= 0xfdd0 && cp <= 0xfdef) || (cp & 0xfffe) === 0xfffe) { | |
emitError(context, 21 /* NONCHARACTER_CHARACTER_REFERENCE */); | |
} | |
else if ((cp >= 0x01 && cp <= 0x08) || | |
cp === 0x0b || | |
(cp >= 0x0d && cp <= 0x1f) || | |
(cp >= 0x7f && cp <= 0x9f)) { | |
emitError(context, 4 /* CONTROL_CHARACTER_REFERENCE */); | |
cp = CCR_REPLACEMENTS[cp] || cp; | |
} | |
text += String.fromCodePoint(cp); | |
advanceBy(context, body[0].length); | |
if (!body[0].endsWith(';')) { | |
emitError(context, 18 /* MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE */); | |
} | |
} | |
} | |
} | |
return text; | |
} | |
function getCursor(context) { | |
const { column, line, offset } = context; | |
return { column, line, offset }; | |
} | |
function getSelection(context, start, end) { | |
end = end || getCursor(context); | |
return { | |
start, | |
end, | |
source: context.originalSource.slice(start.offset, end.offset) | |
}; | |
} | |
function last(xs) { | |
return xs[xs.length - 1]; | |
} | |
function startsWith(source, searchString) { | |
return source.startsWith(searchString); | |
} | |
function advanceBy(context, numberOfCharacters) { | |
const { source } = context; | |
assert(numberOfCharacters <= source.length); | |
advancePositionWithMutation(context, source, numberOfCharacters); | |
context.source = source.slice(numberOfCharacters); | |
} | |
function advanceSpaces(context) { | |
const match = /^[\t\r\n\f ]+/.exec(context.source); | |
if (match) { | |
advanceBy(context, match[0].length); | |
} | |
} | |
function getNewPosition(context, start, numberOfCharacters) { | |
return advancePositionWithClone(start, context.originalSource.slice(start.offset, numberOfCharacters), numberOfCharacters); | |
} | |
function emitError(context, code, offset) { | |
const loc = getCursor(context); | |
if (offset) { | |
loc.offset += offset; | |
loc.column += offset; | |
} | |
context.options.onError(createCompilerError(code, { | |
start: loc, | |
end: loc, | |
source: '' | |
})); | |
} | |
function isEnd(context, mode, ancestors) { | |
const s = context.source; | |
switch (mode) { | |
case 0 /* DATA */: | |
if (startsWith(s, '</')) { | |
//TODO: probably bad performance | |
for (let i = ancestors.length - 1; i >= 0; --i) { | |
if (startsWithEndTagOpen(s, ancestors[i].tag)) { | |
return true; | |
} | |
} | |
} | |
break; | |
case 1 /* RCDATA */: | |
case 2 /* RAWTEXT */: { | |
const parent = last(ancestors); | |
if (parent && startsWithEndTagOpen(s, parent.tag)) { | |
return true; | |
} | |
break; | |
} | |
case 3 /* CDATA */: | |
if (startsWith(s, ']]>')) { | |
return true; | |
} | |
break; | |
} | |
return !s; | |
} | |
function startsWithEndTagOpen(source, tag) { | |
return (startsWith(source, '</') && | |
source.substr(2, tag.length).toLowerCase() === tag.toLowerCase() && | |
/[\t\n\f />]/.test(source[2 + tag.length] || '>')); | |
} | |
// https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state | |
const CCR_REPLACEMENTS = { | |
0x80: 0x20ac, | |
0x82: 0x201a, | |
0x83: 0x0192, | |
0x84: 0x201e, | |
0x85: 0x2026, | |
0x86: 0x2020, | |
0x87: 0x2021, | |
0x88: 0x02c6, | |
0x89: 0x2030, | |
0x8a: 0x0160, | |
0x8b: 0x2039, | |
0x8c: 0x0152, | |
0x8e: 0x017d, | |
0x91: 0x2018, | |
0x92: 0x2019, | |
0x93: 0x201c, | |
0x94: 0x201d, | |
0x95: 0x2022, | |
0x96: 0x2013, | |
0x97: 0x2014, | |
0x98: 0x02dc, | |
0x99: 0x2122, | |
0x9a: 0x0161, | |
0x9b: 0x203a, | |
0x9c: 0x0153, | |
0x9e: 0x017e, | |
0x9f: 0x0178 | |
}; | |
function hoistStatic(root, context) { | |
walk(root.children, context, new Map(), isSingleElementRoot(root, root.children[0])); | |
} | |
function isSingleElementRoot(root, child) { | |
const { children } = root; | |
return (children.length === 1 && | |
child.type === 1 /* ELEMENT */ && | |
!isSlotOutlet(child)); | |
} | |
function walk(children, context, resultCache, doNotHoistNode = false) { | |
for (let i = 0; i < children.length; i++) { | |
const child = children[i]; | |
// only plain elements are eligible for hoisting. | |
if (child.type === 1 /* ELEMENT */ && | |
child.tagType === 0 /* ELEMENT */) { | |
if (!doNotHoistNode && isStaticNode(child, resultCache)) { | |
// whole tree is static | |
child.codegenNode = context.hoist(child.codegenNode); | |
continue; | |
} | |
else { | |
// node may contain dynamic children, but its props may be eligible for | |
// hoisting. | |
const codegenNode = child.codegenNode; | |
if (codegenNode.type === 13 /* JS_CALL_EXPRESSION */) { | |
const flag = getPatchFlag(codegenNode); | |
if ((!flag || | |
flag === 32 /* NEED_PATCH */ || | |
flag === 1 /* TEXT */) && | |
!hasDynamicKeyOrRef(child) && | |
!hasCachedProps()) { | |
const props = getNodeProps(child); | |
if (props && props !== `null`) { | |
getVNodeCall(codegenNode).arguments[1] = context.hoist(props); | |
} | |
} | |
} | |
} | |
} | |
if (child.type === 1 /* ELEMENT */) { | |
walk(child.children, context, resultCache); | |
} | |
else if (child.type === 11 /* FOR */) { | |
// Do not hoist v-for single child because it has to be a block | |
walk(child.children, context, resultCache, child.children.length === 1); | |
} | |
else if (child.type === 9 /* IF */) { | |
for (let i = 0; i < child.branches.length; i++) { | |
const branchChildren = child.branches[i].children; | |
// Do not hoist v-if single child because it has to be a block | |
walk(branchChildren, context, resultCache, branchChildren.length === 1); | |
} | |
} | |
} | |
} | |
function isStaticNode(node, resultCache = new Map()) { | |
switch (node.type) { | |
case 1 /* ELEMENT */: | |
if (node.tagType !== 0 /* ELEMENT */) { | |
return false; | |
} | |
const cached = resultCache.get(node); | |
if (cached !== undefined) { | |
return cached; | |
} | |
const codegenNode = node.codegenNode; | |
if (codegenNode.type !== 13 /* JS_CALL_EXPRESSION */) { | |
return false; | |
} | |
const flag = getPatchFlag(codegenNode); | |
if (!flag && !hasDynamicKeyOrRef(node) && !hasCachedProps()) { | |
// element self is static. check its children. | |
for (let i = 0; i < node.children.length; i++) { | |
if (!isStaticNode(node.children[i], resultCache)) { | |
resultCache.set(node, false); | |
return false; | |
} | |
} | |
resultCache.set(node, true); | |
return true; | |
} | |
else { | |
resultCache.set(node, false); | |
return false; | |
} | |
case 2 /* TEXT */: | |
case 3 /* COMMENT */: | |
return true; | |
case 9 /* IF */: | |
case 11 /* FOR */: | |
return false; | |
case 5 /* INTERPOLATION */: | |
case 12 /* TEXT_CALL */: | |
return isStaticNode(node.content, resultCache); | |
case 4 /* SIMPLE_EXPRESSION */: | |
return node.isConstant; | |
case 8 /* COMPOUND_EXPRESSION */: | |
return node.children.every(child => { | |
return (isString(child) || isSymbol(child) || isStaticNode(child, resultCache)); | |
}); | |
default: | |
return false; | |
} | |
} | |
function hasDynamicKeyOrRef(node) { | |
return !!(findProp(node, 'key', true) || findProp(node, 'ref', true)); | |
} | |
function hasCachedProps(node) { | |
{ | |
return false; | |
} | |
} | |
function getNodeProps(node) { | |
const codegenNode = node.codegenNode; | |
if (codegenNode.type === 13 /* JS_CALL_EXPRESSION */) { | |
return getVNodeArgAt(codegenNode, 1); | |
} | |
} | |
function getVNodeArgAt(node, index) { | |
return getVNodeCall(node).arguments[index]; | |
} | |
function getVNodeCall(node) { | |
return node.callee === WITH_DIRECTIVES ? node.arguments[0] : node; | |
} | |
function getPatchFlag(node) { | |
const flag = getVNodeArgAt(node, 3); | |
return flag ? parseInt(flag, 10) : undefined; | |
} | |
function createTransformContext(root, { prefixIdentifiers = false, hoistStatic = false, cacheHandlers = false, nodeTransforms = [], directiveTransforms = {}, onError = defaultOnError }) { | |
const context = { | |
root, | |
helpers: new Set(), | |
components: new Set(), | |
directives: new Set(), | |
hoists: [], | |
cached: 0, | |
identifiers: {}, | |
scopes: { | |
vFor: 0, | |
vSlot: 0, | |
vPre: 0, | |
vOnce: 0 | |
}, | |
prefixIdentifiers, | |
hoistStatic, | |
cacheHandlers, | |
nodeTransforms, | |
directiveTransforms, | |
onError, | |
parent: null, | |
currentNode: root, | |
childIndex: 0, | |
helper(name) { | |
context.helpers.add(name); | |
return name; | |
}, | |
helperString(name) { | |
return ((context.prefixIdentifiers ? `` : `_`) + | |
helperNameMap[context.helper(name)]); | |
}, | |
replaceNode(node) { | |
/* istanbul ignore if */ | |
{ | |
if (!context.currentNode) { | |
throw new Error(`Node being replaced is already removed.`); | |
} | |
if (!context.parent) { | |
throw new Error(`Cannot replace root node.`); | |
} | |
} | |
context.parent.children[context.childIndex] = context.currentNode = node; | |
}, | |
removeNode(node) { | |
if ( !context.parent) { | |
throw new Error(`Cannot remove root node.`); | |
} | |
const list = context.parent.children; | |
const removalIndex = node | |
? list.indexOf(node) | |
: context.currentNode | |
? context.childIndex | |
: -1; | |
/* istanbul ignore if */ | |
if ( removalIndex < 0) { | |
throw new Error(`node being removed is not a child of current parent`); | |
} | |
if (!node || node === context.currentNode) { | |
// current node removed | |
context.currentNode = null; | |
context.onNodeRemoved(); | |
} | |
else { | |
// sibling node removed | |
if (context.childIndex > removalIndex) { | |
context.childIndex--; | |
context.onNodeRemoved(); | |
} | |
} | |
context.parent.children.splice(removalIndex, 1); | |
}, | |
onNodeRemoved: () => { }, | |
addIdentifiers(exp) { | |
}, | |
removeIdentifiers(exp) { | |
}, | |
hoist(exp) { | |
context.hoists.push(exp); | |
return createSimpleExpression(`_hoisted_${context.hoists.length}`, false, exp.loc, true); | |
}, | |
cache(exp, isVNode = false) { | |
return createCacheExpression(++context.cached, exp, isVNode); | |
} | |
}; | |
return context; | |
} | |
function transform(root, options) { | |
const context = createTransformContext(root, options); | |
traverseNode(root, context); | |
if (options.hoistStatic) { | |
hoistStatic(root, context); | |
} | |
finalizeRoot(root, context); | |
} | |
function finalizeRoot(root, context) { | |
const { helper } = context; | |
const { children } = root; | |
const child = children[0]; | |
if (children.length === 1) { | |
// if the single child is an element, turn it into a block. | |
if (isSingleElementRoot(root, child) && child.codegenNode) { | |
// single element root is never hoisted so codegenNode will never be | |
// SimpleExpressionNode | |
const codegenNode = child.codegenNode; | |
if (codegenNode.type !== 20 /* JS_CACHE_EXPRESSION */) { | |
if (codegenNode.callee === WITH_DIRECTIVES) { | |
codegenNode.arguments[0].callee = helper(CREATE_BLOCK); | |
} | |
else { | |
codegenNode.callee = helper(CREATE_BLOCK); | |
} | |
root.codegenNode = createBlockExpression(codegenNode, context); | |
} | |
else { | |
root.codegenNode = codegenNode; | |
} | |
} | |
else { | |
// - single <slot/>, IfNode, ForNode: already blocks. | |
// - single text node: always patched. | |
// root codegen falls through via genNode() | |
root.codegenNode = child; | |
} | |
} | |
else if (children.length > 1) { | |
// root has multiple nodes - return a fragment block. | |
root.codegenNode = createBlockExpression(createCallExpression(helper(CREATE_BLOCK), [ | |
helper(FRAGMENT), | |
`null`, | |
root.children | |
]), context); | |
} | |
// finalize meta information | |
root.helpers = [...context.helpers]; | |
root.components = [...context.components]; | |
root.directives = [...context.directives]; | |
root.hoists = context.hoists; | |
root.cached = context.cached; | |
} | |
function traverseChildren(parent, context) { | |
let i = 0; | |
const nodeRemoved = () => { | |
i--; | |
}; | |
for (; i < parent.children.length; i++) { | |
const child = parent.children[i]; | |
if (isString(child)) | |
continue; | |
context.currentNode = child; | |
context.parent = parent; | |
context.childIndex = i; | |
context.onNodeRemoved = nodeRemoved; | |
traverseNode(child, context); | |
} | |
} | |
function traverseNode(node, context) { | |
// apply transform plugins | |
const { nodeTransforms } = context; | |
const exitFns = []; | |
for (let i = 0; i < nodeTransforms.length; i++) { | |
const onExit = nodeTransforms[i](node, context); | |
if (onExit) { | |
if (isArray(onExit)) { | |
exitFns.push(...onExit); | |
} | |
else { | |
exitFns.push(onExit); | |
} | |
} | |
if (!context.currentNode) { | |
// node was removed | |
return; | |
} | |
else { | |
// node may have been replaced | |
node = context.currentNode; | |
} | |
} | |
switch (node.type) { | |
case 3 /* COMMENT */: | |
// inject import for the Comment symbol, which is needed for creating | |
// comment nodes with `createVNode` | |
context.helper(CREATE_COMMENT); | |
break; | |
case 5 /* INTERPOLATION */: | |
// no need to traverse, but we need to inject toString helper | |
context.helper(TO_STRING); | |
break; | |
// for container types, further traverse downwards | |
case 9 /* IF */: | |
for (let i = 0; i < node.branches.length; i++) { | |
traverseChildren(node.branches[i], context); | |
} | |
break; | |
case 11 /* FOR */: | |
case 1 /* ELEMENT */: | |
case 0 /* ROOT */: | |
traverseChildren(node, context); | |
break; | |
} | |
// exit transforms | |
let i = exitFns.length; | |
while (i--) { | |
exitFns[i](); | |
} | |
} | |
function createStructuralDirectiveTransform(name, fn) { | |
const matches = isString(name) | |
? (n) => n === name | |
: (n) => name.test(n); | |
return (node, context) => { | |
if (node.type === 1 /* ELEMENT */) { | |
const { props } = node; | |
// structural directive transforms are not concerned with slots | |
// as they are handled separately in vSlot.ts | |
if (node.tagType === 3 /* TEMPLATE */ && props.some(isVSlot)) { | |
return; | |
} | |
const exitFns = []; | |
for (let i = 0; i < props.length; i++) { | |
const prop = props[i]; | |
if (prop.type === 7 /* DIRECTIVE */ && matches(prop.name)) { | |
// structural directives are removed to avoid infinite recursion | |
// also we remove them *before* applying so that it can further | |
// traverse itself in case it moves the node around | |
props.splice(i, 1); | |
i--; | |
const onExit = fn(node, prop, context); | |
if (onExit) | |
exitFns.push(onExit); | |
} | |
} | |
return exitFns; | |
} | |
}; | |
} | |
function createCodegenContext(ast, { mode = 'function', prefixIdentifiers = mode === 'module', sourceMap = false, filename = `template.vue.html` }) { | |
const context = { | |
mode, | |
prefixIdentifiers, | |
sourceMap, | |
filename, | |
source: ast.loc.source, | |
code: ``, | |
column: 1, | |
line: 1, | |
offset: 0, | |
indentLevel: 0, | |
// lazy require source-map implementation, only in non-browser builds! | |
map: undefined | |
, | |
helper(key) { | |
const name = helperNameMap[key]; | |
return prefixIdentifiers ? name : `_${name}`; | |
}, | |
push(code, node, openOnly) { | |
context.code += code; | |
}, | |
resetMapping(loc) { | |
}, | |
indent() { | |
newline(++context.indentLevel); | |
}, | |
deindent(withoutNewLine = false) { | |
if (withoutNewLine) { | |
--context.indentLevel; | |
} | |
else { | |
newline(--context.indentLevel); | |
} | |
}, | |
newline() { | |
newline(context.indentLevel); | |
} | |
}; | |
function newline(n) { | |
context.push('\n' + ` `.repeat(n)); | |
} | |
return context; | |
} | |
function generate(ast, options = {}) { | |
const context = createCodegenContext(ast, options); | |
const { mode, push, helper, prefixIdentifiers, indent, deindent, newline } = context; | |
const hasHelpers = ast.helpers.length > 0; | |
const useWithBlock = !prefixIdentifiers && mode !== 'module'; | |
// preambles | |
if (mode === 'function') { | |
// Generate const declaration for helpers | |
// In prefix mode, we place the const declaration at top so it's done | |
// only once; But if we not prefixing, we place the declaration inside the | |
// with block so it doesn't incur the `in` check cost for every helper access. | |
if (hasHelpers) { | |
if (prefixIdentifiers) { | |
push(`const { ${ast.helpers.map(helper).join(', ')} } = Vue\n`); | |
} | |
else { | |
// "with" mode. | |
// save Vue in a separate variable to avoid collision | |
push(`const _Vue = Vue\n`); | |
// in "with" mode, helpers are declared inside the with block to avoid | |
// has check cost, but hoists are lifted out of the function - we need | |
// to provide the helper here. | |
if (ast.hoists.length) { | |
push(`const _${helperNameMap[CREATE_VNODE]} = Vue.${helperNameMap[CREATE_VNODE]}\n`); | |
if (ast.helpers.includes(CREATE_COMMENT)) { | |
push(`const _${helperNameMap[CREATE_COMMENT]} = Vue.${helperNameMap[CREATE_COMMENT]}\n`); | |
} | |
} | |
} | |
} | |
genHoists(ast.hoists, context); | |
newline(); | |
push(`return `); | |
} | |
else { | |
// generate import statements for helpers | |
if (hasHelpers) { | |
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`); | |
} | |
genHoists(ast.hoists, context); | |
newline(); | |
push(`export default `); | |
} | |
// enter render function | |
push(`function render() {`); | |
indent(); | |
if (useWithBlock) { | |
push(`with (this) {`); | |
indent(); | |
// function mode const declarations should be inside with block | |
// also they should be renamed to avoid collision with user properties | |
if (hasHelpers) { | |
push(`const { ${ast.helpers | |
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`) | |
.join(', ')} } = _Vue`); | |
newline(); | |
if (ast.cached > 0) { | |
push(`const _cache = $cache`); | |
newline(); | |
} | |
newline(); | |
} | |
} | |
else { | |
push(`const _ctx = this`); | |
if (ast.cached > 0) { | |
newline(); | |
push(`const _cache = _ctx.$cache`); | |
} | |
newline(); | |
} | |
// generate asset resolution statements | |
if (ast.components.length) { | |
genAssets(ast.components, 'component', context); | |
} | |
if (ast.directives.length) { | |
genAssets(ast.directives, 'directive', context); | |
} | |
if (ast.components.length || ast.directives.length) { | |
newline(); | |
} | |
// generate the VNode tree expression | |
push(`return `); | |
if (ast.codegenNode) { | |
genNode(ast.codegenNode, context); | |
} | |
else { | |
push(`null`); | |
} | |
if (useWithBlock) { | |
deindent(); | |
push(`}`); | |
} | |
deindent(); | |
push(`}`); | |
return { | |
ast, | |
code: context.code, | |
map: context.map ? context.map.toJSON() : undefined | |
}; | |
} | |
function genAssets(assets, type, context) { | |
const resolver = context.helper(type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE); | |
for (let i = 0; i < assets.length; i++) { | |
const id = assets[i]; | |
context.push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`); | |
context.newline(); | |
} | |
} | |
function genHoists(hoists, context) { | |
if (!hoists.length) { | |
return; | |
} | |
context.newline(); | |
hoists.forEach((exp, i) => { | |
context.push(`const _hoisted_${i + 1} = `); | |
genNode(exp, context); | |
context.newline(); | |
}); | |
} | |
function isText(n) { | |
return (isString(n) || | |
n.type === 4 /* SIMPLE_EXPRESSION */ || | |
n.type === 2 /* TEXT */ || | |
n.type === 5 /* INTERPOLATION */ || | |
n.type === 8 /* COMPOUND_EXPRESSION */); | |
} | |
function genNodeListAsArray(nodes, context) { | |
const multilines = nodes.length > 3 || | |
( nodes.some(n => isArray(n) || !isText(n))); | |
context.push(`[`); | |
multilines && context.indent(); | |
genNodeList(nodes, context, multilines); | |
multilines && context.deindent(); | |
context.push(`]`); | |
} | |
function genNodeList(nodes, context, multilines = false) { | |
const { push, newline } = context; | |
for (let i = 0; i < nodes.length; i++) { | |
const node = nodes[i]; | |
if (isString(node)) { | |
push(node); | |
} | |
else if (isArray(node)) { | |
genNodeListAsArray(node, context); | |
} | |
else { | |
genNode(node, context); | |
} | |
if (i < nodes.length - 1) { | |
if (multilines) { | |
push(','); | |
newline(); | |
} | |
else { | |
push(', '); | |
} | |
} | |
} | |
} | |
function genNode(node, context) { | |
if (isString(node)) { | |
context.push(node); | |
return; | |
} | |
if (isSymbol(node)) { | |
context.push(context.helper(node)); | |
return; | |
} | |
switch (node.type) { | |
case 1 /* ELEMENT */: | |
case 9 /* IF */: | |
case 11 /* FOR */: | |
assert(node.codegenNode != null, `Codegen node is missing for element/if/for node. ` + | |
`Apply appropriate transforms first.`); | |
genNode(node.codegenNode, context); | |
break; | |
case 2 /* TEXT */: | |
genText(node, context); | |
break; | |
case 4 /* SIMPLE_EXPRESSION */: | |
genExpression(node, context); | |
break; | |
case 5 /* INTERPOLATION */: | |
genInterpolation(node, context); | |
break; | |
case 12 /* TEXT_CALL */: | |
genNode(node.codegenNode, context); | |
break; | |
case 8 /* COMPOUND_EXPRESSION */: | |
genCompoundExpression(node, context); | |
break; | |
case 3 /* COMMENT */: | |
genComment(node, context); | |
break; | |
case 13 /* JS_CALL_EXPRESSION */: | |
genCallExpression(node, context); | |
break; | |
case 14 /* JS_OBJECT_EXPRESSION */: | |
genObjectExpression(node, context); | |
break; | |
case 16 /* JS_ARRAY_EXPRESSION */: | |
genArrayExpression(node, context); | |
break; | |
case 17 /* JS_FUNCTION_EXPRESSION */: | |
genFunctionExpression(node, context); | |
break; | |
case 18 /* JS_SEQUENCE_EXPRESSION */: | |
genSequenceExpression(node, context); | |
break; | |
case 19 /* JS_CONDITIONAL_EXPRESSION */: | |
genConditionalExpression(node, context); | |
break; | |
case 20 /* JS_CACHE_EXPRESSION */: | |
genCacheExpression(node, context); | |
break; | |
/* istanbul ignore next */ | |
default: | |
{ | |
assert(false, `unhandled codegen node type: ${node.type}`); | |
// make sure we exhaust all possible types | |
const exhaustiveCheck = node; | |
return exhaustiveCheck; | |
} | |
} | |
} | |
function genText(node, context) { | |
context.push(JSON.stringify(node.content), node); | |
} | |
function genExpression(node, context) { | |
const { content, isStatic } = node; | |
context.push(isStatic ? JSON.stringify(content) : content, node); | |
} | |
function genInterpolation(node, context) { | |
const { push, helper } = context; | |
push(`${helper(TO_STRING)}(`); | |
genNode(node.content, context); | |
push(`)`); | |
} | |
function genCompoundExpression(node, context) { | |
for (let i = 0; i < node.children.length; i++) { | |
const child = node.children[i]; | |
if (isString(child)) { | |
context.push(child); | |
} | |
else { | |
genNode(child, context); | |
} | |
} | |
} | |
function genExpressionAsPropertyKey(node, context) { | |
const { push } = context; | |
if (node.type === 8 /* COMPOUND_EXPRESSION */) { | |
push(`[`); | |
genCompoundExpression(node, context); | |
push(`]`); | |
} | |
else if (node.isStatic) { | |
// only quote keys if necessary | |
const text = isSimpleIdentifier(node.content) | |
? node.content | |
: JSON.stringify(node.content); | |
push(text, node); | |
} | |
else { | |
push(`[${node.content}]`, node); | |
} | |
} | |
function genComment(node, context) { | |
{ | |
const { push, helper } = context; | |
push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node); | |
} | |
} | |
// JavaScript | |
function genCallExpression(node, context) { | |
const callee = isString(node.callee) | |
? node.callee | |
: context.helper(node.callee); | |
context.push(callee + `(`, node, true); | |
genNodeList(node.arguments, context); | |
context.push(`)`); | |
} | |
function genObjectExpression(node, context) { | |
const { push, indent, deindent, newline, resetMapping } = context; | |
const { properties } = node; | |
if (!properties.length) { | |
push(`{}`, node); | |
return; | |
} | |
const multilines = properties.length > 1 || | |
( | |
properties.some(p => p.value.type !== 4 /* SIMPLE_EXPRESSION */)); | |
push(multilines ? `{` : `{ `); | |
multilines && indent(); | |
for (let i = 0; i < properties.length; i++) { | |
const { key, value, loc } = properties[i]; | |
resetMapping(loc); // reset source mapping for every property. | |
// key | |
genExpressionAsPropertyKey(key, context); | |
push(`: `); | |
// value | |
genNode(value, context); | |
if (i < properties.length - 1) { | |
// will only reach this if it's multilines | |
push(`,`); | |
newline(); | |
} | |
} | |
multilines && deindent(); | |
const lastChar = context.code[context.code.length - 1]; | |
push(multilines || /[\])}]/.test(lastChar) ? `}` : ` }`); | |
} | |
function genArrayExpression(node, context) { | |
genNodeListAsArray(node.elements, context); | |
} | |
function genFunctionExpression(node, context) { | |
const { push, indent, deindent } = context; | |
const { params, returns, newline } = node; | |
push(`(`, node); | |
if (isArray(params)) { | |
genNodeList(params, context); | |
} | |
else if (params) { | |
genNode(params, context); | |
} | |
push(`) => `); | |
if (newline) { | |
push(`{`); | |
indent(); | |
push(`return `); | |
} | |
if (isArray(returns)) { | |
genNodeListAsArray(returns, context); | |
} | |
else { | |
genNode(returns, context); | |
} | |
if (newline) { | |
deindent(); | |
push(`}`); | |
} | |
} | |
function genConditionalExpression(node, context) { | |
const { test, consequent, alternate } = node; | |
const { push, indent, deindent, newline } = context; | |
if (test.type === 4 /* SIMPLE_EXPRESSION */) { | |
const needsParens = !isSimpleIdentifier(test.content); | |
needsParens && push(`(`); | |
genExpression(test, context); | |
needsParens && push(`)`); | |
} | |
else { | |
push(`(`); | |
genCompoundExpression(test, context); | |
push(`)`); | |
} | |
indent(); | |
context.indentLevel++; | |
push(`? `); | |
genNode(consequent, context); | |
context.indentLevel--; | |
newline(); | |
push(`: `); | |
const isNested = alternate.type === 19 /* JS_CONDITIONAL_EXPRESSION */; | |
if (!isNested) { | |
context.indentLevel++; | |
} | |
genNode(alternate, context); | |
if (!isNested) { | |
context.indentLevel--; | |
} | |
deindent(true /* without newline */); | |
} | |
function genSequenceExpression(node, context) { | |
context.push(`(`); | |
genNodeList(node.expressions, context); | |
context.push(`)`); | |
} | |
function genCacheExpression(node, context) { | |
const { push, helper, indent, deindent, newline } = context; | |
push(`_cache[${node.index}] || (`); | |
if (node.isVNode) { | |
indent(); | |
push(`${helper(SET_BLOCK_TRACKING)}(-1),`); | |
newline(); | |
} | |
push(`_cache[${node.index}] = `); | |
genNode(node.value, context); | |
if (node.isVNode) { | |
push(`,`); | |
newline(); | |
push(`${helper(SET_BLOCK_TRACKING)}(1),`); | |
newline(); | |
push(`_cache[${node.index}]`); | |
deindent(); | |
} | |
push(`)`); | |
} | |
const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/, (node, dir, context) => { | |
if (dir.name !== 'else' && | |
(!dir.exp || !dir.exp.content.trim())) { | |
const loc = dir.exp ? dir.exp.loc : node.loc; | |
context.onError(createCompilerError(35 /* X_V_IF_NO_EXPRESSION */, dir.loc)); | |
dir.exp = createSimpleExpression(`true`, false, loc); | |
} | |
if (dir.name === 'if') { | |
const branch = createIfBranch(node, dir); | |
const codegenNode = createSequenceExpression([ | |
createCallExpression(context.helper(OPEN_BLOCK)) | |
]); | |
context.replaceNode({ | |
type: 9 /* IF */, | |
loc: node.loc, | |
branches: [branch], | |
codegenNode | |
}); | |
// Exit callback. Complete the codegenNode when all children have been | |
// transformed. | |
return () => { | |
codegenNode.expressions.push(createCodegenNodeForBranch(branch, 0, context)); | |
}; | |
} | |
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 === 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]; | |
} | |
sibling.branches.push(branch); | |
// since the branch was removed, it will not be traversed. | |
// make sure to traverse here. | |
traverseChildren(branch, context); | |
// make sure to reset currentNode after traversal to indicate this | |
// node has been removed. | |
context.currentNode = null; | |
// attach this branch's codegen node to the v-if root. | |
let parentCondition = sibling.codegenNode | |
.expressions[1]; | |
while (true) { | |
if (parentCondition.alternate.type === | |
19 /* JS_CONDITIONAL_EXPRESSION */) { | |
parentCondition = parentCondition.alternate; | |
} | |
else { | |
parentCondition.alternate = createCodegenNodeForBranch(branch, sibling.branches.length - 1, context); | |
break; | |
} | |
} | |
} | |
else { | |
context.onError(createCompilerError(36 /* 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 */ ? node.children : [node] | |
}; | |
} | |
function createCodegenNodeForBranch(branch, index, context) { | |
if (branch.condition) { | |
return createConditionalExpression(branch.condition, createChildrenCodegenNode(branch, index, 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, index, context); | |
} | |
} | |
function createChildrenCodegenNode(branch, index, context) { | |
const { helper } = context; | |
const keyProperty = createObjectProperty(`key`, createSimpleExpression(index + '', false)); | |
const { children } = branch; | |
const child = children[0]; | |
const needFragmentWrapper = children.length !== 1 || child.type !== 1 /* ELEMENT */; | |
if (needFragmentWrapper) { | |
const blockArgs = [ | |
helper(FRAGMENT), | |
createObjectExpression([keyProperty]), | |
children | |
]; | |
if (children.length === 1 && child.type === 11 /* FOR */) { | |
// optimize away nested fragments when child is a ForNode | |
const forBlockArgs = child.codegenNode.expressions[1].arguments; | |
// directly use the for block's children and patchFlag | |
blockArgs[2] = forBlockArgs[2]; | |
blockArgs[3] = forBlockArgs[3]; | |
} | |
return createCallExpression(helper(CREATE_BLOCK), blockArgs); | |
} | |
else { | |
const childCodegen = child.codegenNode; | |
let vnodeCall = childCodegen; | |
// Element with custom directives. Locate the actual createVNode() call. | |
if (vnodeCall.callee === WITH_DIRECTIVES) { | |
vnodeCall = vnodeCall.arguments[0]; | |
} | |
// Change createVNode to createBlock. | |
if (vnodeCall.callee === CREATE_VNODE) { | |
vnodeCall.callee = helper(CREATE_BLOCK); | |
} | |
// inject branch key | |
injectProp(vnodeCall, keyProperty, context); | |
return childCodegen; | |
} | |
} | |
const transformFor = createStructuralDirectiveTransform('for', (node, dir, context) => { | |
if (!dir.exp) { | |
context.onError(createCompilerError(37 /* X_V_FOR_NO_EXPRESSION */, dir.loc)); | |
return; | |
} | |
const parseResult = parseForExpression( | |
// can only be simple expression because vFor transform is applied | |
// before expression transform. | |
dir.exp); | |
if (!parseResult) { | |
context.onError(createCompilerError(38 /* X_V_FOR_MALFORMED_EXPRESSION */, dir.loc)); | |
return; | |
} | |
const { helper, addIdentifiers, removeIdentifiers, scopes } = context; | |
const { source, value, key, index } = parseResult; | |
// 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), [source]); | |
const keyProp = findProp(node, `key`); | |
const fragmentFlag = keyProp | |
? 64 /* KEYED_FRAGMENT */ | |
: 128 /* UNKEYED_FRAGMENT */; | |
const codegenNode = createSequenceExpression([ | |
// fragment blocks disable tracking since they always diff their children | |
createCallExpression(helper(OPEN_BLOCK), [`false`]), | |
createCallExpression(helper(CREATE_BLOCK), [ | |
helper(FRAGMENT), | |
`null`, | |
renderExp, | |
fragmentFlag + ( ` /* ${PatchFlagNames[fragmentFlag]} */` ) | |
]) | |
]); | |
context.replaceNode({ | |
type: 11 /* FOR */, | |
loc: dir.loc, | |
source, | |
valueAlias: value, | |
keyAlias: key, | |
objectIndexAlias: index, | |
children: node.tagType === 3 /* TEMPLATE */ ? node.children : [node], | |
codegenNode | |
}); | |
// bookkeeping | |
scopes.vFor++; | |
return () => { | |
scopes.vFor--; | |
// finish the codegen now that all children have been traversed | |
let childBlock; | |
const isTemplate = isTemplateNode(node); | |
const slotOutlet = isSlotOutlet(node) | |
? node | |
: isTemplate && | |
node.children.length === 1 && | |
isSlotOutlet(node.children[0]) | |
? node.children[0] // api-extractor somehow fails to infer this | |
: null; | |
const keyProperty = keyProp | |
? createObjectProperty(`key`, keyProp.type === 6 /* ATTRIBUTE */ | |
? createSimpleExpression(keyProp.value.content, true) | |
: keyProp.exp) | |
: null; | |
if (slotOutlet) { | |
// <slot v-for="..."> or <template v-for="..."><slot/></template> | |
childBlock = slotOutlet.codegenNode; | |
if (isTemplate && keyProperty) { | |
// <template v-for="..." :key="..."><slot/></template> | |
// we need to inject the key to the renderSlot() call. | |
// the props for renderSlot is passed as the 3rd argument. | |
injectProp(childBlock, keyProperty, context); | |
} | |
} | |
else if (isTemplate) { | |
// <template v-for="..."> | |
// should generate a fragment block for each loop | |
childBlock = createBlockExpression(createCallExpression(helper(CREATE_BLOCK), [ | |
helper(FRAGMENT), | |
keyProperty ? createObjectExpression([keyProperty]) : `null`, | |
node.children | |
]), context); | |
} | |
else { | |
// Normal element v-for. Directly use the child's codegenNode | |
// arguments, but replace createVNode() with createBlock() | |
let codegenNode = node.codegenNode; | |
if (codegenNode.callee === WITH_DIRECTIVES) { | |
codegenNode.arguments[0].callee = helper(CREATE_BLOCK); | |
} | |
else { | |
codegenNode.callee = helper(CREATE_BLOCK); | |
} | |
childBlock = createBlockExpression(codegenNode, context); | |
} | |
renderExp.arguments.push(createFunctionExpression(createForLoopParams(parseResult), childBlock, true /* force newline */)); | |
}; | |
}); | |
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/; | |
// This regex doesn't cover the case if key or index aliases have destructuring, | |
// but those do not make sense in the first place, so this works in practice. | |
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/; | |
const stripParensRE = /^\(|\)$/g; | |
function parseForExpression(input, context) { | |
const loc = input.loc; | |
const exp = input.content; | |
const inMatch = exp.match(forAliasRE); | |
if (!inMatch) | |
return; | |
const [, LHS, RHS] = inMatch; | |
const result = { | |
source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)), | |
value: undefined, | |
key: undefined, | |
index: undefined | |
}; | |
let valueContent = LHS.trim() | |
.replace(stripParensRE, '') | |
.trim(); | |
const trimmedOffset = LHS.indexOf(valueContent); | |
const iteratorMatch = valueContent.match(forIteratorRE); | |
if (iteratorMatch) { | |
valueContent = valueContent.replace(forIteratorRE, '').trim(); | |
const keyContent = iteratorMatch[1].trim(); | |
let keyOffset; | |
if (keyContent) { | |
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length); | |
result.key = createAliasExpression(loc, keyContent, keyOffset); | |
} | |
if (iteratorMatch[2]) { | |
const indexContent = iteratorMatch[2].trim(); | |
if (indexContent) { | |
result.index = createAliasExpression(loc, indexContent, exp.indexOf(indexContent, result.key | |
? keyOffset + keyContent.length | |
: trimmedOffset + valueContent.length)); | |
} | |
} | |
} | |
if (valueContent) { | |
result.value = createAliasExpression(loc, valueContent, trimmedOffset); | |
} | |
return result; | |
} | |
function createAliasExpression(range, content, offset) { | |
return createSimpleExpression(content, false, getInnerRange(range, offset, content.length)); | |
} | |
function createForLoopParams({ value, key, index }) { | |
const params = []; | |
if (value) { | |
params.push(value); | |
} | |
if (key) { | |
if (!value) { | |
params.push(createSimpleExpression(`_`, false)); | |
} | |
params.push(key); | |
} | |
if (index) { | |
if (!key) { | |
if (!value) { | |
params.push(createSimpleExpression(`_`, false)); | |
} | |
params.push(createSimpleExpression(`__`, false)); | |
} | |
params.push(index); | |
} | |
return params; | |
} | |
const isStaticExp = (p) => p.type === 4 /* SIMPLE_EXPRESSION */ && p.isStatic; | |
const defaultFallback = createSimpleExpression(`undefined`, false); | |
// A NodeTransform that: | |
// 1. Tracks scope identifiers for scoped slots so that they don't get prefixed | |
// by transformExpression. This is only applied in non-browser builds with | |
// { prefixIdentifiers: true }. | |
// 2. Track v-slot depths so that we know a slot is inside another slot. | |
// Note the exit callback is executed before buildSlots() on the same node, | |
// so only nested slots see positive numbers. | |
const trackSlotScopes = (node, context) => { | |
if (node.type === 1 /* ELEMENT */ && | |
(node.tagType === 1 /* COMPONENT */ || | |
node.tagType === 3 /* TEMPLATE */)) { | |
// We are only checking non-empty v-slot here | |
// since we only care about slots that introduce scope variables. | |
const vSlot = findDir(node, 'slot'); | |
if (vSlot) { | |
const slotProps = vSlot.exp; | |
context.scopes.vSlot++; | |
return () => { | |
context.scopes.vSlot--; | |
}; | |
} | |
} | |
}; | |
// Instead of being a DirectiveTransform, v-slot processing is called during | |
// transformElement to build the slots object for a component. | |
function buildSlots(node, context) { | |
const { children, loc } = node; | |
const slotsProperties = []; | |
const dynamicSlots = []; | |
// If the slot is inside a v-for or another v-slot, force it to be dynamic | |
// since it likely uses a scope variable. | |
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0; | |
// 1. Check for default slot with slotProps on component itself. | |
// <Comp v-slot="{ prop }"/> | |
const explicitDefaultSlot = findDir(node, 'slot', true); | |
if (explicitDefaultSlot) { | |
const { arg, exp, loc } = explicitDefaultSlot; | |
if (arg) { | |
context.onError(createCompilerError(42 /* X_V_SLOT_NAMED_SLOT_ON_COMPONENT */, loc)); | |
} | |
slotsProperties.push(buildDefaultSlot(exp, children, loc)); | |
} | |
// 2. Iterate through children and check for template slots | |
// <template v-slot:foo="{ prop }"> | |
let hasTemplateSlots = false; | |
let extraneousChild = undefined; | |
const seenSlotNames = new Set(); | |
for (let i = 0; i < children.length; i++) { | |
const slotElement = children[i]; | |
let slotDir; | |
if (!isTemplateNode(slotElement) || | |
!(slotDir = findDir(slotElement, 'slot', true))) { | |
// not a <template v-slot>, skip. | |
if (slotElement.type !== 3 /* COMMENT */ && !extraneousChild) { | |
extraneousChild = slotElement; | |
} | |
continue; | |
} | |
if (explicitDefaultSlot) { | |
// already has on-component default slot - this is incorrect usage. | |
context.onError(createCompilerError(43 /* X_V_SLOT_MIXED_SLOT_USAGE */, slotDir.loc)); | |
break; | |
} | |
hasTemplateSlots = true; | |
const { children: slotChildren, loc: slotLoc } = slotElement; | |
const { arg: slotName = createSimpleExpression(`default`, true), exp: slotProps, loc: dirLoc } = slotDir; | |
// check if name is dynamic. | |
let staticSlotName; | |
if (isStaticExp(slotName)) { | |
staticSlotName = slotName ? slotName.content : `default`; | |
} | |
else { | |
hasDynamicSlots = true; | |
} | |
const slotFunction = createFunctionExpression(slotProps, slotChildren, false, slotChildren.length ? slotChildren[0].loc : slotLoc); | |
// check if this slot is conditional (v-if/v-for) | |
let vIf; | |
let vElse; | |
let vFor; | |
if ((vIf = findDir(slotElement, 'if'))) { | |
hasDynamicSlots = true; | |
dynamicSlots.push(createConditionalExpression(vIf.exp, buildDynamicSlot(slotName, slotFunction), defaultFallback)); | |
} | |
else if ((vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))) { | |
// find adjacent v-if | |
let j = i; | |
let prev; | |
while (j--) { | |
prev = children[j]; | |
if (prev.type !== 3 /* COMMENT */) { | |
break; | |
} | |
} | |
if (prev && isTemplateNode(prev) && findDir(prev, 'if')) { | |
// remove node | |
children.splice(i, 1); | |
i--; | |
assert(dynamicSlots.length > 0); | |
// attach this slot to previous conditional | |
let conditional = dynamicSlots[dynamicSlots.length - 1]; | |
while (conditional.alternate.type === 19 /* JS_CONDITIONAL_EXPRESSION */) { | |
conditional = conditional.alternate; | |
} | |
conditional.alternate = vElse.exp | |
? createConditionalExpression(vElse.exp, buildDynamicSlot(slotName, slotFunction), defaultFallback) | |
: buildDynamicSlot(slotName, slotFunction); | |
} | |
else { | |
context.onError(createCompilerError(36 /* X_V_ELSE_NO_ADJACENT_IF */, vElse.loc)); | |
} | |
} | |
else if ((vFor = findDir(slotElement, 'for'))) { | |
hasDynamicSlots = true; | |
const parseResult = vFor.parseResult || | |
parseForExpression(vFor.exp); | |
if (parseResult) { | |
// Render the dynamic slots as an array and add it to the createSlot() | |
// args. The runtime knows how to handle it appropriately. | |
dynamicSlots.push(createCallExpression(context.helper(RENDER_LIST), [ | |
parseResult.source, | |
createFunctionExpression(createForLoopParams(parseResult), buildDynamicSlot(slotName, slotFunction), true) | |
])); | |
} | |
else { | |
context.onError(createCompilerError(38 /* X_V_FOR_MALFORMED_EXPRESSION */, vFor.loc)); | |
} | |
} | |
else { | |
// check duplicate static names | |
if (staticSlotName) { | |
if (seenSlotNames.has(staticSlotName)) { | |
context.onError(createCompilerError(44 /* X_V_SLOT_DUPLICATE_SLOT_NAMES */, dirLoc)); | |
continue; | |
} | |
seenSlotNames.add(staticSlotName); | |
} | |
slotsProperties.push(createObjectProperty(slotName, slotFunction)); | |
} | |
} | |
if (hasTemplateSlots && extraneousChild) { | |
context.onError(createCompilerError(45 /* X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN */, extraneousChild.loc)); | |
} | |
if (!explicitDefaultSlot && !hasTemplateSlots) { | |
// implicit default slot. | |
slotsProperties.push(buildDefaultSlot(undefined, children, loc)); | |
} | |
let slots = createObjectExpression(slotsProperties.concat(createObjectProperty(`_compiled`, createSimpleExpression(`true`, false))), loc); | |
if (dynamicSlots.length) { | |
slots = createCallExpression(context.helper(CREATE_SLOTS), [ | |
slots, | |
createArrayExpression(dynamicSlots) | |
]); | |
} | |
return { | |
slots, | |
hasDynamicSlots | |
}; | |
} | |
function buildDefaultSlot(slotProps, children, loc) { | |
return createObjectProperty(`default`, createFunctionExpression(slotProps, children, false, children.length ? children[0].loc : loc)); | |
} | |
function buildDynamicSlot(name, fn) { | |
return createObjectExpression([ | |
createObjectProperty(`name`, name), | |
createObjectProperty(`fn`, fn) | |
]); | |
} | |
// some directive transforms (e.g. v-model) may return a symbol for runtime | |
// import, which should be used instead of a resolveDirective call. | |
const directiveImportMap = new WeakMap(); | |
// generate a JavaScript AST for this element's codegen | |
const transformElement = (node, context) => { | |
if (node.type !== 1 /* ELEMENT */ || | |
// handled by transformSlotOutlet | |
node.tagType === 2 /* SLOT */ || | |
// <template v-if/v-for> should have already been replaced | |
// <templte v-slot> is handled by buildSlots | |
(node.tagType === 3 /* TEMPLATE */ && node.props.some(isVSlot))) { | |
return; | |
} | |
// perform the work on exit, after all child expressions have been | |
// processed and merged. | |
return () => { | |
const { tag, tagType, props } = node; | |
const isPortal = tag === 'portal' || tag === 'Portal'; | |
const isSuspense = tag === 'suspense' || tag === 'Suspense'; | |
const isKeepAlive = tag === 'keep-alive' || tag === 'KeepAlive'; | |
const isComponent = tagType === 1 /* COMPONENT */; | |
let hasProps = props.length > 0; | |
let patchFlag = 0; | |
let runtimeDirectives; | |
let dynamicPropNames; | |
let dynamicComponent; | |
// handle dynamic component | |
const isProp = findProp(node, 'is'); | |
if (tag === 'component') { | |
if (isProp) { | |
// static <component is="foo" /> | |
if (isProp.type === 6 /* ATTRIBUTE */) { | |
const tag = isProp.value && isProp.value.content; | |
if (tag) { | |
context.helper(RESOLVE_COMPONENT); | |
context.components.add(tag); | |
dynamicComponent = toValidAssetId(tag, `component`); | |
} | |
} | |
// dynamic <component :is="asdf" /> | |
else if (isProp.exp) { | |
dynamicComponent = createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [isProp.exp]); | |
} | |
} | |
} | |
let nodeType; | |
if (dynamicComponent) { | |
nodeType = dynamicComponent; | |
} | |
else if (isPortal) { | |
nodeType = context.helper(PORTAL); | |
} | |
else if (isSuspense) { | |
nodeType = context.helper(SUSPENSE); | |
} | |
else if (isKeepAlive) { | |
nodeType = context.helper(KEEP_ALIVE); | |
} | |
else if (isComponent) { | |
// user component w/ resolve | |
context.helper(RESOLVE_COMPONENT); | |
context.components.add(tag); | |
nodeType = toValidAssetId(tag, `component`); | |
} | |
else { | |
// plain element | |
nodeType = `"${node.tag}"`; | |
} | |
const args = [nodeType]; | |
// props | |
if (hasProps) { | |
const propsBuildResult = buildProps(node, context, | |
// skip reserved "is" prop <component is> | |
node.props.filter(p => p !== isProp)); | |
patchFlag = propsBuildResult.patchFlag; | |
dynamicPropNames = propsBuildResult.dynamicPropNames; | |
runtimeDirectives = propsBuildResult.directives; | |
if (!propsBuildResult.props) { | |
hasProps = false; | |
} | |
else { | |
args.push(propsBuildResult.props); | |
} | |
} | |
// children | |
const hasChildren = node.children.length > 0; | |
if (hasChildren) { | |
if (!hasProps) { | |
args.push(`null`); | |
} | |
// Portal should have normal children instead of slots | |
if (isComponent && !isPortal) { | |
const { slots, hasDynamicSlots } = buildSlots(node, context); | |
args.push(slots); | |
if (hasDynamicSlots) { | |
patchFlag |= 256 /* DYNAMIC_SLOTS */; | |
} | |
} | |
else if (node.children.length === 1) { | |
const child = node.children[0]; | |
const type = child.type; | |
// check for dynamic text children | |
const hasDynamicTextChild = type === 5 /* INTERPOLATION */ || | |
type === 8 /* COMPOUND_EXPRESSION */; | |
if (hasDynamicTextChild && !isStaticNode(child)) { | |
patchFlag |= 1 /* TEXT */; | |
} | |
// pass directly if the only child is a text node | |
// (plain / interpolation / expression) | |
if (hasDynamicTextChild || type === 2 /* TEXT */) { | |
args.push(child); | |
} | |
else { | |
args.push(node.children); | |
} | |
} | |
else { | |
args.push(node.children); | |
} | |
} | |
// patchFlag & dynamicPropNames | |
if (patchFlag !== 0) { | |
if (!hasChildren) { | |
if (!hasProps) { | |
args.push(`null`); | |
} | |
args.push(`null`); | |
} | |
{ | |
const flagNames = Object.keys(PatchFlagNames) | |
.map(Number) | |
.filter(n => n > 0 && patchFlag & n) | |
.map(n => PatchFlagNames[n]) | |
.join(`, `); | |
args.push(patchFlag + ` /* ${flagNames} */`); | |
} | |
if (dynamicPropNames && dynamicPropNames.length) { | |
args.push(`[${dynamicPropNames.map(n => JSON.stringify(n)).join(`, `)}]`); | |
} | |
} | |
const { loc } = node; | |
const vnode = createCallExpression(context.helper(CREATE_VNODE), args, loc); | |
if (runtimeDirectives && runtimeDirectives.length) { | |
node.codegenNode = createCallExpression(context.helper(WITH_DIRECTIVES), [ | |
vnode, | |
createArrayExpression(runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)), loc) | |
], loc); | |
} | |
else { | |
node.codegenNode = vnode; | |
} | |
}; | |
}; | |
function buildProps(node, context, props = node.props) { | |
const elementLoc = node.loc; | |
const isComponent = node.tagType === 1 /* COMPONENT */; | |
let properties = []; | |
const mergeArgs = []; | |
const runtimeDirectives = []; | |
// patchFlag analysis | |
let patchFlag = 0; | |
let hasRef = false; | |
let hasClassBinding = false; | |
let hasStyleBinding = false; | |
let hasDynamicKeys = false; | |
const dynamicPropNames = []; | |
const analyzePatchFlag = ({ key, value }) => { | |
if (key.type === 4 /* SIMPLE_EXPRESSION */ && key.isStatic) { | |
if (value.type === 20 /* JS_CACHE_EXPRESSION */ || | |
((value.type === 4 /* SIMPLE_EXPRESSION */ || | |
value.type === 8 /* COMPOUND_EXPRESSION */) && | |
isStaticNode(value))) { | |
return; | |
} | |
const name = key.content; | |
if (name === 'ref') { | |
hasRef = true; | |
} | |
else if (name === 'class') { | |
hasClassBinding = true; | |
} | |
else if (name === 'style') { | |
hasStyleBinding = true; | |
} | |
else if (name !== 'key') { | |
dynamicPropNames.push(name); | |
} | |
} | |
else { | |
hasDynamicKeys = true; | |
} | |
}; | |
for (let i = 0; i < props.length; i++) { | |
// static attribute | |
const prop = props[i]; | |
if (prop.type === 6 /* ATTRIBUTE */) { | |
const { loc, name, value } = prop; | |
if (name === 'ref') { | |
hasRef = true; | |
} | |
properties.push(createObjectProperty(createSimpleExpression(name, true, getInnerRange(loc, 0, name.length)), createSimpleExpression(value ? value.content : '', true, value ? value.loc : loc))); | |
} | |
else { | |
// directives | |
const { name, arg, exp, loc } = prop; | |
// skip v-slot - it is handled by its dedicated transform. | |
if (name === 'slot') { | |
if (!isComponent) { | |
context.onError(createCompilerError(46 /* X_V_SLOT_MISPLACED */, loc)); | |
} | |
continue; | |
} | |
// skip v-once - it is handled by its dedicated transform. | |
if (name === 'once') { | |
continue; | |
} | |
// special case for v-bind and v-on with no argument | |
const isBind = name === 'bind'; | |
const isOn = name === 'on'; | |
if (!arg && (isBind || isOn)) { | |
hasDynamicKeys = true; | |
if (exp) { | |
if (properties.length) { | |
mergeArgs.push(createObjectExpression(dedupeProperties(properties), elementLoc)); | |
properties = []; | |
} | |
if (isBind) { | |
mergeArgs.push(exp); | |
} | |
else { | |
// v-on="obj" -> toHandlers(obj) | |
mergeArgs.push({ | |
type: 13 /* JS_CALL_EXPRESSION */, | |
loc, | |
callee: context.helper(TO_HANDLERS), | |
arguments: [exp] | |
}); | |
} | |
} | |
else { | |
context.onError(createCompilerError(isBind | |
? 39 /* X_V_BIND_NO_EXPRESSION */ | |
: 40 /* X_V_ON_NO_EXPRESSION */, loc)); | |
} | |
continue; | |
} | |
const directiveTransform = context.directiveTransforms[name]; | |
if (directiveTransform) { | |
// has built-in directive transform. | |
const { props, needRuntime } = directiveTransform(prop, node, context); | |
props.forEach(analyzePatchFlag); | |
properties.push(...props); | |
if (needRuntime) { | |
runtimeDirectives.push(prop); | |
if (isSymbol(needRuntime)) { | |
directiveImportMap.set(prop, needRuntime); | |
} | |
} | |
} | |
else { | |
// no built-in transform, this is a user custom directive. | |
runtimeDirectives.push(prop); | |
} | |
} | |
} | |
let propsExpression = undefined; | |
// has v-bind="object" or v-on="object", wrap with mergeProps | |
if (mergeArgs.length) { | |
if (properties.length) { | |
mergeArgs.push(createObjectExpression(dedupeProperties(properties), elementLoc)); | |
} | |
if (mergeArgs.length > 1) { | |
propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc); | |
} | |
else { | |
// single v-bind with nothing else - no need for a mergeProps call | |
propsExpression = mergeArgs[0]; | |
} | |
} | |
else if (properties.length) { | |
propsExpression = createObjectExpression(dedupeProperties(properties), elementLoc); | |
} | |
// patchFlag analysis | |
if (hasDynamicKeys) { | |
patchFlag |= 16 /* FULL_PROPS */; | |
} | |
else { | |
if (hasClassBinding) { | |
patchFlag |= 2 /* CLASS */; | |
} | |
if (hasStyleBinding) { | |
patchFlag |= 4 /* STYLE */; | |
} | |
if (dynamicPropNames.length) { | |
patchFlag |= 8 /* PROPS */; | |
} | |
} | |
if (patchFlag === 0 && (hasRef || runtimeDirectives.length > 0)) { | |
patchFlag |= 32 /* NEED_PATCH */; | |
} | |
return { | |
props: propsExpression, | |
directives: runtimeDirectives, | |
patchFlag, | |
dynamicPropNames | |
}; | |
} | |
// Dedupe props in an object literal. | |
// Literal duplicated attributes would have been warned during the parse phase, | |
// however, it's possible to encounter duplicated `onXXX` handlers with different | |
// modifiers. We also need to merge static and dynamic class / style attributes. | |
// - onXXX handlers / style: merge into array | |
// - class: merge into single expression with concatenation | |
function dedupeProperties(properties) { | |
const knownProps = {}; | |
const deduped = []; | |
for (let i = 0; i < properties.length; i++) { | |
const prop = properties[i]; | |
// dynamic keys are always allowed | |
if (prop.key.type === 8 /* COMPOUND_EXPRESSION */ || !prop.key.isStatic) { | |
deduped.push(prop); | |
continue; | |
} | |
const name = prop.key.content; | |
const existing = knownProps[name]; | |
if (existing) { | |
if (name === 'style' || | |
name === 'class' || | |
name.startsWith('on') || | |
name.startsWith('vnode')) { | |
mergeAsArray(existing, prop); | |
} | |
// unexpected duplicate, should have emitted error during parse | |
} | |
else { | |
knownProps[name] = prop; | |
deduped.push(prop); | |
} | |
} | |
return deduped; | |
} | |
function mergeAsArray(existing, incoming) { | |
if (existing.value.type === 16 /* JS_ARRAY_EXPRESSION */) { | |
existing.value.elements.push(incoming.value); | |
} | |
else { | |
existing.value = createArrayExpression([existing.value, incoming.value], existing.loc); | |
} | |
} | |
function buildDirectiveArgs(dir, context) { | |
const dirArgs = []; | |
const runtime = directiveImportMap.get(dir); | |
if (runtime) { | |
context.helper(runtime); | |
dirArgs.push(context.helperString(runtime)); | |
} | |
else { | |
// inject statement for resolving directive | |
context.helper(RESOLVE_DIRECTIVE); | |
context.directives.add(dir.name); | |
dirArgs.push(toValidAssetId(dir.name, `directive`)); | |
} | |
const { loc } = dir; | |
if (dir.exp) | |
dirArgs.push(dir.exp); | |
if (dir.arg) { | |
if (!dir.exp) { | |
dirArgs.push(`void 0`); | |
} | |
dirArgs.push(dir.arg); | |
} | |
if (Object.keys(dir.modifiers).length) { | |
if (!dir.arg) { | |
if (!dir.exp) { | |
dirArgs.push(`void 0`); | |
} | |
dirArgs.push(`void 0`); | |
} | |
dirArgs.push(createObjectExpression(dir.modifiers.map(modifier => createObjectProperty(modifier, createSimpleExpression(`true`, false, loc))), loc)); | |
} | |
return createArrayExpression(dirArgs, dir.loc); | |
} | |
const transformSlotOutlet = (node, context) => { | |
if (isSlotOutlet(node)) { | |
const { props, children, loc } = node; | |
const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`; | |
let slotName = `"default"`; | |
// check for <slot name="xxx" OR :name="xxx" /> | |
let nameIndex = -1; | |
for (let i = 0; i < props.length; i++) { | |
const prop = props[i]; | |
if (prop.type === 6 /* ATTRIBUTE */) { | |
if (prop.name === `name` && prop.value) { | |
// static name="xxx" | |
slotName = JSON.stringify(prop.value.content); | |
nameIndex = i; | |
break; | |
} | |
} | |
else if (prop.name === `bind`) { | |
const { arg, exp } = prop; | |
if (arg && | |
exp && | |
arg.type === 4 /* SIMPLE_EXPRESSION */ && | |
arg.isStatic && | |
arg.content === `name`) { | |
// dynamic :name="xxx" | |
slotName = exp; | |
nameIndex = i; | |
break; | |
} | |
} | |
} | |
const slotArgs = [$slots, slotName]; | |
const propsWithoutName = nameIndex > -1 | |
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1)) | |
: props; | |
let hasProps = propsWithoutName.length > 0; | |
if (hasProps) { | |
const { props: propsExpression, directives } = buildProps(node, context, propsWithoutName); | |
if (directives.length) { | |
context.onError(createCompilerError(41 /* X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET */, directives[0].loc)); | |
} | |
if (propsExpression) { | |
slotArgs.push(propsExpression); | |
} | |
else { | |
hasProps = false; | |
} | |
} | |
if (children.length) { | |
if (!hasProps) { | |
slotArgs.push(`{}`); | |
} | |
slotArgs.push(children); | |
} | |
node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc); | |
} | |
}; | |
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/; | |
const transformOn = (dir, node, context, augmentor) => { | |
const { loc, modifiers, arg } = dir; | |
if (!dir.exp && !modifiers.length) { | |
context.onError(createCompilerError(40 /* X_V_ON_NO_EXPRESSION */, loc)); | |
} | |
let eventName; | |
if (arg.type === 4 /* SIMPLE_EXPRESSION */) { | |
if (arg.isStatic) { | |
eventName = createSimpleExpression(`on${capitalize(arg.content)}`, true, arg.loc); | |
} | |
else { | |
eventName = createCompoundExpression([`"on" + (`, arg, `)`]); | |
} | |
} | |
else { | |
// already a compound expression. | |
eventName = arg; | |
eventName.children.unshift(`"on" + (`); | |
eventName.children.push(`)`); | |
} | |
// handler processing | |
let exp = dir.exp; | |
let isCacheable = !exp; | |
if (exp) { | |
const isMemberExp = isMemberExpression(exp.content); | |
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content)); | |
if (isInlineStatement || (isCacheable && isMemberExp)) { | |
// wrap inline statement in a function expression | |
exp = createCompoundExpression([ | |
`$event => (`, | |
...(exp.type === 4 /* SIMPLE_EXPRESSION */ ? [exp] : exp.children), | |
`)` | |
]); | |
} | |
} | |
let ret = { | |
props: [ | |
createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`, false, loc)) | |
], | |
needRuntime: false | |
}; | |
// apply extended compiler augmentor | |
if (augmentor) { | |
ret = augmentor(ret); | |
} | |
if (isCacheable) { | |
// cache handlers so that it's always the same handler being passed down. | |
// this avoids unnecessary re-renders when users use inline handlers on | |
// components. | |
ret.props[0].value = context.cache(ret.props[0].value); | |
} | |
return ret; | |
}; | |
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting | |
// codegen for the entire props object. This transform here is only for v-bind | |
// *with* args. | |
const transformBind = (dir, node, context) => { | |
const { exp, modifiers, loc } = dir; | |
const arg = dir.arg; | |
if (!exp) { | |
context.onError(createCompilerError(39 /* X_V_BIND_NO_EXPRESSION */, loc)); | |
} | |
// .prop is no longer necessary due to new patch behavior | |
// .sync is replaced by v-model:arg | |
if (modifiers.includes('camel')) { | |
if (arg.type === 4 /* SIMPLE_EXPRESSION */) { | |
if (arg.isStatic) { | |
arg.content = camelize(arg.content); | |
} | |
else { | |
arg.content = `${context.helperString(CAMELIZE)}(${arg.content})`; | |
} | |
} | |
else { | |
arg.children.unshift(`${context.helperString(CAMELIZE)}(`); | |
arg.children.push(`)`); | |
} | |
} | |
return { | |
props: [ | |
createObjectProperty(arg, exp || createSimpleExpression('', true, loc)) | |
], | |
needRuntime: false | |
}; | |
}; | |
const isText$1 = (node) => node.type === 5 /* INTERPOLATION */ || node.type === 2 /* TEXT */; | |
// Merge adjacent text nodes and expressions into a single expression | |
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child. | |
const transformText = (node, context) => { | |
if (node.type === 0 /* ROOT */ || node.type === 1 /* ELEMENT */) { | |
// perform the transform on node exit so that all expressions have already | |
// been processed. | |
return () => { | |
const children = node.children; | |
let currentContainer = undefined; | |
let hasText = false; | |
for (let i = 0; i < children.length; i++) { | |
const child = children[i]; | |
if (isText$1(child)) { | |
hasText = true; | |
for (let j = i + 1; j < children.length; j++) { | |
const next = children[j]; | |
if (isText$1(next)) { | |
if (!currentContainer) { | |
currentContainer = children[i] = { | |
type: 8 /* COMPOUND_EXPRESSION */, | |
loc: child.loc, | |
children: [child] | |
}; | |
} | |
// merge adjacent text node into current | |
currentContainer.children.push(` + `, next); | |
children.splice(j, 1); | |
j--; | |
} | |
else { | |
currentContainer = undefined; | |
break; | |
} | |
} | |
} | |
} | |
if (hasText && children.length > 1) { | |
// when an element has mixed text/element children, convert text nodes | |
// into createTextVNode(text) calls. | |
for (let i = 0; i < children.length; i++) { | |
const child = children[i]; | |
if (isText$1(child) || child.type === 8 /* COMPOUND_EXPRESSION */) { | |
const callArgs = []; | |
// createTextVNode defaults to single whitespace, so if it is a | |
// single space the code could be an empty call to save bytes. | |
if (child.type !== 2 /* TEXT */ || child.content !== ' ') { | |
callArgs.push(child); | |
} | |
// mark dynamic text with flag so it gets patched inside a block | |
if (child.type !== 2 /* TEXT */) { | |
callArgs.push(`${1 /* TEXT */} /* ${PatchFlagNames[1 /* TEXT */]} */`); | |
} | |
children[i] = { | |
type: 12 /* TEXT_CALL */, | |
content: child, | |
loc: child.loc, | |
codegenNode: createCallExpression(context.helper(CREATE_TEXT), callArgs) | |
}; | |
} | |
} | |
} | |
}; | |
} | |
}; | |
const transformOnce = (node, context) => { | |
if (node.type === 1 /* ELEMENT */ && findDir(node, 'once', true)) { | |
context.helper(SET_BLOCK_TRACKING); | |
return () => { | |
if (node.codegenNode) { | |
node.codegenNode = context.cache(node.codegenNode, true /* isVNode */); | |
} | |
}; | |
} | |
}; | |
const transformModel = (dir, node, context) => { | |
const { exp, arg } = dir; | |
if (!exp) { | |
context.onError(createCompilerError(47 /* X_V_MODEL_NO_EXPRESSION */, dir.loc)); | |
return createTransformProps(); | |
} | |
const expString = exp.type === 4 /* SIMPLE_EXPRESSION */ ? exp.content : exp.loc.source; | |
if (!isMemberExpression(expString)) { | |
context.onError(createCompilerError(48 /* X_V_MODEL_MALFORMED_EXPRESSION */, exp.loc)); | |
return createTransformProps(); | |
} | |
const propName = arg ? arg : createSimpleExpression('modelValue', true); | |
const eventName = arg | |
? arg.type === 4 /* SIMPLE_EXPRESSION */ && arg.isStatic | |
? `onUpdate:${arg.content}` | |
: createCompoundExpression([ | |
'"onUpdate:" + ', | |
...(arg.type === 4 /* SIMPLE_EXPRESSION */ ? [arg] : arg.children) | |
]) | |
: `onUpdate:modelValue`; | |
const props = [ | |
// modelValue: foo | |
createObjectProperty(propName, dir.exp), | |
// "onUpdate:modelValue": $event => (foo = $event) | |
createObjectProperty(eventName, createCompoundExpression([ | |
`$event => (`, | |
...(exp.type === 4 /* SIMPLE_EXPRESSION */ ? [exp] : exp.children), | |
` = $event)` | |
])) | |
]; | |
// modelModifiers: { foo: true, "bar-baz": true } | |
if (dir.modifiers.length && node.tagType === 1 /* COMPONENT */) { | |
const modifiers = dir.modifiers | |
.map(m => (isSimpleIdentifier(m) ? m : JSON.stringify(m)) + `: true`) | |
.join(`, `); | |
const modifiersKey = arg | |
? arg.type === 4 /* SIMPLE_EXPRESSION */ && arg.isStatic | |
? `${arg.content}Modifiers` | |
: createCompoundExpression([ | |
...(arg.type === 4 /* SIMPLE_EXPRESSION */ | |
? [arg] | |
: arg.children), | |
' + "Modifiers"' | |
]) | |
: `modelModifiers`; | |
props.push(createObjectProperty(modifiersKey, createSimpleExpression(`{ ${modifiers} }`, false, dir.loc, true))); | |
} | |
return createTransformProps(props); | |
}; | |
function createTransformProps(props = []) { | |
return { props, needRuntime: false }; | |
} | |
// we name it `baseCompile` so that higher order compilers like @vue/compiler-dom | |
// can export `compile` while re-exporting everything else. | |
function baseCompile(template, options = {}) { | |
/* istanbul ignore if */ | |
{ | |
const onError = options.onError || defaultOnError; | |
if (options.prefixIdentifiers === true) { | |
onError(createCompilerError(51 /* X_PREFIX_ID_NOT_SUPPORTED */)); | |
} | |
else if (options.mode === 'module') { | |
onError(createCompilerError(52 /* X_MODULE_MODE_NOT_SUPPORTED */)); | |
} | |
} | |
const ast = isString(template) ? parse(template, options) : template; | |
const prefixIdentifiers = !true && | |
(options.prefixIdentifiers === true || options.mode === 'module'); | |
transform(ast, { | |
...options, | |
prefixIdentifiers, | |
nodeTransforms: [ | |
transformOnce, | |
transformIf, | |
transformFor, | |
...( []), | |
transformSlotOutlet, | |
transformElement, | |
trackSlotScopes, | |
transformText, | |
...(options.nodeTransforms || []) // user transforms | |
], | |
directiveTransforms: { | |
on: transformOn, | |
bind: transformBind, | |
model: transformModel, | |
...(options.directiveTransforms || {}) // user transforms | |
} | |
}); | |
return generate(ast, { | |
...options, | |
prefixIdentifiers | |
}); | |
} | |
const isRawTextContainer = /*#__PURE__*/ makeMap('style,iframe,script,noscript', true); | |
const parserOptionsMinimal = { | |
isVoidTag, | |
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag), | |
isPreTag: tag => tag === 'pre', | |
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher | |
getNamespace(tag, parent) { | |
let ns = parent ? parent.ns : 0 /* HTML */; | |
if (parent && ns === 2 /* MATH_ML */) { | |
if (parent.tag === 'annotation-xml') { | |
if (tag === 'svg') { | |
return 1 /* SVG */; | |
} | |
if (parent.props.some(a => a.type === 6 /* ATTRIBUTE */ && | |
a.name === 'encoding' && | |
a.value != null && | |
(a.value.content === 'text/html' || | |
a.value.content === 'application/xhtml+xml'))) { | |
ns = 0 /* HTML */; | |
} | |
} | |
else if (/^m(?:[ions]|text)$/.test(parent.tag) && | |
tag !== 'mglyph' && | |
tag !== 'malignmark') { | |
ns = 0 /* HTML */; | |
} | |
} | |
else if (parent && ns === 1 /* SVG */) { | |
if (parent.tag === 'foreignObject' || | |
parent.tag === 'desc' || | |
parent.tag === 'title') { | |
ns = 0 /* HTML */; | |
} | |
} | |
if (ns === 0 /* HTML */) { | |
if (tag === 'svg') { | |
return 1 /* SVG */; | |
} | |
if (tag === 'math') { | |
return 2 /* MATH_ML */; | |
} | |
} | |
return ns; | |
}, | |
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-html-fragments | |
getTextMode(tag, ns) { | |
if (ns === 0 /* HTML */) { | |
if (tag === 'textarea' || tag === 'title') { | |
return 1 /* RCDATA */; | |
} | |
if (isRawTextContainer(tag)) { | |
return 2 /* RAWTEXT */; | |
} | |
} | |
return 0 /* DATA */; | |
} | |
}; | |
// Parse inline CSS strings for static style attributes into an object. | |
// This is a NodeTransform since it works on the static `style` attribute and | |
// converts it into a dynamic equivalent: | |
// style="color: red" -> :style='{ "color": "red" }' | |
// It is then processed by `transformElement` and included in the generated | |
// props. | |
const transformStyle = (node, context) => { | |
if (node.type === 1 /* ELEMENT */) { | |
node.props.forEach((p, i) => { | |
if (p.type === 6 /* ATTRIBUTE */ && p.name === 'style' && p.value) { | |
// replace p with an expression node | |
const parsed = JSON.stringify(parseInlineCSS(p.value.content)); | |
const exp = context.hoist(createSimpleExpression(parsed, false, p.loc)); | |
node.props[i] = { | |
type: 7 /* DIRECTIVE */, | |
name: `bind`, | |
arg: createSimpleExpression(`style`, true, p.loc), | |
exp, | |
modifiers: [], | |
loc: p.loc | |
}; | |
} | |
}); | |
} | |
}; | |
const listDelimiterRE = /;(?![^(]*\))/g; | |
const propertyDelimiterRE = /:(.+)/; | |
function parseInlineCSS(cssText) { | |
const res = {}; | |
cssText.split(listDelimiterRE).forEach(function (item) { | |
if (item) { | |
const tmp = item.split(propertyDelimiterRE); | |
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()); | |
} | |
}); | |
return res; | |
} | |
const transformCloak = (node, context) => { | |
return { props: [], needRuntime: false }; | |
}; | |
function createDOMCompilerError(code, loc) { | |
return createCompilerError(code, loc, DOMErrorMessages ); | |
} | |
const DOMErrorMessages = { | |
[53 /* X_V_HTML_NO_EXPRESSION */]: `v-html is missing expression.`, | |
[54 /* X_V_HTML_WITH_CHILDREN */]: `v-html will override element children.`, | |
[55 /* X_V_TEXT_NO_EXPRESSION */]: `v-text is missing expression.`, | |
[56 /* X_V_TEXT_WITH_CHILDREN */]: `v-text will override element children.`, | |
[57 /* X_V_MODEL_ON_INVALID_ELEMENT */]: `v-model can only be used on <input>, <textarea> and <select> elements.`, | |
[58 /* X_V_MODEL_ARG_ON_ELEMENT */]: `v-model argument is not supported on plain elements.`, | |
[59 /* X_V_MODEL_ON_FILE_INPUT_ELEMENT */]: `v-model cannot used on file inputs since they are read-only. Use a v-on:change listener instead.` | |
}; | |
const transformVHtml = (dir, node, context) => { | |
const { exp, loc } = dir; | |
if (!exp) { | |
context.onError(createDOMCompilerError(53 /* X_V_HTML_NO_EXPRESSION */, loc)); | |
} | |
if (node.children.length) { | |
context.onError(createDOMCompilerError(54 /* X_V_HTML_WITH_CHILDREN */, loc)); | |
node.children.length = 0; | |
} | |
return { | |
props: [ | |
createObjectProperty(createSimpleExpression(`innerHTML`, true, loc), exp || createSimpleExpression('', true)) | |
], | |
needRuntime: false | |
}; | |
}; | |
const transformVText = (dir, node, context) => { | |
const { exp, loc } = dir; | |
if (!exp) { | |
context.onError(createDOMCompilerError(55 /* X_V_TEXT_NO_EXPRESSION */, loc)); | |
} | |
if (node.children.length) { | |
context.onError(createDOMCompilerError(56 /* X_V_TEXT_WITH_CHILDREN */, loc)); | |
node.children.length = 0; | |
} | |
return { | |
props: [ | |
createObjectProperty(createSimpleExpression(`textContent`, true, loc), exp || createSimpleExpression('', true)) | |
], | |
needRuntime: false | |
}; | |
}; | |
const V_MODEL_RADIO = Symbol( `vModelRadio` ); | |
const V_MODEL_CHECKBOX = Symbol( `vModelCheckbox` ); | |
const V_MODEL_TEXT = Symbol( `vModelText` ); | |
const V_MODEL_SELECT = Symbol( `vModelSelect` ); | |
const V_MODEL_DYNAMIC = Symbol( `vModelDynamic` ); | |
const V_ON_WITH_MODIFIERS = Symbol( `vOnModifiersGuard` ); | |
const V_ON_WITH_KEYS = Symbol( `vOnKeysGuard` ); | |
registerRuntimeHelpers({ | |
[V_MODEL_RADIO]: `vModelRadio`, | |
[V_MODEL_CHECKBOX]: `vModelCheckbox`, | |
[V_MODEL_TEXT]: `vModelText`, | |
[V_MODEL_SELECT]: `vModelSelect`, | |
[V_MODEL_DYNAMIC]: `vModelDynamic`, | |
[V_ON_WITH_MODIFIERS]: `withModifiers`, | |
[V_ON_WITH_KEYS]: `withKeys` | |
}); | |
const transformModel$1 = (dir, node, context) => { | |
const baseResult = transformModel(dir, node, context); | |
// base transform has errors | |
if (!baseResult.props.length) { | |
return baseResult; | |
} | |
const { tag, tagType } = node; | |
if (tagType === 0 /* ELEMENT */) { | |
if (dir.arg) { | |
context.onError(createDOMCompilerError(58 /* X_V_MODEL_ARG_ON_ELEMENT */, dir.arg.loc)); | |
} | |
if (tag === 'input' || tag === 'textarea' || tag === 'select') { | |
let directiveToUse = V_MODEL_TEXT; | |
let isInvalidType = false; | |
if (tag === 'input') { | |
const type = findProp(node, `type`); | |
if (type) { | |
if (type.type === 7 /* DIRECTIVE */) { | |
// :type="foo" | |
directiveToUse = V_MODEL_DYNAMIC; | |
} | |
else if (type.value) { | |
switch (type.value.content) { | |
case 'radio': | |
directiveToUse = V_MODEL_RADIO; | |
break; | |
case 'checkbox': | |
directiveToUse = V_MODEL_CHECKBOX; | |
break; | |
case 'file': | |
isInvalidType = true; | |
context.onError(createDOMCompilerError(59 /* X_V_MODEL_ON_FILE_INPUT_ELEMENT */, dir.loc)); | |
break; | |
} | |
} | |
} | |
} | |
else if (tag === 'select') { | |
directiveToUse = V_MODEL_SELECT; | |
} | |
// inject runtime directive | |
// by returning the helper symbol via needRuntime | |
// the import will replaced a resolveDirective call. | |
if (!isInvalidType) { | |
baseResult.needRuntime = context.helper(directiveToUse); | |
} | |
} | |
else { | |
context.onError(createDOMCompilerError(57 /* X_V_MODEL_ON_INVALID_ELEMENT */, dir.loc)); | |
} | |
} | |
return baseResult; | |
}; | |
const isEventOptionModifier = /*#__PURE__*/ makeMap(`passive,once,capture`); | |
const isNonKeyModifier = /*#__PURE__*/ makeMap( | |
// event propagation management | |
`stop,prevent,self,` + | |
// system modifiers + exact | |
`ctrl,shift,alt,meta,exact,` + | |
// mouse | |
`left,middle,right`); | |
const isKeyboardEvent = /*#__PURE__*/ makeMap(`onkeyup,onkeydown,onkeypress`, true); | |
const transformOn$1 = (dir, node, context) => { | |
return transformOn(dir, node, context, baseResult => { | |
const { modifiers } = dir; | |
if (!modifiers.length) | |
return baseResult; | |
let { key, value: handlerExp } = baseResult.props[0]; | |
// modifiers for addEventListener() options, e.g. .passive & .capture | |
const eventOptionModifiers = modifiers.filter(isEventOptionModifier); | |
// modifiers that needs runtime guards | |
const runtimeModifiers = modifiers.filter(m => !isEventOptionModifier(m)); | |
// built-in modifiers that are not keys | |
const nonKeyModifiers = runtimeModifiers.filter(isNonKeyModifier); | |
if (nonKeyModifiers.length) { | |
handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [ | |
handlerExp, | |
JSON.stringify(nonKeyModifiers) | |
]); | |
} | |
const keyModifiers = runtimeModifiers.filter(m => !isNonKeyModifier(m)); | |
if (keyModifiers.length && | |
// if event name is dynamic, always wrap with keys guard | |
(key.type === 8 /* COMPOUND_EXPRESSION */ || | |
!key.isStatic || | |
isKeyboardEvent(key.content))) { | |
handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [ | |
handlerExp, | |
JSON.stringify(keyModifiers) | |
]); | |
} | |
if (eventOptionModifiers.length) { | |
handlerExp = createObjectExpression([ | |
createObjectProperty('handler', handlerExp), | |
createObjectProperty('options', createObjectExpression(eventOptionModifiers.map(modifier => createObjectProperty(modifier, createSimpleExpression('true', false))))) | |
]); | |
} | |
return { | |
props: [createObjectProperty(key, handlerExp)], | |
needRuntime: false | |
}; | |
}); | |
}; | |
function compile(template, options = {}) { | |
return baseCompile(template, { | |
...options, | |
...( parserOptionsMinimal ), | |
nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])], | |
directiveTransforms: { | |
cloak: transformCloak, | |
html: transformVHtml, | |
text: transformVText, | |
model: transformModel$1, | |
on: transformOn$1, | |
...(options.directiveTransforms || {}) | |
} | |
}); | |
} | |
// global immutability lock | |
let LOCKED = true; | |
function lock() { | |
LOCKED = true; | |
} | |
function unlock() { | |
LOCKED = false; | |
} | |
const builtInSymbols = new Set(Object.getOwnPropertyNames(Symbol) | |
.map(key => Symbol[key]) | |
.filter(isSymbol)); | |
function createGetter(isReadonly, unwrap = true) { | |
return function get(target, key, receiver) { | |
let res = Reflect.get(target, key, receiver); | |
if (isSymbol(key) && builtInSymbols.has(key)) { | |
return res; | |
} | |
if (unwrap && isRef(res)) { | |
res = res.value; | |
} | |
else { | |
track(target, "get" /* GET */, key); | |
} | |
return isObject(res) | |
? isReadonly | |
? // need to lazy access readonly and reactive here to avoid | |
// circular dependency | |
readonly(res) | |
: reactive(res) | |
: res; | |
}; | |
} | |
function set(target, key, value, receiver) { | |
value = toRaw(value); | |
const oldValue = target[key]; | |
if (isRef(oldValue) && !isRef(value)) { | |
oldValue.value = value; | |
return true; | |
} | |
const hadKey = hasOwn(target, key); | |
const result = Reflect.set(target, key, value, receiver); | |
// don't trigger if target is something up in the prototype chain of original | |
if (target === toRaw(receiver)) { | |
/* istanbul ignore else */ | |
{ | |
const extraInfo = { oldValue, newValue: value }; | |
if (!hadKey) { | |
trigger(target, "add" /* ADD */, key, extraInfo); | |
} | |
else if (hasChanged(value, oldValue)) { | |
trigger(target, "set" /* SET */, key, extraInfo); | |
} | |
} | |
} | |
return result; | |
} | |
function deleteProperty(target, key) { | |
const hadKey = hasOwn(target, key); | |
const oldValue = target[key]; | |
const result = Reflect.deleteProperty(target, key); | |
if (result && hadKey) { | |
/* istanbul ignore else */ | |
{ | |
trigger(target, "delete" /* DELETE */, key, { oldValue }); | |
} | |
} | |
return result; | |
} | |
function has(target, key) { | |
const result = Reflect.has(target, key); | |
track(target, "has" /* HAS */, key); | |
return result; | |
} | |
function ownKeys(target) { | |
track(target, "iterate" /* ITERATE */); | |
return Reflect.ownKeys(target); | |
} | |
const mutableHandlers = { | |
get: createGetter(false), | |
set, | |
deleteProperty, | |
has, | |
ownKeys | |
}; | |
const readonlyHandlers = { | |
get: createGetter(true), | |
set(target, key, value, receiver) { | |
if (LOCKED) { | |
{ | |
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target); | |
} | |
return true; | |
} | |
else { | |
return set(target, key, value, receiver); | |
} | |
}, | |
deleteProperty(target, key) { | |
if (LOCKED) { | |
{ | |
console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target); | |
} | |
return true; | |
} | |
else { | |
return deleteProperty(target, key); | |
} | |
}, | |
has, | |
ownKeys | |
}; | |
// props handlers are special in the sense that it should not unwrap top-level | |
// refs (in order to allow refs to be explicitly passed down), but should | |
// retain the reactivity of the normal readonly object. | |
const readonlyPropsHandlers = { | |
...readonlyHandlers, | |
get: createGetter(true, false) | |
}; | |
const toReactive = (value) => isObject(value) ? reactive(value) : value; | |
const toReadonly = (value) => isObject(value) ? readonly(value) : value; | |
const getProto = (v) => Reflect.getPrototypeOf(v); | |
function get(target, key, wrap) { | |
target = toRaw(target); | |
key = toRaw(key); | |
track(target, "get" /* GET */, key); | |
return wrap(getProto(target).get.call(target, key)); | |
} | |
function has$1(key) { | |
const target = toRaw(this); | |
key = toRaw(key); | |
track(target, "has" /* HAS */, key); | |
return getProto(target).has.call(target, key); | |
} | |
function size(target) { | |
target = toRaw(target); | |
track(target, "iterate" /* ITERATE */); | |
return Reflect.get(getProto(target), 'size', target); | |
} | |
function add(value) { | |
value = toRaw(value); | |
const target = toRaw(this); | |
const proto = getProto(target); | |
const hadKey = proto.has.call(target, value); | |
const result = proto.add.call(target, value); | |
if (!hadKey) { | |
/* istanbul ignore else */ | |
{ | |
trigger(target, "add" /* ADD */, value, { newValue: value }); | |
} | |
} | |
return result; | |
} | |
function set$1(key, value) { | |
value = toRaw(value); | |
const target = toRaw(this); | |
const proto = getProto(target); | |
const hadKey = proto.has.call(target, key); | |
const oldValue = proto.get.call(target, key); | |
const result = proto.set.call(target, key, value); | |
/* istanbul ignore else */ | |
{ | |
const extraInfo = { oldValue, newValue: value }; | |
if (!hadKey) { | |
trigger(target, "add" /* ADD */, key, extraInfo); | |
} | |
else if (hasChanged(value, oldValue)) { | |
trigger(target, "set" /* SET */, key, extraInfo); | |
} | |
} | |
return result; | |
} | |
function deleteEntry(key) { | |
const target = toRaw(this); | |
const proto = getProto(target); | |
const hadKey = proto.has.call(target, key); | |
const oldValue = proto.get ? proto.get.call(target, key) : undefined; | |
// forward the operation before queueing reactions | |
const result = proto.delete.call(target, key); | |
if (hadKey) { | |
/* istanbul ignore else */ | |
{ | |
trigger(target, "delete" /* DELETE */, key, { oldValue }); | |
} | |
} | |
return result; | |
} | |
function clear() { | |
const target = toRaw(this); | |
const hadItems = target.size !== 0; | |
const oldTarget = target instanceof Map | |
? new Map(target) | |
: new Set(target) | |
; | |
// forward the operation before queueing reactions | |
const result = getProto(target).clear.call(target); | |
if (hadItems) { | |
/* istanbul ignore else */ | |
{ | |
trigger(target, "clear" /* CLEAR */, void 0, { oldTarget }); | |
} | |
} | |
return result; | |
} | |
function createForEach(isReadonly) { | |
return function forEach(callback, thisArg) { | |
const observed = this; | |
const target = toRaw(observed); | |
const wrap = isReadonly ? toReadonly : toReactive; | |
track(target, "iterate" /* ITERATE */); | |
// important: create sure the callback is | |
// 1. invoked with the reactive map as `this` and 3rd arg | |
// 2. the value received should be a corresponding reactive/readonly. | |
function wrappedCallback(value, key) { | |
return callback.call(observed, wrap(value), wrap(key), observed); | |
} | |
return getProto(target).forEach.call(target, wrappedCallback, thisArg); | |
}; | |
} | |
function createIterableMethod(method, isReadonly) { | |
return function (...args) { | |
const target = toRaw(this); | |
const isPair = method === 'entries' || | |
(method === Symbol.iterator && target instanceof Map); | |
const innerIterator = getProto(target)[method].apply(target, args); | |
const wrap = isReadonly ? toReadonly : toReactive; | |
track(target, "iterate" /* ITERATE */); | |
// return a wrapped iterator which returns observed versions of the | |
// values emitted from the real iterator | |
return { | |
// iterator protocol | |
next() { | |
const { value, done } = innerIterator.next(); | |
return done | |
? { value, done } | |
: { | |
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), | |
done | |
}; | |
}, | |
// iterable protocol | |
[Symbol.iterator]() { | |
return this; | |
} | |
}; | |
}; | |
} | |
function createReadonlyMethod(method, type) { | |
return function (...args) { | |
if (LOCKED) { | |
{ | |
const key = args[0] ? `on key "${args[0]}" ` : ``; | |
console.warn(`${capitalize(type)} operation ${key}failed: target is readonly.`, toRaw(this)); | |
} | |
return type === "delete" /* DELETE */ ? false : this; | |
} | |
else { | |
return method.apply(this, args); | |
} | |
}; | |
} | |
const mutableInstrumentations = { | |
get(key) { | |
return get(this, key, toReactive); | |
}, | |
get size() { | |
return size(this); | |
}, | |
has: has$1, | |
add, | |
set: set$1, | |
delete: deleteEntry, | |
clear, | |
forEach: createForEach(false) | |
}; | |
const readonlyInstrumentations = { | |
get(key) { | |
return get(this, key, toReadonly); | |
}, | |
get size() { | |
return size(this); | |
}, | |
has: has$1, | |
add: createReadonlyMethod(add, "add" /* ADD */), | |
set: createReadonlyMethod(set$1, "set" /* SET */), | |
delete: createReadonlyMethod(deleteEntry, "delete" /* DELETE */), | |
clear: createReadonlyMethod(clear, "clear" /* CLEAR */), | |
forEach: createForEach(true) | |
}; | |
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]; | |
iteratorMethods.forEach(method => { | |
mutableInstrumentations[method] = createIterableMethod(method, false); | |
readonlyInstrumentations[method] = createIterableMethod(method, true); | |
}); | |
function createInstrumentationGetter(instrumentations) { | |
return (target, key, receiver) => Reflect.get(hasOwn(instrumentations, key) && key in target | |
? instrumentations | |
: target, key, receiver); | |
} | |
const mutableCollectionHandlers = { | |
get: createInstrumentationGetter(mutableInstrumentations) | |
}; | |
const readonlyCollectionHandlers = { | |
get: createInstrumentationGetter(readonlyInstrumentations) | |
}; | |
const targetMap = new WeakMap(); | |
// WeakMaps that store {raw <-> observed} pairs. | |
const rawToReactive = new WeakMap(); | |
const reactiveToRaw = new WeakMap(); | |
const rawToReadonly = new WeakMap(); | |
const readonlyToRaw = new WeakMap(); | |
// WeakSets for values that are marked readonly or non-reactive during | |
// observable creation. | |
const readonlyValues = new WeakSet(); | |
const nonReactiveValues = new WeakSet(); | |
const collectionTypes = new Set([Set, Map, WeakMap, WeakSet]); | |
const isObservableType = /*#__PURE__*/ makeMap('Object,Array,Map,Set,WeakMap,WeakSet'); | |
const canObserve = (value) => { | |
return (!value._isVue && | |
!value._isVNode && | |
isObservableType(toRawType(value)) && | |
!nonReactiveValues.has(value)); | |
}; | |
function reactive(target) { | |
// if trying to observe a readonly proxy, return the readonly version. | |
if (readonlyToRaw.has(target)) { | |
return target; | |
} | |
// target is explicitly marked as readonly by user | |
if (readonlyValues.has(target)) { | |
return readonly(target); | |
} | |
return createReactiveObject(target, rawToReactive, reactiveToRaw, mutableHandlers, mutableCollectionHandlers); | |
} | |
function readonly(target) { | |
// value is a mutable observable, retrieve its original and return | |
// a readonly version. | |
if (reactiveToRaw.has(target)) { | |
target = reactiveToRaw.get(target); | |
} | |
return createReactiveObject(target, rawToReadonly, readonlyToRaw, readonlyHandlers, readonlyCollectionHandlers); | |
} | |
// @internal | |
// Return a readonly-copy of a props object, without unwrapping refs at the root | |
// level. This is intended to allow explicitly passing refs as props. | |
// Technically this should use different global cache from readonly(), but | |
// since it is only used on internal objects so it's not really necessary. | |
function readonlyProps(target) { | |
return createReactiveObject(target, rawToReadonly, readonlyToRaw, readonlyPropsHandlers, readonlyCollectionHandlers); | |
} | |
function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) { | |
if (!isObject(target)) { | |
{ | |
console.warn(`value cannot be made reactive: ${String(target)}`); | |
} | |
return target; | |
} | |
// target already has corresponding Proxy | |
let observed = toProxy.get(target); | |
if (observed !== void 0) { | |
return observed; | |
} | |
// target is already a Proxy | |
if (toRaw.has(target)) { | |
return target; | |
} | |
// only a whitelist of value types can be observed. | |
if (!canObserve(target)) { | |
return target; | |
} | |
const handlers = collectionTypes.has(target.constructor) | |
? collectionHandlers | |
: baseHandlers; | |
observed = new Proxy(target, handlers); | |
toProxy.set(target, observed); | |
toRaw.set(observed, target); | |
if (!targetMap.has(target)) { | |
targetMap.set(target, new Map()); | |
} | |
return observed; | |
} | |
function isReactive(value) { | |
return reactiveToRaw.has(value) || readonlyToRaw.has(value); | |
} | |
function isReadonly(value) { | |
return readonlyToRaw.has(value); | |
} | |
function toRaw(observed) { | |
return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed; | |
} | |
function markReadonly(value) { | |
readonlyValues.add(value); | |
return value; | |
} | |
function markNonReactive(value) { | |
nonReactiveValues.add(value); | |
return value; | |
} | |
const effectStack = []; | |
const ITERATE_KEY = Symbol('iterate'); | |
function isEffect(fn) { | |
return fn != null && fn._isEffect === true; | |
} | |
function effect(fn, options = EMPTY_OBJ) { | |
if (isEffect(fn)) { | |
fn = fn.raw; | |
} | |
const effect = createReactiveEffect(fn, options); | |
if (!options.lazy) { | |
effect(); | |
} | |
return effect; | |
} | |
function stop(effect) { | |
if (effect.active) { | |
cleanup(effect); | |
if (effect.options.onStop) { | |
effect.options.onStop(); | |
} | |
effect.active = false; | |
} | |
} | |
function createReactiveEffect(fn, options) { | |
const effect = function reactiveEffect(...args) { | |
return run(effect, fn, args); | |
}; | |
effect._isEffect = true; | |
effect.active = true; | |
effect.raw = fn; | |
effect.deps = []; | |
effect.options = options; | |
return effect; | |
} | |
function run(effect, fn, args) { | |
if (!effect.active) { | |
return fn(...args); | |
} | |
if (!effectStack.includes(effect)) { | |
cleanup(effect); | |
try { | |
effectStack.push(effect); | |
return fn(...args); | |
} | |
finally { | |
effectStack.pop(); | |
} | |
} | |
} | |
function cleanup(effect) { | |
const { deps } = effect; | |
if (deps.length) { | |
for (let i = 0; i < deps.length; i++) { | |
deps[i].delete(effect); | |
} | |
deps.length = 0; | |
} | |
} | |
let shouldTrack = true; | |
function pauseTracking() { | |
shouldTrack = false; | |
} | |
function resumeTracking() { | |
shouldTrack = true; | |
} | |
function track(target, type, key) { | |
if (!shouldTrack || effectStack.length === 0) { | |
return; | |
} | |
const effect = effectStack[effectStack.length - 1]; | |
if (type === "iterate" /* ITERATE */) { | |
key = ITERATE_KEY; | |
} | |
let depsMap = targetMap.get(target); | |
if (depsMap === void 0) { | |
targetMap.set(target, (depsMap = new Map())); | |
} | |
let dep = depsMap.get(key); | |
if (dep === void 0) { | |
depsMap.set(key, (dep = new Set())); | |
} | |
if (!dep.has(effect)) { | |
dep.add(effect); | |
effect.deps.push(dep); | |
if ( effect.options.onTrack) { | |
effect.options.onTrack({ | |
effect, | |
target, | |
type, | |
key | |
}); | |
} | |
} | |
} | |
function trigger(target, type, key, extraInfo) { | |
const depsMap = targetMap.get(target); | |
if (depsMap === void 0) { | |
// never been tracked | |
return; | |
} | |
const effects = new Set(); | |
const computedRunners = new Set(); | |
if (type === "clear" /* CLEAR */) { | |
// collection being cleared, trigger all effects for target | |
depsMap.forEach(dep => { | |
addRunners(effects, computedRunners, dep); | |
}); | |
} | |
else { | |
// schedule runs for SET | ADD | DELETE | |
if (key !== void 0) { | |
addRunners(effects, computedRunners, depsMap.get(key)); | |
} | |
// also run for iteration key on ADD | DELETE | |
if (type === "add" /* ADD */ || type === "delete" /* DELETE */) { | |
const iterationKey = isArray(target) ? 'length' : ITERATE_KEY; | |
addRunners(effects, computedRunners, depsMap.get(iterationKey)); | |
} | |
} | |
const run = (effect) => { | |
scheduleRun(effect, target, type, key, extraInfo); | |
}; | |
// Important: computed effects must be run first so that computed getters | |
// can be invalidated before any normal effects that depend on them are run. | |
computedRunners.forEach(run); | |
effects.forEach(run); | |
} | |
function addRunners(effects, computedRunners, effectsToAdd) { | |
if (effectsToAdd !== void 0) { | |
effectsToAdd.forEach(effect => { | |
if (effect.options.computed) { | |
computedRunners.add(effect); | |
} | |
else { | |
effects.add(effect); | |
} | |
}); | |
} | |
} | |
function scheduleRun(effect, target, type, key, extraInfo) { | |
if ( effect.options.onTrigger) { | |
const event = { | |
effect, | |
target, | |
key, | |
type | |
}; | |
effect.options.onTrigger(extraInfo ? extend(event, extraInfo) : event); | |
} | |
if (effect.options.scheduler !== void 0) { | |
effect.options.scheduler(effect); | |
} | |
else { | |
effect(); | |
} | |
} | |
const convert = (val) => isObject(val) ? reactive(val) : val; | |
function isRef(r) { | |
return r ? r._isRef === true : false; | |
} | |
function ref(raw) { | |
if (isRef(raw)) { | |
return raw; | |
} | |
raw = convert(raw); | |
const r = { | |
_isRef: true, | |
get value() { | |
track(r, "get" /* GET */, 'value'); | |
return raw; | |
}, | |
set value(newVal) { | |
raw = convert(newVal); | |
trigger(r, "set" /* SET */, 'value', { newValue: newVal } ); | |
} | |
}; | |
return r; | |
} | |
function toRefs(object) { | |
if ( !isReactive(object)) { | |
console.warn(`toRefs() expects a reactive object but received a plain one.`); | |
} | |
const ret = {}; | |
for (const key in object) { | |
ret[key] = toProxyRef(object, key); | |
} | |
return ret; | |
} | |
function toProxyRef(object, key) { | |
return { | |
_isRef: true, | |
get value() { | |
return object[key]; | |
}, | |
set value(newVal) { | |
object[key] = newVal; | |
} | |
}; | |
} | |
function computed(getterOrOptions) { | |
let getter; | |
let setter; | |
if (isFunction(getterOrOptions)) { | |
getter = getterOrOptions; | |
setter = () => { | |
console.warn('Write operation failed: computed value is readonly'); | |
} | |
; | |
} | |
else { | |
getter = getterOrOptions.get; | |
setter = getterOrOptions.set; | |
} | |
let dirty = true; | |
let value; | |
const runner = effect(getter, { | |
lazy: true, | |
// mark effect as computed so that it gets priority during trigger | |
computed: true, | |
scheduler: () => { | |
dirty = true; | |
} | |
}); | |
return { | |
_isRef: true, | |
// expose effect so computed can be stopped | |
effect: runner, | |
get value() { | |
if (dirty) { | |
value = runner(); | |
dirty = false; | |
} | |
// When computed effects are accessed in a parent effect, the parent | |
// should track all the dependencies the computed property has tracked. | |
// This should also apply for chained computed properties. | |
trackChildRun(runner); | |
return value; | |
}, | |
set value(newValue) { | |
setter(newValue); | |
} | |
}; | |
} | |
function trackChildRun(childRunner) { | |
if (effectStack.length === 0) { | |
return; | |
} | |
const parentRunner = effectStack[effectStack.length - 1]; | |
for (let i = 0; i < childRunner.deps.length; i++) { | |
const dep = childRunner.deps[i]; | |
if (!dep.has(parentRunner)) { | |
dep.add(parentRunner); | |
parentRunner.deps.push(dep); | |
} | |
} | |
} | |
const Fragment = Symbol( 'Fragment' ); | |
const Portal = Symbol( 'Portal' ); | |
const Text = Symbol( 'Text' ); | |
const Comment = Symbol( 'Comment' ); | |
// Since v-if and v-for are the two possible ways node structure can dynamically | |
// change, once we consider v-if branches and each v-for fragment a block, we | |
// can divide a template into nested blocks, and within each block the node | |
// structure would be stable. This allows us to skip most children diffing | |
// and only worry about the dynamic nodes (indicated by patch flags). | |
const blockStack = []; | |
let currentBlock = null; | |
// Open a block. | |
// This must be called before `createBlock`. It cannot be part of `createBlock` | |
// because the children of the block are evaluated before `createBlock` itself | |
// is called. The generated code typically looks like this: | |
// | |
// function render() { | |
// return (openBlock(),createBlock('div', null, [...])) | |
// } | |
// | |
// disableTracking is true when creating a fragment block, since a fragment | |
// always diffs its children. | |
function openBlock(disableTracking) { | |
blockStack.push((currentBlock = disableTracking ? null : [])); | |
} | |
// Whether we should be tracking dynamic child nodes inside a block. | |
// Only tracks when this value is > 0 | |
// We are not using a simple boolean because this value may need to be | |
// incremented/decremented by nested usage of v-once (see below) | |
let shouldTrack$1 = 1; | |
// Block tracking sometimes needs to be disabled, for example during the | |
// creation of a tree that needs to be cached by v-once. The compiler generates | |
// code like this: | |
// _cache[1] || ( | |
// setBlockTracking(-1), | |
// _cache[1] = createVNode(...), | |
// setBlockTracking(1), | |
// _cache[1] | |
// ) | |
function setBlockTracking(value) { | |
shouldTrack$1 += value; | |
} | |
// Create a block root vnode. Takes the same exact arguments as `createVNode`. | |
// A block root keeps track of dynamic nodes within the block in the | |
// `dynamicChildren` array. | |
function createBlock(type, props, children, patchFlag, dynamicProps) { | |
// avoid a block with patchFlag tracking itself | |
shouldTrack$1--; | |
const vnode = createVNode(type, props, children, patchFlag, dynamicProps); | |
shouldTrack$1++; | |
// save current block children on the block vnode | |
vnode.dynamicChildren = currentBlock || EMPTY_ARR; | |
// close block | |
blockStack.pop(); | |
currentBlock = blockStack[blockStack.length - 1] || null; | |
// a block is always going to be patched, so track it as a child of its | |
// parent block | |
if (currentBlock !== null) { | |
currentBlock.push(vnode); | |
} | |
return vnode; | |
} | |
function isVNode(value) { | |
return value ? value._isVNode === true : false; | |
} | |
function createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null) { | |
// class & style normalization. | |
if (props !== null) { | |
// for reactive or proxy objects, we need to clone it to enable mutation. | |
if (isReactive(props) || SetupProxySymbol in props) { | |
props = extend({}, props); | |
} | |
let { class: klass, style } = props; | |
if (klass != null && !isString(klass)) { | |
props.class = normalizeClass(klass); | |
} | |
if (style != null) { | |
// reactive state objects need to be cloned since they are likely to be | |
// mutated | |
if (isReactive(style) && !isArray(style)) { | |
style = extend({}, style); | |
} | |
props.style = normalizeStyle(style); | |
} | |
} | |
// encode the vnode type information into a bitmap | |
const shapeFlag = isString(type) | |
? 1 /* ELEMENT */ | |
: type.__isSuspense === true | |
? 64 /* SUSPENSE */ | |
: isObject(type) | |
? 4 /* STATEFUL_COMPONENT */ | |
: isFunction(type) | |
? 2 /* FUNCTIONAL_COMPONENT */ | |
: 0; | |
const vnode = { | |
_isVNode: true, | |
type, | |
props, | |
key: (props !== null && props.key) || null, | |
ref: (props !== null && props.ref) || null, | |
children: null, | |
component: null, | |
suspense: null, | |
dirs: null, | |
el: null, | |
anchor: null, | |
target: null, | |
shapeFlag, | |
patchFlag, | |
dynamicProps, | |
dynamicChildren: null, | |
appContext: null | |
}; | |
normalizeChildren(vnode, children); | |
// presence of a patch flag indicates this node needs patching on updates. | |
// component nodes also should always be patched, because even if the | |
// component doesn't need to update, it needs to persist the instance on to | |
// the next vnode so that it can be properly unmounted later. | |
if (shouldTrack$1 > 0 && | |
currentBlock !== null && | |
(patchFlag > 0 || | |
shapeFlag & 4 /* STATEFUL_COMPONENT */ || | |
shapeFlag & 2 /* FUNCTIONAL_COMPONENT */)) { | |
currentBlock.push(vnode); | |
} | |
return vnode; | |
} | |
function cloneVNode(vnode, extraProps) { | |
// This is intentionally NOT using spread or extend to avoid the runtime | |
// key enumeration cost. | |
return { | |
_isVNode: true, | |
type: vnode.type, | |
props: extraProps | |
? vnode.props | |
? mergeProps(vnode.props, extraProps) | |
: extraProps | |
: vnode.props, | |
key: vnode.key, | |
ref: vnode.ref, | |
children: vnode.children, | |
target: vnode.target, | |
shapeFlag: vnode.shapeFlag, | |
patchFlag: vnode.patchFlag, | |
dynamicProps: vnode.dynamicProps, | |
dynamicChildren: vnode.dynamicChildren, | |
appContext: vnode.appContext, | |
dirs: vnode.dirs, | |
// These should technically only be non-null on mounted VNodes. However, | |
// they *should* be copied for kept-alive vnodes. So we just always copy | |
// them since them being non-null during a mount doesn't affect the logic as | |
// they will simply be overwritten. | |
component: vnode.component, | |
suspense: vnode.suspense, | |
el: vnode.el, | |
anchor: vnode.anchor | |
}; | |
} | |
function createTextVNode(text = ' ', flag = 0) { | |
return createVNode(Text, null, text, flag); | |
} | |
function createCommentVNode(text = '', | |
// when used as the v-else branch, the comment node must be created as a | |
// block to ensure correct updates. | |
asBlock = false) { | |
return asBlock | |
? createBlock(Comment, null, text) | |
: createVNode(Comment, null, text); | |
} | |
function normalizeVNode(child) { | |
if (child == null) { | |
// empty placeholder | |
return createVNode(Comment); | |
} | |
else if (isArray(child)) { | |
// fragment | |
return createVNode(Fragment, null, child); | |
} | |
else if (typeof child === 'object') { | |
// already vnode, this should be the most common since compiled templates | |
// always produce all-vnode children arrays | |
return child.el === null ? child : cloneVNode(child); | |
} | |
else { | |
// primitive types | |
return createVNode(Text, null, child + ''); | |
} | |
} | |
function normalizeChildren(vnode, children) { | |
let type = 0; | |
if (children == null) { | |
children = null; | |
} | |
else if (isArray(children)) { | |
type = 16 /* ARRAY_CHILDREN */; | |
} | |
else if (typeof children === 'object') { | |
type = 32 /* SLOTS_CHILDREN */; | |
} | |
else if (isFunction(children)) { | |
children = { default: children }; | |
type = 32 /* SLOTS_CHILDREN */; | |
} | |
else { | |
children = isString(children) ? children : children + ''; | |
type = 8 /* TEXT_CHILDREN */; | |
} | |
vnode.children = children; | |
vnode.shapeFlag |= type; | |
} | |
function normalizeStyle(value) { | |
if (isArray(value)) { | |
const res = {}; | |
for (let i = 0; i < value.length; i++) { | |
const normalized = normalizeStyle(value[i]); | |
if (normalized) { | |
for (const key in normalized) { | |
res[key] = normalized[key]; | |
} | |
} | |
} | |
return res; | |
} | |
else if (isObject(value)) { | |
return value; | |
} | |
} | |
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(); | |
} | |
const handlersRE = /^on|^vnode/; | |
function mergeProps(...args) { | |
const ret = {}; | |
extend(ret, args[0]); | |
for (let i = 1; i < args.length; i++) { | |
const toMerge = args[i]; | |
for (const key in toMerge) { | |
if (key === 'class') { | |
ret.class = normalizeClass([ret.class, toMerge.class]); | |
} | |
else if (key === 'style') { | |
ret.style = normalizeStyle([ret.style, toMerge.style]); | |
} | |
else if (handlersRE.test(key)) { | |
// on*, vnode* | |
const existing = ret[key]; | |
ret[key] = existing | |
? [].concat(existing, toMerge[key]) | |
: toMerge[key]; | |
} | |
else { | |
ret[key] = toMerge[key]; | |
} | |
} | |
} | |
return ret; | |
} | |
const stack = []; | |
function pushWarningContext(vnode) { | |
stack.push(vnode); | |
} | |
function popWarningContext() { | |
stack.pop(); | |
} | |
function warn(msg, ...args) { | |
// avoid props formatting or warn handler tracking deps that might be mutated | |
// during patch, leading to infinite recursion. | |
pauseTracking(); | |
const instance = stack.length ? stack[stack.length - 1].component : null; | |
const appWarnHandler = instance && instance.appContext.config.warnHandler; | |
const trace = getComponentTrace(); | |
if (appWarnHandler) { | |
callWithErrorHandling(appWarnHandler, instance, 9 /* APP_WARN_HANDLER */, [ | |
msg + args.join(''), | |
instance && instance.renderProxy, | |
trace | |
.map(({ vnode }) => `at <${formatComponentName(vnode)}>`) | |
.join('\n'), | |
trace | |
]); | |
} | |
else { | |
const warnArgs = [`[Vue warn]: ${msg}`, ...args]; | |
if (trace.length && | |
// avoid spamming console during tests | |
!false) { | |
warnArgs.push(`\n`, ...formatTrace(trace)); | |
} | |
console.warn(...warnArgs); | |
} | |
resumeTracking(); | |
} | |
function getComponentTrace() { | |
let currentVNode = stack[stack.length - 1]; | |
if (!currentVNode) { | |
return []; | |
} | |
// we can't just use the stack because it will be incomplete during updates | |
// that did not start from the root. Re-construct the parent chain using | |
// instance parent pointers. | |
const normalizedStack = []; | |
while (currentVNode) { | |
const last = normalizedStack[0]; | |
if (last && last.vnode === currentVNode) { | |
last.recurseCount++; | |
} | |
else { | |
normalizedStack.push({ | |
vnode: currentVNode, | |
recurseCount: 0 | |
}); | |
} | |
const parentInstance = currentVNode.component | |
.parent; | |
currentVNode = parentInstance && parentInstance.vnode; | |
} | |
return normalizedStack; | |
} | |
function formatTrace(trace) { | |
const logs = []; | |
trace.forEach((entry, i) => { | |
logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry)); | |
}); | |
return logs; | |
} | |
function formatTraceEntry({ vnode, recurseCount }) { | |
const postfix = recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``; | |
const open = ` at <${formatComponentName(vnode)}`; | |
const close = `>` + postfix; | |
const rootLabel = vnode.component.parent == null ? `(Root)` : ``; | |
return vnode.props | |
? [open, ...formatProps(vnode.props), close, rootLabel] | |
: [open + close, rootLabel]; | |
} | |
const classifyRE = /(?:^|[-_])(\w)/g; | |
const classify = (str) => str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, ''); | |
function formatComponentName(vnode, file) { | |
const Component = vnode.type; | |
let name = isFunction(Component) | |
? Component.displayName || Component.name | |
: Component.name; | |
if (!name && file) { | |
const match = file.match(/([^/\\]+)\.vue$/); | |
if (match) { | |
name = match[1]; | |
} | |
} | |
return name ? classify(name) : 'Anonymous'; | |
} | |
function formatProps(props) { | |
const res = []; | |
for (const key in props) { | |
res.push(...formatProp(key, props[key])); | |
} | |
return res; | |
} | |
function formatProp(key, value, raw) { | |
if (isString(value)) { | |
value = JSON.stringify(value); | |
return raw ? value : [`${key}=${value}`]; | |
} | |
else if (typeof value === 'number' || value == null) { | |
return raw ? value : [`${key}=${value}`]; | |
} | |
else if (isRef(value)) { | |
value = formatProp(key, toRaw(value.value), true); | |
return raw ? value : [`${key}=Ref<`, value, `>`]; | |
} | |
else { | |
value = toRaw(value); | |
return raw ? value : [`${key}=`, value]; | |
} | |
} | |
const ErrorTypeStrings = { | |
["bc" /* BEFORE_CREATE */]: 'beforeCreate hook', | |
["c" /* CREATED */]: 'created hook', | |
["bm" /* BEFORE_MOUNT */]: 'beforeMount hook', | |
["m" /* MOUNTED */]: 'mounted hook', | |
["bu" /* BEFORE_UPDATE */]: 'beforeUpdate hook', | |
["u" /* UPDATED */]: 'updated', | |
["bum" /* BEFORE_UNMOUNT */]: 'beforeUnmount hook', | |
["um" /* UNMOUNTED */]: 'unmounted hook', | |
["a" /* ACTIVATED */]: 'activated hook', | |
["da" /* DEACTIVATED */]: 'deactivated hook', | |
["ec" /* ERROR_CAPTURED */]: 'errorCaptured hook', | |
["rtc" /* RENDER_TRACKED */]: 'renderTracked hook', | |
["rtg" /* RENDER_TRIGGERED */]: 'renderTriggered hook', | |
[0 /* SETUP_FUNCTION */]: 'setup function', | |
[1 /* RENDER_FUNCTION */]: 'render function', | |
[2 /* WATCH_GETTER */]: 'watcher getter', | |
[3 /* WATCH_CALLBACK */]: 'watcher callback', | |
[4 /* WATCH_CLEANUP */]: 'watcher cleanup function', | |
[5 /* NATIVE_EVENT_HANDLER */]: 'native event handler', | |
[6 /* COMPONENT_EVENT_HANDLER */]: 'component event handler', | |
[7 /* DIRECTIVE_HOOK */]: 'directive hook', | |
[8 /* APP_ERROR_HANDLER */]: 'app errorHandler', | |
[9 /* APP_WARN_HANDLER */]: 'app warnHandler', | |
[10 /* FUNCTION_REF */]: 'ref function', | |
[11 /* SCHEDULER */]: 'scheduler flush. This is likely a Vue internals bug. ' + | |
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue' | |
}; | |
function callWithErrorHandling(fn, instance, type, args) { | |
let res; | |
try { | |
res = args ? fn(...args) : fn(); | |
} | |
catch (err) { | |
handleError(err, instance, type); | |
} | |
return res; | |
} | |
function callWithAsyncErrorHandling(fn, instance, type, args) { | |
if (isFunction(fn)) { | |
const res = callWithErrorHandling(fn, instance, type, args); | |
if (res != null && !res._isVue && isPromise(res)) { | |
res.catch((err) => { | |
handleError(err, instance, type); | |
}); | |
} | |
return res; | |
} | |
for (let i = 0; i < fn.length; i++) { | |
callWithAsyncErrorHandling(fn[i], instance, type, args); | |
} | |
} | |
function handleError(err, instance, type) { | |
const contextVNode = instance ? instance.vnode : null; | |
if (instance) { | |
let cur = instance.parent; | |
// the exposed instance is the render proxy to keep it consistent with 2.x | |
const exposedInstance = instance.renderProxy; | |
// in production the hook receives only the error code | |
const errorInfo = ErrorTypeStrings[type] ; | |
while (cur) { | |
const errorCapturedHooks = cur.ec; | |
if (errorCapturedHooks !== null) { | |
for (let i = 0; i < errorCapturedHooks.length; i++) { | |
if (errorCapturedHooks[i](err, exposedInstance, errorInfo)) { | |
return; | |
} | |
} | |
} | |
cur = cur.parent; | |
} | |
// app-level handling | |
const appErrorHandler = instance.appContext.config.errorHandler; | |
if (appErrorHandler) { | |
callWithErrorHandling(appErrorHandler, null, 8 /* APP_ERROR_HANDLER */, [err, exposedInstance, errorInfo]); | |
return; | |
} | |
} | |
logError(err, type, contextVNode); | |
} | |
// Test-only toggle for testing the uhandled warning behavior | |
let forceRecover = false; | |
function logError(err, type, contextVNode) { | |
// default behavior is crash in prod & test, recover in dev. | |
if ( (forceRecover || !false)) { | |
const info = ErrorTypeStrings[type]; | |
if (contextVNode) { | |
pushWarningContext(contextVNode); | |
} | |
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`); | |
console.error(err); | |
if (contextVNode) { | |
popWarningContext(); | |
} | |
} | |
else { | |
throw err; | |
} | |
} | |
const queue = []; | |
const postFlushCbs = []; | |
const p = Promise.resolve(); | |
let isFlushing = false; | |
let isFlushPending = false; | |
function nextTick(fn) { | |
return fn ? p.then(fn) : p; | |
} | |
function queueJob(job) { | |
if (!queue.includes(job)) { | |
queue.push(job); | |
queueFlush(); | |
} | |
} | |
function queuePostFlushCb(cb) { | |
if (!isArray(cb)) { | |
postFlushCbs.push(cb); | |
} | |
else { | |
postFlushCbs.push(...cb); | |
} | |
queueFlush(); | |
} | |
function queueFlush() { | |
if (!isFlushing && !isFlushPending) { | |
isFlushPending = true; | |
nextTick(flushJobs); | |
} | |
} | |
const dedupe = (cbs) => [...new Set(cbs)]; | |
function flushPostFlushCbs() { | |
if (postFlushCbs.length) { | |
const cbs = dedupe(postFlushCbs); | |
postFlushCbs.length = 0; | |
for (let i = 0; i < cbs.length; i++) { | |
cbs[i](); | |
} | |
} | |
} | |
const RECURSION_LIMIT = 100; | |
function flushJobs(seenJobs) { | |
isFlushPending = false; | |
isFlushing = true; | |
let job; | |
{ | |
seenJobs = seenJobs || new Map(); | |
} | |
while ((job = queue.shift())) { | |
{ | |
const seen = seenJobs; | |
if (!seen.has(job)) { | |
seen.set(job, 1); | |
} | |
else { | |
const count = seen.get(job); | |
if (count > RECURSION_LIMIT) { | |
throw new Error('Maximum recursive updates exceeded. ' + | |
"You may have code that is mutating state in your component's " + | |
'render function or updated hook.'); | |
} | |
else { | |
seen.set(job, count + 1); | |
} | |
} | |
} | |
callWithErrorHandling(job, null, 11 /* SCHEDULER */); | |
} | |
flushPostFlushCbs(); | |
isFlushing = false; | |
// some postFlushCb queued jobs! | |
// keep flushing until it drains. | |
if (queue.length || postFlushCbs.length) { | |
flushJobs(seenJobs); | |
} | |
} | |
// mark the current rendering instance for asset resolution (e.g. | |
// resolveComponent, resolveDirective) during render | |
let currentRenderingInstance = null; | |
// dev only flag to track whether $attrs was used during render. | |
// If $attrs was used during render then the warning for failed attrs | |
// fallthrough can be suppressed. | |
let accessedAttrs = false; | |
function markAttrsAccessed() { | |
accessedAttrs = true; | |
} | |
function renderComponentRoot(instance) { | |
const { type: Component, vnode, renderProxy, props, slots, attrs, emit } = instance; | |
let result; | |
currentRenderingInstance = instance; | |
{ | |
accessedAttrs = false; | |
} | |
try { | |
if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) { | |
result = normalizeVNode(instance.render.call(renderProxy)); | |
} | |
else { | |
// functional | |
const render = Component; | |
const propsToPass = true ? readonlyProps(props) : props; | |
result = normalizeVNode(render.length > 1 | |
? render(propsToPass, { | |
attrs, | |
slots, | |
emit | |
}) | |
: render(propsToPass, null /* we know it doesn't need it */)); | |
} | |
// attr merging | |
if (Component.props != null && | |
Component.inheritAttrs !== false && | |
attrs !== EMPTY_OBJ && | |
Object.keys(attrs).length) { | |
if (result.shapeFlag & 1 /* ELEMENT */ || | |
result.shapeFlag & 6 /* COMPONENT */) { | |
result = cloneVNode(result, attrs); | |
} | |
else if (true && !accessedAttrs) { | |
warn(`Extraneous non-props attributes (${Object.keys(attrs).join(',')}) ` + | |
`were passed to component but could not be automatically inhertied ` + | |
`because component renders fragment or text root nodes.`); | |
} | |
} | |
} | |
catch (err) { | |
handleError(err, instance, 1 /* RENDER_FUNCTION */); | |
result = createVNode(Comment); | |
} | |
currentRenderingInstance = null; | |
return result; | |
} | |
function shouldUpdateComponent(prevVNode, nextVNode, optimized) { | |
const { props: prevProps, children: prevChildren } = prevVNode; | |
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode; | |
if (patchFlag > 0) { | |
if (patchFlag & 256 /* DYNAMIC_SLOTS */) { | |
// slot content that references values that might have changed, | |
// e.g. in a v-for | |
return true; | |
} | |
if (patchFlag & 16 /* FULL_PROPS */) { | |
// presence of this flag indicates props are always non-null | |
return hasPropsChanged(prevProps, nextProps); | |
} | |
else if (patchFlag & 8 /* PROPS */) { | |
const dynamicProps = nextVNode.dynamicProps; | |
for (let i = 0; i < dynamicProps.length; i++) { | |
const key = dynamicProps[i]; | |
if (nextProps[key] !== prevProps[key]) { | |
return true; | |
} | |
} | |
} | |
} | |
else if (!optimized) { | |
// this path is only taken by manually written render functions | |
// so presence of any children leads to a forced update | |
if (prevChildren != null || nextChildren != null) { | |
return true; | |
} | |
if (prevProps === nextProps) { | |
return false; | |
} | |
if (prevProps === null) { | |
return nextProps !== null; | |
} | |
if (nextProps === null) { | |
return true; | |
} | |
return hasPropsChanged(prevProps, nextProps); | |
} | |
return false; | |
} | |
function hasPropsChanged(prevProps, nextProps) { | |
const nextKeys = Object.keys(nextProps); | |
if (nextKeys.length !== Object.keys(prevProps).length) { | |
return true; | |
} | |
for (let i = 0; i < nextKeys.length; i++) { | |
const key = nextKeys[i]; | |
if (nextProps[key] !== prevProps[key]) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function updateHOCHostEl({ vnode, parent }, el // HostNode | |
) { | |
while (parent && parent.subTree === vnode) { | |
(vnode = parent.vnode).el = el; | |
parent = parent.parent; | |
} | |
} | |
// resolve raw VNode data. | |
// - filter out reserved keys (key, ref, slots) | |
// - extract class and style into $attrs (to be merged onto child | |
// component root) | |
// - for the rest: | |
// - if has declared props: put declared ones in `props`, the rest in `attrs` | |
// - else: everything goes in `props`. | |
function resolveProps(instance, rawProps, _options) { | |
const hasDeclaredProps = _options != null; | |
const options = normalizePropsOptions(_options); | |
if (!rawProps && !hasDeclaredProps) { | |
return; | |
} | |
const props = {}; | |
let attrs = void 0; | |
// update the instance propsProxy (passed to setup()) to trigger potential | |
// changes | |
const propsProxy = instance.propsProxy; | |
const setProp = propsProxy | |
? (key, val) => { | |
props[key] = val; | |
propsProxy[key] = val; | |
} | |
: (key, val) => { | |
props[key] = val; | |
}; | |
// allow mutation of propsProxy (which is readonly by default) | |
unlock(); | |
if (rawProps != null) { | |
for (const key in rawProps) { | |
// key, ref are reserved | |
if (isReservedProp(key)) | |
continue; | |
// prop option names are camelized during normalization, so to support | |
// kebab -> camel conversion here we need to camelize the key. | |
const camelKey = camelize(key); | |
if (hasDeclaredProps && !hasOwn(options, camelKey)) { | |
(attrs || (attrs = {}))[key] = rawProps[key]; | |
} | |
else { | |
setProp(camelKey, rawProps[key]); | |
} | |
} | |
} | |
// set default values, cast booleans & run validators | |
if (hasDeclaredProps) { | |
for (const key in options) { | |
let opt = options[key]; | |
if (opt == null) | |
continue; | |
const isAbsent = !hasOwn(props, key); | |
const hasDefault = hasOwn(opt, 'default'); | |
const currentValue = props[key]; | |
// default values | |
if (hasDefault && currentValue === undefined) { | |
const defaultValue = opt.default; | |
setProp(key, isFunction(defaultValue) ? defaultValue() : defaultValue); | |
} | |
// boolean casting | |
if (opt["1" /* shouldCast */]) { | |
if (isAbsent && !hasDefault) { | |
setProp(key, false); | |
} | |
else if (opt["2" /* shouldCastTrue */] && | |
(currentValue === '' || currentValue === hyphenate(key))) { | |
setProp(key, true); | |
} | |
} | |
// runtime validation | |
if ( rawProps) { | |
let rawValue; | |
if (!(key in rawProps) && hyphenate(key) in rawProps) { | |
rawValue = rawProps[hyphenate(key)]; | |
} | |
else { | |
rawValue = rawProps[key]; | |
} | |
validateProp(key, toRaw(rawValue), opt, isAbsent); | |
} | |
} | |
} | |
else { | |
// if component has no declared props, $attrs === $props | |
attrs = props; | |
} | |
// in case of dynamic props, check if we need to delete keys from | |
// the props proxy | |
const { patchFlag } = instance.vnode; | |
if (propsProxy !== null && | |
(patchFlag === 0 || patchFlag & 16 /* FULL_PROPS */)) { | |
const rawInitialProps = toRaw(propsProxy); | |
for (const key in rawInitialProps) { | |
if (!hasOwn(props, key)) { | |
delete propsProxy[key]; | |
} | |
} | |
} | |
// lock readonly | |
lock(); | |
instance.props = props; | |
instance.attrs = options ? attrs || EMPTY_OBJ : props; | |
} | |
const normalizationMap = new WeakMap(); | |
function normalizePropsOptions(raw) { | |
if (!raw) { | |
return null; | |
} | |
if (normalizationMap.has(raw)) { | |
return normalizationMap.get(raw); | |
} | |
const normalized = {}; | |
normalizationMap.set(raw, normalized); | |
if (isArray(raw)) { | |
for (let i = 0; i < raw.length; i++) { | |
if ( !isString(raw[i])) { | |
warn(`props must be strings when using array syntax.`, raw[i]); | |
} | |
const normalizedKey = camelize(raw[i]); | |
if (normalizedKey[0] !== '$') { | |
normalized[normalizedKey] = EMPTY_OBJ; | |
} | |
else { | |
warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`); | |
} | |
} | |
} | |
else { | |
if ( !isObject(raw)) { | |
warn(`invalid props options`, raw); | |
} | |
for (const key in raw) { | |
const normalizedKey = camelize(key); | |
if (normalizedKey[0] !== '$') { | |
const opt = raw[key]; | |
const prop = (normalized[normalizedKey] = | |
isArray(opt) || isFunction(opt) ? { type: opt } : opt); | |
if (prop != null) { | |
const booleanIndex = getTypeIndex(Boolean, prop.type); | |
const stringIndex = getTypeIndex(String, prop.type); | |
prop["1" /* shouldCast */] = booleanIndex > -1; | |
prop["2" /* shouldCastTrue */] = booleanIndex < stringIndex; | |
} | |
} | |
else { | |
warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`); | |
} | |
} | |
} | |
return normalized; | |
} | |
// use function string name to check type constructors | |
// so that it works across vms / iframes. | |
function getType(ctor) { | |
const match = ctor && ctor.toString().match(/^\s*function (\w+)/); | |
return match ? match[1] : ''; | |
} | |
function isSameType(a, b) { | |
return getType(a) === getType(b); | |
} | |
function getTypeIndex(type, expectedTypes) { | |
if (isArray(expectedTypes)) { | |
for (let i = 0, len = expectedTypes.length; i < len; i++) { | |
if (isSameType(expectedTypes[i], type)) { | |
return i; | |
} | |
} | |
} | |
else if (isObject(expectedTypes)) { | |
return isSameType(expectedTypes, type) ? 0 : -1; | |
} | |
return -1; | |
} | |
function validateProp(name, value, prop, isAbsent) { | |
const { type, required, validator } = prop; | |
// required! | |
if (required && isAbsent) { | |
warn('Missing required prop: "' + name + '"'); | |
return; | |
} | |
// missing but optional | |
if (value == null && !prop.required) { | |
return; | |
} | |
// type check | |
if (type != null && type !== true) { | |
let isValid = false; | |
const types = isArray(type) ? type : [type]; | |
const expectedTypes = []; | |
// value is valid as long as one of the specified types match | |
for (let i = 0; i < types.length && !isValid; i++) { | |
const { valid, expectedType } = assertType(value, types[i]); | |
expectedTypes.push(expectedType || ''); | |
isValid = valid; | |
} | |
if (!isValid) { | |
warn(getInvalidTypeMessage(name, value, expectedTypes)); | |
return; | |
} | |
} | |
// custom validator | |
if (validator && !validator(value)) { | |
warn('Invalid prop: custom validator check failed for prop "' + name + '".'); | |
} | |
} | |
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol'); | |
function assertType(value, type) { | |
let valid; | |
const expectedType = getType(type); | |
if (isSimpleType(expectedType)) { | |
const t = typeof value; | |
valid = t === expectedType.toLowerCase(); | |
// for primitive wrapper objects | |
if (!valid && t === 'object') { | |
valid = value instanceof type; | |
} | |
} | |
else if (expectedType === 'Object') { | |
valid = toRawType(value) === 'Object'; | |
} | |
else if (expectedType === 'Array') { | |
valid = isArray(value); | |
} | |
else { | |
valid = value instanceof type; | |
} | |
return { | |
valid, | |
expectedType | |
}; | |
} | |
function getInvalidTypeMessage(name, value, expectedTypes) { | |
let message = `Invalid prop: type check failed for prop "${name}".` + | |
` Expected ${expectedTypes.map(capitalize).join(', ')}`; | |
const expectedType = expectedTypes[0]; | |
const receivedType = toRawType(value); | |
const expectedValue = styleValue(value, expectedType); | |
const receivedValue = styleValue(value, receivedType); | |
// check if we need to specify expected value | |
if (expectedTypes.length === 1 && | |
isExplicable(expectedType) && | |
!isBoolean(expectedType, receivedType)) { | |
message += ` with value ${expectedValue}`; | |
} | |
message += `, got ${receivedType} `; | |
// check if we need to specify received value | |
if (isExplicable(receivedType)) { | |
message += `with value ${receivedValue}.`; | |
} | |
return message; | |
} | |
function styleValue(value, type) { | |
if (type === 'String') { | |
return `"${value}"`; | |
} | |
else if (type === 'Number') { | |
return `${Number(value)}`; | |
} | |
else { | |
return `${value}`; | |
} | |
} | |
function isExplicable(type) { | |
const explicitTypes = ['string', 'number', 'boolean']; | |
return explicitTypes.some(elem => type.toLowerCase() === elem); | |
} | |
function isBoolean(...args) { | |
return args.some(elem => elem.toLowerCase() === 'boolean'); | |
} | |
const normalizeSlotValue = (value) => isArray(value) | |
? value.map(normalizeVNode) | |
: [normalizeVNode(value)]; | |
const normalizeSlot = (key, rawSlot) => (props) => { | |
if ( currentInstance != null) { | |
warn(`Slot "${key}" invoked outside of the render function: ` + | |
`this will not track dependencies used in the slot. ` + | |
`Invoke the slot function inside the render function instead.`); | |
} | |
return normalizeSlotValue(rawSlot(props)); | |
}; | |
function resolveSlots(instance, children) { | |
let slots; | |
if (instance.vnode.shapeFlag & 32 /* SLOTS_CHILDREN */) { | |
const rawSlots = children; | |
if (rawSlots._compiled) { | |
// pre-normalized slots object generated by compiler | |
slots = children; | |
} | |
else { | |
slots = {}; | |
for (const key in rawSlots) { | |
const value = rawSlots[key]; | |
if (isFunction(value)) { | |
slots[key] = normalizeSlot(key, value); | |
} | |
else if (value != null) { | |
{ | |
warn(`Non-function value encountered for slot "${key}". ` + | |
`Prefer function slots for better performance.`); | |
} | |
const normalized = normalizeSlotValue(value); | |
slots[key] = () => normalized; | |
} | |
} | |
} | |
} | |
else if (children !== null) { | |
// non slot object children (direct value) passed to a component | |
{ | |
warn(`Non-function value encountered for default slot. ` + | |
`Prefer function slots for better performance.`); | |
} | |
const normalized = normalizeSlotValue(children); | |
slots = { default: () => normalized }; | |
} | |
if (slots !== void 0) { | |
instance.slots = slots; | |
} | |
} | |
/** | |
Runtime helper for applying directives to a vnode. Example usage: | |
const comp = resolveComponent('comp') | |
const foo = resolveDirective('foo') | |
const bar = resolveDirective('bar') | |
return withDirectives(h(comp), [ | |
[foo, this.x], | |
[bar, this.y] | |
]) | |
*/ | |
const isBuiltInDirective = /*#__PURE__*/ makeMap('bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text'); | |
function validateDirectiveName(name) { | |
if (isBuiltInDirective(name)) { | |
warn('Do not use built-in directive ids as custom directive id: ' + name); | |
} | |
} | |
const directiveToVnodeHooksMap = /*#__PURE__*/ [ | |
'beforeMount', | |
'mounted', | |
'beforeUpdate', | |
'updated', | |
'beforeUnmount', | |
'unmounted' | |
].reduce((map, key) => { | |
const vnodeKey = `onVnode` + key[0].toUpperCase() + key.slice(1); | |
const vnodeHook = (vnode, prevVnode) => { | |
const bindings = vnode.dirs; | |
const prevBindings = prevVnode ? prevVnode.dirs : EMPTY_ARR; | |
for (let i = 0; i < bindings.length; i++) { | |
const binding = bindings[i]; | |
const hook = binding.dir[key]; | |
if (hook != null) { | |
if (prevVnode != null) { | |
binding.oldValue = prevBindings[i].value; | |
} | |
hook(vnode.el, binding, vnode, prevVnode); | |
} | |
} | |
}; | |
map[key] = [vnodeKey, vnodeHook]; | |
return map; | |
}, {}); | |
function withDirectives(vnode, directives) { | |
const internalInstance = currentRenderingInstance; | |
if (internalInstance === null) { | |
warn(`withDirectives can only be used inside render functions.`); | |
return vnode; | |
} | |
const instance = internalInstance.renderProxy; | |
const props = vnode.props || (vnode.props = {}); | |
const bindings = vnode.dirs || (vnode.dirs = new Array(directives.length)); | |
const injected = {}; | |
for (let i = 0; i < directives.length; i++) { | |
let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]; | |
if (isFunction(dir)) { | |
dir = { | |
mounted: dir, | |
updated: dir | |
}; | |
} | |
bindings[i] = { | |
dir, | |
instance, | |
value, | |
oldValue: void 0, | |
arg, | |
modifiers | |
}; | |
// inject onVnodeXXX hooks | |
for (const key in dir) { | |
if (!injected[key]) { | |
const { 0: hookName, 1: hook } = directiveToVnodeHooksMap[key]; | |
const existing = props[hookName]; | |
props[hookName] = existing ? [].concat(existing, hook) : hook; | |
injected[key] = true; | |
} | |
} | |
} | |
return vnode; | |
} | |
function invokeDirectiveHook(hook, instance, vnode, prevVNode = null) { | |
callWithAsyncErrorHandling(hook, instance, 7 /* DIRECTIVE_HOOK */, [ | |
vnode, | |
prevVNode | |
]); | |
} | |
function createAppContext() { | |
return { | |
config: { | |
devtools: true, | |
performance: false, | |
isNativeTag: NO, | |
isCustomElement: NO, | |
errorHandler: undefined, | |
warnHandler: undefined | |
}, | |
mixins: [], | |
components: {}, | |
directives: {}, | |
provides: {} | |
}; | |
} | |
function createAppAPI(render) { | |
return function createApp() { | |
const context = createAppContext(); | |
const installedPlugins = new Set(); | |
let isMounted = false; | |
const app = { | |
get config() { | |
return context.config; | |
}, | |
set config(v) { | |
{ | |
warn(`app.config cannot be replaced. Modify individual options instead.`); | |
} | |
}, | |
use(plugin) { | |
if (installedPlugins.has(plugin)) { | |
warn(`Plugin has already been applied to target app.`); | |
} | |
else if (isFunction(plugin)) { | |
installedPlugins.add(plugin); | |
plugin(app); | |
} | |
else if (isFunction(plugin.install)) { | |
installedPlugins.add(plugin); | |
plugin.install(app); | |
} | |
else { | |
warn(`A plugin must either be a function or an object with an "install" ` + | |
`function.`); | |
} | |
return app; | |
}, | |
mixin(mixin) { | |
if (!context.mixins.includes(mixin)) { | |
context.mixins.push(mixin); | |
} | |
else { | |
warn('Mixin has already been applied to target app' + | |
(mixin.name ? `: ${mixin.name}` : '')); | |
} | |
return app; | |
}, | |
component(name, component) { | |
{ | |
validateComponentName(name, context.config); | |
} | |
if (!component) { | |
return context.components[name]; | |
} | |
else { | |
if ( context.components[name]) { | |
warn(`Component "${name}" has already been registered in target app.`); | |
} | |
context.components[name] = component; | |
return app; | |
} | |
}, | |
directive(name, directive) { | |
{ | |
validateDirectiveName(name); | |
} | |
if (!directive) { | |
return context.directives[name]; | |
} | |
else { | |
if ( context.directives[name]) { | |
warn(`Directive "${name}" has already been registered in target app.`); | |
} | |
context.directives[name] = directive; | |
return app; | |
} | |
}, | |
mount(rootComponent, rootContainer, rootProps) { | |
if (!isMounted) { | |
const vnode = createVNode(rootComponent, rootProps); | |
// store app context on the root VNode. | |
// this will be set on the root instance on initial mount. | |
vnode.appContext = context; | |
render(vnode, rootContainer); | |
isMounted = true; | |
return vnode.component.renderProxy; | |
} | |
else { | |
warn(`App has already been mounted. Create a new app instance instead.`); | |
} | |
}, | |
provide(key, value) { | |
if ( key in context.provides) { | |
warn(`App already provides property with key "${key}". ` + | |
`It will be overwritten with the new value.`); | |
} | |
// TypeScript doesn't allow symbols as index type | |
// https://github.com/Microsoft/TypeScript/issues/24587 | |
context.provides[key] = value; | |
return app; | |
} | |
}; | |
return app; | |
}; | |
} | |
// Suspense exposes a component-like API, and is treated like a component | |
// in the compiler, but internally it's a special built-in type that hooks | |
// directly into the renderer. | |
const SuspenseImpl = { | |
// In order to make Suspense tree-shakable, we need to avoid importing it | |
// directly in the renderer. The renderer checks for the __isSuspense flag | |
// on a vnode's type and calls the `process` method, passing in renderer | |
// internals. | |
__isSuspense: true, | |
process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, | |
// platform-specific impl passed from renderer | |
rendererInternals) { | |
if (n1 == null) { | |
mountSuspense(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, rendererInternals); | |
} | |
else { | |
patchSuspense(n1, n2, container, anchor, parentComponent, isSVG, optimized, rendererInternals); | |
} | |
} | |
}; | |
// Force-casted public typing for h and TSX props inference | |
const Suspense = ( SuspenseImpl | |
); | |
function mountSuspense(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, rendererInternals) { | |
const { patch, options: { createElement } } = rendererInternals; | |
const hiddenContainer = createElement('div'); | |
const suspense = (n2.suspense = createSuspenseBoundary(n2, parentSuspense, parentComponent, container, hiddenContainer, anchor, isSVG, optimized, rendererInternals)); | |
const { content, fallback } = normalizeSuspenseChildren(n2); | |
suspense.subTree = content; | |
suspense.fallbackTree = fallback; | |
// start mounting the content subtree in an off-dom container | |
patch(null, content, hiddenContainer, null, parentComponent, suspense, isSVG, optimized); | |
// now check if we have encountered any async deps | |
if (suspense.deps > 0) { | |
// mount the fallback tree | |
patch(null, fallback, container, anchor, parentComponent, null, // fallback tree will not have suspense context | |
isSVG, optimized); | |
n2.el = fallback.el; | |
} | |
else { | |
// Suspense has no async deps. Just resolve. | |
suspense.resolve(); | |
} | |
} | |
function patchSuspense(n1, n2, container, anchor, parentComponent, isSVG, optimized, { patch }) { | |
const suspense = (n2.suspense = n1.suspense); | |
suspense.vnode = n2; | |
const { content, fallback } = normalizeSuspenseChildren(n2); | |
const oldSubTree = suspense.subTree; | |
const oldFallbackTree = suspense.fallbackTree; | |
if (!suspense.isResolved) { | |
patch(oldSubTree, content, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, optimized); | |
if (suspense.deps > 0) { | |
// still pending. patch the fallback tree. | |
patch(oldFallbackTree, fallback, container, anchor, parentComponent, null, // fallback tree will not have suspense context | |
isSVG, optimized); | |
n2.el = fallback.el; | |
} | |
// If deps somehow becomes 0 after the patch it means the patch caused an | |
// async dep component to unmount and removed its dep. It will cause the | |
// suspense to resolve and we don't need to do anything here. | |
} | |
else { | |
// just normal patch inner content as a fragment | |
patch(oldSubTree, content, container, anchor, parentComponent, suspense, isSVG, optimized); | |
n2.el = content.el; | |
} | |
suspense.subTree = content; | |
suspense.fallbackTree = fallback; | |
} | |
function createSuspenseBoundary(vnode, parent, parentComponent, container, hiddenContainer, anchor, isSVG, optimized, rendererInternals) { | |
const { patch, move, unmount, next, options: { parentNode } } = rendererInternals; | |
const suspense = { | |
vnode, | |
parent, | |
parentComponent, | |
isSVG, | |
optimized, | |
container, | |
hiddenContainer, | |
anchor, | |
deps: 0, | |
subTree: null, | |
fallbackTree: null, | |
isResolved: false, | |
isUnmounted: false, | |
effects: [], | |
resolve() { | |
{ | |
if (suspense.isResolved) { | |
throw new Error(`resolveSuspense() is called on an already resolved suspense boundary.`); | |
} | |
if (suspense.isUnmounted) { | |
throw new Error(`resolveSuspense() is called on an already unmounted suspense boundary.`); | |
} | |
} | |
const { vnode, subTree, fallbackTree, effects, parentComponent, container } = suspense; | |
// this is initial anchor on mount | |
let { anchor } = suspense; | |
// unmount fallback tree | |
if (fallbackTree.el) { | |
// if the fallback tree was mounted, it may have been moved | |
// as part of a parent suspense. get the latest anchor for insertion | |
anchor = next(fallbackTree); | |
unmount(fallbackTree, parentComponent, suspense, true); | |
} | |
// move content from off-dom container to actual container | |
move(subTree, container, anchor); | |
const el = (vnode.el = subTree.el); | |
// suspense as the root node of a component... | |
if (parentComponent && parentComponent.subTree === vnode) { | |
parentComponent.vnode.el = el; | |
updateHOCHostEl(parentComponent, el); | |
} | |
// check if there is a pending parent suspense | |
let parent = suspense.parent; | |
let hasUnresolvedAncestor = false; | |
while (parent) { | |
if (!parent.isResolved) { | |
// found a pending parent suspense, merge buffered post jobs | |
// into that parent | |
parent.effects.push(...effects); | |
hasUnresolvedAncestor = true; | |
break; | |
} | |
parent = parent.parent; | |
} | |
// no pending parent suspense, flush all jobs | |
if (!hasUnresolvedAncestor) { | |
queuePostFlushCb(effects); | |
} | |
suspense.isResolved = true; | |
// invoke @resolve event | |
const onResolve = vnode.props && vnode.props.onResolve; | |
if (isFunction(onResolve)) { | |
onResolve(); | |
} | |
}, | |
recede() { | |
suspense.isResolved = false; | |
const { vnode, subTree, fallbackTree, parentComponent, container, hiddenContainer, isSVG, optimized } = suspense; | |
// move content tree back to the off-dom container | |
const anchor = next(subTree); | |
move(subTree, hiddenContainer, null); | |
// remount the fallback tree | |
patch(null, fallbackTree, container, anchor, parentComponent, null, // fallback tree will not have suspense context | |
isSVG, optimized); | |
const el = (vnode.el = fallbackTree.el); | |
// suspense as the root node of a component... | |
if (parentComponent && parentComponent.subTree === vnode) { | |
parentComponent.vnode.el = el; | |
updateHOCHostEl(parentComponent, el); | |
} | |
// invoke @recede event | |
const onRecede = vnode.props && vnode.props.onRecede; | |
if (isFunction(onRecede)) { | |
onRecede(); | |
} | |
}, | |
move(container, anchor) { | |
move(suspense.isResolved ? suspense.subTree : suspense.fallbackTree, container, anchor); | |
suspense.container = container; | |
}, | |
next() { | |
return next(suspense.isResolved ? suspense.subTree : suspense.fallbackTree); | |
}, | |
registerDep(instance, setupRenderEffect) { | |
// suspense is already resolved, need to recede. | |
// use queueJob so it's handled synchronously after patching the current | |
// suspense tree | |
if (suspense.isResolved) { | |
queueJob(() => { | |
suspense.recede(); | |
}); | |
} | |
suspense.deps++; | |
instance | |
.asyncDep.catch(err => { | |
handleError(err, instance, 0 /* SETUP_FUNCTION */); | |
}) | |
.then(asyncSetupResult => { | |
// retry when the setup() promise resolves. | |
// component may have been unmounted before resolve. | |
if (instance.isUnmounted || suspense.isUnmounted) { | |
return; | |
} | |
suspense.deps--; | |
// retry from this component | |
instance.asyncResolved = true; | |
const { vnode } = instance; | |
{ | |
pushWarningContext(vnode); | |
} | |
handleSetupResult(instance, asyncSetupResult, suspense); | |
setupRenderEffect(instance, parentComponent, suspense, vnode, | |
// component may have been moved before resolve | |
parentNode(instance.subTree.el), next(instance.subTree), isSVG); | |
updateHOCHostEl(instance, vnode.el); | |
{ | |
popWarningContext(); | |
} | |
if (suspense.deps === 0) { | |
suspense.resolve(); | |
} | |
}); | |
}, | |
unmount(parentSuspense, doRemove) { | |
suspense.isUnmounted = true; | |
unmount(suspense.subTree, parentComponent, parentSuspense, doRemove); | |
if (!suspense.isResolved) { | |
unmount(suspense.fallbackTree, parentComponent, parentSuspense, doRemove); | |
} | |
} | |
}; | |
return suspense; | |
} | |
function normalizeSuspenseChildren(vnode) { | |
const { shapeFlag, children } = vnode; | |
if (shapeFlag & 32 /* SLOTS_CHILDREN */) { | |
const { default: d, fallback } = children; | |
return { | |
content: normalizeVNode(isFunction(d) ? d() : d), | |
fallback: normalizeVNode(isFunction(fallback) ? fallback() : fallback) | |
}; | |
} | |
else { | |
return { | |
content: normalizeVNode(children), | |
fallback: normalizeVNode(null) | |
}; | |
} | |
} | |
function queueEffectWithSuspense(fn, suspense) { | |
if (suspense !== null && !suspense.isResolved) { | |
if (isArray(fn)) { | |
suspense.effects.push(...fn); | |
} | |
else { | |
suspense.effects.push(fn); | |
} | |
} | |
else { | |
queuePostFlushCb(fn); | |
} | |
} | |
function createDevEffectOptions(instance) { | |
return { | |
scheduler: queueJob, | |
onTrack: instance.rtc ? e => invokeHooks(instance.rtc, e) : void 0, | |
onTrigger: instance.rtg ? e => invokeHooks(instance.rtg, e) : void 0 | |
}; | |
} | |
function isSameType$1(n1, n2) { | |
return n1.type === n2.type && n1.key === n2.key; | |
} | |
function invokeHooks(hooks, arg) { | |
for (let i = 0; i < hooks.length; i++) { | |
hooks[i](arg); | |
} | |
} | |
const queuePostRenderEffect = queueEffectWithSuspense | |
; | |
/** | |
* The createRenderer function accepts two generic arguments: | |
* HostNode and HostElement, corresponding to Node and Element types in the | |
* host environment. For example, for runtime-dom, HostNode would be the DOM | |
* `Node` interface and HostElement would be the DOM `Element` interface. | |
* | |
* Custom renderers can pass in the platform specific types like this: | |
* | |
* ``` js | |
* const { render, createApp } = createRenderer<Node, Element>({ | |
* patchProp, | |
* ...nodeOps | |
* }) | |
* ``` | |
*/ | |
function createRenderer(options) { | |
const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, querySelector: hostQuerySelector } = options; | |
const internals = { | |
patch, | |
unmount, | |
move, | |
next: getNextHostNode, | |
options | |
}; | |
function patch(n1, // null means this is a mount | |
n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) { | |
// patching & not same type, unmount old tree | |
if (n1 != null && !isSameType$1(n1, n2)) { | |
anchor = getNextHostNode(n1); | |
unmount(n1, parentComponent, parentSuspense, true); | |
n1 = null; | |
} | |
const { type, shapeFlag } = n2; | |
switch (type) { | |
case Text: | |
processText(n1, n2, container, anchor); | |
break; | |
case Comment: | |
processCommentNode(n1, n2, container, anchor); | |
break; | |
case Fragment: | |
processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
break; | |
case Portal: | |
processPortal(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
break; | |
default: | |
if (shapeFlag & 1 /* ELEMENT */) { | |
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
else if (shapeFlag & 6 /* COMPONENT */) { | |
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
else if ( shapeFlag & 64 /* SUSPENSE */) { | |
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals); | |
} | |
else { | |
warn('Invalid HostVNode type:', n2.type, `(${typeof n2.type})`); | |
} | |
} | |
} | |
function processText(n1, n2, container, anchor) { | |
if (n1 == null) { | |
hostInsert((n2.el = hostCreateText(n2.children)), container, anchor); | |
} | |
else { | |
const el = (n2.el = n1.el); | |
if (n2.children !== n1.children) { | |
hostSetText(el, n2.children); | |
} | |
} | |
} | |
function processCommentNode(n1, n2, container, anchor) { | |
if (n1 == null) { | |
hostInsert((n2.el = hostCreateComment(n2.children || '')), container, anchor); | |
} | |
else { | |
// there's no support for dynamic comments | |
n2.el = n1.el; | |
} | |
} | |
function processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { | |
if (n1 == null) { | |
mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
else { | |
patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
if (n2.ref !== null && parentComponent !== null) { | |
setRef(n2.ref, n1 && n1.ref, parentComponent, n2.el); | |
} | |
} | |
function mountElement(vnode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { | |
const tag = vnode.type; | |
isSVG = isSVG || tag === 'svg'; | |
const el = (vnode.el = hostCreateElement(tag, isSVG)); | |
const { props, shapeFlag } = vnode; | |
if (props != null) { | |
for (const key in props) { | |
if (isReservedProp(key)) | |
continue; | |
hostPatchProp(el, key, props[key], null, isSVG); | |
} | |
if (props.onVnodeBeforeMount != null) { | |
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode); | |
} | |
} | |
if (shapeFlag & 8 /* TEXT_CHILDREN */) { | |
hostSetElementText(el, vnode.children); | |
} | |
else if (shapeFlag & 16 /* ARRAY_CHILDREN */) { | |
mountChildren(vnode.children, el, null, parentComponent, parentSuspense, isSVG, optimized || vnode.dynamicChildren !== null); | |
} | |
hostInsert(el, container, anchor); | |
if (props != null && props.onVnodeMounted != null) { | |
queuePostRenderEffect(() => { | |
invokeDirectiveHook(props.onVnodeMounted, parentComponent, vnode); | |
}, parentSuspense); | |
} | |
} | |
function mountChildren(children, container, anchor, parentComponent, parentSuspense, isSVG, optimized, start = 0) { | |
for (let i = start; i < children.length; i++) { | |
const child = optimized | |
? children[i] | |
: (children[i] = normalizeVNode(children[i])); | |
patch(null, child, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
} | |
function patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized) { | |
const el = (n2.el = n1.el); | |
const { patchFlag, dynamicChildren } = n2; | |
const oldProps = (n1 && n1.props) || EMPTY_OBJ; | |
const newProps = n2.props || EMPTY_OBJ; | |
if (newProps.onVnodeBeforeUpdate != null) { | |
invokeDirectiveHook(newProps.onVnodeBeforeUpdate, parentComponent, n2, n1); | |
} | |
if (patchFlag > 0) { | |
// the presence of a patchFlag means this element's render code was | |
// generated by the compiler and can take the fast path. | |
// in this path old node and new node are guaranteed to have the same shape | |
// (i.e. at the exact same position in the source template) | |
if (patchFlag & 16 /* FULL_PROPS */) { | |
// element props contain dynamic keys, full diff needed | |
patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG); | |
} | |
else { | |
// class | |
// this flag is matched when the element has dynamic class bindings. | |
if (patchFlag & 2 /* CLASS */) { | |
if (oldProps.class !== newProps.class) { | |
hostPatchProp(el, 'class', newProps.class, null, isSVG); | |
} | |
} | |
// style | |
// this flag is matched when the element has dynamic style bindings | |
if (patchFlag & 4 /* STYLE */) { | |
hostPatchProp(el, 'style', newProps.style, oldProps.style, isSVG); | |
} | |
// props | |
// This flag is matched when the element has dynamic prop/attr bindings | |
// other than class and style. The keys of dynamic prop/attrs are saved for | |
// faster iteration. | |
// Note dynamic keys like :[foo]="bar" will cause this optimization to | |
// bail out and go through a full diff because we need to unset the old key | |
if (patchFlag & 8 /* PROPS */) { | |
// if the flag is present then dynamicProps must be non-null | |
const propsToUpdate = n2.dynamicProps; | |
for (let i = 0; i < propsToUpdate.length; i++) { | |
const key = propsToUpdate[i]; | |
const prev = oldProps[key]; | |
const next = newProps[key]; | |
if (prev !== next) { | |
hostPatchProp(el, key, next, prev, isSVG, n1.children, parentComponent, parentSuspense, unmountChildren); | |
} | |
} | |
} | |
} | |
// text | |
// This flag is matched when the element has only dynamic text children. | |
// this flag is terminal (i.e. skips children diffing). | |
if (patchFlag & 1 /* TEXT */) { | |
if (n1.children !== n2.children) { | |
hostSetElementText(el, n2.children); | |
} | |
return; // terminal | |
} | |
} | |
else if (!optimized && dynamicChildren == null) { | |
// unoptimized, full diff | |
patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG); | |
} | |
if (dynamicChildren != null) { | |
patchBlockChildren(n1.dynamicChildren, dynamicChildren, el, parentComponent, parentSuspense, isSVG); | |
} | |
else if (!optimized) { | |
// full diff | |
patchChildren(n1, n2, el, null, parentComponent, parentSuspense, isSVG); | |
} | |
if (newProps.onVnodeUpdated != null) { | |
queuePostRenderEffect(() => { | |
invokeDirectiveHook(newProps.onVnodeUpdated, parentComponent, n2, n1); | |
}, parentSuspense); | |
} | |
} | |
// The fast path for blocks. | |
function patchBlockChildren(oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, isSVG) { | |
for (let i = 0; i < newChildren.length; i++) { | |
const oldVNode = oldChildren[i]; | |
patch(oldVNode, newChildren[i], | |
// in the case of a Fragment, we need to provide the actual parent | |
// of the Fragment itself so it can move its children. In other cases, | |
// the parent container is not actually used so we just pass the | |
// block element here to avoid a DOM parentNode call. | |
oldVNode.type === Fragment | |
? hostParentNode(oldVNode.el) | |
: fallbackContainer, null, parentComponent, parentSuspense, isSVG, true); | |
} | |
} | |
function patchProps(el, vnode, oldProps, newProps, parentComponent, parentSuspense, isSVG) { | |
if (oldProps !== newProps) { | |
for (const key in newProps) { | |
if (isReservedProp(key)) | |
continue; | |
const next = newProps[key]; | |
const prev = oldProps[key]; | |
if (next !== prev) { | |
hostPatchProp(el, key, next, prev, isSVG, vnode.children, parentComponent, parentSuspense, unmountChildren); | |
} | |
} | |
if (oldProps !== EMPTY_OBJ) { | |
for (const key in oldProps) { | |
if (isReservedProp(key)) | |
continue; | |
if (!(key in newProps)) { | |
hostPatchProp(el, key, null, null, isSVG, vnode.children, parentComponent, parentSuspense, unmountChildren); | |
} | |
} | |
} | |
} | |
} | |
let devFragmentID = 0; | |
function processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { | |
const fragmentStartAnchor = (n2.el = n1 | |
? n1.el | |
: hostCreateComment( `fragment-${devFragmentID}-start` )); | |
const fragmentEndAnchor = (n2.anchor = n1 | |
? n1.anchor | |
: hostCreateComment( `fragment-${devFragmentID}-end` )); | |
{ | |
devFragmentID++; | |
} | |
if (n1 == null) { | |
hostInsert(fragmentStartAnchor, container, anchor); | |
hostInsert(fragmentEndAnchor, container, anchor); | |
// a fragment can only have array children | |
// since they are either generated by the compiler, or implicitly created | |
// from arrays. | |
mountChildren(n2.children, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
else { | |
patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
} | |
function processPortal(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { | |
const targetSelector = n2.props && n2.props.target; | |
const { patchFlag, shapeFlag, children } = n2; | |
if (n1 == null) { | |
const target = (n2.target = isString(targetSelector) | |
? hostQuerySelector(targetSelector) | |
: targetSelector); | |
if (target != null) { | |
if (shapeFlag & 8 /* TEXT_CHILDREN */) { | |
hostSetElementText(target, children); | |
} | |
else if (shapeFlag & 16 /* ARRAY_CHILDREN */) { | |
mountChildren(children, target, null, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
} | |
else { | |
warn('Invalid Portal target on mount:', target, `(${typeof target})`); | |
} | |
} | |
else { | |
// update content | |
const target = (n2.target = n1.target); | |
if (patchFlag === 1 /* TEXT */) { | |
hostSetElementText(target, children); | |
} | |
else if (n2.dynamicChildren) { | |
// fast path when the portal happens to be a block root | |
patchBlockChildren(n1.dynamicChildren, n2.dynamicChildren, container, parentComponent, parentSuspense, isSVG); | |
} | |
else if (!optimized) { | |
patchChildren(n1, n2, target, null, parentComponent, parentSuspense, isSVG); | |
} | |
// target changed | |
if (targetSelector !== (n1.props && n1.props.target)) { | |
const nextTarget = (n2.target = isString(targetSelector) | |
? hostQuerySelector(targetSelector) | |
: targetSelector); | |
if (nextTarget != null) { | |
// move content | |
if (shapeFlag & 8 /* TEXT_CHILDREN */) { | |
hostSetElementText(target, ''); | |
hostSetElementText(nextTarget, children); | |
} | |
else if (shapeFlag & 16 /* ARRAY_CHILDREN */) { | |
for (let i = 0; i < children.length; i++) { | |
move(children[i], nextTarget, null); | |
} | |
} | |
} | |
else { | |
warn('Invalid Portal target on update:', target, `(${typeof target})`); | |
} | |
} | |
} | |
// insert an empty node as the placeholder for the portal | |
processCommentNode(n1, n2, container, anchor); | |
} | |
function processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { | |
if (n1 == null) { | |
if (n2.shapeFlag & 256 /* COMPONENT_KEPT_ALIVE */) { | |
parentComponent.sink.activate(n2, container, anchor); | |
} | |
else { | |
mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG); | |
} | |
} | |
else { | |
const instance = (n2.component = n1.component); | |
if (shouldUpdateComponent(n1, n2, optimized)) { | |
if ( | |
instance.asyncDep && | |
!instance.asyncResolved) { | |
// async & still pending - just update props and slots | |
// since the component's reactive effect for render isn't set-up yet | |
{ | |
pushWarningContext(n2); | |
} | |
updateComponentPreRender(instance, n2); | |
{ | |
popWarningContext(); | |
} | |
return; | |
} | |
else { | |
// normal update | |
instance.next = n2; | |
// instance.update is the reactive effect runner. | |
instance.update(); | |
} | |
} | |
else { | |
// no update needed. just copy over properties | |
n2.component = n1.component; | |
n2.el = n1.el; | |
} | |
} | |
if (n2.ref !== null && parentComponent !== null) { | |
if ( !(n2.shapeFlag & 4 /* STATEFUL_COMPONENT */)) { | |
pushWarningContext(n2); | |
warn(`Functional components do not support "ref" because they do not ` + | |
`have instances.`); | |
popWarningContext(); | |
} | |
setRef(n2.ref, n1 && n1.ref, parentComponent, n2.component.renderProxy); | |
} | |
} | |
function mountComponent(initialVNode, container, anchor, parentComponent, parentSuspense, isSVG) { | |
const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent)); | |
{ | |
pushWarningContext(initialVNode); | |
} | |
const Comp = initialVNode.type; | |
// inject renderer internals for keepAlive | |
if (Comp.__isKeepAlive) { | |
const sink = instance.sink; | |
sink.renderer = internals; | |
sink.parentSuspense = parentSuspense; | |
} | |
// resolve props and slots for setup context | |
const propsOptions = Comp.props; | |
resolveProps(instance, initialVNode.props, propsOptions); | |
resolveSlots(instance, initialVNode.children); | |
// setup stateful logic | |
if (initialVNode.shapeFlag & 4 /* STATEFUL_COMPONENT */) { | |
setupStatefulComponent(instance, parentSuspense); | |
} | |
// setup() is async. This component relies on async logic to be resolved | |
// before proceeding | |
if ( instance.asyncDep) { | |
if (!parentSuspense) { | |
warn('async setup() is used without a suspense boundary!'); | |
return; | |
} | |
parentSuspense.registerDep(instance, setupRenderEffect); | |
// give it a placeholder | |
const placeholder = (instance.subTree = createVNode(Comment)); | |
processCommentNode(null, placeholder, container, anchor); | |
initialVNode.el = placeholder.el; | |
return; | |
} | |
setupRenderEffect(instance, parentComponent, parentSuspense, initialVNode, container, anchor, isSVG); | |
{ | |
popWarningContext(); | |
} | |
} | |
function setupRenderEffect(instance, parentComponent, parentSuspense, initialVNode, container, anchor, isSVG) { | |
// create reactive effect for rendering | |
let mounted = false; | |
instance.update = effect(function componentEffect() { | |
if (!mounted) { | |
const subTree = (instance.subTree = renderComponentRoot(instance)); | |
// beforeMount hook | |
if (instance.bm !== null) { | |
invokeHooks(instance.bm); | |
} | |
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG); | |
initialVNode.el = subTree.el; | |
// mounted hook | |
if (instance.m !== null) { | |
queuePostRenderEffect(instance.m, parentSuspense); | |
} | |
// activated hook for keep-alive roots. | |
if (instance.a !== null && | |
instance.vnode.shapeFlag & 128 /* COMPONENT_SHOULD_KEEP_ALIVE */) { | |
queuePostRenderEffect(instance.a, parentSuspense); | |
} | |
mounted = true; | |
} | |
else { | |
// updateComponent | |
// This is triggered by mutation of component's own state (next: null) | |
// OR parent calling processComponent (next: HostVNode) | |
const { next } = instance; | |
{ | |
pushWarningContext(next || instance.vnode); | |
} | |
if (next !== null) { | |
updateComponentPreRender(instance, next); | |
} | |
const prevTree = instance.subTree; | |
const nextTree = (instance.subTree = renderComponentRoot(instance)); | |
// beforeUpdate hook | |
if (instance.bu !== null) { | |
invokeHooks(instance.bu); | |
} | |
// reset refs | |
// only needed if previous patch had refs | |
if (instance.refs !== EMPTY_OBJ) { | |
instance.refs = {}; | |
} | |
patch(prevTree, nextTree, | |
// parent may have changed if it's in a portal | |
hostParentNode(prevTree.el), | |
// anchor may have changed if it's in a fragment | |
getNextHostNode(prevTree), instance, parentSuspense, isSVG); | |
instance.vnode.el = nextTree.el; | |
if (next === null) { | |
// self-triggered update. In case of HOC, update parent component | |
// vnode el. HOC is indicated by parent instance's subTree pointing | |
// to child component's vnode | |
updateHOCHostEl(instance, nextTree.el); | |
} | |
// updated hook | |
if (instance.u !== null) { | |
queuePostRenderEffect(instance.u, parentSuspense); | |
} | |
{ | |
popWarningContext(); | |
} | |
} | |
}, createDevEffectOptions(instance) ); | |
} | |
function updateComponentPreRender(instance, nextVNode) { | |
nextVNode.component = instance; | |
instance.vnode = nextVNode; | |
instance.next = null; | |
resolveProps(instance, nextVNode.props, nextVNode.type.props); | |
resolveSlots(instance, nextVNode.children); | |
} | |
function patchChildren(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized = false) { | |
const c1 = n1 && n1.children; | |
const prevShapeFlag = n1 ? n1.shapeFlag : 0; | |
const c2 = n2.children; | |
const { patchFlag, shapeFlag } = n2; | |
if (patchFlag === -1 /* BAIL */) { | |
optimized = false; | |
} | |
// fast path | |
if (patchFlag > 0) { | |
if (patchFlag & 64 /* KEYED_FRAGMENT */) { | |
// this could be either fully-keyed or mixed (some keyed some not) | |
// presence of patchFlag means children are guaranteed to be arrays | |
patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
return; | |
} | |
else if (patchFlag & 128 /* UNKEYED_FRAGMENT */) { | |
// unkeyed | |
patchUnkeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
return; | |
} | |
} | |
// children has 3 possibilities: text, array or no children. | |
if (shapeFlag & 8 /* TEXT_CHILDREN */) { | |
// text children fast path | |
if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) { | |
unmountChildren(c1, parentComponent, parentSuspense); | |
} | |
if (c2 !== c1) { | |
hostSetElementText(container, c2); | |
} | |
} | |
else { | |
if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) { | |
// prev children was array | |
if (shapeFlag & 16 /* ARRAY_CHILDREN */) { | |
// two arrays, cannot assume anything, do full diff | |
patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
else { | |
// no new children, just unmount old | |
unmountChildren(c1, parentComponent, parentSuspense, true); | |
} | |
} | |
else { | |
// prev children was text OR null | |
// new children is array OR null | |
if (prevShapeFlag & 8 /* TEXT_CHILDREN */) { | |
hostSetElementText(container, ''); | |
} | |
// mount new if array | |
if (shapeFlag & 16 /* ARRAY_CHILDREN */) { | |
mountChildren(c2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
} | |
} | |
} | |
function patchUnkeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { | |
c1 = c1 || EMPTY_ARR; | |
c2 = c2 || EMPTY_ARR; | |
const oldLength = c1.length; | |
const newLength = c2.length; | |
const commonLength = Math.min(oldLength, newLength); | |
let i; | |
for (i = 0; i < commonLength; i++) { | |
const nextChild = optimized | |
? c2[i] | |
: (c2[i] = normalizeVNode(c2[i])); | |
patch(c1[i], nextChild, container, null, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
if (oldLength > newLength) { | |
// remove old | |
unmountChildren(c1, parentComponent, parentSuspense, true, commonLength); | |
} | |
else { | |
// mount new | |
mountChildren(c2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, commonLength); | |
} | |
} | |
// can be all-keyed or mixed | |
function patchKeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) { | |
let i = 0; | |
const l2 = c2.length; | |
let e1 = c1.length - 1; // prev ending index | |
let e2 = l2 - 1; // next ending index | |
// 1. sync from start | |
// (a b) c | |
// (a b) d e | |
while (i <= e1 && i <= e2) { | |
const n1 = c1[i]; | |
const n2 = optimized | |
? c2[i] | |
: (c2[i] = normalizeVNode(c2[i])); | |
if (isSameType$1(n1, n2)) { | |
patch(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
else { | |
break; | |
} | |
i++; | |
} | |
// 2. sync from end | |
// a (b c) | |
// d e (b c) | |
while (i <= e1 && i <= e2) { | |
const n1 = c1[e1]; | |
const n2 = optimized | |
? c2[i] | |
: (c2[e2] = normalizeVNode(c2[e2])); | |
if (isSameType$1(n1, n2)) { | |
patch(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized); | |
} | |
else { | |
break; | |
} | |
e1--; | |
e2--; | |
} | |
// 3. common sequence + mount | |
// (a b) | |
// (a b) c | |
// i = 2, e1 = 1, e2 = 2 | |
// (a b) | |
// c (a b) | |
// i = 0, e1 = -1, e2 = 0 | |
if (i > e1) { | |
if (i <= e2) { | |
const nextPos = e2 + 1; | |
const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor; | |
while (i <= e2) { | |
patch(null, optimized ? c2[i] : (c2[i] = normalizeVNode(c2[i])), container, anchor, parentComponent, parentSuspense, isSVG); | |
i++; | |
} | |
} | |
} | |
// 4. common sequence + unmount | |
// (a b) c | |
// (a b) | |
// i = 2, e1 = 2, e2 = 1 | |
// a (b c) | |
// (b c) | |
// i = 0, e1 = 0, e2 = -1 | |
else if (i > e2) { | |
while (i <= e1) { | |
unmount(c1[i], parentComponent, parentSuspense, true); | |
i++; | |
} | |
} | |
// 5. unknown sequence | |
// [i ... e1 + 1]: a b [c d e] f g | |
// [i ... e2 + 1]: a b [e d c h] f g | |
// i = 2, e1 = 4, e2 = 5 | |
else { | |
const s1 = i; // prev starting index | |
const s2 = i; // next starting index | |
// 5.1 build key:index map for newChildren | |
const keyToNewIndexMap = new Map(); | |
for (i = s2; i <= e2; i++) { | |
const nextChild = optimized | |
? c2[i] | |
: (c2[i] = normalizeVNode(c2[i])); | |
if (nextChild.key != null) { | |
if ( keyToNewIndexMap.has(nextChild.key)) { | |
warn(`Duplicate keys found during update:`, JSON.stringify(nextChild.key), `Make sure keys are unique.`); | |
} | |
keyToNewIndexMap.set(nextChild.key, i); | |
} | |
} | |
// 5.2 loop through old children left to be patched and try to patch | |
// matching nodes & remove nodes that are no longer present | |
let j; | |
let patched = 0; | |
const toBePatched = e2 - s2 + 1; | |
let moved = false; | |
// used to track whether any node has moved | |
let maxNewIndexSoFar = 0; | |
// works as Map<newIndex, oldIndex> | |
// Note that oldIndex is offset by +1 | |
// and oldIndex = 0 is a special value indicating the new node has | |
// no corresponding old node. | |
// used for determining longest stable subsequence | |
const newIndexToOldIndexMap = new Array(toBePatched); | |
for (i = 0; i < toBePatched; i++) | |
newIndexToOldIndexMap[i] = 0; | |
for (i = s1; i <= e1; i++) { | |
const prevChild = c1[i]; | |
if (patched >= toBePatched) { | |
// all new children have been patched so this can only be a removal | |
unmount(prevChild, parentComponent, parentSuspense, true); | |
continue; | |
} | |
let newIndex; | |
if (prevChild.key != null) { | |
newIndex = keyToNewIndexMap.get(prevChild.key); | |
} | |
else { | |
// key-less node, try to locate a key-less node of the same type | |
for (j = s2; j <= e2; j++) { | |
if (newIndexToOldIndexMap[j - s2] === 0 && | |
isSameType$1(prevChild, c2[j])) { | |
newIndex = j; | |
break; | |
} | |
} | |
} | |
if (newIndex === undefined) { | |
unmount(prevChild, parentComponent, parentSuspense, true); | |
} | |
else { | |
newIndexToOldIndexMap[newIndex - s2] = i + 1; | |
if (newIndex >= maxNewIndexSoFar) { | |
maxNewIndexSoFar = newIndex; | |
} | |
else { | |
moved = true; | |
} | |
patch(prevChild, c2[newIndex], container, null, parentComponent, parentSuspense, isSVG, optimized); | |
patched++; | |
} | |
} | |
// 5.3 move and mount | |
// generate longest stable subsequence only when nodes have moved | |
const increasingNewIndexSequence = moved | |
? getSequence(newIndexToOldIndexMap) | |
: EMPTY_ARR; | |
j = increasingNewIndexSequence.length - 1; | |
// looping backwards so that we can use last patched node as anchor | |
for (i = toBePatched - 1; i >= 0; i--) { | |
const nextIndex = s2 + i; | |
const nextChild = c2[nextIndex]; | |
const anchor = nextIndex + 1 < l2 | |
? c2[nextIndex + 1].el | |
: parentAnchor; | |
if (newIndexToOldIndexMap[i] === 0) { | |
// mount new | |
patch(null, nextChild, container, anchor, parentComponent, parentSuspense, isSVG); | |
} | |
else if (moved) { | |
// move if: | |
// There is no stable subsequence (e.g. a reverse) | |
// OR current node is not among the stable sequence | |
if (j < 0 || i !== increasingNewIndexSequence[j]) { | |
move(nextChild, container, anchor); | |
} | |
else { | |
j--; | |
} | |
} | |
} | |
} | |
} | |
function move(vnode, container, anchor) { | |
if (vnode.shapeFlag & 6 /* COMPONENT */) { | |
move(vnode.component.subTree, container, anchor); | |
return; | |
} | |
if ( vnode.shapeFlag & 64 /* SUSPENSE */) { | |
vnode.suspense.move(container, anchor); | |
return; | |
} | |
if (vnode.type === Fragment) { | |
hostInsert(vnode.el, container, anchor); | |
const children = vnode.children; | |
for (let i = 0; i < children.length; i++) { | |
move(children[i], container, anchor); | |
} | |
hostInsert(vnode.anchor, container, anchor); | |
} | |
else { | |
hostInsert(vnode.el, container, anchor); | |
} | |
} | |
function unmount(vnode, parentComponent, parentSuspense, doRemove) { | |
const { props, ref, type, children, dynamicChildren, shapeFlag, anchor } = vnode; | |
// unset ref | |
if (ref !== null && parentComponent !== null) { | |
setRef(ref, null, parentComponent, null); | |
} | |
if (shapeFlag & 6 /* COMPONENT */) { | |
if (shapeFlag & 128 /* COMPONENT_SHOULD_KEEP_ALIVE */) { | |
parentComponent.sink.deactivate(vnode); | |
} | |
else { | |
unmountComponent(vnode.component, parentSuspense, doRemove); | |
} | |
return; | |
} | |
if ( shapeFlag & 64 /* SUSPENSE */) { | |
vnode.suspense.unmount(parentSuspense, doRemove); | |
return; | |
} | |
if (props != null && props.onVnodeBeforeUnmount != null) { | |
invokeDirectiveHook(props.onVnodeBeforeUnmount, parentComponent, vnode); | |
} | |
const shouldRemoveChildren = type === Fragment && doRemove; | |
if (dynamicChildren != null) { | |
unmountChildren(dynamicChildren, parentComponent, parentSuspense, shouldRemoveChildren); | |
} | |
else if (shapeFlag & 16 /* ARRAY_CHILDREN */) { | |
unmountChildren(children, parentComponent, parentSuspense, shouldRemoveChildren); | |
} | |
if (doRemove) { | |
hostRemove(vnode.el); | |
if (anchor != null) | |
hostRemove(anchor); | |
} | |
if (props != null && props.onVnodeUnmounted != null) { | |
queuePostRenderEffect(() => { | |
invokeDirectiveHook(props.onVnodeUnmounted, parentComponent, vnode); | |
}, parentSuspense); | |
} | |
} | |
function unmountComponent(instance, parentSuspense, doRemove) { | |
const { bum, effects, update, subTree, um, da, isDeactivated } = instance; | |
// beforeUnmount hook | |
if (bum !== null) { | |
invokeHooks(bum); | |
} | |
if (effects !== null) { | |
for (let i = 0; i < effects.length; i++) { | |
stop(effects[i]); | |
} | |
} | |
// update may be null if a component is unmounted before its async | |
// setup has resolved. | |
if (update !== null) { | |
stop(update); | |
unmount(subTree, instance, parentSuspense, doRemove); | |
} | |
// unmounted hook | |
if (um !== null) { | |
queuePostRenderEffect(um, parentSuspense); | |
} | |
// deactivated hook | |
if (da !== null && | |
!isDeactivated && | |
instance.vnode.shapeFlag & 128 /* COMPONENT_SHOULD_KEEP_ALIVE */) { | |
queuePostRenderEffect(da, parentSuspense); | |
} | |
queuePostFlushCb(() => { | |
instance.isUnmounted = true; | |
}); | |
// A component with async dep inside a pending suspense is unmounted before | |
// its async dep resolves. This should remove the dep from the suspense, and | |
// cause the suspense to resolve immediately if that was the last dep. | |
if ( | |
parentSuspense !== null && | |
!parentSuspense.isResolved && | |
!parentSuspense.isUnmounted && | |
instance.asyncDep !== null && | |
!instance.asyncResolved) { | |
parentSuspense.deps--; | |
if (parentSuspense.deps === 0) { | |
parentSuspense.resolve(); | |
} | |
} | |
} | |
function unmountChildren(children, parentComponent, parentSuspense, doRemove, start = 0) { | |
for (let i = start; i < children.length; i++) { | |
unmount(children[i], parentComponent, parentSuspense, doRemove); | |
} | |
} | |
function getNextHostNode(vnode) { | |
if (vnode.shapeFlag & 6 /* COMPONENT */) { | |
return getNextHostNode(vnode.component.subTree); | |
} | |
if ( vnode.shapeFlag & 64 /* SUSPENSE */) { | |
return vnode.suspense.next(); | |
} | |
return hostNextSibling((vnode.anchor || vnode.el)); | |
} | |
function setRef(ref, oldRef, parent, value) { | |
const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs; | |
const renderContext = toRaw(parent.renderContext); | |
// unset old ref | |
if (oldRef !== null && oldRef !== ref) { | |
if (isString(oldRef)) { | |
refs[oldRef] = null; | |
const oldSetupRef = renderContext[oldRef]; | |
if (isRef(oldSetupRef)) { | |
oldSetupRef.value = null; | |
} | |
} | |
else if (isRef(oldRef)) { | |
oldRef.value = null; | |
} | |
} | |
if (isString(ref)) { | |
const setupRef = renderContext[ref]; | |
if (isRef(setupRef)) { | |
setupRef.value = value; | |
} | |
refs[ref] = value; | |
} | |
else if (isRef(ref)) { | |
ref.value = value; | |
} | |
else if (isFunction(ref)) { | |
callWithErrorHandling(ref, parent, 10 /* FUNCTION_REF */, [value, refs]); | |
} | |
else { | |
warn('Invalid template ref type:', value, `(${typeof value})`); | |
} | |
} | |
const render = (vnode, container) => { | |
if (vnode == null) { | |
if (container._vnode) { | |
unmount(container._vnode, null, null, true); | |
} | |
} | |
else { | |
patch(container._vnode || null, vnode, container); | |
} | |
flushPostFlushCbs(); | |
container._vnode = vnode; | |
}; | |
return { | |
render, | |
createApp: createAppAPI(render) | |
}; | |
} | |
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence | |
function getSequence(arr) { | |
const p = arr.slice(); | |
const result = [0]; | |
let i, j, u, v, c; | |
const len = arr.length; | |
for (i = 0; i < len; i++) { | |
const arrI = arr[i]; | |
if (arrI !== 0) { | |
j = result[result.length - 1]; | |
if (arr[j] < arrI) { | |
p[i] = j; | |
result.push(i); | |
continue; | |
} | |
u = 0; | |
v = result.length - 1; | |
while (u < v) { | |
c = ((u + v) / 2) | 0; | |
if (arr[result[c]] < arrI) { | |
u = c + 1; | |
} | |
else { | |
v = c; | |
} | |
} | |
if (arrI < arr[result[u]]) { | |
if (u > 0) { | |
p[i] = result[u - 1]; | |
} | |
result[u] = i; | |
} | |
} | |
} | |
u = result.length; | |
v = result[u - 1]; | |
while (u-- > 0) { | |
result[u] = v; | |
v = p[v]; | |
} | |
return result; | |
} | |
const KeepAliveImpl = { | |
name: `KeepAlive`, | |
// Marker for special handling inside the renderer. We are not using a === | |
// check directly on KeepAlive in the renderer, because importing it directly | |
// would prevent it from being tree-shaken. | |
__isKeepAlive: true, | |
setup(props, { slots }) { | |
const cache = new Map(); | |
const keys = new Set(); | |
let current = null; | |
const instance = getCurrentInstance(); | |
// KeepAlive communicates with the instantiated renderer via the "sink" | |
// where the renderer passes in platform-specific functions, and the | |
// KeepAlivei instance expses activcate/decativate implementations. | |
// The whole point of this is to avoid importing KeepAlive directly in the | |
// renderer to facilitate tree-shaking. | |
const sink = instance.sink; | |
const { renderer: { move, unmount: _unmount, options: { createElement } }, parentSuspense } = sink; | |
const storageContainer = createElement('div'); | |
sink.activate = (vnode, container, anchor) => { | |
move(vnode, container, anchor); | |
queuePostRenderEffect(() => { | |
const component = vnode.component; | |
component.isDeactivated = false; | |
if (component.a !== null) { | |
invokeHooks(component.a); | |
} | |
}, parentSuspense); | |
}; | |
sink.deactivate = (vnode) => { | |
move(vnode, storageContainer, null); | |
queuePostRenderEffect(() => { | |
const component = vnode.component; | |
if (component.da !== null) { | |
invokeHooks(component.da); | |
} | |
component.isDeactivated = true; | |
}, parentSuspense); | |
}; | |
function unmount(vnode) { | |
// reset the shapeFlag so it can be properly unmounted | |
vnode.shapeFlag = 4 /* STATEFUL_COMPONENT */; | |
_unmount(vnode, instance, parentSuspense); | |
} | |
function pruneCache(filter) { | |
cache.forEach((vnode, key) => { | |
const name = getName(vnode.type); | |
if (name && (!filter || !filter(name))) { | |
pruneCacheEntry(key); | |
} | |
}); | |
} | |
function pruneCacheEntry(key) { | |
const cached = cache.get(key); | |
if (!current || cached.type !== current.type) { | |
unmount(cached); | |
} | |
else if (current) { | |
// current active instance should no longer be kept-alive. | |
// we can't unmount it now but it might be later, so reset its flag now. | |
current.shapeFlag = 4 /* STATEFUL_COMPONENT */; | |
} | |
cache.delete(key); | |
keys.delete(key); | |
} | |
watch(() => [props.include, props.exclude], ([include, exclude]) => { | |
include && pruneCache(name => matches(include, name)); | |
exclude && pruneCache(name => matches(exclude, name)); | |
}, { lazy: true }); | |
onBeforeUnmount(() => { | |
cache.forEach(unmount); | |
}); | |
return () => { | |
if (!slots.default) { | |
return null; | |
} | |
const children = slots.default(); | |
let vnode = children[0]; | |
if (children.length > 1) { | |
{ | |
warn(`KeepAlive should contain exactly one component child.`); | |
} | |
current = null; | |
return children; | |
} | |
else if (!isVNode(vnode) || | |
!(vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */)) { | |
current = null; | |
return vnode; | |
} | |
const comp = vnode.type; | |
const name = getName(comp); | |
const { include, exclude, max } = props; | |
if ((include && (!name || !matches(include, name))) || | |
(exclude && name && matches(exclude, name))) { | |
return vnode; | |
} | |
const key = vnode.key == null ? comp : vnode.key; | |
const cached = cache.get(key); | |
// clone vnode if it's reused because we are going to mutate it | |
if (vnode.el) { | |
vnode = cloneVNode(vnode); | |
} | |
cache.set(key, vnode); | |
if (cached) { | |
// copy over mounted state | |
vnode.el = cached.el; | |
vnode.anchor = cached.anchor; | |
vnode.component = cached.component; | |
// avoid vnode being mounted as fresh | |
vnode.shapeFlag |= 256 /* COMPONENT_KEPT_ALIVE */; | |
// make this key the freshest | |
keys.delete(key); | |
keys.add(key); | |
} | |
else { | |
keys.add(key); | |
// prune oldest entry | |
if (max && keys.size > parseInt(max, 10)) { | |
pruneCacheEntry(Array.from(keys)[0]); | |
} | |
} | |
// avoid vnode being unmounted | |
vnode.shapeFlag |= 128 /* COMPONENT_SHOULD_KEEP_ALIVE */; | |
current = vnode; | |
return vnode; | |
}; | |
} | |
}; | |
{ | |
KeepAliveImpl.props = { | |
include: [String, RegExp, Array], | |
exclude: [String, RegExp, Array], | |
max: [String, Number] | |
}; | |
} | |
// export the public type for h/tsx inference | |
const KeepAlive = KeepAliveImpl; | |
function getName(comp) { | |
return comp.displayName || comp.name; | |
} | |
function matches(pattern, name) { | |
if (isArray(pattern)) { | |
return pattern.some((p) => matches(p, name)); | |
} | |
else if (isString(pattern)) { | |
return pattern.split(',').indexOf(name) > -1; | |
} | |
else if (pattern.test) { | |
return pattern.test(name); | |
} | |
/* istanbul ignore next */ | |
return false; | |
} | |
function onActivated(hook, target) { | |
registerKeepAliveHook(hook, "a" /* ACTIVATED */, target); | |
} | |
function onDeactivated(hook, target) { | |
registerKeepAliveHook(hook, "da" /* DEACTIVATED */, target); | |
} | |
function registerKeepAliveHook(hook, type, target = currentInstance) { | |
// cache the deactivate branch check wrapper for injected hooks so the same | |
// hook can be properly deduped by the scheduler. "__wdc" stands for "with | |
// deactivation check". | |
const wrappedHook = hook.__wdc || | |
(hook.__wdc = () => { | |
// only fire the hook if the target instance is NOT in a deactivated branch. | |
let current = target; | |
while (current) { | |
if (current.isDeactivated) { | |
return; | |
} | |
current = current.parent; | |
} | |
hook(); | |
}); | |
injectHook(type, wrappedHook, target); | |
// In addition to registering it on the target instance, we walk up the parent | |
// chain and register it on all ancestor instances that are keep-alive roots. | |
// This avoids the need to walk the entire component tree when invoking these | |
// hooks, and more importantly, avoids the need to track child components in | |
// arrays. | |
if (target) { | |
let current = target.parent; | |
while (current && current.parent) { | |
if (current.parent.type === KeepAliveImpl) { | |
injectToKeepAliveRoot(wrappedHook, type, target, current); | |
} | |
current = current.parent; | |
} | |
} | |
} | |
function injectToKeepAliveRoot(hook, type, target, keepAliveRoot) { | |
injectHook(type, hook, keepAliveRoot, true /* prepend */); | |
onUnmounted(() => { | |
const hooks = keepAliveRoot[type]; | |
hooks.splice(hooks.indexOf(hook), 1); | |
}, target); | |
} | |
function injectHook(type, hook, target = currentInstance, prepend = false) { | |
if (target) { | |
const hooks = target[type] || (target[type] = []); | |
// cache the error handling wrapper for injected hooks so the same hook | |
// can be properly deduped by the scheduler. "__weh" stands for "with error | |
// handling". | |
const wrappedHook = hook.__weh || | |
(hook.__weh = (...args) => { | |
if (target.isUnmounted) { | |
return; | |
} | |
// disable tracking inside all lifecycle hooks | |
// since they can potentially be called inside effects. | |
pauseTracking(); | |
// Set currentInstance during hook invocation. | |
// This assumes the hook does not synchronously trigger other hooks, which | |
// can only be false when the user does something really funky. | |
setCurrentInstance(target); | |
const res = callWithAsyncErrorHandling(hook, target, type, args); | |
setCurrentInstance(null); | |
resumeTracking(); | |
return res; | |
}); | |
if (prepend) { | |
hooks.unshift(wrappedHook); | |
} | |
else { | |
hooks.push(wrappedHook); | |
} | |
} | |
else { | |
const apiName = `on${capitalize(ErrorTypeStrings[type].replace(/ hook$/, ''))}`; | |
warn(`${apiName} is called when there is no active component instance to be ` + | |
`associated with. ` + | |
`Lifecycle injection APIs can only be used during execution of setup().` + | |
( ` If you are using async setup(), make sure to register lifecycle ` + | |
`hooks before the first await statement.` | |
)); | |
} | |
} | |
const createHook = (lifecycle) => (hook, target = currentInstance) => injectHook(lifecycle, hook, target); | |
const onBeforeMount = createHook("bm" /* BEFORE_MOUNT */); | |
const onMounted = createHook("m" /* MOUNTED */); | |
const onBeforeUpdate = createHook("bu" /* BEFORE_UPDATE */); | |
const onUpdated = createHook("u" /* UPDATED */); | |
const onBeforeUnmount = createHook("bum" /* BEFORE_UNMOUNT */); | |
const onUnmounted = createHook("um" /* UNMOUNTED */); | |
const onRenderTriggered = createHook("rtg" /* RENDER_TRIGGERED */); | |
const onRenderTracked = createHook("rtc" /* RENDER_TRACKED */); | |
const onErrorCaptured = createHook("ec" /* ERROR_CAPTURED */); | |
const invoke = (fn) => fn(); | |
// implementation | |
function watch(effectOrSource, cbOrOptions, options) { | |
if (isFunction(cbOrOptions)) { | |
// effect callback as 2nd argument - this is a source watcher | |
return doWatch(effectOrSource, cbOrOptions, options); | |
} | |
else { | |
// 2nd argument is either missing or an options object | |
// - this is a simple effect watcher | |
return doWatch(effectOrSource, null, cbOrOptions); | |
} | |
} | |
function doWatch(source, cb, { lazy, deep, flush, onTrack, onTrigger } = EMPTY_OBJ) { | |
const instance = currentInstance; | |
const suspense = currentSuspense; | |
let getter; | |
if (isArray(source)) { | |
getter = () => source.map(s => isRef(s) | |
? s.value | |
: callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */)); | |
} | |
else if (isRef(source)) { | |
getter = () => source.value; | |
} | |
else if (cb) { | |
// getter with cb | |
getter = () => callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */); | |
} | |
else { | |
// no cb -> simple effect | |
getter = () => { | |
if (instance && instance.isUnmounted) { | |
return; | |
} | |
if (cleanup) { | |
cleanup(); | |
} | |
return callWithErrorHandling(source, instance, 3 /* WATCH_CALLBACK */, [registerCleanup]); | |
}; | |
} | |
if (deep) { | |
const baseGetter = getter; | |
getter = () => traverse(baseGetter()); | |
} | |
let cleanup; | |
const registerCleanup = (fn) => { | |
cleanup = runner.options.onStop = () => { | |
callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */); | |
}; | |
}; | |
let oldValue = isArray(source) ? [] : undefined; | |
const applyCb = cb | |
? () => { | |
if (instance && instance.isUnmounted) { | |
return; | |
} | |
const newValue = runner(); | |
if (deep || hasChanged(newValue, oldValue)) { | |
// cleanup before running cb again | |
if (cleanup) { | |
cleanup(); | |
} | |
callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [ | |
newValue, | |
oldValue, | |
registerCleanup | |
]); | |
oldValue = newValue; | |
} | |
} | |
: void 0; | |
let scheduler; | |
if (flush === 'sync') { | |
scheduler = invoke; | |
} | |
else if (flush === 'pre') { | |
scheduler = job => { | |
if (!instance || instance.vnode.el != null) { | |
queueJob(job); | |
} | |
else { | |
// with 'pre' option, the first call must happen before | |
// the component is mounted so it is called synchronously. | |
job(); | |
} | |
}; | |
} | |
else { | |
scheduler = job => { | |
queuePostRenderEffect(job, suspense); | |
}; | |
} | |
const runner = effect(getter, { | |
lazy: true, | |
// so it runs before component update effects in pre flush mode | |
computed: true, | |
onTrack, | |
onTrigger, | |
scheduler: applyCb ? () => scheduler(applyCb) : scheduler | |
}); | |
if (!lazy) { | |
if (applyCb) { | |
scheduler(applyCb); | |
} | |
else { | |
scheduler(runner); | |
} | |
} | |
else { | |
oldValue = runner(); | |
} | |
recordEffect(runner); | |
return () => { | |
stop(runner); | |
}; | |
} | |
// this.$watch | |
function instanceWatch(source, cb, options) { | |
const ctx = this.renderProxy; | |
const getter = isString(source) ? () => ctx[source] : source.bind(ctx); | |
const stop = watch(getter, cb.bind(ctx), options); | |
onBeforeUnmount(stop, this); | |
return stop; | |
} | |
function traverse(value, seen = new Set()) { | |
if (!isObject(value) || seen.has(value)) { | |
return; | |
} | |
seen.add(value); | |
if (isArray(value)) { | |
for (let i = 0; i < value.length; i++) { | |
traverse(value[i], seen); | |
} | |
} | |
else if (value instanceof Map) { | |
value.forEach((v, key) => { | |
// to register mutation dep for existing keys | |
traverse(value.get(key), seen); | |
}); | |
} | |
else if (value instanceof Set) { | |
value.forEach(v => { | |
traverse(v, seen); | |
}); | |
} | |
else { | |
for (const key in value) { | |
traverse(value[key], seen); | |
} | |
} | |
return value; | |
} | |
const publicPropertiesMap = { | |
$data: 'data', | |
$props: 'propsProxy', | |
$attrs: 'attrs', | |
$slots: 'slots', | |
$refs: 'refs', | |
$parent: 'parent', | |
$root: 'root', | |
$emit: 'emit', | |
$options: 'type' | |
}; | |
const PublicInstanceProxyHandlers = { | |
get(target, key) { | |
// fast path for unscopables when using `with` block | |
if ( key === Symbol.unscopables) { | |
return; | |
} | |
const { renderContext, data, props, propsProxy, accessCache, type, sink } = target; | |
// This getter gets called for every property access on the render context | |
// during render and is a major hotspot. The most expensive part of this | |
// is the multiple hasOwn() calls. It's much faster to do a simple property | |
// access on a plain object, so we use an accessCache object (with null | |
// prototype) to memoize what access type a key corresponds to. | |
const n = accessCache[key]; | |
if (n !== undefined) { | |
switch (n) { | |
case 0 /* DATA */: | |
return data[key]; | |
case 1 /* CONTEXT */: | |
return renderContext[key]; | |
case 2 /* PROPS */: | |
return propsProxy[key]; | |
} | |
} | |
else if (data !== EMPTY_OBJ && hasOwn(data, key)) { | |
accessCache[key] = 0 /* DATA */; | |
return data[key]; | |
} | |
else if (hasOwn(renderContext, key)) { | |
accessCache[key] = 1 /* CONTEXT */; | |
return renderContext[key]; | |
} | |
else if (hasOwn(props, key)) { | |
// only cache props access if component has declared (thus stable) props | |
if (type.props != null) { | |
accessCache[key] = 2 /* PROPS */; | |
} | |
// return the value from propsProxy for ref unwrapping and readonly | |
return propsProxy[key]; | |
} | |
else if (key === '$cache') { | |
return target.renderCache || (target.renderCache = []); | |
} | |
else if (key === '$el') { | |
return target.vnode.el; | |
} | |
else if (hasOwn(publicPropertiesMap, key)) { | |
if ( key === '$attrs') { | |
markAttrsAccessed(); | |
} | |
return target[publicPropertiesMap[key]]; | |
} | |
// methods are only exposed when options are supported | |
{ | |
switch (key) { | |
case '$forceUpdate': | |
return target.update; | |
case '$nextTick': | |
return nextTick; | |
case '$watch': | |
return instanceWatch.bind(target); | |
} | |
} | |
if (hasOwn(sink, key)) { | |
return sink[key]; | |
} | |
else if ( currentRenderingInstance != null) { | |
warn(`Property ${JSON.stringify(key)} was accessed during render ` + | |
`but is not defined on instance.`); | |
} | |
}, | |
set(target, key, value) { | |
const { data, renderContext } = target; | |
if (data !== EMPTY_OBJ && hasOwn(data, key)) { | |
data[key] = value; | |
} | |
else if (hasOwn(renderContext, key)) { | |
renderContext[key] = value; | |
} | |
else if (key[0] === '$' && key.slice(1) in target) { | |
warn(`Attempting to mutate public property "${key}". ` + | |
`Properties starting with $ are reserved and readonly.`, target); | |
return false; | |
} | |
else if (key in target.props) { | |
warn(`Attempting to mutate prop "${key}". Props are readonly.`, target); | |
return false; | |
} | |
else { | |
target.sink[key] = value; | |
} | |
return true; | |
} | |
}; | |
{ | |
// this trap is only called in browser-compiled render functions that use | |
// `with (this) {}` | |
PublicInstanceProxyHandlers.has = (_, key) => { | |
return key[0] !== '_' && !isGloballyWhitelisted(key); | |
}; | |
} | |
function provide(key, value) { | |
if (!currentInstance) { | |
{ | |
warn(`provide() can only be used inside setup().`); | |
} | |
} | |
else { | |
let provides = currentInstance.provides; | |
// by default an instance inherits its parent's provides object | |
// but when it needs to provide values of its own, it creates its | |
// own provides object using parent provides object as prototype. | |
// this way in `inject` we can simply look up injections from direct | |
// parent and let the prototype chain do the work. | |
const parentProvides = currentInstance.parent && currentInstance.parent.provides; | |
if (parentProvides === provides) { | |
provides = currentInstance.provides = Object.create(parentProvides); | |
} | |
// TS doesn't allow symbol as index type | |
provides[key] = value; | |
} | |
} | |
function inject(key, defaultValue) { | |
// fallback to `currentRenderingInstance` so that this can be called in | |
// a functional component | |
const instance = currentInstance || currentRenderingInstance; | |
if (instance) { | |
const provides = instance.provides; | |
if (key in provides) { | |
// TS doesn't allow symbol as index type | |
return provides[key]; | |
} | |
else if (defaultValue !== undefined) { | |
return defaultValue; | |
} | |
else { | |
warn(`injection "${String(key)}" not found.`); | |
} | |
} | |
else { | |
warn(`inject() can only be used inside setup() or functional components.`); | |
} | |
} | |
function createDuplicateChecker() { | |
const cache = Object.create(null); | |
return (type, key) => { | |
if (cache[key]) { | |
warn(`${type} property "${key}" is already defined in ${cache[key]}.`); | |
} | |
else { | |
cache[key] = type; | |
} | |
}; | |
} | |
function applyOptions(instance, options, asMixin = false) { | |
const renderContext = instance.renderContext === EMPTY_OBJ | |
? (instance.renderContext = reactive({})) | |
: instance.renderContext; | |
const ctx = instance.renderProxy; | |
const { | |
// composition | |
mixins, extends: extendsOptions, | |
// state | |
props: propsOptions, data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions, | |
// assets | |
components, directives, | |
// lifecycle | |
beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeUnmount, unmounted, renderTracked, renderTriggered, errorCaptured } = options; | |
const globalMixins = instance.appContext.mixins; | |
// call it only during dev | |
const checkDuplicateProperties = createDuplicateChecker() ; | |
// applyOptions is called non-as-mixin once per instance | |
if (!asMixin) { | |
callSyncHook('beforeCreate', options, ctx, globalMixins); | |
// global mixins are applied first | |
applyMixins(instance, globalMixins); | |
} | |
// extending a base component... | |
if (extendsOptions) { | |
applyOptions(instance, extendsOptions, true); | |
} | |
// local mixins | |
if (mixins) { | |
applyMixins(instance, mixins); | |
} | |
if ( propsOptions) { | |
for (const key in propsOptions) { | |
checkDuplicateProperties("Props" /* PROPS */, key); | |
} | |
} | |
// state options | |
if (dataOptions) { | |
const data = isFunction(dataOptions) ? dataOptions.call(ctx) : dataOptions; | |
if (!isObject(data)) { | |
warn(`data() should return an object.`); | |
} | |
else if (instance.data === EMPTY_OBJ) { | |
{ | |
for (const key in data) { | |
checkDuplicateProperties("Data" /* DATA */, key); | |
} | |
} | |
instance.data = reactive(data); | |
} | |
else { | |
// existing data: this is a mixin or extends. | |
extend(instance.data, data); | |
} | |
} | |
if (computedOptions) { | |
for (const key in computedOptions) { | |
const opt = computedOptions[key]; | |
checkDuplicateProperties("Computed" /* COMPUTED */, key); | |
if (isFunction(opt)) { | |
renderContext[key] = computed$1(opt.bind(ctx)); | |
} | |
else { | |
const { get, set } = opt; | |
if (isFunction(get)) { | |
renderContext[key] = computed$1({ | |
get: get.bind(ctx), | |
set: isFunction(set) | |
? set.bind(ctx) | |
: () => { | |
warn(`Computed property "${key}" was assigned to but it has no setter.`); | |
} | |
}); | |
} | |
else { | |
warn(`Computed property "${key}" has no getter.`); | |
} | |
} | |
} | |
} | |
if (methods) { | |
for (const key in methods) { | |
const methodHandler = methods[key]; | |
if (isFunction(methodHandler)) { | |
checkDuplicateProperties("Methods" /* METHODS */, key); | |
renderContext[key] = methodHandler.bind(ctx); | |
} | |
else { | |
warn(`Method "${key}" has type "${typeof methodHandler}" in the component definition. ` + | |
`Did you reference the function correctly?`); | |
} | |
} | |
} | |
if (watchOptions) { | |
for (const key in watchOptions) { | |
createWatcher(watchOptions[key], renderContext, ctx, key); | |
} | |
} | |
if (provideOptions) { | |
const provides = isFunction(provideOptions) | |
? provideOptions.call(ctx) | |
: provideOptions; | |
for (const key in provides) { | |
provide(key, provides[key]); | |
} | |
} | |
if (injectOptions) { | |
if (isArray(injectOptions)) { | |
for (let i = 0; i < injectOptions.length; i++) { | |
const key = injectOptions[i]; | |
checkDuplicateProperties("Inject" /* INJECT */, key); | |
renderContext[key] = inject(key); | |
} | |
} | |
else { | |
for (const key in injectOptions) { | |
checkDuplicateProperties("Inject" /* INJECT */, key); | |
const opt = injectOptions[key]; | |
if (isObject(opt)) { | |
renderContext[key] = inject(opt.from, opt.default); | |
} | |
else { | |
renderContext[key] = inject(opt); | |
} | |
} | |
} | |
} | |
// asset options | |
if (components) { | |
extend(instance.components, components); | |
} | |
if (directives) { | |
extend(instance.directives, directives); | |
} | |
// lifecycle options | |
if (!asMixin) { | |
callSyncHook('created', options, ctx, globalMixins); | |
} | |
if (beforeMount) { | |
onBeforeMount(beforeMount.bind(ctx)); | |
} | |
if (mounted) { | |
onMounted(mounted.bind(ctx)); | |
} | |
if (beforeUpdate) { | |
onBeforeUpdate(beforeUpdate.bind(ctx)); | |
} | |
if (updated) { | |
onUpdated(updated.bind(ctx)); | |
} | |
if (activated) { | |
onActivated(activated.bind(ctx)); | |
} | |
if (deactivated) { | |
onDeactivated(deactivated.bind(ctx)); | |
} | |
if (errorCaptured) { | |
onErrorCaptured(errorCaptured.bind(ctx)); | |
} | |
if (renderTracked) { | |
onRenderTracked(renderTracked.bind(ctx)); | |
} | |
if (renderTriggered) { | |
onRenderTriggered(renderTriggered.bind(ctx)); | |
} | |
if (beforeUnmount) { | |
onBeforeUnmount(beforeUnmount.bind(ctx)); | |
} | |
if (unmounted) { | |
onUnmounted(unmounted.bind(ctx)); | |
} | |
} | |
function callSyncHook(name, options, ctx, globalMixins) { | |
callHookFromMixins(name, globalMixins, ctx); | |
const baseHook = options.extends && options.extends[name]; | |
if (baseHook) { | |
baseHook.call(ctx); | |
} | |
const mixins = options.mixins; | |
if (mixins) { | |
callHookFromMixins(name, mixins, ctx); | |
} | |
const selfHook = options[name]; | |
if (selfHook) { | |
selfHook.call(ctx); | |
} | |
} | |
function callHookFromMixins(name, mixins, ctx) { | |
for (let i = 0; i < mixins.length; i++) { | |
const fn = mixins[i][name]; | |
if (fn) { | |
fn.call(ctx); | |
} | |
} | |
} | |
function applyMixins(instance, mixins) { | |
for (let i = 0; i < mixins.length; i++) { | |
applyOptions(instance, mixins[i], true); | |
} | |
} | |
function createWatcher(raw, renderContext, ctx, key) { | |
const getter = () => ctx[key]; | |
if (isString(raw)) { | |
const handler = renderContext[raw]; | |
if (isFunction(handler)) { | |
watch(getter, handler); | |
} | |
else { | |
warn(`Invalid watch handler specified by key "${raw}"`, handler); | |
} | |
} | |
else if (isFunction(raw)) { | |
watch(getter, raw.bind(ctx)); | |
} | |
else if (isObject(raw)) { | |
if (isArray(raw)) { | |
raw.forEach(r => createWatcher(r, renderContext, ctx, key)); | |
} | |
else { | |
watch(getter, raw.handler.bind(ctx), raw); | |
} | |
} | |
else { | |
warn(`Invalid watch option: "${key}"`); | |
} | |
} | |
const emptyAppContext = createAppContext(); | |
function createComponentInstance(vnode, parent) { | |
// inherit parent app context - or - if root, adopt from root vnode | |
const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext; | |
const instance = { | |
vnode, | |
parent, | |
appContext, | |
type: vnode.type, | |
root: null, | |
next: null, | |
subTree: null, | |
update: null, | |
render: null, | |
renderProxy: null, | |
propsProxy: null, | |
setupContext: null, | |
effects: null, | |
provides: parent ? parent.provides : Object.create(appContext.provides), | |
accessCache: null, | |
renderCache: null, | |
// setup context properties | |
renderContext: EMPTY_OBJ, | |
data: EMPTY_OBJ, | |
props: EMPTY_OBJ, | |
attrs: EMPTY_OBJ, | |
slots: EMPTY_OBJ, | |
refs: EMPTY_OBJ, | |
// per-instance asset storage (mutable during options resolution) | |
components: Object.create(appContext.components), | |
directives: Object.create(appContext.directives), | |
// async dependency management | |
asyncDep: null, | |
asyncResult: null, | |
asyncResolved: false, | |
// user namespace for storing whatever the user assigns to `this` | |
// can also be used as a wildcard storage for ad-hoc injections internally | |
sink: {}, | |
// lifecycle hooks | |
// not using enums here because it results in computed properties | |
isUnmounted: false, | |
isDeactivated: false, | |
bc: null, | |
c: null, | |
bm: null, | |
m: null, | |
bu: null, | |
u: null, | |
um: null, | |
bum: null, | |
da: null, | |
a: null, | |
rtg: null, | |
rtc: null, | |
ec: null, | |
emit: (event, ...args) => { | |
const props = instance.vnode.props || EMPTY_OBJ; | |
const handler = props[`on${event}`] || props[`on${capitalize(event)}`]; | |
if (handler) { | |
callWithAsyncErrorHandling(handler, instance, 6 /* COMPONENT_EVENT_HANDLER */, args); | |
} | |
} | |
}; | |
instance.root = parent ? parent.root : instance; | |
return instance; | |
} | |
let currentInstance = null; | |
let currentSuspense = null; | |
const getCurrentInstance = () => currentInstance; | |
const setCurrentInstance = (instance) => { | |
currentInstance = instance; | |
}; | |
const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component'); | |
function validateComponentName(name, config) { | |
const appIsNativeTag = config.isNativeTag || NO; | |
if (isBuiltInTag(name) || appIsNativeTag(name)) { | |
warn('Do not use built-in or reserved HTML elements as component id: ' + name); | |
} | |
} | |
function setupStatefulComponent(instance, parentSuspense) { | |
const Component = instance.type; | |
{ | |
if (Component.name) { | |
validateComponentName(Component.name, instance.appContext.config); | |
} | |
if (Component.components) { | |
const names = Object.keys(Component.components); | |
for (let i = 0; i < names.length; i++) { | |
validateComponentName(names[i], instance.appContext.config); | |
} | |
} | |
if (Component.directives) { | |
const names = Object.keys(Component.directives); | |
for (let i = 0; i < names.length; i++) { | |
validateDirectiveName(names[i]); | |
} | |
} | |
} | |
// 0. create render proxy property access cache | |
instance.accessCache = {}; | |
// 1. create render proxy | |
instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers); | |
// 2. create props proxy | |
// the propsProxy is a reactive AND readonly proxy to the actual props. | |
// it will be updated in resolveProps() on updates before render | |
const propsProxy = (instance.propsProxy = readonlyProps(instance.props)); | |
// 3. call setup() | |
const { setup } = Component; | |
if (setup) { | |
const setupContext = (instance.setupContext = | |
setup.length > 1 ? createSetupContext(instance) : null); | |
currentInstance = instance; | |
currentSuspense = parentSuspense; | |
const setupResult = callWithErrorHandling(setup, instance, 0 /* SETUP_FUNCTION */, [propsProxy, setupContext]); | |
currentInstance = null; | |
currentSuspense = null; | |
if (isPromise(setupResult)) { | |
{ | |
// async setup returned Promise. | |
// bail here and wait for re-entry. | |
instance.asyncDep = setupResult; | |
} | |
return; | |
} | |
else { | |
handleSetupResult(instance, setupResult, parentSuspense); | |
} | |
} | |
else { | |
finishComponentSetup(instance, parentSuspense); | |
} | |
} | |
function handleSetupResult(instance, setupResult, parentSuspense) { | |
if (isFunction(setupResult)) { | |
// setup returned an inline render function | |
instance.render = setupResult; | |
} | |
else if (isObject(setupResult)) { | |
if ( isVNode(setupResult)) { | |
warn(`setup() should not return VNodes directly - ` + | |
`return a render function instead.`); | |
} | |
// setup returned bindings. | |
// assuming a render function compiled from template is present. | |
instance.renderContext = reactive(setupResult); | |
} | |
else if ( setupResult !== undefined) { | |
warn(`setup() should return an object. Received: ${setupResult === null ? 'null' : typeof setupResult}`); | |
} | |
finishComponentSetup(instance, parentSuspense); | |
} | |
let compile$1; | |
// exported method uses any to avoid d.ts relying on the compiler types. | |
function registerRuntimeCompiler(_compile) { | |
compile$1 = _compile; | |
} | |
function finishComponentSetup(instance, parentSuspense) { | |
const Component = instance.type; | |
if (!instance.render) { | |
if ( Component.template && !Component.render) { | |
// true ensures `compile` is provided | |
Component.render = compile$1(Component.template, { | |
isCustomElement: instance.appContext.config.isCustomElement || NO, | |
onError(err) { | |
{ | |
const message = `Template compilation error: ${err.message}`; | |
const codeFrame = err.loc && | |
generateCodeFrame(Component.template, err.loc.start.offset, err.loc.end.offset); | |
warn(codeFrame ? `${message}\n${codeFrame}` : message); | |
} | |
} | |
}); | |
} | |
if ( !Component.render) { | |
/* istanbul ignore if */ | |
{ | |
warn(`Component is missing${ ` template or` } render function.`); | |
} | |
} | |
instance.render = (Component.render || NOOP); | |
} | |
// support for 2.x options | |
{ | |
currentInstance = instance; | |
currentSuspense = parentSuspense; | |
applyOptions(instance, Component); | |
currentInstance = null; | |
currentSuspense = null; | |
} | |
if (instance.renderContext === EMPTY_OBJ) { | |
instance.renderContext = reactive({}); | |
} | |
} | |
// used to identify a setup context proxy | |
const SetupProxySymbol = Symbol(); | |
const SetupProxyHandlers = {}; | |
['attrs', 'slots'].forEach((type) => { | |
SetupProxyHandlers[type] = { | |
get: (instance, key) => instance[type][key], | |
has: (instance, key) => key === SetupProxySymbol || key in instance[type], | |
ownKeys: instance => Reflect.ownKeys(instance[type]), | |
// this is necessary for ownKeys to work properly | |
getOwnPropertyDescriptor: (instance, key) => Reflect.getOwnPropertyDescriptor(instance[type], key), | |
set: () => false, | |
deleteProperty: () => false | |
}; | |
}); | |
function createSetupContext(instance) { | |
const context = { | |
// attrs & slots are non-reactive, but they need to always expose | |
// the latest values (instance.xxx may get replaced during updates) so we | |
// need to expose them through a proxy | |
attrs: new Proxy(instance, SetupProxyHandlers.attrs), | |
slots: new Proxy(instance, SetupProxyHandlers.slots), | |
emit: instance.emit | |
}; | |
return Object.freeze(context) ; | |
} | |
// record effects created during a component's setup() so that they can be | |
// stopped when the component unmounts | |
function recordEffect(effect) { | |
if (currentInstance) { | |
(currentInstance.effects || (currentInstance.effects = [])).push(effect); | |
} | |
} | |
function computed$1(getterOrOptions) { | |
const c = computed(getterOrOptions); | |
recordEffect(c.effect); | |
return c; | |
} | |
// implementation, close to no-op | |
function createComponent(options) { | |
return isFunction(options) ? { setup: options } : options; | |
} | |
// Actual implementation | |
function h(type, propsOrChildren, children) { | |
if (arguments.length === 2) { | |
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) { | |
// single vnode without props | |
if (isVNode(propsOrChildren)) { | |
return createVNode(type, null, [propsOrChildren]); | |
} | |
// props without children | |
return createVNode(type, propsOrChildren); | |
} | |
else { | |
// omit props | |
return createVNode(type, null, propsOrChildren); | |
} | |
} | |
else { | |
if (isVNode(children)) { | |
children = [children]; | |
} | |
return createVNode(type, propsOrChildren, children); | |
} | |
} | |
// For runtime consumption | |
const PublicShapeFlags = { | |
ELEMENT: 1 /* ELEMENT */, | |
FUNCTIONAL_COMPONENT: 2 /* FUNCTIONAL_COMPONENT */, | |
STATEFUL_COMPONENT: 4 /* STATEFUL_COMPONENT */, | |
TEXT_CHILDREN: 8 /* TEXT_CHILDREN */, | |
ARRAY_CHILDREN: 16 /* ARRAY_CHILDREN */, | |
SLOTS_CHILDREN: 32 /* SLOTS_CHILDREN */, | |
SUSPENSE: 64 /* SUSPENSE */, | |
COMPONENT_SHOULD_KEEP_ALIVE: 128 /* COMPONENT_SHOULD_KEEP_ALIVE */, | |
COMPONENT_KEPT_ALIVE: 256 /* COMPONENT_KEPT_ALIVE */, | |
COMPONENT: 6 /* COMPONENT */ | |
}; | |
function resolveComponent(name) { | |
return resolveAsset('components', name); | |
} | |
function resolveDynamicComponent(component) { | |
if (!component) | |
return; | |
if (isString(component)) { | |
return resolveAsset('components', component); | |
} | |
else if (isFunction(component) || isObject(component)) { | |
return component; | |
} | |
} | |
function resolveDirective(name) { | |
return resolveAsset('directives', name); | |
} | |
function resolveAsset(type, name) { | |
const instance = currentRenderingInstance || currentInstance; | |
if (instance) { | |
let camelized; | |
const registry = instance[type]; | |
const res = registry[name] || | |
registry[(camelized = camelize(name))] || | |
registry[capitalize(camelized)]; | |
if ( !res) { | |
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`); | |
} | |
return res; | |
} | |
else { | |
warn(`resolve${capitalize(type.slice(0, -1))} ` + | |
`can only be used in render() or setup().`); | |
} | |
} | |
function renderList(source, renderItem) { | |
let ret; | |
if (isArray(source) || isString(source)) { | |
ret = new Array(source.length); | |
for (let i = 0, l = source.length; i < l; i++) { | |
ret[i] = renderItem(source[i], i); | |
} | |
} | |
else if (typeof source === 'number') { | |
ret = new Array(source); | |
for (let i = 0; i < source; i++) { | |
ret[i] = renderItem(i + 1, i); | |
} | |
} | |
else if (isObject(source)) { | |
if (source[Symbol.iterator]) { | |
ret = Array.from(source, renderItem); | |
} | |
else { | |
const keys = Object.keys(source); | |
ret = new Array(keys.length); | |
for (let i = 0, l = keys.length; i < l; i++) { | |
const key = keys[i]; | |
ret[i] = renderItem(source[key], key, i); | |
} | |
} | |
} | |
return ret; | |
} | |
// for converting {{ interpolation }} values to displayed strings. | |
function toString(val) { | |
return val == null | |
? '' | |
: isArray(val) || (isPlainObject(val) && val.toString === objectToString) | |
? JSON.stringify(val, null, 2) | |
: String(val); | |
} | |
// For prefixing keys in v-on="obj" with "on" | |
function toHandlers(obj) { | |
const ret = {}; | |
if ( !isObject(obj)) { | |
warn(`v-on with no argument expects an object value.`); | |
return ret; | |
} | |
for (const key in obj) { | |
ret[`on${key}`] = obj[key]; | |
} | |
return ret; | |
} | |
function renderSlot(slots, name, props = {}, | |
// this is not a user-facing function, so the fallback is always generated by | |
// the compiler and guaranteed to be an array | |
fallback) { | |
const slot = slots[name]; | |
return (openBlock(), | |
createBlock(Fragment, { key: props.key }, slot ? slot(props) : fallback || [], slots._compiled ? 0 : -1 /* BAIL */)); | |
} | |
function createSlots(slots, dynamicSlots) { | |
for (let i = 0; i < dynamicSlots.length; i++) { | |
const slot = dynamicSlots[i]; | |
// array of dynamic slot generated by <template v-for="..." #[...]> | |
if (isArray(slot)) { | |
for (let j = 0; j < slot.length; j++) { | |
slots[slot[j].name] = slot[j].fn; | |
} | |
} | |
else { | |
// conditional single slot generated by <template v-if="..." #foo> | |
slots[slot.name] = slot.fn; | |
} | |
} | |
return slots; | |
} | |
// Public API ------------------------------------------------------------------ | |
const version = "3.0.0-alpha.1"; | |
const PatchFlags = PublicPatchFlags; | |
const capitalize$1 = capitalize; | |
const camelize$1 = camelize; | |
const doc = document; | |
const svgNS = 'http://www.w3.org/2000/svg'; | |
const nodeOps = { | |
insert: (child, parent, anchor) => { | |
if (anchor != null) { | |
parent.insertBefore(child, anchor); | |
} | |
else { | |
parent.appendChild(child); | |
} | |
}, | |
remove: (child) => { | |
const parent = child.parentNode; | |
if (parent != null) { | |
parent.removeChild(child); | |
} | |
}, | |
createElement: (tag, isSVG) => isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag), | |
createText: (text) => doc.createTextNode(text), | |
createComment: (text) => doc.createComment(text), | |
setText: (node, text) => { | |
node.nodeValue = text; | |
}, | |
setElementText: (el, text) => { | |
el.textContent = text; | |
}, | |
parentNode: (node) => node.parentNode, | |
nextSibling: (node) => node.nextSibling, | |
querySelector: (selector) => doc.querySelector(selector) | |
}; | |
// compiler should normalize class + :class bindings on the same element | |
// into a single binding ['staticClass', dynamic] | |
function patchClass(el, value, isSVG) { | |
// directly setting className should be faster than setAttribute in theory | |
if (isSVG) { | |
el.setAttribute('class', value); | |
} | |
else { | |
el.className = value; | |
} | |
} | |
function patchStyle(el, prev, next) { | |
const style = el.style; | |
if (!next) { | |
el.removeAttribute('style'); | |
} | |
else if (isString(next)) { | |
style.cssText = next; | |
} | |
else { | |
for (const key in next) { | |
setStyle(style, key, next[key]); | |
} | |
if (prev && !isString(prev)) { | |
for (const key in prev) { | |
if (!next[key]) { | |
setStyle(style, key, ''); | |
} | |
} | |
} | |
} | |
} | |
const importantRE = /\s*!important$/; | |
function setStyle(style, name, val) { | |
if (name.startsWith('--')) { | |
// custom property definition | |
style.setProperty(name, val); | |
} | |
else { | |
const prefixed = autoPrefix(style, name); | |
if (importantRE.test(val)) { | |
// !important | |
style.setProperty(hyphenate(prefixed), val.replace(importantRE, ''), 'important'); | |
} | |
else { | |
style[prefixed] = val; | |
} | |
} | |
} | |
const prefixes = ['Webkit', 'Moz', 'ms']; | |
const prefixCache = {}; | |
function autoPrefix(style, rawName) { | |
const cached = prefixCache[rawName]; | |
if (cached) { | |
return cached; | |
} | |
let name = camelize$1(rawName); | |
if (name !== 'filter' && name in style) { | |
return (prefixCache[rawName] = name); | |
} | |
name = capitalize(name); | |
for (let i = 0; i < prefixes.length; i++) { | |
const prefixed = prefixes[i] + name; | |
if (prefixed in style) { | |
return (prefixCache[rawName] = prefixed); | |
} | |
} | |
return rawName; | |
} | |
function patchAttr(el, key, value) { | |
if (value == null) { | |
el.removeAttribute(key); | |
} | |
else { | |
el.setAttribute(key, value); | |
} | |
} | |
function patchDOMProp(el, key, value, | |
// the following args are passed only due to potential innerHTML/textContent | |
// overriding existing VNodes, in which case the old tree must be properly | |
// unmounted. | |
prevChildren, parentComponent, parentSuspense, unmountChildren) { | |
if ((key === 'innerHTML' || key === 'textContent') && prevChildren != null) { | |
unmountChildren(prevChildren, parentComponent, parentSuspense); | |
} | |
if (key === 'value' && el.tagName !== 'PROGRESS') { | |
// store value as _value as well since | |
// non-string values will be stringified. | |
el._value = value; | |
} | |
if (value === '' && typeof el[key] === 'boolean') { | |
// e.g. <select multiple> compiles to { multiple: '' } | |
el[key] = true; | |
} | |
else { | |
el[key] = value == null ? '' : value; | |
} | |
} | |
// Async edge case fix requires storing an event listener's attach timestamp. | |
let _getNow = Date.now; | |
// Determine what event timestamp the browser is using. Annoyingly, the | |
// timestamp can either be hi-res ( relative to page load) or low-res | |
// (relative to UNIX epoch), so in order to compare time we have to use the | |
// same timestamp type when saving the flush timestamp. | |
if (typeof document !== 'undefined' && | |
_getNow() > document.createEvent('Event').timeStamp) { | |
// if the low-res timestamp which is bigger than the event timestamp | |
// (which is evaluated AFTER) it means the event is using a hi-res timestamp, | |
// and we need to use the hi-res version for event listeners as well. | |
_getNow = () => performance.now(); | |
} | |
// To avoid the overhead of repeatedly calling performance.now(), we cache | |
// and use the same timestamp for all event listeners attached in the same tick. | |
let cachedNow = 0; | |
const p$1 = Promise.resolve(); | |
const reset = () => { | |
cachedNow = 0; | |
}; | |
const getNow = () => cachedNow || (p$1.then(reset), (cachedNow = _getNow())); | |
function addEventListener(el, event, handler, options) { | |
el.addEventListener(event, handler, options); | |
} | |
function removeEventListener(el, event, handler, options) { | |
el.removeEventListener(event, handler, options); | |
} | |
function patchEvent(el, name, prevValue, nextValue, instance = null) { | |
const prevOptions = prevValue && 'options' in prevValue && prevValue.options; | |
const nextOptions = nextValue && 'options' in nextValue && nextValue.options; | |
const invoker = prevValue && prevValue.invoker; | |
const value = nextValue && 'handler' in nextValue ? nextValue.handler : nextValue; | |
if (prevOptions || nextOptions) { | |
const prev = prevOptions || EMPTY_OBJ; | |
const next = nextOptions || EMPTY_OBJ; | |
if (prev.capture !== next.capture || | |
prev.passive !== next.passive || | |
prev.once !== next.once) { | |
if (invoker) { | |
removeEventListener(el, name, invoker, prev); | |
} | |
if (nextValue && value) { | |
const invoker = createInvoker(value, instance); | |
nextValue.invoker = invoker; | |
addEventListener(el, name, invoker, next); | |
} | |
return; | |
} | |
} | |
if (nextValue && value) { | |
if (invoker) { | |
prevValue.invoker = null; | |
invoker.value = value; | |
nextValue.invoker = invoker; | |
invoker.lastUpdated = getNow(); | |
} | |
else { | |
addEventListener(el, name, createInvoker(value, instance), nextOptions || void 0); | |
} | |
} | |
else if (invoker) { | |
removeEventListener(el, name, invoker, prevOptions || void 0); | |
} | |
} | |
function createInvoker(initialValue, instance) { | |
const invoker = (e) => { | |
// async edge case #6566: inner click event triggers patch, event handler | |
// attached to outer element during patch, and triggered again. This | |
// happens because browsers fire microtask ticks between event propagation. | |
// the solution is simple: we save the timestamp when a handler is attached, | |
// and the handler would only fire if the event passed to it was fired | |
// AFTER it was attached. | |
if (e.timeStamp >= invoker.lastUpdated - 1) { | |
callWithAsyncErrorHandling(invoker.value, instance, 5 /* NATIVE_EVENT_HANDLER */, [e]); | |
} | |
}; | |
invoker.value = initialValue; | |
initialValue.invoker = invoker; | |
invoker.lastUpdated = getNow(); | |
return invoker; | |
} | |
function patchProp(el, key, nextValue, prevValue, isSVG, prevChildren, parentComponent, parentSuspense, unmountChildren) { | |
switch (key) { | |
// special | |
case 'class': | |
patchClass(el, nextValue, isSVG); | |
break; | |
case 'style': | |
patchStyle(el, prevValue, nextValue); | |
break; | |
case 'modelValue': | |
case 'onUpdate:modelValue': | |
// Do nothing. This is handled by v-model directives. | |
break; | |
default: | |
if (isOn(key)) { | |
patchEvent(el, key.slice(2).toLowerCase(), prevValue, nextValue, parentComponent); | |
} | |
else if (!isSVG && key in el) { | |
patchDOMProp(el, key, nextValue, prevChildren, parentComponent, parentSuspense, unmountChildren); | |
} | |
else { | |
patchAttr(el, key, nextValue); | |
} | |
break; | |
} | |
} | |
const getModelAssigner = (vnode) => vnode.props['onUpdate:modelValue']; | |
function onCompositionStart(e) { | |
e.target.composing = true; | |
} | |
function onCompositionEnd(e) { | |
const target = e.target; | |
if (target.composing) { | |
target.composing = false; | |
trigger$1(target, 'input'); | |
} | |
} | |
function trigger$1(el, type) { | |
const e = document.createEvent('HTMLEvents'); | |
e.initEvent(type, true, true); | |
el.dispatchEvent(e); | |
} | |
function toNumber(val) { | |
const n = parseFloat(val); | |
return isNaN(n) ? val : n; | |
} | |
// We are exporting the v-model runtime directly as vnode hooks so that it can | |
// be tree-shaken in case v-model is never used. | |
const vModelText = { | |
beforeMount(el, { value, modifiers: { lazy, trim, number } }, vnode) { | |
el.value = value; | |
const assign = getModelAssigner(vnode); | |
const castToNumber = number || el.type === 'number'; | |
addEventListener(el, lazy ? 'change' : 'input', () => { | |
let domValue = el.value; | |
if (trim) { | |
domValue = domValue.trim(); | |
} | |
else if (castToNumber) { | |
domValue = toNumber(domValue); | |
} | |
assign(domValue); | |
}); | |
if (trim) { | |
addEventListener(el, 'change', () => { | |
el.value = el.value.trim(); | |
}); | |
} | |
if (!lazy) { | |
addEventListener(el, 'compositionstart', onCompositionStart); | |
addEventListener(el, 'compositionend', onCompositionEnd); | |
// Safari < 10.2 & UIWebView doesn't fire compositionend when | |
// switching focus before confirming composition choice | |
// this also fixes the issue where some browsers e.g. iOS Chrome | |
// fires "change" instead of "input" on autocomplete. | |
addEventListener(el, 'change', onCompositionEnd); | |
} | |
}, | |
beforeUpdate(el, { value, oldValue, modifiers: { trim, number } }) { | |
if (value === oldValue) { | |
return; | |
} | |
if (document.activeElement === el) { | |
if (trim && el.value.trim() === value) { | |
return; | |
} | |
if ((number || el.type === 'number') && toNumber(el.value) === value) { | |
return; | |
} | |
} | |
el.value = value; | |
} | |
}; | |
const vModelCheckbox = { | |
beforeMount(el, binding, vnode) { | |
setChecked(el, binding, vnode); | |
const assign = getModelAssigner(vnode); | |
addEventListener(el, 'change', () => { | |
const modelValue = el._modelValue; | |
const elementValue = getValue(el); | |
const checked = el.checked; | |
if (isArray(modelValue)) { | |
const index = looseIndexOf(modelValue, elementValue); | |
const found = index !== -1; | |
if (checked && !found) { | |
assign(modelValue.concat(elementValue)); | |
} | |
else if (!checked && found) { | |
const filtered = [...modelValue]; | |
filtered.splice(index, 1); | |
assign(filtered); | |
} | |
} | |
else { | |
assign(checked); | |
} | |
}); | |
}, | |
beforeUpdate: setChecked | |
}; | |
function setChecked(el, { value, oldValue }, vnode) { | |
el._modelValue = value; | |
if (isArray(value)) { | |
el.checked = looseIndexOf(value, vnode.props.value) > -1; | |
} | |
else if (value !== oldValue) { | |
el.checked = !!value; | |
} | |
} | |
const vModelRadio = { | |
beforeMount(el, { value }, vnode) { | |
el.checked = looseEqual(value, vnode.props.value); | |
const assign = getModelAssigner(vnode); | |
addEventListener(el, 'change', () => { | |
assign(getValue(el)); | |
}); | |
}, | |
beforeUpdate(el, { value, oldValue }, vnode) { | |
if (value !== oldValue) { | |
el.checked = looseEqual(value, vnode.props.value); | |
} | |
} | |
}; | |
const vModelSelect = { | |
// use mounted & updated because <select> relies on its children <option>s. | |
mounted(el, { value }, vnode) { | |
setSelected(el, value); | |
const assign = getModelAssigner(vnode); | |
addEventListener(el, 'change', () => { | |
const selectedVal = Array.prototype.filter | |
.call(el.options, (o) => o.selected) | |
.map(getValue); | |
assign(el.multiple ? selectedVal : selectedVal[0]); | |
}); | |
}, | |
updated(el, { value }) { | |
setSelected(el, value); | |
} | |
}; | |
function setSelected(el, value) { | |
const isMultiple = el.multiple; | |
if (isMultiple && !isArray(value)) { | |
warn(`<select multiple v-model> expects an Array value for its binding, ` + | |
`but got ${Object.prototype.toString.call(value).slice(8, -1)}.`); | |
return; | |
} | |
for (let i = 0, l = el.options.length; i < l; i++) { | |
const option = el.options[i]; | |
const optionValue = getValue(option); | |
if (isMultiple) { | |
option.selected = looseIndexOf(value, optionValue) > -1; | |
} | |
else { | |
if (looseEqual(getValue(option), value)) { | |
el.selectedIndex = i; | |
return; | |
} | |
} | |
} | |
if (!isMultiple) { | |
el.selectedIndex = -1; | |
} | |
} | |
function looseEqual(a, b) { | |
if (a === b) | |
return true; | |
const isObjectA = isObject(a); | |
const isObjectB = isObject(b); | |
if (isObjectA && isObjectB) { | |
try { | |
const isArrayA = isArray(a); | |
const isArrayB = isArray(b); | |
if (isArrayA && isArrayB) { | |
return (a.length === b.length && | |
a.every((e, i) => looseEqual(e, b[i]))); | |
} | |
else if (a instanceof Date && b instanceof Date) { | |
return a.getTime() === b.getTime(); | |
} | |
else if (!isArrayA && !isArrayB) { | |
const keysA = Object.keys(a); | |
const keysB = Object.keys(b); | |
return (keysA.length === keysB.length && | |
keysA.every(key => looseEqual(a[key], b[key]))); | |
} | |
else { | |
/* istanbul ignore next */ | |
return false; | |
} | |
} | |
catch (e) { | |
/* istanbul ignore next */ | |
return false; | |
} | |
} | |
else if (!isObjectA && !isObjectB) { | |
return String(a) === String(b); | |
} | |
else { | |
return false; | |
} | |
} | |
function looseIndexOf(arr, val) { | |
return arr.findIndex(item => looseEqual(item, val)); | |
} | |
// retrieve raw value set via :value bindings | |
function getValue(el) { | |
return '_value' in el ? el._value : el.value; | |
} | |
const vModelDynamic = { | |
beforeMount(el, binding, vnode) { | |
callModelHook(el, binding, vnode, null, 'beforeMount'); | |
}, | |
mounted(el, binding, vnode) { | |
callModelHook(el, binding, vnode, null, 'mounted'); | |
}, | |
beforeUpdate(el, binding, vnode, prevVNode) { | |
callModelHook(el, binding, vnode, prevVNode, 'beforeUpdate'); | |
}, | |
updated(el, binding, vnode, prevVNode) { | |
callModelHook(el, binding, vnode, prevVNode, 'updated'); | |
} | |
}; | |
function callModelHook(el, binding, vnode, prevVNode, hook) { | |
let modelToUse; | |
switch (el.tagName) { | |
case 'SELECT': | |
modelToUse = vModelSelect; | |
break; | |
case 'TEXTAREA': | |
modelToUse = vModelText; | |
break; | |
default: | |
switch (el.type) { | |
case 'checkbox': | |
modelToUse = vModelCheckbox; | |
break; | |
case 'radio': | |
modelToUse = vModelRadio; | |
break; | |
default: | |
modelToUse = vModelText; | |
} | |
} | |
const fn = modelToUse[hook]; | |
fn && fn(el, binding, vnode, prevVNode); | |
} | |
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta']; | |
const modifierGuards = { | |
stop: e => e.stopPropagation(), | |
prevent: e => e.preventDefault(), | |
self: e => e.target !== e.currentTarget, | |
ctrl: e => !e.ctrlKey, | |
shift: e => !e.shiftKey, | |
alt: e => !e.altKey, | |
meta: e => !e.metaKey, | |
left: e => 'button' in e && e.button !== 0, | |
middle: e => 'button' in e && e.button !== 1, | |
right: e => 'button' in e && e.button !== 2, | |
exact: (e, modifiers) => systemModifiers.some(m => e[`${m}Key`] && !modifiers.includes(m)) | |
}; | |
const withModifiers = (fn, modifiers) => { | |
return (event) => { | |
for (let i = 0; i < modifiers.length; i++) { | |
const guard = modifierGuards[modifiers[i]]; | |
if (guard && guard(event, modifiers)) | |
return; | |
} | |
return fn(event); | |
}; | |
}; | |
// Kept for 2.x compat. | |
// Note: IE11 compat for `spacebar` and `del` is removed for now. | |
const keyNames = { | |
esc: 'escape', | |
space: ' ', | |
up: 'arrow-up', | |
left: 'arrow-left', | |
right: 'arrow-right', | |
down: 'arrow-down', | |
delete: 'backspace' | |
}; | |
const withKeys = (fn, modifiers) => { | |
return (event) => { | |
if (!('key' in event)) | |
return; | |
const eventKey = hyphenate(event.key); | |
if ( | |
// None of the provided key modifiers match the current event key | |
!modifiers.some(k => k === eventKey || keyNames[k] === eventKey)) { | |
return; | |
} | |
return fn(event); | |
}; | |
}; | |
const { render: baseRender, createApp: baseCreateApp } = createRenderer({ | |
patchProp, | |
...nodeOps | |
}); | |
// use explicit type casts here to avoid import() calls in rolled-up d.ts | |
const render = baseRender; | |
const createApp = () => { | |
const app = baseCreateApp(); | |
{ | |
// Inject `isNativeTag` | |
// this is used for component name validation (dev only) | |
Object.defineProperty(app.config, 'isNativeTag', { | |
value: (tag) => isHTMLTag(tag) || isSVGTag(tag), | |
writable: false | |
}); | |
} | |
const mount = app.mount; | |
app.mount = (component, container, props) => { | |
if (isString(container)) { | |
container = document.querySelector(container); | |
if (!container) { | |
warn(`Failed to mount app: mount target selector returned null.`); | |
return; | |
} | |
} | |
if ( | |
!isFunction(component) && | |
!component.render && | |
!component.template) { | |
component.template = container.innerHTML; | |
} | |
// clear content before mounting | |
container.innerHTML = ''; | |
return mount(component, container, props); | |
}; | |
return app; | |
}; | |
var runtimeDom = /*#__PURE__*/Object.freeze({ | |
__proto__: null, | |
render: render, | |
createApp: createApp, | |
vModelText: vModelText, | |
vModelCheckbox: vModelCheckbox, | |
vModelRadio: vModelRadio, | |
vModelSelect: vModelSelect, | |
vModelDynamic: vModelDynamic, | |
withModifiers: withModifiers, | |
withKeys: withKeys, | |
version: version, | |
PatchFlags: PatchFlags, | |
capitalize: capitalize$1, | |
camelize: camelize$1, | |
nextTick: nextTick, | |
createComponent: createComponent, | |
getCurrentInstance: getCurrentInstance, | |
h: h, | |
createVNode: createVNode, | |
cloneVNode: cloneVNode, | |
mergeProps: mergeProps, | |
openBlock: openBlock, | |
createBlock: createBlock, | |
Text: Text, | |
Comment: Comment, | |
Fragment: Fragment, | |
Portal: Portal, | |
Suspense: Suspense, | |
KeepAlive: KeepAlive, | |
ShapeFlags: PublicShapeFlags, | |
createRenderer: createRenderer, | |
warn: warn, | |
handleError: handleError, | |
callWithErrorHandling: callWithErrorHandling, | |
callWithAsyncErrorHandling: callWithAsyncErrorHandling, | |
withDirectives: withDirectives, | |
resolveComponent: resolveComponent, | |
resolveDirective: resolveDirective, | |
resolveDynamicComponent: resolveDynamicComponent, | |
renderList: renderList, | |
toString: toString, | |
toHandlers: toHandlers, | |
renderSlot: renderSlot, | |
createSlots: createSlots, | |
setBlockTracking: setBlockTracking, | |
createTextVNode: createTextVNode, | |
createCommentVNode: createCommentVNode, | |
registerRuntimeCompiler: registerRuntimeCompiler, | |
recordEffect: recordEffect, | |
computed: computed$1, | |
ref: ref, | |
isRef: isRef, | |
toRefs: toRefs, | |
reactive: reactive, | |
isReactive: isReactive, | |
readonly: readonly, | |
isReadonly: isReadonly, | |
toRaw: toRaw, | |
markReadonly: markReadonly, | |
markNonReactive: markNonReactive, | |
effect: effect, | |
watch: watch, | |
instanceWatch: instanceWatch, | |
injectHook: injectHook, | |
createHook: createHook, | |
onBeforeMount: onBeforeMount, | |
onMounted: onMounted, | |
onBeforeUpdate: onBeforeUpdate, | |
onUpdated: onUpdated, | |
onBeforeUnmount: onBeforeUnmount, | |
onUnmounted: onUnmounted, | |
onRenderTriggered: onRenderTriggered, | |
onRenderTracked: onRenderTracked, | |
onErrorCaptured: onErrorCaptured, | |
onActivated: onActivated, | |
onDeactivated: onDeactivated, | |
provide: provide, | |
inject: inject | |
}); | |
// This package is the "full-build" that includes both the runtime | |
function compileToFunction(template, options) { | |
const { code } = compile(template, { | |
hoistStatic: true, | |
...options | |
}); | |
return new Function('Vue', code)(runtimeDom); | |
} | |
registerRuntimeCompiler(compileToFunction); | |
{ | |
console[console.info ? 'info' : 'log'](`You are running a development build of Vue.\n` + | |
`Make sure to use the production build (*.prod.js) when deploying for production.`); | |
} | |
exports.Comment = Comment; | |
exports.Fragment = Fragment; | |
exports.KeepAlive = KeepAlive; | |
exports.PatchFlags = PatchFlags; | |
exports.Portal = Portal; | |
exports.ShapeFlags = PublicShapeFlags; | |
exports.Suspense = Suspense; | |
exports.Text = Text; | |
exports.callWithAsyncErrorHandling = callWithAsyncErrorHandling; | |
exports.callWithErrorHandling = callWithErrorHandling; | |
exports.camelize = camelize$1; | |
exports.capitalize = capitalize$1; | |
exports.cloneVNode = cloneVNode; | |
exports.compile = compileToFunction; | |
exports.computed = computed$1; | |
exports.createApp = createApp; | |
exports.createBlock = createBlock; | |
exports.createCommentVNode = createCommentVNode; | |
exports.createComponent = createComponent; | |
exports.createHook = createHook; | |
exports.createRenderer = createRenderer; | |
exports.createSlots = createSlots; | |
exports.createTextVNode = createTextVNode; | |
exports.createVNode = createVNode; | |
exports.effect = effect; | |
exports.getCurrentInstance = getCurrentInstance; | |
exports.h = h; | |
exports.handleError = handleError; | |
exports.inject = inject; | |
exports.injectHook = injectHook; | |
exports.instanceWatch = instanceWatch; | |
exports.isReactive = isReactive; | |
exports.isReadonly = isReadonly; | |
exports.isRef = isRef; | |
exports.markNonReactive = markNonReactive; | |
exports.markReadonly = markReadonly; | |
exports.mergeProps = mergeProps; | |
exports.nextTick = nextTick; | |
exports.onActivated = onActivated; | |
exports.onBeforeMount = onBeforeMount; | |
exports.onBeforeUnmount = onBeforeUnmount; | |
exports.onBeforeUpdate = onBeforeUpdate; | |
exports.onDeactivated = onDeactivated; | |
exports.onErrorCaptured = onErrorCaptured; | |
exports.onMounted = onMounted; | |
exports.onRenderTracked = onRenderTracked; | |
exports.onRenderTriggered = onRenderTriggered; | |
exports.onUnmounted = onUnmounted; | |
exports.onUpdated = onUpdated; | |
exports.openBlock = openBlock; | |
exports.provide = provide; | |
exports.reactive = reactive; | |
exports.readonly = readonly; | |
exports.recordEffect = recordEffect; | |
exports.ref = ref; | |
exports.registerRuntimeCompiler = registerRuntimeCompiler; | |
exports.render = render; | |
exports.renderList = renderList; | |
exports.renderSlot = renderSlot; | |
exports.resolveComponent = resolveComponent; | |
exports.resolveDirective = resolveDirective; | |
exports.resolveDynamicComponent = resolveDynamicComponent; | |
exports.setBlockTracking = setBlockTracking; | |
exports.toHandlers = toHandlers; | |
exports.toRaw = toRaw; | |
exports.toRefs = toRefs; | |
exports.toString = toString; | |
exports.vModelCheckbox = vModelCheckbox; | |
exports.vModelDynamic = vModelDynamic; | |
exports.vModelRadio = vModelRadio; | |
exports.vModelSelect = vModelSelect; | |
exports.vModelText = vModelText; | |
exports.version = version; | |
exports.warn = warn; | |
exports.watch = watch; | |
exports.withDirectives = withDirectives; | |
exports.withKeys = withKeys; | |
exports.withModifiers = withModifiers; | |
return exports; | |
}({})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment