UNPKG

vue-next

Version:

## Status: Pre-Alpha.

1,307 lines (1,296 loc) 138 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); // Patch flags are optimization hints generated by the compiler. // 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` }; // 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]; } 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); /** * Always return false. */ const NO = () => false; const extend = (a, b) => { for (const key in b) { a[key] = b[key]; } return a; }; 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'; const camelizeRE = /-(\w)/g; const camelize = (str) => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : '')); }; const capitalize = (str) => { return str.charAt(0).toUpperCase() + str.slice(1); }; 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 '&lt;' 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 createInterpolation(content, loc) { return { type: 5 /* INTERPOLATION */, loc, content: isString(content) ? createSimpleExpression(content, false, loc) : content }; } 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( ``); const PORTAL = Symbol( ``); const SUSPENSE = Symbol( ``); const OPEN_BLOCK = Symbol( ``); const CREATE_BLOCK = Symbol( ``); const CREATE_VNODE = Symbol( ``); const CREATE_COMMENT = Symbol( ``); const CREATE_TEXT = Symbol( ``); const RESOLVE_COMPONENT = Symbol( ``); const RESOLVE_DYNAMIC_COMPONENT = Symbol( ``); const RESOLVE_DIRECTIVE = Symbol( ``); const WITH_DIRECTIVES = Symbol( ``); const RENDER_LIST = Symbol( ``); const RENDER_SLOT = Symbol( ``); const CREATE_SLOTS = Symbol( ``); const TO_STRING = Symbol( ``); const MERGE_PROPS = Symbol( ``); const TO_HANDLERS = Symbol( ``); const CAMELIZE = Symbol( ``); const SET_BLOCK_TRACKING = Symbol( ``); // 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`, [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]; }); } // cache node requires // lazy require dependencies so that they don't end up in rollup's dep graph // and thus can be tree-shaken in browser builds. let _parse; let _walk; function loadDep(name) { if (typeof process !== 'undefined' && isFunction(require)) { return require(name); } else { // This is only used when we are building a dev-only build of the compiler // which runs in the browser but also uses Node deps. return window._deps[name]; } } const parseJS = (code, options) => { assert(!false, `Expression AST analysis can only be performed in non-browser builds.`); const parse = _parse || (_parse = loadDep('acorn').parse); return parse(code, options); }; const walkJS = (ast, walker) => { assert(!false, `Expression AST analysis can only be performed in non-browser builds.`); const walk = _walk || (_walk = loadDep('estree-walker').walk); return walk(ast, walker); }; 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) { const source = loc.source.substr(offset, length); const newLoc = { source, start: advancePositionWithClone(loc.start, loc.source, offset), end: loc.end }; if (length != null) { 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 ]); } const isVSlot = (p) => p.type === 7 /* DIRECTIVE */ && p.name === 'slot'; const isTemplateNode = (node) => node.type === 1 /* ELEMENT */ && node.tagType === 3 /* TEMPLATE */; const isSlotOutlet = (node) => 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, '_')}`; } // Check if a node contains expressions that reference current context scope ids function hasScopeRef(node, ids) { if (!node || Object.keys(ids).length === 0) { return false; } switch (node.type) { case 1 /* ELEMENT */: for (let i = 0; i < node.props.length; i++) { const p = node.props[i]; if (p.type === 7 /* DIRECTIVE */ && (hasScopeRef(p.arg, ids) || hasScopeRef(p.exp, ids))) { return true; } } return node.children.some(c => hasScopeRef(c, ids)); case 11 /* FOR */: if (hasScopeRef(node.source, ids)) { return true; } return node.children.some(c => hasScopeRef(c, ids)); case 9 /* IF */: return node.branches.some(b => hasScopeRef(b, ids)); case 10 /* IF_BRANCH */: if (hasScopeRef(node.condition, ids)) { return true; } return node.children.some(c => hasScopeRef(c, ids)); case 4 /* SIMPLE_EXPRESSION */: return (!node.isStatic && isSimpleIdentifier(node.content) && !!ids[node.content]); case 8 /* COMPOUND_EXPRESSION */: return node.children.some(c => isObject(c) && hasScopeRef(c, ids)); case 5 /* INTERPOLATION */: case 12 /* TEXT_CALL */: return hasScopeRef(node.content, ids); case 2 /* TEXT */: case 3 /* COMMENT */: return false; default: return false; } } 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)) { 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 (Array.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 (!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) { // ignore comments in production /* istanbul ignore next */ if ( node.type === 3 /* COMMENT */) { return; } 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) { advanceBy(context, 9); const nodes = parseChildren(context, 3 /* CDATA */, ancestors); if (context.source.length === 0) { emitError(context, 9 /* EOF_IN_CDATA */); } else { advanceBy(context, 3); } return nodes; } function parseComment(context) { 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) { 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) { // 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) { // 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 (/^[A-Z]/.test(tag)) tagType = 1 /* COMPONENT */; } if (tag === 'slot') tagType = 2 /* SLOT */; else if (tag === 'template') tagType = 3 /* TEMPLATE */; else if (tag === 'portal' || tag === 'Portal') tagType = 4 /* PORTAL */; else if (tag === 'suspense' || tag === 'Suspense') tagType = 5 /* SUSPENSE */; } 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) { // 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; 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) { 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)); 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; 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(child)) { 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, contex