UNPKG

vue-svg-loader

Version:
1,264 lines (1,246 loc) 76.8 kB
'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