开发笔记-web端解析scss

84 阅读2分钟

状态

初稿!

需求场景

在一个低代码框架上,给vue组件即时编辑scss ,要求和vue组件的style标签有类似scoped(作用域)功能、能够解析::v-deep等。

实现步骤一:使用sass.js解析成普通css

github库

github.com/medialize/s…

教程

github.com/medialize/s…

案例

<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一模一样的解析。