├── .gitignore ├── .prettierrc.js ├── jest.config.js ├── lib ├── stylePlugins │ ├── trim.ts │ └── scoped.ts ├── index.ts ├── types.ts ├── templateCompilerModules │ ├── assetUrl.ts │ ├── srcset.ts │ └── utils.ts ├── styleProcessors │ └── index.ts ├── parse.ts ├── compileStyle.ts └── compileTemplate.ts ├── tsconfig.json ├── .circleci └── config.yml ├── package.json ├── test ├── stylePluginScoped.spec.ts ├── compileStyle.spec.ts └── compileTemplate.spec.ts ├── README.md └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.log 4 | dist 5 | coverage 6 | .idea 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true 4 | } 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost', 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/stylePlugins/trim.ts: -------------------------------------------------------------------------------- 1 | import { Root } from 'postcss' 2 | import * as postcss from 'postcss' 3 | 4 | export default postcss.plugin('trim', () => (css: Root) => { 5 | css.walk(({ type, raws }) => { 6 | if (type === 'rule' || type === 'atrule') { 7 | if (raws.before) raws.before = '\n' 8 | if (raws.after) raws.after = '\n' 9 | } 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "dist", 5 | "sourceMap": false, 6 | "target": "es2015", 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "declaration": true, 11 | "allowJs": false, 12 | "allowSyntheticDefaultImports": true, 13 | "noUnusedLocals": true, 14 | "strictNullChecks": true, 15 | "noImplicitAny": true, 16 | "removeComments": false, 17 | "lib": [ 18 | "es6", 19 | "es7" 20 | ] 21 | }, 22 | "include": [ 23 | "lib" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import { parse, SFCBlock, SFCCustomBlock, SFCDescriptor } from './parse' 2 | 3 | import { 4 | compileTemplate, 5 | TemplateCompileOptions, 6 | TemplateCompileResult 7 | } from './compileTemplate' 8 | 9 | import { 10 | compileStyle, 11 | compileStyleAsync, 12 | StyleCompileOptions, 13 | StyleCompileResults 14 | } from './compileStyle' 15 | 16 | // API 17 | export { parse, compileTemplate, compileStyle, compileStyleAsync } 18 | 19 | // types 20 | export { 21 | SFCBlock, 22 | SFCCustomBlock, 23 | SFCDescriptor, 24 | TemplateCompileOptions, 25 | TemplateCompileResult, 26 | StyleCompileOptions, 27 | StyleCompileResults 28 | } 29 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | - image: circleci/node:8 10 | 11 | working_directory: ~/repo 12 | 13 | steps: 14 | - checkout 15 | 16 | # Download and cache dependencies 17 | - restore_cache: 18 | keys: 19 | - v1-dependencies-{{ checksum "package.json" }} 20 | # fallback to using the latest cache if no exact match is found 21 | - v1-dependencies- 22 | 23 | - run: yarn install --ignore-engines 24 | 25 | - save_cache: 26 | paths: 27 | - node_modules 28 | key: v1-dependencies-{{ checksum "package.json" }} 29 | 30 | # run tests! 31 | - run: yarn test 32 | -------------------------------------------------------------------------------- /lib/types.ts: -------------------------------------------------------------------------------- 1 | import { SFCDescriptor } from './parse' 2 | 3 | export interface StartOfSourceMap { 4 | file?: string 5 | sourceRoot?: string 6 | } 7 | 8 | export interface RawSourceMap extends StartOfSourceMap { 9 | version: string 10 | sources: string[] 11 | names: string[] 12 | sourcesContent?: string[] 13 | mappings: string 14 | } 15 | 16 | export interface VueTemplateCompiler { 17 | parseComponent(source: string, options?: any): SFCDescriptor 18 | 19 | compile( 20 | template: string, 21 | options: VueTemplateCompilerOptions 22 | ): VueTemplateCompilerResults 23 | 24 | ssrCompile( 25 | template: string, 26 | options: VueTemplateCompilerOptions 27 | ): VueTemplateCompilerResults 28 | } 29 | 30 | // we'll just shim this much for now - in the future these types 31 | // should come from vue-template-compiler directly, or this package should be 32 | // part of the vue monorepo. 33 | export interface VueTemplateCompilerOptions { 34 | modules?: Object[] 35 | outputSourceRange?: boolean 36 | whitespace?: 'preserve' | 'condense' 37 | directives?: { [key: string]: Function } 38 | } 39 | 40 | export interface VueTemplateCompilerParseOptions { 41 | pad?: 'line' | 'space' 42 | } 43 | 44 | export interface ErrorWithRange { 45 | msg: string 46 | start: number 47 | end: number 48 | } 49 | 50 | export interface VueTemplateCompilerResults { 51 | ast: Object | undefined 52 | render: string 53 | staticRenderFns: string[] 54 | errors: (string | ErrorWithRange)[] 55 | tips: (string | ErrorWithRange)[] 56 | } 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue/component-compiler-utils", 3 | "version": "3.3.0", 4 | "description": "Lower level utilities for compiling Vue single file components", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "dist", 9 | "lib" 10 | ], 11 | "scripts": { 12 | "lint": "prettier --write \"{lib,test}/**/*.ts\"", 13 | "test": "prettier --list-different \"{lib,test}/**/*.ts\" && jest --coverage", 14 | "build": "rm -rf dist && tsc", 15 | "prepublishOnly": "yarn build && conventional-changelog -p angular -r 2 -i CHANGELOG.md -s" 16 | }, 17 | "gitHooks": { 18 | "pre-commit": "lint-staged" 19 | }, 20 | "lint-staged": { 21 | "*.{ts,js}": [ 22 | "prettier --write", 23 | "git add" 24 | ] 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/vuejs/component-compiler-utils.git" 29 | }, 30 | "keywords": [ 31 | "vue", 32 | "sfc", 33 | "component", 34 | "compiler" 35 | ], 36 | "author": "Evan You", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/vuejs/component-compiler-utils/issues" 40 | }, 41 | "homepage": "https://github.com/vuejs/component-compiler-utils#readme", 42 | "devDependencies": { 43 | "@types/jest": "^22.2.3", 44 | "@types/node": "^10.12.20", 45 | "conventional-changelog-cli": "^2.0.11", 46 | "jest": "^24.0.0", 47 | "less": "^3.9.0", 48 | "lint-staged": "^8.1.1", 49 | "pug": "^2.0.3", 50 | "sass": "^1.17.3", 51 | "stylus": "^0.54.5", 52 | "ts-jest": "^24.0.0", 53 | "typescript": "^3.3.0", 54 | "vue": "^2.6.6", 55 | "vue-template-compiler": "^2.6.6", 56 | "yorkie": "^2.0.0" 57 | }, 58 | "dependencies": { 59 | "consolidate": "^0.15.1", 60 | "hash-sum": "^1.0.2", 61 | "lru-cache": "^4.1.2", 62 | "merge-source-map": "^1.1.0", 63 | "postcss": "^7.0.36", 64 | "postcss-selector-parser": "^6.0.2", 65 | "source-map": "~0.6.1", 66 | "vue-template-es2015-compiler": "^1.9.0" 67 | }, 68 | "optionalDependencies": { 69 | "prettier": "^1.18.2 || ^2.0.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/templateCompilerModules/assetUrl.ts: -------------------------------------------------------------------------------- 1 | // vue compiler module for transforming `:` to `require` 2 | 3 | import { urlToRequire, ASTNode, Attr } from './utils' 4 | 5 | export interface AssetURLOptions { 6 | [name: string]: string | string[] 7 | } 8 | 9 | export interface TransformAssetUrlsOptions { 10 | /** 11 | * If base is provided, instead of transforming relative asset urls into 12 | * imports, they will be directly rewritten to absolute urls. 13 | */ 14 | base?: string 15 | } 16 | 17 | const defaultOptions: AssetURLOptions = { 18 | audio: 'src', 19 | video: ['src', 'poster'], 20 | source: 'src', 21 | img: 'src', 22 | image: ['xlink:href', 'href'], 23 | use: ['xlink:href', 'href'] 24 | } 25 | 26 | export default ( 27 | userOptions?: AssetURLOptions, 28 | transformAssetUrlsOption?: TransformAssetUrlsOptions 29 | ) => { 30 | const options = userOptions 31 | ? Object.assign({}, defaultOptions, userOptions) 32 | : defaultOptions 33 | 34 | return { 35 | postTransformNode: (node: ASTNode) => { 36 | transform(node, options, transformAssetUrlsOption) 37 | } 38 | } 39 | } 40 | 41 | function transform( 42 | node: ASTNode, 43 | options: AssetURLOptions, 44 | transformAssetUrlsOption?: TransformAssetUrlsOptions 45 | ) { 46 | for (const tag in options) { 47 | if ((tag === '*' || node.tag === tag) && node.attrs) { 48 | const attributes = options[tag] 49 | if (typeof attributes === 'string') { 50 | node.attrs.some(attr => 51 | rewrite(attr, attributes, transformAssetUrlsOption) 52 | ) 53 | } else if (Array.isArray(attributes)) { 54 | attributes.forEach(item => 55 | node.attrs.some(attr => rewrite(attr, item, transformAssetUrlsOption)) 56 | ) 57 | } 58 | } 59 | } 60 | } 61 | 62 | function rewrite( 63 | attr: Attr, 64 | name: string, 65 | transformAssetUrlsOption?: TransformAssetUrlsOptions 66 | ) { 67 | if (attr.name === name) { 68 | const value = attr.value 69 | // only transform static URLs 70 | if (value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') { 71 | attr.value = urlToRequire(value.slice(1, -1), transformAssetUrlsOption) 72 | return true 73 | } 74 | } 75 | return false 76 | } 77 | -------------------------------------------------------------------------------- /lib/templateCompilerModules/srcset.ts: -------------------------------------------------------------------------------- 1 | // vue compiler module for transforming `img:srcset` to a number of `require`s 2 | 3 | import { urlToRequire, ASTNode } from './utils' 4 | import { TransformAssetUrlsOptions } from './assetUrl' 5 | 6 | interface ImageCandidate { 7 | require: string 8 | descriptor: string 9 | } 10 | 11 | export default (transformAssetUrlsOptions?: TransformAssetUrlsOptions) => ({ 12 | postTransformNode: (node: ASTNode) => { 13 | transform(node, transformAssetUrlsOptions) 14 | } 15 | }) 16 | 17 | // http://w3c.github.io/html/semantics-embedded-content.html#ref-for-image-candidate-string-5 18 | const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g 19 | 20 | function transform( 21 | node: ASTNode, 22 | transformAssetUrlsOptions?: TransformAssetUrlsOptions 23 | ) { 24 | const tags = ['img', 'source'] 25 | 26 | if (tags.indexOf(node.tag) !== -1 && node.attrs) { 27 | node.attrs.forEach(attr => { 28 | if (attr.name === 'srcset') { 29 | // same logic as in transform-require.js 30 | const value = attr.value 31 | const isStatic = 32 | value.charAt(0) === '"' && value.charAt(value.length - 1) === '"' 33 | if (!isStatic) { 34 | return 35 | } 36 | 37 | const imageCandidates: ImageCandidate[] = value 38 | .substr(1, value.length - 2) 39 | .split(',') 40 | .map(s => { 41 | // The attribute value arrives here with all whitespace, except 42 | // normal spaces, represented by escape sequences 43 | const [url, descriptor] = s 44 | .replace(escapedSpaceCharacters, ' ') 45 | .trim() 46 | .split(' ', 2) 47 | return { 48 | require: urlToRequire(url, transformAssetUrlsOptions), 49 | descriptor 50 | } 51 | }) 52 | 53 | // "require(url1)" 54 | // "require(url1) 1x" 55 | // "require(url1), require(url2)" 56 | // "require(url1), require(url2) 2x" 57 | // "require(url1) 1x, require(url2)" 58 | // "require(url1) 1x, require(url2) 2x" 59 | const code = imageCandidates 60 | .map( 61 | ({ require, descriptor }) => 62 | `${require} + "${descriptor ? ' ' + descriptor : ''}, " + ` 63 | ) 64 | .join('') 65 | .slice(0, -6) 66 | .concat('"') 67 | .replace(/ \+ ""$/, '') 68 | 69 | attr.value = code 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/templateCompilerModules/utils.ts: -------------------------------------------------------------------------------- 1 | import { TransformAssetUrlsOptions } from './assetUrl' 2 | import { UrlWithStringQuery, parse as uriParse } from 'url' 3 | import path from 'path' 4 | 5 | export interface Attr { 6 | name: string 7 | value: string 8 | } 9 | 10 | export interface ASTNode { 11 | tag: string 12 | attrs: Attr[] 13 | } 14 | 15 | export function urlToRequire( 16 | url: string, 17 | transformAssetUrlsOption: TransformAssetUrlsOptions = {} 18 | ): string { 19 | const returnValue = `"${url}"` 20 | // same logic as in transform-require.js 21 | const firstChar = url.charAt(0) 22 | if (firstChar === '~') { 23 | const secondChar = url.charAt(1) 24 | url = url.slice(secondChar === '/' ? 2 : 1) 25 | } 26 | 27 | const uriParts = parseUriParts(url) 28 | 29 | if (transformAssetUrlsOption.base) { 30 | // explicit base - directly rewrite the url into absolute url 31 | // does not apply to absolute urls or urls that start with `@` 32 | // since they are aliases 33 | if (firstChar === '.' || firstChar === '~') { 34 | // when packaged in the browser, path will be using the posix- 35 | // only version provided by rollup-plugin-node-builtins. 36 | return `"${(path.posix || path).join( 37 | transformAssetUrlsOption.base, 38 | uriParts.path + (uriParts.hash || '') 39 | )}"` 40 | } 41 | return returnValue 42 | } 43 | 44 | if (firstChar === '.' || firstChar === '~' || firstChar === '@') { 45 | if (!uriParts.hash) { 46 | return `require("${url}")` 47 | } else { 48 | // support uri fragment case by excluding it from 49 | // the require and instead appending it as string; 50 | // assuming that the path part is sufficient according to 51 | // the above caseing(t.i. no protocol-auth-host parts expected) 52 | return `require("${uriParts.path}") + "${uriParts.hash}"` 53 | } 54 | } 55 | return returnValue 56 | } 57 | 58 | /** 59 | * vuejs/component-compiler-utils#22 Support uri fragment in transformed require 60 | * @param urlString an url as a string 61 | */ 62 | function parseUriParts(urlString: string): UrlWithStringQuery { 63 | // initialize return value 64 | const returnValue: UrlWithStringQuery = uriParse('') 65 | if (urlString) { 66 | // A TypeError is thrown if urlString is not a string 67 | // @see https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost 68 | if ('string' === typeof urlString) { 69 | // check is an uri 70 | return uriParse(urlString) // take apart the uri 71 | } 72 | } 73 | return returnValue 74 | } 75 | -------------------------------------------------------------------------------- /lib/styleProcessors/index.ts: -------------------------------------------------------------------------------- 1 | const merge = require('merge-source-map') 2 | 3 | export interface StylePreprocessor { 4 | render( 5 | source: string, 6 | map: any | null, 7 | options: any 8 | ): StylePreprocessorResults 9 | } 10 | 11 | export interface StylePreprocessorResults { 12 | code: string 13 | map?: any 14 | errors: Array 15 | } 16 | 17 | // .scss/.sass processor 18 | const scss: StylePreprocessor = { 19 | render( 20 | source: string, 21 | map: any | null, 22 | options: any 23 | ): StylePreprocessorResults { 24 | const nodeSass = require('sass') 25 | const finalOptions = Object.assign({}, options, { 26 | data: source, 27 | file: options.filename, 28 | outFile: options.filename, 29 | sourceMap: !!map 30 | }) 31 | 32 | try { 33 | const result = nodeSass.renderSync(finalOptions) 34 | 35 | if (map) { 36 | return { 37 | code: result.css.toString(), 38 | map: merge(map, JSON.parse(result.map.toString())), 39 | errors: [] 40 | } 41 | } 42 | 43 | return { code: result.css.toString(), errors: [] } 44 | } catch (e) { 45 | return { code: '', errors: [e] } 46 | } 47 | } 48 | } 49 | 50 | const sass = { 51 | render( 52 | source: string, 53 | map: any | null, 54 | options: any 55 | ): StylePreprocessorResults { 56 | return scss.render( 57 | source, 58 | map, 59 | Object.assign({}, options, { indentedSyntax: true }) 60 | ) 61 | } 62 | } 63 | 64 | // .less 65 | const less = { 66 | render( 67 | source: string, 68 | map: any | null, 69 | options: any 70 | ): StylePreprocessorResults { 71 | const nodeLess = require('less') 72 | 73 | let result: any 74 | let error: Error | null = null 75 | nodeLess.render( 76 | source, 77 | Object.assign({}, options, { syncImport: true }), 78 | (err: Error | null, output: any) => { 79 | error = err 80 | result = output 81 | } 82 | ) 83 | 84 | if (error) return { code: '', errors: [error] } 85 | 86 | if (map) { 87 | return { 88 | code: result.css.toString(), 89 | map: merge(map, result.map), 90 | errors: [] 91 | } 92 | } 93 | 94 | return { code: result.css.toString(), errors: [] } 95 | } 96 | } 97 | 98 | // .styl 99 | const styl = { 100 | render( 101 | source: string, 102 | map: any | null, 103 | options: any 104 | ): StylePreprocessorResults { 105 | const nodeStylus = require('stylus') 106 | try { 107 | const ref = nodeStylus(source) 108 | Object.keys(options).forEach(key => ref.set(key, options[key])) 109 | if (map) ref.set('sourcemap', { inline: false, comment: false }) 110 | 111 | const result = ref.render() 112 | if (map) { 113 | return { 114 | code: result, 115 | map: merge(map, ref.sourcemap), 116 | errors: [] 117 | } 118 | } 119 | 120 | return { code: result, errors: [] } 121 | } catch (e) { 122 | return { code: '', errors: [e] } 123 | } 124 | } 125 | } 126 | 127 | export const processors: { [key: string]: StylePreprocessor } = { 128 | less, 129 | sass, 130 | scss, 131 | styl, 132 | stylus: styl 133 | } 134 | -------------------------------------------------------------------------------- /lib/parse.ts: -------------------------------------------------------------------------------- 1 | import { SourceMapGenerator } from 'source-map' 2 | import { 3 | RawSourceMap, 4 | VueTemplateCompiler, 5 | VueTemplateCompilerParseOptions 6 | } from './types' 7 | 8 | const hash = require('hash-sum') 9 | const cache = new (require('lru-cache'))(100) 10 | 11 | const splitRE = /\r?\n/g 12 | const emptyRE = /^(?:\/\/)?\s*$/ 13 | 14 | export interface ParseOptions { 15 | source: string 16 | filename?: string 17 | compiler: VueTemplateCompiler 18 | compilerParseOptions?: VueTemplateCompilerParseOptions 19 | sourceRoot?: string 20 | needMap?: boolean 21 | } 22 | 23 | export interface SFCCustomBlock { 24 | type: string 25 | content: string 26 | attrs: { [key: string]: string | true } 27 | start: number 28 | end: number 29 | map?: RawSourceMap 30 | } 31 | 32 | export interface SFCBlock extends SFCCustomBlock { 33 | lang?: string 34 | src?: string 35 | scoped?: boolean 36 | module?: string | boolean 37 | } 38 | 39 | export interface SFCDescriptor { 40 | template: SFCBlock | null 41 | script: SFCBlock | null 42 | styles: SFCBlock[] 43 | customBlocks: SFCCustomBlock[] 44 | } 45 | 46 | export function parse(options: ParseOptions): SFCDescriptor { 47 | const { 48 | source, 49 | filename = '', 50 | compiler, 51 | compilerParseOptions = { pad: 'line' } as VueTemplateCompilerParseOptions, 52 | sourceRoot = '', 53 | needMap = true 54 | } = options 55 | const cacheKey = hash( 56 | filename + source + JSON.stringify(compilerParseOptions) 57 | ) 58 | let output: SFCDescriptor = cache.get(cacheKey) 59 | if (output) return output 60 | output = compiler.parseComponent(source, compilerParseOptions) 61 | if (needMap) { 62 | if (output.script && !output.script.src) { 63 | output.script.map = generateSourceMap( 64 | filename, 65 | source, 66 | output.script.content, 67 | sourceRoot, 68 | compilerParseOptions.pad 69 | ) 70 | } 71 | if (output.styles) { 72 | output.styles.forEach(style => { 73 | if (!style.src) { 74 | style.map = generateSourceMap( 75 | filename, 76 | source, 77 | style.content, 78 | sourceRoot, 79 | compilerParseOptions.pad 80 | ) 81 | } 82 | }) 83 | } 84 | } 85 | cache.set(cacheKey, output) 86 | return output 87 | } 88 | 89 | function generateSourceMap( 90 | filename: string, 91 | source: string, 92 | generated: string, 93 | sourceRoot: string, 94 | pad?: 'line' | 'space' 95 | ): RawSourceMap { 96 | const map = new SourceMapGenerator({ 97 | file: filename.replace(/\\/g, '/'), 98 | sourceRoot: sourceRoot.replace(/\\/g, '/') 99 | }) 100 | let offset = 0 101 | if (!pad) { 102 | offset = 103 | source 104 | .split(generated) 105 | .shift()! 106 | .split(splitRE).length - 1 107 | } 108 | map.setSourceContent(filename, source) 109 | generated.split(splitRE).forEach((line, index) => { 110 | if (!emptyRE.test(line)) { 111 | map.addMapping({ 112 | source: filename, 113 | original: { 114 | line: index + 1 + offset, 115 | column: 0 116 | }, 117 | generated: { 118 | line: index + 1, 119 | column: 0 120 | } 121 | }) 122 | } 123 | }) 124 | return JSON.parse(map.toString()) 125 | } 126 | -------------------------------------------------------------------------------- /lib/stylePlugins/scoped.ts: -------------------------------------------------------------------------------- 1 | import { Root } from 'postcss' 2 | import * as postcss from 'postcss' 3 | // postcss-selector-parser does have typings but it's problematic to work with. 4 | const selectorParser = require('postcss-selector-parser') 5 | 6 | export default postcss.plugin('add-id', (options: any) => (root: Root) => { 7 | const id: string = options 8 | const keyframes = Object.create(null) 9 | 10 | root.each(function rewriteSelector(node: any) { 11 | if (!node.selector) { 12 | // handle media queries 13 | if (node.type === 'atrule') { 14 | if (node.name === 'media' || node.name === 'supports') { 15 | node.each(rewriteSelector) 16 | } else if (/-?keyframes$/.test(node.name)) { 17 | // register keyframes 18 | keyframes[node.params] = node.params = node.params + '-' + id 19 | } 20 | } 21 | return 22 | } 23 | node.selector = selectorParser((selectors: any) => { 24 | selectors.each((selector: any) => { 25 | let node: any = null 26 | 27 | // find the last child node to insert attribute selector 28 | selector.each((n: any) => { 29 | // ">>>" combinator 30 | // and /deep/ alias for >>>, since >>> doesn't work in SASS 31 | if ( 32 | n.type === 'combinator' && 33 | (n.value === '>>>' || n.value === '/deep/') 34 | ) { 35 | n.value = ' ' 36 | n.spaces.before = n.spaces.after = '' 37 | return false 38 | } 39 | 40 | // in newer versions of sass, /deep/ support is also dropped, so add a ::v-deep alias 41 | if (n.type === 'pseudo' && n.value === '::v-deep') { 42 | n.value = n.spaces.before = n.spaces.after = '' 43 | return false 44 | } 45 | 46 | if (n.type !== 'pseudo' && n.type !== 'combinator') { 47 | node = n 48 | } 49 | }) 50 | 51 | if (node) { 52 | node.spaces.after = '' 53 | } else { 54 | // For deep selectors & standalone pseudo selectors, 55 | // the attribute selectors are prepended rather than appended. 56 | // So all leading spaces must be eliminated to avoid problems. 57 | selector.first.spaces.before = '' 58 | } 59 | 60 | selector.insertAfter( 61 | node, 62 | selectorParser.attribute({ 63 | attribute: id 64 | }) 65 | ) 66 | }) 67 | }).processSync(node.selector) 68 | }) 69 | 70 | // If keyframes are found in this \n', 13 | compiler: compiler as VueTemplateCompiler, 14 | filename: 'example.vue', 15 | needMap: true 16 | }).styles[0] 17 | const result = compileStyle({ 18 | id: 'v-scope-xxx', 19 | filename: 'example.vue', 20 | source: style.content, 21 | map: style.map, 22 | scoped: false, 23 | preprocessLang: style.lang 24 | }) 25 | 26 | expect(result.errors.length).toBe(0) 27 | expect(result.code).toEqual(expect.stringContaining('color: #ff0000;')) 28 | expect(result.map).toBeTruthy() 29 | }) 30 | 31 | test('preprocess scss', () => { 32 | const style = parse({ 33 | source: 34 | '\n', 38 | compiler: compiler as VueTemplateCompiler, 39 | filename: 'example.vue', 40 | needMap: true 41 | }).styles[0] 42 | const result = compileStyle({ 43 | id: 'v-scope-xxx', 44 | filename: 'example.vue', 45 | source: style.content, 46 | map: style.map, 47 | scoped: false, 48 | preprocessLang: style.lang 49 | }) 50 | 51 | expect(result.errors.length).toBe(0) 52 | expect(result.code).toEqual(expect.stringContaining('color: red;')) 53 | expect(result.map).toBeTruthy() 54 | }) 55 | 56 | test('preprocess sass', () => { 57 | const style = parse({ 58 | source: 59 | '\n', 64 | compiler: compiler as VueTemplateCompiler, 65 | filename: 'example.vue', 66 | needMap: true 67 | }).styles[0] 68 | const result = compileStyle({ 69 | id: 'v-scope-xxx', 70 | filename: 'example.vue', 71 | source: style.content, 72 | map: style.map, 73 | scoped: false, 74 | preprocessLang: style.lang 75 | }) 76 | 77 | expect(result.errors.length).toBe(0) 78 | expect(result.code).toEqual(expect.stringContaining('color: red;')) 79 | expect(result.map).toBeTruthy() 80 | }) 81 | 82 | test('preprocess stylus', () => { 83 | const style = parse({ 84 | source: 85 | '\n', 90 | compiler: compiler as VueTemplateCompiler, 91 | filename: 'example.vue', 92 | needMap: true 93 | }).styles[0] 94 | const result = compileStyle({ 95 | id: 'v-scope-xxx', 96 | filename: 'example.vue', 97 | source: style.content, 98 | map: style.map, 99 | scoped: false, 100 | preprocessLang: style.lang 101 | }) 102 | 103 | expect(result.errors.length).toBe(0) 104 | expect(result.code).toEqual(expect.stringContaining('color: #f00;')) 105 | expect(result.map).toBeTruthy() 106 | }) 107 | 108 | test('custom postcss plugin', () => { 109 | const spy = jest.fn() 110 | 111 | compileStyle({ 112 | id: 'v-scope-xxx', 113 | filename: 'example.vue', 114 | source: '.foo { color: red }', 115 | scoped: false, 116 | postcssPlugins: [require('postcss').plugin('test-plugin', () => spy)()] 117 | }) 118 | 119 | expect(spy).toHaveBeenCalled() 120 | }) 121 | 122 | test('custom postcss options', () => { 123 | const result = compileStyle({ 124 | id: 'v-scope-xxx', 125 | filename: 'example.vue', 126 | source: '.foo { color: red }', 127 | scoped: false, 128 | postcssOptions: { random: 'foo' } 129 | }) 130 | 131 | expect((result.rawResult as any).opts.random).toBe('foo') 132 | }) 133 | 134 | test('async postcss plugin in sync mode', () => { 135 | const result = compileStyle({ 136 | id: 'v-scope-xxx', 137 | filename: 'example.vue', 138 | source: '.foo { color: red }', 139 | scoped: false, 140 | postcssPlugins: [ 141 | require('postcss').plugin('test-plugin', () => async (result: any) => 142 | result 143 | ) 144 | ] 145 | }) 146 | 147 | expect(result.errors).toHaveLength(1) 148 | }) 149 | 150 | test('async postcss plugin', async () => { 151 | const promise = compileStyleAsync({ 152 | id: 'v-scope-xxx', 153 | filename: 'example.vue', 154 | source: '.foo { color: red }', 155 | scoped: false, 156 | postcssPlugins: [ 157 | require('postcss').plugin('test-plugin', () => async (result: any) => 158 | result 159 | ) 160 | ] 161 | }) 162 | 163 | expect(promise instanceof Promise).toBe(true) 164 | 165 | const result = await promise 166 | expect(result.errors).toHaveLength(0) 167 | expect(result.code).toEqual(expect.stringContaining('color: red')) 168 | }) 169 | 170 | test('media query', () => { 171 | const result = compileStyle({ 172 | id: 'v-scope-xxx', 173 | scoped: true, 174 | filename: 'example.vue', 175 | source: ` 176 | @media print { 177 | .foo { 178 | color: #000; 179 | } 180 | }` 181 | }) 182 | 183 | expect(result.errors).toHaveLength(0) 184 | expect(result.code).toContain( 185 | '@media print {\n.foo[v-scope-xxx] {\n color: #000;\n}\n}' 186 | ) 187 | }) 188 | 189 | test('supports query', () => { 190 | const result = compileStyle({ 191 | id: 'v-scope-xxx', 192 | scoped: true, 193 | filename: 'example.vue', 194 | source: ` 195 | @supports ( color: #000 ) { 196 | .foo { 197 | color: #000; 198 | } 199 | }` 200 | }) 201 | 202 | expect(result.errors).toHaveLength(0) 203 | expect(result.code).toContain( 204 | '@supports ( color: #000 ) {\n.foo[v-scope-xxx] {\n color: #000;\n}\n}' 205 | ) 206 | }) 207 | -------------------------------------------------------------------------------- /lib/compileTemplate.ts: -------------------------------------------------------------------------------- 1 | import { 2 | VueTemplateCompiler, 3 | VueTemplateCompilerOptions, 4 | ErrorWithRange 5 | } from './types' 6 | 7 | import assetUrlsModule, { 8 | AssetURLOptions, 9 | TransformAssetUrlsOptions 10 | } from './templateCompilerModules/assetUrl' 11 | import srcsetModule from './templateCompilerModules/srcset' 12 | 13 | const consolidate = require('consolidate') 14 | const transpile = require('vue-template-es2015-compiler') 15 | 16 | export interface TemplateCompileOptions { 17 | source: string 18 | filename: string 19 | compiler: VueTemplateCompiler 20 | compilerOptions?: VueTemplateCompilerOptions 21 | transformAssetUrls?: AssetURLOptions | boolean 22 | transformAssetUrlsOptions?: TransformAssetUrlsOptions 23 | preprocessLang?: string 24 | preprocessOptions?: any 25 | transpileOptions?: any 26 | isProduction?: boolean 27 | isFunctional?: boolean 28 | optimizeSSR?: boolean 29 | prettify?: boolean 30 | } 31 | 32 | export interface TemplateCompileResult { 33 | ast: Object | undefined 34 | code: string 35 | source: string 36 | tips: (string | ErrorWithRange)[] 37 | errors: (string | ErrorWithRange)[] 38 | } 39 | 40 | export function compileTemplate( 41 | options: TemplateCompileOptions 42 | ): TemplateCompileResult { 43 | const { preprocessLang } = options 44 | const preprocessor = preprocessLang && consolidate[preprocessLang] 45 | if (preprocessor) { 46 | return actuallyCompile( 47 | Object.assign({}, options, { 48 | source: preprocess(options, preprocessor) 49 | }) 50 | ) 51 | } else if (preprocessLang) { 52 | return { 53 | ast: {}, 54 | code: `var render = function () {}\n` + `var staticRenderFns = []\n`, 55 | source: options.source, 56 | tips: [ 57 | `Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.` 58 | ], 59 | errors: [ 60 | `Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.` 61 | ] 62 | } 63 | } else { 64 | return actuallyCompile(options) 65 | } 66 | } 67 | 68 | function preprocess( 69 | options: TemplateCompileOptions, 70 | preprocessor: any 71 | ): string { 72 | const { source, filename, preprocessOptions } = options 73 | 74 | const finalPreprocessOptions = Object.assign( 75 | { 76 | filename 77 | }, 78 | preprocessOptions 79 | ) 80 | 81 | // Consolidate exposes a callback based API, but the callback is in fact 82 | // called synchronously for most templating engines. In our case, we have to 83 | // expose a synchronous API so that it is usable in Jest transforms (which 84 | // have to be sync because they are applied via Node.js require hooks) 85 | let res: any, err 86 | preprocessor.render( 87 | source, 88 | finalPreprocessOptions, 89 | (_err: Error | null, _res: string) => { 90 | if (_err) err = _err 91 | res = _res 92 | } 93 | ) 94 | 95 | if (err) throw err 96 | return res 97 | } 98 | 99 | function actuallyCompile( 100 | options: TemplateCompileOptions 101 | ): TemplateCompileResult { 102 | const { 103 | source, 104 | compiler, 105 | compilerOptions = {}, 106 | transpileOptions = {}, 107 | transformAssetUrls, 108 | transformAssetUrlsOptions, 109 | isProduction = process.env.NODE_ENV === 'production', 110 | isFunctional = false, 111 | optimizeSSR = false, 112 | prettify = true 113 | } = options 114 | 115 | const compile = 116 | optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile 117 | 118 | let finalCompilerOptions = compilerOptions 119 | if (transformAssetUrls) { 120 | const builtInModules = [ 121 | transformAssetUrls === true 122 | ? assetUrlsModule(undefined, transformAssetUrlsOptions) 123 | : assetUrlsModule(transformAssetUrls, transformAssetUrlsOptions), 124 | srcsetModule(transformAssetUrlsOptions) 125 | ] 126 | finalCompilerOptions = Object.assign({}, compilerOptions, { 127 | modules: [...builtInModules, ...(compilerOptions.modules || [])], 128 | filename: options.filename 129 | }) 130 | } 131 | 132 | const { ast, render, staticRenderFns, tips, errors } = compile( 133 | source, 134 | finalCompilerOptions 135 | ) 136 | 137 | if (errors && errors.length) { 138 | return { 139 | ast, 140 | code: `var render = function () {}\n` + `var staticRenderFns = []\n`, 141 | source, 142 | tips, 143 | errors 144 | } 145 | } else { 146 | const finalTranspileOptions = Object.assign({}, transpileOptions, { 147 | transforms: Object.assign({}, transpileOptions.transforms, { 148 | stripWithFunctional: isFunctional 149 | }) 150 | }) 151 | 152 | const toFunction = (code: string): string => { 153 | return `function (${isFunctional ? `_h,_vm` : ``}) {${code}}` 154 | } 155 | 156 | // transpile code with vue-template-es2015-compiler, which is a forked 157 | // version of Buble that applies ES2015 transforms + stripping `with` usage 158 | let code = 159 | transpile( 160 | `var __render__ = ${toFunction(render)}\n` + 161 | `var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`, 162 | finalTranspileOptions 163 | ) + `\n` 164 | 165 | // #23 we use __render__ to avoid `render` not being prefixed by the 166 | // transpiler when stripping with, but revert it back to `render` to 167 | // maintain backwards compat 168 | code = code.replace(/\s__(render|staticRenderFns)__\s/g, ' $1 ') 169 | 170 | if (!isProduction) { 171 | // mark with stripped (this enables Vue to use correct runtime proxy 172 | // detection) 173 | code += `render._withStripped = true` 174 | 175 | if (prettify) { 176 | try { 177 | code = require('prettier').format(code, { 178 | semi: false, 179 | parser: 'babel' 180 | }) 181 | } catch (e) { 182 | if (e.code === 'MODULE_NOT_FOUND') { 183 | tips.push( 184 | 'The `prettify` option is on, but the dependency `prettier` is not found.\n' + 185 | 'Please either turn off `prettify` or manually install `prettier`.' 186 | ) 187 | } 188 | tips.push( 189 | `Failed to prettify component ${options.filename} template source after compilation.` 190 | ) 191 | } 192 | } 193 | } 194 | 195 | return { 196 | ast, 197 | code, 198 | source, 199 | tips, 200 | errors 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /test/compileTemplate.spec.ts: -------------------------------------------------------------------------------- 1 | import { parse } from '../lib/parse' 2 | import { compileTemplate } from '../lib/compileTemplate' 3 | import * as compiler from 'vue-template-compiler' 4 | import Vue from 'vue' 5 | 6 | import { VueTemplateCompiler } from '../lib/types' 7 | 8 | afterEach(() => jest.resetAllMocks().resetModules()) 9 | 10 | function mockRender(code: string, options: any = {}) { 11 | eval( 12 | `${code}; options.render = render; options.staticRenderFns = staticRenderFns` 13 | ) 14 | const vm = new Vue(Object.assign({}, options)) 15 | vm.$mount() 16 | return (vm as any)._vnode 17 | } 18 | 19 | test('should work', () => { 20 | const source = `

{{ render }}

` 21 | 22 | const result = compileTemplate({ 23 | filename: 'example.vue', 24 | source, 25 | compiler: compiler as VueTemplateCompiler 26 | }) 27 | 28 | expect(result.errors.length).toBe(0) 29 | expect(result.source).toBe(source) 30 | // should expose render fns 31 | expect(result.code).toMatch(`var render = function`) 32 | expect(result.code).toMatch(`var staticRenderFns = []`) 33 | // should mark with stripped 34 | expect(result.code).toMatch(`render._withStripped = true`) 35 | // should prefix bindings 36 | expect(result.code).toMatch(`_vm.render`) 37 | expect(result.ast).not.toBeUndefined() 38 | }) 39 | 40 | test('preprocess pug', () => { 41 | const template = parse({ 42 | source: 43 | '\n', 49 | compiler: compiler as VueTemplateCompiler, 50 | filename: 'example.vue', 51 | needMap: true 52 | }).template as compiler.SFCBlock 53 | 54 | const result = compileTemplate({ 55 | filename: 'example.vue', 56 | source: template.content, 57 | preprocessLang: template.lang, 58 | compiler: compiler as VueTemplateCompiler 59 | }) 60 | 61 | expect(result.errors.length).toBe(0) 62 | }) 63 | 64 | /** 65 | * vuejs/component-compiler-utils#22 Support uri fragment in transformed require 66 | */ 67 | test('supports uri fragment in transformed require', () => { 68 | const source = '\ 69 | \ 70 | ' // 71 | const result = compileTemplate({ 72 | filename: 'svgparticle.html', 73 | source: source, 74 | transformAssetUrls: { 75 | use: 'href' 76 | }, 77 | compiler: compiler as VueTemplateCompiler 78 | }) 79 | expect(result.errors.length).toBe(0) 80 | expect(result.code).toMatch( 81 | /href: require\("@svg\/file.svg"\) \+ "#fragment"/ 82 | ) 83 | }) 84 | 85 | /** 86 | * vuejs/component-compiler-utils#22 Support uri fragment in transformed require 87 | */ 88 | test('when too short uri then empty require', () => { 89 | const source = '\ 90 | \ 91 | ' // 92 | const result = compileTemplate({ 93 | filename: 'svgparticle.html', 94 | source: source, 95 | transformAssetUrls: { 96 | use: 'href' 97 | }, 98 | compiler: compiler as VueTemplateCompiler 99 | }) 100 | expect(result.errors.length).toBe(0) 101 | expect(result.code).toMatch(/href: require\(""\)/) 102 | }) 103 | 104 | test('warn missing preprocessor', () => { 105 | const template = parse({ 106 | source: '\n', 107 | compiler: compiler as VueTemplateCompiler, 108 | filename: 'example.vue', 109 | needMap: true 110 | }).template as compiler.SFCBlock 111 | 112 | const result = compileTemplate({ 113 | filename: 'example.vue', 114 | source: template.content, 115 | preprocessLang: template.lang, 116 | compiler: compiler as VueTemplateCompiler 117 | }) 118 | 119 | expect(result.errors.length).toBe(1) 120 | }) 121 | 122 | test('transform assetUrls', () => { 123 | const source = ` 124 |
125 | 126 | 127 | 128 |
129 | ` 130 | const result = compileTemplate({ 131 | compiler: compiler as VueTemplateCompiler, 132 | filename: 'example.vue', 133 | source, 134 | transformAssetUrls: true 135 | }) 136 | expect(result.errors.length).toBe(0) 137 | 138 | jest.mock('./logo.png', () => 'a', { virtual: true }) 139 | jest.mock('fixtures/logo.png', () => 'b', { virtual: true }) 140 | 141 | const vnode = mockRender(result.code) 142 | expect(vnode.children[0].data.attrs.src).toBe('a') 143 | expect(vnode.children[2].data.attrs.src).toBe('b') 144 | expect(vnode.children[4].data.attrs.src).toBe('b') 145 | }) 146 | 147 | test('transform srcset', () => { 148 | // TODO: 149 | const source = ` 150 |
151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 171 |
172 | ` 173 | const result = compileTemplate({ 174 | compiler: compiler as VueTemplateCompiler, 175 | filename: 'example.vue', 176 | source, 177 | transformAssetUrls: true 178 | }) 179 | expect(result.errors.length).toBe(0) 180 | 181 | jest.mock('./logo.png', () => 'test-url', { virtual: true }) 182 | const vnode = mockRender(result.code) 183 | 184 | // img tag 185 | expect(vnode.children[0].data.attrs.src).toBe('test-url') 186 | // image tag (SVG) 187 | expect(vnode.children[2].children[0].data.attrs['xlink:href']).toBe( 188 | 'test-url' 189 | ) 190 | // use tag (SVG) 191 | expect(vnode.children[4].children[0].data.attrs['xlink:href']).toBe( 192 | 'test-url' 193 | ) 194 | 195 | // image tag with srcset 196 | expect(vnode.children[6].data.attrs.srcset).toBe('test-url') 197 | expect(vnode.children[8].data.attrs.srcset).toBe('test-url 2x') 198 | // image tag with multiline srcset 199 | expect(vnode.children[10].data.attrs.srcset).toBe('test-url, test-url 2x') 200 | expect(vnode.children[12].data.attrs.srcset).toBe('test-url 2x, test-url') 201 | expect(vnode.children[14].data.attrs.srcset).toBe('test-url 2x, test-url 3x') 202 | expect(vnode.children[16].data.attrs.srcset).toBe( 203 | 'test-url, test-url 2x, test-url 3x' 204 | ) 205 | expect(vnode.children[18].data.attrs.srcset).toBe('test-url 2x, test-url 3x') 206 | }) 207 | 208 | test('transform assetUrls and srcset with base option', () => { 209 | const source = ` 210 |
211 | 212 | 213 | 214 | 215 |
216 | ` 217 | const result = compileTemplate({ 218 | compiler: compiler as VueTemplateCompiler, 219 | filename: 'example.vue', 220 | source, 221 | transformAssetUrls: true, 222 | transformAssetUrlsOptions: { base: '/base/' } 223 | }) 224 | 225 | expect(result.errors.length).toBe(0) 226 | 227 | const vnode = mockRender(result.code) 228 | expect(vnode.children[0].data.attrs.src).toBe('/base/logo.png') 229 | expect(vnode.children[2].data.attrs.src).toBe('/base/fixtures/logo.png') 230 | expect(vnode.children[4].data.attrs.src).toBe('/base/fixtures/logo.png') 231 | expect(vnode.children[6].data.attrs.srcset).toBe( 232 | '/base/logo.png 2x, /base/logo.png 3x' 233 | ) 234 | }) 235 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [3.3.0](https://github.com/vuejs/component-compiler-utils/compare/v3.2.2...v3.3.0) (2021-10-26) 2 | 3 | ### New Feature 4 | 5 | * allow prettier v2 to be the optional dependency ([#89](https://github.com/vuejs/component-compiler-utils/pull/89)) ([cab504f](https://github.com/vuejs/component-compiler-utils/commit/26de735854a7288d704a606a054bff51f7d7307f)) 6 | 7 | 8 | ## [3.2.2](https://github.com/vuejs/component-compiler-utils/compare/v3.2.1...v3.2.2) (2021-06-15) 9 | 10 | 11 | ### Reverts 12 | 13 | * Revert "fix: patch postcss 7 and bundle it in the published npm package (#111)" ([a9e3afd](https://github.com/vuejs/component-compiler-utils/commit/a9e3afd052856edbcccc0baf8355eb6088e10eaf)), closes [#111](https://github.com/vuejs/component-compiler-utils/issues/111) 14 | 15 | ### Bug Fixes 16 | 17 | * Bump postcss version ([2af0c54](https://github.com/vuejs/component-compiler-utils/commit/2af0c546e15b18d0977477e7f45007c1614edd14)) 18 | 19 | 20 | ## [3.2.1](https://github.com/vuejs/component-compiler-utils/compare/v3.2.0...v3.2.1) (2021-06-09) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * patch postcss 7 and bundle it in the published npm package ([#111](https://github.com/vuejs/component-compiler-utils/issues/111)) ([cab504f](https://github.com/vuejs/component-compiler-utils/commit/cab504fb9d900cdef078358870e5ee20d0bc7ec2)) 26 | 27 | 28 | 29 | # [3.2.0](https://github.com/vuejs/component-compiler-utils/compare/v3.1.2...v3.2.0) (2020-07-22) 30 | 31 | 32 | ### Features 33 | 34 | * asset handling for support vite dev. ([#90](https://github.com/vuejs/component-compiler-utils/issues/90)) ([ad83bdf](https://github.com/vuejs/component-compiler-utils/commit/ad83bdfc7eb6f33fd46eb85071125406a85d0545)) 35 | 36 | 37 | 38 | ## [3.1.2](https://github.com/vuejs/component-compiler-utils/compare/v3.1.1...v3.1.2) (2020-04-08) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * add parse options to cache key ([#78](https://github.com/vuejs/component-compiler-utils/issues/78)) ([f7b1f87](https://github.com/vuejs/component-compiler-utils/commit/f7b1f876540a4a933627df323a1d5cbeab245b28)) 44 | * LRUCache v5 has to be invoked with new ([#83](https://github.com/vuejs/component-compiler-utils/issues/83)) ([b541fef](https://github.com/vuejs/component-compiler-utils/commit/b541feff8312f71f8966d6788490510331460023)), closes [#79](https://github.com/vuejs/component-compiler-utils/issues/79) 45 | 46 | ### Others 47 | 48 | * make prettier dependency optional ([aea1b79](https://github.com/vuejs/component-compiler-utils/commit/aea1b79)), closes [#84](https://github.com/vuejs/component-compiler-utils/issues/84) 49 | 50 | 51 | ## [3.1.1](https://github.com/vuejs/component-compiler-utils/compare/v3.1.0...v3.1.1) (2020-01-06) 52 | 53 | ### Bug Fixes 54 | 55 | * upgrade postcss-selector-parser to ^6.0.2 ([#76](https://github.com/vuejs/component-compiler-utils/pull/76)) ([e566a19](https://github.com/vuejs/component-compiler-utils/commit/e566a19)) 56 | 57 | # [3.1.0](https://github.com/vuejs/component-compiler-utils/compare/v3.0.2...v3.1.0) (2019-12-08) 58 | 59 | 60 | ### Features 61 | 62 | * include filename in `finalCompilerOptions` ([#74](https://github.com/vuejs/component-compiler-utils/issues/74)) ([3dda72d](https://github.com/vuejs/component-compiler-utils/commit/3dda72d)) 63 | * support AST for template compile ([#68](https://github.com/vuejs/component-compiler-utils/issues/68)) ([ed44d6f](https://github.com/vuejs/component-compiler-utils/commit/ed44d6f)) 64 | * support audio src in `transformAssetUrls` option by default ([#72](https://github.com/vuejs/component-compiler-utils/issues/72)) ([47f1341](https://github.com/vuejs/component-compiler-utils/commit/47f1341)) 65 | 66 | 67 | 68 | ## [3.0.2](https://github.com/vuejs/component-compiler-utils/compare/v3.0.1...v3.0.2) (2019-11-06) 69 | 70 | 71 | ### Bug Fixes 72 | 73 | * also include "lib" folder for type definitions ([dd42df1](https://github.com/vuejs/component-compiler-utils/commit/dd42df1)), closes [#73](https://github.com/vuejs/component-compiler-utils/issues/73) 74 | 75 | 76 | 77 | ## [3.0.1](https://github.com/vuejs/component-compiler-utils/compare/v3.0.0...v3.0.1) (2019-11-04) 78 | 79 | 80 | ### Bug Fixes 81 | 82 | * should not crash when prettier failed ([89e7900](https://github.com/vuejs/component-compiler-utils/commit/89e7900)) 83 | * unpin prettier version ([59a01bb](https://github.com/vuejs/component-compiler-utils/commit/59a01bb)) 84 | 85 | 86 | # [3.0.0](https://github.com/vuejs/component-compiler-utils/compare/v2.6.0...v3.0.0) (2019-04-11) 87 | 88 | 89 | ### Features 90 | 91 | * Replace node-sass to sass ([#56](https://github.com/vuejs/component-compiler-utils/issues/56)) ([3e4e3fc](https://github.com/vuejs/component-compiler-utils/commit/3e4e3fc)), closes [#50](https://github.com/vuejs/component-compiler-utils/issues/50) 92 | 93 | 94 | ### BREAKING CHANGES 95 | 96 | * Using `sass` instead of `node-sass` package. 97 | 98 | 99 | 100 | # [2.6.0](https://github.com/vuejs/component-compiler-utils/compare/v2.5.2...v2.6.0) (2019-02-21) 101 | 102 | 103 | ### Features 104 | 105 | * implement ::v-deep as a shadow piercing combinator ([#54](https://github.com/vuejs/component-compiler-utils/issues/54)) ([8b2c646](https://github.com/vuejs/component-compiler-utils/commit/8b2c646)) 106 | 107 | 108 | 109 | # [2.6.0](https://github.com/vuejs/component-compiler-utils/compare/v2.5.2...v2.6.0) (2019-02-21) 110 | 111 | 112 | ### Features 113 | 114 | * implement ::v-deep as a shadow piercing combinator ([#54](https://github.com/vuejs/component-compiler-utils/issues/54)) ([8b2c646](https://github.com/vuejs/component-compiler-utils/commit/8b2c646)) 115 | 116 | 117 | 118 | ## [2.5.2](https://github.com/vuejs/component-compiler-utils/compare/v2.5.0...v2.5.2) (2019-01-31) 119 | 120 | 121 | ### Bug Fixes 122 | 123 | * fix sourceMap path separator on Windows, default sourceRoot to "" ([#51](https://github.com/vuejs/component-compiler-utils/issues/51)) ([df32cd9](https://github.com/vuejs/component-compiler-utils/commit/df32cd9)), closes [#47](https://github.com/vuejs/component-compiler-utils/issues/47) 124 | * generate correct source-map when content is not padded ([#52](https://github.com/vuejs/component-compiler-utils/issues/52)) ([81be0ca](https://github.com/vuejs/component-compiler-utils/commit/81be0ca)) 125 | 126 | 127 | 128 | ## [2.5.1](https://github.com/vuejs/component-compiler-utils/compare/v2.5.0...v2.5.1) (2019-01-25) 129 | 130 | 131 | ### Bug Fixes 132 | 133 | * fix sourceMap path separator on Windows, default sourceRoot to "" ([#51](https://github.com/vuejs/component-compiler-utils/issues/51)) ([df32cd9](https://github.com/vuejs/component-compiler-utils/commit/df32cd9)), closes [#47](https://github.com/vuejs/component-compiler-utils/issues/47) 134 | 135 | 136 | 137 | # [2.5.0](https://github.com/vuejs/component-compiler-utils/compare/v2.4.0...v2.5.0) (2019-01-08) 138 | 139 | 140 | ### Features 141 | 142 | * add 'use' tag of SVG to 'transformAssetUrls' option as default ([#45](https://github.com/vuejs/component-compiler-utils/issues/45)) ([f4e3336](https://github.com/vuejs/component-compiler-utils/commit/f4e3336)) 143 | 144 | 145 | 146 | # [2.4.0](https://github.com/vuejs/component-compiler-utils/compare/v2.0.0...v2.4.0) (2019-01-02) 147 | 148 | 149 | ### Bug Fixes 150 | 151 | * do not insert newline if style is already minified ([2603ee2](https://github.com/vuejs/component-compiler-utils/commit/2603ee2)) 152 | * Forward preprocessor options to less ([#25](https://github.com/vuejs/component-compiler-utils/issues/25)) ([3b19c1e](https://github.com/vuejs/component-compiler-utils/commit/3b19c1e)), closes [#24](https://github.com/vuejs/component-compiler-utils/issues/24) 153 | * Move trim and scoped postcss plugins at the start of plugin list ([#36](https://github.com/vuejs/component-compiler-utils/issues/36)) ([0d52d86](https://github.com/vuejs/component-compiler-utils/commit/0d52d86)) 154 | * pin prettier version ([5f138a6](https://github.com/vuejs/component-compiler-utils/commit/5f138a6)) 155 | * remove space after selector when inserting scoped attribute ([5b299ed](https://github.com/vuejs/component-compiler-utils/commit/5b299ed)), closes [vue-loader/#1370](https://github.com/vuejs/component-compiler-utils/issues/1370) 156 | * should work with variable named render (close [#23](https://github.com/vuejs/component-compiler-utils/issues/23)) ([273827b](https://github.com/vuejs/component-compiler-utils/commit/273827b)) 157 | * support standalone pseudo element selectors ([#33](https://github.com/vuejs/component-compiler-utils/issues/33)) ([d6cfbbf](https://github.com/vuejs/component-compiler-utils/commit/d6cfbbf)) 158 | * Typings for SFCDescriptor and SFCCustomBlock ([#29](https://github.com/vuejs/component-compiler-utils/issues/29)) ([bb09115](https://github.com/vuejs/component-compiler-utils/commit/bb09115)) 159 | 160 | 161 | ### Features 162 | 163 | * **scoped-css:** support leading >>> or /deep/ in selectors ([1a3b5bb](https://github.com/vuejs/component-compiler-utils/commit/1a3b5bb)) 164 | * add `prettify ` option ([#42](https://github.com/vuejs/component-compiler-utils/issues/42)) ([db3655b](https://github.com/vuejs/component-compiler-utils/commit/db3655b)) 165 | * Support `stylus` as `