npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@hx-midway/edge

v1.0.6

Published

midway edge.js 模版渲染器

Downloads

94

Readme

基于 edge.jsmidway 模版引擎

介绍

用 midway 做后端模板渲染, 现在官方提供了ejsNunjucks 两种模板引擎, 这两种模板引擎的对比使用中均有遇到过不同程度的问题,使用体验并不是很好;所以最后决定基于edge引擎做支撑,开发midway 模板渲染组件;Edge 是一个用于 Node.js 的模板引擎。他可以呈现任何文本格式, 无论是 HTML、Markdown、还是其他文本格式。

模板引擎对比

  • Edge 和 ejs 二者均支持 javascript 语法,但是相比于 ejs, edge 提供了更为灵活的 js 语法支撑,使用体验优于 ejs ; 而与 Edge 相比 Nunjucks 语法更趋近于 jinjia2, 这里 python 同学也许会很开心, 但是对于用 js 做全栈开发来说其语法有些地方书写起来同样很别扭;
<!-- ejs -->
<% if(user) { %>
<h1> <%= user.name %> </h1>
<% } %>

<ul>
	<% users.forEach(function(user){ %>
    <li> <%= user.name %> </li>
  <% }) %>
</ul>    
  
<!-- Nunjucks -->

{% if user %}
<h1> {{ suer.name }} </h1>
{% endif %}

<ul>
{% for user in users %}
  <li>{{ user.name }}</li>
{% endfor %}
</ul>

<!-- Edge -->
@if(user)
<h1>{{user.name}}</h1>
@end
<!-- Edge 同时也支持可选链操作 -->
<h1>{{ user?.name }}</h1>

<ul>
@each(user in users)
  <li>{{user?.name||'默认值'}}</li>
@end
</ul>

<!-- Edge 同时也支持 同、异步方法 -->
<h1>{{ getUserName() }}</h1>
<h1>{{ await user.getAge() }}</h1>

模板语法

使用 Edge 不需要了解过多的新概念, 而是依赖于 Javascript 的语言功能。

Edge 的语法主要有两个基本单元:

* 大括号 `{{}} `用于 计算表达式并显示输出值;
* tags 标签 `@section('')` 用于 向模板引擎添加新功能,并且还可以用于添加自定义标签。

大括号

Edge 使用 双大括号 方法来执行 javascript 表达式,您可以在大括号内使用任何有效的 javascript 表达式

{{ user?.name ||'默认用户'}}
{{user?.name?.toUpperCase() }}
{{ (11+6)*3/5 }}
{{ (await getUser())?.name }}

同时双括号会表达式的输出进行转义,以便于保护模板免受 XSS 攻击。

{{ '<script>alert('Hello')</script>'}}
<!-- 输出内容为: -->
&lt;script&gt; alert(&#x60;foo&#x60;) &lt;/script&gt;

当内容受您信任,不希望对表达式进行转义时, 可以使用三个大括号:

{{{ '<script> alert(`Hello`) </script>' }}}
<!-- 输出内容为: -->
<script> alert(`Hello`) </script>

忽略大括号

您可以在括号前面添加 @前缀来指示 Edge 忽略大括号。

Hello @{{username}}
<!-- 输出内容为: -->
Hello {{username}}

Tags 标签

标签是以 @ 开头的表达式,用于向模板添加功能:

注意:标签需出现在行首, 且不可与其他内容混用。

{{-- ✅ 正确 --}}
@if(user)
@end


{{-- ❌ 错误 --}}
Hello @if(user)
@end

标签有不同的划分,用以满足不同的模板需求:

  • 块级标签

    块级标签你是可以插入内容的标签, 且必须与闭合标签 @end 成对出现, 例如:

    {{ -- if 是块级标签 -- }}
    @if(user)
    @end
      
    {{ -- section 是块级标签 -- }}
    @section('body')
    @end
      
  • 内嵌标签

    内嵌标签不可插入内容,且是自闭合的, 无需额外使用闭合标签@end, 例如:

    {{ -- include 是内嵌标签 -- }}
    @include('components/button')
       
    {{ -- layout 是内嵌标签 -- }}
    @layout('layouts/base')
       
  • 子封闭块级标签

    有时候块级标签无需插入内容, 因此为了简化闭合标签的写法, 所以可以进行自闭合,常用的有 @component

    {{ -- 插入内容的 button 组件 -- }}
    @component('components/button',{
    	type: 'submit'
    })
    <i class="iconfont"></i>
    @end
       
    {{ -- 无需插入内容 -- }}
    @!component('components/button',{
    	type: 'submit'
    })
  • 注释

    {{ -- 这是一个注释 -- }}
    <!-- 这是一个注释 -->
  • 末尾换行

    <p>Hello
    @if(user)~
     {{ user?.name }}
    @endif~
    </p>
       
    {{ -- 执行后输出如下,若移除 ~ 责会在末尾产出空白行 -- }}
    <p>Hello 张三</p>
       

数据渲染

 await this.ctx.render('index', {
   name: "张三",
   age: 66
 })
  • 全局变量

可通过 global

import { EdgeView } from "@hx-midway/edge"

class DemoController {
  @Inject()
  view: EdgeView
  
  async demo(){
    // 全局共享 tdk 数据
    this.view.global('tdk', {
      title: "标题",
      keywords: "关键词",
      description: "简介"
    })
    // 全局方法 换行符替换为 br 标签
    this.view.global('nl2br', (text)=>{
      return text?.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1<br />$2')
    })
    
  }
}
<p> {{{ nl2br(test.txt) }}} </p>

<meta name="keywords" content="{{tdk?.keywords}}">
<meta name="description" content="{{tdk?.description}}">
<title>{{tdk?.title}}</title>
  • 内联变量

    可使用 @set 标签来定义内联变量

    @set('title', '这是测试标题')
      
    <title>{{title}}</title>

    内联变量作用域类似于 let, 若是在标签内定义, 则标签外无效

    @each(item in list)
    @set('price', item?.price*item?.discount||0)
    <span>{{price||item.price}}</span>
    @end

条件渲染

您可是使用 @if 标签来写条件语句, 语法类似于 javascript 的 if/else 语句:

@if(user)
<p>{{user?.name}}</p>
@end

@if(user?.fullName)
<p>Hello {{user.fullName}}</p>
@elseif(user?.firstName)
<p>Hello {{user.firstName}}</p>
@else
<p>Hello 张三</p>
@end

同时还有 @unless 标签:

@unless(userInfo?.status)
<p>您的账户状态异常, 请联系客服人员进行处理</p>
@end

循环

@each 标签可用于数据循环,原理类似于 Javascript 的 forEach

@each(user in users)
  <li> {{ user?.name }} </li>
@end

{{ -- 使用索引 -- }}
@each( (user, index) in users)
  <li> {{ user?.name }} {{index+1}}</li>
@end

循环同样适用于对象的遍历:


@each((name, age) in user)
  <li> 姓名:{{ name }} 年龄: {{ age }} </li>
@end

标签呢也可以执行同异步方法:

this.ctx.render('index', {
  users: [
    {
      name: "张三",
      publish: async ()=> [{title: "测试1"}]
    },
    {
      name: "李四",
      publish: async ()=> [{title: "测试1"},{title: "测试2"}]
    }
  ]
})
@each(user in users)
<h2>{{user.name}}</h2>

<ul>
  @each(item in await user.publish())
  <li>{{item.name}}</li>
  @end
</ul>

@end

部分

基本示例

有如下文件结构:

├── view
│   ├── components
│   │   ├── footer.index
│   │   ├── header.index
│   │   └── sidebar.index
│   └── index.heml

index.html中可这样使用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  @include('components/header')
  
  <section>
  	@include('components/sidebar')
    <main></main>
  </section>
  
  @include('components/footer')
</body>
</html>

布局

有以下文件结构

├── views
│   ├── components
│   │   ├── header.index
│   │   └── footer.html
│   ├── layouts
│   │   └── base.html
│   └── index.html

layouts/base.html` 中如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  @include('components/header')
  
  @!section('body')
  
  @include('components/footer')
</body>
</html>

index.html 中如下:

@layout('layouts/base')

@section('body')

<p>Hello 张三</p>

@end

组件

创建组件:

{{ -- components/button.html -- }}

<button type="{{ type }}">
	{{ text }}
</button>

{{ -- demo.html -- }}
@!component('components/button', {
	type: 'primary',
	text: '确定'
})

@component 标签共接收两个参数, 第一个是组件路径, 第二个则是要传递的 props

注意:组件无法访问父模板的状态, 即 父模板 使用@set 标签创建的变量, 子组件无法获取并修改

$props

同时你也可以使用 $props 来实现传递的属性转化:

并且提供了 serializeExcept serializeOnly serialize 等方法:

{{ -- components/button.html -- }}
<button {{ $props.serializeExcept(['text']) }}>
  {{ text }}
</button>

{{-- demo.html --}}
@!component('components/button', {
	type: 'primary',
	text: '确定',
	class: 'flex items-center justify-center bg-primary rounded-2'
})

插槽 $slots

{{ -- components/button.html -- }}
<button type="{{ type }}">
  {{{ await $slots.main() }}}
</button>

{{-- demo.html --}}
@component('components/button')
	<i class="fa-icon-lock" />
	<span>登录</span>
@end

命名插槽:

{{ -- components/dialog.html -- }}
<div>
  <header>
    {{{ await $slots.header() }}}
  </header>

  <main>
    {{{ await $slots.main() }}}
  </main>

  <footer>
    @if($slots.footer)
      {{{ await $slots.footer() }}}
    @else
      Default footer
    @end
  </footer>
</div>

{{ -- index.html -- }}
@component('components/dialog')
	@slot('header')
    <h1> 提示 </h1>
  @end

	@slot('main')
    <div>
      <p>此操作不可逆, 是否继续?</p>
    </div>
  @end

	@slot('actions')
    <div class="flex">
      <button>取消</button>
      <button>确定</button>
    </div>
  @end

@end

插槽也可提供数据输出:

{{ -- components/item.html -- }}
<div>
  {{{ await $slots.main({ name, age }) }}}
</div>

{{ -- index.html -- }}
@each(user in users)

  @component('components/item')
  	@slot('main', scope)
			<h2>{{scope?.name}}</h2>
			<p>{{scope?.age}}</p>
		@end
  @end

@end

@inject 变量注入, 组件允许使用@inject 将变量共享给组件数

{{ -- components/button -- }}
@set('conuter', {value:0})
@inject({counter})

<button>
  {{{ await $slots.main() }}}
</button>

{{ -- index.html -- }}
@component('components/button')
<p> {{ $context.counter.value }}</p>

<!-- 修改变量 -->
@set($context, 'counter.value', $context.counter.value+1)

<p>{{ $context.counter.value }}</p>

@end

组件简化写法:

{{ -- components/button.html -- }}
<button type="{{ type }}">
	{{ text }}
</button>


{{ -- demo.html -- }}
@!button( {
	type: 'primary',
	text: '确定'
})