八、核心代码的优化
本节将核心代码整理优化
- 去除 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 }
对核心模块的拆分,使得我们服务器端组件化开发工具比较好理解和维护了。
通过后续使用中不断的完善,这种方式应该有很强的生命力,因为不需要学什么新东西,也就是用我们常规的语法用组件化的思想就可以。
目前,反正我用起来很像vue和react的风格,有了组件化开发的味道了,至此我们"基于nodejs服务端web组件化开发"告一段落。
由于本人水平有限,有不当之处还敬请各位老师指正。
(终结)