Giter Site home page Giter Site logo

v-r-t's Introduction

v-r-t

v-r-t's People

Contributors

ronktsang avatar

Stargazers

 avatar

Watchers

James Cloos avatar  avatar

v-r-t's Issues

webpack 使用记录

Node 模块的 polyfill

在引入 Vue 时,webpack 会很好心的帮我们引入几个垫片库 /buildin/global.js, ./node_modules/setimmediate/setImmediate.js, ./node_modules/timers-browserify/main.js, ./node_modules/process/browser.js, 为啥呢?

官方文档:

这些选项可以配置是否 polyfill 或 mock 某些 Node.js 全局变量和模块。这可以使最初为 Node.js 环境编写的代码,在其他环境(如浏览器)中运行。

之前使用场景都是浏览器,所以这样的 polyfill 是可行的,但是在当前的环境中是不可行的。

解决:

// webpack config
node: false

官方文档:Node

`updateElement`

updateElement

// 原始样式
{
  style: {
     color: 'blue',
     background-color: 'white'
     flex: 2,
     line: 3
  }
}
// 向 native 发送 update 请求
{
  style: {
    color: 'red',                     // 改变 color
    background-color: ''      // 置空,表示删除
    border:1                      // 新增
  }
}

prepare

  • Viola-js

    • framework

    • tools

  • Viola-Vue

    • how
  • Loader

    • viola-style-loader

    • typescript

  • Cli

    • peerDependencis

在 vue-loader V14 中 style 的提取(json化)

vue-loader v14 中对 style 的载入是一个动态的载入

一个 .vue 文件经过 vue-loader 后,style 会是一个注入函数

var injectStyle = require("!!vue-style-loader!css-loader!../node_modules/vue-loader/lib/style-compiler/index?{\"optionsId\":\"0\",\"vue\":true,\"id\":\"data-v-dc790388\",\"scoped\":true,\"sourceMap\":false}!sass-loader!../node_modules/vue-loader/lib/selector?type=styles&index=0!./hello.vue")

injectStyle 是经过 selector, style-compiler, css-loader, vue-style-loader 后生成的一个注入函数,随后被作为参数传入了 normalizeComponent()

normalizeComponent

而关于 normalizeComponent, 文件里的注释如下,说的是这个函数是属于 runtime 的,打包后才运行的

This module is a runtime utility for cleaner component module output and will
be included in the final webpack user bundle.

normalizeComponent 对 style 的处理:

  • 第一步,生成一个样式加载的函数
/* 样式加载函数 */
  var hook
  if (moduleIdentifier) { // server build
    hook = function (context) {
      // 2.3 injection
      context =
        context || // cached call
        (this.$vnode && this.$vnode.ssrContext) || // stateful
        (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
      // 2.2 with runInNewContext: true
      if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
        context = __VUE_SSR_CONTEXT__
      }
      // inject component styles
      if (injectStyles) {
        injectStyles.call(this, context)
      }
      // register component module identifier for async chunk inferrence
      if (context && context._registeredComponents) {
        context._registeredComponents.add(moduleIdentifier)
      }
    }
    // used by ssr in case component is cached and beforeCreate
    // never gets called
    options._ssrRegister = hook
  } else if (injectStyles) {
    hook = shadowMode
      ? function () { injectStyles.call(this, this.$root.$options.shadowRoot) }
      : injectStyles
  }
  • 第二步,样式加载函数作为钩子函数执行
if (hook) {
    console.log('\n\n options: ', options)
    if (options.functional) {
      // for template-only hot-reload because in that case the render fn doesn't
      // go through the normalizer
      options._injectStyles = hook
      // register for functioal component in vue file
      var originalRender = options.render
      options.render = function renderWithStyleInjection (h, context) {
         hook.call(context)
         return originalRender(h, context)
      }
    } else {
      // inject component registration as beforeCreate hook
      var existing = options.beforeCreate
      options.beforeCreate = existing
        ? [].concat(existing, hook)
        : [hook]
    }
  }

生成 loaderString

生成一个 requirestring,用来加载style
// todo

selector,

selectorloaderString 中的第一个 loader,用来选择 .vue 文件的对应代码块
// todo

style-compiler

style-compiler
// todo

vue-style-loader

vue-style-loader 生成动态插入样式的代码
// todo

关于如何自动执行 `createBody`

new Vue( el: '#app')

执行 new Vue( el: '#app') 的最后一步就是:替换 #app 的 dom节点

替换通过 'appendChild' 和 ‘removeChild’ 来实现。

  • appendChild: append 的是 vue 帮我们创建的子树
  • removeChild: remove 的是 原本存在 Dom Tree 的节点

document

我们初始的 document 有一个子节点 body,这个 body 也是一个 Element ( 只是 ref 为 ‘root’,以标志为渲染树的根节点 ),也就是我们在 query 中可以返回 document.body,最后就是 document append 一个子树,因此加上 document.appendChild, document.removeChild 方法

  • document.appendChild: append 的是一个 body,但是 document 应当只有一个 body,所以此时应该是替换操作
appendChild (body) {
   if (body instanceof Element) {
     body.ref = BODY_REF
     this.body = body
     this.render()
   }
 }
  • document.removeChild: remove 了 body 的操作似乎没有意义,直接 return false

在执行 appendChild(body) 时,进行 render 的操作,进行一整颗树的渲染,(注意: 此后的更新是局部更新)

SFC 转化结果怎么转换成实例

weex-loader:

var __vue_exports__, __vue_options__
var __vue_styles__ = []

/* styles */
__vue_styles__.push(require("!!../node_modules/weex-vue-loader/lib/style-loader!../node_modules/weex-vue-loader/lib/style-rewriter?id=data-v-dc790388!../node_modules/weex-vue-loader/lib/selector?type=styles&index=0!./hello.vue")
)

// __vue_styles__ 为一个数组
// 格式为:
// [ 
//   { 
//      className: { 
//         ...style 
//       }, 
//       className: {}
//    },
//   { ... }, ...
// ]

/* script */
__vue_exports__ = require("!!../node_modules/weex-vue-loader/lib/script-loader!../node_modules/weex-vue-loader/lib/selector?type=script&index=0!./hello.vue")

/* template */
var __vue_template__ = require("!!../node_modules/weex-vue-loader/lib/template-compiler?id=data-v-dc790388!../node_modules/weex-vue-loader/lib/selector?type=template&index=0!./hello.vue")
__vue_options__ = __vue_exports__ = __vue_exports__ || {}
if (
  typeof __vue_exports__.default === "object" ||
  typeof __vue_exports__.default === "function"
) {
if (Object.keys(__vue_exports__).some(function (key) { return key !== "default" && key !== "__esModule" })) {console.error("named exports are not supported in *.vue files.")}
__vue_options__ = __vue_exports__ = __vue_exports__.default
}
if (typeof __vue_options__ === "function") {
  __vue_options__ = __vue_options__.options
}
__vue_options__.__file = "D:\\Viola\\webpack-v\\v-r-t\\webpack-test\\src\\hello.vue"
__vue_options__.render = __vue_template__.render
__vue_options__.staticRenderFns = __vue_template__.staticRenderFns
__vue_options__._scopeId = "data-v-8229fd3a"
__vue_options__.style = __vue_options__.style || {}
__vue_styles__.forEach(function (module) {
  for (var name in module) {
    __vue_options__.style[name] = module[name]
  }
})

/*
循环将 所有的 class-style 保持在  __vue_options__.style
格式为:
{
  className: {  ...style  },
  className: {  ...style  }
}

后续可通过 vnode.ctx.$options.style 获取到,但设置 className 时抽取 style 加上 (setStyle)
*/

if (typeof __register_static_styles__ === "function") {
  __register_static_styles__(__vue_options__._scopeId, __vue_styles__)
}

module.exports = __vue_exports__

重要过程:

  • 配置 转换成一个构造器,继承于 Vue,接着作为 new Vue( ) 的参数之一, vnode.componentOptions.Ctor

function createComponentInstanceForVnode()return new vnode.componentOptions.Ctor(options)


weex 的 style 转换

  • Vue-template-compiler 中的 parse 方法将style 部分提出来

  • Weex-style-rewriter

    通过 postCSS 转换为标准css样式表

  • style-loader

    通过 genStyleString 方法转化为 json

    genStyleString 使用的是 weex-styler

  • weex-styler

    • 引用 npm 包 css
    • parse 方法中 css.parse() 转换为了 CSS AST
    • 解析 AST,转化为 style object

Viola-style-loader

Viola-style-loader 的 StyleDescriptior

StyleDescriptior

StyleDescriptior 用来描述一个样式规则的数据结构,目前结构如下(可能后续会改动):

class StyleDescriptor {
  constructor (
    style = {},
    scoped_id = '',
    state = {},
    attrs = {},
    children = []
  ) {
    this.style = style
    this.scoped_id = scoped_id
    this.state = state
    this.attrs = attrs
    this.children = children
  }
}
  • style: 当前这个选择器(如 .container)的样式
  • scoped_id: 组件局域样式 id
  • attrs: 属性选择器
  • state: 伪类
  • children: 子选择器

一个最简单的例子可以表示如下:

 /* scoped_id: data-v-sf023154 */
.viola {
  color: red;
}

.viola[type=number] {
  background-color: white;
}

.viola:hover {
  color: blue
}

StyleDescriptor 表示如下:

{
  viola: {
    style: {
      color: '#ff0000'
    },
    scoped_id: 'data-v-sf023154',
    state: {
      hover: {
        color: 'blue'
      }
    },
    attrs: {
      type: {
        number: {
          backgroundColor: 'white'
        }
      }
    }
  }
}

但是,目前对伪类的样式表示暂时为下面(所以上面说,可能结构会改动):

{
  viola: {
    style: {
      'color': '#ff0000',     // 普通状态
      'color:hover': 'blue'   // hover 状态
    }
}

因为对于伪类的样式表示应该由 native 端来维护,如果由 web 端维护的话,就意味着,web 端需要 native 来通知是否触发伪类,再传递相应的样式改变过去,这期间就多了两次通讯了。

所以, 我觉得应该要用一种形式 (比如上面的示例) 区分开样式,传递给 native ,由 native 进行维护。

在 Vue 中的获取

通过 Viola-style-loader 后,我们将当前组件的样式作为一个 $options 的配置项附在组件上。

// loader inject style

_options._stylesheet = injectStyle

// vue get style

vnode.context.$options._stylesheet

Vue 使用 updateClass 来更新对于 vnodeclass, 那么我们就可以在此时更新的同时,将 class 转换为相应的 style, 然后 setStyle 来更新类样式.

注意:此时还可以进行属性选择器的样式追加

/**
 * 由类名获取样式
 * @return { ...style }
 **/
function getStyle(classList, vnode) {
  let res = {},
    stylesheet = vnode.context.$options._stylesheet
  classList.reduce((res, className) => {
    let styleDescriptor = stylesheet[className]
    if (styleDescriptor) {
      // 融合 类名选择器的样式
      extend(res, styleDescriptor.style)

      // todo attr selector 融合属性选择器的样式
      if (styleDescriptor.attrs) {
        let attrStyle = styleDescriptor.attrs,
          vnodeAttr = vnode.data.attrs
        if (isEmptyObj(vnodeAttr)) return
        for (const k in attrStyle) {
          let vnodeAttrValue = vnodeAttr[k],
            attrStyleCollection = attrStyle[k]
          if (isDef(vnodeAttrValue)) {
            const value = attrStyle[k]
            console.log('有属性样式~~', vnodeAttr[k], value)
          }
        }
      }
    }
  }, res)
  return res
}

Viola-style-loader 的 css 转换

对于样式的转换过程,会调用到以下一系列的 loader,viola-style-loader 是最后一个 loader。

viola-style-loader!style-compiler!sass-loader/lib/loader.js!selector.js

经过前面的 loader ,最终会给 Viola-style-loader 传递的是一份样式表代码:

<!-- 普通类名 -->
.viola[data-v-dc790388] {
  background-color: #4d45be;
  transform: scale(1.5);
}
<!-- 后代选择器 -->
.viola .p[data-v-dc790388] {
    color: blue;
}
<!-- 属性选择 -->
.viola[type=oj][data-v-dc790388] {
    color: red;
}
<!-- 伪类 -->
.viola[data-v-dc790388]:hover {
  width: 300;
}
<!-- 字体 -->
@font-face {
  font-family: "Bitstream Vera Serif Bold";
  src: url("http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf");
}

然后,我们需要对它进行转换,在这里我们使用的是 npm 包 cssgithub 地址). css 可以将普通的样式表解析为一个语法树(style AST)。

// 基本使用如下
var css = require('css')
var AST = css.parse(cssCode)

如我们上面的 css 代码会转换为的 AST 大致如下( 部分省略,具体规则:github 地址 ):

{
  "type": "stylesheet",
  "stylesheet": {
    "rules": [
      {
        "type": "rule",
        "selectors": [
          ".viola[data-v-dc790388]"
        ],
        "declarations": [
          {
            "type": "declaration",
            "property": "background-color",
            "value": "#4d45be",
            "position": {
              "start": {
                "line": 3,
                "column": 3
              },
              "end": {
                "line": 3,
                "column": 28
              }
            }
          },
        ],
        "position": {
          "start": {
            "line": 2,
            "column": 1
          },
          "end": {
            "line": 5,
            "column": 2
          }
        }
      },
      {
        "type": "font-face",
        "declarations": [
          {
            "type": "declaration",
            "property": "font-family",
            "value": "\"Bitstream Vera Serif Bold\"",
            "position": {
              "start": {
                "line": 16,
                "column": 3
              },
              "end": {
                "line": 16,
                "column": 43
              }
            }
          },
          {
            "type": "declaration",
            "property": "src",
            "value": "url(\"http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf\")",
            "position": {
              "start": {
                "line": 17,
                "column": 3
              },
              "end": {
                "line": 17,
                "column": 78
              }
            }
          }
        ],
        "position": {
          "start": {
            "line": 15,
            "column": 1
          },
          "end": {
            "line": 18,
            "column": 2
          }
        }
      }
    ],
    "parsingErrors": []
  }
}

通过上面的例子分析可以得到几个重要的点:

  • typestylesheet, rule, font-face, declaration

    具有不同的 type 来区分节点类型

  • selectors

    选择器数组,但是里面只有一个值,对应选择器(如:.viola[data-v-dc790388]:hover

  • propertyvalue

    对应属性和值

那么我们暂且可以根据这个来进行转换了。

// 定义常量(部分)
const AST_TYPE = {
  STYLESHEET: 'stylesheet',
  RULE: 'rule',
  DECLARATION: 'declaration',
  FONT_FACE: 'font-face',
  IMPORT: 'import',
  KEYFRAME: 'keyframe'
}

// 定义解析正则表达式 (部分)
const REG_EXP = {
  CLASS: /^\./,
  SELECT_TYPE: /^[\.#]/,
  // [data-v-[hash]]
  SCOPE_ID_FIELD: /\[(data-v-[a-z0-9]+)\]/,
  // data-v-[hash]
  SCOPE_ID: /(?:data-v-[a-z0-9]+)$/,
  // selector: .class[attr]:state  $1: class, $2: attr, $3: state
  SELECTOR: /^(?:\.?([A-Za-z0-9_\-]+))(?:\[([A-Za-z0-9=\-]+)\])?(:[a-z:]+)?$/,
  // selector: .class[attr]:state  $1: class, $2: attr, $3: state
  // SELECTOR: /^(\.?[A-Za-z0-9_\-]+)(?:\[([A-Za-z0-9=\-]+)\])?(:[a-z:]+)$/,
  KEY_VALUE: /^([a-zA-Z]+)=([a-zA-Z0-9]+)$/
}

下面是转换的部分代码,目的是转换为用 StyleDescriptior 表示, 后续会改动,暂先做个记录

function walkAST(cssAST) {
  if (!cssAST) return
  if (
    cssAST.type === AST_TYPE.STYLESHEET
    && cssAST.stylesheet 
    && cssAST.stylesheet.rules
  ) {
    let sheet = cssAST.stylesheet // 样式表
    let rules = sheet.rules       // rule array
    let styles = {}               // 收集样式
    rules.forEach((rule) => {
      let styleDescription = {}
      if (rule.type === AST_TYPE.RULE) {
        let declarations = rule.declarations
        
        // selector 是选择器,rule.selectors 为数组
        let selector = Array.isArray(rule.selectors) ? rule.selectors[0] : rule.selectors
        let selectorName
        let selectorsPart = selector.split(' ')
        switch (selectorsPart.length) {
          case 1:
            // selectorName = REG_EXP.CLASS.test(selector) ? selector.slice(1) : selector
            selectorName = selector
            break;
          case 2:
            let _name = selectorsPart[1]
            // selectorName = REG_EXP.CLASS.test(selector) ? selector.slice(1) : selector
            selectorName = _name
          default:
            break;
        }

        let {
          className, scoped_id, attr, state, stateArr
        } = getSelectorData(selectorName)
        
        _c.log('getSelectorData', {
          selectorName,
          className, scoped_id, attr, state, stateArr
        })
        
        let cur = styles[className] || (styles[className] = new StyleDescriptor()), target
        cur.scoped_id = scoped_id
        if (cur && attr[0]) {
          let key = attr[0]
          let _targetAttr = cur.attrs[key] || (cur.attrs[key] = {})
          // target = attr[1]
          //   ? (cur[attr[0]] && cur[attr[0]][attr[1]]) || ((cur[attr[0]] = {}) && (cur[attr[0]][attr[1]] = {}))
          //   : cur[attr[0]] || (cur[attr[0]] = {})
          target = attr[1] 
            ? (_targetAttr[attr[1]] || (_targetAttr[attr[1]] = {}))
            : _targetAttr
        } else {
          target = cur.style
        }
        
        declarations.reduce((result, style) => {
          // todo: property , value filter(transform)
          result[style.property + state] = style.value
          return result
        }, target)
      }
    })
    return styles
  }
}

function getSelectorData(selector) {
  let selectorData = {
    className: null,
    scoped_id: '',
    attr: [],
    state: '',
    stateArr: [],
  }
  selector = selector.replace(REG_EXP.SCOPE_ID_FIELD, (match, $1) => {
    selectorData.scoped_id = $1
    return ''
  })
  
  let matchResult = REG_EXP.SELECTOR.exec(selector)
  if (matchResult) {
    // class
    selectorData.className = matchResult[1]
    let attr = matchResult[2]
    if (attr) {
      // [data-v-hash]
      if (REG_EXP.SCOPE_ID.test(attr)) {
        selectorData.scoped_id = attr
      } else {
        // attr: [key, vaule]
        // ['type', 'number']
        // ['disable']
        selectorData.attr = attr.split('=')
        // isDef(selectorData.attr[2]) || (selectorData.attr[2] = true)
        // selectorData[attrKeyVaule[0]] = isDef(attrKeyVaule[1]) ? attrKeyVaule[1] : true
      } 
    }
    if (matchResult[3]) {
      // state: ':hover:active'
      selectorData.state = matchResult[3]
      // stateArr: ['hover', 'active']
      selectorData.stateArr = selectorData.state.slice(1).split(':')
    }
  }
  return selectorData
}

/**
 * callNative
 * @param module  调用模块,如 dom,jsapi
 * @param action  触发的 action,如 createBody, addElement
 * @param payload 负载参数,对应不同action,参数对象里的格式也是不一样的
 *
 **/
callNative('dom', 'createBody', {...domObj})

总结

  • StyleDescriptior 需要再根据具体的进行 update

    如上面提到的伪类表示。

    用这个结构可以进行 属性选择, 应该也是可以进行 后代选择 的,后续需要评估下有没有这个必要性

  • 样式需要过滤转换

    color: red 应该转换为 color: '#ff0000'

  • 可能可以优化点:

    vue-loader 和 weex-loader 的做法都是抽出样式,然后在 vue 的 runtime 进行融合

    但是不同的是!!

    • vue-loader 抽取后的运行环境是 浏览器,样式的加入是动态插入 style 标签;
    • weex 运行的不是 浏览器,没有 css stylesheet 之类的概念,只能在运行时中做样式融合
    • 不同就在,后一种的处理是不是会比第一种慢上不少,且支持的写法不多??

    有没有可能在 loader 这一层就将样式附加上了??

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.