基于nodejs服务端web组件化开发(八、核心代码的优化)

63 阅读3分钟

八、核心代码的优化

本节将核心代码整理优化

  • 去除 uuid 依赖包,直接使用累加器可以方便跟踪组件的加载顺序。
  • 进行拆分为主控、编译、包装和模板四个模块
  • 在最终添加代码简单压缩把注释和多余空格换行删除了
hcdr
  src
    compile.js
    compose.js
    template.js
  index.js

编译模块

此模块暴露四个方法分别对模块、样式、脚本和压缩进行处理,返回均是字符串类型

// 处理 template
function template(template, data, id) {
  // 给标签属性加上id的方法
  const parse = (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)] //去重

    // 替换标签加上id
    tags.forEach((tag) => {
      str = str.replace(new RegExp(`\\<${tag}`, "g"), `<${tag} ${id}`)
    })
    return str
  }

  // 兼容异步请求
  let code = template(data || {})
  if (code instanceof Promise) {
    return new Promise((resolve, reject) => {
      code
        .then((str) => {
          resolve(parse(str, id))
        })
        .catch((err) => reject(err))
    })
  } else {
    return parse(code, id)
  }
}

// 处理 css 加上标签属性id
function style(style, css, id, name) {
  let strCss = typeof style == "function" ? style(css || {}) : ""
  strCss = strCss
    .replace(/\s+,/g, `[${id}],`)
    .replace(/\.[\w-]+\s+\{/g, (rule) => {
      return `${rule.replace("{", "").trim()}[${id}] {`
    })
  return `\n/* ${name} start */\n${strCss}\n/* ${name} end */\n`
}

// 处理 js 提取函数体重新组织成闭包,避免参数的污染
// 随便加上名称调试时知晓来处
function script(script, name) {
  if (!script || typeof script !== "function") {
    return script || ""
  }
  let str = script.toString()
  let start = str.indexOf("{")
  let end = str.lastIndexOf("}")
  str = str.substring(start + 1, end).trim()
  return str ? `\n;(function ${name.toLowerCase()}() {\n${str}\n}\n)()\n` : ""
}

// 压缩代码,默认是生产环境下才开启
function zip(code) {
  return process.env.NODE_ENV === "production"
    ? code
        .replace(/<!--[\s\S]*?-->/g, "")
        .replace(/\/\*[\s\S]*?\*\//g, "")
        .replace(/\/\/.*$/gm, "")
        .replace(/\*\*[\s\S]*?\*\//g, "")
        .replace(/\s+/g, " ")
    : code
}

module.exports = { template, style, script, zip }

组装模块

此模块提供了一个方法,对用户定义组件涉及的模板、样式、脚本和定义的配置, 根据全局模板和配置进行字符串的组装成一个完整的 HTML 文档字符串。

// src/compose.js
const fs = require("fs")
const path = require("path")

const TPL_FILE_PATH = path.resolve(process.cwd(), "index.tpl")
const CONFIG_FILE_PATH = path.resolve(process.cwd(), "hcdr.config.js")

const template = `
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    { meta }
    <title>{ title }</title>
    { style } { css }
  </head>
  <body>
    { body } { script } { js }
  </body>
</html>
`
// 获取模板
const htmlTemplate = fs.existsSync(TPL_FILE_PATH)
  ? fs.readFileSync(TPL_FILE_PATH, "utf-8")
  : template

// 获取全局配置
const sysConfig = fs.existsSync(CONFIG_FILE_PATH)
  ? require(CONFIG_FILE_PATH)
  : { metas: [], title: "", styles: [], scripts: [] }

// 合并配置
function merge(config = {}) {
  return {
    meta: [...sysConfig.metas, ...(config.metas || [])]
      .map((item) => `<meta name="${item.name}" content="${item.content}">`)
      .join("\n"),
    title: config.title || sysConfig.title,
    style: [...sysConfig.styles, ...(config.styles || [])]
      .map((item) => `<link rel="stylesheet" href="${item}">`)
      .join("\n"),
    script: [...sysConfig.scripts, ...(config.scripts || [])]
      .map((item) => `<script src="${item}"></script>`)
      .join("\n")
  }
}

// 返回组合后的代码
module.exports = ({ config = {}, strHtml = "", strCss = "", strJs = "" }) => {
  // 替换模板的变量
  const tplReplace = (template, obj) =>
    template.replace(/\{(.*?)\}/g, (_, key) => obj[key.trim()])

  let params = {
    ...merge(config),
    body: strHtml,
    css: `<style>${strCss}</style>`,
    js: `<script>${strJs}</script>`
  }

  let code = tplReplace(htmlTemplate, params)

  return code
}

主控模块

主控模块共暴露四个方法用来构建、定义和使用组件。

  • html,css 组件构建
  • define 定义组件
  • render 生成代码
// index.js
const compile = require("./src/compile")
const compose = require("./src/compose")

// 标签模板方法
function tag(strs, ...data) {
  return strs[0] + data.map((val, index) => `${val}${strs[index + 1]}`).join("")
}

// 组件创建工具方法
const html = tag
const css = tag

// 缓存样式字符串数组和脚本函数数组
let container = { style: [], script: [] }
let sum = 0
// 定义组件方法
function define(name, { template, style, script }) {
  return function component(data, css) {
    // 创建id,直接用累加器
    const id = `data-i${sum++}`

    // 样式添加属性 id 并缓存
    let strCss = compile.style(style, css, id, name)
    container.style.push(strCss)

    // 缓存脚本函数
    let strJs = compile.script(script, name)
    container.script.push(strJs)

    // 模板中标签添加属性 id 并返回
    let code = compile.template(template, data, id)
    return code
  }
}

// 渲染组件的方法
async function render(tpl, data, css, config) {
  // 获取各部件的字符串与当前页面配置进行合并
  let strHtml = await tpl(data || {}, css || {})
  let strCss = container.style.filter((str) => str).join("\n")
  let strJs = container.script.filter((fn) => fn).join("\n")

  let code = compose({ config, strHtml, strCss, strJs })

  //初始化缓存
  container = { style: [], script: [] }
  sum = 0

  // 简单压缩代码
  code = compile.zip(code)

  return code
}

module.exports = { html, css, define, render }

对核心模块的拆分,使得我们服务器端组件化开发工具比较好理解和维护了。

通过后续使用中不断的完善,这种方式应该有很强的生命力,因为不需要学什么新东西,也就是用我们常规的语法用组件化的思想就可以。

目前,反正我用起来很像vuereact的风格,有了组件化开发的味道了,至此我们"基于nodejs服务端web组件化开发"告一段落。

由于本人水平有限,有不当之处还敬请各位老师指正。

(终结)