2021-03-01 15:26:05 +08:00

2529 lines
95 KiB
JavaScript

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var CompilerDOM = require('@vue/compiler-dom');
var sourceMap = require('source-map');
var hash = require('hash-sum');
var path = require('path');
var compilerCore = require('@vue/compiler-core');
var url = require('url');
var shared = require('@vue/shared');
var CompilerSSR = require('@vue/compiler-ssr');
var postcss = require('postcss');
var selectorParser = require('postcss-selector-parser');
var merge = require('merge-source-map');
var MagicString = require('magic-string');
var parser = require('@babel/parser');
var estreeWalker = require('estree-walker');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e['default'] : e; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
n[k] = e[k];
});
}
n['default'] = e;
return Object.freeze(n);
}
var CompilerDOM__namespace = /*#__PURE__*/_interopNamespace(CompilerDOM);
var hash__default = /*#__PURE__*/_interopDefaultLegacy(hash);
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
var CompilerSSR__namespace = /*#__PURE__*/_interopNamespace(CompilerSSR);
var postcss__default = /*#__PURE__*/_interopDefaultLegacy(postcss);
var selectorParser__default = /*#__PURE__*/_interopDefaultLegacy(selectorParser);
var merge__default = /*#__PURE__*/_interopDefaultLegacy(merge);
var MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString);
const CSS_VARS_HELPER = `useCssVars`;
const cssVarRE = /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g;
function genCssVarsFromList(vars, id, isProd) {
return `{\n ${vars
.map(key => `"${genVarName(id, key, isProd)}": (${key})`)
.join(',\n ')}\n}`;
}
function genVarName(id, raw, isProd) {
if (isProd) {
return hash__default(id + raw);
}
else {
return `${id}-${raw.replace(/([^\w-])/g, '_')}`;
}
}
function parseCssVars(sfc) {
const vars = [];
sfc.styles.forEach(style => {
let match;
while ((match = cssVarRE.exec(style.content))) {
vars.push(match[1] || match[2] || match[3]);
}
});
return vars;
}
const cssVarsPlugin = opts => {
const { id, isProd } = opts;
return {
postcssPlugin: 'vue-sfc-vars',
Declaration(decl) {
// rewrite CSS variables
if (cssVarRE.test(decl.value)) {
decl.value = decl.value.replace(cssVarRE, (_, $1, $2, $3) => {
return `var(--${genVarName(id, $1 || $2 || $3, isProd)})`;
});
}
}
};
};
cssVarsPlugin.postcss = true;
function genCssVarsCode(vars, bindings, id, isProd) {
const varsExp = genCssVarsFromList(vars, id, isProd);
const exp = CompilerDOM.createSimpleExpression(varsExp, false);
const context = CompilerDOM.createTransformContext(CompilerDOM.createRoot([]), {
prefixIdentifiers: true,
inline: true,
bindingMetadata: bindings
});
const transformed = CompilerDOM.processExpression(exp, context);
const transformedString = transformed.type === 4 /* SIMPLE_EXPRESSION */
? transformed.content
: transformed.children
.map(c => {
return typeof c === 'string'
? c
: c.content;
})
.join('');
return `_${CSS_VARS_HELPER}(_ctx => (${transformedString}))`;
}
// <script setup> already gets the calls injected as part of the transform
// this is only for single normal <script>
function genNormalScriptCssVarsCode(cssVars, bindings, id, isProd) {
return (`\nimport { ${CSS_VARS_HELPER} as _${CSS_VARS_HELPER} } from 'vue'\n` +
`const __injectCSSVars__ = () => {\n${genCssVarsCode(cssVars, bindings, id, isProd)}}\n` +
`const __setup__ = __default__.setup\n` +
`__default__.setup = __setup__\n` +
` ? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }\n` +
` : __injectCSSVars__\n`);
}
const hasWarned = {};
function warnOnce(msg) {
const isNodeProd = typeof process !== 'undefined' && process.env.NODE_ENV === 'production';
if (!isNodeProd && !false && !hasWarned[msg]) {
hasWarned[msg] = true;
warn(msg);
}
}
function warn(msg) {
console.warn(`\x1b[1m\x1b[33m[@vue/compiler-sfc]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`);
}
function warnExperimental(feature, rfcId) {
warnOnce(`${feature} is still an experimental proposal.\n` +
`Follow its status at https://github.com/vuejs/rfcs/pull/${rfcId}.`);
warnOnce(`When using experimental features,\n` +
`it is recommended to pin your vue dependencies to exact versions to avoid breakage.`);
}
const SFC_CACHE_MAX_SIZE = 500;
const sourceToSFC = new (require('lru-cache'))(SFC_CACHE_MAX_SIZE);
function parse(source, { sourceMap = true, filename = 'anonymous.vue', sourceRoot = '', pad = false, compiler = CompilerDOM__namespace } = {}) {
const sourceKey = source + sourceMap + filename + sourceRoot + pad + compiler.parse;
const cache = sourceToSFC.get(sourceKey);
if (cache) {
return cache;
}
const descriptor = {
filename,
source,
template: null,
script: null,
scriptSetup: null,
styles: [],
customBlocks: [],
cssVars: []
};
const errors = [];
const ast = compiler.parse(source, {
// there are no components at SFC parsing level
isNativeTag: () => true,
// preserve all whitespaces
isPreTag: () => true,
getTextMode: ({ tag, props }, parent) => {
// all top level elements except <template> are parsed as raw text
// containers
if ((!parent && tag !== 'template') ||
// <template lang="xxx"> should also be treated as raw text
(tag === 'template' &&
props.some(p => p.type === 6 /* ATTRIBUTE */ &&
p.name === 'lang' &&
p.value &&
p.value.content &&
p.value.content !== 'html'))) {
return 2 /* RAWTEXT */;
}
else {
return 0 /* DATA */;
}
},
onError: e => {
errors.push(e);
}
});
ast.children.forEach(node => {
if (node.type !== 1 /* ELEMENT */) {
return;
}
if (!node.children.length && !hasSrc(node) && node.tag !== 'template') {
return;
}
switch (node.tag) {
case 'template':
if (!descriptor.template) {
const templateBlock = (descriptor.template = createBlock(node, source, false));
templateBlock.ast = node;
}
else {
errors.push(createDuplicateBlockError(node));
}
break;
case 'script':
const scriptBlock = createBlock(node, source, pad);
const isSetup = !!scriptBlock.attrs.setup;
if (isSetup && !descriptor.scriptSetup) {
descriptor.scriptSetup = scriptBlock;
break;
}
if (!isSetup && !descriptor.script) {
descriptor.script = scriptBlock;
break;
}
errors.push(createDuplicateBlockError(node, isSetup));
break;
case 'style':
const styleBlock = createBlock(node, source, pad);
if (styleBlock.attrs.vars) {
errors.push(new SyntaxError(`<style vars> has been replaced by a new proposal: ` +
`https://github.com/vuejs/rfcs/pull/231`));
}
descriptor.styles.push(styleBlock);
break;
default:
descriptor.customBlocks.push(createBlock(node, source, pad));
break;
}
});
if (descriptor.scriptSetup) {
if (descriptor.scriptSetup.src) {
errors.push(new SyntaxError(`<script setup> cannot use the "src" attribute because ` +
`its syntax will be ambiguous outside of the component.`));
descriptor.scriptSetup = null;
}
if (descriptor.script && descriptor.script.src) {
errors.push(new SyntaxError(`<script> cannot use the "src" attribute when <script setup> is ` +
`also present because they must be processed together.`));
descriptor.script = null;
}
}
if (sourceMap) {
const genMap = (block) => {
if (block && !block.src) {
block.map = generateSourceMap(filename, source, block.content, sourceRoot, !pad || block.type === 'template' ? block.loc.start.line - 1 : 0);
}
};
genMap(descriptor.template);
genMap(descriptor.script);
descriptor.styles.forEach(genMap);
descriptor.customBlocks.forEach(genMap);
}
// parse CSS vars
descriptor.cssVars = parseCssVars(descriptor);
if (descriptor.cssVars.length) {
warnExperimental(`v-bind() CSS variable injection`, 231);
}
const result = {
descriptor,
errors
};
sourceToSFC.set(sourceKey, result);
return result;
}
function createDuplicateBlockError(node, isScriptSetup = false) {
const err = new SyntaxError(`Single file component can contain only one <${node.tag}${isScriptSetup ? ` setup` : ``}> element`);
err.loc = node.loc;
return err;
}
function createBlock(node, source, pad) {
const type = node.tag;
let { start, end } = node.loc;
let content = '';
if (node.children.length) {
start = node.children[0].loc.start;
end = node.children[node.children.length - 1].loc.end;
content = source.slice(start.offset, end.offset);
}
const loc = {
source: content,
start,
end
};
const attrs = {};
const block = {
type,
content,
loc,
attrs
};
if (pad) {
block.content = padContent(source, block, pad) + block.content;
}
node.props.forEach(p => {
if (p.type === 6 /* ATTRIBUTE */) {
attrs[p.name] = p.value ? p.value.content || true : true;
if (p.name === 'lang') {
block.lang = p.value && p.value.content;
}
else if (p.name === 'src') {
block.src = p.value && p.value.content;
}
else if (type === 'style') {
if (p.name === 'scoped') {
block.scoped = true;
}
else if (p.name === 'module') {
block.module = attrs[p.name];
}
}
else if (type === 'script' && p.name === 'setup') {
block.setup = attrs.setup;
}
}
});
return block;
}
const splitRE = /\r?\n/g;
const emptyRE = /^(?:\/\/)?\s*$/;
const replaceRE = /./g;
function generateSourceMap(filename, source, generated, sourceRoot, lineOffset) {
const map = new sourceMap.SourceMapGenerator({
file: filename.replace(/\\/g, '/'),
sourceRoot: sourceRoot.replace(/\\/g, '/')
});
map.setSourceContent(filename, source);
generated.split(splitRE).forEach((line, index) => {
if (!emptyRE.test(line)) {
const originalLine = index + 1 + lineOffset;
const generatedLine = index + 1;
for (let i = 0; i < line.length; i++) {
if (!/\s/.test(line[i])) {
map.addMapping({
source: filename,
original: {
line: originalLine,
column: i
},
generated: {
line: generatedLine,
column: i
}
});
}
}
}
});
return JSON.parse(map.toString());
}
function padContent(content, block, pad) {
content = content.slice(0, block.loc.start.offset);
if (pad === 'space') {
return content.replace(replaceRE, ' ');
}
else {
const offset = content.split(splitRE).length;
const padChar = block.type === 'script' && !block.lang ? '//\n' : '\n';
return Array(offset).join(padChar);
}
}
function hasSrc(node) {
return node.props.some(p => {
if (p.type !== 6 /* ATTRIBUTE */) {
return false;
}
return p.name === 'src';
});
}
function isRelativeUrl(url) {
const firstChar = url.charAt(0);
return firstChar === '.' || firstChar === '~' || firstChar === '@';
}
const externalRE = /^https?:\/\//;
function isExternalUrl(url) {
return externalRE.test(url);
}
const dataUrlRE = /^\s*data:/i;
function isDataUrl(url) {
return dataUrlRE.test(url);
}
/**
* Parses string url into URL object.
*/
function parseUrl(url) {
const firstChar = url.charAt(0);
if (firstChar === '~') {
const secondChar = url.charAt(1);
url = url.slice(secondChar === '/' ? 2 : 1);
}
return parseUriParts(url);
}
/**
* vuejs/component-compiler-utils#22 Support uri fragment in transformed require
* @param urlString an url as a string
*/
function parseUriParts(urlString) {
// A TypeError is thrown if urlString is not a string
// @see https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost
return url.parse(shared.isString(urlString) ? urlString : '', false, true);
}
const defaultAssetUrlOptions = {
base: null,
includeAbsolute: false,
tags: {
video: ['src', 'poster'],
source: ['src'],
img: ['src'],
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href']
}
};
const normalizeOptions = (options) => {
if (Object.keys(options).some(key => shared.isArray(options[key]))) {
// legacy option format which directly passes in tags config
return {
...defaultAssetUrlOptions,
tags: options
};
}
return {
...defaultAssetUrlOptions,
...options
};
};
const createAssetUrlTransformWithOptions = (options) => {
return (node, context) => transformAssetUrl(node, context, options);
};
/**
* A `@vue/compiler-core` plugin that transforms relative asset urls into
* either imports or absolute urls.
*
* ``` js
* // Before
* createVNode('img', { src: './logo.png' })
*
* // After
* import _imports_0 from './logo.png'
* createVNode('img', { src: _imports_0 })
* ```
*/
const transformAssetUrl = (node, context, options = defaultAssetUrlOptions) => {
if (node.type === 1 /* ELEMENT */) {
if (!node.props.length) {
return;
}
const tags = options.tags || defaultAssetUrlOptions.tags;
const attrs = tags[node.tag];
const wildCardAttrs = tags['*'];
if (!attrs && !wildCardAttrs) {
return;
}
const assetAttrs = (attrs || []).concat(wildCardAttrs || []);
node.props.forEach((attr, index) => {
if (attr.type !== 6 /* ATTRIBUTE */ ||
!assetAttrs.includes(attr.name) ||
!attr.value ||
isExternalUrl(attr.value.content) ||
isDataUrl(attr.value.content) ||
attr.value.content[0] === '#' ||
(!options.includeAbsolute && !isRelativeUrl(attr.value.content))) {
return;
}
const url = parseUrl(attr.value.content);
if (options.base && attr.value.content[0] === '.') {
// explicit base - directly rewrite relative urls into absolute url
// to avoid generating extra imports
// Allow for full hostnames provided in options.base
const base = parseUrl(options.base);
const protocol = base.protocol || '';
const host = base.host ? protocol + '//' + base.host : '';
const basePath = base.path || '/';
// when packaged in the browser, path will be using the posix-
// only version provided by rollup-plugin-node-builtins.
attr.value.content =
host +
(path__default.posix || path__default).join(basePath, url.path + (url.hash || ''));
return;
}
// otherwise, transform the url into an import.
// this assumes a bundler will resolve the import into the correct
// absolute url (e.g. webpack file-loader)
const exp = getImportsExpressionExp(url.path, url.hash, attr.loc, context);
node.props[index] = {
type: 7 /* DIRECTIVE */,
name: 'bind',
arg: compilerCore.createSimpleExpression(attr.name, true, attr.loc),
exp,
modifiers: [],
loc: attr.loc
};
});
}
};
function getImportsExpressionExp(path, hash, loc, context) {
if (path) {
const existing = context.imports.find(i => i.path === path);
if (existing) {
return existing.exp;
}
const name = `_imports_${context.imports.length}`;
const exp = compilerCore.createSimpleExpression(name, false, loc, 2 /* CAN_HOIST */);
context.imports.push({ exp, path });
if (hash && path) {
return context.hoist(compilerCore.createSimpleExpression(`${name} + '${hash}'`, false, loc, 2 /* CAN_HOIST */));
}
else {
return exp;
}
}
else {
return compilerCore.createSimpleExpression(`''`, false, loc, 2 /* CAN_HOIST */);
}
}
const srcsetTags = ['img', 'source'];
// http://w3c.github.io/html/semantics-embedded-content.html#ref-for-image-candidate-string-5
const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g;
const createSrcsetTransformWithOptions = (options) => {
return (node, context) => transformSrcset(node, context, options);
};
const transformSrcset = (node, context, options = defaultAssetUrlOptions) => {
if (node.type === 1 /* ELEMENT */) {
if (srcsetTags.includes(node.tag) && node.props.length) {
node.props.forEach((attr, index) => {
if (attr.name === 'srcset' && attr.type === 6 /* ATTRIBUTE */) {
if (!attr.value)
return;
const value = attr.value.content;
if (!value)
return;
const imageCandidates = value.split(',').map(s => {
// The attribute value arrives here with all whitespace, except
// normal spaces, represented by escape sequences
const [url, descriptor] = s
.replace(escapedSpaceCharacters, ' ')
.trim()
.split(' ', 2);
return { url, descriptor };
});
// for data url need recheck url
for (let i = 0; i < imageCandidates.length; i++) {
if (imageCandidates[i].url.trim().startsWith('data:')) {
imageCandidates[i + 1].url =
imageCandidates[i].url + ',' + imageCandidates[i + 1].url;
imageCandidates.splice(i, 1);
}
}
// When srcset does not contain any relative URLs, skip transforming
if (!options.includeAbsolute &&
!imageCandidates.some(({ url }) => isRelativeUrl(url))) {
return;
}
if (options.base) {
const base = options.base;
const set = [];
imageCandidates.forEach(({ url, descriptor }) => {
descriptor = descriptor ? ` ${descriptor}` : ``;
if (isRelativeUrl(url)) {
set.push((path__default.posix || path__default).join(base, url) + descriptor);
}
else {
set.push(url + descriptor);
}
});
attr.value.content = set.join(', ');
return;
}
const compoundExpression = compilerCore.createCompoundExpression([], attr.loc);
imageCandidates.forEach(({ url, descriptor }, index) => {
if (!isExternalUrl(url) &&
!isDataUrl(url) &&
(options.includeAbsolute || isRelativeUrl(url))) {
const { path } = parseUrl(url);
let exp;
if (path) {
const existingImportsIndex = context.imports.findIndex(i => i.path === path);
if (existingImportsIndex > -1) {
exp = compilerCore.createSimpleExpression(`_imports_${existingImportsIndex}`, false, attr.loc, 2 /* CAN_HOIST */);
}
else {
exp = compilerCore.createSimpleExpression(`_imports_${context.imports.length}`, false, attr.loc, 2 /* CAN_HOIST */);
context.imports.push({ exp, path });
}
compoundExpression.children.push(exp);
}
}
else {
const exp = compilerCore.createSimpleExpression(`"${url}"`, false, attr.loc, 2 /* CAN_HOIST */);
compoundExpression.children.push(exp);
}
const isNotLast = imageCandidates.length - 1 > index;
if (descriptor && isNotLast) {
compoundExpression.children.push(` + '${descriptor}, ' + `);
}
else if (descriptor) {
compoundExpression.children.push(` + '${descriptor}'`);
}
else if (isNotLast) {
compoundExpression.children.push(` + ', ' + `);
}
});
const hoisted = context.hoist(compoundExpression);
hoisted.constType = 2 /* CAN_HOIST */;
node.props[index] = {
type: 7 /* DIRECTIVE */,
name: 'bind',
arg: compilerCore.createSimpleExpression('srcset', true, attr.loc),
exp: hoisted,
modifiers: [],
loc: attr.loc
};
}
});
}
}
};
function preprocess({ source, filename, preprocessOptions }, preprocessor) {
// Consolidate exposes a callback based API, but the callback is in fact
// called synchronously for most templating engines. In our case, we have to
// expose a synchronous API so that it is usable in Jest transforms (which
// have to be sync because they are applied via Node.js require hooks)
let res = '';
let err = null;
preprocessor.render(source, { filename, ...preprocessOptions }, (_err, _res) => {
if (_err)
err = _err;
res = _res;
});
if (err)
throw err;
return res;
}
function compileTemplate(options) {
const { preprocessLang, preprocessCustomRequire } = options;
const preprocessor = preprocessLang
? preprocessCustomRequire
? preprocessCustomRequire(preprocessLang)
: require('consolidate')[preprocessLang]
: false;
if (preprocessor) {
try {
return doCompileTemplate({
...options,
source: preprocess(options, preprocessor)
});
}
catch (e) {
return {
code: `export default function render() {}`,
source: options.source,
tips: [],
errors: [e]
};
}
}
else if (preprocessLang) {
return {
code: `export default function render() {}`,
source: options.source,
tips: [
`Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
],
errors: [
`Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
]
};
}
else {
return doCompileTemplate(options);
}
}
function doCompileTemplate({ filename, id, scoped, inMap, source, ssr = false, ssrCssVars, isProd = false, compiler = ssr ? CompilerSSR__namespace : CompilerDOM__namespace, compilerOptions = {}, transformAssetUrls }) {
const errors = [];
let nodeTransforms = [];
if (shared.isObject(transformAssetUrls)) {
const assetOptions = normalizeOptions(transformAssetUrls);
nodeTransforms = [
createAssetUrlTransformWithOptions(assetOptions),
createSrcsetTransformWithOptions(assetOptions)
];
}
else if (transformAssetUrls !== false) {
nodeTransforms = [transformAssetUrl, transformSrcset];
}
if (ssr && !ssrCssVars) {
warnOnce(`compileTemplate is called with \`ssr: true\` but no ` +
`corresponding \`cssVars\` option.\`.`);
}
if (!id) {
warnOnce(`compileTemplate now requires the \`id\` option.\`.`);
id = '';
}
const shortId = id.replace(/^data-v-/, '');
const longId = `data-v-${shortId}`;
let { code, ast, preamble, map } = compiler.compile(source, {
mode: 'module',
prefixIdentifiers: true,
hoistStatic: true,
cacheHandlers: true,
ssrCssVars: ssr && ssrCssVars && ssrCssVars.length
? genCssVarsFromList(ssrCssVars, shortId, isProd)
: '',
scopeId: scoped ? longId : undefined,
...compilerOptions,
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,
sourceMap: true,
onError: e => errors.push(e)
});
// inMap should be the map produced by ./parse.ts which is a simple line-only
// mapping. If it is present, we need to adjust the final map and errors to
// reflect the original line numbers.
if (inMap) {
if (map) {
map = mapLines(inMap, map);
}
if (errors.length) {
patchErrors(errors, source, inMap);
}
}
return { code, ast, preamble, source, errors, tips: [], map };
}
function mapLines(oldMap, newMap) {
if (!oldMap)
return newMap;
if (!newMap)
return oldMap;
const oldMapConsumer = new sourceMap.SourceMapConsumer(oldMap);
const newMapConsumer = new sourceMap.SourceMapConsumer(newMap);
const mergedMapGenerator = new sourceMap.SourceMapGenerator();
newMapConsumer.eachMapping(m => {
if (m.originalLine == null) {
return;
}
const origPosInOldMap = oldMapConsumer.originalPositionFor({
line: m.originalLine,
column: m.originalColumn
});
if (origPosInOldMap.source == null) {
return;
}
mergedMapGenerator.addMapping({
generated: {
line: m.generatedLine,
column: m.generatedColumn
},
original: {
line: origPosInOldMap.line,
// use current column, since the oldMap produced by @vue/compiler-sfc
// does not
column: m.originalColumn
},
source: origPosInOldMap.source,
name: origPosInOldMap.name
});
});
// source-map's type definition is incomplete
const generator = mergedMapGenerator;
oldMapConsumer.sources.forEach((sourceFile) => {
generator._sources.add(sourceFile);
const sourceContent = oldMapConsumer.sourceContentFor(sourceFile);
if (sourceContent != null) {
mergedMapGenerator.setSourceContent(sourceFile, sourceContent);
}
});
generator._sourceRoot = oldMap.sourceRoot;
generator._file = oldMap.file;
return generator.toJSON();
}
function patchErrors(errors, source, inMap) {
const originalSource = inMap.sourcesContent[0];
const offset = originalSource.indexOf(source);
const lineOffset = originalSource.slice(0, offset).split(/\r?\n/).length - 1;
errors.forEach(err => {
if (err.loc) {
err.loc.start.line += lineOffset;
err.loc.start.offset += offset;
if (err.loc.end !== err.loc.start) {
err.loc.end.line += lineOffset;
err.loc.end.offset += offset;
}
}
});
}
const trimPlugin = () => {
return {
postcssPlugin: 'vue-sfc-trim',
Once(root) {
root.walk(({ type, raws }) => {
if (type === 'rule' || type === 'atrule') {
if (raws.before)
raws.before = '\n';
if ('after' in raws && raws.after)
raws.after = '\n';
}
});
}
};
};
trimPlugin.postcss = true;
const animationNameRE = /^(-\w+-)?animation-name$/;
const animationRE = /^(-\w+-)?animation$/;
const scopedPlugin = (id = '') => {
const keyframes = Object.create(null);
const shortId = id.replace(/^data-v-/, '');
return {
postcssPlugin: 'vue-sfc-scoped',
Rule(rule) {
processRule(id, rule);
},
AtRule(node) {
if (/-?keyframes$/.test(node.name) &&
!node.params.endsWith(`-${shortId}`)) {
// register keyframes
keyframes[node.params] = node.params = node.params + '-' + shortId;
}
},
OnceExit(root) {
if (Object.keys(keyframes).length) {
// If keyframes are found in this <style>, find and rewrite animation names
// in declarations.
// Caveat: this only works for keyframes and animation rules in the same
// <style> element.
// individual animation-name declaration
root.walkDecls(decl => {
if (animationNameRE.test(decl.prop)) {
decl.value = decl.value
.split(',')
.map(v => keyframes[v.trim()] || v.trim())
.join(',');
}
// shorthand
if (animationRE.test(decl.prop)) {
decl.value = decl.value
.split(',')
.map(v => {
const vals = v.trim().split(/\s+/);
const i = vals.findIndex(val => keyframes[val]);
if (i !== -1) {
vals.splice(i, 1, keyframes[vals[i]]);
return vals.join(' ');
}
else {
return v;
}
})
.join(',');
}
});
}
}
};
};
const processedRules = new WeakSet();
function processRule(id, rule) {
if (processedRules.has(rule)) {
return;
}
processedRules.add(rule);
rule.selector = selectorParser__default(selectorRoot => {
selectorRoot.each(selector => {
rewriteSelector(id, selector, selectorRoot);
});
}).processSync(rule.selector);
}
function rewriteSelector(id, selector, selectorRoot, slotted = false) {
let node = null;
let shouldInject = true;
// find the last child node to insert attribute selector
selector.each(n => {
// DEPRECATED ">>>" and "/deep/" combinator
if (n.type === 'combinator' &&
(n.value === '>>>' || n.value === '/deep/')) {
n.value = ' ';
n.spaces.before = n.spaces.after = '';
warn(`the >>> and /deep/ combinators have been deprecated. ` +
`Use :deep() instead.`);
return false;
}
if (n.type === 'pseudo') {
const { value } = n;
// deep: inject [id] attribute at the node before the ::v-deep
// combinator.
if (value === ':deep' || value === '::v-deep') {
if (n.nodes.length) {
// .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
// replace the current node with ::v-deep's inner selector
let last = n;
n.nodes[0].each(ss => {
selector.insertAfter(last, ss);
last = ss;
});
// insert a space combinator before if it doesn't already have one
const prev = selector.at(selector.index(n) - 1);
if (!prev || !isSpaceCombinator(prev)) {
selector.insertAfter(n, selectorParser__default.combinator({
value: ' '
}));
}
selector.removeChild(n);
}
else {
// DEPRECATED usage
// .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
warn(`::v-deep usage as a combinator has ` +
`been deprecated. Use :deep(<inner-selector>) instead.`);
const prev = selector.at(selector.index(n) - 1);
if (prev && isSpaceCombinator(prev)) {
selector.removeChild(prev);
}
selector.removeChild(n);
}
return false;
}
// slot: use selector inside `::v-slotted` and inject [id + '-s']
// instead.
// ::v-slotted(.foo) -> .foo[xxxxxxx-s]
if (value === ':slotted' || value === '::v-slotted') {
rewriteSelector(id, n.nodes[0], selectorRoot, true /* slotted */);
let last = n;
n.nodes[0].each(ss => {
selector.insertAfter(last, ss);
last = ss;
});
// selector.insertAfter(n, n.nodes[0])
selector.removeChild(n);
// since slotted attribute already scopes the selector there's no
// need for the non-slot attribute.
shouldInject = false;
return false;
}
// global: replace with inner selector and do not inject [id].
// ::v-global(.foo) -> .foo
if (value === ':global' || value === '::v-global') {
selectorRoot.insertAfter(selector, n.nodes[0]);
selectorRoot.removeChild(selector);
return false;
}
}
if (n.type !== 'pseudo' && n.type !== 'combinator') {
node = n;
}
});
if (node) {
node.spaces.after = '';
}
else {
// For deep selectors & standalone pseudo selectors,
// the attribute selectors are prepended rather than appended.
// So all leading spaces must be eliminated to avoid problems.
selector.first.spaces.before = '';
}
if (shouldInject) {
const idToAdd = slotted ? id + '-s' : id;
selector.insertAfter(
// If node is null it means we need to inject [id] at the start
// insertAfter can handle `null` here
node, selectorParser__default.attribute({
attribute: idToAdd,
value: idToAdd,
raws: {},
quoteMark: `"`
}));
}
}
function isSpaceCombinator(node) {
return node.type === 'combinator' && /^\s+$/.test(node.value);
}
scopedPlugin.postcss = true;
// .scss/.sass processor
const scss = (source, map, options, load = require) => {
const nodeSass = load('sass');
const finalOptions = {
...options,
data: getSource(source, options.filename, options.additionalData),
file: options.filename,
outFile: options.filename,
sourceMap: !!map
};
try {
const result = nodeSass.renderSync(finalOptions);
const dependencies = result.stats.includedFiles;
if (map) {
return {
code: result.css.toString(),
map: merge__default(map, JSON.parse(result.map.toString())),
errors: [],
dependencies
};
}
return { code: result.css.toString(), errors: [], dependencies };
}
catch (e) {
return { code: '', errors: [e], dependencies: [] };
}
};
const sass = (source, map, options, load) => scss(source, map, {
...options,
indentedSyntax: true
}, load);
// .less
const less = (source, map, options, load = require) => {
const nodeLess = load('less');
let result;
let error = null;
nodeLess.render(getSource(source, options.filename, options.additionalData), { ...options, syncImport: true }, (err, output) => {
error = err;
result = output;
});
if (error)
return { code: '', errors: [error], dependencies: [] };
const dependencies = result.imports;
if (map) {
return {
code: result.css.toString(),
map: merge__default(map, result.map),
errors: [],
dependencies: dependencies
};
}
return {
code: result.css.toString(),
errors: [],
dependencies: dependencies
};
};
// .styl
const styl = (source, map, options, load = require) => {
const nodeStylus = load('stylus');
try {
const ref = nodeStylus(source);
Object.keys(options).forEach(key => ref.set(key, options[key]));
if (map)
ref.set('sourcemap', { inline: false, comment: false });
const result = ref.render();
const dependencies = ref.deps();
if (map) {
return {
code: result,
map: merge__default(map, ref.sourcemap),
errors: [],
dependencies
};
}
return { code: result, errors: [], dependencies };
}
catch (e) {
return { code: '', errors: [e], dependencies: [] };
}
};
function getSource(source, filename, additionalData) {
if (!additionalData)
return source;
if (shared.isFunction(additionalData)) {
return additionalData(source, filename);
}
return additionalData + source;
}
const processors = {
less,
sass,
scss,
styl,
stylus: styl
};
function compileStyle(options) {
return doCompileStyle({
...options,
isAsync: false
});
}
function compileStyleAsync(options) {
return doCompileStyle({ ...options, isAsync: true });
}
function doCompileStyle(options) {
const { filename, id, scoped = false, trim = true, isProd = false, modules = false, modulesOptions = {}, preprocessLang, postcssOptions, postcssPlugins } = options;
const preprocessor = preprocessLang && processors[preprocessLang];
const preProcessedSource = preprocessor && preprocess$1(options, preprocessor);
const map = preProcessedSource
? preProcessedSource.map
: options.inMap || options.map;
const source = preProcessedSource ? preProcessedSource.code : options.source;
const shortId = id.replace(/^data-v-/, '');
const longId = `data-v-${shortId}`;
const plugins = (postcssPlugins || []).slice();
plugins.unshift(cssVarsPlugin({ id: shortId, isProd }));
if (trim) {
plugins.push(trimPlugin());
}
if (scoped) {
plugins.push(scopedPlugin(longId));
}
let cssModules;
if (modules) {
if (!options.isAsync) {
throw new Error('[@vue/compiler-sfc] `modules` option can only be used with compileStyleAsync().');
}
plugins.push(require('postcss-modules')({
...modulesOptions,
getJSON: (_cssFileName, json) => {
cssModules = json;
}
}));
}
const postCSSOptions = {
...postcssOptions,
to: filename,
from: filename
};
if (map) {
postCSSOptions.map = {
inline: false,
annotation: false,
prev: map
};
}
let result;
let code;
let outMap;
// stylus output include plain css. so need remove the repeat item
const dependencies = new Set(preProcessedSource ? preProcessedSource.dependencies : []);
// sass has filename self when provided filename option
dependencies.delete(filename);
const errors = [];
if (preProcessedSource && preProcessedSource.errors.length) {
errors.push(...preProcessedSource.errors);
}
const recordPlainCssDependencies = (messages) => {
messages.forEach(msg => {
if (msg.type === 'dependency') {
// postcss output path is absolute position path
dependencies.add(msg.file);
}
});
return dependencies;
};
try {
result = postcss__default(plugins).process(source, postCSSOptions);
// In async mode, return a promise.
if (options.isAsync) {
return result
.then(result => ({
code: result.css || '',
map: result.map && result.map.toJSON(),
errors,
modules: cssModules,
rawResult: result,
dependencies: recordPlainCssDependencies(result.messages)
}))
.catch(error => ({
code: '',
map: undefined,
errors: [...errors, error],
rawResult: undefined,
dependencies
}));
}
recordPlainCssDependencies(result.messages);
// force synchronous transform (we know we only have sync plugins)
code = result.css;
outMap = result.map;
}
catch (e) {
errors.push(e);
}
return {
code: code || ``,
map: outMap && outMap.toJSON(),
errors,
rawResult: result,
dependencies
};
}
function preprocess$1(options, preprocessor) {
return preprocessor(options.source, options.inMap || options.map, {
filename: options.filename,
...options.preprocessOptions
}, options.preprocessCustomRequire);
}
const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/;
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)as(\s*)default/;
/**
* Utility for rewriting `export default` in a script block into a variable
* declaration so that we can inject things into it
*/
function rewriteDefault(input, as, parserPlugins) {
if (!hasDefaultExport(input)) {
return input + `\nconst ${as} = {}`;
}
const replaced = input.replace(defaultExportRE, `$1const ${as} =`);
if (!hasDefaultExport(replaced)) {
return replaced;
}
// if the script somehow still contains `default export`, it probably has
// multi-line comments or template strings. fallback to a full parse.
const s = new MagicString__default(input);
const ast = parser.parse(input, {
sourceType: 'module',
plugins: parserPlugins
}).program.body;
ast.forEach(node => {
if (node.type === 'ExportDefaultDeclaration') {
s.overwrite(node.start, node.declaration.start, `const ${as} = `);
}
if (node.type === 'ExportNamedDeclaration') {
node.specifiers.forEach(specifier => {
if (specifier.type === 'ExportSpecifier' &&
specifier.exported.type === 'Identifier' &&
specifier.exported.name === 'default') {
const end = specifier.end;
s.overwrite(specifier.start, input.charAt(end) === ',' ? end + 1 : end, ``);
s.append(`\nconst ${as} = ${specifier.local.name}`);
}
});
}
});
return s.toString();
}
function hasDefaultExport(input) {
return defaultExportRE.test(input) || namedDefaultExportRE.test(input);
}
const DEFINE_PROPS = 'defineProps';
const DEFINE_EMIT = 'defineEmit';
/**
* Compile `<script setup>`
* It requires the whole SFC descriptor because we need to handle and merge
* normal `<script>` + `<script setup>` if both are present.
*/
function compileScript(sfc, options) {
const { script, scriptSetup, source, filename } = sfc;
if (scriptSetup) {
warnExperimental(`<script setup>`, 227);
}
// for backwards compat
if (!options) {
options = { id: '' };
}
if (!options.id) {
warnOnce(`compileScript now requires passing the \`id\` option.\n` +
`Upgrade your vite or vue-loader version for compatibility with ` +
`the latest experimental proposals.`);
}
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : '';
const cssVars = sfc.cssVars;
const hasInheritAttrsFlag = sfc.template && sfc.template.attrs['inherit-attrs'] === 'false';
const scriptLang = script && script.lang;
const scriptSetupLang = scriptSetup && scriptSetup.lang;
const isTS = scriptLang === 'ts' || scriptSetupLang === 'ts';
const plugins = [...shared.babelParserDefaultPlugins, 'jsx'];
if (options.babelParserPlugins)
plugins.push(...options.babelParserPlugins);
if (isTS)
plugins.push('typescript', 'decorators-legacy');
if (!scriptSetup) {
if (!script) {
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`);
}
if (scriptLang && scriptLang !== 'ts') {
// do not process non js/ts script blocks
return script;
}
try {
const scriptAst = parser.parse(script.content, {
plugins,
sourceType: 'module'
}).program.body;
const bindings = analyzeScriptBindings(scriptAst);
const needRewrite = cssVars.length || hasInheritAttrsFlag;
let content = script.content;
if (needRewrite) {
content = rewriteDefault(content, `__default__`, plugins);
if (cssVars.length) {
content += genNormalScriptCssVarsCode(cssVars, bindings, scopeId, !!options.isProd);
}
if (hasInheritAttrsFlag) {
content += `__default__.inheritAttrs = false`;
}
content += `\nexport default __default__`;
}
return {
...script,
content,
bindings,
scriptAst
};
}
catch (e) {
// silently fallback if parse fails since user may be using custom
// babel syntax
return script;
}
}
if (script && scriptLang !== scriptSetupLang) {
throw new Error(`[@vue/compiler-sfc] <script> and <script setup> must have the same language type.`);
}
if (scriptSetupLang && scriptSetupLang !== 'ts') {
// do not process non js/ts script blocks
return scriptSetup;
}
const defaultTempVar = `__default__`;
const bindingMetadata = {};
const helperImports = new Set();
const userImports = Object.create(null);
const userImportAlias = Object.create(null);
const setupBindings = Object.create(null);
const refBindings = Object.create(null);
const refIdentifiers = new Set();
const enableRefSugar = options.refSugar !== false;
let defaultExport;
let hasDefinePropsCall = false;
let hasDefineEmitCall = false;
let propsRuntimeDecl;
let propsTypeDecl;
let propsIdentifier;
let emitRuntimeDecl;
let emitTypeDecl;
let emitIdentifier;
let hasAwait = false;
let hasInlinedSsrRenderFn = false;
// props/emits declared via types
const typeDeclaredProps = {};
const typeDeclaredEmits = new Set();
// record declared types for runtime props type generation
const declaredTypes = {};
// magic-string state
const s = new MagicString__default(source);
const startOffset = scriptSetup.loc.start.offset;
const endOffset = scriptSetup.loc.end.offset;
const scriptStartOffset = script && script.loc.start.offset;
const scriptEndOffset = script && script.loc.end.offset;
function helper(key) {
helperImports.add(key);
return `_${key}`;
}
function parse(input, options, offset) {
try {
return parser.parse(input, options).program.body;
}
catch (e) {
e.message = `[@vue/compiler-sfc] ${e.message}\n\n${sfc.filename}\n${shared.generateCodeFrame(source, e.pos + offset, e.pos + offset + 1)}`;
throw e;
}
}
function error(msg, node, end = node.end + startOffset) {
throw new Error(`[@vue/compiler-sfc] ${msg}\n\n${sfc.filename}\n${shared.generateCodeFrame(source, node.start + startOffset, end)}`);
}
function registerUserImport(source, local, imported, isType) {
if (source === 'vue' && imported) {
userImportAlias[imported] = local;
}
userImports[local] = {
isType,
imported: imported || 'default',
source
};
}
function processDefineProps(node) {
if (isCallOf(node, DEFINE_PROPS)) {
if (hasDefinePropsCall) {
error(`duplicate ${DEFINE_PROPS}() call`, node);
}
hasDefinePropsCall = true;
propsRuntimeDecl = node.arguments[0];
// context call has type parameters - infer runtime types from it
if (node.typeParameters) {
if (propsRuntimeDecl) {
error(`${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +
`at the same time. Use one or the other.`, node);
}
const typeArg = node.typeParameters.params[0];
if (typeArg.type === 'TSTypeLiteral') {
propsTypeDecl = typeArg;
}
else {
error(`type argument passed to ${DEFINE_PROPS}() must be a literal type.`, typeArg);
}
}
return true;
}
return false;
}
function processDefineEmit(node) {
if (isCallOf(node, DEFINE_EMIT)) {
if (hasDefineEmitCall) {
error(`duplicate ${DEFINE_EMIT}() call`, node);
}
hasDefineEmitCall = true;
emitRuntimeDecl = node.arguments[0];
if (node.typeParameters) {
if (emitRuntimeDecl) {
error(`${DEFINE_EMIT}() cannot accept both type and non-type arguments ` +
`at the same time. Use one or the other.`, node);
}
const typeArg = node.typeParameters.params[0];
if (typeArg.type === 'TSFunctionType' ||
typeArg.type === 'TSUnionType') {
emitTypeDecl = typeArg;
}
else {
error(`type argument passed to ${DEFINE_EMIT}() must be a function type ` +
`or a union of function types.`, typeArg);
}
}
return true;
}
return false;
}
function checkInvalidScopeReference(node, method) {
if (!node)
return;
walkIdentifiers(node, id => {
if (setupBindings[id.name]) {
error(`\`${method}()\` in <script setup> cannot reference locally ` +
`declared variables because it will be hoisted outside of the ` +
`setup() function. If your component options requires initialization ` +
`in the module scope, use a separate normal <script> to export ` +
`the options instead.`, id);
}
});
}
function processRefExpression(exp, statement) {
if (exp.type === 'AssignmentExpression') {
const { left, right } = exp;
if (left.type === 'Identifier') {
registerRefBinding(left);
s.prependRight(right.start + startOffset, `${helper('ref')}(`);
s.appendLeft(right.end + startOffset, ')');
}
else if (left.type === 'ObjectPattern') {
// remove wrapping parens
for (let i = left.start; i > 0; i--) {
const char = source[i + startOffset];
if (char === '(') {
s.remove(i + startOffset, i + startOffset + 1);
break;
}
}
for (let i = left.end; i > 0; i++) {
const char = source[i + startOffset];
if (char === ')') {
s.remove(i + startOffset, i + startOffset + 1);
break;
}
}
processRefObjectPattern(left, statement);
}
else if (left.type === 'ArrayPattern') {
processRefArrayPattern(left, statement);
}
}
else if (exp.type === 'SequenceExpression') {
// possible multiple declarations
// ref: x = 1, y = 2
exp.expressions.forEach(e => processRefExpression(e, statement));
}
else if (exp.type === 'Identifier') {
registerRefBinding(exp);
s.appendLeft(exp.end + startOffset, ` = ${helper('ref')}()`);
}
else {
error(`ref: statements can only contain assignment expressions.`, exp);
}
}
function registerRefBinding(id) {
if (id.name[0] === '$') {
error(`ref variable identifiers cannot start with $.`, id);
}
refBindings[id.name] = setupBindings[id.name] = "setup-ref" /* SETUP_REF */;
refIdentifiers.add(id);
}
function processRefObjectPattern(pattern, statement) {
for (const p of pattern.properties) {
let nameId;
if (p.type === 'ObjectProperty') {
if (p.key.start === p.value.start) {
// shorthand { foo } --> { foo: __foo }
nameId = p.key;
s.appendLeft(nameId.end + startOffset, `: __${nameId.name}`);
if (p.value.type === 'AssignmentPattern') {
// { foo = 1 }
refIdentifiers.add(p.value.left);
}
}
else {
if (p.value.type === 'Identifier') {
// { foo: bar } --> { foo: __bar }
nameId = p.value;
s.prependRight(nameId.start + startOffset, `__`);
}
else if (p.value.type === 'ObjectPattern') {
processRefObjectPattern(p.value, statement);
}
else if (p.value.type === 'ArrayPattern') {
processRefArrayPattern(p.value, statement);
}
else if (p.value.type === 'AssignmentPattern') {
// { foo: bar = 1 } --> { foo: __bar = 1 }
nameId = p.value.left;
s.prependRight(nameId.start + startOffset, `__`);
}
}
}
else {
// rest element { ...foo } --> { ...__foo }
nameId = p.argument;
s.prependRight(nameId.start + startOffset, `__`);
}
if (nameId) {
registerRefBinding(nameId);
// append binding declarations after the parent statement
s.appendLeft(statement.end + startOffset, `\nconst ${nameId.name} = ${helper('ref')}(__${nameId.name});`);
}
}
}
function processRefArrayPattern(pattern, statement) {
for (const e of pattern.elements) {
if (!e)
continue;
let nameId;
if (e.type === 'Identifier') {
// [a] --> [__a]
nameId = e;
}
else if (e.type === 'AssignmentPattern') {
// [a = 1] --> [__a = 1]
nameId = e.left;
}
else if (e.type === 'RestElement') {
// [...a] --> [...__a]
nameId = e.argument;
}
else if (e.type === 'ObjectPattern') {
processRefObjectPattern(e, statement);
}
else if (e.type === 'ArrayPattern') {
processRefArrayPattern(e, statement);
}
if (nameId) {
registerRefBinding(nameId);
// prefix original
s.prependRight(nameId.start + startOffset, `__`);
// append binding declarations after the parent statement
s.appendLeft(statement.end + startOffset, `\nconst ${nameId.name} = ${helper('ref')}(__${nameId.name});`);
}
}
}
// 1. process normal <script> first if it exists
let scriptAst;
if (script) {
// import dedupe between <script> and <script setup>
scriptAst = parse(script.content, {
plugins,
sourceType: 'module'
}, scriptStartOffset);
for (const node of scriptAst) {
if (node.type === 'ImportDeclaration') {
// record imports for dedupe
for (const specifier of node.specifiers) {
const imported = specifier.type === 'ImportSpecifier' &&
specifier.imported.type === 'Identifier' &&
specifier.imported.name;
registerUserImport(node.source.value, specifier.local.name, imported, node.importKind === 'type');
}
}
else if (node.type === 'ExportDefaultDeclaration') {
// export default
defaultExport = node;
const start = node.start + scriptStartOffset;
s.overwrite(start, start + `export default`.length, `const ${defaultTempVar} =`);
}
else if (node.type === 'ExportNamedDeclaration' && node.specifiers) {
const defaultSpecifier = node.specifiers.find(s => s.exported.type === 'Identifier' && s.exported.name === 'default');
if (defaultSpecifier) {
defaultExport = node;
// 1. remove specifier
if (node.specifiers.length > 1) {
s.remove(defaultSpecifier.start + scriptStartOffset, defaultSpecifier.end + scriptStartOffset);
}
else {
s.remove(node.start + scriptStartOffset, node.end + scriptStartOffset);
}
if (node.source) {
// export { x as default } from './x'
// rewrite to `import { x as __default__ } from './x'` and
// add to top
s.prepend(`import { ${defaultSpecifier.local.name} as ${defaultTempVar} } from '${node.source.value}'\n`);
}
else {
// export { x as default }
// rewrite to `const __default__ = x` and move to end
s.append(`\nconst ${defaultTempVar} = ${defaultSpecifier.local.name}\n`);
}
}
}
}
}
// 2. parse <script setup> and walk over top level statements
const scriptSetupAst = parse(scriptSetup.content, {
plugins: [
...plugins,
// allow top level await but only inside <script setup>
'topLevelAwait'
],
sourceType: 'module'
}, startOffset);
for (const node of scriptSetupAst) {
const start = node.start + startOffset;
let end = node.end + startOffset;
// import or type declarations: move to top
// locate comment
if (node.trailingComments && node.trailingComments.length > 0) {
const lastCommentNode = node.trailingComments[node.trailingComments.length - 1];
end = lastCommentNode.end + startOffset;
}
// locate the end of whitespace between this statement and the next
while (end <= source.length) {
if (!/\s/.test(source.charAt(end))) {
break;
}
end++;
}
// process `ref: x` bindings (convert to refs)
if (node.type === 'LabeledStatement' &&
node.label.name === 'ref' &&
node.body.type === 'ExpressionStatement') {
if (enableRefSugar) {
warnExperimental(`ref: sugar`, 228);
s.overwrite(node.label.start + startOffset, node.body.start + startOffset, 'const ');
processRefExpression(node.body.expression, node);
}
else {
// TODO if we end up shipping ref: sugar as an opt-in feature,
// need to proxy the option in vite, vue-loader and rollup-plugin-vue.
error(`ref: sugar needs to be explicitly enabled via vite or vue-loader options.`, node);
}
}
if (node.type === 'ImportDeclaration') {
// import declarations are moved to top
s.move(start, end, 0);
// dedupe imports
let removed = 0;
const removeSpecifier = (i) => {
const removeLeft = i > removed;
removed++;
const current = node.specifiers[i];
const next = node.specifiers[i + 1];
s.remove(removeLeft
? node.specifiers[i - 1].end + startOffset
: current.start + startOffset, next && !removeLeft
? next.start + startOffset
: current.end + startOffset);
};
for (let i = 0; i < node.specifiers.length; i++) {
const specifier = node.specifiers[i];
const local = specifier.local.name;
const imported = specifier.type === 'ImportSpecifier' &&
specifier.imported.type === 'Identifier' &&
specifier.imported.name;
const source = node.source.value;
const existing = userImports[local];
if (source === 'vue' &&
(imported === DEFINE_PROPS || imported === DEFINE_EMIT)) {
removeSpecifier(i);
}
else if (existing) {
if (existing.source === source && existing.imported === imported) {
// already imported in <script setup>, dedupe
removeSpecifier(i);
}
else {
error(`different imports aliased to same local name.`, specifier);
}
}
else {
registerUserImport(source, local, imported, node.importKind === 'type');
}
}
if (node.specifiers.length && removed === node.specifiers.length) {
s.remove(node.start + startOffset, node.end + startOffset);
}
}
// process `defineProps` and `defineEmit` calls
if (node.type === 'ExpressionStatement' &&
(processDefineProps(node.expression) ||
processDefineEmit(node.expression))) {
s.remove(node.start + startOffset, node.end + startOffset);
}
if (node.type === 'VariableDeclaration' && !node.declare) {
for (const decl of node.declarations) {
if (decl.init) {
const isDefineProps = processDefineProps(decl.init);
if (isDefineProps) {
propsIdentifier = scriptSetup.content.slice(decl.id.start, decl.id.end);
}
const isDefineEmit = processDefineEmit(decl.init);
if (isDefineEmit) {
emitIdentifier = scriptSetup.content.slice(decl.id.start, decl.id.end);
}
if (isDefineProps || isDefineEmit)
if (node.declarations.length === 1) {
s.remove(node.start + startOffset, node.end + startOffset);
}
else {
s.remove(decl.start + startOffset, decl.end + startOffset);
}
}
}
}
// walk decalrations to record declared bindings
if ((node.type === 'VariableDeclaration' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration') &&
!node.declare) {
walkDeclaration(node, setupBindings, userImportAlias);
}
// Type declarations
if (node.type === 'VariableDeclaration' && node.declare) {
s.remove(start, end);
}
// move all type declarations to outer scope
if (node.type.startsWith('TS') ||
(node.type === 'ExportNamedDeclaration' && node.exportKind === 'type')) {
recordType(node, declaredTypes);
s.move(start, end, 0);
}
// walk statements & named exports / variable declarations for top level
// await
if ((node.type === 'VariableDeclaration' && !node.declare) ||
node.type.endsWith('Statement')) {
estreeWalker.walk(node, {
enter(node) {
if (isFunction(node)) {
this.skip();
}
if (node.type === 'AwaitExpression') {
hasAwait = true;
}
}
});
}
if ((node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
node.type === 'ExportAllDeclaration' ||
node.type === 'ExportDefaultDeclaration') {
error(`<script setup> cannot contain ES module exports. ` +
`If you are using a previous version of <script setup>, please ` +
`consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`, node);
}
}
// 3. Do a full walk to rewrite identifiers referencing let exports with ref
// value access
if (enableRefSugar && Object.keys(refBindings).length) {
for (const node of scriptSetupAst) {
if (node.type !== 'ImportDeclaration') {
walkIdentifiers(node, (id, parent, parentStack) => {
if (refBindings[id.name] && !refIdentifiers.has(id)) {
if (isStaticProperty(parent) && parent.shorthand) {
// let binding used in a property shorthand
// { foo } -> { foo: foo.value }
// skip for destructure patterns
if (!parent.inPattern ||
isInDestructureAssignment(parent, parentStack)) {
s.appendLeft(id.end + startOffset, `: ${id.name}.value`);
}
}
else {
s.appendLeft(id.end + startOffset, '.value');
}
}
else if (id.name[0] === '$' && refBindings[id.name.slice(1)]) {
// $xxx raw ref access variables, remove the $ prefix
s.remove(id.start + startOffset, id.start + startOffset + 1);
}
});
}
}
}
// 4. extract runtime props/emits code from setup context type
if (propsTypeDecl) {
extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes);
}
if (emitTypeDecl) {
extractRuntimeEmits(emitTypeDecl, typeDeclaredEmits);
}
// 5. check useOptions args to make sure it doesn't reference setup scope
// variables
checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS);
checkInvalidScopeReference(emitRuntimeDecl, DEFINE_PROPS);
// 6. remove non-script content
if (script) {
if (startOffset < scriptStartOffset) {
// <script setup> before <script>
s.remove(0, startOffset);
s.remove(endOffset, scriptStartOffset);
s.remove(scriptEndOffset, source.length);
}
else {
// <script> before <script setup>
s.remove(0, scriptStartOffset);
s.remove(scriptEndOffset, startOffset);
s.remove(endOffset, source.length);
}
}
else {
// only <script setup>
s.remove(0, startOffset);
s.remove(endOffset, source.length);
}
// 7. analyze binding metadata
if (scriptAst) {
Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst));
}
if (propsRuntimeDecl) {
for (const key of getObjectOrArrayExpressionKeys(propsRuntimeDecl)) {
bindingMetadata[key] = "props" /* PROPS */;
}
}
for (const key in typeDeclaredProps) {
bindingMetadata[key] = "props" /* PROPS */;
}
for (const [key, { isType, imported, source }] of Object.entries(userImports)) {
if (isType)
continue;
bindingMetadata[key] =
(imported === 'default' && source.endsWith('.vue')) || source === 'vue'
? "setup-const" /* SETUP_CONST */
: "setup-maybe-ref" /* SETUP_MAYBE_REF */;
}
for (const key in setupBindings) {
bindingMetadata[key] = setupBindings[key];
}
// 8. inject `useCssVars` calls
if (cssVars.length) {
helperImports.add(CSS_VARS_HELPER);
helperImports.add('unref');
s.prependRight(startOffset, `\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, !!options.isProd)}\n`);
}
// 9. finalize setup() argument signature
let args = `__props`;
if (propsTypeDecl) {
args += `: ${scriptSetup.content.slice(propsTypeDecl.start, propsTypeDecl.end)}`;
}
// inject user assignment of props
// we use a default __props so that template expressions referencing props
// can use it directly
if (propsIdentifier) {
s.prependRight(startOffset, `\nconst ${propsIdentifier} = __props`);
}
if (emitIdentifier) {
args +=
emitIdentifier === `emit` ? `, { emit }` : `, { emit: ${emitIdentifier} }`;
if (emitTypeDecl) {
args += `: {
emit: (${scriptSetup.content.slice(emitTypeDecl.start, emitTypeDecl.end)}),
slots: any,
attrs: any
}`;
}
}
// 10. generate return statement
let returned;
if (options.inlineTemplate) {
if (sfc.template && !sfc.template.src) {
if (options.templateOptions && options.templateOptions.ssr) {
hasInlinedSsrRenderFn = true;
}
// inline render function mode - we are going to compile the template and
// inline it right here
const { code, ast, preamble, tips, errors } = compileTemplate({
filename,
source: sfc.template.content,
inMap: sfc.template.map,
...options.templateOptions,
id: scopeId,
scoped: sfc.styles.some(s => s.scoped),
isProd: options.isProd,
ssrCssVars: sfc.cssVars,
compilerOptions: {
...(options.templateOptions &&
options.templateOptions.compilerOptions),
inline: true,
isTS,
bindingMetadata
}
});
if (tips.length) {
tips.forEach(warnOnce);
}
const err = errors[0];
if (typeof err === 'string') {
throw new Error(err);
}
else if (err) {
if (err.loc) {
err.message +=
`\n\n` +
sfc.filename +
'\n' +
shared.generateCodeFrame(source, err.loc.start.offset, err.loc.end.offset) +
`\n`;
}
throw err;
}
if (preamble) {
s.prepend(preamble);
}
// avoid duplicated unref import
// as this may get injected by the render function preamble OR the
// css vars codegen
if (ast && ast.helpers.includes(compilerCore.UNREF)) {
helperImports.delete('unref');
}
returned = code;
}
else {
returned = `() => {}`;
}
}
else {
// return bindings from setup
const allBindings = { ...setupBindings };
for (const key in userImports) {
if (!userImports[key].isType) {
allBindings[key] = true;
}
}
returned = `{ ${Object.keys(allBindings).join(', ')} }`;
}
s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`);
// 11. finalize default export
// expose: [] makes <script setup> components "closed" by default.
let runtimeOptions = `\n expose: [],`;
if (hasInheritAttrsFlag) {
runtimeOptions += `\n inheritAttrs: false,`;
}
if (hasInlinedSsrRenderFn) {
runtimeOptions += `\n __ssrInlineRender: true,`;
}
if (propsRuntimeDecl) {
runtimeOptions += `\n props: ${scriptSetup.content
.slice(propsRuntimeDecl.start, propsRuntimeDecl.end)
.trim()},`;
}
else if (propsTypeDecl) {
runtimeOptions += genRuntimeProps(typeDeclaredProps);
}
if (emitRuntimeDecl) {
runtimeOptions += `\n emits: ${scriptSetup.content
.slice(emitRuntimeDecl.start, emitRuntimeDecl.end)
.trim()},`;
}
else if (emitTypeDecl) {
runtimeOptions += genRuntimeEmits(typeDeclaredEmits);
}
if (isTS) {
// for TS, make sure the exported type is still valid type with
// correct props information
// we have to use object spread for types to be merged properly
// user's TS setting should compile it down to proper targets
const def = defaultExport ? `\n ...${defaultTempVar},` : ``;
// wrap setup code with function.
// export the content of <script setup> as a named export, `setup`.
// this allows `import { setup } from '*.vue'` for testing purposes.
s.prependLeft(startOffset, `\nexport default ${helper(`defineComponent`)}({${def}${runtimeOptions}\n ${hasAwait ? `async ` : ``}setup(${args}) {\n`);
s.appendRight(endOffset, `})`);
}
else {
if (defaultExport) {
// can't rely on spread operator in non ts mode
s.prependLeft(startOffset, `\n${hasAwait ? `async ` : ``}function setup(${args}) {\n`);
s.append(`\nexport default /*#__PURE__*/ Object.assign(${defaultTempVar}, {${runtimeOptions}\n setup\n})\n`);
}
else {
s.prependLeft(startOffset, `\nexport default {${runtimeOptions}\n ` +
`${hasAwait ? `async ` : ``}setup(${args}) {\n`);
s.appendRight(endOffset, `}`);
}
}
// 12. finalize Vue helper imports
if (helperImports.size > 0) {
s.prepend(`import { ${[...helperImports]
.map(h => `${h} as _${h}`)
.join(', ')} } from 'vue'\n`);
}
s.trim();
return {
...scriptSetup,
bindings: bindingMetadata,
content: s.toString(),
map: s.generateMap({
source: filename,
hires: true,
includeContent: true
}),
scriptAst,
scriptSetupAst
};
}
function walkDeclaration(node, bindings, userImportAlias) {
if (node.type === 'VariableDeclaration') {
const isConst = node.kind === 'const';
// export const foo = ...
for (const { id, init } of node.declarations) {
const isDefineCall = !!(isConst &&
(isCallOf(init, DEFINE_PROPS) || isCallOf(init, DEFINE_EMIT)));
if (id.type === 'Identifier') {
let bindingType;
const userReactiveBinding = userImportAlias['reactive'] || 'reactive';
if (isCallOf(init, userReactiveBinding)) {
// treat reactive() calls as let since it's meant to be mutable
bindingType = "setup-let" /* SETUP_LET */;
}
else if (
// if a declaration is a const literal, we can mark it so that
// the generated render fn code doesn't need to unref() it
isDefineCall ||
(isConst && canNeverBeRef(init, userReactiveBinding))) {
bindingType = "setup-const" /* SETUP_CONST */;
}
else if (isConst) {
if (isCallOf(init, userImportAlias['ref'] || 'ref')) {
bindingType = "setup-ref" /* SETUP_REF */;
}
else {
bindingType = "setup-maybe-ref" /* SETUP_MAYBE_REF */;
}
}
else {
bindingType = "setup-let" /* SETUP_LET */;
}
bindings[id.name] = bindingType;
}
else if (id.type === 'ObjectPattern') {
walkObjectPattern(id, bindings, isConst, isDefineCall);
}
else if (id.type === 'ArrayPattern') {
walkArrayPattern(id, bindings, isConst, isDefineCall);
}
}
}
else if (node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration') {
// export function foo() {} / export class Foo {}
// export declarations must be named.
bindings[node.id.name] = "setup-const" /* SETUP_CONST */;
}
}
function walkObjectPattern(node, bindings, isConst, isDefineCall = false) {
for (const p of node.properties) {
if (p.type === 'ObjectProperty') {
// key can only be Identifier in ObjectPattern
if (p.key.type === 'Identifier') {
if (p.key === p.value) {
// const { x } = ...
bindings[p.key.name] = isDefineCall
? "setup-const" /* SETUP_CONST */
: isConst
? "setup-maybe-ref" /* SETUP_MAYBE_REF */
: "setup-let" /* SETUP_LET */;
}
else {
walkPattern(p.value, bindings, isConst, isDefineCall);
}
}
}
else {
// ...rest
// argument can only be identifer when destructuring
bindings[p.argument.name] = isConst
? "setup-const" /* SETUP_CONST */
: "setup-let" /* SETUP_LET */;
}
}
}
function walkArrayPattern(node, bindings, isConst, isDefineCall = false) {
for (const e of node.elements) {
e && walkPattern(e, bindings, isConst, isDefineCall);
}
}
function walkPattern(node, bindings, isConst, isDefineCall = false) {
if (node.type === 'Identifier') {
bindings[node.name] = isDefineCall
? "setup-const" /* SETUP_CONST */
: isConst
? "setup-maybe-ref" /* SETUP_MAYBE_REF */
: "setup-let" /* SETUP_LET */;
}
else if (node.type === 'RestElement') {
// argument can only be identifer when destructuring
bindings[node.argument.name] = isConst
? "setup-const" /* SETUP_CONST */
: "setup-let" /* SETUP_LET */;
}
else if (node.type === 'ObjectPattern') {
walkObjectPattern(node, bindings, isConst);
}
else if (node.type === 'ArrayPattern') {
walkArrayPattern(node, bindings, isConst);
}
else if (node.type === 'AssignmentPattern') {
if (node.left.type === 'Identifier') {
bindings[node.left.name] = isDefineCall
? "setup-const" /* SETUP_CONST */
: isConst
? "setup-maybe-ref" /* SETUP_MAYBE_REF */
: "setup-let" /* SETUP_LET */;
}
else {
walkPattern(node.left, bindings, isConst);
}
}
}
function recordType(node, declaredTypes) {
if (node.type === 'TSInterfaceDeclaration') {
declaredTypes[node.id.name] = [`Object`];
}
else if (node.type === 'TSTypeAliasDeclaration') {
declaredTypes[node.id.name] = inferRuntimeType(node.typeAnnotation, declaredTypes);
}
else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
recordType(node.declaration, declaredTypes);
}
}
function extractRuntimeProps(node, props, declaredTypes) {
for (const m of node.members) {
if (m.type === 'TSPropertySignature' && m.key.type === 'Identifier') {
props[m.key.name] = {
key: m.key.name,
required: !m.optional,
type: m.typeAnnotation
? inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes)
: [`null`]
};
}
}
}
function inferRuntimeType(node, declaredTypes) {
switch (node.type) {
case 'TSStringKeyword':
return ['String'];
case 'TSNumberKeyword':
return ['Number'];
case 'TSBooleanKeyword':
return ['Boolean'];
case 'TSObjectKeyword':
return ['Object'];
case 'TSTypeLiteral':
// TODO (nice to have) generate runtime property validation
return ['Object'];
case 'TSFunctionType':
return ['Function'];
case 'TSArrayType':
case 'TSTupleType':
// TODO (nice to have) generate runtime element type/length checks
return ['Array'];
case 'TSLiteralType':
switch (node.literal.type) {
case 'StringLiteral':
return ['String'];
case 'BooleanLiteral':
return ['Boolean'];
case 'NumericLiteral':
case 'BigIntLiteral':
return ['Number'];
default:
return [`null`];
}
case 'TSTypeReference':
if (node.typeName.type === 'Identifier') {
if (declaredTypes[node.typeName.name]) {
return declaredTypes[node.typeName.name];
}
switch (node.typeName.name) {
case 'Array':
case 'Function':
case 'Object':
case 'Set':
case 'Map':
case 'WeakSet':
case 'WeakMap':
return [node.typeName.name];
case 'Record':
case 'Partial':
case 'Readonly':
case 'Pick':
case 'Omit':
case 'Exclude':
case 'Extract':
case 'Required':
case 'InstanceType':
return ['Object'];
}
}
return [`null`];
case 'TSUnionType':
return [
...new Set([].concat(node.types.map(t => inferRuntimeType(t, declaredTypes))))
];
case 'TSIntersectionType':
return ['Object'];
default:
return [`null`]; // no runtime check
}
}
function genRuntimeProps(props) {
const keys = Object.keys(props);
if (!keys.length) {
return ``;
}
return `\n props: {\n ${keys
.map(key => {
const { type, required } = props[key];
return `${key}: { type: ${toRuntimeTypeString(type)}, required: ${required} }`;
})
.join(',\n ')}\n } as unknown as undefined,`;
}
function toRuntimeTypeString(types) {
return types.some(t => t === 'null')
? `null`
: types.length > 1
? `[${types.join(', ')}]`
: types[0];
}
function extractRuntimeEmits(node, emits) {
if (node.type === 'TSUnionType') {
for (let t of node.types) {
if (t.type === 'TSParenthesizedType')
t = t.typeAnnotation;
if (t.type === 'TSFunctionType') {
extractRuntimeEmits(t, emits);
}
}
return;
}
const eventName = node.parameters[0];
if (eventName.type === 'Identifier' &&
eventName.typeAnnotation &&
eventName.typeAnnotation.type === 'TSTypeAnnotation') {
const typeNode = eventName.typeAnnotation.typeAnnotation;
if (typeNode.type === 'TSLiteralType') {
emits.add(String(typeNode.literal.value));
}
else if (typeNode.type === 'TSUnionType') {
for (const t of typeNode.types) {
if (t.type === 'TSLiteralType') {
emits.add(String(t.literal.value));
}
}
}
}
}
function genRuntimeEmits(emits) {
return emits.size
? `\n emits: [${Array.from(emits)
.map(p => JSON.stringify(p))
.join(', ')}] as unknown as undefined,`
: ``;
}
/**
* Walk an AST and find identifiers that are variable references.
* This is largely the same logic with `transformExpressions` in compiler-core
* but with some subtle differences as this needs to handle a wider range of
* possible syntax.
*/
function walkIdentifiers(root, onIdentifier) {
const parentStack = [];
const knownIds = Object.create(null);
estreeWalker.walk(root, {
enter(node, parent) {
parent && parentStack.push(parent);
if (node.type === 'Identifier') {
if (!knownIds[node.name] &&
isRefIdentifier(node, parent, parentStack)) {
onIdentifier(node, parent, parentStack);
}
}
else if (isFunction(node)) {
// walk function expressions and add its arguments to known identifiers
// so that we don't prefix them
node.params.forEach(p => estreeWalker.walk(p, {
enter(child, parent) {
if (child.type === 'Identifier' &&
// do not record as scope variable if is a destructured key
!isStaticPropertyKey(child, parent) &&
// do not record if this is a default value
// assignment of a destructured variable
!(parent &&
parent.type === 'AssignmentPattern' &&
parent.right === child)) {
const { name } = child;
if (node.scopeIds && node.scopeIds.has(name)) {
return;
}
if (name in knownIds) {
knownIds[name]++;
}
else {
knownIds[name] = 1;
}
(node.scopeIds || (node.scopeIds = new Set())).add(name);
}
}
}));
}
else if (node.type === 'ObjectProperty' &&
parent.type === 'ObjectPattern') {
node.inPattern = true;
}
},
leave(node, parent) {
parent && parentStack.pop();
if (node.scopeIds) {
node.scopeIds.forEach((id) => {
knownIds[id]--;
if (knownIds[id] === 0) {
delete knownIds[id];
}
});
}
}
});
}
function isRefIdentifier(id, parent, parentStack) {
// declaration id
if ((parent.type === 'VariableDeclarator' ||
parent.type === 'ClassDeclaration') &&
parent.id === id) {
return false;
}
if (isFunction(parent)) {
// function decalration/expression id
if (parent.id === id) {
return false;
}
// params list
if (parent.params.includes(id)) {
return false;
}
}
// property key
// this also covers object destructure pattern
if (isStaticPropertyKey(id, parent)) {
return false;
}
// non-assignment array destructure pattern
if (parent.type === 'ArrayPattern' &&
!isInDestructureAssignment(parent, parentStack)) {
return false;
}
// member expression property
if ((parent.type === 'MemberExpression' ||
parent.type === 'OptionalMemberExpression') &&
parent.property === id &&
!parent.computed) {
return false;
}
// is a special keyword but parsed as identifier
if (id.name === 'arguments') {
return false;
}
return true;
}
const isStaticProperty = (node) => node &&
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
!node.computed;
const isStaticPropertyKey = (node, parent) => isStaticProperty(parent) && parent.key === node;
function isFunction(node) {
return /Function(?:Expression|Declaration)$|Method$/.test(node.type);
}
function isCallOf(node, name) {
return !!(node &&
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === name);
}
function canNeverBeRef(node, userReactiveImport) {
if (isCallOf(node, userReactiveImport)) {
return true;
}
switch (node.type) {
case 'UnaryExpression':
case 'BinaryExpression':
case 'ArrayExpression':
case 'ObjectExpression':
case 'FunctionExpression':
case 'ArrowFunctionExpression':
case 'UpdateExpression':
case 'ClassExpression':
case 'TaggedTemplateExpression':
return true;
case 'SequenceExpression':
return canNeverBeRef(node.expressions[node.expressions.length - 1], userReactiveImport);
default:
if (node.type.endsWith('Literal')) {
return true;
}
return false;
}
}
function isInDestructureAssignment(parent, parentStack) {
if (parent &&
(parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')) {
let i = parentStack.length;
while (i--) {
const p = parentStack[i];
if (p.type === 'AssignmentExpression') {
const root = parentStack[0];
// if this is a ref: destructure, it should be treated like a
// variable decalration!
return !(root.type === 'LabeledStatement' && root.label.name === 'ref');
}
else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) {
break;
}
}
}
return false;
}
/**
* Analyze bindings in normal `<script>`
* Note that `compileScriptSetup` already analyzes bindings as part of its
* compilation process so this should only be used on single `<script>` SFCs.
*/
function analyzeScriptBindings(ast) {
for (const node of ast) {
if (node.type === 'ExportDefaultDeclaration' &&
node.declaration.type === 'ObjectExpression') {
return analyzeBindingsFromOptions(node.declaration);
}
}
return {};
}
function analyzeBindingsFromOptions(node) {
const bindings = {};
for (const property of node.properties) {
if (property.type === 'ObjectProperty' &&
!property.computed &&
property.key.type === 'Identifier') {
// props
if (property.key.name === 'props') {
// props: ['foo']
// props: { foo: ... }
for (const key of getObjectOrArrayExpressionKeys(property.value)) {
bindings[key] = "props" /* PROPS */;
}
}
// inject
else if (property.key.name === 'inject') {
// inject: ['foo']
// inject: { foo: {} }
for (const key of getObjectOrArrayExpressionKeys(property.value)) {
bindings[key] = "options" /* OPTIONS */;
}
}
// computed & methods
else if (property.value.type === 'ObjectExpression' &&
(property.key.name === 'computed' || property.key.name === 'methods')) {
// methods: { foo() {} }
// computed: { foo() {} }
for (const key of getObjectExpressionKeys(property.value)) {
bindings[key] = "options" /* OPTIONS */;
}
}
}
// setup & data
else if (property.type === 'ObjectMethod' &&
property.key.type === 'Identifier' &&
(property.key.name === 'setup' || property.key.name === 'data')) {
for (const bodyItem of property.body.body) {
// setup() {
// return {
// foo: null
// }
// }
if (bodyItem.type === 'ReturnStatement' &&
bodyItem.argument &&
bodyItem.argument.type === 'ObjectExpression') {
for (const key of getObjectExpressionKeys(bodyItem.argument)) {
bindings[key] =
property.key.name === 'setup'
? "setup-maybe-ref" /* SETUP_MAYBE_REF */
: "data" /* DATA */;
}
}
}
}
}
return bindings;
}
function getObjectExpressionKeys(node) {
const keys = [];
for (const prop of node.properties) {
if ((prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
!prop.computed) {
if (prop.key.type === 'Identifier') {
keys.push(prop.key.name);
}
else if (prop.key.type === 'StringLiteral') {
keys.push(prop.key.value);
}
}
}
return keys;
}
function getArrayExpressionKeys(node) {
const keys = [];
for (const element of node.elements) {
if (element && element.type === 'StringLiteral') {
keys.push(element.value);
}
}
return keys;
}
function getObjectOrArrayExpressionKeys(value) {
if (value.type === 'ArrayExpression') {
return getArrayExpressionKeys(value);
}
if (value.type === 'ObjectExpression') {
return getObjectExpressionKeys(value);
}
return [];
}
exports.generateCodeFrame = compilerCore.generateCodeFrame;
exports.compileScript = compileScript;
exports.compileStyle = compileStyle;
exports.compileStyleAsync = compileStyleAsync;
exports.compileTemplate = compileTemplate;
exports.parse = parse;
exports.rewriteDefault = rewriteDefault;