HMPL 上的示例应用程序列表
此存储库包含使用 HMPL 模板语言编写的测试应用程序列表。您可以放心地使用它们并进行修改(只需替换图片)。
图库应用程序
一个具有分页功能的花卉图库应用。图片以及应用本身的名称均从服务器上传。
登陆页面
登陆页面的组件完全位于服务器上,因此原始应用程序文件只有几千字节。
大家好!本文将介绍如何创建一个 Gallery 应用。您可以放心地使用此应用并根据需要进行编辑(由于需要许可证,因此只能更改图片)。虽然它功能不多,但我认为它非常适合用作工作示例。
该应用程序是一个小型图像列表,可以导航到其中的页面。界面如下所示:
桌面
移动的
在功能方面,您可以单击“下一步”按钮进入下一页,单击“上一页”按钮可以返回第一页。
此外,如果您单击任意图像,您都可以看到其完整格式:
这是该应用程序的主要功能。它将使用开源模板语言 HMPL 编写。您可以点个星标支持一下,谢谢 ❤️!
此应用程序的主要特性之一是,所有图片(例如标题文本)均来自服务器。也就是说,我们不会在客户端的站点存储库中存储 10 张图片。它们全部来自服务器。我们可以通过以下方法实现这一点:将主要内容的 HTML 存储在服务器上,然后在客户端将其输出到一些单元格中,这样实际上占用的磁盘空间很小。
请记住,当你从远程存储库克隆文件时,如果代码很少,克隆视频或图像可能会花费大量时间。这里也是如此。这是此类应用程序的主要优势之一。
另外,在客户端浏览器上,如果我们考虑应用程序的加载,当用户首次进入网站时,它可能会加载几秒钟。他可以关闭此资源并转到另一个资源,因此从成本角度来看,这在某些情况下可以节省预算。
这种方法是面向服务器的,但不是服务器端渲染,因为组件是在客户端渲染的,机器人不会看到结果。
无论如何,如今确实存在这样一种创建网站的方法,它非常便捷,并且有其优势。如今有很多库实现了类似的功能。HMPL 就是其中之一。
首先,您需要选择编写应用程序的平台。我们所说的平台指的是后端的Express.js和通常的Node.js,而在客户端上,我们将使用一个简单的Webpack程序包。
关于从哪里开始,有很多不同的方法。从服务器开始,或者从客户端开始。在我们的例子中,最好从客户端开始,因为我们知道在服务器上已经生成了图片列表和标题,但如何最好地将它们集成到 DOM 中——这正是我们首先需要解决的问题。
让我们继续查看原始 HTML 文件:
索引.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Gallery App</title>
</head>
<body></body>
</html>
看起来这里什么都没有,是的,你说得对。作为内容的组件将被加载到这个文件中。我们将在.hmpl扩展文件中创建它们,这会稍微扩展 HTML 的功能。
为此,我们将创建一个components文件夹来存储这些文件。我们将通过 JavaScript 将每个文件连接到页面。它们的标记如下:
画廊.hmpl
<div>
<div class="gallery-initial" id="gallery-initial">
{
{
src: "http://localhost:8000/api/images",
method: "POST"
}
}
</div>
<div class="gallery" id="gallery">
{
{
src: "http://localhost:8000/api/images",
after:
"click:.navigation-button",
method: "POST"
}
}
</div>
<div class="pagination">
<button class="navigation-button" data-page="1" id="previous" disabled>
Previous
</button>
<button class="navigation-button" data-page="2" id="next">Next</button>
</div>
<div class="modal" id="modal">
<img
src="https://raw.githubusercontent.com/hmpl-language/media/refs/heads/main/logo.png"
alt=""
/>
</div>
</div>
值得注意的是,这里标记了两个对象,第一个对象在页面加载时触发,第二个对象在点击导航按钮后触发。
标题.hmpl
<h1 id="title">{{ src: "http://localhost:8000/api/title" }}</h1>
在这里,对象将从服务器转换为 HTML。现在,它们应该连接起来。为此,将它们导入到 main.js 中:
import "./index.scss";
import GalleryTemplate from "./components/Gallery/Gallery.hmpl";
import TitleTemplate from "./components/Title/Title.hmpl";
const { response: Title } = TitleTemplate();
const { response: Gallery } = GalleryTemplate(({ request: { event } }) => {
return {
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
page: event ? Number(event.target.getAttribute("data-page")) : 1,
}),
};
});
document.body.append(Title);
document.body.append(Gallery);
const gallery = document.querySelector("#gallery");
const galleryInitial = document.querySelector("#gallery-initial");
const modal = document.querySelector("#modal");
const modalImg = modal.querySelector("img");
const navigationButtons = document.querySelectorAll(".navigation-button");
const setActive = (e) => {
if (e.target.tagName === "IMG") {
modalImg.src = e.target.src;
modal.classList.add("active");
}
};
modal.addEventListener("click", () => {
modal.classList.remove("active");
});
galleryInitial.addEventListener("click", (e) => {
setActive(e);
});
gallery.addEventListener("click", (e) => {
setActive(e);
});
for (let i = 0; i < navigationButtons.length; i++) {
const btn = navigationButtons[i];
btn.addEventListener("click", () => {
if (!galleryInitial.classList.contains("hidden"))
galleryInitial.classList.add("hidden");
btn.setAttribute("disabled", "");
navigationButtons[i === 0 ? 1 : 0].removeAttribute("disabled");
});
}
另外,main.js我们将描述应用程序的逻辑。在这里,我们向服务器发送请求并接收HTML。我们尚未准备好HTML,但会在开发过程中进行准备。由于服务器HTML位于div块中,因此我们可以轻松地将组件添加到DOM中,而无需等待响应。
这里需要在disabled按钮的属性之间添加另一个开关。理想情况下,应该从服务器获取页面数量并专注于此,但由于应用程序本身很小,并且所有常量都已预先确定,因此最好不要添加额外的代码来使其过载。顺便说一下,图像更改本身会在向 API 发出请求后自动进行。
并且还需要在点击时显示图像 - 这是通过在包装标签上挂一个事件并确定如果点击图像,则必须相应地激活该块来完成的。
我们包含的样式如下:
索引.scss
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
background-color: #f4f4f4;
}
h1 {
margin: 20px 0;
color: #333;
}
.gallery-initial.active {
display: flex;
}
.gallery,
.gallery-initial {
display: flex;
gap: 20px;
width: 90%;
max-width: 1000px;
@media (max-width:1023px) {
display: grid;
grid-template-columns: repeat(2, 1fr);
max-width: unset;
justify-content: center;
align-items: center;
width: 100%;
}
}
.hidden {
display: none;
}
.gallery img,
.gallery-initial img {
width: 150px;
height: 100px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: transform 0.2s;
}
.gallery img:hover,
.gallery-initial img:hover {
transform: scale(1.05);
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
justify-content: center;
align-items: center;
}
.modal img {
max-width: 90%;
max-height: 90%;
border-radius: 10px;
}
.modal.active {
display: flex;
}
.pagination {
margin: 20px 0;
display: flex;
gap: 10px;
align-items: center;
justify-content: center;
}
.pagination button {
padding: 10px 20px;
border: none;
background-color: #333;
color: #fff;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
}
.pagination button:hover {
background-color: #555;
}
.pagination button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
风格简约,只是为了让画廊看起来或多或少比较美观。
另外,我通常使用很久以前制作的现成的 webpack 程序集(我必须为该框架制作一个网站),但现在没有必要纠结于每个点上哪个负责哪个webpack.config.js。该文件可以在这里查看。
现在,是时候转到后端了。
创建后端后,我们现在可以冷静地查看客户端,并在此基础上创建所需的路由。假设我创建了一个图库——太棒了,那么我需要下载图片并设置图库中描述的路由。
我们看到需要创建的路线是/api/images:
src: "http://localhost:8000/api/images",
现在,您只需准备响应中发出的 HTML 标记即可。此外,路由的方法将是POST,因为您需要在 中body传递RequestInit所需page的值。让我们设置一个类似的路由:
路线/post.js
const express = require("express");
const expressRouter = express.Router();
const imagePaths = [
"http://localhost:8000/images/img1.jpg",
"http://localhost:8000/images/img2.jpg",
"http://localhost:8000/images/img3.jpg",
"http://localhost:8000/images/img4.jpg",
"http://localhost:8000/images/img5.jpg",
"http://localhost:8000/images/img6.jpg",
"http://localhost:8000/images/img7.jpg",
"http://localhost:8000/images/img8.jpg",
"http://localhost:8000/images/img9.jpg",
"http://localhost:8000/images/img10.jpg",
];
const imagesController = (req, res) => {
const { page } = req.body;
if (!page || isNaN(page)) {
return res.status(400).send("Page number error");
}
const pageNumber = parseInt(page);
const itemsPerPage = 5;
const startIndex = (pageNumber - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
if (startIndex >= imagePaths.length || pageNumber < 1) {
return res.status(404).send("Page not found");
}
const imagesForPage = imagePaths.slice(startIndex, endIndex);
const htmlResponse = `
${imagesForPage
.map((img, index) => `<img src="${img}" alt="Image${index}"/>`)
.join("\n")}
`;
res.send(htmlResponse);
};
expressRouter.post("/images", imagesController);
module.exports = expressRouter;
重要的是,我们要根据页面动态生成图像。另外,值得注意的是,图像的路径不会指向文件夹,而是指向地址本身。在app.js文件中,我们将这样做,以便从文件夹加载图像。
现在,它非常简单。当我们发出GET获取标题的请求时,我们会发送一个简单的 HTML 文件。代码如下:
路线/get.js
const express = require("express");
const expressRouter = express.Router();
const path = require("path");
const titleController = (req, res) => {
res.sendFile(path.join(__dirname, "../components/GET/title.html"));
};
expressRouter.use("/title", titleController);
module.exports = expressRouter;
在 html 文件中,我们只需要一个span包含“Gallery App”文本的 HTML 文件即可。理论上,可以发起POST请求并添加多语言功能,但这又会给应用程序带来负担。我希望它简洁明了,同时兼具美观和实用性。
现在,剩下的就是将所有这些连接到一个文件中并启动我们的服务器。为此,让我们导入文件并创建一个 express 应用程序:
应用程序.js
const express = require("express");
const path = require("path");
const bodyParser = require("body-parser");
const cors = require("cors");
const PORT = 8000;
const app = express();
const getRoutes = require("./routes/get");
const postRoutes = require("./routes/post");
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors({ origin: true, credentials: true }));
const imagesFolder = path.join(__dirname, "./images");
app.use("/images", express.static(imagesFolder));
app.use(express.static(path.join(__dirname, "src")));
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "src/index.html"));
});
app.use("/api", getRoutes);
app.use("/api", postRoutes);
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
我们在这里进行设置,CORS以便可以从另一个端口发送请求localhost,并从文件夹加载图像。具体来说,我们在这里执行此操作:
const imagesFolder = path.join(__dirname, "./images");
app.use("/images", express.static(imagesFolder));
另外,您可以指定PORT服务器,但我指定了默认的8000。您还需要进行配置bodyParser以便于使用 HTML,实际上,只需将路由连接到 API 即可。现在,我想您可以安全地使用该应用程序了!
这个应用程序,即使看似很小,考虑到只实现了最低限度的功能,也显得相当复杂。但这也很酷,因为它有修改的空间、高质量的组件和简洁的现代模块。你可以用 PHP 或其他语言实现后端,对客户端来说,意义不会有太大变化,所以我认为这个应用程序甚至可以作为一个宠物项目。
非常感谢大家阅读这篇文章!虽然我尝试在某些地方缩短叙述,避免涉及细节,但文章篇幅确实很长,但我希望它能对你们有趣且有用!
该项目位于 GitHub 上。您可以在那里更详细地了解代码。未来可能会有其他类似甚至更酷的项目。