状态
初稿!
需求场景
在一个低代码框架上,给vue组件即时编辑scss ,要求和vue组件的style标签有类似scoped(作用域)功能、能够解析::v-deep等。
实现步骤一:使用sass.js解析成普通css
github库
教程
案例
<script src="dist/sass.js"></script>
<script>
var sass = new Sass();
var scss = '$someVar: 123px; .some-selector { width: $someVar; }';
sass.compile(scss, function(result) {
console.log(result);
});
</script>
实现步骤二:构造postcss.js,再次解析css
在网上没有找到相关的插件,使用打算从vue、webpack入手,抄一手代码。
vue组件的style标签主要是通过postcss插件和autoprefixer实现的。
但是postcss是node的插件,不能直接在web层面直接使用。
经过研究发现,postcss解析css部分都是纯js逻辑,所以打算下载postcss进行修改。
经过简单的代码修改(主要是删除掉文件操作部分、node插件调用部分),最终得到一个纯碎的postcss解析器。
然后去看vue的源码,抄解析css的相关代码:
解析<style lang="scss" scoped>的核心代码
大于或等于vue2.7版本
@vue/compiler-sfc/dist/compiler-sfc.js
小于vue2.7版本
@vue/component-compiler-utils/dist/compileStyle.js
@vue/component-compiler-utils/dist/stylePlugins/scoped.js
关键字doCompileStyle
然后就是下载浏览器兼容的postcss插件autoprefixer,补充相关依赖,autoprefixer是纯js的,没有node相关的代码,把里面require('postcss')改为上面修改后的postcss的模块路径就行了。
之后就是组合postcss和相关插件,下面是关键代码:
const postcss = require('./libs/postcss')
let overrideBrowserslist = ['> 1%', 'last 2 versions', 'not ie <= 8']
module.exports.postcss = postcss
module.exports.parse = function (css, options = {}) {
const { trim = true, autoprefixerOptions = {}, scopeId, processOptions } = options
const plugins = options.plugins ? [...options.plugins] : []
if (trim) {
plugins.push(require('./plugins/trim').default())
}
if (scopeId) {
plugins.push(require('./plugins/scoped').default(scopeId))
}
if (autoprefixerOptions && typeof autoprefixerOptions === 'object') {
let apfOptions = { ...autoprefixerOptions }
apfOptions.overrideBrowserslist = apfOptions.overrideBrowserslist || overrideBrowserslist
plugins.push(require('./libs/autoprefixer')(apfOptions))
}
const postcss = require('./libs/postcss')
return postcss(plugins).process(css, processOptions).css
}
最后一步,用webpack打包成module,得到一个纯js的postcss.js模块。
实现步骤三:获取scope id
在vue组件获取this.$options.scopeId(就是组件样式中的data-v-xxx),但是这个是scopeId是module层面的,而且跟vue组件的style并且紧密相关,不是预期组件实例层面的
经过研究,发现可以在vue-loader上做处理
vue.options.compilerOptions 添加
modules: [
{
preTransformNode: (el) => {
// 参考:
//https://vue-loader.vuejs.org/zh/options.html?#compiler
// @vue/compiler-sfc/dist/compiler-sfc.js或 vue-template-compiler/build.js
// 为所有元素节点添加 :[attr]="''"
if (el.type === 1) {
addRawAttr(el, ':[$caseScopeId]', "''");
// 表示组件的根节点
if (!el.parent) {
addRawAttr(el, ':[$parent.$caseScopeId]', "''");
}
}
return el;
},
},
]
//-----
function addRawAttr(el, name, value, range) {
el.attrsMap[name] = value;
el.attrsList.push(rangeSetItem({ name: name, value: value }, range));
}
function rangeSetItem(item, range) {
if (range) {
if (range.start != null) {
item.start = range.start;
}
if (range.end != null) {
item.end = range.end;
}
}
return item;
}
然后在vue代码补上$caseScopeId
Vue.mixin({
computed: {
$caseScopeId() {
return 'case-v-' + this._uid
}
}
})
最后结合sass.js和postcss.js,就获取了vue组件style一模一样的解析。