2562 lines
101 KiB
JavaScript
2562 lines
101 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');
|
|
var refTransform = require('@vue/ref-transform');
|
|
|
|
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;
|
|
// ignore v-bind() in comments /* ... */
|
|
const content = style.content.replace(/\/\*([\s\S]*?)\*\//g, '');
|
|
while ((match = cssVarRE.exec(content))) {
|
|
const variable = match[1] || match[2] || match[3];
|
|
if (!vars.includes(variable)) {
|
|
vars.push(variable);
|
|
}
|
|
}
|
|
});
|
|
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.__isScriptSetup === false ? undefined : 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`);
|
|
}
|
|
|
|
function createCache(size = 500) {
|
|
return new (require('lru-cache'))(size);
|
|
}
|
|
|
|
const sourceToSFC = createCache();
|
|
function parse(source, { sourceMap = true, filename = 'anonymous.vue', sourceRoot = '', pad = false, ignoreEmpty = true, 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: [],
|
|
slotted: false
|
|
};
|
|
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;
|
|
}
|
|
// we only want to keep the nodes that are not empty (when the tag is not a template)
|
|
if (ignoreEmpty &&
|
|
node.tag !== 'template' &&
|
|
isEmpty(node) &&
|
|
!hasSrc(node)) {
|
|
return;
|
|
}
|
|
switch (node.tag) {
|
|
case 'template':
|
|
if (!descriptor.template) {
|
|
const templateBlock = (descriptor.template = createBlock(node, source, false));
|
|
templateBlock.ast = node;
|
|
// warn against 2.x <template functional>
|
|
if (templateBlock.attrs.functional) {
|
|
const err = new SyntaxError(`<template functional> is no longer supported in Vue 3, since ` +
|
|
`functional components no longer have significant performance ` +
|
|
`difference from stateful ones. Just use a normal <template> ` +
|
|
`instead.`);
|
|
err.loc = node.props.find(p => p.name === 'functional').loc;
|
|
errors.push(err);
|
|
}
|
|
}
|
|
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);
|
|
// check if the SFC uses :slotted
|
|
const slottedRE = /(?:::v-|:)slotted\(/;
|
|
descriptor.slotted = descriptor.styles.some(s => s.scoped && slottedRE.test(s.content));
|
|
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);
|
|
}
|
|
else {
|
|
const offset = node.loc.source.indexOf(`</`);
|
|
if (offset > -1) {
|
|
start = {
|
|
line: start.line,
|
|
column: start.column + offset,
|
|
offset: start.offset + offset
|
|
};
|
|
}
|
|
end = Object.assign({}, start);
|
|
}
|
|
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';
|
|
});
|
|
}
|
|
/**
|
|
* Returns true if the node has no children
|
|
* once the empty text nodes (trimmed content) have been filtered out.
|
|
*/
|
|
function isEmpty(node) {
|
|
return (node.children.filter(child => child.type !== 2 /* TEXT */ || child.content.trim() !== '').length === 0);
|
|
}
|
|
|
|
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 Object.assign(Object.assign({}, defaultAssetUrlOptions), { tags: options });
|
|
}
|
|
return Object.assign(Object.assign({}, 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 };
|
|
});
|
|
// data urls contains comma after the ecoding so we need to re-merge
|
|
// them
|
|
for (let i = 0; i < imageCandidates.length; i++) {
|
|
const { url } = imageCandidates[i];
|
|
if (isDataUrl(url)) {
|
|
imageCandidates[i + 1].url =
|
|
url + ',' + imageCandidates[i + 1].url;
|
|
imageCandidates.splice(i, 1);
|
|
}
|
|
}
|
|
const hasQualifiedUrl = imageCandidates.some(({ url }) => {
|
|
return (!isExternalUrl(url) &&
|
|
!isDataUrl(url) &&
|
|
(options.includeAbsolute || isRelativeUrl(url)));
|
|
});
|
|
// When srcset does not contain any qualified URLs, skip transforming
|
|
if (!hasQualifiedUrl) {
|
|
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
|
|
};
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
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 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, Object.assign({ 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(Object.assign(Object.assign({}, 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, slotted, inMap, source, ssr = false, ssrCssVars, isProd = false, compiler = ssr ? CompilerSSR__namespace : CompilerDOM__namespace, compilerOptions = {}, transformAssetUrls }) {
|
|
const errors = [];
|
|
const warnings = [];
|
|
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, Object.assign(Object.assign({ mode: 'module', prefixIdentifiers: true, hoistStatic: true, cacheHandlers: true, ssrCssVars: ssr && ssrCssVars && ssrCssVars.length
|
|
? genCssVarsFromList(ssrCssVars, shortId, isProd)
|
|
: '', scopeId: scoped ? longId : undefined, slotted }, compilerOptions), { nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []), filename, sourceMap: true, onError: e => errors.push(e), onWarn: w => warnings.push(w) }));
|
|
// 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);
|
|
}
|
|
}
|
|
const tips = warnings.map(w => {
|
|
let msg = w.message;
|
|
if (w.loc) {
|
|
msg += `\n${shared.generateCodeFrame(source, w.loc.start.offset, w.loc.end.offset)}`;
|
|
}
|
|
return msg;
|
|
});
|
|
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) ||
|
|
(rule.parent &&
|
|
rule.parent.type === 'atrule' &&
|
|
/-?keyframes$/.test(rule.parent.name))) {
|
|
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 = Object.assign(Object.assign({}, 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, Object.assign(Object.assign({}, 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), Object.assign(Object.assign({}, 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(Object.assign(Object.assign({}, options), { isAsync: false }));
|
|
}
|
|
function compileStyleAsync(options) {
|
|
return doCompileStyle(Object.assign(Object.assign({}, 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')(Object.assign(Object.assign({}, modulesOptions), { getJSON: (_cssFileName, json) => {
|
|
cssModules = json;
|
|
} })));
|
|
}
|
|
const postCSSOptions = Object.assign(Object.assign({}, 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, Object.assign({ filename: options.filename }, options.preprocessOptions), options.preprocessCustomRequire);
|
|
}
|
|
|
|
const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/;
|
|
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)as(\s*)default/s;
|
|
const exportDefaultClassRE = /((?:^|\n|;)\s*)export\s+default\s+class\s+([\w$]+)/;
|
|
/**
|
|
* 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} = {}`;
|
|
}
|
|
let replaced;
|
|
const classMatch = input.match(exportDefaultClassRE);
|
|
if (classMatch) {
|
|
replaced =
|
|
input.replace(exportDefaultClassRE, '$1class $2') +
|
|
`\nconst ${as} = ${classMatch[2]}`;
|
|
}
|
|
else {
|
|
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);
|
|
}
|
|
|
|
// Special compiler macros
|
|
const DEFINE_PROPS = 'defineProps';
|
|
const DEFINE_EMITS = 'defineEmits';
|
|
const DEFINE_EXPOSE = 'defineExpose';
|
|
const WITH_DEFAULTS = 'withDefaults';
|
|
const isBuiltInDir = shared.makeMap(`once,memo,if,else,else-if,slot,text,html,on,bind,model,show,cloak,is`);
|
|
/**
|
|
* 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) {
|
|
let { script, scriptSetup, source, filename } = sfc;
|
|
// feature flags
|
|
const enableRefTransform = !!options.refSugar || !!options.refTransform;
|
|
let refBindings;
|
|
// 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 scriptLang = script && script.lang;
|
|
const scriptSetupLang = scriptSetup && scriptSetup.lang;
|
|
const isTS = scriptLang === 'ts' ||
|
|
scriptLang === 'tsx' ||
|
|
scriptSetupLang === 'ts' ||
|
|
scriptSetupLang === 'tsx';
|
|
const plugins = [...shared.babelParserDefaultPlugins];
|
|
if (!isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') {
|
|
plugins.push('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 && !isTS && scriptLang !== 'jsx') {
|
|
// do not process non js/ts script blocks
|
|
return script;
|
|
}
|
|
try {
|
|
let content = script.content;
|
|
let map = script.map;
|
|
const scriptAst = parser.parse(content, {
|
|
plugins,
|
|
sourceType: 'module'
|
|
}).program;
|
|
const bindings = analyzeScriptBindings(scriptAst.body);
|
|
if (enableRefTransform && refTransform.shouldTransform(content)) {
|
|
const s = new MagicString__default(source);
|
|
const startOffset = script.loc.start.offset;
|
|
const endOffset = script.loc.end.offset;
|
|
const { importedHelpers } = refTransform.transformAST(scriptAst, s, startOffset);
|
|
if (importedHelpers.length) {
|
|
s.prepend(`import { ${importedHelpers
|
|
.map(h => `${h} as _${h}`)
|
|
.join(', ')} } from 'vue'\n`);
|
|
}
|
|
s.remove(0, startOffset);
|
|
s.remove(endOffset, source.length);
|
|
content = s.toString();
|
|
map = s.generateMap({
|
|
source: filename,
|
|
hires: true,
|
|
includeContent: true
|
|
});
|
|
}
|
|
if (cssVars.length) {
|
|
content = rewriteDefault(content, `__default__`, plugins);
|
|
content += genNormalScriptCssVarsCode(cssVars, bindings, scopeId, !!options.isProd);
|
|
content += `\nexport default __default__`;
|
|
}
|
|
return Object.assign(Object.assign({}, script), { content,
|
|
map,
|
|
bindings, scriptAst: scriptAst.body });
|
|
}
|
|
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 && !isTS && scriptSetupLang !== 'jsx') {
|
|
// do not process non js/ts script blocks
|
|
return scriptSetup;
|
|
}
|
|
// metadata that needs to be returned
|
|
const bindingMetadata = {};
|
|
const defaultTempVar = `__default__`;
|
|
const helperImports = new Set();
|
|
const userImports = Object.create(null);
|
|
const userImportAlias = Object.create(null);
|
|
const setupBindings = Object.create(null);
|
|
let defaultExport;
|
|
let hasDefinePropsCall = false;
|
|
let hasDefineEmitCall = false;
|
|
let hasDefineExposeCall = false;
|
|
let propsRuntimeDecl;
|
|
let propsRuntimeDefaults;
|
|
let propsTypeDecl;
|
|
let propsTypeDeclRaw;
|
|
let propsIdentifier;
|
|
let emitsRuntimeDecl;
|
|
let emitsTypeDecl;
|
|
let emitsTypeDeclRaw;
|
|
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;
|
|
}
|
|
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, isFromSetup) {
|
|
if (source === 'vue' && imported) {
|
|
userImportAlias[imported] = local;
|
|
}
|
|
let isUsedInTemplate = true;
|
|
if (isTS && sfc.template && !sfc.template.src && !sfc.template.lang) {
|
|
isUsedInTemplate = new RegExp(
|
|
// #4274 escape $ since it's a special char in regex
|
|
// (and is the only regex special char that is valid in identifiers)
|
|
`[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`).test(resolveTemplateUsageCheckString(sfc));
|
|
}
|
|
userImports[local] = {
|
|
isType,
|
|
imported: imported || 'default',
|
|
source,
|
|
isFromSetup,
|
|
isUsedInTemplate
|
|
};
|
|
}
|
|
function processDefineProps(node) {
|
|
if (!isCallOf(node, DEFINE_PROPS)) {
|
|
return false;
|
|
}
|
|
if (hasDefinePropsCall) {
|
|
error(`duplicate ${DEFINE_PROPS}() call`, node);
|
|
}
|
|
hasDefinePropsCall = true;
|
|
propsRuntimeDecl = node.arguments[0];
|
|
// 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);
|
|
}
|
|
propsTypeDeclRaw = node.typeParameters.params[0];
|
|
propsTypeDecl = resolveQualifiedType(propsTypeDeclRaw, node => node.type === 'TSTypeLiteral');
|
|
if (!propsTypeDecl) {
|
|
error(`type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +
|
|
`or a reference to an interface or literal type.`, propsTypeDeclRaw);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function processWithDefaults(node) {
|
|
if (!isCallOf(node, WITH_DEFAULTS)) {
|
|
return false;
|
|
}
|
|
if (processDefineProps(node.arguments[0])) {
|
|
if (propsRuntimeDecl) {
|
|
error(`${WITH_DEFAULTS} can only be used with type-based ` +
|
|
`${DEFINE_PROPS} declaration.`, node);
|
|
}
|
|
propsRuntimeDefaults = node.arguments[1];
|
|
if (!propsRuntimeDefaults ||
|
|
propsRuntimeDefaults.type !== 'ObjectExpression') {
|
|
error(`The 2nd argument of ${WITH_DEFAULTS} must be an object literal.`, propsRuntimeDefaults || node);
|
|
}
|
|
}
|
|
else {
|
|
error(`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`, node.arguments[0] || node);
|
|
}
|
|
return true;
|
|
}
|
|
function processDefineEmits(node) {
|
|
if (!isCallOf(node, DEFINE_EMITS)) {
|
|
return false;
|
|
}
|
|
if (hasDefineEmitCall) {
|
|
error(`duplicate ${DEFINE_EMITS}() call`, node);
|
|
}
|
|
hasDefineEmitCall = true;
|
|
emitsRuntimeDecl = node.arguments[0];
|
|
if (node.typeParameters) {
|
|
if (emitsRuntimeDecl) {
|
|
error(`${DEFINE_EMITS}() cannot accept both type and non-type arguments ` +
|
|
`at the same time. Use one or the other.`, node);
|
|
}
|
|
emitsTypeDeclRaw = node.typeParameters.params[0];
|
|
emitsTypeDecl = resolveQualifiedType(emitsTypeDeclRaw, node => node.type === 'TSFunctionType' || node.type === 'TSTypeLiteral');
|
|
if (!emitsTypeDecl) {
|
|
error(`type argument passed to ${DEFINE_EMITS}() must be a function type, ` +
|
|
`a literal type with call signatures, or a reference to the above types.`, emitsTypeDeclRaw);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function resolveQualifiedType(node, qualifier) {
|
|
if (qualifier(node)) {
|
|
return node;
|
|
}
|
|
if (node.type === 'TSTypeReference' &&
|
|
node.typeName.type === 'Identifier') {
|
|
const refName = node.typeName.name;
|
|
const isQualifiedType = (node) => {
|
|
if (node.type === 'TSInterfaceDeclaration' &&
|
|
node.id.name === refName) {
|
|
return node.body;
|
|
}
|
|
else if (node.type === 'TSTypeAliasDeclaration' &&
|
|
node.id.name === refName &&
|
|
qualifier(node.typeAnnotation)) {
|
|
return node.typeAnnotation;
|
|
}
|
|
else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
return isQualifiedType(node.declaration);
|
|
}
|
|
};
|
|
for (const node of scriptSetupAst.body) {
|
|
const qualified = isQualifiedType(node);
|
|
if (qualified) {
|
|
return qualified;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function processDefineExpose(node) {
|
|
if (isCallOf(node, DEFINE_EXPOSE)) {
|
|
if (hasDefineExposeCall) {
|
|
error(`duplicate ${DEFINE_EXPOSE}() call`, node);
|
|
}
|
|
hasDefineExposeCall = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function checkInvalidScopeReference(node, method) {
|
|
if (!node)
|
|
return;
|
|
CompilerDOM.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);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* await foo()
|
|
* -->
|
|
* (([__temp, __restore] = withAsyncContext(() => foo())),__temp=await __temp,__restore(),__temp)
|
|
*/
|
|
function processAwait(node, isStatement) {
|
|
s.overwrite(node.start + startOffset, node.argument.start + startOffset, `${isStatement ? `;` : ``}(([__temp,__restore]=${helper(`withAsyncContext`)}(()=>(`);
|
|
s.appendLeft(node.end + startOffset, `))),__temp=await __temp,__restore()${isStatement ? `` : `,__temp`})`);
|
|
}
|
|
/**
|
|
* check defaults. If the default object is an object literal with only
|
|
* static properties, we can directly generate more optimzied default
|
|
* decalrations. Otherwise we will have to fallback to runtime merging.
|
|
*/
|
|
function checkStaticDefaults() {
|
|
return (propsRuntimeDefaults &&
|
|
propsRuntimeDefaults.type === 'ObjectExpression' &&
|
|
propsRuntimeDefaults.properties.every(node => (node.type === 'ObjectProperty' && !node.computed) ||
|
|
node.type === 'ObjectMethod'));
|
|
}
|
|
function genRuntimeProps(props) {
|
|
const keys = Object.keys(props);
|
|
if (!keys.length) {
|
|
return ``;
|
|
}
|
|
const hasStaticDefaults = checkStaticDefaults();
|
|
const scriptSetupSource = scriptSetup.content;
|
|
let propsDecls = `{
|
|
${keys
|
|
.map(key => {
|
|
let defaultString;
|
|
if (hasStaticDefaults) {
|
|
const prop = propsRuntimeDefaults.properties.find((node) => node.key.name === key);
|
|
if (prop) {
|
|
if (prop.type === 'ObjectProperty') {
|
|
// prop has corresponding static default value
|
|
defaultString = `default: ${scriptSetupSource.slice(prop.value.start, prop.value.end)}`;
|
|
}
|
|
else {
|
|
defaultString = `default() ${scriptSetupSource.slice(prop.body.start, prop.body.end)}`;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
const { type, required } = props[key];
|
|
return `${key}: { type: ${toRuntimeTypeString(type)}, required: ${required}${defaultString ? `, ${defaultString}` : ``} }`;
|
|
}
|
|
})
|
|
.join(',\n ')}\n }`;
|
|
if (propsRuntimeDefaults && !hasStaticDefaults) {
|
|
propsDecls = `${helper('mergeDefaults')}(${propsDecls}, ${source.slice(propsRuntimeDefaults.start + startOffset, propsRuntimeDefaults.end + startOffset)})`;
|
|
}
|
|
return `\n props: ${propsDecls},`;
|
|
}
|
|
function genSetupPropsType(node) {
|
|
const scriptSetupSource = scriptSetup.content;
|
|
if (checkStaticDefaults()) {
|
|
// if withDefaults() is used, we need to remove the optional flags
|
|
// on props that have default values
|
|
let res = `{ `;
|
|
const members = node.type === 'TSTypeLiteral' ? node.members : node.body;
|
|
for (const m of members) {
|
|
if ((m.type === 'TSPropertySignature' ||
|
|
m.type === 'TSMethodSignature') &&
|
|
m.typeAnnotation &&
|
|
m.key.type === 'Identifier') {
|
|
if (propsRuntimeDefaults.properties.some((p) => p.key.name === m.key.name)) {
|
|
res +=
|
|
m.key.name +
|
|
(m.type === 'TSMethodSignature' ? '()' : '') +
|
|
scriptSetupSource.slice(m.typeAnnotation.start, m.typeAnnotation.end) +
|
|
', ';
|
|
}
|
|
else {
|
|
res += scriptSetupSource.slice(m.start, m.end) + `, `;
|
|
}
|
|
}
|
|
}
|
|
return (res.length ? res.slice(0, -2) : res) + ` }`;
|
|
}
|
|
else {
|
|
return scriptSetupSource.slice(node.start, node.end);
|
|
}
|
|
}
|
|
// 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.body) {
|
|
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', false);
|
|
}
|
|
}
|
|
else if (node.type === 'ExportDefaultDeclaration') {
|
|
// export default
|
|
defaultExport = node;
|
|
const start = node.start + scriptStartOffset;
|
|
const end = node.declaration.start + scriptStartOffset;
|
|
s.overwrite(start, end, `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`);
|
|
}
|
|
}
|
|
}
|
|
else if ((node.type === 'VariableDeclaration' ||
|
|
node.type === 'FunctionDeclaration' ||
|
|
node.type === 'ClassDeclaration') &&
|
|
!node.declare) {
|
|
walkDeclaration(node, setupBindings, userImportAlias);
|
|
}
|
|
}
|
|
// apply ref transform
|
|
if (enableRefTransform && refTransform.shouldTransform(script.content)) {
|
|
const { rootVars, importedHelpers } = refTransform.transformAST(scriptAst, s, scriptStartOffset);
|
|
refBindings = rootVars;
|
|
for (const h of importedHelpers) {
|
|
helperImports.add(h);
|
|
}
|
|
}
|
|
}
|
|
// 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.body) {
|
|
const start = node.start + startOffset;
|
|
let end = node.end + startOffset;
|
|
// 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++;
|
|
}
|
|
// (Dropped) `ref: x` bindings
|
|
if (node.type === 'LabeledStatement' &&
|
|
node.label.name === 'ref' &&
|
|
node.body.type === 'ExpressionStatement') {
|
|
error(`ref sugar using the label syntax was an experimental proposal and ` +
|
|
`has been dropped based on community feedback. Please check out ` +
|
|
`the new proposal at https://github.com/vuejs/rfcs/discussions/369`, 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_EMITS ||
|
|
imported === DEFINE_EXPOSE)) {
|
|
warnOnce(`\`${imported}\` is a compiler macro and no longer needs to be imported.`);
|
|
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', true);
|
|
}
|
|
}
|
|
if (node.specifiers.length && removed === node.specifiers.length) {
|
|
s.remove(node.start + startOffset, node.end + startOffset);
|
|
}
|
|
}
|
|
if (node.type === 'ExpressionStatement') {
|
|
// process `defineProps` and `defineEmit(s)` calls
|
|
if (processDefineProps(node.expression) ||
|
|
processDefineEmits(node.expression) ||
|
|
processWithDefaults(node.expression)) {
|
|
s.remove(node.start + startOffset, node.end + startOffset);
|
|
}
|
|
else if (processDefineExpose(node.expression)) {
|
|
// defineExpose({}) -> expose({})
|
|
const callee = node.expression.callee;
|
|
s.overwrite(callee.start + startOffset, callee.end + startOffset, 'expose');
|
|
}
|
|
}
|
|
if (node.type === 'VariableDeclaration' && !node.declare) {
|
|
const total = node.declarations.length;
|
|
let left = total;
|
|
for (let i = 0; i < total; i++) {
|
|
const decl = node.declarations[i];
|
|
if (decl.init) {
|
|
// defineProps / defineEmits
|
|
const isDefineProps = processDefineProps(decl.init) || processWithDefaults(decl.init);
|
|
if (isDefineProps) {
|
|
propsIdentifier = scriptSetup.content.slice(decl.id.start, decl.id.end);
|
|
}
|
|
const isDefineEmits = processDefineEmits(decl.init);
|
|
if (isDefineEmits) {
|
|
emitIdentifier = scriptSetup.content.slice(decl.id.start, decl.id.end);
|
|
}
|
|
if (isDefineProps || isDefineEmits) {
|
|
if (left === 1) {
|
|
s.remove(node.start + startOffset, node.end + startOffset);
|
|
}
|
|
else {
|
|
let start = decl.start + startOffset;
|
|
let end = decl.end + startOffset;
|
|
if (i < total - 1) {
|
|
// not the last one, locate the start of the next
|
|
end = node.declarations[i + 1].start + startOffset;
|
|
}
|
|
else {
|
|
// last one, locate the end of the prev
|
|
start = node.declarations[i - 1].end + startOffset;
|
|
}
|
|
s.remove(start, end);
|
|
left--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// walk decalrations to record declared bindings
|
|
if ((node.type === 'VariableDeclaration' ||
|
|
node.type === 'FunctionDeclaration' ||
|
|
node.type === 'ClassDeclaration') &&
|
|
!node.declare) {
|
|
walkDeclaration(node, setupBindings, userImportAlias);
|
|
}
|
|
// walk statements & named exports / variable declarations for top level
|
|
// await
|
|
if ((node.type === 'VariableDeclaration' && !node.declare) ||
|
|
node.type.endsWith('Statement')) {
|
|
estreeWalker.walk(node, {
|
|
enter(child, parent) {
|
|
if (CompilerDOM.isFunctionType(child)) {
|
|
this.skip();
|
|
}
|
|
if (child.type === 'AwaitExpression') {
|
|
hasAwait = true;
|
|
processAwait(child, parent.type === 'ExpressionStatement');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
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);
|
|
}
|
|
if (isTS) {
|
|
// runtime enum
|
|
if (node.type === 'TSEnumDeclaration') {
|
|
registerBinding(setupBindings, node.id, "setup-const" /* SETUP_CONST */);
|
|
}
|
|
// move all Type declarations to outer scope
|
|
if (node.type.startsWith('TS') ||
|
|
(node.type === 'ExportNamedDeclaration' &&
|
|
node.exportKind === 'type') ||
|
|
(node.type === 'VariableDeclaration' && node.declare)) {
|
|
recordType(node, declaredTypes);
|
|
s.move(start, end, 0);
|
|
}
|
|
}
|
|
}
|
|
// 3. Apply ref sugar transform
|
|
if (enableRefTransform && refTransform.shouldTransform(scriptSetup.content)) {
|
|
const { rootVars, importedHelpers } = refTransform.transformAST(scriptSetupAst, s, startOffset, refBindings);
|
|
refBindings = refBindings ? [...refBindings, ...rootVars] : rootVars;
|
|
for (const h of importedHelpers) {
|
|
helperImports.add(h);
|
|
}
|
|
}
|
|
// 4. extract runtime props/emits code from setup context type
|
|
if (propsTypeDecl) {
|
|
extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes);
|
|
}
|
|
if (emitsTypeDecl) {
|
|
extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits);
|
|
}
|
|
// 5. check useOptions args to make sure it doesn't reference setup scope
|
|
// variables
|
|
checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS);
|
|
checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS);
|
|
checkInvalidScopeReference(emitsRuntimeDecl, 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.body));
|
|
}
|
|
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];
|
|
}
|
|
// known ref bindings
|
|
if (refBindings) {
|
|
for (const key of refBindings) {
|
|
bindingMetadata[key] = "setup-ref" /* SETUP_REF */;
|
|
}
|
|
}
|
|
// 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) {
|
|
// mark as any and only cast on assignment
|
|
// since the user defined complex types may be incompatible with the
|
|
// inferred type from generated runtime declarations
|
|
args += `: any`;
|
|
}
|
|
// 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${propsTypeDecl ? ` as ${genSetupPropsType(propsTypeDecl)}` : ``}`);
|
|
}
|
|
// inject temp variables for async context preservation
|
|
if (hasAwait) {
|
|
const any = isTS ? `: any` : ``;
|
|
s.prependRight(startOffset, `\nlet __temp${any}, __restore${any}\n`);
|
|
}
|
|
const destructureElements = hasDefineExposeCall || !options.inlineTemplate ? [`expose`] : [];
|
|
if (emitIdentifier) {
|
|
destructureElements.push(emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`);
|
|
}
|
|
if (destructureElements.length) {
|
|
args += `, { ${destructureElements.join(', ')} }`;
|
|
if (emitsTypeDecl) {
|
|
args += `: { emit: (${scriptSetup.content.slice(emitsTypeDecl.start, emitsTypeDecl.end)}), expose: any, 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(Object.assign(Object.assign({ 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: Object.assign(Object.assign({}, (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(CompilerDOM.UNREF)) {
|
|
helperImports.delete('unref');
|
|
}
|
|
returned = code;
|
|
}
|
|
else {
|
|
returned = `() => {}`;
|
|
}
|
|
}
|
|
else {
|
|
// return bindings from setup
|
|
const allBindings = Object.assign({}, setupBindings);
|
|
for (const key in userImports) {
|
|
if (!userImports[key].isType && userImports[key].isUsedInTemplate) {
|
|
allBindings[key] = true;
|
|
}
|
|
}
|
|
returned = `{ ${Object.keys(allBindings).join(', ')} }`;
|
|
}
|
|
if (!options.inlineTemplate && !false) {
|
|
// in non-inline mode, the `__isScriptSetup: true` flag is used by
|
|
// componentPublicInstance proxy to allow properties that start with $ or _
|
|
s.appendRight(endOffset, `\nconst __returned__ = ${returned}\n` +
|
|
`Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n` +
|
|
`return __returned__` +
|
|
`\n}\n\n`);
|
|
}
|
|
else {
|
|
s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`);
|
|
}
|
|
// 11. finalize default export
|
|
let runtimeOptions = ``;
|
|
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 (emitsRuntimeDecl) {
|
|
runtimeOptions += `\n emits: ${scriptSetup.content
|
|
.slice(emitsRuntimeDecl.start, emitsRuntimeDecl.end)
|
|
.trim()},`;
|
|
}
|
|
else if (emitsTypeDecl) {
|
|
runtimeOptions += genRuntimeEmits(typeDeclaredEmits);
|
|
}
|
|
// <script setup> components are closed by default. If the user did not
|
|
// explicitly call `defineExpose`, call expose() with no args.
|
|
const exposeCall = hasDefineExposeCall || options.inlineTemplate ? `` : ` expose()\n`;
|
|
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.
|
|
if (defaultExport) {
|
|
s.prependLeft(startOffset, `\n${hasAwait ? `async ` : ``}function setup(${args}) {\n`);
|
|
s.append(`\nexport default /*#__PURE__*/${helper(`defineComponent`)}({${def}${runtimeOptions}\n setup})`);
|
|
}
|
|
else {
|
|
s.prependLeft(startOffset, `\nexport default /*#__PURE__*/${helper(`defineComponent`)}({${def}${runtimeOptions}\n ${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`);
|
|
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${exposeCall}`);
|
|
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 Object.assign(Object.assign({}, scriptSetup), { bindings: bindingMetadata, content: s.toString(), map: s.generateMap({
|
|
source: filename,
|
|
hires: true,
|
|
includeContent: true
|
|
}), scriptAst: scriptAst === null || scriptAst === void 0 ? void 0 : scriptAst.body, scriptSetupAst: scriptSetupAst === null || scriptSetupAst === void 0 ? void 0 : scriptSetupAst.body });
|
|
}
|
|
function registerBinding(bindings, node, type) {
|
|
bindings[node.name] = type;
|
|
}
|
|
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, c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS));
|
|
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 */;
|
|
}
|
|
registerBinding(bindings, id, 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 } = ...
|
|
const type = isDefineCall
|
|
? "setup-const" /* SETUP_CONST */
|
|
: isConst
|
|
? "setup-maybe-ref" /* SETUP_MAYBE_REF */
|
|
: "setup-let" /* SETUP_LET */;
|
|
registerBinding(bindings, p.key, type);
|
|
}
|
|
else {
|
|
walkPattern(p.value, bindings, isConst, isDefineCall);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// ...rest
|
|
// argument can only be identifer when destructuring
|
|
const type = isConst ? "setup-const" /* SETUP_CONST */ : "setup-let" /* SETUP_LET */;
|
|
registerBinding(bindings, p.argument, type);
|
|
}
|
|
}
|
|
}
|
|
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') {
|
|
const type = isDefineCall
|
|
? "setup-const" /* SETUP_CONST */
|
|
: isConst
|
|
? "setup-maybe-ref" /* SETUP_MAYBE_REF */
|
|
: "setup-let" /* SETUP_LET */;
|
|
registerBinding(bindings, node, type);
|
|
}
|
|
else if (node.type === 'RestElement') {
|
|
// argument can only be identifer when destructuring
|
|
const type = isConst ? "setup-const" /* SETUP_CONST */ : "setup-let" /* SETUP_LET */;
|
|
registerBinding(bindings, node.argument, type);
|
|
}
|
|
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') {
|
|
const type = isDefineCall
|
|
? "setup-const" /* SETUP_CONST */
|
|
: isConst
|
|
? "setup-maybe-ref" /* SETUP_MAYBE_REF */
|
|
: "setup-let" /* SETUP_LET */;
|
|
registerBinding(bindings, node.left, type);
|
|
}
|
|
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) {
|
|
const members = node.type === 'TSTypeLiteral' ? node.members : node.body;
|
|
for (const m of members) {
|
|
if ((m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
|
|
m.key.type === 'Identifier') {
|
|
let type;
|
|
{
|
|
if (m.type === 'TSMethodSignature') {
|
|
type = ['Function'];
|
|
}
|
|
else if (m.typeAnnotation) {
|
|
type = inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes);
|
|
}
|
|
}
|
|
props[m.key.name] = {
|
|
key: m.key.name,
|
|
required: !m.optional,
|
|
type: type || [`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 'TSParenthesizedType':
|
|
return inferRuntimeType(node.typeAnnotation, declaredTypes);
|
|
case 'TSUnionType':
|
|
return [
|
|
...new Set([].concat(...node.types.map(t => inferRuntimeType(t, declaredTypes))))
|
|
];
|
|
case 'TSIntersectionType':
|
|
return ['Object'];
|
|
default:
|
|
return [`null`]; // no runtime check
|
|
}
|
|
}
|
|
function toRuntimeTypeString(types) {
|
|
return types.length > 1 ? `[${types.join(', ')}]` : types[0];
|
|
}
|
|
function extractRuntimeEmits(node, emits) {
|
|
if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {
|
|
const members = node.type === 'TSTypeLiteral' ? node.members : node.body;
|
|
for (let t of members) {
|
|
if (t.type === 'TSCallSignatureDeclaration') {
|
|
extractEventNames(t.parameters[0], emits);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else {
|
|
extractEventNames(node.parameters[0], emits);
|
|
}
|
|
}
|
|
function extractEventNames(eventName, emits) {
|
|
if (eventName.type === 'Identifier' &&
|
|
eventName.typeAnnotation &&
|
|
eventName.typeAnnotation.type === 'TSTypeAnnotation') {
|
|
const typeNode = eventName.typeAnnotation.typeAnnotation;
|
|
if (typeNode.type === 'TSLiteralType') {
|
|
if (typeNode.literal.type !== 'UnaryExpression') {
|
|
emits.add(String(typeNode.literal.value));
|
|
}
|
|
}
|
|
else if (typeNode.type === 'TSUnionType') {
|
|
for (const t of typeNode.types) {
|
|
if (t.type === 'TSLiteralType' &&
|
|
t.literal.type !== 'UnaryExpression') {
|
|
emits.add(String(t.literal.value));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function genRuntimeEmits(emits) {
|
|
return emits.size
|
|
? `\n emits: [${Array.from(emits)
|
|
.map(p => JSON.stringify(p))
|
|
.join(', ')}],`
|
|
: ``;
|
|
}
|
|
function isCallOf(node, test) {
|
|
return !!(node &&
|
|
node.type === 'CallExpression' &&
|
|
node.callee.type === 'Identifier' &&
|
|
(typeof test === 'string'
|
|
? node.callee.name === test
|
|
: test(node.callee.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;
|
|
}
|
|
}
|
|
/**
|
|
* 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 = {};
|
|
// #3270, #3275
|
|
// mark non-script-setup so we don't resolve components/directives from these
|
|
Object.defineProperty(bindings, '__isScriptSetup', {
|
|
enumerable: false,
|
|
value: false
|
|
});
|
|
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 [];
|
|
}
|
|
const templateUsageCheckCache = createCache();
|
|
function resolveTemplateUsageCheckString(sfc) {
|
|
const { content, ast } = sfc.template;
|
|
const cached = templateUsageCheckCache.get(content);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
let code = '';
|
|
CompilerDOM.transform(CompilerDOM.createRoot([ast]), {
|
|
nodeTransforms: [
|
|
node => {
|
|
if (node.type === 1 /* ELEMENT */) {
|
|
if (!CompilerDOM.parserOptions.isNativeTag(node.tag) &&
|
|
!CompilerDOM.parserOptions.isBuiltInComponent(node.tag)) {
|
|
code += `,${shared.camelize(node.tag)},${shared.capitalize(shared.camelize(node.tag))}`;
|
|
}
|
|
for (let i = 0; i < node.props.length; i++) {
|
|
const prop = node.props[i];
|
|
if (prop.type === 7 /* DIRECTIVE */) {
|
|
if (!isBuiltInDir(prop.name)) {
|
|
code += `,v${shared.capitalize(shared.camelize(prop.name))}`;
|
|
}
|
|
if (prop.exp) {
|
|
code += `,${stripStrings(prop.exp.content)}`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (node.type === 5 /* INTERPOLATION */) {
|
|
code += `,${stripStrings(node.content.content)}`;
|
|
}
|
|
}
|
|
]
|
|
});
|
|
code += ';';
|
|
templateUsageCheckCache.set(content, code);
|
|
return code;
|
|
}
|
|
function stripStrings(exp) {
|
|
return exp
|
|
.replace(/'[^']+'|"[^"]+"/g, '')
|
|
.replace(/`[^`]+`/g, stripTemplateString);
|
|
}
|
|
function stripTemplateString(str) {
|
|
const interpMatch = str.match(/\${[^}]+}/g);
|
|
if (interpMatch) {
|
|
return interpMatch.map(m => m.slice(2, -1)).join(',');
|
|
}
|
|
return '';
|
|
}
|
|
|
|
exports.extractIdentifiers = compilerCore.extractIdentifiers;
|
|
exports.generateCodeFrame = compilerCore.generateCodeFrame;
|
|
exports.isInDestructureAssignment = compilerCore.isInDestructureAssignment;
|
|
exports.isStaticProperty = compilerCore.isStaticProperty;
|
|
exports.walkIdentifiers = compilerCore.walkIdentifiers;
|
|
exports.MagicString = MagicString__default;
|
|
exports.babelParse = parser.parse;
|
|
exports.walk = estreeWalker.walk;
|
|
exports.shouldTransformRef = refTransform.shouldTransform;
|
|
exports.transformRef = refTransform.transform;
|
|
exports.transformRefAST = refTransform.transformAST;
|
|
exports.compileScript = compileScript;
|
|
exports.compileStyle = compileStyle;
|
|
exports.compileStyleAsync = compileStyleAsync;
|
|
exports.compileTemplate = compileTemplate;
|
|
exports.parse = parse;
|
|
exports.rewriteDefault = rewriteDefault;
|