vue-svg-loader
Version:
Use SVG files as Vue Components
1,264 lines (1,246 loc) • 76.8 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var CompilerDOM = require('@vue/compiler-dom');
var sourceMap = require('source-map');
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; } else {
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 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 SFC_CACHE_MAX_SIZE = 500;
const sourceToSFC = new (require('lru-cache'))(SFC_CACHE_MAX_SIZE);
function parse(source, { sourceMap = true, filename = 'component.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: []
};
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
props.some(p => p.type === 6 /* ATTRIBUTE */ &&
p.name === 'lang' &&
p.value &&
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)) {
return;
}
switch (node.tag) {
case 'template':
if (!descriptor.template) {
descriptor.template = createBlock(node, source, false);
}
else {
errors.push(createDuplicateBlockError(node));
}
break;
case 'script':
const block = createBlock(node, source, pad);
const isSetup = !!block.attrs.setup;
if (isSetup && !descriptor.scriptSetup) {
descriptor.scriptSetup = block;
break;
}
if (!isSetup && !descriptor.script) {
descriptor.script = block;
break;
}
errors.push(createDuplicateBlockError(node, isSetup));
break;
case 'style':
descriptor.styles.push(createBlock(node, source, pad));
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);
}
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 === 'vars' && typeof attrs.vars === 'string') {
block.vars = attrs.vars;
}
else if (p.name === 'module') {
block.module = attrs[p.name];
}
}
else if (type === 'template' && p.name === 'functional') {
block.functional = true;
}
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 : '');
}
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) {
// explicit base - directly rewrite the url into absolute url
// does not apply to absolute urls or urls that start with `@`
// since they are aliases
if (attr.value.content[0] !== '@' &&
isRelativeUrl(attr.value.content)) {
// when packaged in the browser, path will be using the posix-
// only version provided by rollup-plugin-node-builtins.
attr.value.content = (path__default.posix || path__default).join(options.base, 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 importsArray = Array.from(context.imports);
const existing = importsArray.find(i => i.path === path);
if (existing) {
return existing.exp;
}
const name = `_imports_${importsArray.length}`;
const exp = compilerCore.createSimpleExpression(name, false, loc, true);
exp.isRuntimeConstant = true;
context.imports.add({ exp, path });
if (hash && path) {
const ret = context.hoist(compilerCore.createSimpleExpression(`${name} + '${hash}'`, false, loc, true));
ret.isRuntimeConstant = true;
return ret;
}
else {
return exp;
}
}
else {
return compilerCore.createSimpleExpression(`''`, false, loc, true);
}
}
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;
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 importsArray = Array.from(context.imports);
const existingImportsIndex = importsArray.findIndex(i => i.path === path);
if (existingImportsIndex > -1) {
exp = compilerCore.createSimpleExpression(`_imports_${existingImportsIndex}`, false, attr.loc, true);
}
else {
exp = compilerCore.createSimpleExpression(`_imports_${importsArray.length}`, false, attr.loc, true);
context.imports.add({ exp, path });
}
compoundExpression.children.push(exp);
}
}
else {
const exp = compilerCore.createSimpleExpression(`"${url}"`, false, attr.loc, true);
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.isRuntimeConstant = true;
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, inMap, source, ssr = 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];
}
let { code, map } = compiler.compile(source, {
mode: 'module',
prefixIdentifiers: true,
hoistStatic: true,
cacheHandlers: true,
...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, 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;
}
}
});
}
var trimPlugin = postcss__default.plugin('trim', () => (css) => {
css.walk(({ type, raws }) => {
if (type === 'rule' || type === 'atrule') {
if (raws.before)
raws.before = '\n';
if (raws.after)
raws.after = '\n';
}
});
});
const animationNameRE = /^(-\w+-)?animation-name$/;
const animationRE = /^(-\w+-)?animation$/;
var scopedPlugin = postcss__default.plugin('vue-scoped', (id) => (root) => {
const keyframes = Object.create(null);
const shortId = id.replace(/^data-v-/, '');
root.each(function rewriteSelectors(node) {
if (node.type !== 'rule') {
// handle media queries
if (node.type === 'atrule') {
if (node.name === 'media' || node.name === 'supports') {
node.each(rewriteSelectors);
}
else if (/-?keyframes$/.test(node.name)) {
// register keyframes
keyframes[node.params] = node.params = node.params + '-' + shortId;
}
}
return;
}
node.selector = selectorParser__default(selectors => {
function rewriteSelector(selector, slotted) {
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 = '';
console.warn(`[@vue/compiler-sfc] the >>> and /deep/ combinators have ` +
`been deprecated. Use ::v-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
selector.insertAfter(n, n.nodes[0]);
// 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
console.warn(`[@vue/compiler-sfc] ::v-deep usage as a combinator has ` +
`been deprecated. Use ::v-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(n.nodes[0], true /* slotted */);
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') {
selectors.insertAfter(selector, n.nodes[0]);
selectors.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: `"`
}));
}
}
selectors.each(selector => rewriteSelector(selector));
}).processSync(node.selector);
});
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(',');
}
});
}
});
function isSpaceCombinator(node) {
return node.type === 'combinator' && /^\s+$/.test(node.value);
}
const cssVarRE = /\bvar\(--(global:)?([^)]+)\)/g;
var scopedVarsPlugin = postcss__default.plugin('vue-scoped', (id) => (root) => {
const shortId = id.replace(/^data-v-/, '');
root.walkDecls(decl => {
// rewrite CSS variables
if (cssVarRE.test(decl.value)) {
decl.value = decl.value.replace(cssVarRE, (_, $1, $2) => {
return $1 ? `var(--${$2})` : `var(--${shortId}-${$2})`;
});
}
});
});
// .scss/.sass processor
const scss = (source, map, options, load = require) => {
const nodeSass = load('sass');
const finalOptions = {
...options,
data: source,
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(source, { ...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: [] };
}
};
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, vars = false, trim = true, 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.map;
const source = preProcessedSource ? preProcessedSource.code : options.source;
const plugins = (postcssPlugins || []).slice();
if (vars && scoped) {
// vars + scoped, only applies to raw source before other transforms
// #1623
plugins.unshift(scopedVarsPlugin(id));
}
if (trim) {
plugins.push(trimPlugin());
}
if (scoped) {
plugins.push(scopedPlugin(id));
}
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.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.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);
}
function genCssVarsCode(varsExp, scoped, knownBindings) {
const exp = CompilerDOM.createSimpleExpression(varsExp, false);
const context = CompilerDOM.createTransformContext(CompilerDOM.createRoot([]), {
prefixIdentifiers: true
});
if (knownBindings) {
// when compiling <script setup> we already know what bindings are exposed
// so we can avoid prefixing them from the ctx.
for (const key in knownBindings) {
context.identifiers[key] = 1;
}
}
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 `__useCssVars__(_ctx => (${transformedString})${scoped ? `, true` : ``})`;
}
// <script setup> already gets the calls injected as part of the transform
// this is only for single normal <script>
function injectCssVarsCalls(sfc, parserPlugins) {
const script = rewriteDefault(sfc.script.content, `__default__`, parserPlugins);
let calls = ``;
for (const style of sfc.styles) {
const vars = style.attrs.vars;
if (typeof vars === 'string') {
calls += genCssVarsCode(vars, !!style.scoped) + '\n';
}
}
return (script +
`\nimport { useCssVars as __useCssVars__ } from 'vue'\n` +
`const __injectCSSVars__ = () => {\n${calls}}\n` +
`const __setup__ = __default__.setup\n` +
`__default__.setup = __setup__\n` +
` ? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }\n` +
` : __injectCSSVars__\n` +
`export default __default__`);
}
let hasWarned = false;
/**
* 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, styles, source, filename } = sfc;
if ( !hasWarned && scriptSetup) {
hasWarned = true;
// @ts-ignore `console.info` cannot be null error
console[console.info ? 'info' : 'log'](`\n[@vue/compiler-sfc] <script setup> is still an experimental proposal.\n` +
`Follow https://github.com/vuejs/rfcs/pull/182 for its status.\n`);
}
const hasCssVars = styles.some(s => typeof s.attrs.vars === 'string');
const scriptLang = script && script.lang;
const scriptSetupLang = scriptSetup && scriptSetup.lang;
const isTS = scriptLang === 'ts' || scriptSetupLang === 'ts';
const plugins = [
...(options.babelParserPlugins || []),
...shared.babelParserDefaultPlugins,
...(isTS ? ['typescript'] : [])
];
if (!scriptSetup) {
if (!script) {
throw new Error(`SFC contains no <script> tags.`);
}
if (scriptLang && scriptLang !== 'ts') {
// do not process non js/ts script blocks
return script;
}
return {
...script,
content: hasCssVars ? injectCssVarsCalls(sfc, plugins) : script.content,
bindings: analyzeScriptBindings()
};
}
if (script && scriptLang !== scriptSetupLang) {
throw new Error(`<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 bindings = {};
const imports = {};
const setupScopeVars = {};
const setupExports = {};
let exportAllIndex = 0;
let defaultExport;
let needDefaultExportRefCheck = false;
let hasAwait = false;
const checkDuplicateDefaultExport = (node) => {
if (defaultExport) {
// <script> already has export default
throw new Error(`Default export is already declared in normal <script>.\n\n` +
shared.generateCodeFrame(source, node.start + startOffset, node.start + startOffset + `export default`.length));
}
};
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;
// 1. process normal <script> first if it exists
if (script) {
// import dedupe between <script> and <script setup>
const scriptAST = parser.parse(script.content, {
plugins,
sourceType: 'module'
}).program.body;
for (const node of scriptAST) {
if (node.type === 'ImportDeclaration') {
// record imports for dedupe
for (const { local: { name } } of node.specifiers) {
imports[name] = node.source.value;
}
}
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.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. check <script setup="xxx"> function signature
const setupValue = scriptSetup.setup;
const hasExplicitSignature = typeof setupValue === 'string';
let propsVar;
let emitVar;
let slotsVar;
let attrsVar;
let propsType = `{}`;
let emitType = `(e: string, ...args: any[]) => void`;
let slotsType = `__Slots__`;
let attrsType = `Record<string, any>`;
let propsASTNode;
let setupCtxASTNode;
// props/emits declared via types
const typeDeclaredProps = {};
const typeDeclaredEmits = new Set();
// record declared types for runtime props type generation
const declaredTypes = {};
if (isTS && hasExplicitSignature) {
// <script setup="xxx" lang="ts">
// parse the signature to extract the props/emit variables the user w