diff --git a/.circleci/config.yml b/.circleci/config.yml
index 42f34e2..03d74ca 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,7 +3,7 @@ jobs:
build:
working_directory: ~/project
docker:
- - image: alekzonder/puppeteer # base image: node/8-slim
+ - image: circleci/node:12-browsers
steps:
- checkout
- run:
diff --git a/.gitignore b/.gitignore
index 4d88ed9..fee8225 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@ node_modules
/dist
*.log
/test/output/
+.idea
+.vscode
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c533c02..095edb3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,158 @@
-# Change Log
+# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+### [4.2.4](https://github.com/vuejs/vue-component-compiler/compare/v4.2.3...v4.2.4) (2021-05-05)
+
+
+## [4.2.3](https://github.com/vuejs/vue-component-compiler/compare/v4.2.2...v4.2.3) (2020-05-22)
+
+
+### Bug Fixes
+
+* set correct clean-css import ([#103](https://github.com/vuejs/vue-component-compiler/issues/103)) ([b1aff3f](https://github.com/vuejs/vue-component-compiler/commit/b1aff3f))
+
+
+
+
+## [4.2.2](https://github.com/vuejs/vue-component-compiler/compare/v4.2.1...v4.2.2) (2020-05-22)
+
+
+### Bug Fixes
+
+* produce deterministic css class names for css modules ([#101](https://github.com/vuejs/vue-component-compiler/issues/101)) ([b8bfc58](https://github.com/vuejs/vue-component-compiler/commit/b8bfc58))
+
+
+
+
+## [4.2.1](https://github.com/vuejs/vue-component-compiler/compare/v4.2.0...v4.2.1) (2020-05-11)
+
+
+### Bug Fixes
+
+* add pure annotation for tree shaking support ([#96](https://github.com/vuejs/vue-component-compiler/issues/96)) ([7b4dceb](https://github.com/vuejs/vue-component-compiler/commit/7b4dceb))
+
+
+
+
+# [4.2.0](https://github.com/vuejs/vue-component-compiler/compare/v4.1.0...v4.2.0) (2019-11-21)
+
+
+### Features
+
+* use __vue_component__ identifier for normalized component and default export ([#91](https://github.com/vuejs/vue-component-compiler/issues/91)) ([fb1ab41](https://github.com/vuejs/vue-component-compiler/commit/fb1ab41))
+
+
+
+
+# [4.1.0](https://github.com/vuejs/vue-component-compiler/compare/v4.0.0...v4.1.0) (2019-10-26)
+
+
+### Features
+
+* inject styles to shadow DOM ([#89](https://github.com/vuejs/vue-component-compiler/issues/89)) ([714f08d](https://github.com/vuejs/vue-component-compiler/commit/714f08d))
+
+
+
+
+# [4.0.0](https://github.com/vuejs/vue-component-compiler/compare/v3.4.5...v4.0.0) (2019-04-11)
+
+
+### Bug Fixes
+
+* **#84:** Move postcss-modules plugins to last ([#86](https://github.com/vuejs/vue-component-compiler/issues/86)) ([d4ae5be](https://github.com/vuejs/vue-component-compiler/commit/d4ae5be)), closes [#84](https://github.com/vuejs/vue-component-compiler/issues/84)
+* normalize source map file path on windows ([#83](https://github.com/vuejs/vue-component-compiler/issues/83)) ([c4bcd40](https://github.com/vuejs/vue-component-compiler/commit/c4bcd40)), closes [vuejs/component-compiler-utils#51](https://github.com/vuejs/component-compiler-utils/issues/51)
+
+
+### Chores
+
+* Upgrade [@vue](https://github.com/vue)/component-compiler-utils ([12f4878](https://github.com/vuejs/vue-component-compiler/commit/12f4878))
+
+
+### Features
+
+* Add compileToDescriptorAsync and compileStyleAsync methods ([ccf7d84](https://github.com/vuejs/vue-component-compiler/commit/ccf7d84))
+* Allow disabling clean CSS ([9d43b80](https://github.com/vuejs/vue-component-compiler/commit/9d43b80))
+
+
+### BREAKING CHANGES
+
+* Update to @vue/component-compiler-utils@3.0.0 which
+uses `sass` instead of `node-sass`
+
+
+
+
+# [3.6.0](https://github.com/vuejs/vue-component-compiler/compare/v3.5.0...v3.6.0) (2018-08-28)
+
+
+### Features
+
+* Add compileToDescriptorAsync and compileStyleAsync methods ([ccf7d84](https://github.com/vuejs/vue-component-compiler/commit/ccf7d84))
+
+
+
+
+# [3.5.0](https://github.com/vuejs/vue-component-compiler/compare/v3.4.5...v3.5.0) (2018-08-28)
+
+
+### Features
+
+* Allow disabling clean CSS ([9d43b80](https://github.com/vuejs/vue-component-compiler/commit/9d43b80))
+
+
+
+
+## [3.4.5](https://github.com/vuejs/vue-component-compiler/compare/v3.4.4...v3.4.5) (2018-08-28)
+
+
+### Bug Fixes
+
+* Do not generate style injector code if component has no style ([78a2da3](https://github.com/vuejs/vue-component-compiler/commit/78a2da3))
+
+
+
+
+## [3.4.4](https://github.com/vuejs/vue-component-compiler/compare/v3.4.3...v3.4.4) (2018-07-31)
+
+
+
+
+## [3.4.3](https://github.com/vuejs/vue-component-compiler/compare/v3.4.2...v3.4.3) (2018-07-31)
+
+
+### Bug Fixes
+
+* Use basename for __file in production mode ([541a824](https://github.com/vuejs/vue-component-compiler/commit/541a824))
+
+
+
+
+## [3.4.2](https://github.com/vuejs/vue-component-compiler/compare/v3.4.1...v3.4.2) (2018-07-13)
+
+
+
+
+## [3.4.1](https://github.com/vuejs/vue-component-compiler/compare/v3.4.0...v3.4.1) (2018-06-27)
+
+
+### Bug Fixes
+
+* Add scope ID only if scoped style is present ([1a42be3](https://github.com/vuejs/vue-component-compiler/commit/1a42be3)), closes [#75](https://github.com/vuejs/vue-component-compiler/issues/75)
+
+
+
+
+# [3.4.0](https://github.com/vuejs/vue-component-compiler/compare/v3.3.3...v3.4.0) (2018-06-24)
+
+
+### Features
+
+* preprocessor `data` option to prepend shared styles ([#73](https://github.com/vuejs/vue-component-compiler/issues/73)) ([5a81749](https://github.com/vuejs/vue-component-compiler/commit/5a81749))
+* Script source map support in assemble ([#74](https://github.com/vuejs/vue-component-compiler/issues/74)) ([13cd119](https://github.com/vuejs/vue-component-compiler/commit/13cd119))
+
+
+
## [3.3.3](https://github.com/vuejs/vue-component-compiler/compare/v3.3.2...v3.3.3) (2018-05-25)
diff --git a/README.md b/README.md
index f2cc4f2..2936a39 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,12 @@
+> ⚠️ DEPRECATED: please use [@vue/compiler-sfc](https://github.com/vuejs/core/tree/main/packages/compiler-sfc) instead.
+>
+> Vue 2.7 also ships [2.x of `@vue/compiler-sfc`](https://github.com/vuejs/vue/blob/main/packages/compiler-sfc/src/index.ts) with largely compatible API with 3.x.
+
# @vue/component-compiler [](https://circleci.com/gh/vuejs/vue-component-compiler)
> High level utilities for compiling Vue single file components
-This package contains hid level utilities that you can use if you are writing a plugin / transform for a bundler or module system that compiles Vue single file components into JavaScript. It is used in [rollup-plugin-vue](https://github.com/vuejs/rollup-plugin-vue) version 3 and above.
+This package contains high level utilities that you can use if you are writing a plugin / transform for a bundler or module system that compiles Vue single file components into JavaScript. It is used in [rollup-plugin-vue](https://github.com/vuejs/rollup-plugin-vue) version 3 and above.
The API surface is intentionally minimal - the goal is to reuse as much as possible while being as flexible as possible.
diff --git a/package.json b/package.json
index dbfcea1..45d8540 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/component-compiler",
- "version": "3.3.3",
+ "version": "4.2.4",
"description": "bundler agnostic API for compiling Vue SFC",
"repository": {
"type": "git",
@@ -18,45 +18,55 @@
},
"main": "dist/index.js",
"typings": "dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
"scripts": {
"build": "tsc",
- "prepare": "yarn build",
+ "prepare": "rm -rf dist && npm run build",
+ "pretest": "npm run build",
"test": "jest",
- "pretest": "yarn build",
- "changelog": "conventional-changelog -p angular -r 2 -i CHANGELOG.md -s",
- "version": "npm run changelog && git add CHANGELOG.md"
+ "prerelease": "npm run test",
+ "release": "standard-version -a"
},
"homepage": "https://github.com/vuejs/vue-component-compiler#readme",
"devDependencies": {
"@types/clean-css": "^3.4.30",
- "@types/jest": "^22.2.3",
+ "@types/jest": "^25.2.3",
"@types/node": "^9.4.7",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.1",
"conventional-changelog": "^1.1.24",
"jest": "^22.4.2",
- "node-sass": "^4.9.0",
- "pug": "^2.0.3",
+ "pug": "^3.0.1",
"puppeteer": "^1.3.0",
"rollup": "^0.58.2",
"rollup-plugin-babel": "^3.0.4",
"rollup-plugin-commonjs": "^9.1.0",
"rollup-plugin-image": "^1.0.2",
"rollup-plugin-node-resolve": "^3.3.0",
+ "sass": "^1.18.0",
"ts-jest": "^22.4.2",
- "typescript": "^2.7.2",
+ "typescript": "^3.2.4",
"typescript-eslint-parser": "^15.0.0",
"vue": "^2.5.16",
"vue-template-compiler": "^2.5.16"
},
+ "optionalDependencies": {
+ "less": "^3.9.0",
+ "pug": "^3.0.1",
+ "sass": "^1.18.0",
+ "stylus": "^0.54.5"
+ },
"peerDependencies": {
"postcss": ">=6.0",
"vue-template-compiler": "*"
},
"dependencies": {
- "@vue/component-compiler-utils": "^1.2.1",
+ "@vue/component-compiler-utils": "^3.0.0",
"clean-css": "^4.1.11",
"hash-sum": "^1.0.2",
- "postcss-modules-sync": "^1.0.0"
+ "postcss-modules-sync": "^1.0.0",
+ "source-map": "0.6.*"
}
}
diff --git a/src/assembler.ts b/src/assembler.ts
index 5198d6e..00e62c6 100644
--- a/src/assembler.ts
+++ b/src/assembler.ts
@@ -1,4 +1,9 @@
+import { SourceMapGenerator } from 'source-map'
import { SFCCompiler, DescriptorCompileResult } from './compiler'
+import { merge } from './source-map'
+import * as path from 'path'
+
+// const merge = require('merge-source-map')
export interface AssembleSource {
filename: string
@@ -21,9 +26,11 @@ export interface AssembleResults {
}
export interface AssembleOptions {
+ isWebComponent?: boolean
normalizer?: string
styleInjector?: string
styleInjectorSSR?: string
+ styleInjectorShadow?: string
}
export function assemble(
@@ -35,17 +42,22 @@ export function assemble(
return assembleFromSource(compiler, options, {
filename,
scopeId: result.scopeId,
- script: result.script && { source: result.script.code },
+ script: result.script && {
+ source: result.script.code,
+ map: result.script.map
+ },
template: result.template && {
+ ...result.template,
source: result.template.code,
functional: result.template.functional
- },
+ } as any,
styles: result.styles.map(style => {
if (style.errors.length) {
console.error(style.errors)
}
return {
+ ...style,
source: style.code,
media: style.media,
scoped: style.scoped,
@@ -63,9 +75,11 @@ export function assembleFromSource(
): AssembleResults {
script = script || { source: 'export default {}' }
template = template || { source: '' }
+ let map = undefined
+ const mapGenerator = new SourceMapGenerator({ file: filename.replace(/\\/g, '/') })
const hasScopedStyle = styles.some(style => style.scoped === true)
- const hasStyle = styles.some(style => (style.source || style.module))
+ const hasStyle = styles.some(style => style.source || style.module)
const e = (any: any): string => JSON.stringify(any)
const createImport = (name: string, value: string) =>
value.startsWith('~')
@@ -76,115 +90,148 @@ export function assembleFromSource(
// language=JavaScript
const inlineCreateInjector = `function __vue_create_injector__() {
- const head = document.head || document.getElementsByTagName('head')[0]
- const styles = __vue_create_injector__.styles || (__vue_create_injector__.styles = {})
- const isOldIE =
- typeof navigator !== 'undefined' &&
- /msie [6-9]\\\\b/.test(navigator.userAgent.toLowerCase())
-
- return function addStyle(id, css) {
- if (document.querySelector('style[data-vue-ssr-id~="' + id + '"]')) return // SSR styles are present.
-
- const group = isOldIE ? css.media || 'default' : id
- const style = styles[group] || (styles[group] = { ids: [], parts: [], element: undefined })
+ const styles = __vue_create_injector__.styles || (__vue_create_injector__.styles = {})
+ const isOldIE =
+ typeof navigator !== 'undefined' &&
+ /msie [6-9]\\\\b/.test(navigator.userAgent.toLowerCase())
+
+ return function addStyle(id, css) {
+ if (document.querySelector('style[data-vue-ssr-id~="' + id + '"]')) return // SSR styles are present.
+
+ const group = isOldIE ? css.media || 'default' : id
+ const style = styles[group] || (styles[group] = { ids: [], parts: [], element: undefined })
+
+ if (!style.ids.includes(id)) {
+ let code = css.source
+ let index = style.ids.length
+
+ style.ids.push(id)
+
+ if (${e(compiler.template.isProduction)} && css.map) {
+ // https://developer.chrome.com/devtools/docs/javascript-debugging
+ // this makes source maps inside style tags work properly in Chrome
+ code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */'
+ // http://stackoverflow.com/a/26603875
+ code +=
+ '\\n/*# sourceMappingURL=data:application/json;base64,' +
+ btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
+ ' */'
+ }
- if (!style.ids.includes(id)) {
- let code = css.source
- let index = style.ids.length
+ if (isOldIE) {
+ style.element = style.element || document.querySelector('style[data-group=' + group + ']')
+ }
- style.ids.push(id)
+ if (!style.element) {
+ const head = document.head || document.getElementsByTagName('head')[0]
+ const el = style.element = document.createElement('style')
+ el.type = 'text/css'
- if (${e(compiler.template.isProduction)} && css.map) {
- // https://developer.chrome.com/devtools/docs/javascript-debugging
- // this makes source maps inside style tags work properly in Chrome
- code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */'
- // http://stackoverflow.com/a/26603875
- code +=
- '\\n/*# sourceMappingURL=data:application/json;base64,' +
- btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
- ' */'
- }
-
- if (isOldIE) {
- style.element = style.element || document.querySelector('style[data-group=' + group + ']')
- }
+ if (css.media) el.setAttribute('media', css.media)
+ if (isOldIE) {
+ el.setAttribute('data-group', group)
+ el.setAttribute('data-next-index', '0')
+ }
- if (!style.element) {
- const el = style.element = document.createElement('style')
- el.type = 'text/css'
+ head.appendChild(el)
+ }
- if (css.media) el.setAttribute('media', css.media)
if (isOldIE) {
- el.setAttribute('data-group', group)
- el.setAttribute('data-next-index', '0')
+ index = parseInt(style.element.getAttribute('data-next-index'))
+ style.element.setAttribute('data-next-index', index + 1)
}
- head.appendChild(el)
- }
-
- if (isOldIE) {
- index = parseInt(style.element.getAttribute('data-next-index'))
- style.element.setAttribute('data-next-index', index + 1)
- }
-
- if (style.element.styleSheet) {
- style.parts.push(code)
- style.element.styleSheet.cssText = style.parts
- .filter(Boolean)
- .join('\\n')
- } else {
- const textNode = document.createTextNode(code)
- const nodes = style.element.childNodes
- if (nodes[index]) style.element.removeChild(nodes[index])
- if (nodes.length) style.element.insertBefore(textNode, nodes[index])
- else style.element.appendChild(textNode)
+ if (style.element.styleSheet) {
+ style.parts.push(code)
+ style.element.styleSheet.cssText = style.parts
+ .filter(Boolean)
+ .join('\\n')
+ } else {
+ const textNode = document.createTextNode(code)
+ const nodes = style.element.childNodes
+ if (nodes[index]) style.element.removeChild(nodes[index])
+ if (nodes.length) style.element.insertBefore(textNode, nodes[index])
+ else style.element.appendChild(textNode)
+ }
}
}
- }
-}`
+ }`
const createInjector = options.styleInjector
? createImport('__vue_create_injector__', options.styleInjector)
: inlineCreateInjector
// language=JavaScript
const inlineCreateInjectorSSR = `function __vue_create_injector_ssr__(context) {
- if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
- context = __VUE_SSR_CONTEXT__
- }
+ if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
+ context = __VUE_SSR_CONTEXT__
+ }
- if (!context) return function () {}
+ if (!context) return function () {}
- if (!context.hasOwnProperty('styles')) {
- Object.defineProperty(context, 'styles', {
- enumerable: true,
- get: () => context._styles
- })
- context._renderStyles = renderStyles
- }
-
- function renderStyles(styles) {
- let css = ''
- for (const {ids, media, parts} of styles) {
- css +=
- ''
+ if (!context.hasOwnProperty('styles')) {
+ Object.defineProperty(context, 'styles', {
+ enumerable: true,
+ get: () => context._styles
+ })
+ context._renderStyles = renderStyles
}
- return css
- }
+ function renderStyles(styles) {
+ let css = ''
+ for (const {ids, media, parts} of styles) {
+ css +=
+ ''
+ }
+
+ return css
+ }
- return function addStyle(id, css) {
- const group = ${e(
- compiler.template.isProduction
- )} ? css.media || 'default' : id
- const style = context._styles[group] || (context._styles[group] = { ids: [], parts: [] })
+ return function addStyle(id, css) {
+ const group = ${e(
+ compiler.template.isProduction
+ )} ? css.media || 'default' : id
+ const style = context._styles[group] || (context._styles[group] = { ids: [], parts: [] })
+
+ if (!style.ids.includes(id)) {
+ style.media = css.media
+ style.ids.push(id)
+ let code = css.source
+ if (${e(!compiler.template.isProduction)} && css.map) {
+ // https://developer.chrome.com/devtools/docs/javascript-debugging
+ // this makes source maps inside style tags work properly in Chrome
+ code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */'
+ // http://stackoverflow.com/a/26603875
+ code +=
+ '\\n/*# sourceMappingURL=data:application/json;base64,' +
+ btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
+ ' */'
+ }
+ style.parts.push(code)
+ }
+ }
+ }`
+ const createInjectorSSR = options.styleInjectorSSR
+ ? createImport('__vue_create_injector_ssr__', options.styleInjectorSSR)
+ : inlineCreateInjectorSSR
- if (!style.ids.includes(id)) {
- style.media = css.media
- style.ids.push(id)
+ const inlineCreateInjectorShadow = `function __vue_create_injector_shadow__(__, shadowRoot) {
+ function createStyleElement(shadowRoot) {
+ var styleElement = document.createElement('style')
+ styleElement.type = 'text/css'
+ shadowRoot.appendChild(styleElement)
+
+ return styleElement
+ }
+
+ return function addStyle(id, css) {
+ const styleElement = createStyleElement(shadowRoot)
+ if (css.media) styleElement.setAttribute('media', css.media)
+
let code = css.source
- if (${e(!compiler.template.isProduction)} && css.map) {
+
+ if (${e(compiler.template.isProduction)} && css.map) {
// https://developer.chrome.com/devtools/docs/javascript-debugging
// this makes source maps inside style tags work properly in Chrome
code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */'
@@ -194,161 +241,241 @@ export function assembleFromSource(
btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
' */'
}
- style.parts.push(code)
+
+ if ('styleSheet' in styleElement) {
+ styleElement.styleSheet.cssText = code
+ } else {
+ while (styleElement.firstChild) {
+ styleElement.removeChild(styleElement.firstChild)
+ }
+ styleElement.appendChild(document.createTextNode(code))
+ }
}
- }
-}`
- const createInjectorSSR = options.styleInjectorSSR
- ? createImport('__vue_create_injector_ssr__', options.styleInjectorSSR)
- : inlineCreateInjectorSSR
+ }`
+
+ const createInjectorShadow = options.styleInjectorShadow
+ ? createImport('__vue_create_injector_shadow__', options.styleInjectorShadow)
+ : inlineCreateInjectorShadow
// language=JavaScript
const inlineNormalizeComponent = `function __vue_normalize__(
- template, style, script,
- scope, functional, moduleIdentifier,
- createInjector, createInjectorSSR
-) {
- const component = (typeof script === 'function' ? script.options : script) || {}
-
- if (${e(!compiler.template.isProduction)}) {
- component.__file = ${e(filename)}
- }
+ template, style, script,
+ scope, functional, moduleIdentifier, shadowMode,
+ createInjector, createInjectorSSR, createInjectorShadow
+ ) {
+ const component = (typeof script === 'function' ? script.options : script) || {}
- if (!component.render) {
- component.render = template.render
- component.staticRenderFns = template.staticRenderFns
- component._compiled = true
+ // For security concerns, we use only base name in production mode.
+ component.__file = ${compiler.template.isProduction ? e(path.basename(filename)) : e(filename)}
- if (functional) component.functional = true
- }
+ if (!component.render) {
+ component.render = template.render
+ component.staticRenderFns = template.staticRenderFns
+ component._compiled = true
- component._scopeId = scope
-
- if (${e(hasStyle)}) {
- let hook
- if (${e(compiler.template.optimizeSSR)}) {
- // In SSR.
- hook = function(context) {
- // 2.3 injection
- context =
- context || // cached call
- (this.$vnode && this.$vnode.ssrContext) || // stateful
- (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
- // 2.2 with runInNewContext: true
- if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
- context = __VUE_SSR_CONTEXT__
- }
- // inject component styles
- if (style) {
- style.call(this, createInjectorSSR(context))
- }
- // register component module identifier for async chunk inference
- if (context && context._registeredComponents) {
- context._registeredComponents.add(moduleIdentifier)
+ if (functional) component.functional = true
+ }
+
+ component._scopeId = scope
+
+ if (${e(hasStyle)}) {
+ let hook
+ if (${e(compiler.template.optimizeSSR)}) {
+ // In SSR.
+ hook = function(context) {
+ // 2.3 injection
+ context =
+ context || // cached call
+ (this.$vnode && this.$vnode.ssrContext) || // stateful
+ (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
+ // 2.2 with runInNewContext: true
+ if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
+ context = __VUE_SSR_CONTEXT__
+ }
+ // inject component styles
+ if (style) {
+ style.call(this, createInjectorSSR(context))
+ }
+ // register component module identifier for async chunk inference
+ if (context && context._registeredComponents) {
+ context._registeredComponents.add(moduleIdentifier)
+ }
}
+ // used by ssr in case component is cached and beforeCreate
+ // never gets called
+ component._ssrRegister = hook
}
- // used by ssr in case component is cached and beforeCreate
- // never gets called
- component._ssrRegister = hook
- }
- else if (style) {
- hook = function(context) {
- style.call(this, createInjector(context))
+ else if (style) {
+ hook = shadowMode
+ ? function(context) {
+ style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot))
+ }
+ : function(context) {
+ style.call(this, createInjector(context))
+ }
}
- }
- if (hook !== undefined) {
- if (component.functional) {
- // register for functional component in vue file
- const originalRender = component.render
- component.render = function renderWithStyleInjection(h, context) {
- hook.call(context)
- return originalRender(h, context)
+ if (hook !== undefined) {
+ if (component.functional) {
+ // register for functional component in vue file
+ const originalRender = component.render
+ component.render = function renderWithStyleInjection(h, context) {
+ hook.call(context)
+ return originalRender(h, context)
+ }
+ } else {
+ // inject component registration as beforeCreate hook
+ const existing = component.beforeCreate
+ component.beforeCreate = existing ? [].concat(existing, hook) : [hook]
}
- } else {
- // inject component registration as beforeCreate hook
- const existing = component.beforeCreate
- component.beforeCreate = existing ? [].concat(existing, hook) : [hook]
}
}
- }
- return component
-}`
+ return component
+ }`
const normalizeComponent = options.normalizer
? createImport('__vue_normalize__', options.normalizer)
: inlineNormalizeComponent
+ const DEFAULT_EXPORT = 'const __vue_script__ ='
// language=JavaScript
- const code = `
-/* script */
-${script.source.replace(/export default/, 'const __vue_script__ =')}
-/* template */
-${template.source
- .replace('var render =', 'var __vue_render__ =')
- .replace('var staticRenderFns =', 'var __vue_staticRenderFns__ =')
- .replace('render._withStripped =', '__vue_render__._withStripped =')}
-const __vue_template__ = typeof __vue_render__ !== 'undefined'
- ? { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }
- : {}
-/* style */
-const __vue_inject_styles__ = ${hasStyle} ? function (inject) {
- if (!inject) return
- ${styles.map((style, index) => {
- const source = IDENTIFIER.test(style.source)
- ? style.source
- : e(style.source)
- const map = !compiler.template.isProduction
- ? typeof style.map === 'string' && IDENTIFIER.test(style.map)
- ? style.map
- : o(style.map)
- : undefined
- const tokens =
- typeof style.module === 'string' && IDENTIFIER.test(style.module)
- ? style.module
- : o(style.module)
-
- return (
- (source
- ? `inject("${scopeId +
- '_' +
- index}", { source: ${source}, map: ${map}, media: ${e(
- style.media
- )} })\n`
- : '') +
- (style.moduleName
- ? `Object.defineProperty(this, "${
- style.moduleName
- }", { value: ${tokens} })` + '\n'
- : '')
- )
- })}
-} : undefined
-/* scoped */
-const __vue_scope_id__ = ${e(hasScopedStyle)} ? "${scopeId}" : undefined
-/* module identifier */
-const __vue_module_identifier__ = ${e(
- compiler.template.optimizeSSR
- )} ? "${scopeId}" : undefined
-/* functional template */
-const __vue_is_functional_template__ = ${e(template.functional)}
-/* component normalizer */
-${normalizeComponent}
-/* style inject */
-${!compiler.template.optimizeSSR ? createInjector : ''}
-/* style inject SSR */
-${compiler.template.optimizeSSR ? createInjectorSSR : ''}
-
-export default __vue_normalize__(
- __vue_template__,
- __vue_inject_styles__,
- typeof __vue_script__ === 'undefined' ? {} : __vue_script__,
- __vue_scope_id__,
- __vue_is_functional_template__,
- __vue_module_identifier__,
- typeof __vue_create_injector__ !== 'undefined' ? __vue_create_injector__ : function () {},
- typeof __vue_create_injector_ssr__ !== 'undefined' ? __vue_create_injector_ssr__ : function () {}
-)`
-
- return { code }
+ let code =
+ `/* script */\n${script.source.replace(
+ /export\s+default/,
+ DEFAULT_EXPORT
+ )}` +
+ `\n/* template */\n${template.source
+ .replace('var render =', 'var __vue_render__ =')
+ .replace('var staticRenderFns =', 'var __vue_staticRenderFns__ =')
+ .replace('render._withStripped =', '__vue_render__._withStripped =')}
+ /* style */
+ const __vue_inject_styles__ = ${hasStyle ? `function (inject) {
+ if (!inject) return
+ ${styles.map((style, index) => {
+ const source = IDENTIFIER.test(style.source)
+ ? style.source
+ : e(style.source)
+ const map = !compiler.template.isProduction
+ ? typeof style.map === 'string' && IDENTIFIER.test(style.map)
+ ? style.map
+ : o(style.map)
+ : undefined
+ const tokens =
+ typeof style.module === 'string' && IDENTIFIER.test(style.module)
+ ? style.module
+ : o(style.module)
+
+ return (
+ (source
+ ? `inject("${scopeId +
+ '_' +
+ index}", { source: ${source}, map: ${map}, media: ${e(
+ style.media
+ )} })\n`
+ : '') +
+ (style.moduleName
+ ? `Object.defineProperty(this, "${
+ style.moduleName
+ }", { value: ${tokens} })` + '\n'
+ : '')
+ )
+ })}
+ }` : 'undefined'}
+ /* scoped */
+ const __vue_scope_id__ = ${hasScopedStyle ? e(scopeId) : 'undefined'}
+ /* module identifier */
+ const __vue_module_identifier__ = ${
+ compiler.template.optimizeSSR ? e(scopeId) : 'undefined'
+ }
+ /* functional template */
+ const __vue_is_functional_template__ = ${e(template.functional)}
+ /* component normalizer */
+ ${normalizeComponent}
+ /* style inject */
+ ${hasStyle && !compiler.template.optimizeSSR && !options.isWebComponent ? createInjector : ''}
+ /* style inject SSR */
+ ${hasStyle && compiler.template.optimizeSSR ? createInjectorSSR : ''}
+ /* style inject shadow dom */
+ ${hasStyle && options.isWebComponent ? createInjectorShadow : ''}
+
+ `
+
+ // generate source map.
+ {
+ const input = script.source.split('\n')
+
+ input.forEach((sourceLine, index) => {
+ if (!sourceLine) return
+ const matches = /export\s+default/.exec(sourceLine)
+ if (matches) {
+ const pos = sourceLine.indexOf(matches[0])
+ if (pos > 0) {
+ mapGenerator.addMapping({
+ source: filename,
+ original: { line: index + 1, column: 0 },
+ generated: { line: index + 2, column: 0 }
+ })
+ }
+
+ mapGenerator.addMapping({
+ source: filename,
+ original: { line: index + 1, column: pos },
+ generated: { line: index + 2, column: pos }
+ })
+
+ if (sourceLine.substr(pos + matches[0].length)) {
+ mapGenerator.addMapping({
+ source: filename,
+ original: { line: index + 1, column: pos + matches[0].length },
+ generated: { line: index + 2, column: pos + DEFAULT_EXPORT.length }
+ })
+ }
+ } else {
+ mapGenerator.addMapping({
+ source: filename,
+ original: { line: index + 1, column: 0 },
+ generated: { line: index + 2, column: 0 }
+ })
+ }
+ })
+ }
+
+ code += `
+ const __vue_component__ = /*#__PURE__*/__vue_normalize__(
+ ${
+ code.indexOf('__vue_render__') > -1
+ ? '{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }'
+ : '{}'
+ },
+ __vue_inject_styles__,
+ ${code.indexOf('__vue_script__') > -1 ? '__vue_script__' : '{}'},
+ __vue_scope_id__,
+ __vue_is_functional_template__,
+ __vue_module_identifier__,
+ ${options.isWebComponent ? 'true' : 'false'},
+ ${
+ code.indexOf('__vue_create_injector__') > -1
+ ? '__vue_create_injector__'
+ : 'undefined'
+ },
+ ${
+ code.indexOf('__vue_create_injector_ssr__') > -1
+ ? '__vue_create_injector_ssr__'
+ : 'undefined'
+ },
+ ${
+ code.indexOf('__vue_create_injector_shadow__') > -1
+ ? '__vue_create_injector_shadow__'
+ : 'undefined'
+ }
+ )\n
+ export default __vue_component__`
+
+ if (script.map) {
+ map = merge(script.map, JSON.parse(mapGenerator.toString()))
+ } else {
+ map = JSON.parse(mapGenerator.toString())
+ }
+
+ return { code, map }
}
diff --git a/src/compiler.ts b/src/compiler.ts
index 0749263..ff14650 100644
--- a/src/compiler.ts
+++ b/src/compiler.ts
@@ -2,9 +2,11 @@ import {
parse,
compileTemplate,
compileStyle,
+ compileStyleAsync,
SFCBlock,
StyleCompileResults,
- TemplateCompileResult
+ TemplateCompileResult,
+ StyleCompileOptions
} from '@vue/component-compiler-utils'
import {
VueTemplateCompiler,
@@ -14,10 +16,12 @@ import { AssetURLOptions } from '@vue/component-compiler-utils/dist/templateComp
import postcssModules from 'postcss-modules-sync'
import postcssClean from './postcss-clean'
-import hash = require('hash-sum')
import * as fs from 'fs'
import * as path from 'path'
+const hash = require('hash-sum')
+const templateCompiler = require('vue-template-compiler')
+
export interface TemplateOptions {
compiler: VueTemplateCompiler
compilerOptions: VueTemplateCompilerOptions
@@ -85,7 +89,8 @@ export class SFCCompiler {
const descriptor = parse({
source,
filename,
- needMap: true
+ needMap: true,
+ compiler: templateCompiler
})
const scopeId =
@@ -94,20 +99,69 @@ export class SFCCompiler {
? hash(path.basename(filename) + source)
: hash(filename + source))
- const template =
- descriptor.template && this.compileTemplate(filename, descriptor.template)
+ const template = descriptor.template
+ ? this.compileTemplate(filename, descriptor.template)
+ : undefined
const styles = descriptor.styles.map(style =>
this.compileStyle(filename, scopeId, style)
)
const { script: rawScript, customBlocks } = descriptor
- const script = rawScript && {
- code: rawScript.src
- ? this.read(rawScript.src, filename)
- : rawScript.content,
- map: rawScript.map
+ const script = rawScript
+ ? {
+ code: rawScript.src
+ ? this.read(rawScript.src, filename)
+ : rawScript.content,
+ map: rawScript.map
+ }
+ : undefined
+
+ return {
+ scopeId,
+ template,
+ styles,
+ script,
+ customBlocks
}
+ }
+
+ async compileToDescriptorAsync(
+ filename: string,
+ source: string
+ ): Promise {
+ const descriptor = parse({
+ source,
+ filename,
+ needMap: true,
+ compiler: templateCompiler
+ })
+
+ const scopeId =
+ 'data-v-' +
+ (this.template.isProduction
+ ? hash(path.basename(filename) + source)
+ : hash(filename + source))
+
+ const template = descriptor.template
+ ? this.compileTemplate(filename, descriptor.template)
+ : undefined
+
+ const styles = await Promise.all(
+ descriptor.styles.map(style =>
+ this.compileStyleAsync(filename, scopeId, style)
+ )
+ )
+
+ const { script: rawScript, customBlocks } = descriptor
+ const script = rawScript
+ ? {
+ code: rawScript.src
+ ? this.read(rawScript.src, filename)
+ : rawScript.content,
+ map: rawScript.map
+ }
+ : undefined
return {
scopeId,
@@ -149,49 +203,78 @@ export class SFCCompiler {
scopeId: string,
style: SFCBlock
): StyleCompileResult {
- let tokens = undefined
+ const { options, prepare } = this.doCompileStyle(filename, scopeId, style)
+
+ return prepare(compileStyle(options))
+ }
+
+ async compileStyleAsync(
+ filename: string,
+ scopeId: string,
+ style: SFCBlock
+ ): Promise {
+ const { options, prepare } = this.doCompileStyle(filename, scopeId, style)
+
+ return prepare(await compileStyleAsync(options))
+ }
+
+ private doCompileStyle(
+ filename: string,
+ scopeId: string,
+ style: SFCBlock
+ ): { options: StyleCompileOptions, prepare: (result: StyleCompileResults) => StyleCompileResult } {
+ let tokens: any = undefined
+
const needsCSSModules =
style.module === true || typeof style.module === 'string'
- const postcssPlugins = [
- needsCSSModules
- ? postcssModules({
- generateScopedName: '[path][local]-[hash:base64:4]',
- ...this.style.postcssModulesOptions,
- getJSON: (t: any) => {
- tokens = t
- }
- })
- : undefined,
- this.template.isProduction
- ? postcssClean(this.style.postcssCleanOptions)
- : undefined
- ]
- .concat(this.style.postcssPlugins)
+ const needsCleanCSS =
+ this.template.isProduction && !(this.style.postcssCleanOptions && this.style.postcssCleanOptions.disabled)
+ const postcssPlugins = (this.style.postcssPlugins || [])
+ .slice()
+ .concat([
+ needsCSSModules
+ ? postcssModules({
+ generateScopedName: '[path][local]-[hash:base64:4]',
+ ...this.style.postcssModulesOptions,
+ getJSON: (t: any) => {
+ tokens = t
+ }
+ })
+ : undefined,
+ needsCleanCSS
+ ? postcssClean(this.style.postcssCleanOptions)
+ : undefined,
+ ])
.filter(Boolean)
- const result = compileStyle({
- source: style.src ? this.read(style.src, filename) : style.content,
- filename,
- id: scopeId,
- map: style.map,
- scoped: style.scoped || false,
- postcssPlugins,
- postcssOptions: this.style.postcssOptions,
- preprocessLang: style.lang,
- preprocessOptions:
- (style.lang &&
- this.style.preprocessOptions &&
- this.style.preprocessOptions[style.lang]) ||
- {},
- trim: this.style.trim
- })
+ const preprocessOptions =
+ (style.lang &&
+ this.style.preprocessOptions &&
+ this.style.preprocessOptions[style.lang]) ||
+ {}
+ const source = style.src ? this.read(style.src, filename) : style.content
return {
- media: style.attrs.media,
- scoped: style.scoped,
- moduleName: style.module === true ? '$style' : style.module,
- module: tokens,
- ...result,
- code: result.code
+ options: {
+ source: preprocessOptions.data ? `${preprocessOptions.data}\n${source}` : source,
+ filename,
+ id: scopeId,
+ map: style.map,
+ scoped: style.scoped || false,
+ postcssPlugins,
+ postcssOptions: this.style.postcssOptions,
+ preprocessLang: style.lang,
+ preprocessOptions,
+ trim: this.style.trim
+ },
+
+ prepare: result => ({
+ media: typeof style.attrs.media === 'string' ? style.attrs.media : undefined,
+ scoped: style.scoped,
+ moduleName: style.module === true ? '$style' : style.module,
+ module: tokens,
+ ...result,
+ code: result.code
+ })
}
}
diff --git a/src/postcss-clean.ts b/src/postcss-clean.ts
index 13e9304..ac1ff60 100644
--- a/src/postcss-clean.ts
+++ b/src/postcss-clean.ts
@@ -1,7 +1,9 @@
import * as postcss from 'postcss'
-import CleanCSS = require('clean-css')
+// ESM import of clean-css breaks test/runtime check this fix for reference:
+// https://github.com/vuejs/vue-component-compiler/pull/103#issuecomment-632676899
+const CleanCSS = require('clean-css')
-export default postcss.plugin('clean', options => {
+export default postcss.plugin('clean', (options: any) => {
const clean = new CleanCSS({ compatibility: 'ie9', ...options })
return (css: any, res: any) => {
diff --git a/src/source-map.ts b/src/source-map.ts
new file mode 100644
index 0000000..eb20b1e
--- /dev/null
+++ b/src/source-map.ts
@@ -0,0 +1,58 @@
+import {
+ SourceMapConsumer,
+ SourceMapGenerator,
+ RawSourceMap,
+ MappingItem
+} from 'source-map'
+
+export function merge(
+ oldMap: RawSourceMap,
+ newMap: RawSourceMap
+): RawSourceMap {
+ if (!oldMap) return newMap
+ if (!newMap) return oldMap
+
+ const oldMapConsumer: SourceMapConsumer = new SourceMapConsumer(oldMap) as any
+ const newMapConsumer: SourceMapConsumer = new SourceMapConsumer(newMap) as any
+ const mergedMapGenerator = new SourceMapGenerator()
+
+ // iterate on new map and overwrite original position of new map with one of old map
+ newMapConsumer.eachMapping((mapping: MappingItem) => {
+ // pass when `originalLine` is null.
+ // It occurs in case that the node does not have origin in original code.
+ if (mapping.originalLine == null) return
+
+ const origPosInOldMap = oldMapConsumer.originalPositionFor({
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ })
+
+ if (origPosInOldMap.source == null) return
+
+ mergedMapGenerator.addMapping({
+ original: {
+ line: origPosInOldMap.line,
+ column: origPosInOldMap.column
+ },
+ generated: {
+ line: mapping.generatedLine,
+ column: mapping.generatedColumn
+ },
+ source: origPosInOldMap.source,
+ name: origPosInOldMap.name
+ } as any)
+ })
+
+ const maps = [oldMap, newMap]
+ const consumers = [oldMapConsumer, newMapConsumer]
+ consumers.forEach((consumer, index) => {
+ maps[index].sources.forEach(sourceFile => {
+ const sourceContent = consumer.sourceContentFor(sourceFile, true)
+ if (sourceContent !== null) {
+ mergedMapGenerator.setSourceContent(sourceFile, sourceContent)
+ }
+ })
+ })
+
+ return JSON.parse(mergedMapGenerator.toString())
+}
diff --git a/test/__snapshots__/compile.spec.ts.snap b/test/__snapshots__/compile.spec.ts.snap
new file mode 100644
index 0000000..2bda97a
--- /dev/null
+++ b/test/__snapshots__/compile.spec.ts.snap
@@ -0,0 +1,231 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should compile to descriptor (async) 1`] = `
+Object {
+ "customBlocks": Array [],
+ "scopeId": "data-v-01ebe570",
+ "script": Object {
+ "code": "//
+//
+//
+//
+//
+
+export default {
+ data () {
+ return { name: 'John Doe' }
+ }
+}
+",
+ "map": Object {
+ "file": "foo.vue",
+ "mappings": ";;;;;;AAMA;AACA;AACA;AACA;AACA",
+ "names": Array [],
+ "sourceRoot": "",
+ "sources": Array [
+ "foo.vue",
+ ],
+ "sourcesContent": Array [
+ "
+
+ Hello {{ name }}!
+
+
+
+
+
+",
+ ],
+ "version": 3,
+ },
+ },
+ "styles": Array [
+ Object {
+ "code": "
+.title {
+ color: red;
+}
+",
+ "errors": Array [],
+ "map": Object {
+ "file": "foo.vue",
+ "mappings": ";AAcA;EACA,UAAA;AACA",
+ "names": Array [],
+ "sources": Array [
+ "foo.vue",
+ ],
+ "sourcesContent": Array [
+ "
+
+ Hello {{ name }}!
+
+
+
+
+
+",
+ ],
+ "version": 3,
+ },
+ "media": undefined,
+ "module": undefined,
+ "moduleName": undefined,
+ "scoped": undefined,
+ },
+ ],
+ "template": Object {
+ "code": "var render = function() {
+ var _vm = this
+ var _h = _vm.$createElement
+ var _c = _vm._self._c || _h
+ return _c(\\"h1\\", { staticClass: \\"title\\", attrs: { id: \\"test\\" } }, [
+ _vm._v(\\"Hello \\" + _vm._s(_vm.name) + \\"!\\")
+ ])
+}
+var staticRenderFns = []
+render._withStripped = true
+",
+ "errors": Array [],
+ "functional": false,
+ "source": "
+Hello {{ name }}!
+",
+ "tips": Array [],
+ },
+}
+`;
+
+exports[`should compile to descriptor 1`] = `
+Object {
+ "customBlocks": Array [],
+ "scopeId": "data-v-01ebe570",
+ "script": Object {
+ "code": "//
+//
+//
+//
+//
+
+export default {
+ data () {
+ return { name: 'John Doe' }
+ }
+}
+",
+ "map": Object {
+ "file": "foo.vue",
+ "mappings": ";;;;;;AAMA;AACA;AACA;AACA;AACA",
+ "names": Array [],
+ "sourceRoot": "",
+ "sources": Array [
+ "foo.vue",
+ ],
+ "sourcesContent": Array [
+ "
+
+ Hello {{ name }}!
+
+
+
+
+
+",
+ ],
+ "version": 3,
+ },
+ },
+ "styles": Array [
+ Object {
+ "code": "
+.title {
+ color: red;
+}
+",
+ "errors": Array [],
+ "map": Object {
+ "file": "foo.vue",
+ "mappings": ";AAcA;EACA,UAAA;AACA",
+ "names": Array [],
+ "sources": Array [
+ "foo.vue",
+ ],
+ "sourcesContent": Array [
+ "
+
+ Hello {{ name }}!
+
+
+
+
+
+",
+ ],
+ "version": 3,
+ },
+ "media": undefined,
+ "module": undefined,
+ "moduleName": undefined,
+ "scoped": undefined,
+ },
+ ],
+ "template": Object {
+ "code": "var render = function() {
+ var _vm = this
+ var _h = _vm.$createElement
+ var _c = _vm._self._c || _h
+ return _c(\\"h1\\", { staticClass: \\"title\\", attrs: { id: \\"test\\" } }, [
+ _vm._v(\\"Hello \\" + _vm._s(_vm.name) + \\"!\\")
+ ])
+}
+var staticRenderFns = []
+render._withStripped = true
+",
+ "errors": Array [],
+ "functional": false,
+ "source": "
+Hello {{ name }}!
+",
+ "tips": Array [],
+ },
+}
+`;
diff --git a/test/compile.spec.ts b/test/compile.spec.ts
new file mode 100644
index 0000000..d6a6b85
--- /dev/null
+++ b/test/compile.spec.ts
@@ -0,0 +1,123 @@
+import {createDefaultCompiler, DescriptorCompileResult} from "../src"
+
+it('should prepend data scss option to actual style', () => {
+ const compiler = createDefaultCompiler({
+ style: {
+ preprocessOptions : {
+ scss: {
+ data: `$testColor: red;`
+ }
+ }
+ }
+ })
+ const result = compiler.compileStyle('foo.vue', 'foo',
+ {type: 'style', lang: 'scss', content: '.foo_0{ color: $testColor }', map: undefined, attrs: {}, start: 1, end: 1}
+ );
+
+ expect(result.code).toEqual(expect.stringContaining('color: red'))
+})
+
+const source = `
+
+ Hello {{ name }}!
+
+
+
+
+
+`
+
+it('should compile to descriptor', () => {
+ const compiler = createDefaultCompiler()
+ const result = compiler.compileToDescriptor('foo.vue', source)
+
+ expect(removeRawResult(result)).toMatchSnapshot()
+})
+
+it('should compile to descriptor (async)', async () => {
+ const compiler = createDefaultCompiler()
+ const expected = compiler.compileToDescriptor('foo.vue', source)
+ const result = await compiler.compileToDescriptorAsync('foo.vue', source)
+
+ expect(removeRawResult(result)).toMatchSnapshot()
+ expect(removeRawResult(result)).toEqual(removeRawResult(expected))
+})
+
+function removeRawResult(result: DescriptorCompileResult): DescriptorCompileResult {
+ result.styles.map(style => {
+ delete style.rawResult
+ })
+
+ return result
+}
+
+describe('when source contains css module', () => {
+ const componentSource = `
+
+ Hello {{ name }}!
+
+
+
+
+
+ `
+
+
+ describe('production mode', () => {
+ const prodCompiler = createDefaultCompiler(({
+ template: {
+ isProduction: true
+ }
+ }) as any)
+
+ it('should generate deterministic class names when the same component is compiled multiple times', () => {
+
+ const result1 = prodCompiler.compileToDescriptor('foo.vue', componentSource)
+ const result2 = prodCompiler.compileToDescriptor('foo.vue', componentSource)
+
+ const styles1 = result1.styles[0].code;
+ const styles2 = result2.styles[0].code;
+
+ expect(styles1).toEqual(styles2)
+ })
+ })
+
+ describe('develop mode', () => {
+ const devCompiler = createDefaultCompiler(({
+ template: {
+ isProduction: false
+ }
+ }) as any)
+
+ it('should generate deterministic class names when the same component is compiled multiple times', () => {
+
+ const result1 = devCompiler.compileToDescriptor('foo.vue', componentSource)
+ const result2 = devCompiler.compileToDescriptor('foo.vue', componentSource)
+
+ const styles1 = result1.styles[0].code;
+ const styles2 = result2.styles[0].code;
+
+ expect(styles1).toEqual(styles2)
+ })
+ })
+})
diff --git a/test/setup/utils.ts b/test/setup/utils.ts
index d8c015f..202bc1d 100644
--- a/test/setup/utils.ts
+++ b/test/setup/utils.ts
@@ -24,7 +24,9 @@ function inline(filename, code) {
if (id === filename) return filename
},
load(id) {
- if (id === filename) return code
+ if (id === filename) {
+ return code
+ }
}
}
}
@@ -55,7 +57,7 @@ function compile(filename, source) {
if (style.errors.length) console.error(style.errors)
})
- return assemble(compiler, filename, result).code
+ return assemble(compiler, filename, result)
}
const babelit = babel({
@@ -147,7 +149,8 @@ async function build(filename) {
const vueSource = readFileSync(
resolve(__dirname, '../../node_modules/vue/dist/vue.min.js')
-)
+).toString()
+const escape = (any: string) => any.replace(/<\//g, '<\/')
async function open(name, browser, code, id = '#test') {
const page = await browser.newPage()
@@ -159,8 +162,12 @@ async function open(name, browser, code, id = '#test') {
-
-
+
+