Skip to content

Commit 4fb6b8c

Browse files
authored
Merge pull request #28 from Treri/master
支持 vueify 和 vue-loader 形式的自动 scopeId, 不需要占位符
2 parents 94cc454 + d3c539b commit 4fb6b8c

File tree

9 files changed

+168
-74
lines changed

9 files changed

+168
-74
lines changed

README.md

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ fis.match('src/**.vue', {
3333
// styleNameJoin
3434
styleNameJoin: '', // 样式文件命名连接符 `component-xx-a.css`
3535

36+
extractCSS: true, // 是否将css生成新的文件, 如果为false, 则会内联到js中
37+
3638
// css scoped
37-
ccssScopedFlag: '__vuec__', // 组件scoped占位符
3839
cssScopedIdPrefix: '_v-', // hash前缀:_v-23j232jj
3940
cssScopedHashType: 'sum', // hash生成模式,num:使用`hash-sum`, md5: 使用`fis.util.md5`
4041
cssScopedHashLength: 8, // hash 长度,cssScopedHashType为md5时有效
@@ -116,7 +117,7 @@ fis.match('src/(component/**.css)', {
116117

117118
参考[vue-loader](https://github.com/vuejs/vue-loader)源码,结合fis3的编译特性而编写,下面是parser阶段的主要过程:
118119

119-
1. 解析vue文件,找到其中的`style`,'template','script'标签。
120+
1. 解析vue文件,找到其中的`style`,`template`,`script`标签。
120121

121122
2. 每一个`style`标签创建一个对应的虚拟文件,后缀为`lang`属性指定,默认`css`,你可以指定`less`或其它的后缀。对创建虚拟文件,一样会进行fis3的编译流程(属性`lang`的值决定该文件怎么编译),并加入当前文件的依赖。
122123

@@ -126,42 +127,20 @@ fis.match('src/(component/**.css)', {
126127

127128
## 组件编写规范
128129

129-
`style`标签可以有多个,`template``script`标签只能有一个,具体请参考[vue 单文件组件](http://vuejs.org.cn/guide/application.html)
130+
`style`标签可以有多个,`template``script`标签只能有一个,具体请参考[vue 单文件组件](https://vuejs.org/v2/guide/single-file-components.html)
130131

131132
## css scoped支持
132133

133134
> 为了保证每一个组件样式的独立性,是当前组件定义的样式只在当前的组件内生效,引入css scoped机制。
134135
135-
- 在模板的元素上(一般是根节点)加上scoped标志(class,attr,id),默认为`__vuec__`, 你可以通过`cssScopedFlag`自定义。可以加在class,或者属性,或者id。
136-
137-
```html
138-
<template>
139-
<div class="test" __vuec__></div>
140-
<div class="test __vuec__"></div>
141-
</template>
142-
```
143-
144-
- 在样式中使用scoped标志。
145-
146-
```css
147-
.test[__vuec__] {
148-
//
149-
}
150-
// or
151-
.other.__vuec__ {
152-
153-
}
154-
// or
155-
.__vuec__ {
156-
.a {
157-
158-
}
159-
}
160-
```
136+
- 在style标签中使用scoped标志。
161137

162-
- scoped标志会根据文件路径生成唯一的hash字符串(如:`_v-23j232jj` );
138+
```css
139+
<style scoped></style>
140+
<style lang="scss" scoped></style>
141+
```
163142

164-
- 配置:scoped标志默认为`__vuec__`,你可以自定义。
143+
- scoped标志会根据文件路径生成唯一的hash字符串(如:`_v-23j232jj` )
165144

166145
## 测试demo
167146

index.js

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ var objectAssign = require('object-assign');
33
var hashSum = require('hash-sum');
44
var compiler = require('vue-template-compiler');
55

6+
var genId = require('./lib/gen-id');
7+
var rewriteStyle = require('./lib/style-rewriter');
68
var compileTemplate = require('./lib/template-compiler');
79
var insertCSS = require('./lib/insert-css');
810

911
// exports
1012
module.exports = function(content, file, conf) {
1113
var scriptStr = '';
12-
var output, configs, vuecId, jsLang;
14+
var output, configs, jsLang;
1315

1416
// configs
1517
configs = objectAssign({
1618
extractCSS: true,
17-
cssScopedFlag: '__vuec__',
1819
cssScopedIdPrefix: '_v-',
1920
cssScopedHashType: 'sum',
2021
cssScopedHashLength: 8,
@@ -23,29 +24,19 @@ module.exports = function(content, file, conf) {
2324
runtimeOnly: false,
2425
}, conf);
2526

26-
// replace scoped flag
27-
function replaceScopedFlag(str) {
28-
var reg = new RegExp('([^a-zA-Z0-9\-_])('+ configs.cssScopedFlag +')([^a-zA-Z0-9\-_])', 'g');
29-
str = str.replace(reg, function($0, $1, $2, $3) {
30-
return $1 + vuecId + $3;
31-
});
32-
return str;
33-
}
34-
3527
// 兼容content为buffer的情况
3628
content = content.toString();
3729

38-
// scope replace
39-
if (configs.cssScopedType == 'sum') {
40-
vuecId = configs.cssScopedIdPrefix + hashSum(file.subpath);
41-
} else {
42-
vuecId = configs.cssScopedIdPrefix + fis.util.md5(file.subpath, configs.cssScopedHashLength);
43-
}
44-
content = replaceScopedFlag(content);
45-
30+
// generate css scope id
31+
var id = configs.cssScopedIdPrefix + genId(file, configs);
4632
// parse
4733
var output = compiler.parseComponent(content.toString(), { pad: true });
4834

35+
// check for scoped style nodes
36+
var hasScopedStyle = output.styles.some(function (style) {
37+
return style.scoped
38+
});
39+
4940
// script
5041
if (output.script) {
5142
scriptStr = output.script.content;
@@ -65,6 +56,13 @@ module.exports = function(content, file, conf) {
6556
isJsLike: true
6657
});
6758

59+
scriptStr += '\nvar __vue__options__;\n';
60+
scriptStr += 'if(module.exports.__esModule && module.exports.default){\n';
61+
scriptStr += ' __vue__options__ = module.exports.default;\n';
62+
scriptStr += '}else{\n';
63+
scriptStr += ' __vue__options__ = module.exports;\n';
64+
scriptStr += '}\n';
65+
6866
if(output.template){
6967
var templateContent = fis.compile.partial(output.template.content, file, {
7068
ext: output.template.lang || 'html',
@@ -74,22 +72,22 @@ module.exports = function(content, file, conf) {
7472
if(configs.runtimeOnly){
7573
var result = compileTemplate(templateContent);
7674
if(result){
77-
scriptStr += '\n;\n(function(renderFun, staticRenderFns){\n'
78-
scriptStr += '\nif(module && module.exports){ module.exports.render=renderFun; module.exports.staticRenderFns=staticRenderFns;}\n';
79-
scriptStr += '\nif(exports && exports.default){ exports.default.render=renderFun; exports.default.staticRenderFns=staticRenderFns;}\n';
80-
scriptStr += '\n})(' + result.render + ',' + result.staticRenderFns + ');\n';
75+
scriptStr += '__vue__options__.render =' + result.render + '\n';
76+
scriptStr += '__vue__options__.staticRenderFns =' + result.staticRenderFns + '\n';
8177
}
8278
}else{
8379
// template
84-
scriptStr += '\n;\n(function(template){\n'
85-
scriptStr += '\nmodule && module.exports && (module.exports.template = template);\n';
86-
scriptStr += '\nexports && exports.default && (exports.default.template = template);\n';
87-
scriptStr += '\n})(' + JSON.stringify(templateContent) + ');\n';
80+
scriptStr += '__vue__options__.template = ' + JSON.stringify(templateContent) + '\n';
8881
}
8982
}
9083

84+
if(hasScopedStyle){
85+
// template
86+
scriptStr += '__vue__options__._scopeId = ' + JSON.stringify(id) + '\n';
87+
}
88+
9189
// style
92-
output['styles'].forEach(function(item, index) {
90+
output.styles.forEach(function(item, index) {
9391
if(!item.content){
9492
return;
9593
}
@@ -105,6 +103,8 @@ module.exports = function(content, file, conf) {
105103
isCssLike: true
106104
});
107105

106+
styleContent = rewriteStyle(id, styleContent, item.scoped, {})
107+
108108
if(!configs.extractCSS){
109109
scriptStr += '\n;(' + insertCSS + ')(' + JSON.stringify(styleContent) + ');\n';
110110
return;
@@ -131,8 +131,5 @@ module.exports = function(content, file, conf) {
131131
file.addRequire(styleFile.getId());
132132
});
133133

134-
// 处理一遍scoped css
135-
scriptStr = replaceScopedFlag(scriptStr);
136-
137134
return scriptStr;
138135
};

lib/gen-id.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// utility for generating a uid for each component file
2+
// used in scoped CSS rewriting
3+
var hash = require('hash-sum')
4+
var cache = Object.create(null)
5+
6+
// module.exports = function genId (file) {
7+
// return cache[file] || (cache[file] = hash(file))
8+
// }
9+
10+
module.exports = function genId(file, configs){
11+
if(cache[file.subpath]){
12+
return cache[file.subpath];
13+
}
14+
15+
var scopeId;
16+
17+
// scope replace
18+
if (configs.cssScopedType == 'sum') {
19+
scopeId = hash(file.subpath);
20+
} else {
21+
scopeId = fis.util.md5(file.subpath, configs.cssScopedHashLength);
22+
}
23+
24+
return cache[file.subpath] = scopeId;
25+
};

lib/style-rewriter.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
var postcss = require('postcss')
2+
var selectorParser = require('postcss-selector-parser')
3+
var cache = require('lru-cache')(100)
4+
var assign = require('object-assign')
5+
6+
var deasync = require('deasync')
7+
8+
var currentId
9+
var addId = postcss.plugin('add-id', function () {
10+
return function (root) {
11+
root.each(function rewriteSelector (node) {
12+
if (!node.selector) {
13+
// handle media queries
14+
if (node.type === 'atrule' && node.name === 'media') {
15+
node.each(rewriteSelector)
16+
}
17+
return
18+
}
19+
node.selector = selectorParser(function (selectors) {
20+
selectors.each(function (selector) {
21+
var node = null
22+
selector.each(function (n) {
23+
if (n.type !== 'pseudo') node = n
24+
})
25+
selector.insertAfter(node, selectorParser.attribute({
26+
attribute: currentId
27+
}))
28+
})
29+
}).process(node.selector).result
30+
})
31+
}
32+
})
33+
34+
/**
35+
* Add attribute selector to css
36+
*
37+
* @param {String} id
38+
* @param {String} css
39+
* @param {Boolean} scoped
40+
* @param {Object} options
41+
* @return {Promise}
42+
*/
43+
44+
module.exports = deasync(function (id, css, scoped, options, cbk) {
45+
var key = id + '!!' + scoped + '!!' + css
46+
var val = cache.get(key)
47+
if (val) {
48+
cbk(null, val)
49+
} else {
50+
var plugins = []
51+
var opts = {}
52+
53+
// // do not support post plugins here, use fis3 style plugins
54+
// if (options.postcss instanceof Array) {
55+
// plugins = options.postcss.slice()
56+
// } else if (options.postcss instanceof Object) {
57+
// plugins = options.postcss.plugins || []
58+
// opts = options.postcss.options
59+
// }
60+
61+
// scoped css rewrite
62+
// make sure the addId plugin is only pushed once
63+
if (scoped && plugins.indexOf(addId) === -1) {
64+
plugins.push(addId)
65+
}
66+
67+
// remove the addId plugin if the style block is not scoped
68+
if (!scoped && plugins.indexOf(addId) !== -1) {
69+
plugins.splice(plugins.indexOf(addId), 1)
70+
}
71+
72+
// // do not support cssnano minification here, use fis3 style plugins
73+
// // minification
74+
// if (process.env.NODE_ENV === 'production') {
75+
// plugins.push(require('cssnano')(assign({
76+
// safe: true
77+
// }, options.cssnano)))
78+
// }
79+
currentId = id
80+
81+
postcss(plugins)
82+
.process(css, opts)
83+
.then(function (res) {
84+
cache.set(key, res.css)
85+
cbk(null, res.css)
86+
})
87+
}
88+
})
89+

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
"main": "index.js",
1313
"dependencies": {
1414
"chalk": "^1.1.3",
15+
"deasync": "^0.1.9",
1516
"hash-sum": "^1.0.2",
17+
"lru-cache": "^4.0.2",
1618
"object-assign": "^4.1.0",
19+
"postcss": "^5.2.16",
20+
"postcss-selector-parser": "^2.2.3",
1721
"uglify-js": "^2.8.14",
1822
"vue-template-compiler": "^2.1.6",
1923
"vue-template-es2015-compiler": "^1.4.0"

test/src/component/a.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="component-a" __vuec__>
2+
<div class="component-a">
33
Component A
44
</div>
55
</template>
@@ -15,13 +15,13 @@
1515
}
1616
</script>
1717

18-
<style lang="less">
18+
<style lang="less" scoped>
1919
body {
2020
a {
2121
color: inherit;
2222
}
2323
}
24-
.component-a[__vuec__] {
24+
.component-a {
2525
line-height: 50px;
2626
text-align: center;
2727
color: #fff;

test/src/component/index.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

2-
<style lang="less">
2+
<style lang="less" scoped>
33
@import "../less/other.less";
4-
.index.__vuec__ {
4+
.index {
55
> p {
66
line-height: 50px;
77
text-align: center;
@@ -23,7 +23,7 @@ $blue : #1875e7; 
2323
</style>
2424

2525
<template>
26-
<div class="index __vuec__" >
26+
<div class="index" >
2727
<p>fis3-parser-vue-component demo runing ~</p>
2828
<component-a></component-a>
2929
<component-b></component-b>

test2/src/component/a.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template lang="html">
2-
<div class="component-a" __vuec__>
2+
<div class="component-a">
33
Component A
44
</div>
55
</template>
@@ -15,13 +15,13 @@
1515
}
1616
</script>
1717

18-
<style lang="less">
18+
<style lang="less" scoped>
1919
body {
2020
a {
2121
color: inherit;
2222
}
2323
}
24-
.component-a[__vuec__] {
24+
.component-a {
2525
line-height: 50px;
2626
text-align: center;
2727
color: #fff;

0 commit comments

Comments
 (0)