如何在无框架网站中专业可扩展地管理Routes、Template Engines与Web Components?
无框架Web项目整合实践指南
一、模板引擎与Web Components结合落地
核心思路:用模板引擎做服务端渲染的页面骨架,Web Components封装可复用前端组件,两者通过服务端传递的数据完成衔接,既保留服务端渲染的优势,又实现前端组件的隔离与复用。
实际案例(EJS + 原生Web Components)
1. 服务端模板渲染页面骨架(EJS)
views/layout.ejs - 通用页面布局:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title><%= pageTitle %></title> <!-- 延迟加载Web Components定义脚本 --> <script src="/components/user-card.js" defer></script> </head> <body> <header><%= include('partials/header', { navItems }) %></header> <main> <!-- 从服务端数据库获取的用户数据,通过模板引擎注入到Web Components属性 --> <% users.forEach(user => { %> <user-card name="<%= user.name %>" avatar="<%= user.avatarUrl %>" bio="<%= user.bio %>" ></user-card> <% }) %> </main> <footer><%= include('partials/footer') %></footer> </body> </html>
2. Web Components封装可复用UI
public/components/user-card.js - 自定义用户卡片组件:
class UserCard extends HTMLElement { constructor() { super(); // 用Shadow DOM隔离组件样式,避免全局污染 const shadow = this.attachShadow({ mode: 'open' }); // 从DOM属性读取服务端传递的数据 const name = this.getAttribute('name'); const avatar = this.getAttribute('avatar'); const bio = this.getAttribute('bio'); shadow.innerHTML = ` <style> .card { border: 1px solid #eee; padding: 1rem; border-radius: 8px; max-width: 300px; margin: 1rem 0; } .avatar { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; } .name { font-weight: 600; margin: 0.5rem 0; } .bio { color: #666; font-size: 0.9rem; line-height: 1.4; } </style> <div class="card"> <img class="avatar" src="${avatar}" alt="${name}"> <div class="name">${name}</div> <div class="bio">${bio}</div> </div> `; } // 监听属性变化,实现组件动态更新 static get observedAttributes() { return ['name', 'avatar', 'bio']; } attributeChangedCallback(attrName, oldVal, newVal) { if (oldVal === newVal) return; const targetEl = this.shadowRoot.querySelector( attrName === 'avatar' ? '.avatar' : `.${attrName}` ); if (targetEl) { attrName === 'avatar' ? targetEl.src = newVal : targetEl.textContent = newVal; } } } customElements.define('user-card', UserCard);
3. Node.js路由处理(Express)
app.get('/users', async (req, res) => { // 从数据库查询用户数据 const users = await db.query('SELECT name, avatar_url as avatarUrl, bio FROM users'); // 渲染EJS模板并传递数据 res.render('users', { pageTitle: '用户列表', users, navItems: await db.query('SELECT title, path FROM nav_items') }); });
二、Node.js数据库管理路由的可扩展实现
核心思路:将路由规则(路径、请求方法、处理逻辑、权限等)存储到数据库,启动时加载路由配置并动态注册到Express,实现路由的灵活管理与业务逻辑解耦。
实际案例
1. 数据库路由表设计(MySQL)
CREATE TABLE routes ( id INT AUTO_INCREMENT PRIMARY KEY, path VARCHAR(255) NOT NULL UNIQUE, -- 路由路径,如 '/users/:id' method ENUM('GET', 'POST', 'PUT', 'DELETE') DEFAULT 'GET', handler VARCHAR(100) NOT NULL, -- 对应Controller中的方法名 template VARCHAR(100), -- 对应模板文件(页面路由用) requires_auth BOOLEAN DEFAULT FALSE, -- 是否需要认证 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
2. 路由加载与注册工具
lib/router-loader.js:
const fs = require('fs'); const path = require('path'); // 加载所有业务Controller const loadControllers = () => { const controllers = {}; const controllerDir = path.join(__dirname, '../controllers'); fs.readdirSync(controllerDir).forEach(file => { if (file.endsWith('.js')) { const ctrl = require(path.join(controllerDir, file)); Object.assign(controllers, ctrl); } }); return controllers; }; // 从数据库加载路由并注册到Express const registerRoutes = async (app, db) => { const controllers = loadControllers(); const routes = await db.query('SELECT * FROM routes'); routes.forEach(route => { const { method, path, handler, template, requires_auth } = route; const middlewareChain = []; // 加入认证中间件(如果需要) if (requires_auth) { middlewareChain.push((req, res, next) => { // 替换为实际的认证逻辑(如Session/JWT验证) if (!req.session?.user) return res.redirect('/login'); next(); }); } // 加入业务处理逻辑 middlewareChain.push(async (req, res) => { try { // 调用对应Controller方法获取数据 const data = await controllers[handler](req, db); // 页面路由渲染模板,API路由返回JSON template ? res.render(template, { ...data }) : res.json(data); } catch (err) { console.error(err); res.status(500).send('服务器内部错误'); } }); // 注册路由到Express app[method.toLowerCase()](path, ...middlewareChain); }); }; module.exports = { registerRoutes };
3. 业务Controller示例
controllers/userController.js:
// 获取用户列表 exports.getUsers = async (req, db) => { const users = await db.query('SELECT name, avatar_url as avatarUrl, bio FROM users'); return { pageTitle: '用户列表', users }; }; // 获取单个用户详情 exports.getUserDetail = async (req, db) => { const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]); return { pageTitle: user[0].name, user: user[0] }; };
4. 启动时加载路由
const express = require('express'); const { registerRoutes } = require('./lib/router-loader'); const db = require('./lib/db'); // 数据库连接模块 const app = express(); // 配置模板引擎与静态资源 app.set('view engine', 'ejs'); app.set('views', './views'); app.use(express.static('public')); // 动态注册路由并启动服务 (async () => { await registerRoutes(app, db); app.listen(3000, () => console.log('服务运行在 http://localhost:3000')); })();
三、落地建议与资源方向
- 模板引擎选择:除EJS外,Pug、Handlebars都是成熟选项,核心逻辑一致——服务端传递数据到页面,为Web Components提供属性输入。
- Web Components进阶:重点掌握Shadow DOM样式隔离、自定义元素生命周期、属性/属性绑定三个核心点,无需依赖任何框架即可实现组件化。
- 实战项目推荐:从零搭建个人博客系统——用模板引擎渲染文章列表/详情页,Web Components封装评论区、标签云组件,数据库管理文章分类、页面路由,完全脱离前端框架。
内容的提问来源于stack exchange,提问作者Peter SH
相关产品推荐
相关产品推荐

