├── tsconfig.json
├── README.md
├── package.json
├── src
├── jsx.d.ts
├── index.tsx
└── template.ts
├── .gitignore
└── yarn.lock
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsxFactory": "Template.create",
4 | "jsx": "react",
5 |
6 | "moduleResolution": "node",
7 | "module": "commonjs",
8 | "outDir": "dest",
9 | "baseUrl": "src",
10 | "allowJs": false,
11 | "target": "es2017",
12 | "strictNullChecks": true,
13 | "lib": [ "es2017" ]
14 | },
15 | "exclude": [
16 | "node_modules",
17 | "dest"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | ### TypeScript JSX syntax as typed DSL
4 |
5 | This is an example repository for an article.
6 |
7 | #### Run
8 | To run the example:
9 | - Clone this repository;
10 | - Run `yarn` to install dependencies;
11 | - Run `yarn start` to run the example.
12 |
13 |
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsx-typescript-dsl",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "author": "Ike Ku ",
6 | "license": "MIT",
7 | "scripts": {
8 | "build": "tsc",
9 | "start": "tsc; node dest/index.js"
10 | },
11 | "devDependencies": {
12 | "@types/lodash": "^4.14.88",
13 | "@types/node": "^8.0.57",
14 | "typescript": "^2.6.2"
15 | },
16 | "dependencies": {
17 | "lodash": "^4.17.4"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/jsx.d.ts:
--------------------------------------------------------------------------------
1 | /* Picked up by compiler automatically */
2 | declare namespace JSX {
3 | interface Element {
4 | toMessage(): {
5 | text?: string
6 | attachments?: {
7 | text?: string
8 | title?: string
9 | title_link?: string
10 | author_name?: string
11 | author_icon?: string
12 | color?: string
13 | }[]
14 | }
15 | }
16 | interface IntrinsicElements {
17 | i: {}
18 | message: {}
19 | author: { icon: string }
20 | title: { link?: string }
21 | attachment: {
22 | color?: string
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Framework folders
2 |
3 | /log
4 | /tmp
5 |
6 | # Local configuration
7 |
8 | /.env
9 | /.envrc
10 | /.rbenv-vars
11 | /.rbenv-version
12 | /.ruby-gemset
13 | /.ruby-version
14 | /.rvmrc
15 |
16 | # Build and tool artifacts
17 |
18 | *.DS_Store
19 | *.iml
20 | /.bundle
21 | /bundle
22 | .idea
23 | .vscode
24 | .byebug_history
25 | .ipynb_checkpoints
26 |
27 | # Logs
28 | logs
29 | *.log
30 | npm-debug.log*
31 |
32 | # Runtime data
33 | pids
34 | *.pid
35 | *.seed
36 |
37 | # Dependency directories
38 | node_modules
39 | coverage
40 | .nyc_output
41 | jspm_packages
42 |
43 | # Optional npm cache directory
44 | .npm
45 |
46 | .DS_Store
47 |
48 | # Compiled
49 | dest
50 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@types/lodash@^4.14.88":
6 | version "4.14.88"
7 | resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.88.tgz#97eaf2dc668f33ed8e511a5b627035f4ea20b0f5"
8 |
9 | "@types/node@^8.0.57":
10 | version "8.0.57"
11 | resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.57.tgz#e5d8b4dc112763e35cfc51988f4f38da3c486d99"
12 |
13 | lodash@^4.17.4:
14 | version "4.17.4"
15 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
16 |
17 | typescript@^2.6.2:
18 | version "2.6.2"
19 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
20 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as Template from './template'
2 |
3 | interface Story {
4 | title: string
5 | link: string
6 | publishedAt: Date
7 | author: { name: string, avatarURL: string }
8 | }
9 |
10 | const template = (username: string, stories: Story[]) =>
11 |
12 | :wave: Hi {username}, check out our recent stories.
13 |
14 | {stories.map(s =>
15 |
16 | {s.author.name}
17 | {s.title}
18 |
19 | Published at {s.publishedAt}.
20 |
21 | )}
22 |
23 |
24 | const stories: Story[] = [
25 | {
26 | title: 'Story title',
27 | link: 'https://google.com',
28 | publishedAt: new Date(),
29 | author: {
30 | name: 'Ike Ku',
31 | avatarURL: 'https://avatars2.githubusercontent.com/u/4568573'
32 | }
33 | }
34 | ]
35 |
36 | console.log(template('User', stories).toMessage())
37 |
--------------------------------------------------------------------------------
/src/template.ts:
--------------------------------------------------------------------------------
1 | import { flatten } from 'lodash'
2 |
3 | type Kinds = keyof JSX.IntrinsicElements // Tag names
4 | type Attrubute = JSX.IntrinsicElements[K] // Tag attributes
5 |
6 | const isElement = (e: any): e is Element =>
7 | e && e.kind
8 |
9 | const is = (k: K, e: string | Element): e is Element =>
10 | isElement(e) && e.kind === k
11 |
12 | /* Concat all direct child nodes that aren't Elements (strings) */
13 | const buildText = (e: Element) =>
14 | e.children.filter(i => !isElement(i)).join('')
15 |
16 | const buildTitle = (e: Element<'title'>) => ({
17 | title: buildText(e),
18 | title_link: e.attributes.link
19 | })
20 |
21 | const buildAuthor = (e: Element<'author'>) => ({
22 | author_name: buildText(e),
23 | author_icon: e.attributes.icon
24 | })
25 |
26 | const buildAttachment = (e: Element<'attachment'>) => {
27 | const authorNode = e.children.find(i => is('author', i))
28 | const author = authorNode ? buildAuthor(>authorNode) : {}
29 |
30 | const titleNode = e.children.find(i => is('title', i))
31 | const title = titleNode ? buildTitle(>titleNode) : {}
32 |
33 | return { text: buildText(e), ...title, ...author, ...e.attributes }
34 | }
35 |
36 | class Element {
37 | children: Array>
38 |
39 | constructor(
40 | public kind: K,
41 | public attributes: Attrubute,
42 | children: Array>
43 | ) {
44 | this.children = flatten(children)
45 | }
46 |
47 | /*
48 | * Convert this Element to actual Slack message
49 | * only if it is a higher level Element — .
50 | */
51 | toMessage() {
52 | if (!is('message', this)) return {}
53 | const attachments = this.children.filter(i => is('attachment', i)).map(buildAttachment)
54 | return { attachments, text: buildText(this) }
55 | }
56 | }
57 |
58 | export const create = (kind: K, attributes: Attrubute, ...children) => {
59 | switch (kind) {
60 | case 'i': return `_${children.join('')}_`
61 | default: return new Element(kind, attributes, children)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------