├── .prettierignore
├── .gitignore
├── examples
├── example-koa
│ ├── .gitignore
│ ├── src
│ │ ├── views
│ │ │ ├── home.tsx
│ │ │ ├── users.tsx
│ │ │ ├── user-details.tsx
│ │ │ └── layout.tsx
│ │ ├── routes
│ │ │ ├── home.ts
│ │ │ └── users.ts
│ │ ├── routes.ts
│ │ ├── global.ts
│ │ └── index.ts
│ ├── README.md
│ ├── tsconfig.json
│ └── package.json
├── example-tsx-to-html
│ ├── .gitignore
│ ├── src
│ │ ├── pages
│ │ │ └── index.tsx
│ │ ├── types.ts
│ │ └── layout.tsx
│ ├── tsconfig.json
│ ├── package.json
│ └── README.md
└── example-ttypescript
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── package.json
│ ├── README.md
│ └── src
│ └── home.tsx
├── src
├── modules.d.ts
├── index.test.ts
├── utils.ts
└── index.ts
├── tsconfig-build.json
├── greenkeeper.json
├── tsconfig.json
├── .travis.yml
├── LICENSE
├── package.json
├── CODE_OF_CONDUCT.md
├── CHANGELOG.md
└── README.md
/.prettierignore:
--------------------------------------------------------------------------------
1 | src/__fixtures/**/*
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | dist/
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/examples/example-koa/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | package-lock.json
--------------------------------------------------------------------------------
/examples/example-tsx-to-html/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/examples/example-ttypescript/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | src/home.js
4 |
--------------------------------------------------------------------------------
/src/modules.d.ts:
--------------------------------------------------------------------------------
1 | declare module "safe-eval" {
2 | function safeEval(content: string, fileName?: string): any;
3 | export default safeEval;
4 | }
5 |
--------------------------------------------------------------------------------
/examples/example-koa/src/views/home.tsx:
--------------------------------------------------------------------------------
1 | import Base from "./layout";
2 |
3 | export default () => (
4 |
5 |
Hello World
6 |
7 | );
8 |
--------------------------------------------------------------------------------
/examples/example-tsx-to-html/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "../layout";
2 |
3 | export default () => (
4 |
5 | Hello World
6 |
7 | );
8 |
--------------------------------------------------------------------------------
/examples/example-koa/src/routes/home.ts:
--------------------------------------------------------------------------------
1 | import { Context } from "koa";
2 | import ViewHome from "../views/home";
3 |
4 | export default function Home(ctx: Context) {
5 | ctx.body = ViewHome();
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig-build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src"
6 | },
7 | "exclude": ["**/*.test.ts", "examples/**/*"]
8 | }
9 |
--------------------------------------------------------------------------------
/examples/example-koa/README.md:
--------------------------------------------------------------------------------
1 | # example-koa
2 |
3 | Example using koa with this tsx as the template engine
4 |
5 | ## Test
6 |
7 | ```sh
8 | $ npm install
9 | $ npm run build
10 | # or
11 | $ npm start
12 | ```
13 |
--------------------------------------------------------------------------------
/examples/example-koa/src/routes.ts:
--------------------------------------------------------------------------------
1 | const routes = {
2 | home: () => "/",
3 | user: {
4 | index: () => "/users",
5 | byId: (id: string) => `/users/${id}`
6 | }
7 | };
8 |
9 | export default routes;
10 |
--------------------------------------------------------------------------------
/examples/example-koa/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "jsx": "react-native",
6 | "rootDir": "src",
7 | "outDir": "dist",
8 | "plugins": [{ "transform": "../.." }]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/example-tsx-to-html/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "jsx": "react-native",
6 | "rootDir": "src",
7 | "outDir": "pre",
8 | "plugins": [{ "transform": "../.." }]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/example-ttypescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "jsx": "react-native",
6 | "strict": true,
7 | "plugins": [{ "transform": "../.." }],
8 | "esModuleInterop": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/example-koa/src/global.ts:
--------------------------------------------------------------------------------
1 | declare namespace JSX {
2 | type Element = string;
3 | interface ElementChildrenAttribute {
4 | children: any;
5 | }
6 | interface IntrinsicElements {
7 | [element: string]: {
8 | [property: string]: any;
9 | };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-tsx-to-html/src/types.ts:
--------------------------------------------------------------------------------
1 | declare namespace JSX {
2 | type Element = string;
3 | interface ElementChildrenAttribute {
4 | children: any;
5 | }
6 | interface IntrinsicElements {
7 | [element: string]: {
8 | [property: string]: any;
9 | };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/greenkeeper.json:
--------------------------------------------------------------------------------
1 | {
2 | "groups": {
3 | "default": {
4 | "packages": [
5 | "examples/example-koa/package.json",
6 | "examples/example-tsx-to-html/package.json",
7 | "examples/example-ttypescript/package.json",
8 | "package.json"
9 | ]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-ttypescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-ttypescript",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "build": "ttsc",
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "license": "ISC",
10 | "dependencies": {
11 | "ttypescript": "^1.5.5",
12 | "typescript": "^3.2.2"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "jsx": "react-native",
5 | "module": "commonjs",
6 | "declaration": true,
7 | "strict": true,
8 | "noUnusedLocals": true,
9 | "noUnusedParameters": true,
10 | "noImplicitReturns": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "esModuleInterop": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/example-tsx-to-html/src/layout.tsx:
--------------------------------------------------------------------------------
1 | export default (props: { title: string; children?: any }) => (
2 |
3 |
4 |
8 |
9 | {props.title}
10 |
11 | {props.children}
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/examples/example-koa/src/views/users.tsx:
--------------------------------------------------------------------------------
1 | import Base from "./layout";
2 | import routes from "../routes";
3 |
4 | export default (props: { users: { id: string; name: string }[] }) => (
5 |
6 | Users
7 |
8 | {props.users.map(({ id, name }) => (
9 | -
10 | {name}
11 |
12 | ))}
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/examples/example-koa/src/views/user-details.tsx:
--------------------------------------------------------------------------------
1 | import Base from "./layout";
2 | import routes from "../routes";
3 |
4 | export default (props: { user: { id: string; name: string } }) => (
5 |
6 |
7 |
8 | | Id: |
9 | {props.user.id} |
10 |
11 |
12 | | Name: |
13 | {props.user.name} |
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/examples/example-koa/src/index.ts:
--------------------------------------------------------------------------------
1 | import Koa = require("koa");
2 | import Router = require("koa-router");
3 | import Home from "./routes/home";
4 | import * as Users from "./routes/users";
5 | import routes from "./routes";
6 |
7 | const app = new Koa();
8 | const router = new Router();
9 |
10 | router.get(routes.home(), Home);
11 | router.get(routes.user.index(), Users.index);
12 | router.get(routes.user.byId(":id"), Users.byId);
13 |
14 | app.use(router.routes()).use(router.allowedMethods());
15 |
16 | app.listen(3000);
17 | console.log("Server listening at http://localhost:3000");
18 |
--------------------------------------------------------------------------------
/examples/example-ttypescript/README.md:
--------------------------------------------------------------------------------
1 | # example-ttypescript
2 |
3 | ## Test
4 |
5 | ```sh
6 | $ npm install
7 | $ npm run build
8 | $ cat src/home.js
9 | ```
10 |
11 | ```tsx
12 | // src/home.tsx
13 | export const App = (props: { persons: Person[] }) => (
14 |
15 | {props.persons.map(person => (
16 | -
17 | {person.name} is {person.age} years old
18 |
19 | ))}
20 |
21 | );
22 | ```
23 |
24 | ```js
25 | // src/home.js
26 | exports.App = props =>
27 | `${props.persons
28 | .map(person => `- ${person.name} is ${person.age} years old
`)
29 | .join("")}
`;
30 | ```
31 |
--------------------------------------------------------------------------------
/examples/example-ttypescript/src/home.tsx:
--------------------------------------------------------------------------------
1 | declare global {
2 | namespace JSX {
3 | type Element = string;
4 | interface ElementChildrenAttribute {
5 | children: any;
6 | }
7 | interface IntrinsicElements {
8 | [element: string]: {
9 | [property: string]: any;
10 | };
11 | }
12 | }
13 | }
14 | interface Person {
15 | name: string;
16 | age: number;
17 | }
18 |
19 | export const App = (props: { persons: Person[] }) => (
20 |
21 | {props.persons.map(person => (
22 | -
23 | {person.name} is {person.age} years old
24 |
25 | ))}
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/examples/example-tsx-to-html/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-tsx-to-html",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "TS_NODE_COMPILER=ttypescript js2file src/pages --ext html --outDir dist --require ts-node/register",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "js-to-file": "^1.1.0",
15 | "ts-node": "^7.0.1",
16 | "ttypescript": "^1.5.5",
17 | "typescript": "^3.2.2",
18 | "typescript-transform-jsx": "^1.1.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-koa/src/routes/users.ts:
--------------------------------------------------------------------------------
1 | import { Context } from "koa";
2 | import ViewUsers from "../views/users";
3 | import ViewUserDetails from "../views/user-details";
4 |
5 | const users = [
6 | {
7 | id: "57fa5d67-e926-4519-ac0d-7abf5a18e368",
8 | name: "Robert"
9 | },
10 | {
11 | id: "4f9d4e2e-6e45-472b-b0e4-1196dd722ff1",
12 | name: "Berta"
13 | }
14 | ];
15 |
16 | export function index(ctx: Context) {
17 | ctx.body = ViewUsers({ users });
18 | }
19 |
20 | export function byId(ctx: import("koa").Context) {
21 | const user = users.filter(({ id }) => id === ctx.params.id)[0];
22 | ctx.body = ViewUserDetails({ user });
23 | }
24 |
--------------------------------------------------------------------------------
/examples/example-koa/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-koa",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "start": "ts-node -C ttypescript src/index.ts",
8 | "build": "ttsc",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "@types/koa": "^2.0.47",
16 | "@types/koa-router": "^7.0.35",
17 | "ts-node": "^7.0.1",
18 | "ttypescript": "^1.5.5",
19 | "typescript": "^3.2.2"
20 | },
21 | "dependencies": {
22 | "koa": "^2.6.2",
23 | "koa-router": "^7.4.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/example-tsx-to-html/README.md:
--------------------------------------------------------------------------------
1 | # example-tsx-to-html
2 |
3 | ## Test
4 |
5 | ```sh
6 | $ npm install
7 | $ npm run build
8 | $ cat dist/index.html
9 | ```
10 |
11 | ```tsx
12 | // src/pages/index.tsx
13 | import Layout from "../layout";
14 |
15 | export default () => (
16 |
17 | Hello World
18 |
19 | );
20 | ```
21 |
22 | ```html
23 |
24 |
25 |
26 |
27 |
28 |
29 | Hello World
30 |
31 |
32 |
33 |
34 | Hello World
35 |
36 |
37 |
38 | ```
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - stable
4 | deploy:
5 | provider: npm
6 | email: danielpza@protonmail.com
7 | skip_cleanup: true
8 | api_key:
9 | secure: 5RYUHdkraBHlw21kqyXkCO75yPyHPFh5Rciwo8iUwxF41snVYSnA99geSX0FC3h/L07q9KZBSIzs6bWi/EiD+nGsz22SaVUBhfKv3SOLrQa56wf4kMX3AWfhGeHurs+EP4OEMhUQXMGWQ9UvrgxnpS6GK0GCg+Vtbci84YAHVkolh2fNJPou7KxhXyEu0OaUJqPErN7a3579IYoznpyh53nxO0yz43T4S1NvaU3If5vFxsQLnh6UH5P2V7fn8VdG1rUadLcIUARxV7eTUBWb3rA11bSL/VxAieK/X5ClTzfD1cC7PoI3DR+2IF9BWqoENZ6htT591xNP+irxMVSQoUd7lG8ZE8IyAmXZBFS5Pvwo950g35wLxblHfyXCpxU+JzdKd9N+d/Q+CagfNvABR/dJmXfBHSq0551m2gYormBzHkCZJCtVG5gns0esF7fdrhUgOauZI6p0acWBuumZQAxp6o1iC2VXasJL+jURme4YJWW22bukAp6i3fpIWfeODz36AV8Yc51Ix28tDRFHiZPqIR52lvd+r/XVpdrvc/YIFi/HAwnCHFN5dorMq7v2TFgq7Zps0U3SPyvytzmfQMpk7RaTrmSuycdJx3cQ3twmtDHXLWekeoyDs+nKNnWef2Vs6M3h47voMPuCpii0ZWarMva1PJXGmv+oIHS63TQ=
10 | on:
11 | tags: true
12 | repo: LeDDGroup/typescript-transform-jsx
13 |
--------------------------------------------------------------------------------
/examples/example-koa/src/views/layout.tsx:
--------------------------------------------------------------------------------
1 | import routes from "../routes";
2 |
3 | const links = [
4 | { id: "home", url: routes.home(), text: "Home" },
5 | { id: "users", url: routes.user.index(), text: "Users" }
6 | ];
7 |
8 | export default (props: {
9 | title: string;
10 | active: "home" | "users";
11 | children?: any;
12 | }) =>
13 | "" +
14 | (
15 |
16 |
17 |
18 | {props.title}
19 |
20 |
21 |
22 | {links.map(({ id, url, text }) => (
23 | -
24 | {id === props.active ? (
25 |
28 | ) : (
29 | {text}
30 | )}
31 |
32 | ))}
33 |
34 |
35 | {props.children}
36 |
37 | );
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 LeddGroup
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typescript-transform-jsx",
3 | "version": "1.5.5",
4 | "description": "Typescript transform jsx to string",
5 | "keywords": [
6 | "jsx",
7 | "templates",
8 | "tsx",
9 | "typesafe",
10 | "typescript"
11 | ],
12 | "homepage": "https://github.com/LeDDGroup/typescript-transform-jsx#readme",
13 | "bugs": {
14 | "url": "https://github.com/LeDDGroup/typescript-transform-jsx/issues"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/LeDDGroup/typescript-transform-jsx.git"
19 | },
20 | "license": "MIT",
21 | "author": "Daniel Perez Alvarez ",
22 | "files": [
23 | "dist"
24 | ],
25 | "main": "dist/index.js",
26 | "types": "dist/index.d.ts",
27 | "scripts": {
28 | "prebuild": "rm -rf dist",
29 | "build": "tsc -p tsconfig-build.json",
30 | "prepare": "npm run build",
31 | "release": "standard-version",
32 | "test": "jest"
33 | },
34 | "jest": {
35 | "preset": "ts-jest",
36 | "testEnvironment": "node"
37 | },
38 | "dependencies": {},
39 | "devDependencies": {
40 | "@types/jest": "^24.0.5",
41 | "@types/node": "^10.12.18",
42 | "jest": "^23.6.0",
43 | "safe-eval": "^0.4.1",
44 | "standard-version": "^8.0.1",
45 | "ts-jest": "^23.10.4",
46 | "typescript": "^3.1.6"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from "typescript";
2 | import transformer from "./index";
3 | import safeEval from "safe-eval";
4 |
5 | const compilerOptions: ts.CompilerOptions = {
6 | target: ts.ScriptTarget.ESNext,
7 | module: ts.ModuleKind.CommonJS,
8 | jsx: ts.JsxEmit.Preserve,
9 | };
10 |
11 | function compile(source: string) {
12 | const { outputText } = ts.transpileModule(source, {
13 | compilerOptions,
14 | transformers: { after: [transformer] },
15 | });
16 | return outputText;
17 | }
18 |
19 | function runFunction(source: string) {
20 | const compiled = compile(source);
21 | const wrapped = `(function () {${compiled}})()`;
22 | return safeEval(wrapped);
23 | }
24 |
25 | function run(source: string) {
26 | return safeEval(compile(source));
27 | }
28 |
29 | function checkf(source: string, expected: string) {
30 | const actual = runFunction(source);
31 | expect(actual).toBe(expected);
32 | }
33 |
34 | function check(source: string, expected: string) {
35 | const actual = run(source);
36 | expect(actual).toBe(expected);
37 | }
38 |
39 | test("simple", () => {
40 | check(
41 | 'Hello World
;',
42 | 'Hello World
'
43 | );
44 | });
45 |
46 | test("interpolation", () => {
47 | check('{"hello"}
', 'hello
');
48 | });
49 |
50 | test("fragments", () => {
51 | check("<>hello
world
>", "hello
world
");
52 | });
53 |
54 | test("More Complex", () => {
55 | checkf(
56 | `
57 | const Control = ({label, children}: any) => {children}
58 | return world;
59 | `,
60 | "world
"
61 | );
62 | });
63 |
64 | test("spread", () => {
65 | checkf(
66 | `
67 | const Control = ({label, children}: any) => {children}
68 | return ;
69 | `,
70 | "world
"
71 | );
72 | });
73 |
74 | test("spread2", () => {
75 | checkf(
76 | `
77 | const Control = ({children, label, ...props}: any) =>
78 | return ;
79 | `,
80 | ''
81 | );
82 | });
83 |
84 | test("spread3", () => {
85 | checkf(
86 | `
87 | const Input = ({ children, ...props }: any) => (
88 |
89 | );
90 | return
91 | `,
92 | ''
93 | );
94 | });
95 |
96 | // https://reactjs.org/docs/jsx-in-depth.html#string-literals-1
97 | test("whitespace between tags", () => {
98 | check(
99 | `
100 |
101 | hello
102 | my
103 | world
104 |
105 | `,
106 | "hello myworld
"
107 | );
108 | });
109 |
110 | test("whitespace between text", () => {
111 | check(
112 | `
113 |
114 | hello
115 | my
116 | w
117 | orld
118 |
119 | `,
120 | "hello my world
"
121 | );
122 | });
123 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at danielpza@protonmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import * as ts from "typescript";
2 |
3 | export class Node {
4 | constructor(private node?: T) {}
5 | getNode(): T {
6 | if (this.node === undefined) {
7 | throw new Error("Node has not been set");
8 | }
9 | return this.node;
10 | }
11 | }
12 |
13 | export class Expression extends Node {
14 | call(...args: ts.Expression[]) {
15 | return new Expression(ts.createCall(this.getNode(), [], args));
16 | }
17 | access(name: string | ts.Identifier) {
18 | return new Expression(ts.createPropertyAccess(this.getNode(), name));
19 | }
20 | }
21 |
22 | export class Identifier extends Expression {
23 | constructor(name: string) {
24 | super(ts.createIdentifier(name));
25 | }
26 | }
27 |
28 | export class ArrowFunction extends Expression {
29 | protected parameters: ts.ParameterDeclaration[] = [];
30 | protected body: ts.Expression | null = null;
31 | constructor(
32 | parameters: (ts.ParameterDeclaration | string[])[] = [],
33 | body: ts.Expression | null = null
34 | ) {
35 | super();
36 | this.addParameter(...parameters);
37 | if (body) this.setBody(body);
38 | }
39 | addParameter(...parameters: (ts.ParameterDeclaration | string[])[]) {
40 | this.parameters = this.parameters.concat(
41 | parameters.map(p => {
42 | if (Array.isArray(p)) {
43 | return createParameter(
44 | ts.createArrayBindingPattern(p.map(createBindingElement))
45 | );
46 | }
47 | return p;
48 | })
49 | );
50 | return this;
51 | }
52 | setBody(body: ts.Expression) {
53 | this.body = body;
54 | return this;
55 | }
56 | getNode() {
57 | if (this.body === null) {
58 | throw new Error(
59 | "Cannot create arrow function because body hasn't been set"
60 | );
61 | }
62 | return ts.createArrowFunction(
63 | undefined,
64 | undefined,
65 | this.parameters,
66 | undefined,
67 | undefined,
68 | this.body
69 | );
70 | }
71 | }
72 |
73 | export class StringTemplateHelper extends Expression<
74 | ts.TemplateExpression | ts.StringLiteral | ts.Expression
75 | > {
76 | private body: [ts.Expression, string][] = [[null as any, ""]];
77 | constructor(...els: (ts.Expression | string)[]) {
78 | super();
79 | this.add(...els);
80 | }
81 | public add(...elements: (ts.Expression | string)[]) {
82 | for (const element of elements) {
83 | if (typeof element === "string") {
84 | this.body[this.body.length - 1][1] += element;
85 | } else {
86 | this.body.push([element, ""]);
87 | }
88 | }
89 | }
90 | getNode() {
91 | if (this.body.length === 1) return ts.createLiteral(this.body[0][1]);
92 | if (
93 | this.body.length === 2 &&
94 | this.body[0][1] === "" &&
95 | this.body[1][1] === ""
96 | ) {
97 | return this.body[1][0];
98 | }
99 | const head = ts.createTemplateHead(this.body[0][1]);
100 | const body = this.body.slice(1).map(([node, lit], index, arr) => {
101 | return ts.createTemplateSpan(
102 | node,
103 | index === arr.length - 1
104 | ? ts.createTemplateTail(lit)
105 | : ts.createTemplateMiddle(lit)
106 | );
107 | });
108 | return ts.createTemplateExpression(head, body);
109 | }
110 | }
111 |
112 | function createParameter(name: string | ts.ArrayBindingPattern) {
113 | return ts.createParameter(undefined, undefined, undefined, name);
114 | }
115 |
116 | function createBindingElement(name: string): ts.BindingElement {
117 | return ts.createBindingElement(undefined, undefined, name, undefined);
118 | }
119 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 |
6 | ## [1.5.5](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.5.3...v1.5.5) (2020-04-16)
7 |
8 |
9 | ### Bug Fixes
10 |
11 | * more whitespace related fix ([f131423](https://github.com/LeDDGroup/typescript-transform-jsx/commit/f131423))
12 |
13 |
14 |
15 |
16 | ## [1.5.3](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.5.2...v1.5.3) (2020-04-16)
17 |
18 |
19 | ### Bug Fixes
20 |
21 | * another issue related to whitspace and line breaks ([32521af](https://github.com/LeDDGroup/typescript-transform-jsx/commit/32521af))
22 |
23 |
24 |
25 |
26 | ## [1.5.2](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.5.1...v1.5.2) (2020-04-16)
27 |
28 |
29 | ### Bug Fixes
30 |
31 | * not removing all line endings ([3ed69fa](https://github.com/LeDDGroup/typescript-transform-jsx/commit/3ed69fa))
32 |
33 |
34 |
35 |
36 | ## [1.5.1](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.5.0...v1.5.1) (2020-04-16)
37 |
38 |
39 | ### Bug Fixes
40 |
41 | * lot of fixes related to function components ([ee13ebe](https://github.com/LeDDGroup/typescript-transform-jsx/commit/ee13ebe))
42 |
43 |
44 |
45 |
46 |
47 | # [1.5.0](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.4.0...v1.5.0) (2019-01-07)
48 |
49 | ### Features
50 |
51 | - support spread operator on html elements ([bac0475](https://github.com/LeDDGroup/typescript-transform-jsx/commit/bac0475))
52 |
53 |
54 |
55 | # [1.4.0](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.3.0...v1.4.0) (2019-01-05)
56 |
57 | ### Features
58 |
59 | - automatically `.join("")` arrays ([d72a156](https://github.com/LeDDGroup/typescript-transform-jsx/commit/d72a156))
60 |
61 |
62 |
63 | # [1.3.0](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.2.1...v1.3.0) (2019-01-02)
64 |
65 | ### Bug Fixes
66 |
67 | - pass empty "" children value on self closing function element ([652fa72](https://github.com/LeDDGroup/typescript-transform-jsx/commit/652fa72))
68 |
69 | ### Features
70 |
71 | - support spread expressions on function elements ([27a021a](https://github.com/LeDDGroup/typescript-transform-jsx/commit/27a021a))
72 |
73 |
74 |
75 | ## [1.2.1](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.2.0...v1.2.1) (2018-12-28)
76 |
77 | ### Bug Fixes
78 |
79 | - escape expressions in properties ([10f1265](https://github.com/LeDDGroup/typescript-transform-jsx/commit/10f1265))
80 |
81 |
82 |
83 | # [1.2.0](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.1.0...v1.2.0) (2018-12-28)
84 |
85 | ### Features
86 |
87 | - only output expression if there's no need for template ([1656acf](https://github.com/LeDDGroup/typescript-transform-jsx/commit/1656acf))
88 |
89 |
90 |
91 | # [1.1.0](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.0.1...v1.1.0) (2018-12-26)
92 |
93 | ### Features
94 |
95 | - support function elements ([854f240](https://github.com/LeDDGroup/typescript-transform-jsx/commit/854f240))
96 |
97 |
98 |
99 | ## [1.0.1](https://github.com/LeDDGroup/typescript-transform-jsx/compare/v1.0.0...v1.0.1) (2018-12-26)
100 |
101 | ### Bug Fixes
102 |
103 | - not working with nested elements ([3c28675](https://github.com/LeDDGroup/typescript-transform-jsx/commit/3c28675))
104 |
105 |
106 |
107 | # 1.0.0 (2018-12-26)
108 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # typescript-transform-jsx
2 |
3 | [](https://www.npmjs.com/package/typescript-transform-jsx)
4 | [](https://greenkeeper.io/)
5 | [](https://codeclimate.com/github/LeDDGroup/typescript-transform-jsx/maintainability)
6 |
7 | [](https://conventionalcommits.org)
8 | [](https://github.com/prettier/prettier)
9 | [](http://spacemacs.org)
10 |
11 | Typescript transform jsx to string
12 |
13 | **Table of Contents**
14 |
15 | - [typescript-transform-jsx](#typescript-transform-jsx)
16 | - [Motivation](#motivation)
17 | - [Install](#install)
18 | - [Usage with ttypescript](#usage-with-ttypescripthttpsgithubcomcevekttypescript)
19 | - [Setup](#setup)
20 | - [Example](#example)
21 | - [Roadmap/Caveats](#roadmapcaveats)
22 | - [Contributing](#contributing)
23 |
24 | ## Motivation
25 |
26 | - Typesafe templates
27 | - Transform jsx to string in compilation time
28 | - Fast runtime
29 |
30 | See [examples](https://github.com/LeDDGroup/typescript-transform-jsx/tree/master/examples)
31 |
32 | ## Install
33 |
34 | ```sh
35 | $ npm i -D typescript-transform-jsx
36 | ```
37 |
38 | ## Usage with [ttypescript](https://github.com/cevek/ttypescript/)
39 |
40 | Add it to _plugins_ in your _tsconfig.json_
41 |
42 | ```json
43 | {
44 | "compilerOptions": {
45 | "jsx": "react-native",
46 | "plugins": [{ "transform": "typescript-transform-jsx" }]
47 | }
48 | }
49 | ```
50 |
51 | See https://github.com/LeDDGroup/typescript-transform-jsx/tree/master/examples/example-ttypescript
52 |
53 | ## Setup
54 |
55 | Set the `jsx` flag to `react-native` or `preserve` in your _tsconfig_ file. Then create a `types.ts` with the following content:
56 |
57 | ```ts
58 | declare namespace JSX {
59 | type Element = string;
60 | interface ElementChildrenAttribute {
61 | children: any;
62 | }
63 | interface IntrinsicElements {
64 | [element: string]: {
65 | [property: string]: any;
66 | };
67 | }
68 | }
69 | ```
70 |
71 | This will declare custom JSX so you don't need react typings.
72 |
73 | ## Example
74 |
75 | ```tsx
76 | interface Person {
77 | name: string;
78 | age: number;
79 | }
80 |
81 | const App = (props: { persons: Person[] }) => (
82 |
83 | {props.persons.map(person => (
84 | -
85 | {person.name} is {person.age} years old
86 |
87 | ))}
88 |
89 | );
90 | ```
91 |
92 | Gets compiled to:
93 |
94 | ```js
95 | const App = props =>
96 | `${props.persons
97 | .map(person => `- ${person.name} is ${person.age} years old
`)
98 | .join("")}
`;
99 | ```
100 |
101 | ## Roadmap/Caveats
102 |
103 | - Always handle `children` property implicitly
104 |
105 | - Self closing tags will be treated as such, (ie no children handling on the props)
106 |
107 | - Using spread operators on html elements require _esnext_ environment because it compiles down to `Object.entries` expression:
108 |
109 | ```tsx
110 | // input
111 | const props = { class: "container" };
112 | ;
113 | // output
114 | const props = { class: "container" };
115 | ` `${key}="${value}"`
117 | )}>
`;
118 | ```
119 |
120 | ## Contributing
121 |
122 | If you have any question or idea of a feature create an issue in github or make an PR.
123 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as ts from "typescript";
2 | import * as utils from "./utils";
3 |
4 | const grabJsx = [
5 | ts.SyntaxKind.JsxElement,
6 | ts.SyntaxKind.JsxFragment,
7 | ts.SyntaxKind.JsxSelfClosingElement,
8 | ];
9 |
10 | class Transformer {
11 | private typeChecker: ts.TypeChecker | undefined;
12 | constructor(
13 | program: ts.Program | undefined,
14 | private context: ts.TransformationContext
15 | ) {
16 | this.typeChecker = program && program.getTypeChecker();
17 | }
18 |
19 | getStringFromClosingElement(
20 | node: ts.JsxClosingElement,
21 | result: utils.StringTemplateHelper
22 | ) {
23 | result.add(`${node.tagName.getText()}>`);
24 | }
25 |
26 | getStringFromJsxSpreadAttribute(
27 | node: ts.JsxSpreadAttribute,
28 | result: utils.StringTemplateHelper
29 | ) {
30 | result.add(
31 | " ",
32 | new utils.Identifier("Object")
33 | .access("entries")
34 | .call(node.expression)
35 | .access("map")
36 | .call(
37 | new utils.ArrowFunction(
38 | [["key", "value"]],
39 | new utils.StringTemplateHelper(
40 | ts.createIdentifier("key"),
41 | '="',
42 | ts.createIdentifier("value"),
43 | '"'
44 | ).getNode()
45 | ).getNode()
46 | )
47 | .access("join")
48 | .call(ts.createLiteral(" "))
49 | .getNode()
50 | );
51 | }
52 |
53 | getStringFromAttribute(
54 | node: ts.JsxAttribute,
55 | result: utils.StringTemplateHelper
56 | ) {
57 | if (
58 | node.initializer &&
59 | node.initializer.kind === ts.SyntaxKind.JsxExpression
60 | ) {
61 | result.add(
62 | ` ${node.name.getText()}="`,
63 | node.initializer.expression!,
64 | `"`
65 | );
66 | } else {
67 | result.add(" " + node.getText());
68 | }
69 | }
70 |
71 | getStringFromAttributes(
72 | node: ts.JsxAttributes,
73 | result: utils.StringTemplateHelper
74 | ) {
75 | for (const property of node.properties) {
76 | if (property.kind === ts.SyntaxKind.JsxSpreadAttribute) {
77 | this.getStringFromJsxSpreadAttribute(property, result);
78 | } else {
79 | this.getStringFromAttribute(property, result);
80 | }
81 | }
82 | }
83 |
84 | getStringFromOpeningElement(
85 | node: ts.JsxOpeningElement,
86 | result: utils.StringTemplateHelper
87 | ) {
88 | result.add(`<${node.tagName.getText()}`);
89 | this.getStringFromAttributes(node.attributes, result);
90 | result.add(">");
91 | }
92 |
93 | getObjectLiteralElementFromAttribute(
94 | property: ts.JsxAttributeLike
95 | ): ts.ObjectLiteralElementLike {
96 | if (property.kind === ts.SyntaxKind.JsxSpreadAttribute) {
97 | return ts.createSpreadAssignment(property.expression);
98 | }
99 | const name = property.name.getText();
100 | const value = property.initializer
101 | ? property.initializer.kind === ts.SyntaxKind.JsxExpression
102 | ? property.initializer.expression!
103 | : ts.createLiteral(property.initializer.text)
104 | : ts.createLiteral(true);
105 | return ts.createPropertyAssignment(name, value);
106 | }
107 |
108 | getStringFromJsxElementComponent(
109 | node: ts.JsxElement,
110 | result: utils.StringTemplateHelper
111 | ) {
112 | const parameters = node.openingElement.attributes.properties.map(
113 | this.getObjectLiteralElementFromAttribute.bind(this)
114 | );
115 | const childrenResult = new utils.StringTemplateHelper();
116 | for (const child of node.children) {
117 | this.getStringFromJsxChild(child, childrenResult);
118 | }
119 | const childrenParameter = ts.createPropertyAssignment(
120 | "children",
121 | childrenResult.getNode()
122 | );
123 | parameters.push(childrenParameter);
124 | result.add(
125 | ts.createCall(
126 | node.openingElement.tagName,
127 | [],
128 | [ts.createObjectLiteral(parameters)]
129 | )
130 | );
131 | }
132 |
133 | getStringFromJsxElement(
134 | node: ts.JsxElement,
135 | result: utils.StringTemplateHelper
136 | ) {
137 | if (node.openingElement.tagName.getText().match(/[A-Z]/)) {
138 | this.getStringFromJsxElementComponent(node, result);
139 | return;
140 | }
141 | this.getStringFromOpeningElement(node.openingElement, result);
142 | for (const child of node.children) {
143 | this.getStringFromJsxChild(child, result);
144 | }
145 | this.getStringFromClosingElement(node.closingElement, result);
146 | }
147 |
148 | getStringFromJsxFragment(
149 | node: ts.JsxFragment,
150 | result: utils.StringTemplateHelper
151 | ) {
152 | for (const child of node.children) {
153 | this.getStringFromJsxChild(child, result);
154 | }
155 | }
156 |
157 | getStringFromJsxSelfClosingElementComponent(
158 | node: ts.JsxSelfClosingElement,
159 | result: utils.StringTemplateHelper
160 | ) {
161 | let parameters: ts.ObjectLiteralElementLike[] = [];
162 | parameters.push(
163 | ts.createPropertyAssignment("children", ts.createLiteral(""))
164 | );
165 | parameters = parameters.concat(
166 | node.attributes.properties.map((property) =>
167 | this.getObjectLiteralElementFromAttribute(property)
168 | )
169 | );
170 | result.add(
171 | ts.createCall(node.tagName, [], [ts.createObjectLiteral(parameters)])
172 | );
173 | }
174 |
175 | getStringFromJsxSelfClosingElement(
176 | node: ts.JsxSelfClosingElement,
177 | result: utils.StringTemplateHelper
178 | ) {
179 | if (node.tagName.getText().match(/[A-Z]/)) {
180 | this.getStringFromJsxSelfClosingElementComponent(node, result);
181 | return;
182 | }
183 | result.add("<", node.tagName.getText());
184 | this.getStringFromAttributes(node.attributes, result);
185 | result.add("/>");
186 | }
187 |
188 | getStringFromJsxExpression(
189 | node: ts.JsxExpression,
190 | result: utils.StringTemplateHelper
191 | ) {
192 | const newNode = ts.visitNode(node.expression!, this.visit.bind(this));
193 | if (this.typeChecker) {
194 | const type = this.typeChecker.getTypeAtLocation(newNode);
195 | const symbol = type.getSymbol();
196 | if (symbol && symbol.getName() === "Array") {
197 | result.add(
198 | ts.createCall(
199 | ts.createPropertyAccess(newNode, "join"),
200 | [],
201 | [ts.createLiteral("")]
202 | )
203 | );
204 | } else {
205 | result.add(newNode);
206 | }
207 | } else {
208 | result.add(newNode);
209 | }
210 | }
211 |
212 | getStringFromJsxChild(node: ts.JsxChild, result: utils.StringTemplateHelper) {
213 | switch (node.kind) {
214 | case ts.SyntaxKind.JsxElement:
215 | this.getStringFromJsxElement(node, result);
216 | break;
217 | case ts.SyntaxKind.JsxFragment:
218 | this.getStringFromJsxFragment(node, result);
219 | break;
220 | case ts.SyntaxKind.JsxSelfClosingElement:
221 | this.getStringFromJsxSelfClosingElement(node, result);
222 | break;
223 | case ts.SyntaxKind.JsxText:
224 | const text = node
225 | .getFullText()
226 | .replace(/^\n* */g, "")
227 | .replace(/\n* *$/g, "")
228 | .replace(/\n+ */g, " ");
229 | result.add(text);
230 | break;
231 | case ts.SyntaxKind.JsxExpression:
232 | this.getStringFromJsxExpression(node, result);
233 | break;
234 | default:
235 | throw new Error("NOT IMPLEMENTED"); // TODO improve error message
236 | }
237 | return result;
238 | }
239 |
240 | visit(node: ts.Node): ts.Node {
241 | if (grabJsx.indexOf(node.kind) !== -1) {
242 | const result = new utils.StringTemplateHelper();
243 | this.getStringFromJsxChild(node as ts.JsxChild, result);
244 | return result.getNode();
245 | }
246 | return ts.visitEachChild(node, this.visit.bind(this), this.context);
247 | }
248 |
249 | transform(rootNode: T): T {
250 | return ts.visitNode(rootNode, this.visit.bind(this));
251 | }
252 | }
253 |
254 | function transformer(
255 | program: ts.Program
256 | ): ts.TransformerFactory;
257 | function transformer(
258 | context: ts.TransformationContext
259 | ): ts.Transformer;
260 | function transformer(
261 | programOrContext: ts.Program | ts.TransformationContext
262 | ) {
263 | if (isProgram(programOrContext)) {
264 | return (context: ts.TransformationContext) => (node: T) =>
265 | new Transformer(programOrContext, context).transform(node);
266 | }
267 | return (node: T) =>
268 | new Transformer(undefined, programOrContext).transform(node);
269 | }
270 |
271 | function isProgram(t: object): t is ts.Program {
272 | return "getTypeChecker" in t;
273 | }
274 |
275 | export default transformer;
276 |
--------------------------------------------------------------------------------