jsp-template-compiler
v1.0.4
Published
前端模板引擎,支持构建时编译,生成纯原生 JavaScript 代码
Maintainers
Readme
jsp-template-compiler
前端模板引擎,支持构建时编译,生成纯原生 JavaScript 代码。
特性
- 🚀 构建时编译 - 模板语法在构建时转换为原生 JS,运行时零开销
- 🔄 自动响应式 - 函数执行后自动重新渲染页面
- 📦 零依赖 - 生成的 HTML 不需要引入任何外部库
- 🎯 简洁语法 - 类似 Mustache/EJS 的模板语法
- 👀 监听模式 - 支持文件变化自动编译
- 🧩 组件化开发 - 支持三段式组件(template/style/script)
- 🎨 Scoped 样式 - 组件样式隔离,避免样式污染
安装
# 全局安装
npm install -g jsp-template-compiler
# 或作为项目依赖
npm install jsp-template-compiler --save-dev快速开始
1. 创建模板文件
创建一个 .jsp 文件(例如 index.jsp):
<div>
<h1>{{ title }}</h1>
{% for (let i = 0; i < list.length; i++) { %}
<p>{{ list[i] }}</p>
{% } %}
<button onclick="addItem()">添加</button>
</div>
<script>
let title = 'Hello World'
let list = ['Item 1', 'Item 2', 'Item 3']
const addItem = () => {
list.push('Item ' + (list.length + 1))
// 无需手动调用 render(),会自动注入
}
</script>
<style>
h1 { color: blue; }
</style>2. 编译
# 编译当前目录
jsp-build
# 编译指定目录
jsp-build src
# 输出到指定目录
jsp-build -o dist
# 监听模式
jsp-build -w3. 查看结果
打开生成的 .html 文件即可运行。
命令行选项
用法:
jsp-build [选项] [目录]
选项:
-h, --help 显示帮助信息
-v, --version 显示版本号
-w, --watch 监听文件变化自动编译
-o, --output 指定输出目录
示例:
jsp-build # 编译当前目录
jsp-build src # 编译 src 目录
jsp-build -o dist # 输出到 dist 目录
jsp-build -w # 监听模式
jsp-build src -o dist -w # 组合使用模板语法
变量插值 {{ expression }}
输出表达式的值:
<h1>{{ title }}</h1>
<p>{{ user.name }}</p>
<span>{{ count + 1 }}</span>
<div class="{{ isActive ? 'active' : '' }}">...</div>组件引入 {% include "path" %}
引入其他 JSP 文件作为组件,支持三段式结构和自动聚合:
<!-- 主文件 -->
{% include "components/header" %}
<div>主要内容</div>
{% include "components/footer" %}
<script>
let title = '我的网站'
</script>组件写法(两种都支持):
A. 三段式(推荐):
<!-- components/header.jsp -->
<template>
<header class="header">
<h1>{{ title }}</h1>
</header>
</template>
<style scoped>
.header {
background: #333;
color: white;
padding: 20px;
}
.header h1 {
margin: 0;
}
</style>
<script>
// 组件自己的逻辑(可选)
console.log('Header component loaded')
</script>B. 兼容旧写法:
<!-- components/footer.jsp -->
<footer class="footer">
<p>© 2024 {{ companyName }}</p>
</footer>
<style>
.footer { text-align: center; }
</style>路径规则:
- 相对路径:相对于当前 JSP 文件
- 绝对路径:从项目根目录开始
- 自动添加
.jsp扩展名(如果未指定)
Scoped 样式:
- 使用
<style scoped>实现样式隔离 - 自动为组件根节点添加
data-c="xxx"属性 - CSS 选择器自动添加作用域前缀
- 避免样式污染页面
自动聚合:
- 所有组件的样式自动聚合到
<style id="components-css"> - 所有组件的脚本自动聚合到页面脚本中
- 页面样式单独放在
<style id="page-css"> - 输出结构清晰,便于调试
注意:
- 支持嵌套 include(组件中可以包含其他组件)
- 自动检测循环引用并报错
- 组件样式和脚本会与页面样式和脚本合并
代码块 {% code %}
执行 JavaScript 代码(循环、条件等):
<!-- 循环 -->
{% for (let i = 0; i < list.length; i++) { %}
<p>{{ list[i] }}</p>
{% } %}
<!-- 条件判断 -->
{% if (show) { %}
<div>显示内容</div>
{% } else { %}
<div>隐藏内容</div>
{% } %}
<!-- forEach -->
{% list.forEach(item => { %}
<p>{{ item.name }}</p>
{% }); %}JSP 文件结构
页面文件(主文件)
<!-- HTML 模板部分 -->
<div>
{{ variable }}
{% code %}
{% include "components/header" %}
</div>
<!-- 数据和逻辑部分 -->
<script>
// 数据定义(顶层变量会自动注入到模板作用域)
let list = [...]
// 函数定义(执行后自动重新渲染)
const fetchData = async () => {
list = await fetch('/api').then(r => r.json())
}
</script>
<!-- 样式部分(可选)-->
<style>
.container { ... }
</style>组件文件(三段式,推荐)
<!-- 模板部分 -->
<template>
<div class="component">
<h2>{{ title }}</h2>
<p>{{ content }}</p>
</div>
</template>
<!-- 样式部分(scoped 实现样式隔离)-->
<style scoped>
.component {
padding: 20px;
border: 1px solid #ddd;
}
.component h2 {
color: #333;
}
</style>
<!-- 脚本部分(可选)-->
<script>
// 组件自己的逻辑
console.log('Component loaded')
</script>编译后的输出结构:
<head>
<!-- 组件样式(自动聚合) -->
<style id="components-css">
/* component style (scoped) from: components/header.jsp */
[data-c="abc123"] .header { ... }
[data-c="def456"] .footer { ... }
</style>
<!-- 页面样式 -->
<style id="page-css">
.container { ... }
</style>
</head>
<body>
<div id="app"></div>
<script>
// 组件脚本(自动聚合)
// component script from: components/header.jsp
...
// 页面脚本
// page script
...
// 渲染函数
function render() { ... }
// 初始渲染
render()
</script>
</body>自动响应式
构建工具会自动在所有函数末尾注入 render() 调用:
编译前:
const updateData = () => {
list = newData
}编译后:
const updateData = () => {
list = newData
render() // ← 自动注入
}编程式使用
const { compile } = require('jsp-template-compiler');
const jspContent = `
<div>{{ message }}</div>
<script>
let message = 'Hello'
</script>
`;
const html = compile(jspContent, 'Page Title');
console.log(html);API
compile(jspContent, title)
编译 JSP 内容为 HTML。
jspContent- JSP 文件内容字符串title- 页面标题(可选,默认 'Page')- 返回编译后的 HTML 字符串
compileTemplate(templateStr, varNames)
编译模板字符串为渲染函数代码。
autoInjectRender(scriptCode)
在脚本代码中的函数末尾自动注入 render() 调用。
extractVarNames(scriptCode)
从脚本代码中提取顶层变量名。
示例
组件化开发(三段式 + Scoped 样式)
主文件 (index.jsp):
{% include "components/header" %}
<main class="main">
<h2>欢迎</h2>
<p>{{ message }}</p>
</main>
{% include "components/footer" %}
<script>
let message = 'Hello World'
let title = '我的网站'
let companyName = 'Acme Inc'
</script>
<style>
.main {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
</style>组件 (components/header.jsp) - 三段式:
<template>
<header class="header">
<h1>{{ title }}</h1>
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
</nav>
</header>
</template>
<style scoped>
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
}
.header h1 {
margin: 0;
font-size: 2rem;
}
.header nav a {
color: white;
margin-right: 15px;
}
</style>
<script>
console.log('Header component loaded')
</script>组件 (components/footer.jsp) - 兼容旧写法:
<footer class="footer">
<p>© 2024 {{ companyName }}. All rights reserved.</p>
</footer>
<style>
.footer {
text-align: center;
padding: 20px;
border-top: 1px solid #eee;
}
</style>编译后的效果:
- 组件样式自动添加 scoped 前缀:
[data-c="abc123"] .header { ... } - 组件根节点自动添加
data-c属性:<header data-c="abc123" class="header"> - 所有组件样式聚合到
<style id="components-css"> - 所有组件脚本聚合到页面脚本中
- 页面样式单独在
<style id="page-css">
列表渲染
<ul>
{% for (let item of items) { %}
<li>{{ item.name }} - {{ item.price }}元</li>
{% } %}
</ul>
<script>
let items = [
{ name: '苹果', price: 5 },
{ name: '香蕉', price: 3 }
]
</script>条件渲染
{% if (isLogin) { %}
<p>欢迎,{{ username }}</p>
{% } else { %}
<button onclick="login()">登录</button>
{% } %}
<script>
let isLogin = false
let username = ''
const login = () => {
isLogin = true
username = '张三'
}
</script>异步数据
<button onclick="fetchData()">加载数据</button>
<ul>
{% for (let item of dataList) { %}
<li>{{ item.title }}</li>
{% } %}
</ul>
<script>
let dataList = []
const fetchData = async () => {
const res = await fetch('/api/data')
dataList = await res.json()
}
</script>注意事项
- 变量必须先定义 - 模板中使用的变量必须在
<script>中用let/const/var声明 - 函数自动注入 render - 所有函数执行后都会自动重新渲染
- 顶层变量 - 只有顶层作用域的变量会被注入到模板中,函数内部的局部变量不会
- 组件样式隔离 - 使用
<style scoped>实现样式隔离,避免污染页面 - 组件自动聚合 - 组件的样式和脚本会自动聚合,无需手动管理
- 根节点注入 - Scoped 样式会自动将
data-c注入到组件根节点,如果无法注入会降级为包裹方案
License
MIT
