基于nodejs服务端web组件化开发(四、样式隔离)

56 阅读2分钟

四、样式隔离

上节,说到样式问题,我们先定义一个组件 Text。

// Text.js 新增
const { html, css, define } = require("hcdr")

const template = ({ title }) => {
  return html`<span class="text">${title}</span>`
}
const style = params => {
  let { type } = params || {}
  let colors = {
    default: "#606266",
    primary: "#409EFF",
    success: "67C23A",
    info: "909399",
    warning: "E6A23C",
    danger: "F56C6C"
  }
  let color = type && colors[type] ? colors[type] : colors["default"]
  return css`
    .text {
      margin: 0 4px;
      color: ${color};
    }
  `
}

module.exports = define("Text", { template, style })

进行组件 Text 复用

// test3.js 新增
const { html, render, define } = require("hcdr")

const Text = require("./Text")

const App = define("App", {
  template: () => {
    return html`<div id="app">
      ${Text({ title: "Default" })} ${Text(
        { title: "Primary" },
        { type: "primary" }
      )} ${Text({ title: "success" }, { type: "success" })} ${Text(
        { title: "info" },
        { type: "info" }
      )} ${Text({ title: "warning" }, { type: "warning" })} ${Text(
        { title: "danger" },
        { type: "danger" }
      )}
    </div> `
  }
})

const code = render(App)
console.log(code)

测试发现样式正常生成,下列样式

.text {
    margin: 0 4px;
    color: #606266;
  }
  .text {
    margin: 0 4px;
    color: #409EFF;
  }
  .text {
    margin: 0 4px;
    color: 67C23A;
  }
  .text {
    margin: 0 4px;
    color: 909399;
  }
  .text {
    margin: 0 4px;
    color: E6A23C;
  }
  .text {
    margin: 0 4px;
    color: F56C6C;
  }

这样最终的效果只会显示最后一种样式,这不是我们想要的结果,因此需要对样式进行隔离。 这里考虑用vuescope样式隔离方法:在标签和样式上同时添加标识属性。 这里想当然对 define 方法动手了

// hcdr/index.js 修改
function define(name, { template, style, script }) {
  return function component(data, css) {
    // 本来想用时间戳居然会重复...
    // 直接采用 nanoid 生成8位数字和小写字母的字符串
    // npm i nanoid@3 ,该版本有问题
    const id = `data-${uuid()}`

    // 样式添加属性
    let strCss = typeof style == "function" ? style(css || {}) : ""
    // 把样式中含有","或".xxx {" 替换加上id
    strCss = strCss
      .replace(/\s+,/g, `[${id}], `)
      .replace(/\.[\w-]+\s+\{/g, rule => {
        return `${rule.replace("{", "").trim()}[${id}] {`
      })
    container.style.push(strCss)

    // 收集起脚本
    container.script.push(script || "")

    // 标签添加上id
    let code = template(data || {})
    // 取出字符串中标签名,遍历替换所有标签
    code = compileTemplate(code, id)

    return code
  }
}

// 编译模板
function compileTemplate(str, id) {
  let tags = []
  const regex = /<([a-z][a-z0-9]*)\b[^>]*>/gim
  let match
  while ((match = regex.exec(str))) {
    tags.push(match[1])
  }
  tags = [...new Set(tags)]
  tags.forEach(tag => {
    str = str.replace(new RegExp(`\\<${tag}`, "g"), `<${tag} ${id}`)
  })
  return str
}

上面,就是把样式和模板字符串进行替换添加标识

运行一把试试, 结构

<!-- 结构内容 -->
<div data-xhay58qv class="app">
  <span data-5qjwn21h data-xhay58qv class="text">Default</span>
  <span data-yq5n3r12 data-xhay58qv class="text">Primary</span>
  <span data-1yl4w6tt data-xhay58qv class="text">success</span>
  <span data-jmsi06lf data-xhay58qv class="text">info</span>
  <span data-3wo42ee0 data-xhay58qv class="text">warning</span>
  <span data-3q52u3fc data-xhay58qv class="text">danger</span>
</div>

样式

.text[data-5qjwn21h] {
  margin: 0 4px;
  color: #606266;
}
.text[data-yq5n3r12] {
  margin: 0 4px;
  color: #409EFF;
}
.text[data-1yl4w6tt] {
  margin: 0 4px;
  color: #67C23A;
}
.text[data-jmsi06lf] {
  margin: 0 4px;
  color: #909399;
}
.text[data-3wo42ee0] {
  margin: 0 4px;
  color: #E6A23C;
}
.text[data-3q52u3fc] {
  margin: 0 4px;
  color: #F56C6C;
}

可以看出父组件生成的标识会添加到子组件上,样式可以达到了效果。 下节,我们看看异步数据请求存在的问题