Comparing version 2.1.0-rc to 3.0.0
{ | ||
"name": "etpl", | ||
"version": "2.1.0-rc", | ||
"version": "3.0.0", | ||
"contributors": [ | ||
@@ -12,3 +12,3 @@ { "name": "erik", "email": "errorrik@gmail.com" }, | ||
"repository": "git://github.com/ecomfe/etpl", | ||
"description": "ETPL是一个灵活、具有强大复用能力的高性能Javascript模板引擎,适用于WEB前端应用中视图的生成,特别是SPA(Single Page APP)类型的应用。", | ||
"description": "ETPL是一个强复用、灵活、高性能的JavaScript模板引擎,适用于浏览器端或Node环境中视图的生成。", | ||
"scripts": { | ||
@@ -18,4 +18,4 @@ "test": "jasmine-node test/spec" | ||
"devDependencies": { | ||
"jasmine-node": "~1.14.2" | ||
"jasmine-node": "1.14.2" | ||
} | ||
} |
630
README.md
@@ -5,653 +5,99 @@ # ETPL (Enterprise Template) | ||
ETPL是一个灵活、具有强大复用能力的高性能的模板引擎,适用于WEB前端应用中视图的生成,特别是SPA(Single Page APP)类型的应用。 | ||
ETPL是一个强复用、灵活、高性能的JavaScript模板引擎,适用于浏览器端或Node环境中视图的生成。 | ||
- [开始](#start) | ||
- [了解ETPL的语法](#syntax) | ||
- [基础](#基础) | ||
- [语法形式](#语法形式) | ||
- [自动结束](#自动结束) | ||
- [target](#target) | ||
- [变量声明](#变量声明) | ||
- [变量替换](#变量替换) | ||
- [内容块过滤](#内容块过滤) | ||
- [模板复用](#模板复用) | ||
- [import](#import) | ||
- [母版](#母版) | ||
- [use](#use) | ||
- [分支与循环](#分支与循环) | ||
- [if](#if) | ||
- [for](#for) | ||
- [浏览ETPL的API](#api) | ||
- [methods](#methods) | ||
- [classes](#classes) | ||
- [与ER中模板引擎的兼容性](#compatibility) | ||
## Start | ||
ETPL可以在`CommonJS/AMD`的模块定义环境中使用,也可以直接在页面下通过`<script src=`的方式引用。`CommonJS/AMD`环境下需要通过如下代码得到ETPL的模块。 | ||
ETpl可以在`CommonJS/AMD`的模块定义环境中使用,也能直接在页面下通过`script`标签引用。 | ||
```javascript | ||
var etpl = require( 'etpl' ); | ||
``` | ||
得到ETPL模块对象后,首先对模板源代码进行编译,就能够得到模板编译后的`function`。 | ||
### 浏览器环境 | ||
```javascript | ||
var render = etpl.compile( 'Hello ${name}!' ); | ||
``` | ||
直接通过script标签引用,你可以获得一个全局的`etpl`变量 | ||
然后执行这个`function`,传入数据对象,就能得到模板执行的结果了。 | ||
```javascript | ||
var text = render( {name: 'etpl'} ); | ||
``` | ||
编写模板和数据前,如果对执行结果有疑虑,就去ETPL的[homepage](http://ecomfe.github.io/etpl/)试试看吧。 | ||
## Syntax | ||
### 基础 | ||
#### 语法形式 | ||
ETPL的指令标签默认为HTML注释的形式,在指令标签内允许声明 `指令起始`、`指令结束`和`注释`。 | ||
`指令起始`的语法形式为: *<!-- command-name: command-value -->*。其中, *command-value* 的具体语法形式详情请参见各指令相关章节。 | ||
```html | ||
<!-- target: targetName --> | ||
<!-- if: ${number} > 0 --> | ||
<!-- for: ${persons} as ${person}, ${index} --> | ||
<script src="etpl.js"></script> | ||
``` | ||
`指令结束`的语法形式为: *<!-- /command-name -->*。 | ||
在AMD环境的模块定义时,你可以通过`同步require`获得ETpl模块 | ||
```html | ||
<!-- /if --> | ||
<!-- /for --> | ||
<!-- /target --> | ||
``` | ||
`注释`的语法形式为: *<!-- //message -->*,注释指令在render时将不输出。 | ||
```html | ||
<!-- // just add some message --> | ||
``` | ||
如果不期望使用HTML注释形式的指令标签,可以通过config API可以配置指令标签的形式: | ||
```javascript | ||
etpl.config({ | ||
commandOpen: '<%', | ||
commandClose: '%>' | ||
define(function (require) { | ||
var etpl = require('etpl'); | ||
}); | ||
/* | ||
配置指令标签的形式为“<% ... %>”,然后指令标签可以像下面一样声明: | ||
<% if: ${number} > 0 %> | ||
greater than zero | ||
<% /if %> | ||
*/ | ||
``` | ||
#### 自动结束 | ||
在AMD环境,你也可以通过`异步require`获得ETpl模块 | ||
为了减少开发者的工作量,部分指令标签支持`自动结束`,模板开发者无需手工编写`指令结束`。比如:当遇见target指令起始时,ETPL自动认为上一个target已经结束。 | ||
具体指令的`自动结束`支持,请参考相应指令相关章节。 | ||
#### target | ||
`target`是ETPL的基本单元,其含义是 **一个模版片段** 。`target`可用于render,也可用于被其他`target`所import或use。 | ||
如果仅仅编写的是一个模板片段,可以省略`target`的声明。这样的编写方式与其他模板引擎类似,ETPL将默认生成匿名target,但模板片段将不可复用(不可被import或use,不可指定母版)。 | ||
匿名target应位于模板源码起始。下面例子中,位于其他target后的模板片段`Bye`将没有任何作用。 | ||
``` | ||
<!-- use: hello(name=${name}) --> | ||
<!-- target: hello --> | ||
Hello ${name}! | ||
<!-- /target --> | ||
Bye | ||
``` | ||
##### 语法 | ||
target的语法形式为: | ||
target: target-name | ||
target: target-name(master=master-name) | ||
target声明可以为其指定相应的母版。母版功能请参考模板复用章节。 | ||
##### 自动结束 | ||
target支持自动结束,当遇见 *target* 或 *master* 时自动结束。 | ||
##### 示例 | ||
```html | ||
<!-- target: hello --> | ||
Hello <strong>ETPL</strong>! | ||
<!-- target: bye --> | ||
Bye <strong>ETPL</strong>! | ||
``` | ||
#### 变量声明 | ||
通过`var`指令可以在模板内部声明一个变量。声明的变量在整个`target`内有效。 | ||
##### 语法 | ||
var的语法形式为: | ||
var: var-name=expression | ||
`expression`中可以使用静态或动态数据。 | ||
##### 示例 | ||
```html | ||
<!-- var: age = 18 --> | ||
<!-- var: age = ${person.age} --> | ||
<!-- var: name = 'etpl' --> | ||
``` | ||
##### 自动结束 | ||
var无需编写`指令结束`,其将在`指令起始`后立即自动结束。 | ||
#### 变量替换 | ||
绝大多数模板引擎都支持变量替换功能。ETPL变量替换的语法为: | ||
${variable-name} | ||
${variable-name|filter-name} | ||
${variable-name|filter-name(arguments)} | ||
${variable-name|filter1|filter2(arguments)|filter3|...} | ||
variable-name支持`.`形式的property access。 | ||
编写模板时可以指定filter,默认使用html filter进行HTML转义。如果想要保留变量的原形式,需要手工指定使用名称为raw的filter,或者通过config API配置引擎的defaultFilter参数。 | ||
${myVariable|raw} | ||
```javascript | ||
etpl.config({ | ||
defaultFilter: '' | ||
require([ 'etpl' ], function (etpl) { | ||
}); | ||
``` | ||
ETPL默认支持3种filters,可以通过引擎的`addFilter`方法自定义扩展filter。 | ||
*在AMD环境下,请确保你的require.config配置能够让Loader找到ETpl模块* | ||
- html: html转义 | ||
- url: url转义 | ||
- raw: 不进行任何转义 | ||
### Node.JS环境 | ||
你可以通过`npm`来安装ETpl | ||
变量替换支持多filter处理。filter之间以类似管道的方式,前一个filter的输出做为后一个filter的输入,向后传递。 | ||
```html | ||
${myVariable|html|url} | ||
``` | ||
filter支持参数传递,参数可以使用动态数据。 | ||
```html | ||
<!-- // 假设存在扩展filter: comma --> | ||
${myVariable|comma(3)} | ||
${myVariable|comma(${commaLength})} | ||
${myVariable|comma(${commaLength}+1)} | ||
$ npm install etpl | ||
``` | ||
在变量替换中,引擎会默认将数据toString后传递给filter,以保证filter输入输出的类型一致性。如果filter期望接受的是原始数据,模板开发者需要通过前缀的`*`指定。 | ||
安装完成后,你就可以通过`require`获得一个ETpl模块,正常地使用它 | ||
```html | ||
<!-- // 假设存在扩展filter: dateFormat --> | ||
${*myDate|dateFormat('yyyy-MM-dd')} | ||
``` | ||
#### 内容块过滤 | ||
除了在变量替换中可以使用filter进行处理,ETPL还可以通过`filter`指令,使用指定的filter对一个模板内容块进行过滤处理。 | ||
##### 语法 | ||
filter的语法形式为: | ||
filter: filter-name | ||
filter: filter-name(arguments) | ||
##### 示例 | ||
下面的例子假定使用者实现了一个markdown的filter | ||
```html | ||
<!-- filter: markdown(${useExtra}, true) --> | ||
## markdown document | ||
This is the content, also I can use `${variables}` | ||
<!-- /filter --> | ||
``` | ||
##### 自动结束 | ||
filter指令不支持自动结束,必须手工编写`指令结束`。 | ||
```html | ||
<!-- /filter --> | ||
``` | ||
### 模板复用 | ||
ETPL支持多种形式的模板复用方式,帮助模板开发者减少模板编写的重复劳动和维护成本。 | ||
#### import | ||
通过import指令,可以在当前位置插入指定target的源码。 | ||
##### 语法 | ||
import的语法形式为: | ||
import: target-name | ||
##### 示例 | ||
```html | ||
<!-- target: hello --> | ||
Hello <strong>${name}</strong>! | ||
<!-- target: main --> | ||
<div class="main"><!-- import: hello --></div> | ||
``` | ||
##### 自动结束 | ||
import无需编写`指令结束`,其将在`指令起始`后立即自动结束。 | ||
#### 母版 | ||
通过`master`指令可以声明一个母版,母版中通过`contentplaceholder`指令声明可被替换的部分。 | ||
`target`声明时通过 **master=master-name** 指定一个母版,就可以继承于这个母版的片段,并且通过`content`指令,替换母版中`contentplaceholder`指令声明部分的内容。指定母版的target中只允许包含`content`指令声明的片段。 | ||
母版功能支持多层母版,`master`声明时也可以通过 **master=master-name** 指定一个母版。母板中的`contentplaceholder`不会再传递下去,即`contentplaceholder`只在一层有效。 | ||
##### 语法 | ||
master的语法形式为: | ||
master: master-name | ||
master: master-name(master=master-name) | ||
contentplaceholder的语法形式为: | ||
contentplaceholder: content-name | ||
content的语法形式为: | ||
content: content-name | ||
##### 示例 | ||
```html | ||
<!-- master: myMaster --> | ||
<div class="title"><!-- contentplaceholder: title -->title<!-- /contentplaceholder --></div> | ||
<div class="main"><!-- contentplaceholder: main --></div> | ||
<!-- master: myMaster-has-sidebar(master=myMaster) --> | ||
<!-- content: title --> | ||
title for has sidebar | ||
<!-- content: main --> | ||
<div class="sidebar"><!-- contentplaceholder: sidebar --></div> | ||
<div class="article"><!-- contentplaceholder: article --></div> | ||
<!-- target: myTarget(master=myMaster) --> | ||
<!-- content: title --> | ||
Building WebKit from Xcode | ||
<!-- content: main --> | ||
<p>To build from within Xcode, you can use the WebKit workspace. </p> | ||
<!-- target: myTarget-has-sidebar(master=myMaster-has-sidebar) --> | ||
<!-- content: sidebar --> | ||
<ul class="navigator">...</ul> | ||
<!-- content: article --> | ||
<p>To build from within Xcode, you can use the WebKit workspace. </p> | ||
``` | ||
##### 自动结束 | ||
master支持自动结束,当遇见 *target* 或 *master* 时自动结束。 | ||
contentplaceholder支持自动结束,当遇见 *contentplaceholder* 或 *target* 或 *master* 时,在`指令标签起始`后自动结束。 | ||
content支持自动结束,当遇见 *content* 或 *target* 或 *master* 时自动结束。 | ||
#### use | ||
通过`use`指令,可以调用指定`target`,在当前位置插入其render后的结果。允许使用静态或动态数据指定数据项。 | ||
##### 语法 | ||
use的语法形式为: | ||
use: target-name | ||
use: target-name(data-name=expression, data-name=expression) | ||
##### 示例 | ||
```html | ||
<!-- target: info --> | ||
name: ${name} | ||
<!-- if: ${email} --> | ||
email: ${email} | ||
<!-- /if --> | ||
<!-- target: main --> | ||
<div class="main"><!-- use: info(name=${person.name}, email=${person.email}) --></div> | ||
``` | ||
##### 自动结束 | ||
use无需编写`指令结束`,其将在`指令起始`后立即自动结束。 | ||
### 分支与循环 | ||
#### if | ||
ETPL提供了分支的支持,相关指令有`if`、`elif`、`else`。 | ||
##### 语法 | ||
if的语法形式为: | ||
if: conditional-expression | ||
elif的语法形式为: | ||
elif: conditional-expression | ||
else的语法形式为: | ||
else | ||
conditional-expression中可以使用动态数据,通过`${variable}`的形式,可以使用模板render的data。`${variable}`支持`.`的property accessor。 | ||
##### 自动结束 | ||
if指令不支持自动结束,必须手工编写指令结束`<!-- /if -->`。 | ||
##### 示例 | ||
```html | ||
<!-- if: ${number} > 0 --> | ||
larger than zero | ||
<!-- elif: ${number} == 0 --> | ||
zero | ||
<!-- else --> | ||
invalid | ||
<!-- /if --> | ||
``` | ||
##### 自动结束 | ||
if指令不支持自动结束,必须手工编写`指令结束`。 | ||
```html | ||
<!-- /if --> | ||
``` | ||
#### for | ||
通过for指令的支持,可以实现对Array和Object的遍历。Array为正序遍历,Object为不保证顺序的forin。 | ||
##### 语法 | ||
for的语法形式为: | ||
for: ${variable} as ${item-variable} | ||
for: ${variable} as ${item-variable}, ${index-variable} | ||
for: ${variable} as ${item-variable}, ${key-variable} | ||
其中,`${variable}`为想要遍历的对象,支持`.`形式的property access。在遍历过程中,声明的`${item-variable}`和`${index-variable}`,分别代表数据项和索引(遍历Object时为键名)。 | ||
##### 示例 | ||
```html | ||
<ul> | ||
<!-- for: ${persons} as ${person}, ${index} --> | ||
<li>${index}: ${person.name} | ||
<!-- /for --> | ||
</ul> | ||
``` | ||
##### 自动结束 | ||
for指令不支持自动结束,必须手工编写`指令结束`。 | ||
```html | ||
<!-- /for --> | ||
``` | ||
## API | ||
### methods | ||
ETPL初始化时自动创建一个默认的引擎实例,并将其暴露。大多数应用场景可直接使用默认的引擎实例。 | ||
```javascript | ||
var etpl = require( 'etpl' ); | ||
var etpl = require('etpl'); | ||
``` | ||
##### {void} addFilter( {string}name, {function({string}, {...*}):string}filter ) | ||
### 使用 | ||
为默认引擎添加过滤器。过滤函数的第一个参数为过滤源字符串,其后的参数可由模板开发者传入。过滤函数必须返回string。 | ||
使用ETPL模块,对模板源代码进行编译,会能得到编译后的function | ||
- `{string}`name - 过滤器名称 | ||
- `{Function}`filter - 过滤函数 | ||
```javascript | ||
etpl.addFilter( 'markdown', function ( source, useExtra ) { | ||
// ...... | ||
} ); | ||
var render = etpl.compile('Hello ${name}!'); | ||
``` | ||
##### {Function} compile( {string}source ) | ||
执行这个function,传入数据对象,就能得到模板执行的结果了 | ||
使用默认引擎编译模板。返回第一个target编译后的renderer函数。 | ||
- `{string}`source - 模板源代码 | ||
```javascript | ||
var helloRenderer = etpl.compile( 'Hello ${name}!' ); | ||
helloRenderer( {name: 'ETPL'} ); // Hello ETPL! | ||
var text = render({ name: 'etpl' }); | ||
``` | ||
##### {void} config( {Object}options ) | ||
查看更多例子,或者对模板渲染结果有疑虑,就去ETPL的[example](http://ecomfe.github.io/etpl/example.html)看看吧。 | ||
对默认引擎进行配置,配置参数将合并到引擎现有的参数中。 | ||
- `{Object}`options - 配置参数对象 | ||
- `{string}`options.commandOpen - 命令语法起始串,默认值为 *<!--* | ||
- `{string}`options.commandClose - 命令语法结束串,默认值为 *-->* | ||
- `{string}`options.defaultFilter - 默认变量替换的filter,默认值为 *html* | ||
- `{boolean}`options.strip - 是否清除命令标签前后的空白字符,默认值为 *false* | ||
- `{string}`options.namingConflict - target或master名字冲突时的处理策略,值可以是`error` | `ignore` | `override`,分别代表`抛出错误`、`保留现有目标,忽略新目标`、`覆盖现有目标`。默认值为 *error* | ||
## Documents | ||
```javascript | ||
etplEngine.config( { | ||
defaultFilter: '' | ||
} ); | ||
``` | ||
通过文档,你可以更详细地了解ETpl的语法格式、使用方法、API等内容。 | ||
##### {string} get( {string}name ) | ||
- [模板语法](doc/syntax.md) | ||
- [API](doc/api.md) | ||
- [配置参数](doc/config.md) | ||
从默认引擎中,根据target名称获取模板内容。 | ||
- `{string}`name - target名称 | ||
## Compatibility | ||
```javascript | ||
etpl.compile( '<!-- target: hello -->Hello ${name}!' ); | ||
etpl.get( 'hello' ); // Hello ${name}! | ||
``` | ||
### ETpl3的新语法 | ||
我们认为,当前流行的通过`block`来表达模板继承中的变化,是更好的表达方式。所以在ETpl3中,我们优化了母版的语法,删除了`master`、`contentplacehoder`、`content`标签,引入了`block`标签。 | ||
对于ETpl2的使用者,我们提供一个[etpl2to3](https://github.com/ecomfe/etpl2to3)工具,能够帮助你平滑地将ETpl2的模板翻译成ETpl3。 | ||
##### {Function} getRenderer( {string}name ) | ||
从默认引擎中,根据target名称获取编译后的renderer函数。 | ||
### get | ||
- `{string}`name - target名称 | ||
ETpl2中,为了前向兼容,Engine的`get`方法可以根据target名称获取模板内容。 | ||
```javascript | ||
etpl.compile( '<!-- target: hello -->Hello ${name}!' ); | ||
var helloRenderer = etpl.getRenderer( 'hello' ); | ||
helloRenderer( {name: 'ETPL'} ); // Hello ETPL! | ||
``` | ||
ETpl3不再支持该方法,所有的模板都通过render来使用: | ||
##### {Function} parse( {string}source ) | ||
- 直接使用engine实例的render方法 | ||
- 调用renderer function | ||
同`compile`方法。该方法的存在是为了兼容老版本的模板引擎api,不建议使用。 | ||
如果仍需要该功能,说明你正在维护一个遗留系统,并且没有很频繁的升级需求。请继续使用ETpl2。 | ||
##### {string} render( {string}name, {Object}data ) | ||
使用默认引擎执行模板渲染,返回渲染后的字符串。 | ||
- `{string}`name - target名称 | ||
- `{Object}`data - 模板数据。可以是plain object,也可以是带有 **{string}get({string}name)** 方法的对象 | ||
```javascript | ||
etpl.compile( '<!-- target: hello -->Hello ${name}!' ); | ||
etpl.render( 'hello', {name: 'ETPL'} ); // Hello ETPL! | ||
``` | ||
### classes | ||
#### Engine | ||
*不同的引擎实例可有效避免target命名冲突的问题。* | ||
##### 初始化 | ||
下面的代码可以初始化一个新的引擎实例。 | ||
```javascript | ||
var etpl = require( 'etpl' ); | ||
var etplEngine = new etpl.Engine(); | ||
``` | ||
引擎实例的初始化允许传入引擎参数。支持的引擎参数见下面的`config`方法。 | ||
```javascript | ||
var etpl = require( 'etpl' ); | ||
var etplEngine = new etpl.Engine({ | ||
commandOpen: '<%', | ||
commandClose: '%>' | ||
}); | ||
``` | ||
##### {void} addFilter( {string}name, {function({string}, {...*}):string}filter ) | ||
添加过滤器。过滤函数的第一个参数为过滤源字符串,其后的参数可由模板开发者传入。过滤函数必须返回string。 | ||
- `{string}`name - 过滤器名称 | ||
- `{Function}`filter - 过滤函数 | ||
```javascript | ||
etplEngine.addFilter( 'markdown', function ( source, useExtra ) { | ||
// ...... | ||
} ); | ||
``` | ||
##### {Function} compile( {string}source ) | ||
编译模板。返回第一个target编译后的renderer函数。 | ||
- `{string}`source - 模板源代码 | ||
```javascript | ||
var helloRenderer = etplEngine.compile( 'Hello ${name}!' ); | ||
helloRenderer( {name: 'ETPL'} ); // Hello ETPL! | ||
``` | ||
##### {void} config( {Object}options ) | ||
对引擎进行配置,配置参数将合并到引擎现有的参数中。 | ||
- `{Object}`options - 配置参数对象 | ||
- `{string}`options.commandOpen - 命令语法起始串,默认值为 *<!--* | ||
- `{string}`options.commandClose - 命令语法结束串,默认值为 *-->* | ||
- `{string}`options.defaultFilter - 默认变量替换的filter,默认值为 *html* | ||
- `{boolean}`options.strip - 是否清除命令标签前后的空白字符,默认值为 *false* | ||
- `{string}`options.namingConflict - target或master名字冲突时的处理策略,值可以是`error` | `ignore` | `override`,分别代表`抛出错误`、`保留现有目标,忽略新目标`、`覆盖现有目标`。默认值为 *error* | ||
```javascript | ||
etplEngine.config( { | ||
defaultFilter: '' | ||
} ); | ||
``` | ||
##### {string} get( {string}name ) | ||
根据target名称获取模板内容。 | ||
- `{string}`name - target名称 | ||
```javascript | ||
etplEngine.compile( '<!-- target: hello -->Hello ${name}!' ); | ||
etplEngine.get( 'hello' ); // Hello ${name}! | ||
``` | ||
##### {Function} getRenderer( {string}name ) | ||
根据target名称获取编译后的renderer函数。 | ||
- `{string}`name - target名称 | ||
```javascript | ||
etplEngine.compile( '<!-- target: hello -->Hello ${name}!' ); | ||
var helloRenderer = etplEngine.getRenderer( 'hello' ); | ||
helloRenderer( {name: 'ETPL'} ); // Hello ETPL! | ||
``` | ||
##### {string} render( {string}name, {Object}data ) | ||
执行模板渲染,返回渲染后的字符串。 | ||
- `{string}`name - target名称 | ||
- `{Object}`data - 模板数据。可以是plain object,也可以是带有 **{string}get({string}name)** 方法的对象 | ||
```javascript | ||
etplEngine.compile( '<!-- target: hello -->Hello ${name}!' ); | ||
etplEngine.render( 'hello', {name: 'ETPL'} ); // Hello ETPL! | ||
``` | ||
## Compatibility | ||
ETPL的前身是[ER框架](https://github.com/ecomfe/er)自带的简易模板引擎,其基本与前身保持兼容。但由于一些考虑因素,存在以下一些不兼容的地方。 | ||
### merge | ||
出于代码体积和使用频度的考虑,ETPL删除了`merge`API。如果想要该API,请在自己的应用中加入如下代码: | ||
ETpl的前身是[ER框架](https://github.com/ecomfe/er)自带的简易模板引擎,其基本与前身保持兼容。但出于代码体积和使用频度的考虑,ETpl删除了`merge`API。如果想要该API,请在自己的应用中加入如下代码: | ||
@@ -658,0 +104,0 @@ ```javascript |
1449
src/main.js
/** | ||
* ETPL (Enterprise Template) | ||
* Copyright 2013 Baidu Inc. All rights reserved. | ||
* | ||
* | ||
* @file 模板引擎 | ||
@@ -10,4 +10,2 @@ * @author errorrik(errorrik@gmail.com) | ||
// 有的正则比较长,所以特别放开一些限制 | ||
/* jshint maxdepth: 10, unused: false, white: false */ | ||
@@ -19,3 +17,3 @@ // HACK: 可见的重复代码未抽取成function和var是为了gzip size,吐槽的一边去 | ||
* 对象属性拷贝 | ||
* | ||
* | ||
* @inner | ||
@@ -26,6 +24,6 @@ * @param {Object} target 目标对象 | ||
*/ | ||
function extend( target, source ) { | ||
for ( var key in source ) { | ||
if ( source.hasOwnProperty( key ) ) { | ||
target[ key ] = source[ key ]; | ||
function extend(target, source) { | ||
for (var key in source) { | ||
if (source.hasOwnProperty(key)) { | ||
target[key] = source[key]; | ||
} | ||
@@ -54,4 +52,4 @@ } | ||
*/ | ||
push: function ( elem ) { | ||
this.raw[ this.length++ ] = elem; | ||
push: function (elem) { | ||
this.raw[this.length++] = elem; | ||
}, | ||
@@ -65,4 +63,4 @@ | ||
pop: function () { | ||
if ( this.length > 0 ) { | ||
var elem = this.raw[ --this.length ]; | ||
if (this.length > 0) { | ||
var elem = this.raw[--this.length]; | ||
this.raw.length = this.length; | ||
@@ -79,3 +77,3 @@ return elem; | ||
top: function () { | ||
return this.raw[ this.length - 1 ]; | ||
return this.raw[this.length - 1]; | ||
}, | ||
@@ -89,3 +87,3 @@ | ||
bottom: function () { | ||
return this.raw[ 0 ]; | ||
return this.raw[0]; | ||
}, | ||
@@ -95,11 +93,11 @@ | ||
* 根据查询条件获取元素 | ||
* | ||
* | ||
* @param {Function} condition 查询函数 | ||
* @return {*} | ||
*/ | ||
find: function ( condition ) { | ||
find: function (condition) { | ||
var index = this.length; | ||
while ( index-- ) { | ||
var item = this.raw[ index ]; | ||
if ( condition( item ) ) { | ||
while (index--) { | ||
var item = this.raw[index]; | ||
if (condition(item)) { | ||
return item; | ||
@@ -113,3 +111,3 @@ } | ||
* 唯一id的起始值 | ||
* | ||
* | ||
* @inner | ||
@@ -122,3 +120,3 @@ * @type {number} | ||
* 获取唯一id,用于匿名target或编译代码的变量名生成 | ||
* | ||
* | ||
* @inner | ||
@@ -133,3 +131,3 @@ * @return {string} | ||
* 构建类之间的继承关系 | ||
* | ||
* | ||
* @inner | ||
@@ -139,3 +137,4 @@ * @param {Function} subClass 子类函数 | ||
*/ | ||
function inherits( subClass, superClass ) { | ||
function inherits(subClass, superClass) { | ||
/* jshint -W054 */ | ||
var F = new Function(); | ||
@@ -145,2 +144,3 @@ F.prototype = superClass.prototype; | ||
subClass.prototype.constructor = subClass; | ||
/* jshint +W054 */ | ||
// 由于引擎内部的使用场景都是inherits后,逐个编写子类的prototype方法 | ||
@@ -152,3 +152,3 @@ // 所以,不考虑将原有子类prototype缓存再逐个拷贝回去 | ||
* HTML Filter替换的字符实体表 | ||
* | ||
* | ||
* @const | ||
@@ -159,2 +159,3 @@ * @inner | ||
var HTML_ENTITY = { | ||
/* jshint ignore:start */ | ||
'&': '&', | ||
@@ -165,2 +166,3 @@ '<': '<', | ||
"'": ''' | ||
/* jshint ignore:end */ | ||
}; | ||
@@ -170,3 +172,3 @@ | ||
* HTML Filter的替换函数 | ||
* | ||
* | ||
* @inner | ||
@@ -176,4 +178,4 @@ * @param {string} c 替换字符 | ||
*/ | ||
function htmlFilterReplacer( c ) { | ||
return HTML_ENTITY[ c ]; | ||
function htmlFilterReplacer(c) { | ||
return HTML_ENTITY[c]; | ||
} | ||
@@ -183,3 +185,3 @@ | ||
* 默认filter | ||
* | ||
* | ||
* @inner | ||
@@ -192,8 +194,8 @@ * @const | ||
* HTML转义filter | ||
* | ||
* | ||
* @param {string} source 源串 | ||
* @return {string} | ||
*/ | ||
html: function ( source ) { | ||
return source.replace( /[&<>"']/g, htmlFilterReplacer ); | ||
html: function (source) { | ||
return source.replace(/[&<>"']/g, htmlFilterReplacer); | ||
}, | ||
@@ -203,3 +205,3 @@ | ||
* URL编码filter | ||
* | ||
* | ||
* @param {string} source 源串 | ||
@@ -212,7 +214,7 @@ * @return {string} | ||
* 源串filter,用于在默认开启HTML转义时获取源串,不进行转义 | ||
* | ||
* | ||
* @param {string} source 源串 | ||
* @return {string} | ||
*/ | ||
raw: function ( source ) { | ||
raw: function (source) { | ||
return source; | ||
@@ -224,3 +226,3 @@ } | ||
* 字符串字面化 | ||
* | ||
* | ||
* @inner | ||
@@ -230,10 +232,10 @@ * @param {string} source 需要字面化的字符串 | ||
*/ | ||
function stringLiteralize( source ) { | ||
function stringLiteralize(source) { | ||
return '"' | ||
+ source | ||
.replace( /\x5C/g, '\\\\' ) | ||
.replace( /"/g, '\\"' ) | ||
.replace( /\x0A/g, '\\n' ) | ||
.replace( /\x09/g, '\\t' ) | ||
.replace( /\x0D/g, '\\r' ) | ||
.replace(/\x5C/g, '\\\\') | ||
.replace(/"/g, '\\"') | ||
.replace(/\x0A/g, '\\n') | ||
.replace(/\x09/g, '\\t') | ||
.replace(/\x0D/g, '\\r') | ||
// .replace( /\x08/g, '\\b' ) | ||
@@ -245,4 +247,17 @@ // .replace( /\x0C/g, '\\f' ) | ||
/** | ||
* 对字符串进行可用于new RegExp的字面化 | ||
* | ||
* @inner | ||
* @param {string} source 需要字面化的字符串 | ||
* @return {string} | ||
*/ | ||
function regexpLiteral(source) { | ||
return source.replace(/[\^\[\]\$\(\)\{\}\?\*\.\+]/g, function (c) { | ||
return '\\' + c; | ||
}); | ||
} | ||
/** | ||
* 字符串格式化 | ||
* | ||
* | ||
* @inner | ||
@@ -253,9 +268,9 @@ * @param {string} source 目标模版字符串 | ||
*/ | ||
function stringFormat( source ) { | ||
function stringFormat(source) { | ||
var args = arguments; | ||
return source.replace( | ||
return source.replace( | ||
/\{([0-9]+)\}/g, | ||
function ( match, index ) { | ||
return args[ index - 0 + 1 ]; | ||
} ); | ||
function (match, index) { | ||
return args[index - 0 + 1]; | ||
}); | ||
} | ||
@@ -265,3 +280,3 @@ | ||
* 用于render的字符串变量声明语句 | ||
* | ||
* | ||
* @inner | ||
@@ -275,3 +290,3 @@ * @const | ||
* 用于render的字符串内容添加语句(起始) | ||
* | ||
* | ||
* @inner | ||
@@ -285,3 +300,3 @@ * @const | ||
* 用于render的字符串内容添加语句(结束) | ||
* | ||
* | ||
* @inner | ||
@@ -295,3 +310,3 @@ * @const | ||
* 用于render的字符串内容返回语句 | ||
* | ||
* | ||
* @inner | ||
@@ -303,5 +318,5 @@ * @const | ||
// HACK: IE8-时,编译后的renderer使用push+join的策略进行字符串拼接 | ||
if ( typeof navigator != 'undefined' | ||
&& /msie\s*([0-9]+)/i.test( navigator.userAgent ) | ||
// HACK: IE8-时,编译后的renderer使用join Array的策略进行字符串拼接 | ||
if (typeof navigator !== 'undefined' | ||
&& /msie\s*([0-9]+)/i.test(navigator.userAgent) | ||
&& RegExp.$1 - 0 < 8 | ||
@@ -317,3 +332,3 @@ ) { | ||
* 用于if、var等命令生成编译代码 | ||
* | ||
* | ||
* @inner | ||
@@ -323,14 +338,15 @@ * @param {string} name 访问变量名 | ||
*/ | ||
function toGetVariableLiteral( name ) { | ||
function toGetVariableLiteral(name) { | ||
name = name.replace(/^\s*\*/, ''); | ||
return stringFormat( | ||
'gv({0},["{1}"])', | ||
stringLiteralize( name ), | ||
stringLiteralize(name), | ||
name.replace( | ||
/\[['"]?([^'"]+)['"]?\]/g, | ||
function ( match, name ) { | ||
/\[['"]?([^'"]+)['"]?\]/g, | ||
function (match, name) { | ||
return '.' + name; | ||
} | ||
) | ||
.split( '.' ) | ||
.join( '","' ) | ||
.split('.') | ||
.join('","') | ||
); | ||
@@ -340,22 +356,5 @@ } | ||
/** | ||
* 替换字符串中的${...}成getVariable调用的编译语句 | ||
* 用于if、var等命令生成编译代码 | ||
* | ||
* @inner | ||
* @param {string} source 源字符串 | ||
* @return {string} | ||
*/ | ||
function replaceGetVariableLiteral( source ) { | ||
return source.replace( | ||
/\$\{([0-9a-z_\.\[\]'"-]+)\}/ig, | ||
function( match, name ){ | ||
return toGetVariableLiteral( name ); | ||
} | ||
); | ||
} | ||
/** | ||
* 解析文本片段中以固定字符串开头和结尾的包含块 | ||
* 用于 命令串:<!-- ... --> 和 变量替换串:${...} 的解析 | ||
* | ||
* | ||
* @inner | ||
@@ -369,37 +368,38 @@ * @param {string} source 要解析的文本 | ||
*/ | ||
function parseTextBlock( source, open, close, greedy, onInBlock, onOutBlock ) { | ||
function parseTextBlock(source, open, close, greedy, onInBlock, onOutBlock) { | ||
var closeLen = close.length; | ||
var texts = source.split( open ); | ||
var texts = source.split(open); | ||
var level = 0; | ||
var buf = []; | ||
for ( var i = 0, len = texts.length; i < len; i++ ) { | ||
var text = texts[ i ]; | ||
for (var i = 0, len = texts.length; i < len; i++) { | ||
var text = texts[i]; | ||
if ( i ) { | ||
if (i) { | ||
var openBegin = 1; | ||
level++; | ||
while ( 1 ) { | ||
var closeIndex = text.indexOf( close ); | ||
if ( closeIndex < 0 ) { | ||
buf.push( text ); | ||
while (1) { | ||
var closeIndex = text.indexOf(close); | ||
if (closeIndex < 0) { | ||
buf.push(level > 1 && openBegin ? open : '', text); | ||
break; | ||
} | ||
else { | ||
level = greedy ? level - 1 : 0; | ||
buf.push( | ||
level > 0 ? open : '', | ||
text.slice( 0, closeIndex ), | ||
level > 0 ? close : '' | ||
); | ||
text = text.slice( closeIndex + closeLen ); | ||
if ( level === 0 ) { | ||
break; | ||
} | ||
level = greedy ? level - 1 : 0; | ||
buf.push( | ||
level > 0 && openBegin ? open : '', | ||
text.slice(0, closeIndex), | ||
level > 0 ? close : '' | ||
); | ||
text = text.slice(closeIndex + closeLen); | ||
openBegin = 0; | ||
if (level === 0) { | ||
break; | ||
} | ||
} | ||
if ( level === 0 ) { | ||
onInBlock( buf.join( '' ) ); | ||
onOutBlock( text ); | ||
if (level === 0) { | ||
onInBlock(buf.join('')); | ||
onOutBlock(text); | ||
buf = []; | ||
@@ -409,9 +409,9 @@ } | ||
else { | ||
text && onOutBlock( text ); | ||
text && onOutBlock(text); | ||
} | ||
} | ||
if ( level > 0 && buf.length > 0 ) { | ||
onOutBlock( open ); | ||
onOutBlock( buf.join( '' ) ); | ||
if (level > 0 && buf.length > 0) { | ||
onOutBlock(open); | ||
onOutBlock(buf.join('')); | ||
} | ||
@@ -421,4 +421,107 @@ } | ||
/** | ||
* 编译变量访问和变量替换的代码 | ||
* 用于普通文本或if、var、filter等命令生成编译代码 | ||
* | ||
* @inner | ||
* @param {string} source 源代码 | ||
* @param {Engine} engine 引擎实例 | ||
* @param {boolean} forText 是否为输出文本的变量替换 | ||
* @return {string} | ||
*/ | ||
function compileVariable(source, engine, forText) { | ||
var code = []; | ||
var options = engine.options; | ||
var toStringHead = ''; | ||
var toStringFoot = ''; | ||
var wrapHead = ''; | ||
var wrapFoot = ''; | ||
// 默认的filter,当forText模式时有效 | ||
var defaultFilter; | ||
if (forText) { | ||
toStringHead = 'ts('; | ||
toStringFoot = ')'; | ||
wrapHead = RENDER_STRING_ADD_START; | ||
wrapFoot = RENDER_STRING_ADD_END; | ||
defaultFilter = options.defaultFilter; | ||
} | ||
parseTextBlock( | ||
source, options.variableOpen, options.variableClose, 1, | ||
function (text) { | ||
// 加入默认filter | ||
// 只有当处理forText时,需要加入默认filter | ||
// 处理if/var/use等command时,不需要加入默认filter | ||
if (forText && text.indexOf('|') < 0 && defaultFilter) { | ||
text += '|' + defaultFilter; | ||
} | ||
// variableCode是一个gv调用,然后通过循环,在外面包filter的调用 | ||
// 形成filter["b"](filter["a"](gv(...))) | ||
// | ||
// 当forText模式,处理的是文本中的变量替换时 | ||
// 传递给filter的需要是字符串形式,所以gv外需要包一层ts调用 | ||
// 形成filter["b"](filter["a"](ts(gv(...)))) | ||
// | ||
// 当variableName以*起始时,忽略ts调用,直接传递原值给filter | ||
var filterCharIndex = text.indexOf('|'); | ||
var variableName = ( | ||
filterCharIndex > 0 | ||
? text.slice(0, filterCharIndex) | ||
: text | ||
).replace(/^\s+/, '').replace(/\s+$/, ''); | ||
var filterSource = filterCharIndex > 0 | ||
? text.slice(filterCharIndex + 1) | ||
: ''; | ||
var variableRawValue = variableName.indexOf('*') === 0; | ||
var variableCode = [ | ||
variableRawValue ? '' : toStringHead, | ||
toGetVariableLiteral(variableName), | ||
variableRawValue ? '' : toStringFoot | ||
]; | ||
if (filterSource) { | ||
filterSource = compileVariable(filterSource, engine); | ||
var filterSegs = filterSource.split('|'); | ||
for (var i = 0, len = filterSegs.length; i < len; i++) { | ||
var seg = filterSegs[i]; | ||
if (/^\s*([a-z0-9_-]+)(\((.*)\))?\s*$/i.test(seg)) { | ||
variableCode.unshift('fs["' + RegExp.$1 + '"]('); | ||
if (RegExp.$3) { | ||
variableCode.push(',', RegExp.$3); | ||
} | ||
variableCode.push(')'); | ||
} | ||
} | ||
} | ||
code.push( | ||
wrapHead, | ||
variableCode.join(''), | ||
wrapFoot | ||
); | ||
}, | ||
function (text) { | ||
code.push( | ||
wrapHead, | ||
forText ? stringLiteralize(text) : text, | ||
wrapFoot | ||
); | ||
} | ||
); | ||
return code.join(''); | ||
} | ||
/** | ||
* 文本节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -429,16 +532,19 @@ * @constructor | ||
*/ | ||
function TextNode( value, engine ) { | ||
function TextNode(value, engine) { | ||
this.value = value; | ||
this.engine = engine; | ||
} | ||
TextNode.prototype = { | ||
/** | ||
* 获取renderer body的生成代码 | ||
* | ||
* | ||
* @return {string} | ||
*/ | ||
getRendererBody: function () { | ||
if ( !this.value | ||
|| ( this.engine.options.strip && /^\s*$/.test( this.value ) ) | ||
var value = this.value; | ||
var options = this.engine.options; | ||
if (!value | ||
|| (options.strip && /^\s*$/.test(value)) | ||
) { | ||
@@ -448,75 +554,12 @@ return ''; | ||
var defaultFilter = this.engine.options.defaultFilter; | ||
var code = []; | ||
parseTextBlock( | ||
this.value, '${', '}', 1, | ||
function ( text ) { // ${...}内文本的处理函数 | ||
// 加入默认filter | ||
if ( text.indexOf( '|' ) < 0 && defaultFilter ) { | ||
text += '|' + defaultFilter; | ||
} | ||
var segs = text.split( /\s*\|\s*/ ); | ||
// variableCode最先通过gv和ts调用,取得variable的string形式 | ||
// 然后通过循环,在外面包filter的调用 | ||
// 形成filter["b"](filter["a"](gvs(...))) | ||
// 当variableName以*起始时,忽略toString,直接传递原值给filter | ||
var variableName = segs[ 0 ]; | ||
var toStringHead = 'ts('; | ||
var toStringFoot = ')'; | ||
if ( variableName.indexOf( '*' ) === 0 ) { | ||
variableName = variableName.slice( 1 ); | ||
toStringHead = toStringFoot = ''; | ||
} | ||
var variableCode = [ | ||
toStringHead, | ||
toGetVariableLiteral( variableName ), | ||
toStringFoot | ||
]; | ||
for ( var i = 1, len = segs.length; i < len; i++ ) { | ||
var seg = segs[ i ]; | ||
if ( /^\s*([a-z0-9_-]+)(\((.*)\))?\s*$/i.test( seg ) ) { | ||
variableCode.unshift( 'fs["' + RegExp.$1 + '"](' ); | ||
if ( RegExp.$3 ) { | ||
variableCode.push( | ||
',', | ||
replaceGetVariableLiteral( RegExp.$3 ) | ||
); | ||
} | ||
variableCode.push( ')' ); | ||
} | ||
} | ||
code.push( | ||
RENDER_STRING_ADD_START, | ||
variableCode.join( '' ), | ||
RENDER_STRING_ADD_END | ||
); | ||
}, | ||
function ( text ) { // ${...}外普通文本的处理函数 | ||
code.push( | ||
RENDER_STRING_ADD_START, | ||
stringLiteralize( text ), | ||
RENDER_STRING_ADD_END | ||
); | ||
} | ||
); | ||
return code.join( '' ); | ||
return compileVariable(value, this.engine, 1); | ||
}, | ||
/** | ||
* 获取内容 | ||
* | ||
* @return {string} | ||
* 复制节点的方法 | ||
* | ||
* @return {TextNode} | ||
*/ | ||
getContent: function () { | ||
return this.value; | ||
clone: function () { | ||
return this; | ||
} | ||
@@ -527,3 +570,3 @@ }; | ||
* 命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -534,6 +577,7 @@ * @constructor | ||
*/ | ||
function Command( value, engine ) { | ||
function Command(value, engine) { | ||
this.value = value; | ||
this.engine = engine; | ||
this.children = []; | ||
this.cloneProps = []; | ||
} | ||
@@ -544,7 +588,7 @@ | ||
* 添加子节点 | ||
* | ||
* | ||
* @param {TextNode|Command} node 子节点 | ||
*/ | ||
addChild: function ( node ) { | ||
this.children.push( node ); | ||
addChild: function (node) { | ||
this.children.push(node); | ||
}, | ||
@@ -554,10 +598,9 @@ | ||
* 节点open,解析开始 | ||
* | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
open: function ( context ) { | ||
open: function (context) { | ||
var parent = context.stack.top(); | ||
this.parent = parent; | ||
parent && parent.addChild( this ); | ||
context.stack.push( this ); | ||
parent && parent.addChild(this); | ||
context.stack.push(this); | ||
}, | ||
@@ -567,21 +610,14 @@ | ||
* 节点闭合,解析结束 | ||
* | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
close: function ( context ) { | ||
while (context.stack.pop().constructor !== this.constructor) {} | ||
close: function (context) { | ||
if (context.stack.top() === this) { | ||
context.stack.pop(); | ||
} | ||
}, | ||
/** | ||
* 添加文本节点 | ||
* | ||
* @param {TextNode} node 节点 | ||
*/ | ||
addTextNode: function ( node ) { | ||
this.addChild( node ); | ||
}, | ||
/** | ||
* 获取renderer body的生成代码 | ||
* | ||
* | ||
* @return {string} | ||
@@ -592,7 +628,26 @@ */ | ||
var children = this.children; | ||
for ( var i = 0; i < children.length; i++ ) { | ||
buf.push( children[ i ].getRendererBody() ); | ||
for (var i = 0; i < children.length; i++) { | ||
buf.push(children[i].getRendererBody()); | ||
} | ||
return buf.join( '' ); | ||
return buf.join(''); | ||
}, | ||
/** | ||
* 复制节点的方法 | ||
* | ||
* @return {Command} | ||
*/ | ||
clone: function () { | ||
var node = new this.constructor(this.value, this.engine); | ||
for (var i = 0, l = this.children.length; i < l; i++) { | ||
node.addChild(this.children[i].clone()); | ||
} | ||
for (var i = 0, l = this.cloneProps.length; i < l; i++) { | ||
var prop = this.cloneProps[i]; | ||
node[prop] = this[prop]; | ||
} | ||
return node; | ||
} | ||
@@ -603,3 +658,3 @@ }; | ||
* 命令自动闭合 | ||
* | ||
* | ||
* @inner | ||
@@ -609,24 +664,29 @@ * @param {Object} context 语法分析环境对象 | ||
*/ | ||
function autoCloseCommand( context, CommandType ) { | ||
function autoCloseCommand(context, CommandType) { | ||
var stack = context.stack; | ||
var closeEnd = CommandType | ||
? stack.find( function ( item ) { | ||
return item instanceof CommandType; | ||
} ) | ||
var closeEnd = CommandType | ||
? stack.find( | ||
function (item) { | ||
return item instanceof CommandType; | ||
} | ||
) | ||
: stack.bottom(); | ||
if ( closeEnd ) { | ||
if (closeEnd) { | ||
var node; | ||
do { | ||
node = stack.top(); | ||
while ((node = stack.top()) !== closeEnd) { | ||
/* jshint ignore:start */ | ||
// 如果节点对象不包含autoClose方法 | ||
// 则认为该节点不支持自动闭合,需要抛出错误 | ||
// for等节点不支持自动闭合 | ||
if ( !node.autoClose ) { | ||
throw new Error( node.type + ' must be closed manually: ' + node.value ); | ||
if (!node.autoClose) { | ||
throw new Error(node.type + ' must be closed manually: ' + node.value); | ||
} | ||
node.autoClose( context ); | ||
} while ( node !== closeEnd ); | ||
/* jshint ignore:end */ | ||
node.autoClose(context); | ||
} | ||
closeEnd.close(context); | ||
} | ||
@@ -639,3 +699,3 @@ | ||
* renderer body起始代码段 | ||
* | ||
* | ||
* @inner | ||
@@ -673,3 +733,3 @@ * @const | ||
* Target命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -680,19 +740,22 @@ * @constructor | ||
*/ | ||
function TargetCommand( value, engine ) { | ||
if ( !/^\s*([a-z0-9_-]+)\s*(\(\s*master\s*=\s*([a-z0-9_-]+)\s*\))?\s*/i.test( value ) ) { | ||
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value ); | ||
function TargetCommand(value, engine) { | ||
/* jshint ignore:start */ | ||
if (!/^\s*([a-z0-9\/_-]+)\s*(\(\s*master\s*=\s*([a-z0-9\/_-]+)\s*\))?\s*/i.test(value)) { | ||
throw new Error('Invalid ' + this.type + ' syntax: ' + value); | ||
} | ||
/* jshint ignore:end */ | ||
this.master = RegExp.$3; | ||
this.name = RegExp.$1; | ||
Command.call( this, value, engine ); | ||
this.contents = {}; | ||
Command.call(this, value, engine); | ||
this.blocks = {}; | ||
} | ||
// 创建Target命令节点继承关系 | ||
inherits( TargetCommand, Command ); | ||
inherits(TargetCommand, Command); | ||
/** | ||
* Master命令节点类 | ||
* | ||
* Block命令节点类 | ||
* | ||
* @inner | ||
@@ -703,59 +766,18 @@ * @constructor | ||
*/ | ||
function MasterCommand( value, engine ) { | ||
if ( !/^\s*([a-z0-9_-]+)\s*(\(\s*master\s*=\s*([a-z0-9_-]+)\s*\))?\s*/i.test( value ) ) { | ||
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value ); | ||
function BlockCommand(value, engine) { | ||
if (!/^\s*([a-z0-9\/_-]+)\s*$/i.test(value)) { | ||
throw new Error('Invalid ' + this.type + ' syntax: ' + value); | ||
} | ||
this.master = RegExp.$3; | ||
this.name = RegExp.$1; | ||
Command.call( this, value, engine ); | ||
this.contents = {}; | ||
} | ||
// 创建Master命令节点继承关系 | ||
inherits( MasterCommand, Command ); | ||
/** | ||
* Content命令节点类 | ||
* | ||
* @inner | ||
* @constructor | ||
* @param {string} value 命令节点的value | ||
* @param {Engine} engine 引擎实例 | ||
*/ | ||
function ContentCommand( value, engine ) { | ||
if ( !/^\s*([a-z0-9_-]+)\s*$/i.test( value ) ) { | ||
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value ); | ||
} | ||
this.name = RegExp.$1; | ||
Command.call( this, value, engine ); | ||
Command.call(this, value, engine); | ||
this.cloneProps = [ 'name' ]; | ||
} | ||
// 创建Content命令节点继承关系 | ||
inherits( ContentCommand, Command ); | ||
// 创建Block命令节点继承关系 | ||
inherits(BlockCommand, Command); | ||
/** | ||
* ContentPlaceHolder命令节点类 | ||
* | ||
* @inner | ||
* @constructor | ||
* @param {string} value 命令节点的value | ||
* @param {Engine} engine 引擎实例 | ||
*/ | ||
function ContentPlaceHolderCommand( value, engine ) { | ||
if ( !/^\s*([a-z0-9_-]+)\s*$/i.test( value ) ) { | ||
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value ); | ||
} | ||
this.name = RegExp.$1; | ||
Command.call( this, value, engine ); | ||
} | ||
// 创建ContentPlaceHolder命令节点继承关系 | ||
inherits( ContentPlaceHolderCommand, Command ); | ||
/** | ||
* Import命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -766,17 +788,19 @@ * @constructor | ||
*/ | ||
function ImportCommand( value, engine ) { | ||
if ( !/^\s*([a-z0-9_-]+)\s*$/i.test( value ) ) { | ||
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value ); | ||
function ImportCommand(value, engine) { | ||
if (!/^\s*([a-z0-9\/_-]+)\s*$/i.test(value)) { | ||
throw new Error('Invalid ' + this.type + ' syntax: ' + value); | ||
} | ||
this.name = RegExp.$1; | ||
Command.call( this, value, engine ); | ||
Command.call(this, value, engine); | ||
this.cloneProps = [ 'name', 'state', 'blocks' ]; | ||
this.blocks = {}; | ||
} | ||
// 创建Import命令节点继承关系 | ||
inherits( ImportCommand, Command ); | ||
inherits(ImportCommand, Command); | ||
/** | ||
* Var命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -787,5 +811,5 @@ * @constructor | ||
*/ | ||
function VarCommand( value, engine ) { | ||
if ( !/^\s*([a-z0-9_]+)\s*=([\s\S]*)$/i.test( value ) ) { | ||
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value ); | ||
function VarCommand(value, engine) { | ||
if (!/^\s*([a-z0-9_]+)\s*=([\s\S]*)$/i.test(value)) { | ||
throw new Error('Invalid ' + this.type + ' syntax: ' + value); | ||
} | ||
@@ -795,11 +819,12 @@ | ||
this.expr = RegExp.$2; | ||
Command.call( this, value, engine ); | ||
Command.call(this, value, engine); | ||
this.cloneProps = [ 'name', 'expr' ]; | ||
} | ||
// 创建Var命令节点继承关系 | ||
inherits( VarCommand, Command ); | ||
inherits(VarCommand, Command); | ||
/** | ||
* filter命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -810,5 +835,5 @@ * @constructor | ||
*/ | ||
function FilterCommand( value, engine ) { | ||
if ( !/^\s*([a-z0-9_-]+)\s*(\(([\s\S]*)\))?\s*$/i.test( value ) ) { | ||
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value ); | ||
function FilterCommand(value, engine) { | ||
if (!/^\s*([a-z0-9_-]+)\s*(\(([\s\S]*)\))?\s*$/i.test(value)) { | ||
throw new Error('Invalid ' + this.type + ' syntax: ' + value); | ||
} | ||
@@ -818,11 +843,12 @@ | ||
this.args = RegExp.$3; | ||
Command.call( this, value, engine ); | ||
Command.call(this, value, engine); | ||
this.cloneProps = [ 'name', 'args' ]; | ||
} | ||
// 创建filter命令节点继承关系 | ||
inherits( FilterCommand, Command ); | ||
inherits(FilterCommand, Command); | ||
/** | ||
* Use命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -833,5 +859,5 @@ * @constructor | ||
*/ | ||
function UseCommand( value, engine ) { | ||
if ( !/^\s*([a-z0-9_-]+)\s*(\(([\s\S]*)\))?\s*$/i.test( value ) ) { | ||
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value ); | ||
function UseCommand(value, engine) { | ||
if (!/^\s*([a-z0-9\/_-]+)\s*(\(([\s\S]*)\))?\s*$/i.test(value)) { | ||
throw new Error('Invalid ' + this.type + ' syntax: ' + value); | ||
} | ||
@@ -841,11 +867,12 @@ | ||
this.args = RegExp.$3; | ||
Command.call( this, value, engine ); | ||
Command.call(this, value, engine); | ||
this.cloneProps = [ 'name', 'args' ]; | ||
} | ||
// 创建Use命令节点继承关系 | ||
inherits( UseCommand, Command ); | ||
inherits(UseCommand, Command); | ||
/** | ||
* for命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -856,19 +883,32 @@ * @constructor | ||
*/ | ||
function ForCommand( value, engine ) { | ||
if ( !/^\s*\$\{([0-9a-z_\.\[\]'"-]+)\}\s+as\s+\$\{([0-9a-z_]+)\}\s*(,\s*\$\{([0-9a-z_]+)\})?\s*$/i.test( value ) ) { | ||
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value ); | ||
function ForCommand(value, engine) { | ||
var rule = new RegExp( | ||
stringFormat( | ||
/* jshint ignore:start */ | ||
'^\\s*({0}[\\s\\S]+{1})\\s+as\\s+{0}([0-9a-z_]+){1}\\s*(,\\s*{0}([0-9a-z_]+){1})?\\s*$', | ||
/* jshint ignore:end */ | ||
regexpLiteral(engine.options.variableOpen), | ||
regexpLiteral(engine.options.variableClose) | ||
), | ||
'i' | ||
); | ||
if (!rule.test(value)) { | ||
throw new Error('Invalid ' + this.type + ' syntax: ' + value); | ||
} | ||
this.list = RegExp.$1; | ||
this.item = RegExp.$2; | ||
this.index = RegExp.$4; | ||
Command.call( this, value, engine ); | ||
Command.call(this, value, engine); | ||
this.cloneProps = [ 'list', 'item', 'index' ]; | ||
} | ||
// 创建for命令节点继承关系 | ||
inherits( ForCommand, Command ); | ||
inherits(ForCommand, Command); | ||
/** | ||
* if命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -879,12 +919,12 @@ * @constructor | ||
*/ | ||
function IfCommand( value, engine ) { | ||
Command.call( this, value, engine ); | ||
function IfCommand(value, engine) { | ||
Command.call(this, value, engine); | ||
} | ||
// 创建if命令节点继承关系 | ||
inherits( IfCommand, Command ); | ||
inherits(IfCommand, Command); | ||
/** | ||
* elif命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -895,12 +935,12 @@ * @constructor | ||
*/ | ||
function ElifCommand( value, engine ) { | ||
IfCommand.call( this, value, engine ); | ||
function ElifCommand(value, engine) { | ||
IfCommand.call(this, value, engine); | ||
} | ||
// 创建elif命令节点继承关系 | ||
inherits( ElifCommand, IfCommand ); | ||
inherits(ElifCommand, IfCommand); | ||
/** | ||
* else命令节点类 | ||
* | ||
* | ||
* @inner | ||
@@ -911,15 +951,15 @@ * @constructor | ||
*/ | ||
function ElseCommand( value, engine ) { | ||
Command.call( this, value, engine ); | ||
function ElseCommand(value, engine) { | ||
Command.call(this, value, engine); | ||
} | ||
// 创建else命令节点继承关系 | ||
inherits( ElseCommand, Command ); | ||
inherits(ElseCommand, IfCommand); | ||
/** | ||
* Target和Master的节点状态 | ||
* | ||
* Target的节点状态 | ||
* | ||
* @inner | ||
*/ | ||
var TMNodeState = { | ||
var TargetState = { | ||
READING: 1, | ||
@@ -932,69 +972,40 @@ READED: 2, | ||
/** | ||
* 节点闭合,解析结束 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
MasterCommand.prototype.close = | ||
/** | ||
* 节点闭合,解析结束。自闭合时被调用 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
MasterCommand.prototype.autoClose = | ||
/** | ||
* 节点闭合,解析结束 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
TargetCommand.prototype.close = | ||
/** | ||
* 节点闭合,解析结束。自闭合时被调用 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
TargetCommand.prototype.autoClose = function ( context ) { | ||
Command.prototype.close.call( this, context ); | ||
this.state = this.master ? TMNodeState.READED : TMNodeState.APPLIED; | ||
context.targetOrMaster = null; | ||
}; | ||
/** | ||
* 应用其继承的母版,返回是否成功应用母版 | ||
* | ||
* | ||
* @return {boolean} | ||
*/ | ||
TargetCommand.prototype.applyMaster = | ||
ImportCommand.prototype.applyMaster = | ||
/** | ||
* 应用其继承的母版,返回是否成功应用母版 | ||
* | ||
* | ||
* @return {boolean} | ||
*/ | ||
MasterCommand.prototype.applyMaster = function () { | ||
if ( this.state >= TMNodeState.APPLIED ) { | ||
TargetCommand.prototype.applyMaster = function (masterName) { | ||
if (this.state >= TargetState.APPLIED) { | ||
return 1; | ||
} | ||
var masterNode = this.engine.masters[ this.master ]; | ||
if ( masterNode && masterNode.applyMaster() ) { | ||
this.children = []; | ||
var blocks = this.blocks; | ||
for ( var i = 0, len = masterNode.children.length; i < len; i++ ) { | ||
var child = masterNode.children[ i ]; | ||
function replaceBlock(node) { | ||
var children = node.children; | ||
if ( child instanceof ContentPlaceHolderCommand ) { | ||
this.children.push.apply( | ||
this.children, | ||
(this.contents[ child.name ] || child).children | ||
); | ||
if (children instanceof Array) { | ||
for (var i = 0, len = children.length; i < len; i++) { | ||
var child = children[i]; | ||
if (child instanceof BlockCommand && blocks[child.name]) { | ||
child = children[i] = blocks[child.name]; | ||
} | ||
replaceBlock(child); | ||
} | ||
else { | ||
this.children.push( child ); | ||
} | ||
} | ||
} | ||
this.state = TMNodeState.APPLIED; | ||
var master = this.engine.targets[masterName]; | ||
if (master && master.applyMaster(master.master)) { | ||
this.children = master.clone().children; | ||
replaceBlock(this); | ||
this.state = TargetState.APPLIED; | ||
return 1; | ||
@@ -1006,8 +1017,8 @@ } | ||
* 判断target是否ready | ||
* 包括是否成功应用母版,以及import和use语句依赖的target是否ready | ||
* | ||
* 包括是否成功应用母版,以及import语句依赖的target是否ready | ||
* | ||
* @return {boolean} | ||
*/ | ||
TargetCommand.prototype.isReady = function () { | ||
if ( this.state >= TMNodeState.READY ) { | ||
if (this.state >= TargetState.READY) { | ||
return 1; | ||
@@ -1021,16 +1032,16 @@ } | ||
* 递归检查节点的ready状态 | ||
* | ||
* | ||
* @inner | ||
* @param {Command|TextNode} node 目标节点 | ||
*/ | ||
function checkReadyState( node ) { | ||
for ( var i = 0, len = node.children.length; i < len; i++ ) { | ||
var child = node.children[ i ]; | ||
if ( child instanceof ImportCommand ) { | ||
var target = engine.targets[ child.name ]; | ||
readyState = readyState | ||
&& target && target.isReady( engine ); | ||
function checkReadyState(node) { | ||
for (var i = 0, len = node.children.length; i < len; i++) { | ||
var child = node.children[i]; | ||
if (child instanceof ImportCommand) { | ||
var target = engine.targets[child.name]; | ||
readyState = readyState | ||
&& target && target.isReady(engine); | ||
} | ||
else if ( child instanceof Command ) { | ||
checkReadyState( child ); | ||
else if (child instanceof Command) { | ||
checkReadyState(child); | ||
} | ||
@@ -1040,5 +1051,5 @@ } | ||
if ( this.applyMaster() ) { | ||
checkReadyState( this ); | ||
readyState && (this.state = TMNodeState.READY); | ||
if (this.applyMaster(this.master)) { | ||
checkReadyState(this); | ||
readyState && (this.state = TargetState.READY); | ||
return readyState; | ||
@@ -1050,16 +1061,18 @@ } | ||
* 获取target的renderer函数 | ||
* | ||
* | ||
* @return {function(Object):string} | ||
*/ | ||
TargetCommand.prototype.getRenderer = function () { | ||
if ( this.renderer ) { | ||
if (this.renderer) { | ||
return this.renderer; | ||
} | ||
if ( this.isReady() ) { | ||
// console.log(RENDERER_BODY_START +RENDER_STRING_DECLATION | ||
// + this.getRendererBody() | ||
// + RENDER_STRING_RETURN) | ||
if (this.isReady()) { | ||
// console.log(this.name + ' ------------------'); | ||
// console.log(RENDERER_BODY_START + RENDER_STRING_DECLATION | ||
// + this.getRendererBody() | ||
// + RENDER_STRING_RETURN); | ||
var realRenderer = new Function( | ||
/* jshint -W054 */ | ||
var realRenderer = new Function( | ||
'data', 'engine', | ||
@@ -1071,9 +1084,9 @@ [ | ||
RENDER_STRING_RETURN | ||
].join( '\n' ) | ||
].join('\n') | ||
); | ||
/* jshint +W054 */ | ||
var engine = this.engine; | ||
this.renderer = function ( data ) { | ||
return realRenderer( data, engine ); | ||
this.renderer = function (data) { | ||
return realRenderer(data, engine); | ||
}; | ||
@@ -1088,50 +1101,30 @@ | ||
/** | ||
* 获取内容 | ||
* | ||
* @return {string} | ||
*/ | ||
TargetCommand.prototype.getContent = function () { | ||
if ( this.isReady() ) { | ||
var buf = []; | ||
var children = this.children; | ||
for ( var i = 0; i < children.length; i++ ) { | ||
buf.push( children[ i ].getContent() ); | ||
} | ||
return buf.join( '' ); | ||
} | ||
return ''; | ||
}; | ||
/** | ||
* 将target或master节点对象添加到语法分析环境中 | ||
* | ||
* 将target节点对象添加到语法分析环境中 | ||
* | ||
* @inner | ||
* @param {TargetCommand|MasterCommand} targetOrMaster target或master节点对象 | ||
* @param {TargetCommand} target target节点对象 | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
function addTargetOrMasterToContext( targetOrMaster, context ) { | ||
context.targetOrMaster = targetOrMaster; | ||
function addTargetToContext(target, context) { | ||
context.target = target; | ||
var engine = context.engine; | ||
var name = targetOrMaster.name; | ||
var isTarget = targetOrMaster instanceof TargetCommand; | ||
var prop = isTarget ? 'targets' : 'masters'; | ||
var name = target.name; | ||
if ( engine[ prop ][ name ] ) { | ||
switch ( engine.options.namingConflict ) { | ||
if (engine.targets[name]) { | ||
switch (engine.options.namingConflict) { | ||
/* jshint ignore:start */ | ||
case 'override': | ||
engine[ prop ][ name ] = targetOrMaster; | ||
isTarget && context.targets.push( name ); | ||
engine.targets[name] = target; | ||
context.targets.push(name); | ||
case 'ignore': | ||
break; | ||
/* jshint ignore:end */ | ||
default: | ||
throw new Error( ( isTarget ? 'Target' :'Master' ) | ||
+ ' is exists: ' + name ); | ||
throw new Error('Target exists: ' + name); | ||
} | ||
} | ||
else { | ||
engine[ prop ][ name ] = targetOrMaster; | ||
isTarget && context.targets.push( name ); | ||
engine.targets[name] = target; | ||
context.targets.push(name); | ||
} | ||
@@ -1142,151 +1135,208 @@ } | ||
* target节点open,解析开始 | ||
* | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
TargetCommand.prototype.open = | ||
TargetCommand.prototype.open = function (context) { | ||
autoCloseCommand(context); | ||
Command.prototype.open.call(this, context); | ||
this.state = TargetState.READING; | ||
addTargetToContext(this, context); | ||
}; | ||
/** | ||
* master节点open,解析开始 | ||
* | ||
* Var节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
MasterCommand.prototype.open = function ( context ) { | ||
autoCloseCommand( context ); | ||
Command.prototype.open.call( this, context ); | ||
this.state = TMNodeState.READING; | ||
addTargetOrMasterToContext( this, context ); | ||
VarCommand.prototype.open = | ||
/** | ||
* Use节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
UseCommand.prototype.open = function (context) { | ||
context.stack.top().addChild(this); | ||
}; | ||
/** | ||
* Import节点open,解析开始 | ||
* | ||
* Block节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ImportCommand.prototype.open = | ||
BlockCommand.prototype.open = function (context) { | ||
Command.prototype.open.call(this, context); | ||
(context.imp || context.target).blocks[this.name] = this; | ||
}; | ||
/** | ||
* Var节点open,解析开始 | ||
* | ||
* elif节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
VarCommand.prototype.open = | ||
ElifCommand.prototype.open = function (context) { | ||
var elseCommand = new ElseCommand(); | ||
elseCommand.open(context); | ||
var ifCommand = autoCloseCommand(context, IfCommand); | ||
ifCommand.addChild(this); | ||
context.stack.push(this); | ||
}; | ||
/** | ||
* Use节点open,解析开始 | ||
* | ||
* else节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
UseCommand.prototype.open = function ( context ) { | ||
var parent = context.stack.top(); | ||
this.parent = parent; | ||
parent.addChild( this ); | ||
ElseCommand.prototype.open = function (context) { | ||
var ifCommand = autoCloseCommand(context, IfCommand); | ||
ifCommand.addChild(this); | ||
context.stack.push(this); | ||
}; | ||
/** | ||
* import节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ImportCommand.prototype.open = function (context) { | ||
this.parent = context.stack.top(); | ||
this.target = context.target; | ||
Command.prototype.open.call(this, context); | ||
this.state = TargetState.READING; | ||
context.imp = this; | ||
}; | ||
/** | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* 节点解析结束 | ||
* 由于use节点无需闭合,处理时不会入栈,所以将close置为空函数 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
UseCommand.prototype.beforeOpen = | ||
UseCommand.prototype.close = | ||
/** | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* 节点解析结束 | ||
* 由于var节点无需闭合,处理时不会入栈,所以将close置为空函数 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ImportCommand.prototype.beforeOpen = | ||
VarCommand.prototype.close = function () {}; | ||
/** | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* 节点解析结束 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
VarCommand.prototype.beforeOpen = | ||
ImportCommand.prototype.close = function (context) { | ||
Command.prototype.close.call(this, context); | ||
this.state = TargetState.READED; | ||
context.imp = null; | ||
}; | ||
/** | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* 节点闭合,解析结束 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ForCommand.prototype.beforeOpen = | ||
TargetCommand.prototype.close = function (context) { | ||
Command.prototype.close.call(this, context); | ||
this.state = this.master ? TargetState.READED : TargetState.APPLIED; | ||
context.target = null; | ||
}; | ||
/** | ||
* 节点自动闭合,解析结束 | ||
* ImportCommand的自动结束逻辑为,在其开始位置后马上结束 | ||
* 所以,其自动结束时children应赋予其所属的parent | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ImportCommand.prototype.autoClose = function (context) { | ||
// move children to parent | ||
var parentChildren = this.parent.children; | ||
parentChildren.push.apply(parentChildren, this.children); | ||
this.children.length = 0; | ||
// move blocks to target | ||
for (var key in this.blocks) { | ||
this.target.blocks[key] = this.blocks[key]; | ||
} | ||
this.blocks = {}; | ||
// do close | ||
this.close(context); | ||
}; | ||
/** | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
FilterCommand.prototype.beforeOpen = | ||
UseCommand.prototype.beforeOpen = | ||
/** | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
IfCommand.prototype.beforeOpen = | ||
ImportCommand.prototype.beforeOpen = | ||
/** | ||
* 文本节点被添加到分析环境前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
TextNode.prototype.beforeAdd = function ( context ) { | ||
if ( context.stack.bottom() ) { | ||
return; | ||
} | ||
VarCommand.prototype.beforeOpen = | ||
var target = new TargetCommand( generateGUID(), context.engine ); | ||
target.open( context ); | ||
}; | ||
/** | ||
* 节点解析结束 | ||
* 由于use节点无需闭合,处理时不会入栈,所以将close置为空函数 | ||
* | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
UseCommand.prototype.close = | ||
ForCommand.prototype.beforeOpen = | ||
/** | ||
* 节点解析结束 | ||
* 由于import节点无需闭合,处理时不会入栈,所以将close置为空函数 | ||
* | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ImportCommand.prototype.close = | ||
*/ | ||
FilterCommand.prototype.beforeOpen = | ||
/** | ||
* 节点解析结束 | ||
* 由于else节点无需闭合,处理时不会入栈,闭合由if负责。所以将close置为空函数 | ||
* | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ElseCommand.prototype.close = | ||
BlockCommand.prototype.beforeOpen = | ||
/** | ||
* 节点解析结束 | ||
* 由于var节点无需闭合,处理时不会入栈,所以将close置为空函数 | ||
* | ||
* 节点open前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
VarCommand.prototype.close = function () {}; | ||
IfCommand.prototype.beforeOpen = | ||
/** | ||
* 获取内容 | ||
* | ||
* @return {string} | ||
* 文本节点被添加到分析环境前的处理动作:节点不在target中时,自动创建匿名target | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ImportCommand.prototype.getContent = function () { | ||
var target = this.engine.targets[ this.name ]; | ||
return target.getContent(); | ||
TextNode.prototype.beforeAdd = function (context) { | ||
if (context.stack.bottom()) { | ||
return; | ||
} | ||
var target = new TargetCommand(generateGUID(), context.engine); | ||
target.open(context); | ||
}; | ||
/** | ||
* 获取renderer body的生成代码 | ||
* | ||
* | ||
* @return {string} | ||
*/ | ||
ImportCommand.prototype.getRendererBody = function () { | ||
var target = this.engine.targets[ this.name ]; | ||
return target.getRendererBody(); | ||
this.applyMaster(this.name); | ||
return Command.prototype.getRendererBody.call(this); | ||
}; | ||
@@ -1296,3 +1346,3 @@ | ||
* 获取renderer body的生成代码 | ||
* | ||
* | ||
* @return {string} | ||
@@ -1305,25 +1355,23 @@ */ | ||
RENDER_STRING_ADD_END, | ||
stringLiteralize( this.name ), | ||
replaceGetVariableLiteral( | ||
this.args.replace( | ||
/(^|,)\s*([a-z0-9_]+)\s*=/ig, | ||
function ( match, start, argName ) { | ||
return (start || '') + stringLiteralize( argName ) + ':'; | ||
} | ||
) | ||
stringLiteralize(this.name), | ||
compileVariable(this.args, this.engine).replace( | ||
/(^|,)\s*([a-z0-9_]+)\s*=/ig, | ||
function (match, start, argName) { | ||
return (start || '') + stringLiteralize(argName) + ':'; | ||
} | ||
) | ||
); | ||
}; | ||
/** | ||
* 获取renderer body的生成代码 | ||
* | ||
* | ||
* @return {string} | ||
*/ | ||
VarCommand.prototype.getRendererBody = function () { | ||
if ( this.expr ) { | ||
return stringFormat( | ||
if (this.expr) { | ||
return stringFormat( | ||
'v[{0}]={1};', | ||
stringLiteralize( this.name ), | ||
replaceGetVariableLiteral( this.expr ) | ||
stringLiteralize(this.name), | ||
compileVariable(this.expr, this.engine) | ||
); | ||
@@ -1337,24 +1385,23 @@ } | ||
* 获取renderer body的生成代码 | ||
* | ||
* | ||
* @return {string} | ||
*/ | ||
IfCommand.prototype.getRendererBody = function () { | ||
var rendererBody = stringFormat( | ||
return stringFormat( | ||
'if({0}){{1}}', | ||
replaceGetVariableLiteral( this.value ), | ||
Command.prototype.getRendererBody.call( this ) | ||
compileVariable(this.value, this.engine), | ||
Command.prototype.getRendererBody.call(this) | ||
); | ||
}; | ||
var elseCommand = this[ 'else' ]; | ||
if ( elseCommand ) { | ||
return [ | ||
rendererBody, | ||
stringFormat( | ||
'else{{0}}', | ||
elseCommand.getRendererBody() | ||
) | ||
].join( '' ); | ||
} | ||
return rendererBody; | ||
/** | ||
* 获取renderer body的生成代码 | ||
* | ||
* @return {string} | ||
*/ | ||
ElseCommand.prototype.getRendererBody = function () { | ||
return stringFormat( | ||
'}else{{0}', | ||
Command.prototype.getRendererBody.call(this) | ||
); | ||
}; | ||
@@ -1364,3 +1411,3 @@ | ||
* 获取renderer body的生成代码 | ||
* | ||
* | ||
* @return {string} | ||
@@ -1370,2 +1417,3 @@ */ | ||
return stringFormat( | ||
/* jshint ignore:start */ | ||
'' | ||
@@ -1377,9 +1425,10 @@ + 'var {0}={1};' | ||
+ 'for(var {4} in {0}){v[{2}]={4};v[{3}]={0}[{4}];{6}}', | ||
/* jshint ignore:end */ | ||
generateGUID(), | ||
toGetVariableLiteral( this.list ), | ||
stringLiteralize( this.index || generateGUID() ), | ||
stringLiteralize( this.item ), | ||
compileVariable(this.list, this.engine), | ||
stringLiteralize(this.index || generateGUID()), | ||
stringLiteralize(this.item), | ||
generateGUID(), | ||
generateGUID(), | ||
Command.prototype.getRendererBody.call( this ) | ||
Command.prototype.getRendererBody.call(this) | ||
); | ||
@@ -1390,3 +1439,3 @@ }; | ||
* 获取renderer body的生成代码 | ||
* | ||
* | ||
* @return {string} | ||
@@ -1402,5 +1451,5 @@ */ | ||
RENDER_STRING_ADD_END, | ||
Command.prototype.getRendererBody.call( this ), | ||
stringLiteralize( this.name ), | ||
args ? ',' + replaceGetVariableLiteral( args ) : '' | ||
Command.prototype.getRendererBody.call(this), | ||
stringLiteralize(this.name), | ||
args ? ',' + compileVariable(args, this.engine) : '' | ||
); | ||
@@ -1410,92 +1459,4 @@ }; | ||
/** | ||
* content节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ContentCommand.prototype.open = function ( context ) { | ||
autoCloseCommand( context, ContentCommand ); | ||
Command.prototype.open.call( this, context ); | ||
context.targetOrMaster.contents[ this.name ] = this; | ||
}; | ||
/** | ||
* content节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ContentPlaceHolderCommand.prototype.open = function ( context ) { | ||
autoCloseCommand( context, ContentPlaceHolderCommand ); | ||
Command.prototype.open.call( this, context ); | ||
}; | ||
/** | ||
* 节点自动闭合,解析结束 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ContentCommand.prototype.autoClose = | ||
/** | ||
* 节点自动闭合,解析结束 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
IfCommand.prototype.autoClose = Command.prototype.close; | ||
/** | ||
* 节点自动闭合,解析结束 | ||
* contentplaceholder的自动结束逻辑为,在其开始位置后马上结束 | ||
* 所以,其自动结束时children应赋予其所属的parent,也就是master | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ContentPlaceHolderCommand.prototype.autoClose = function ( context ) { | ||
var parentChildren = this.parent.children; | ||
parentChildren.push.apply( parentChildren, this.children ); | ||
this.children.length = 0; | ||
this.close( context ); | ||
}; | ||
/** | ||
* 添加子节点 | ||
* | ||
* @param {TextNode|Command} node 子节点 | ||
*/ | ||
IfCommand.prototype.addChild = function ( node ) { | ||
var elseCommand = this[ 'else' ]; | ||
( elseCommand | ||
? elseCommand.children | ||
: this.children | ||
).push( node ); | ||
}; | ||
/** | ||
* elif节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ElifCommand.prototype.open = function ( context ) { | ||
var elseCommand = new ElseCommand(); | ||
elseCommand.open( context ); | ||
var ifCommand = autoCloseCommand( context, IfCommand ); | ||
ifCommand.addChild( this ); | ||
context.stack.push( this ); | ||
}; | ||
/** | ||
* else节点open,解析开始 | ||
* | ||
* @param {Object} context 语法分析环境对象 | ||
*/ | ||
ElseCommand.prototype.open = function ( context ) { | ||
var ifCommand = autoCloseCommand( context, IfCommand ); | ||
ifCommand[ 'else' ] = this; | ||
context.stack.push( ifCommand ); | ||
}; | ||
/** | ||
* 命令类型集合 | ||
* | ||
* | ||
* @type {Object} | ||
@@ -1507,3 +1468,3 @@ */ | ||
* 添加命令类型 | ||
* | ||
* | ||
* @inner | ||
@@ -1513,24 +1474,22 @@ * @param {string} name 命令名称 | ||
*/ | ||
function addCommandType( name, Type ) { | ||
commandTypes[ name ] = Type; | ||
function addCommandType(name, Type) { | ||
commandTypes[name] = Type; | ||
Type.prototype.type = name; | ||
} | ||
addCommandType( 'target', TargetCommand ); | ||
addCommandType( 'master', MasterCommand ); | ||
addCommandType( 'content', ContentCommand ); | ||
addCommandType( 'contentplaceholder', ContentPlaceHolderCommand ); | ||
addCommandType( 'import', ImportCommand ); | ||
addCommandType( 'use', UseCommand ); | ||
addCommandType( 'var', VarCommand ); | ||
addCommandType( 'for', ForCommand ); | ||
addCommandType( 'if', IfCommand ); | ||
addCommandType( 'elif', ElifCommand ); | ||
addCommandType( 'else', ElseCommand ); | ||
addCommandType( 'filter', FilterCommand ); | ||
addCommandType('target', TargetCommand); | ||
addCommandType('block', BlockCommand); | ||
addCommandType('import', ImportCommand); | ||
addCommandType('use', UseCommand); | ||
addCommandType('var', VarCommand); | ||
addCommandType('for', ForCommand); | ||
addCommandType('if', IfCommand); | ||
addCommandType('elif', ElifCommand); | ||
addCommandType('else', ElseCommand); | ||
addCommandType('filter', FilterCommand); | ||
/** | ||
* etpl引擎类 | ||
* | ||
* | ||
* @constructor | ||
@@ -1540,15 +1499,19 @@ * @param {Object=} options 引擎参数 | ||
* @param {string=} options.commandClose 命令语法结束串 | ||
* @param {string=} options.variableOpen 变量语法起始串 | ||
* @param {string=} options.variableClose 变量语法结束串 | ||
* @param {string=} options.defaultFilter 默认变量替换的filter | ||
* @param {boolean=} options.strip 是否清除命令标签前后的空白字符 | ||
* @param {string=} options.namingConflict target或master名字冲突时的处理策略 | ||
* @param {string=} options.namingConflict target名字冲突时的处理策略 | ||
*/ | ||
function Engine( options ) { | ||
function Engine(options) { | ||
this.options = { | ||
commandOpen: '<!--', | ||
commandClose: '-->', | ||
commandSyntax: /^\s*(\/)?([a-z]+)\s*(?::([\s\S]*))?$/, | ||
variableOpen: '${', | ||
variableClose: '}', | ||
defaultFilter: 'html' | ||
}; | ||
this.config( options ); | ||
this.masters = {}; | ||
this.config(options); | ||
this.targets = {}; | ||
@@ -1560,12 +1523,14 @@ this.filters = extend({}, DEFAULT_FILTERS); | ||
* 配置引擎参数,设置的参数将被合并到现有参数中 | ||
* | ||
* | ||
* @param {Object} options 参数对象 | ||
* @param {string=} options.commandOpen 命令语法起始串 | ||
* @param {string=} options.commandClose 命令语法结束串 | ||
* @param {string=} options.variableOpen 变量语法起始串 | ||
* @param {string=} options.variableClose 变量语法结束串 | ||
* @param {string=} options.defaultFilter 默认变量替换的filter | ||
* @param {boolean=} options.strip 是否清除命令标签前后的空白字符 | ||
* @param {string=} options.namingConflict target或master名字冲突时的处理策略 | ||
* @param {string=} options.namingConflict target名字冲突时的处理策略 | ||
*/ | ||
Engine.prototype.config = function ( options ) { | ||
extend( this.options, options ); | ||
Engine.prototype.config = function (options) { | ||
extend(this.options, options); | ||
}; | ||
@@ -1575,7 +1540,7 @@ | ||
* 解析模板并编译,返回第一个target编译后的renderer函数。 | ||
* | ||
* | ||
* @param {string} source 模板源代码 | ||
* @return {function(Object):string} | ||
*/ | ||
Engine.prototype.compile = | ||
Engine.prototype.compile = | ||
@@ -1585,26 +1550,28 @@ /** | ||
* 该方法的存在为了兼容老模板引擎 | ||
* | ||
* | ||
* @param {string} source 模板源代码 | ||
* @return {function(Object):string} | ||
*/ | ||
Engine.prototype.parse = function ( source ) { | ||
if ( source ) { | ||
var targetNames = parseSource( source, this ); | ||
if ( targetNames.length ) { | ||
return this.targets[ targetNames[ 0 ] ].getRenderer(); | ||
Engine.prototype.parse = function (source) { | ||
if (source) { | ||
var targetNames = parseSource(source, this); | ||
if (targetNames.length) { | ||
return this.targets[targetNames[0]].getRenderer(); | ||
} | ||
} | ||
/* jshint -W054 */ | ||
return new Function('return ""'); | ||
/* jshint +W054 */ | ||
}; | ||
/** | ||
* 根据target名称获取编译后的renderer函数 | ||
* | ||
* | ||
* @param {string} name target名称 | ||
* @return {function(Object):string} | ||
*/ | ||
Engine.prototype.getRenderer = function ( name ) { | ||
var target = this.targets[ name ]; | ||
if ( target ) { | ||
Engine.prototype.getRenderer = function (name) { | ||
var target = this.targets[name]; | ||
if (target) { | ||
return target.getRenderer(); | ||
@@ -1615,19 +1582,4 @@ } | ||
/** | ||
* 根据target名称获取模板内容 | ||
* | ||
* @param {string} name target名称 | ||
* @return {string} | ||
*/ | ||
Engine.prototype.get = function ( name ) { | ||
var target = this.targets[ name ]; | ||
if ( target ) { | ||
return target.getContent(); | ||
} | ||
return ''; | ||
}; | ||
/** | ||
* 执行模板渲染,返回渲染后的字符串。 | ||
* | ||
* | ||
* @param {string} name target名称 | ||
@@ -1639,6 +1591,6 @@ * @param {Object=} data 模板数据。 | ||
*/ | ||
Engine.prototype.render = function ( name, data ) { | ||
var renderer = this.getRenderer( name ); | ||
if ( renderer ) { | ||
return renderer( data ); | ||
Engine.prototype.render = function (name, data) { | ||
var renderer = this.getRenderer(name); | ||
if (renderer) { | ||
return renderer(data); | ||
} | ||
@@ -1651,9 +1603,9 @@ | ||
* 增加过滤器 | ||
* | ||
* | ||
* @param {string} name 过滤器名称 | ||
* @param {Function} filter 过滤函数 | ||
*/ | ||
Engine.prototype.addFilter = function ( name, filter ) { | ||
if ( typeof filter == 'function' ) { | ||
this.filters[ name ] = filter; | ||
Engine.prototype.addFilter = function (name, filter) { | ||
if (typeof filter === 'function') { | ||
this.filters[name] = filter; | ||
} | ||
@@ -1664,3 +1616,3 @@ }; | ||
* 解析源代码 | ||
* | ||
* | ||
* @inner | ||
@@ -1671,5 +1623,6 @@ * @param {string} source 模板源代码 | ||
*/ | ||
function parseSource( source, engine ) { | ||
function parseSource(source, engine) { | ||
var commandOpen = engine.options.commandOpen; | ||
var commandClose = engine.options.commandClose; | ||
var commandSyntax = engine.options.commandSyntax; | ||
@@ -1680,3 +1633,4 @@ var stack = new Stack(); | ||
targets: [], | ||
stack: stack | ||
stack: stack, | ||
target: null | ||
}; | ||
@@ -1693,14 +1647,14 @@ | ||
function flushTextBuf() { | ||
if ( textBuf.length > 0 ) { | ||
var text = textBuf.join( '' ); | ||
var textNode = new TextNode( text, engine ); | ||
textNode.beforeAdd( analyseContext ); | ||
var text; | ||
if (textBuf.length > 0 && (text = textBuf.join(''))) { | ||
var textNode = new TextNode(text, engine); | ||
textNode.beforeAdd(analyseContext); | ||
stack.top().addTextNode( textNode ); | ||
stack.top().addChild(textNode); | ||
textBuf = []; | ||
if ( engine.options.strip | ||
&& analyseContext.current instanceof Command | ||
if (engine.options.strip | ||
&& analyseContext.current instanceof Command | ||
) { | ||
textNode.value = text.replace( /^[\x20\t\r]*\n/, '' ); | ||
textNode.value = text.replace(/^[\x20\t\r]*\n/, ''); | ||
} | ||
@@ -1713,45 +1667,32 @@ analyseContext.current = textNode; | ||
/** | ||
* 判断节点是否是NodeType类型的实例 | ||
* 用于在stack中find提供filter | ||
* | ||
* @inner | ||
* @param {Command} node 目标节点 | ||
* @return {boolean} | ||
*/ | ||
function isInstanceofNodeType( node ) { | ||
return node instanceof NodeType; | ||
} | ||
parseTextBlock( | ||
source, commandOpen, commandClose, 0, | ||
function ( text ) { // <!--...-->内文本的处理函数 | ||
var match = /^\s*(\/)?([a-z]+)\s*(:([\s\S]*))?$/.exec( text ); | ||
function (text) { // <!--...-->内文本的处理函数 | ||
var match = commandSyntax.exec(text); | ||
// 符合command规则,并且存在相应的Command类,说明是合法有含义的Command | ||
// 否则,为不具有command含义的普通文本 | ||
if ( match | ||
&& ( NodeType = commandTypes[ match[2].toLowerCase() ] ) | ||
&& typeof NodeType == 'function' | ||
if (match | ||
&& (NodeType = commandTypes[match[2].toLowerCase()]) | ||
&& typeof NodeType === 'function' | ||
) { | ||
// 先将缓冲区中的text节点内容写入 | ||
flushTextBuf(); | ||
flushTextBuf(); | ||
var currentNode = analyseContext.current; | ||
if ( engine.options.strip && currentNode instanceof TextNode ) { | ||
if (engine.options.strip && currentNode instanceof TextNode) { | ||
currentNode.value = currentNode.value | ||
.replace( /\r?\n[\x20\t]*$/, '\n' ); | ||
.replace(/\r?\n[\x20\t]*$/, '\n'); | ||
} | ||
if ( match[1] ) { | ||
currentNode = stack.find( isInstanceofNodeType ); | ||
currentNode && currentNode.close( analyseContext ); | ||
if (match[1]) { | ||
currentNode = autoCloseCommand(analyseContext, NodeType); | ||
} | ||
else { | ||
currentNode = new NodeType( match[4], engine ); | ||
if ( typeof currentNode.beforeOpen == 'function' ) { | ||
currentNode.beforeOpen( analyseContext ); | ||
currentNode = new NodeType(match[3], engine); | ||
if (typeof currentNode.beforeOpen === 'function') { | ||
currentNode.beforeOpen(analyseContext); | ||
} | ||
currentNode.open( analyseContext ); | ||
currentNode.open(analyseContext); | ||
} | ||
@@ -1761,5 +1702,5 @@ | ||
} | ||
else if ( !/^\s*\/\//.test( text ) ) { | ||
else if (!/^\s*\/\//.test(text)) { | ||
// 如果不是模板注释,则作为普通文本,写入缓冲区 | ||
textBuf.push( commandOpen, text, commandClose ); | ||
textBuf.push(commandOpen, text, commandClose); | ||
} | ||
@@ -1770,5 +1711,5 @@ | ||
function ( text ) { // <!--...-->外,普通文本的处理函数 | ||
function (text) { // <!--...-->外,普通文本的处理函数 | ||
// 普通文本直接写入缓冲区 | ||
textBuf.push( text ); | ||
textBuf.push(text); | ||
} | ||
@@ -1779,3 +1720,3 @@ ); | ||
flushTextBuf(); // 将缓冲区中的text节点内容写入 | ||
autoCloseCommand( analyseContext ); | ||
autoCloseCommand(analyseContext); | ||
@@ -1787,10 +1728,10 @@ return analyseContext.targets; | ||
etpl.Engine = Engine; | ||
if ( typeof exports == 'object' && typeof module == 'object' ) { | ||
if (typeof exports === 'object' && typeof module === 'object') { | ||
// For CommonJS | ||
exports = module.exports = etpl; | ||
} | ||
else if ( typeof define == 'function' && define.amd ) { | ||
else if (typeof define === 'function' && define.amd) { | ||
// For AMD | ||
define( etpl ); | ||
define(etpl); | ||
} | ||
@@ -1797,0 +1738,0 @@ else { |
/** | ||
* ETPL (Enterprise Template) | ||
* Copyright 2013 Baidu Inc. All rights reserved. | ||
* | ||
* | ||
* @file 加载模板的amd模块 | ||
@@ -9,27 +9,31 @@ * @author errorrik(errorrik@gmail.com) | ||
define( | ||
function ( require, exports, module ) { | ||
var etpl = require( '.' ); | ||
define( | ||
function (require, exports, module) { | ||
var etpl = require('.'); | ||
return { | ||
load: function ( resourceId, req, load, config ) { | ||
var xhr = window.XMLHttpRequest | ||
load: function (resourceId, req, load) { | ||
var xhr = window.XMLHttpRequest | ||
? new XMLHttpRequest() | ||
: new ActiveXObject( 'Microsoft.XMLHTTP' ); | ||
xhr.open( 'GET', req.toUrl( resourceId ), true ); | ||
: new ActiveXObject('Microsoft.XMLHTTP'); | ||
xhr.open('GET', req.toUrl(resourceId), true); | ||
xhr.onreadystatechange = function () { | ||
if ( xhr.readyState == 4 ) { | ||
if ( xhr.status >= 200 && xhr.status < 300 ) { | ||
if (xhr.readyState === 4) { | ||
if (xhr.status >= 200 && xhr.status < 300) { | ||
var source = xhr.responseText; | ||
var moduleConfig = module.config(); | ||
if ( typeof moduleConfig.autoCompile == 'undefined' | ||
|| moduleConfig.autoCompile | ||
if (moduleConfig.autoCompile | ||
|| moduleConfig.autoCompile == null | ||
) { | ||
etpl.compile( source ); | ||
etpl.compile(source); | ||
} | ||
load( source ); | ||
load(source); | ||
} | ||
/* jshint -W054 */ | ||
xhr.onreadystatechange = new Function(); | ||
/* jshint +W054 */ | ||
xhr = null; | ||
@@ -39,6 +43,6 @@ } | ||
xhr.send( null ); | ||
xhr.send(null); | ||
} | ||
}; | ||
} | ||
); | ||
); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
71104
12
0
1505
118