初次提交
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/node_modules
|
||||
/dist
|
||||
/types
|
||||
/package-lock.json
|
||||
/.vscode
|
||||
/.env
|
4
.npmignore
Normal file
4
.npmignore
Normal file
@ -0,0 +1,4 @@
|
||||
/node_modules
|
||||
/.vscode
|
||||
/test
|
||||
/package-lock.json
|
136
README.md
Normal file
136
README.md
Normal file
@ -0,0 +1,136 @@
|
||||
# HTML模板引擎
|
||||
|
||||
使用此模板引擎,可以方便的将数据渲染到网页上。
|
||||
|
||||
## 安装
|
||||
```
|
||||
npm install @yizhi/render
|
||||
```
|
||||
|
||||
## 使用
|
||||
使用render函数,传入模板字符串和数据对象,即可渲染出网页内容。
|
||||
```typescript
|
||||
import { render } from '@yizhi/render';
|
||||
const html = render("/path/to/template.html", {name:"Join", age:20, love:["eat", "sleep", "code"] });
|
||||
console.log(html);
|
||||
```
|
||||
|
||||
### 内容输出
|
||||
使用`{{ 表达式 }}`语法,可以将表达式的值输出到网页。
|
||||
```html
|
||||
<h1>{{ data.name }}</h1>
|
||||
```
|
||||
|
||||
经过渲染后,输出的HTML内容为:
|
||||
```html
|
||||
<h1>Join</h1>
|
||||
```
|
||||
|
||||
### 条件判断
|
||||
在标签上使用`:if`和`:else`来进行条件判断,如果else有值,则表示elseif。
|
||||
|
||||
|
||||
```html
|
||||
<div :if="data.age < 3">你还是个宝宝</div>
|
||||
<div :else="data.age < 18">你还未成年</div>
|
||||
<div :else>你已经长大了</div>
|
||||
```
|
||||
|
||||
经过渲染后,输出的HTML内容为:
|
||||
```html
|
||||
<div>你已经长大了</div>
|
||||
```
|
||||
|
||||
### 循环
|
||||
在标签上使用`:for`来进行循环,支持两种循环方式:
|
||||
|
||||
- `:for="数据项 in 数据"`
|
||||
- `:for="(索引, 数据项) in 数据"`
|
||||
|
||||
其中,数据项可以是数组或对象。
|
||||
|
||||
示例:
|
||||
|
||||
```html
|
||||
<ul>
|
||||
<li :for="item in data.love">{{ item }}</li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li :for="(index, item) in data.love">{{ index }} - {{ item }}</li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li :for="(key, value) in data">{{ key }} : {{ value }}</li>
|
||||
</ul>
|
||||
```
|
||||
经过渲染后,输出的HTML内容为:
|
||||
```html
|
||||
<ul>
|
||||
<li>eat</li>
|
||||
<li>sleep</li>
|
||||
<li>code</li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li>0 - eat</li>
|
||||
<li>1 - sleep</li>
|
||||
<li>2 - code</li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li>name : Join</li>
|
||||
<li>age : 20</li>
|
||||
<li>love : eat,sleep,code</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
### 模板定义
|
||||
可以使用`template`来定义模板,通过`name`属性来指定模板名称。
|
||||
|
||||
```html
|
||||
<template name="my-template">
|
||||
<h2>{{ data.name }}</h2>
|
||||
<ul>
|
||||
<li :for="item in data.love">{{ item }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
```
|
||||
|
||||
通过上面方式,我们就定义了一个名为`my-template`的模板。
|
||||
|
||||
|
||||
### 模板使用
|
||||
通过`use`标签,可以将模板内容渲染到网页上,`data`属性指定数据对象。
|
||||
|
||||
```html
|
||||
<div>
|
||||
<h1>模板使用</h1>
|
||||
<use name="my-template" data="data"/>
|
||||
</div>
|
||||
```
|
||||
|
||||
如果模板文件在其他位置,还可以通过`from`属性指定模板文件路径。
|
||||
|
||||
```html
|
||||
<div>
|
||||
<h1>模板使用</h1>
|
||||
<use from="/path/to/my-template.html" name="my-template" data="data"/>
|
||||
</div>
|
||||
```
|
||||
|
||||
经过渲染后,输出的HTML内容为:
|
||||
```html
|
||||
<div>
|
||||
<h1>模板使用</h1>
|
||||
<h2>Join</h2>
|
||||
<ul>
|
||||
<li>eat</li>
|
||||
<li>sleep</li>
|
||||
<li>code</li>
|
||||
</ul>
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
|
16
package.json
Normal file
16
package.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "@yizhi/render",
|
||||
"version": "1.0.1",
|
||||
"main": "dist/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.9.3"
|
||||
}
|
||||
}
|
15
src/index.ts
Normal file
15
src/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
export { render } from "./render";
|
||||
export { config } from "./render/config";
|
||||
|
||||
if (require.main === module) (async function test() {
|
||||
try {
|
||||
const { render } = await import("./render");
|
||||
const res = render("test/dist/index", { id: 1, name: "yizhi", love: ["eat", "sleep", "play", "code"] });
|
||||
console.log(res);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
debugger
|
||||
}
|
||||
})();
|
||||
|
204
src/parser/elem.ts
Normal file
204
src/parser/elem.ts
Normal file
@ -0,0 +1,204 @@
|
||||
/**
|
||||
* 位置
|
||||
*/
|
||||
export class Location {
|
||||
#line: number;
|
||||
#column: number;
|
||||
#offset: number;
|
||||
|
||||
constructor(line: number, column: number, offset: number) {
|
||||
this.#line = line;
|
||||
this.#column = column;
|
||||
this.#offset = offset;
|
||||
}
|
||||
|
||||
/** 行号 */
|
||||
public get line(): number { return this.#line; }
|
||||
|
||||
/** 列号 */
|
||||
public get column(): number { return this.#column; }
|
||||
|
||||
/** 偏移 */
|
||||
public get offset(): number { return this.#offset; }
|
||||
}
|
||||
|
||||
/** 位置信息 */
|
||||
export class Position {
|
||||
#start: Location;
|
||||
#end: Location;
|
||||
|
||||
constructor(start: Location, end: Location) {
|
||||
this.#start = start;
|
||||
this.#end = end;
|
||||
}
|
||||
|
||||
/** 起始位置 */
|
||||
public get start(): Location { return this.#start; }
|
||||
|
||||
/** 结束位置 */
|
||||
public get end(): Location { return this.#end; }
|
||||
|
||||
|
||||
/** 长度 */
|
||||
public get length(): number { return this.#end.offset - this.#start.offset; }
|
||||
}
|
||||
|
||||
/** 节点 */
|
||||
export abstract class Node {
|
||||
#position: Position;
|
||||
|
||||
constructor(position: Position) {
|
||||
this.#position = position;
|
||||
}
|
||||
|
||||
/** 位置信息 */
|
||||
public get position(): Position { return this.#position; }
|
||||
}
|
||||
|
||||
/** 属性名 */
|
||||
export class ElementAttributeName extends Node {
|
||||
#name: string;
|
||||
|
||||
constructor(position: Position, name: string) {
|
||||
super(position);
|
||||
this.#name = name;
|
||||
}
|
||||
|
||||
/** 属性名 */
|
||||
public get name(): string { return this.#name; }
|
||||
}
|
||||
|
||||
/** 属性值 */
|
||||
export class ElementAttributeValue extends Node {
|
||||
#value: string;
|
||||
|
||||
constructor(position: Position, value: string) {
|
||||
super(position);
|
||||
this.#value = value;
|
||||
}
|
||||
|
||||
/** 属性值 */
|
||||
public get value(): string { return this.#value; }
|
||||
}
|
||||
|
||||
/** 元素标签 */
|
||||
export class ElementTag extends Node {
|
||||
#tagName: string;
|
||||
|
||||
constructor(position: Position, tagName: string) {
|
||||
super(position);
|
||||
this.#tagName = tagName;
|
||||
}
|
||||
|
||||
/** 标签名 */
|
||||
public get tagName(): string { return this.#tagName; }
|
||||
}
|
||||
|
||||
/** 元素属性 */
|
||||
export class ElementAttribute extends Node {
|
||||
#name: ElementAttributeName;
|
||||
#value: ElementAttributeValue | null;
|
||||
#control: boolean;
|
||||
|
||||
constructor(name: ElementAttributeName, value: ElementAttributeValue | null, control: boolean) {
|
||||
super(new Position(name.position.start, (value ?? name).position.end));
|
||||
this.#name = name;
|
||||
this.#value = value;
|
||||
this.#control = control;
|
||||
}
|
||||
|
||||
/** 属性名 */
|
||||
public get name(): ElementAttributeName { return this.#name; }
|
||||
|
||||
/** 属性值 */
|
||||
public get value(): ElementAttributeValue | null { return this.#value; }
|
||||
|
||||
/** 是否控制属性 */
|
||||
public get control(): boolean { return this.#control; }
|
||||
}
|
||||
|
||||
/** 元素 */
|
||||
export class Element extends Node {
|
||||
#tag: ElementTag;
|
||||
#attributes: ElementAttribute[];
|
||||
#children: (Element | Text | Comment)[];
|
||||
#selfClosing: boolean;
|
||||
|
||||
constructor(position: Position, tag: ElementTag, attributes: ElementAttribute[], selfClosing: boolean, children: (Element | Text | Comment)[]) {
|
||||
super(position);
|
||||
this.#tag = tag;
|
||||
this.#attributes = attributes;
|
||||
this.#selfClosing = selfClosing;
|
||||
this.#children = children;
|
||||
}
|
||||
|
||||
/** 标签 */
|
||||
public get tag() { return this.#tag; }
|
||||
|
||||
/** 属性列表 */
|
||||
public get attributes() { return this.#attributes; }
|
||||
|
||||
/** 标签名 */
|
||||
public get tagName() { return this.#tag.tagName; }
|
||||
|
||||
/** 是否自闭合 */
|
||||
public get selfClosing() { return this.#selfClosing; }
|
||||
|
||||
/** 子节点列表 */
|
||||
public get children() { return this.#children; }
|
||||
}
|
||||
|
||||
/** 文本节点 */
|
||||
export class Text extends Node {
|
||||
#value: string;
|
||||
|
||||
constructor(position: Position, value: string) {
|
||||
super(position);
|
||||
this.#value = value;
|
||||
}
|
||||
|
||||
/** 文本内容 */
|
||||
public get value(): string { return this.#value; }
|
||||
}
|
||||
|
||||
/** 文档类型 */
|
||||
export class DocType extends Node {
|
||||
#items: string[];
|
||||
|
||||
constructor(position: Position, items: string[]) {
|
||||
super(position);
|
||||
this.#items = items;
|
||||
}
|
||||
|
||||
/** 文档类型信息 */
|
||||
public get items(): string[] { return this.#items; }
|
||||
}
|
||||
|
||||
/** 注释 */
|
||||
export class Comment extends Node {
|
||||
#value: string;
|
||||
|
||||
constructor(position: Position, value: string) {
|
||||
super(position);
|
||||
this.#value = value;
|
||||
}
|
||||
|
||||
/** 注释内容 */
|
||||
public get value(): string { return this.#value; }
|
||||
}
|
||||
|
||||
/** 文档 */
|
||||
export class Document {
|
||||
#doctype: DocType | null = null;
|
||||
#children: (Element | Text | Comment)[] = [];
|
||||
|
||||
/** 文档类型 */
|
||||
public set doctype(doctype: DocType | null) { this.#doctype = doctype; }
|
||||
public get doctype() { return this.#doctype; }
|
||||
|
||||
/** 子节点列表 */
|
||||
public get children() { return this.#children; }
|
||||
|
||||
/** 添加子节点 */
|
||||
public addChild(child: Element | Text | Comment) { this.#children.push(child); }
|
||||
}
|
16
src/parser/error.ts
Normal file
16
src/parser/error.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Location } from "./elem";
|
||||
|
||||
/**
|
||||
* HTML解析错误
|
||||
*/
|
||||
export class ParseError extends Error {
|
||||
#location: Location;
|
||||
constructor(location: Location, message: string) {
|
||||
super(message);
|
||||
this.#location = location;
|
||||
}
|
||||
|
||||
get location(): Location {
|
||||
return this.#location;
|
||||
}
|
||||
}
|
3
src/parser/index.ts
Normal file
3
src/parser/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./elem";
|
||||
export * from "./error";
|
||||
export * from "./parser";
|
243
src/parser/parser.ts
Normal file
243
src/parser/parser.ts
Normal file
@ -0,0 +1,243 @@
|
||||
import fs from 'fs';
|
||||
import { Comment, DocType, Document, Element, ElementAttribute, ElementAttributeName, ElementAttributeValue, ElementTag, Location, Position, Text } from './elem';
|
||||
import { ParseError } from './error';
|
||||
|
||||
export class Parser {
|
||||
#filePath: string;
|
||||
#content: string;
|
||||
#line: number = 0;
|
||||
#column: number = 0;
|
||||
#offset: number = 0;
|
||||
#document!: Document;
|
||||
|
||||
private constructor(filePath: string, content: string) {
|
||||
this.#filePath = filePath;
|
||||
this.#content = content;
|
||||
}
|
||||
|
||||
public static fromFile(filePath: string) { return new Parser(filePath, fs.readFileSync(filePath, 'utf-8')); }
|
||||
|
||||
public static fromString(content: string, filePath: string) { return new Parser(filePath, content); }
|
||||
|
||||
public parse() {
|
||||
this.#line = 0;
|
||||
this.#column = 0;
|
||||
this.#offset = 0;
|
||||
this.#document = new Document();
|
||||
this.#parse().forEach(e => this.#document.addChild(e));
|
||||
return this.#document;
|
||||
}
|
||||
|
||||
private is(text: string, offset: number = 0, caseSensitive: boolean = false) {
|
||||
const sub = this.#content.slice(this.#offset + offset, this.#offset + offset + text.length);
|
||||
if (caseSensitive) return sub === text;
|
||||
else return sub.toLowerCase() === text.toLowerCase();
|
||||
}
|
||||
|
||||
private go(length: number) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (this.#content[this.#offset + i] === '\n') {
|
||||
this.#line++;
|
||||
this.#column = 0;
|
||||
}
|
||||
else this.#column++;
|
||||
}
|
||||
this.#offset += length;
|
||||
}
|
||||
|
||||
private eof() {
|
||||
return this.#offset >= this.#content.length;
|
||||
}
|
||||
|
||||
private eat(text: string, caseSensitive: boolean = false) {
|
||||
if (this.is(text, 0, caseSensitive)) {
|
||||
this.go(text.length);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private eatSpace(required: boolean = false) {
|
||||
const start = this.#offset;
|
||||
while (true) {
|
||||
const ch = this.#content[this.#offset];
|
||||
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') this.go(1);
|
||||
else break;
|
||||
}
|
||||
if (required && start === this.#offset) {
|
||||
throw new ParseError(new Location(this.#line, this.#column, this.#offset), `此处应该有空白`);
|
||||
}
|
||||
}
|
||||
|
||||
private eatString() {
|
||||
const start = new Location(this.#line, this.#column, this.#offset);
|
||||
const quote = this.#content[this.#offset];
|
||||
if (quote !== '"' && quote !== '\'') throw new ParseError(start, `字符串应该以双引号或单引号开始`);
|
||||
let finished = false;
|
||||
this.go(1);
|
||||
while (!this.eof()) {
|
||||
const ch = this.#content[this.#offset];
|
||||
if (ch === quote) {
|
||||
this.go(1);
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
else if (ch === '\\') this.go(2);
|
||||
else this.go(1);
|
||||
}
|
||||
const end = new Location(this.#line, this.#column, this.#offset);
|
||||
if (!finished) throw new ParseError(end, `字符串应该以双引号或单引号结束`);
|
||||
return { start, end, text: this.#content.slice(start.offset, end.offset) };
|
||||
}
|
||||
|
||||
private eatComment() {
|
||||
let finished = false;
|
||||
let start = this.#offset;
|
||||
let end = start;
|
||||
while (!this.eof()) {
|
||||
if (this.is('-->')) {
|
||||
end = this.#offset;
|
||||
this.go(3);
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
this.go(1);
|
||||
}
|
||||
if (!finished) throw new ParseError(new Location(this.#line, this.#column, this.#offset), `注释应该以 --> 结束`);
|
||||
return { text: this.#content.slice(start, end) };
|
||||
}
|
||||
|
||||
#isIDentifierChar(code: number) {
|
||||
if ((code >= 97 && code <= 122) || // a-z
|
||||
(code >= 65 && code <= 90) || // A-Z
|
||||
(code >= 48 && code <= 57) || // 0-9
|
||||
code === 95 || // _
|
||||
code === 45) // -
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private eatIdentifier() {
|
||||
const start = new Location(this.#line, this.#column, this.#offset);
|
||||
while (true) {
|
||||
const code = this.#content.codePointAt(this.#offset);
|
||||
if (!code) break;
|
||||
if (!this.#isIDentifierChar(code)) break;
|
||||
this.go(1);
|
||||
}
|
||||
const end = new Location(this.#line, this.#column, this.#offset);
|
||||
if (start.offset === end.offset) throw new ParseError(start, `标识符不能为空`);
|
||||
return { start, end, text: this.#content.slice(start.offset, end.offset) };
|
||||
}
|
||||
|
||||
private eatText() {
|
||||
const start = this.#offset;
|
||||
while (!this.eof() && !this.is('<')) this.go(1);
|
||||
const end = this.#offset;
|
||||
return { start, end, text: this.#content.slice(start, end) };
|
||||
}
|
||||
|
||||
#parse(): Array<Element | Text | Comment> {
|
||||
const items: Array<Element | Text | Comment> = [];
|
||||
while (!this.eof()) {
|
||||
const start = new Location(this.#line, this.#column, this.#offset);
|
||||
// DOCTYPE
|
||||
if (this.eat('<!DOCTYPE')) {
|
||||
const items = this.#parseDoctype();
|
||||
this.#document.doctype = new DocType(new Position(start, new Location(this.#line, this.#column, this.#offset)), items);
|
||||
}
|
||||
// 注释
|
||||
else if (this.eat("<!--")) {
|
||||
const { text } = this.eatComment();
|
||||
items.push(new Comment(new Position(start, new Location(this.#line, this.#column, this.#offset)), text));
|
||||
}
|
||||
//结束
|
||||
else if (this.is("</")) break;
|
||||
// 标签
|
||||
else if (this.eat("<")) {
|
||||
const { tag, attrs, closed } = this.#parseElement();
|
||||
//闭合
|
||||
if (closed || ["br", "hr", "img", "input", "meta", "link", "base", "col", "command", "param", "source", "track", "wbr"].includes(tag.tagName.toLowerCase())) {
|
||||
items.push(new Element(
|
||||
new Position(start, new Location(this.#line, this.#column, this.#offset)),
|
||||
tag, attrs, true, [],
|
||||
))
|
||||
}
|
||||
//非闭合
|
||||
else {
|
||||
const children = this.#parse();
|
||||
if (!this.eat("</" + tag.tagName + ">")) throw new ParseError(new Location(this.#line, this.#column, this.#offset), `标签 ${tag.tagName} 应该有结束标签`);
|
||||
items.push(new Element(
|
||||
new Position(start, new Location(this.#line, this.#column, this.#offset)),
|
||||
tag, attrs, false, children,
|
||||
));
|
||||
}
|
||||
}
|
||||
//文本
|
||||
else {
|
||||
const { text } = this.eatText();
|
||||
items.push(new Text(new Position(start, new Location(this.#line, this.#column, this.#offset)), text));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
#parseDoctype() {
|
||||
this.eatSpace(true);
|
||||
const name = this.eatIdentifier();
|
||||
const items: string[] = [];
|
||||
while (!this.eof() && !this.eat('>')) {
|
||||
this.eatSpace(true);
|
||||
if (this.is('"') || this.is("'")) {
|
||||
const item = this.eatString();
|
||||
items.push(item.text);
|
||||
}
|
||||
else {
|
||||
const item = this.eatIdentifier();
|
||||
items.push(item.text);
|
||||
}
|
||||
}
|
||||
return [name.text, ...items];
|
||||
}
|
||||
|
||||
#parseElement() {
|
||||
const name = this.eatIdentifier();
|
||||
const attrs: Array<ElementAttribute> = [];
|
||||
let closed = false;
|
||||
let gotBooleanAttribute = false;
|
||||
while (!this.eat('>')) {
|
||||
if (this.eat("/>")) {
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
this.eatSpace(!gotBooleanAttribute);
|
||||
let isControlAttribute = false;
|
||||
if (this.eat(":")) isControlAttribute = true;
|
||||
else if (this.eat("/>")) {
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
const name = this.eatIdentifier();
|
||||
this.eatSpace(false);
|
||||
if (this.eat("=")) {
|
||||
gotBooleanAttribute = false;
|
||||
this.eatSpace(false);
|
||||
const value = this.eatString();
|
||||
attrs.push(new ElementAttribute(
|
||||
new ElementAttributeName(new Position(name.start, name.end), name.text),
|
||||
new ElementAttributeValue(new Position(value.start, value.end), value.text),
|
||||
isControlAttribute,
|
||||
));
|
||||
}
|
||||
else {
|
||||
gotBooleanAttribute = true;
|
||||
attrs.push(new ElementAttribute(new ElementAttributeName(new Position(name.start, name.end), name.text), null, isControlAttribute));
|
||||
}
|
||||
}
|
||||
return {
|
||||
tag: new ElementTag(new Position(name.start, new Location(this.#line, this.#column, this.#offset)), name.text),
|
||||
attrs,
|
||||
closed,
|
||||
};
|
||||
}
|
||||
}
|
22
src/render/config.ts
Normal file
22
src/render/config.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/** 渲染配置 */
|
||||
interface IRenderConfig {
|
||||
/** 是否使用缓存 */
|
||||
cache: boolean
|
||||
/** 扩展名 */
|
||||
extname: string
|
||||
}
|
||||
|
||||
/** 全局渲染配置 */
|
||||
export const renderConfig: IRenderConfig = {
|
||||
cache: true,
|
||||
extname: '.html'
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置渲染器
|
||||
* @param key 配置键
|
||||
* @param value 配置值
|
||||
*/
|
||||
export function config<K extends keyof IRenderConfig>(key: K, value: IRenderConfig[K]) {
|
||||
renderConfig[key] = value;
|
||||
}
|
218
src/render/element.ts
Normal file
218
src/render/element.ts
Normal file
@ -0,0 +1,218 @@
|
||||
import path from "path";
|
||||
import { __system__, check } from "./util";
|
||||
|
||||
export abstract class HTMLNode {
|
||||
public abstract build(...args: string[]): string;
|
||||
}
|
||||
|
||||
export class HTMLDocument extends HTMLNode {
|
||||
#doctype: string | null = null;
|
||||
#children: Array<HTMLElement | HTMLComment | HTMLText> = [];
|
||||
#templates: Array<HTMLTemplate> = [];
|
||||
|
||||
public setDoctype(doctype: string) {
|
||||
this.#doctype = doctype;
|
||||
return this;
|
||||
}
|
||||
|
||||
public addChild(child: HTMLElement | HTMLComment | HTMLText) {
|
||||
this.#children.push(child);
|
||||
return this;
|
||||
}
|
||||
|
||||
public addTemplate(template: HTMLTemplate) {
|
||||
this.#templates.push(template);
|
||||
return this;
|
||||
}
|
||||
|
||||
public get doctype() { return this.#doctype; }
|
||||
public get templates() { return this.#templates; }
|
||||
public get children() { return this.#children; }
|
||||
|
||||
public build() {
|
||||
const buffer: string[] = [];
|
||||
if (this.doctype) buffer.push(this.doctype);
|
||||
for (const child of this.children) buffer.push(child.build());
|
||||
return buffer.join("");
|
||||
}
|
||||
}
|
||||
|
||||
export class HTMLElement extends HTMLNode {
|
||||
#tag: string;
|
||||
#attributes: Array<{ name: string, value: string | null }> = [];
|
||||
#controls: Array<{ name: string, value: string }> = [];
|
||||
#children: Array<HTMLElement | HTMLComment | HTMLText> = [];
|
||||
#closed: boolean;
|
||||
#ifKey: number | null = null;
|
||||
|
||||
constructor(tag: string, closed: boolean) {
|
||||
super();
|
||||
this.#tag = tag;
|
||||
this.#closed = closed;
|
||||
}
|
||||
|
||||
public addAttribute(name: string, value: string | null) {
|
||||
this.#attributes.push({ name, value });
|
||||
return this;
|
||||
}
|
||||
|
||||
public addControl(name: string, value: string) {
|
||||
if (!value) value = "false";
|
||||
if (value.startsWith("'") || value.startsWith('"')) value = value.slice(1, -1);
|
||||
this.#controls.push({ name, value });
|
||||
return this;
|
||||
}
|
||||
|
||||
public setIf(key: number | null) {
|
||||
this.#ifKey = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public get ifKey() { return this.#ifKey }
|
||||
|
||||
public addChild(child: HTMLElement | HTMLComment | HTMLText) {
|
||||
this.#children.push(child);
|
||||
return this;
|
||||
}
|
||||
|
||||
public get tag() { return this.#tag; }
|
||||
public get attributes() { return this.#attributes; }
|
||||
public get controls() { return this.#controls; }
|
||||
public get closed() { return this.#closed; }
|
||||
public get children() { return this.#children; }
|
||||
|
||||
|
||||
public build() {
|
||||
const resolveControl = (controls: Array<{ name: string, value: string }>, action: () => string): string => {
|
||||
if (!controls.length) return action();
|
||||
const [control, ...rest] = controls;
|
||||
|
||||
if (control.name === "show") {
|
||||
return `\${__system__.if(${check(control.value, "false")}, null, __context__, ()=>\`${resolveControl(rest, action)}\`)}`;
|
||||
}
|
||||
if (control.name === "hide") {
|
||||
return `\${__system__.if(${check(`!(${control.value})`, "false")}, null, __context__, ()=>\`${resolveControl(rest, action)}\`)}`;
|
||||
}
|
||||
else if (control.name === "for") {
|
||||
const inIndex = control.value.indexOf(" in ");
|
||||
if (inIndex <= 0) return "";
|
||||
const itVal = control.value.slice(0, inIndex).trim();
|
||||
const itData = control.value.slice(inIndex + 4).trim().split(" as ");
|
||||
if (!itVal.length || !itData.length) return "";
|
||||
|
||||
let itKey: string | null = null;
|
||||
let itValue: string | null = null;
|
||||
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(itVal)) itValue = itVal;
|
||||
else {
|
||||
const match = itVal.match(/^\(\s*([a-zA-Z0-9_][a-zA-Z0-9_]*)\s*,\s*([a-zA-Z0-9_][a-zA-Z0-9_]*)\s*\)$/);
|
||||
if (match) {
|
||||
itKey = match[1];
|
||||
itValue = match[2];
|
||||
}
|
||||
}
|
||||
if (!itValue) return "";
|
||||
return `\${__system__.for(${check(itData)}, (${itValue}${itKey ? `, ${itKey}` : ""})=>\`${resolveControl(rest, action)}\`)}`;
|
||||
}
|
||||
else if (control.name === "if") {
|
||||
return `\${__system__.if(${check(control.value)}, ${this.ifKey}, __context__, ()=>\`${resolveControl(rest, action)}\`)}`;
|
||||
}
|
||||
else if (control.name === "else") {
|
||||
if (!this.ifKey) return "";
|
||||
if (control.value.length) return `\${__system__.elseIf(${check(control.value)}, ${this.ifKey}, __context__, ()=>\`${resolveControl(rest, action)}\`)}`;
|
||||
else return `\${__system__.else(${this.ifKey}, __context__, ()=>\`${resolveControl(rest, action)}\`)}`;
|
||||
}
|
||||
else return `\`\``;
|
||||
}
|
||||
|
||||
return resolveControl(this.controls, () => {
|
||||
if (this.tag === "use") {
|
||||
const from = this.attributes.find(a => a.name === "from")?.value;
|
||||
const name = this.attributes.find(a => a.name === "name")?.value;
|
||||
const data = this.attributes.find(a => a.name === "data")?.value;
|
||||
return `\${__system__.use(${from ? JSON.stringify(from) : "__filename__"}, ${JSON.stringify(name)}, ${data ?? "null"}, __dirname__)}`
|
||||
}
|
||||
else {
|
||||
const buffer = [`<${this.tag}`];
|
||||
for (const { name, value } of this.attributes) {
|
||||
if (value === null) buffer.push(` ${name}`);
|
||||
else buffer.push(` ${name}=${value.replace(/\{\{(.+?)\}\}/g, "${$1}")}`);
|
||||
}
|
||||
if (this.closed) buffer.push(` />`);
|
||||
else {
|
||||
buffer.push(">");
|
||||
for (const child of this.children) {
|
||||
buffer.push(child.build());
|
||||
}
|
||||
buffer.push(`</${this.tag}>`);
|
||||
}
|
||||
return buffer.join("");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class HTMLText extends HTMLNode {
|
||||
#text: string;
|
||||
constructor(text: string) {
|
||||
super();
|
||||
this.#text = text;
|
||||
}
|
||||
|
||||
public get text() { return this.#text; }
|
||||
|
||||
|
||||
public build() {
|
||||
return this.text.replace(/\`/g, "\\`").replace(/\$/g, "\\$").replace(/\{\{(.+?)\}\}/g, "${$1}");
|
||||
}
|
||||
}
|
||||
|
||||
export class HTMLComment extends HTMLNode {
|
||||
#comment: string;
|
||||
constructor(comment: string) {
|
||||
super();
|
||||
this.#comment = comment;
|
||||
}
|
||||
public get comment() { return this.#comment; }
|
||||
|
||||
|
||||
public build() {
|
||||
return `<!--${this.comment}-->`;
|
||||
}
|
||||
}
|
||||
|
||||
export class HTMLTemplate extends HTMLNode {
|
||||
#name: string;
|
||||
#argument: string;
|
||||
#children: Array<HTMLElement | HTMLComment | HTMLText> = [];
|
||||
#filename: string;
|
||||
#cache: ((data: any) => string) | null = null;
|
||||
|
||||
constructor(name: string, argument: string, filename: string) {
|
||||
super();
|
||||
this.#name = name;
|
||||
this.#argument = argument;
|
||||
this.#filename = path.resolve(process.cwd(), filename);
|
||||
}
|
||||
|
||||
public addChild(child: HTMLElement | HTMLComment | HTMLText) {
|
||||
this.#children.push(child);
|
||||
return this;
|
||||
}
|
||||
|
||||
public get name() { return this.#name; }
|
||||
public get argument() { return this.#argument; }
|
||||
public get children() { return this.#children; }
|
||||
|
||||
public build() {
|
||||
return this.children.map(child => child.build()).join("");
|
||||
}
|
||||
|
||||
public resolve() {
|
||||
if (!this.#cache) {
|
||||
const body = this.build();
|
||||
const func = new Function("data", "__system__", "__context__", "__filename__", "__dirname__", `return \`${body}\``);
|
||||
this.#cache = (data: any) => func(data, __system__, { if: 0 }, this.#filename, path.dirname(this.#filename));
|
||||
}
|
||||
return this.#cache;
|
||||
}
|
||||
}
|
1
src/render/index.ts
Normal file
1
src/render/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { render } from "./render";
|
147
src/render/module.ts
Normal file
147
src/render/module.ts
Normal file
@ -0,0 +1,147 @@
|
||||
|
||||
import path from "path";
|
||||
import { Comment, Element, Parser, Text } from "../parser";
|
||||
import { HTMLComment, HTMLDocument, HTMLElement, HTMLTemplate, HTMLText } from "./element";
|
||||
import { __system__ } from "./util";
|
||||
import { renderConfig } from "./config";
|
||||
|
||||
export class Module {
|
||||
#parser: Parser;
|
||||
#document: HTMLDocument;
|
||||
#cache: ((data: any) => string) | null = null;
|
||||
static #modules: Map<string, Module> = new Map();
|
||||
|
||||
public static get(filename: string) {
|
||||
if (path.extname(filename) !== renderConfig.extname) filename += renderConfig.extname;
|
||||
//使用缓存
|
||||
if (renderConfig.cache) {
|
||||
if (!this.#modules.has(filename)) {
|
||||
this.#modules.set(filename, new Module(filename));
|
||||
}
|
||||
return Module.#modules.get(filename)!;
|
||||
}
|
||||
//不是有缓存
|
||||
else return new Module(filename);
|
||||
}
|
||||
|
||||
private constructor(public readonly filename: string) {
|
||||
this.#parser = Parser.fromFile(filename);
|
||||
this.#document = new HTMLDocument();
|
||||
const doc = this.#parser.parse();
|
||||
if (doc.doctype) this.#document.setDoctype(`<!DOCTYPE ${doc.doctype.items.join(' ')}>`);
|
||||
this.#resolve(this.#document, this.#document, doc.children, { if: 0 });
|
||||
Module.#modules.set(filename, this);
|
||||
}
|
||||
|
||||
public template(name: string) {
|
||||
return this.#document.templates.find(t => t.name === name) ?? null;
|
||||
}
|
||||
|
||||
#findLastElement(children: Array<HTMLElement | HTMLComment | HTMLText>): HTMLElement | null {
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
if (children[i] instanceof HTMLElement) return children[i] as HTMLElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#resolve(root: HTMLDocument, parent: HTMLElement | HTMLDocument | HTMLTemplate, children: Array<Element | Text | Comment>, context: { if: number }) {
|
||||
for (const child of children) {
|
||||
if (child instanceof Element) {
|
||||
const tag = child.tagName;
|
||||
const lowerCaseTag = tag.toLowerCase();
|
||||
|
||||
// 模板
|
||||
if (lowerCaseTag === "template") {
|
||||
const nameAttr = child.attributes.find(a => a.name.name === "name");
|
||||
if (!nameAttr?.value) throw new Error("template element must have a name attribute");
|
||||
let name = nameAttr.value.value;
|
||||
if (name.startsWith("'") || name.startsWith('"')) name = name.slice(1, -1);
|
||||
|
||||
const argumentAttr = child.attributes.find(a => a.name.name === "argument");
|
||||
let argument = "data";
|
||||
if (argumentAttr?.value) {
|
||||
const value = argumentAttr.value.value;
|
||||
if (value.startsWith("'") || value.startsWith('"')) argument = value.slice(1, -1);
|
||||
else argument = value;
|
||||
}
|
||||
const template = new HTMLTemplate(name, argument, this.filename);
|
||||
root.addTemplate(template);
|
||||
|
||||
if (child.children.length) this.#resolve(root, template, child.children, context);
|
||||
}
|
||||
|
||||
// 模板引用
|
||||
else if (lowerCaseTag === "use") {
|
||||
const fromAttr = child.attributes.find(a => a.name.name === "from");
|
||||
let from: string | null = null;
|
||||
if (fromAttr) {
|
||||
if (!fromAttr.value) throw new Error("use element must have a from attribute");
|
||||
from = fromAttr.value.value;
|
||||
if (from.startsWith("'") || from.startsWith('"')) from = from.slice(1, -1);
|
||||
}
|
||||
|
||||
const nameAttr = child.attributes.find(a => a.name.name === "name");
|
||||
if (!nameAttr?.value) throw new Error("use element must have a name attribute");
|
||||
let name = nameAttr.value.value;
|
||||
if (name.startsWith("'") || name.startsWith('"')) name = name.slice(1, -1);
|
||||
|
||||
const dataAttr = child.attributes.find(a => a.name.name === "data");
|
||||
let data: string | null = null;
|
||||
if (dataAttr?.value) {
|
||||
const value = dataAttr.value.value;
|
||||
data = value.startsWith("'") || value.startsWith('"') ? value.slice(1, -1) : value;
|
||||
}
|
||||
|
||||
const use = new HTMLElement("use", true);
|
||||
if (from) use.addAttribute("from", from);
|
||||
use.addAttribute("name", name);
|
||||
if (data) use.addAttribute("data", data);
|
||||
|
||||
for (const attr of child.attributes) {
|
||||
const name = attr.name.name;
|
||||
if (attr.control) {
|
||||
use.addControl(name, attr.value?.value ?? "");
|
||||
if (name === "if") use.setIf(++context.if);
|
||||
else if (name === "else") use.setIf(this.#findLastElement(parent.children)?.ifKey ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
parent.addChild(use);
|
||||
}
|
||||
|
||||
//普通元素
|
||||
else {
|
||||
const element = new HTMLElement(tag, child.selfClosing);
|
||||
for (const attr of child.attributes) {
|
||||
const name = attr.name.name;
|
||||
if (attr.control) {
|
||||
element.addControl(name, attr.value?.value ?? "");
|
||||
if (name === "if") element.setIf(++context.if);
|
||||
else if (name === "else") element.setIf(this.#findLastElement(parent.children)?.ifKey ?? null);
|
||||
}
|
||||
else element.addAttribute(name, attr.value?.value ?? null);
|
||||
}
|
||||
parent.addChild(element);
|
||||
this.#resolve(root, element, child.children, context);
|
||||
}
|
||||
}
|
||||
//文本节点
|
||||
else if (child instanceof Text) {
|
||||
parent.addChild(new HTMLText(child.value));
|
||||
}
|
||||
//注释
|
||||
else if (child instanceof Comment) {
|
||||
parent.addChild(new HTMLComment(child.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve() {
|
||||
if (!this.#cache) {
|
||||
const body = this.#document.build();
|
||||
const func = new Function("data", "__system__", "__context__", "__filename__", "__dirname__", `return \`${body}\`;`);
|
||||
this.#cache = (data: any) => func(data, __system__, { if: 0 }, this.filename, path.dirname(this.filename));
|
||||
}
|
||||
return this.#cache;
|
||||
}
|
||||
}
|
13
src/render/render.ts
Normal file
13
src/render/render.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import path from "path";
|
||||
import { Module } from "./module"
|
||||
|
||||
/**
|
||||
* 渲染HTML页面
|
||||
* @param filename 模板文件路径
|
||||
* @param data 要渲染的数据
|
||||
* @returns 渲染好的HTML页面
|
||||
*/
|
||||
export function render(filename: string, data: any) {
|
||||
filename = path.resolve(process.cwd(), filename);
|
||||
return Module.get(filename).resolve()(data);
|
||||
}
|
57
src/render/util.ts
Normal file
57
src/render/util.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { Module } from "./module";
|
||||
|
||||
export function check(value: any, defaultValue: any = null) {
|
||||
return `(()=>{ try{ return (${value})??(${defaultValue}) } catch(e){ return (${defaultValue}) } })()`
|
||||
}
|
||||
|
||||
export const __system__ = {
|
||||
for: (value: any, callback: (value: any, key: any) => any) => {
|
||||
const result: any[] = [];
|
||||
if (value === null || value === undefined) return "";
|
||||
if (value instanceof Array) {
|
||||
for (const [key, item] of value.entries()) {
|
||||
result.push(callback(item, key));
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const [key, item] of Object.entries(value)) {
|
||||
result.push(callback(item, key));
|
||||
}
|
||||
}
|
||||
return result.join("");
|
||||
},
|
||||
if: (value: any, key: number, context: Record<number, boolean>, callback: () => any): any => {
|
||||
if (value) {
|
||||
context[key] = true;
|
||||
return callback();
|
||||
}
|
||||
else {
|
||||
context[key] = false;
|
||||
return "";
|
||||
}
|
||||
},
|
||||
elseIf: (value: any, key: number, context: Record<number, boolean>, callback: () => any) => {
|
||||
if (context[key]) return "";
|
||||
if (value) {
|
||||
context[key] = true;
|
||||
return callback();
|
||||
}
|
||||
else {
|
||||
context[key] = false;
|
||||
return "";
|
||||
}
|
||||
},
|
||||
else: (key: number, context: Record<number, boolean>, callback: () => any) => {
|
||||
if (context[key]) return "";
|
||||
context[key] = true;
|
||||
return callback();
|
||||
},
|
||||
use: (file: string, name: string, data: any, dirname: string) => {
|
||||
const filename = path.resolve(dirname, file);
|
||||
const template = Module.get(filename).template(name);
|
||||
if (!template) return "";
|
||||
return template.resolve()(data);
|
||||
}
|
||||
}
|
19
test/main.html
Normal file
19
test/main.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
|
||||
<title>{{data.name}}Love</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{data.name}}</h1>
|
||||
<div id="love-list">
|
||||
<use :for="item in data.love" from="./mod1" name="love-item" data="item"/>
|
||||
${afdfs}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
8
test/mod1.html
Normal file
8
test/mod1.html
Normal file
@ -0,0 +1,8 @@
|
||||
<template name="love-item">
|
||||
<div data-love="{{data}}">
|
||||
<span :if="data=='eat'">I love eating</span>
|
||||
<span :else="data=='sleep'">I love sleeping</span>
|
||||
<span :else="data=='play'">I love playing</span>
|
||||
<span :else>I don't love anything</span>
|
||||
</div>
|
||||
</template>
|
108
tsconfig.json
Normal file
108
tsconfig.json
Normal file
@ -0,0 +1,108 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
"inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
"declarationDir": "./types", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user