Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add style scoped #86

Merged
merged 18 commits into from
Apr 1, 2017
Merged
Show file tree
Hide file tree
Changes from 6 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
11 changes: 7 additions & 4 deletions packages/wepy-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,17 @@
"babel-preset-stage-1": "^6.16.0"
},
"dependencies": {
"js-base64": "^2.1.9",
"chokidar": "^1.6.1",
"colors": "^1.1.2",
"commander": "^2.9.0",
"mkdirp": "^0.5.1",
"compare-versions": "^3.0.0",
"hash-sum": "^1.0.2",
"ignore": "^3.2.0",
"js-base64": "^2.1.9",
"mkdirp": "^0.5.1",
"postcss": "^5.2.16",
"postcss-selector-parser": "^2.2.3",
"update-notifier": "^1.0.2",
"xmldom": "^0.1.22",
"compare-versions": "^3.0.0"
"xmldom": "^0.1.22"
}
}
75 changes: 57 additions & 18 deletions packages/wepy-cli/src/compile-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,72 @@ import util from './util';
import cache from './cache';

import loader from './loader';

import scopedHandler from './style-compiler/scoped';

const LANG_MAP = {
'less': '.less',
'sass': '.sass;.scss'
};

export default {
compile (lang, content, requires, opath) {
compile (styleRst, requires, opath, moduleId) {
let config = util.getConfig();
let src = cache.getSrc();
let dist = cache.getDist();
let ext = cache.getExt();
const filepath = path.join(opath.dir, opath.base);

if (arguments.length === 2) {
if (typeof styleRst === 'string') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typeof 为 string 时 filepath 此时应该是undefined吧,这里应该是个BUG

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我的锅

// .compile('less', path) 这种形式
opath = requires;
requires = [];
opath = content;
content = util.readFile(path.join(opath.dir, opath.base));
moduleId = '';
styleRst = {
type: styleRst,
scoped: '',
code: '',
blocks: [{
type: styleRst,
scoped: '',
code: util.readFile(filepath) || ''
}]
};
}
const blocks = styleRst.blocks || [];
const allPromises = [];
blocks.forEach((block) => {
let lang = block.type;
const content = block.code;
// 全局 scoped
// 因为对于 app 而言 scoped 会被设置为 ''
const gScoped = styleRst.scoped;
const scoped = gScoped && block.scoped;

if (lang === 'scss')
lang = 'sass';
if (lang === 'scss')
lang = 'sass';

let compiler = loader.loadCompiler(lang);
let compiler = loader.loadCompiler(lang);

if (!compiler) {
throw `未发现相关 ${lang} 编译器配置,请检查wepy.config.js文件。`;
}
if (!compiler) {
throw `未发现相关 ${lang} 编译器配置,请检查wepy.config.js文件。`;
}

compiler(content, config.compilers[lang] || {}, path.join(opath.dir, opath.base)).then((css) => {
const p = compiler(content, config.compilers[lang] || {}, path.join(opath.dir, opath.base)).then((css) => {
// 处理 scoped
if (scoped) {
// 存在有 scoped 的 style
return scopedHandler(moduleId, css).then((cssContent) => {
return cssContent;
});
} else {
return css;
}
});

allPromises.push(p);
});
Promise.all(allPromises).then((rets) => {
let allContent = rets.join('');
if (requires && requires.length) {
requires.forEach((r) => {
let comsrc = util.findComponent(r);
Expand All @@ -51,28 +87,31 @@ export default {
let code = util.readFile(comsrc);
if (isNPM || /<style/.test(code)) {
relative = relative.replace(ext, '.wxss').replace(/\\/ig, '/').replace('../', './');
css = '@import "' + relative + '";\n' + css;
allContent = '@import "' + relative + '";\n' + allContent;
}
}
});
}
write2Target(allContent);
}).catch((e) => {
console.log(e);
});

function write2Target(cssContent) {
let target = util.getDistPath(opath, 'wxss', src, dist);

let plg = new loader.PluginHelper(config.plugins, {
type: 'css',
code: content,
code: cssContent,
file: target,
output (p) {
util.output(p.action, p.file);
},
done (rst) {
util.output('写入', rst.file);
util.writeFile(target, css);
util.writeFile(target, rst.code);
}
});
}).catch((e) => {
console.log(e);
});
}
}
}
164 changes: 111 additions & 53 deletions packages/wepy-cli/src/compile-wpy.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,72 +68,106 @@ export default {
let config = util.getConfig();
let filepath;

if (arguments.length === 1) {

if (typeof(xml) === 'object' && xml.dir) {
opath = xml;
filepath = path.join(xml.dir, xml.base);
} else {
opath = path.parse(xml);
filepath = xml;
}
let content = util.readFile(filepath);

if (content === null) {
util.error('打开文件失败: ' + filepath)
return;
}
let startlen = content.indexOf('<script') + 7;
while(content[startlen++] !== '>') {
// do nothing;
}
content = util.encode(content, startlen, content.indexOf('</script>') - 1);

// replace :attr to v-bind:attr
/*content = content.replace(/<[\w-\_]*\s[^>]*>/ig, (tag) => {
return tag.replace(/\s+:([\w-_]*)([\.\w]*)\s*=/ig, (attr, name, type) => { // replace :param.sync => v-bind:param.sync
if (type === '.once' || type === '.sync') {
}
else
type = '.once';
return ` v-bind:${name}${type}=`;
}).replace(/\s+\@([\w-_]*)\s*=/ig, (attr, name) => { // replace @change => v-on:change
return `v-on:${name}`;
});
})*/

content = util.attrReplace(content);
if (typeof(xml) === 'object' && xml.dir) {
opath = xml;
filepath = path.join(xml.dir, xml.base);
} else {
opath = path.parse(xml);
filepath = xml;
}
let content = util.readFile(filepath);

xml = this.createParser().parseFromString(content);
if (content === null) {
util.error('打开文件失败: ' + filepath)
return;
}
let startlen = content.indexOf('<script') + 7;
while(content[startlen++] !== '>') {
// do nothing;
}
content = util.encode(content, startlen, content.indexOf('</script>') - 1);

let rst = {style: {code: ''}, template: {code: ''}, script: {code: ''}};
// replace :attr to v-bind:attr
/*content = content.replace(/<[\w-\_]*\s[^>]*>/ig, (tag) => {
return tag.replace(/\s+:([\w-_]*)([\.\w]*)\s*=/ig, (attr, name, type) => { // replace :param.sync => v-bind:param.sync
if (type === '.once' || type === '.sync') {
}
else
type = '.once';
return ` v-bind:${name}${type}=`;
}).replace(/\s+\@([\w-_]*)\s*=/ig, (attr, name) => { // replace @change => v-on:change
return `v-on:${name}`;
});
})*/

content = util.attrReplace(content);

xml = this.createParser().parseFromString(content);

const moduleId = util.genId(filepath);

let rst = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

其实对于 template 和 script 来说,多个标签完全是没有意义的事情,所以我觉得这里完全可以将 style 定义为数组,其它的就定义成 object

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里其实做的时候考虑 把他们合并成一块,我觉得如果要约定的话,就可以直接约定死 style 可以是多个,template 和 script 最多只能有一个,如果发现出现多个,是否可以直接强制报错了?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

对的。这样实现最好,如果不报错,那默认也是被覆盖。
因为暂时来看 template 和 script 完全没有多标签的需要

moduleId: moduleId,
style: {
code: '',
src: '',
type: '',
blocks: []
},
template: {
code: '',
src: '',
type: '',
blocks: []
},
script: {
code: '',
src: '',
type: '',
blocks: []
}
};

[].slice.call(xml.childNodes || []).forEach((child) => {
if (child.nodeName === 'style' || child.nodeName === 'template' || child.nodeName === 'script') {
rst[child.nodeName].src = child.getAttribute('src');
rst[child.nodeName].type = child.getAttribute('lang') || child.getAttribute('type');
const nodeName = child.nodeName;
if (nodeName === 'style' || nodeName === 'template' || nodeName === 'script') {
const blocks = rst[nodeName].blocks;
const rstTypeObj = {
code: ''
};
blocks.push(rstTypeObj);

rstTypeObj.src = child.getAttribute('src');
rstTypeObj.type = child.getAttribute('lang') || child.getAttribute('type');
if (nodeName === 'style') {
// 针对于 style 增加是否包含 scoped 属性
rstTypeObj.scoped = child.getAttribute('scoped');
}

if (rst[child.nodeName].src) {
rst[child.nodeName].src = path.resolve(opath.dir, rst[child.nodeName].src);
if (rstTypeObj.src) {
rstTypeObj.src = path.resolve(opath.dir, rstTypeObj.src);
}

if (rst[child.nodeName].src && util.isFile(rst[child.nodeName].src)) {
rst[child.nodeName].code = util.readFile(rst[child.nodeName].src, 'utf-8');
if (rst[child.nodeName].code === null) {
throw '打开文件失败: ' + rst[child.nodeName].src;
if (rstTypeObj.src && util.isFile(rstTypeObj.src)) {
const fileCode = util.readFile(rstTypeObj.src, 'utf-8');
if (fileCode === null) {
throw '打开文件失败: ' + rstTypeObj.src;
} else {
rstTypeObj.code += fileCode;
}
} else {
[].slice.call(child.childNodes || []).forEach((c) => {
rst[child.nodeName].code += util.decode(c.toString());
rstTypeObj.code += util.decode(c.toString());
});
}

if (!rst[child.nodeName].src)
rst[child.nodeName].src = path.join(opath.dir, opath.name + opath.ext);
if (!rstTypeObj.src)
rstTypeObj.src = path.join(opath.dir, opath.name + opath.ext);
}
});

util.computedWpyFile(rst);

/*
Use components instead
if (rst.template.code) {
Expand Down Expand Up @@ -275,6 +309,14 @@ export default {
}
})();

if (rst.style.scoped && rst.template.code) {
// 存在有 scoped 部分就需要 更新 template.code
var node = this.createParser().parseFromString(rst.template.code);
walkNode(node, rst.moduleId);
// 更新 template.code
rst.template.code = node.toString();
}

return rst;
},

Expand All @@ -301,9 +343,6 @@ export default {
let pages = cache.getPages();

let type = '';

let rst = {style: {code: ''}, template: {code: ''}, script: {code: ''}};

let relative = path.relative(util.currentDir, filepath);

if (filepath === path.join(util.currentDir, src, 'app' + wpyExt)) {
Expand Down Expand Up @@ -343,7 +382,13 @@ export default {
requires.push(path.join(opath.dir, wpy.template.components[k]));
}
}
cStyle.compile(wpy.style.type, wpy.style.code, requires, opath);
if (type === 'app') {
// 如果是 app 没有 wxml 所以这里不能做替换
// 强制 scoped = '' 也就是在 app.wpy 中
// 设置 scoped 无效
wpy.style.scoped = '';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scoped 目前只存在两个值, true or false, 所以使用 boolean 写代码时可能更好理解。

}
cStyle.compile(wpy.style, requires, opath, wpy.moduleId);
} else {
this.remove(opath, 'wxss');
}
Expand All @@ -358,3 +403,16 @@ export default {
}
}
};

function walkNode (node, moduleId) {
if (node.childNodes) {
[].slice.call(node.childNodes || []).forEach((child) => {
if (child.tagName) {
// 是标签 则增加class
const cls = child.getAttribute('class');
child.setAttribute('class', (cls + ' ' + moduleId).trim());
walkNode(child, moduleId);
}
});
}
}
29 changes: 29 additions & 0 deletions packages/wepy-cli/src/style-compiler/scope-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// copy https://github.com/vuejs/vue-loader/blob/master/lib/style-compiler/plugins/scope-id.js and fix by wepy

import postcss from 'postcss';
import selectorParser from 'postcss-selector-parser';

export default postcss.plugin('add-id', function (id) {
return function (root) {
root.each(function rewriteSelector (node) {
if (!node.selector) {
// handle media queries
if (node.type === 'atrule' && node.name === 'media') {
node.each(rewriteSelector);
}
return;
}
node.selector = selectorParser(function (selectors) {
selectors.each(function (selector) {
var node = null;
selector.each(function (n) {
if (n.type !== 'pseudo') node = n;
});
selector.insertAfter(node, selectorParser.className({
value: id
}));
});
}).process(node.selector).result;
});
};
});
Loading