Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

Support scoped css #87

Merged
merged 10 commits into from
May 3, 2017
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ rollup.rollup({
'de-indent',
'debug',
'fs',
'hash-sum',
'html-minifier',
'less',
'magic-string',
Expand All @@ -39,6 +40,7 @@ rollup.rollup({
'path',
'postcss',
'postcss-modules',
'postcss-selector-parser',
'posthtml',
'posthtml-attrs-parser',
'pug',
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@
"camelcase": "^4.0.0",
"de-indent": "^1.0.2",
"debug": "^2.6.0",
"hash-sum": "^1.0.2",
"html-minifier": "^3.2.3",
"magic-string": "^0.19.0",
"merge-options": "0.0.64",
"parse5": "^2.1.0",
"postcss": "^5.2.11",
"postcss-modules": "^0.6.4",
"postcss-selector-parser": "^2.2.3",
"posthtml": "^0.9.2",
"posthtml-attrs-parser": "^0.1.1",
"rollup-pluginutils": "^2.0.1",
Expand Down
15 changes: 15 additions & 0 deletions src/gen-scope-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// utility for generating a uid for each component file
// used in scoped CSS rewriting
import path from 'path'
import hash from 'hash-sum'
const cache = Object.create(null)

export default function genScopeID (file) {
const modified = path.relative(process.cwd(), file)

if (!cache[modified]) {
cache[modified] = 'data-v-' + hash(modified)
}

return cache[modified]
}
23 changes: 23 additions & 0 deletions src/injections.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/options.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { templateJs, moduleJs, renderJs } from './injections'
import { templateJs, moduleJs, scopeJs, renderJs } from './injections'
import { coffee } from './script/index'

export default {
Expand Down Expand Up @@ -80,6 +80,11 @@ export default {
module: {
js: moduleJs,
babel: moduleJs
},

scoped: {
js: scopeJs,
babel: scopeJs
}
},

Expand Down
53 changes: 53 additions & 0 deletions src/style/css.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import postcss from 'postcss'
import modules from 'postcss-modules'
import selectorParser from 'postcss-selector-parser'
import camelcase from 'camelcase'
// import MagicString from 'magic-string'
import genScopeID from '../gen-scope-id'
import debug from '../debug'

const addScopeID = postcss.plugin('add-scope-id', options => {
const selectorTransformer = selectorParser(selectors => {
selectors.each(selector => {
let target = null
selector.each(n => {
if (n.type !== 'pseudo' && n.type !== 'combinator') {
target = n
}
})

selector.insertAfter(target, selectorParser.attribute({
attribute: options.scopeID
}))
})
})

return root => {
root.walkRules(rule => {
rule.selector = selectorTransformer.process(rule.selector).result
})
}
})

function compileModule (code, map, source, options) {
let style
debug(`CSS Modules: ${source.id}`)
Expand All @@ -24,6 +49,22 @@ function compileModule (code, map, source, options) {
)
}

function compileScopedCSS (code, map, source, options) {
debug(`Scoped CSS: ${source.id}`)

return postcss([
addScopeID({
scopeID: genScopeID(source.id)
})
]).process(code, { map: { inline: false, prev: map }, from: source.id, to: source.id })
.then(
result => ({ code: result.css, map: result.map.toString() }),
error => {
throw error
}
)
}

function escapeRegExp (str) {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&')
}
Expand Down Expand Up @@ -70,6 +111,18 @@ export default async function (promise, options) {
}).catch(error => debug(error))
}

if (style.scoped === true) {
return compileScopedCSS(code, map, style, options).then(compiled => {
if (style.$compiled) {
compiled.$prev = style.$compiled
}

style.$compiled = compiled

return style
})
}

const output = { code, map, lang: 'css' }

if (style.$compiled) output.$prev = style.$compiled
Expand Down
48 changes: 38 additions & 10 deletions src/vueTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import templateProcessor from './template/index'
import { relative } from 'path'
import MagicString from 'magic-string'
import debug from './debug'
import { injectModule, injectTemplate, injectRender } from './injections'
import { injectModule, injectScopeID, injectTemplate, injectRender } from './injections'
import genScopeID from './gen-scope-id'

function getNodeAttrs (node) {
if (node.attrs) {
Expand Down Expand Up @@ -65,7 +66,7 @@ async function processTemplate (source, id, content, options, nodes, modules) {
return htmlMinifier.minify(template, options.htmlMinifier)
}

async function processScript (source, id, content, options, nodes, modules) {
async function processScript (source, id, content, options, nodes, modules, scoped) {
const template = await processTemplate(nodes.template[0], id, content, options, nodes, modules)

debug(`Process script: ${id}`)
Expand All @@ -79,19 +80,39 @@ async function processScript (source, id, content, options, nodes, modules) {
source = await options.script[source.attrs.lang](source, id, content, options, nodes)
}

const script = deIndent(padContent(content.slice(0, content.indexOf(source.code))) + source.code)
let script = deIndent(padContent(content.slice(0, content.indexOf(source.code))) + source.code)
const map = (new MagicString(script)).generateMap({ hires: true })
const scriptWithModules = injectModule(script, modules, lang, id, options)

script = processScriptForStyle(script, modules, scoped, lang, id, options)

script = await processScriptForRender(script, template, lang, id, options)

return { map, code: script }
}

function processScriptForStyle (script, modules, scoped, lang, id, options) {
script = injectModule(script, modules, lang, id, options)

if (scoped) {
const scopeID = genScopeID(id)
script = injectScopeID(script, scopeID, lang, id, options)
}

return script
}

async function processScriptForRender (script, template, lang, id, options) {
if (template && options.compileTemplate) {
const render = require('vue-template-compiler').compile(template, options.compileOptions)

return { map, code: await injectRender(scriptWithModules, render, lang, id, options) }
} else if (template) {
return { map, code: await injectTemplate(scriptWithModules, template, lang, id, options) }
} else {
return { map, code: scriptWithModules }
return await injectRender(script, render, lang, id, options)
}

if (template) {
return await injectTemplate(script, template, lang, id, options)
}

return script
}

// eslint-disable-next-line complexity
Expand Down Expand Up @@ -173,11 +194,18 @@ const getModules = function (styles) {
return all
}

const hasScoped = function (styles) {
return styles.reduce((scoped, style) => {
return scoped || style.scoped
}, false)
}

export default async function vueTransform (code, id, options) {
const nodes = parseTemplate(code)
const css = await processStyle(nodes.style, id, code, options, nodes)
const modules = getModules(css)
const js = await processScript(nodes.script[0], id, code, options, nodes, modules)
const scoped = hasScoped(css)
const js = await processScript(nodes.script[0], id, code, options, nodes, modules, scoped)

const isProduction = process.env.NODE_ENV === 'production'
const isWithStripped = options.stripWith !== false
Expand Down
3 changes: 3 additions & 0 deletions test/expects/scoped-css.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.test[data-v-4f57af4d] {
color: red;
}
3 changes: 3 additions & 0 deletions test/expects/scoped-css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var scopedCss = { template: "<div class=\"test\">Foo</div>",_scopeId: 'data-v-4f57af4d'};

export default scopedCss;
13 changes: 13 additions & 0 deletions test/fixtures/scoped-css.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template>
<div class="test">Foo</div>
</template>

<script lang="babel">
export default {}
</script>

<style lang="css" scoped>
.test {
color: red;
}
</style>
2 changes: 1 addition & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function test(name) {
assert.equal(code.trim(), expected.trim(), 'should compile code correctly')

// Check css output
if (['style', 'css-modules', 'css-modules-static', 'scss', 'pug', 'less', 'stylus'].indexOf(name) > -1) {
if (['style', 'css-modules', 'css-modules-static', 'scoped-css', 'scss', 'pug', 'less', 'stylus'].indexOf(name) > -1) {
var css = read('expects/' + name + '.css')
assert.equal(css.trim(), actualCss.trim(), 'should output style tag content')
} else if (['no-css-extract'].indexOf(name) > -1) {
Expand Down
24 changes: 24 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,10 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2"
write "^0.2.1"

flatten@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"

for-in@^0.1.5:
version "0.1.6"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8"
Expand Down Expand Up @@ -1606,6 +1610,10 @@ has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"

hash-sum@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"

hawk@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
Expand Down Expand Up @@ -1692,6 +1700,10 @@ indent-string@^2.1.0:
dependencies:
repeating "^2.0.0"

indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"

inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
Expand Down Expand Up @@ -2534,6 +2546,14 @@ postcss-modules@^0.6.4:
postcss "^5.2.8"
string-hash "^1.1.1"

postcss-selector-parser@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90"
dependencies:
flatten "^1.0.2"
indexes-of "^1.0.1"
uniq "^1.0.1"

postcss@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.1.2.tgz#bd84886a66bcad489afaf7c673eed5ef639551e2"
Expand Down Expand Up @@ -3236,6 +3256,10 @@ uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"

uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"

upper-case@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
Expand Down