├── .github └── workflows │ └── benchmark.yml ├── .gitignore ├── README.md ├── app.js ├── assets ├── src │ ├── app │ │ ├── banner │ │ │ ├── index.hyperapp.js │ │ │ ├── index.inferno.js │ │ │ ├── index.marko │ │ │ ├── index.preact.js │ │ │ ├── index.rax.js │ │ │ ├── index.react.js │ │ │ ├── index.vue.js │ │ │ └── styles.js │ │ ├── index.hyperapp.js │ │ ├── index.inferno.js │ │ ├── index.marko │ │ ├── index.preact.js │ │ ├── index.rax.js │ │ ├── index.react.js │ │ ├── index.vue.js │ │ ├── index.xtpl │ │ └── list │ │ │ ├── index.hyperapp.js │ │ │ ├── index.inferno.js │ │ │ ├── index.marko │ │ │ ├── index.preact.js │ │ │ ├── index.rax.js │ │ │ ├── index.react.js │ │ │ ├── index.vue.js │ │ │ └── styles.js │ ├── client.hyperapp.js │ ├── client.inferno.js │ ├── client.marko.js │ ├── client.preact.js │ ├── client.rax.js │ ├── client.react.js │ ├── client.vue.js │ ├── server.hyperapp.js │ ├── server.inferno.js │ ├── server.preact.js │ ├── server.rax.js │ ├── server.react.js │ └── server.vue.js └── static │ └── index.css ├── benchmarks ├── ab-request.sh ├── index.sh └── renderToString.js ├── controllers ├── hyperapp.js ├── inferno.js ├── marko.js ├── preact.js ├── rax.js ├── react.js ├── vue.js └── xtpl.js ├── marko.json ├── mock ├── banner.js └── list.js ├── package.json ├── views ├── page.marko └── page.xtpl ├── webpack.client.config.js └── webpack.server.config.js /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - 'bench/**' 8 | 9 | jobs: 10 | build-and-publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 10 17 | registry-url: https://registry.npmjs.org/ 18 | - run: npm i 19 | - run: npm run webpack 20 | - run: npm run benchmark -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .travis.yml 3 | *.swp 4 | .sass-cache/ 5 | .DS_Store 6 | .idea/ 7 | zip/ 8 | *.tmp 9 | *.bak 10 | tmp/ 11 | temp/ 12 | coverage/ 13 | npm-debug.log* 14 | assets/build 15 | 16 | .happypack -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Server-side Rendering Comparison 2 | 3 | ## Benchmark info 4 | 5 | - With `NODE_ENV=production`. `renderToString` both require from `lib` not `dist`. 6 | - With about 600 dom nodes. 7 | 8 | ## Run 9 | 10 | ```bash 11 | # prepare 12 | npm install 13 | npm run webpack 14 | 15 | # run renderToString benchmark 16 | npm run benchmark 17 | ``` 18 | 19 | ## Result 20 | 21 | > https://github.com/raxjs/server-side-rendering-comparison/commit/04fdfec8aa8626312ad3e2c0c6a57c6f735ad6de/checks?check_suite_id=401266841 22 | 23 | ```bash 24 | React(16.12.0)#renderToString x 1,178 ops/sec ±1.23% (85 runs sampled) 25 | Rax(1.1.1)#renderToString x 6,047 ops/sec ±1.73% (82 runs sampled) 26 | Inferno(7.3.3)#renderToString x 3,335 ops/sec ±1.77% (82 runs sampled) 27 | Preact(10.2.1)#renderToString x 1,005 ops/sec ±1.10% (86 runs sampled) 28 | Marko(4.18.33)#renderToString x 10,291 ops/sec ±1.64% (86 runs sampled) 29 | xtemplate(4.7.2)#render x 20,600 ops/sec ±2.89% (84 runs sampled) 30 | 31 | The benchmark was run on: 32 | PLATFORM: linux 5.0.0-1027-azure 33 | CPU: Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz 34 | SYSTEM MEMORY: 6.782737731933594GB 35 | NODE VERSION: v10.18.1 36 | ``` 37 | 38 | - Result run by [Github Actions](https://github.com/raxjs/server-side-rendering-comparison/actions) 39 | 40 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.NODE_ENV = 'production'; 4 | 5 | const fs = require('fs'); 6 | const koa = require('koa'); 7 | const serve = require('koa-static'); 8 | const router = require('koa-router')(); 9 | 10 | const hyperappController = require('./controllers/hyperapp'); 11 | const reactController = require('./controllers/react'); 12 | const raxController = require('./controllers/rax'); 13 | const vueController = require('./controllers/vue'); 14 | const preactController = require('./controllers/preact'); 15 | const markoController = require('./controllers/marko'); 16 | const infernoController = require('./controllers/inferno'); 17 | const xtplController = require('./controllers/xtpl'); 18 | 19 | const app = require('xtpl/lib/koa')(require('koa')(), { 20 | views:'./views' 21 | }); 22 | 23 | router.get('/hyperapp', hyperappController.home); 24 | router.get('/react', reactController.home); 25 | router.get('/rax', raxController.home); 26 | router.get('/vue', vueController.home); 27 | router.get('/preact', preactController.home); 28 | router.get('/marko', markoController.home); 29 | router.get('/inferno', infernoController.home); 30 | router.get('/xtpl', xtplController.home); 31 | 32 | app.use(serve('./assets/build')); 33 | app.use(serve('./assets/static')); 34 | app.use(router.routes()); 35 | 36 | app.listen(3300, () => { 37 | console.log('Server start listen at 3300'); 38 | }); 39 | -------------------------------------------------------------------------------- /assets/src/app/banner/index.hyperapp.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import { h } from "hyperapp"; 3 | 4 | export const init = []; 5 | 6 | const alertFx = (dispatch, { message }) => alert(message); 7 | const alertEffect = message => [alertFx, { message }]; 8 | 9 | const ClickAction = (state, item) => [state, alertEffect("click banner:" + item.title)]; 10 | 11 | export const view = state => ( 12 |
13 |

Hyperapp Banner:

14 |
15 | {state.map((item, idx) => ( 16 |
17 | 18 |
19 | ))} 20 |
21 |
22 | ); 23 | -------------------------------------------------------------------------------- /assets/src/app/banner/index.inferno.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'inferno'; 2 | 3 | export default class Banner extends Component { 4 | 5 | state = { 6 | data: this.props.data || [] 7 | }; 8 | 9 | onBannerClick = (item) => { 10 | alert('click banner:' + item.title); 11 | } 12 | 13 | render() { 14 | 15 | const {data} = this.state; 16 | 17 | return ( 18 |
19 |

Inferno Banner:

20 |
21 | { 22 | data.map((item, idx) => { 23 | return ( 24 |
25 | 26 |
27 | ); 28 | }) 29 | } 30 |
31 |
32 | ); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /assets/src/app/banner/index.marko: -------------------------------------------------------------------------------- 1 | class { 2 | onCreate() { 3 | this.state = { 4 | title: 'Marko Banner' 5 | }; 6 | } 7 | onBannerClick(title) { 8 | alert('click banner:' + title); 9 | } 10 | } 11 | 12 |
13 |

${state.title}

14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /assets/src/app/banner/index.preact.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 'use strict'; 3 | 4 | import { h, render, Component } from 'preact'; 5 | 6 | export default class Banner extends Component { 7 | 8 | state = { 9 | data: this.props.data || [] 10 | }; 11 | 12 | onBannerClick = (item) => { 13 | alert('click banner:' + item.title); 14 | } 15 | 16 | render() { 17 | 18 | const {data} = this.state; 19 | 20 | return ( 21 |
22 |

Preact Banner:

23 |
24 | { 25 | data.map((item, idx) => { 26 | return ( 27 |
28 | 29 |
30 | ); 31 | }) 32 | } 33 |
34 |
35 | ); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /assets/src/app/banner/index.rax.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | 3 | "use strict"; 4 | 5 | import { createElement, Component } from "rax"; 6 | 7 | export default class Banner extends Component { 8 | state = { 9 | data: this.props.data || [] 10 | }; 11 | 12 | onBannerClick = item => { 13 | alert("click banner:" + item.title); 14 | }; 15 | 16 | render() { 17 | const { data } = this.state; 18 | return ( 19 |
20 |

Rax Banner:

21 |
22 | {data.map((item, idx) => { 23 | return ( 24 |
28 | 29 |
30 | ); 31 | })} 32 |
33 |
34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /assets/src/app/banner/index.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | export default class Banner extends React.Component { 6 | 7 | state = { 8 | data: this.props.data || [] 9 | }; 10 | 11 | onBannerClick = (item) => { 12 | alert('click banner:' + item.title); 13 | } 14 | 15 | render() { 16 | const {data} = this.state; 17 | 18 | return ( 19 |
20 |

React Banner:

21 |
22 | { 23 | data.map((item, idx) => { 24 | return ( 25 |
26 | 27 |
28 | ); 29 | }) 30 | } 31 |
32 |
33 | ); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /assets/src/app/banner/index.vue.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | props: ['data'], 4 | 5 | render(h) { 6 | 7 | return ( 8 |
9 |

Vue Banner:

10 |
11 | { 12 | this.data.map((item, idx) => { 13 | return ( 14 |
15 | 16 |
17 | ); 18 | }) 19 | } 20 |
21 |
22 | ) 23 | }, 24 | 25 | methods: { 26 | onBannerClick: (item) => { 27 | alert('click banner:' + item.title); 28 | } 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /assets/src/app/banner/styles.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default { 4 | container: { 5 | width: '900px', 6 | margin: '30px auto', 7 | border: '1px solid #eee', 8 | padding: '20px' 9 | }, 10 | list: { 11 | overflow: 'hidden' 12 | }, 13 | item: { 14 | float: 'left', 15 | width: '175px', 16 | marginRight: '20px' 17 | }, 18 | itemImg: { 19 | width: '100%', 20 | height: '100px', 21 | marginBottom: '10px' 22 | } 23 | } -------------------------------------------------------------------------------- /assets/src/app/index.hyperapp.js: -------------------------------------------------------------------------------- 1 | 2 | /** @jsx h */ 3 | import { h } from 'hyperapp'; 4 | import * as Banner from './banner/index.hyperapp'; 5 | import * as List from './list/index.hyperapp'; 6 | 7 | export const init = { 8 | bannerData: Banner.init, 9 | listData: List.init, 10 | }; 11 | 12 | export const view = (state) => ( 13 |
14 | {Banner.view(state.bannerData)} 15 | {List.view(state.listData)} 16 |
17 | ); 18 | -------------------------------------------------------------------------------- /assets/src/app/index.inferno.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'inferno'; 2 | import List from './list/index.inferno'; 3 | import Banner from './banner/index.inferno'; 4 | 5 | export default class App extends Component { 6 | 7 | componentDidMount() { 8 | // console.log('inferno render in client'); 9 | } 10 | 11 | render() { 12 | const props = this.props || {}; 13 | 14 | return ( 15 |
16 | 17 | 18 |
19 | ); 20 | 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /assets/src/app/index.marko: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /assets/src/app/index.preact.js: -------------------------------------------------------------------------------- 1 | 2 | /** @jsx h */ 3 | 4 | import { h, render, Component } from 'preact'; 5 | import List from './list/index.preact'; 6 | import Banner from './banner/index.preact'; 7 | 8 | export default class App extends Component { 9 | 10 | componentDidMount() { 11 | // console.log('react render in client'); 12 | } 13 | 14 | render() { 15 | 16 | const props = this.props || {}; 17 | 18 | return ( 19 |
20 | 21 | 22 |
23 | ); 24 | 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /assets/src/app/index.rax.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | 3 | import { createElement, Component } from 'rax'; 4 | import List from './list/index.rax'; 5 | import Banner from './banner/index.rax'; 6 | 7 | export default class App extends Component { 8 | 9 | componentDidMount() { 10 | // console.log('rax render in client'); 11 | } 12 | 13 | render() { 14 | const props = this.props || {}; 15 | 16 | return ( 17 |
18 | 19 | 20 |
21 | ); 22 | } 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /assets/src/app/index.react.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import List from './list/index.react'; 4 | import Banner from './banner/index.react'; 5 | 6 | export default class App extends React.Component { 7 | state = {}; 8 | 9 | componentDidMount() { 10 | // console.log('react render in client'); 11 | this.setState({ 12 | onClick: function(){ 13 | 14 | } 15 | }); 16 | } 17 | 18 | render() { 19 | 20 | const props = this.props || {}; 21 | 22 | return ( 23 |
24 | 25 | 26 |
27 | ); 28 | 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /assets/src/app/index.vue.js: -------------------------------------------------------------------------------- 1 | 2 | import List from './list/index.vue'; 3 | import Banner from './banner/index.vue'; 4 | 5 | export default { 6 | props: ['bannerData', 'listData'], 7 | render(h) { 8 | 9 | return ( 10 |
11 | 12 | 13 |
14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /assets/src/app/index.xtpl: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

Xtpl Banner:

5 |
6 | {{#each(bannerData)}} 7 |
8 | 9 |
10 | {{/each}} 11 |
12 |
13 | 14 |
15 |

Xtpl List

16 |
17 | {{#each(listData)}} 18 | 19 | 20 |

{{this.title}}

21 |

22 | price: {{this.price}} 23 |

24 |
25 | {{/each}} 26 |
27 |
28 | 29 |
30 | -------------------------------------------------------------------------------- /assets/src/app/list/index.hyperapp.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import { h } from "hyperapp"; 3 | 4 | export const init = []; 5 | 6 | export const view = state => ( 7 |
8 |

HyperappList

9 |
10 | {state.map((item, idx) => ( 11 | 12 | 13 |

{item.title}

14 |

15 | price: {item.price} 16 |

17 |
18 | ))} 19 |
20 |
21 | ); 22 | -------------------------------------------------------------------------------- /assets/src/app/list/index.inferno.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'inferno'; 2 | 3 | export default class List extends Component { 4 | 5 | state = { 6 | data: this.props.data || [] 7 | }; 8 | 9 | render() { 10 | const { data } = this.state; 11 | return ( 12 |
13 |

InfernoList

14 |
15 | { 16 | data.map((item, idx) => { 17 | return ( 18 | 19 | 20 |

{item.title}

21 |

22 | price: {item.price} 23 |

24 |
25 | ); 26 | }) 27 | } 28 |
29 |
30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /assets/src/app/list/index.marko: -------------------------------------------------------------------------------- 1 |
2 |

MarkoList

3 |
4 | 5 | 6 | 7 |

${item.title}

8 |

9 | price: ${item.price} 10 |

11 |
12 | 13 |
14 |
-------------------------------------------------------------------------------- /assets/src/app/list/index.preact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** @jsx h */ 3 | 4 | import { h, render, Component } from 'preact'; 5 | 6 | export default class List extends Component { 7 | 8 | state = { 9 | data: this.props.data || [] 10 | }; 11 | 12 | render() { 13 | const { data } = this.state; 14 | return ( 15 |
16 |

PreactList

17 |
18 | { 19 | data.map((item, idx) => { 20 | return ( 21 | 22 | 23 |

{item.title}

24 |

25 | price: {item.price} 26 |

27 |
28 | ); 29 | }) 30 | } 31 |
32 |
33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /assets/src/app/list/index.rax.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | 3 | 'use strict'; 4 | 5 | import { createElement, Component } from 'rax'; 6 | 7 | export default class List extends Component { 8 | 9 | state = { 10 | data: this.props.data || [] 11 | }; 12 | 13 | render() { 14 | const { data } = this.state; 15 | 16 | return ( 17 |
18 |

RaxList

19 |
20 | { 21 | data.map((item, idx) => { 22 | return ( 23 | 24 | 25 |

{item.title}

26 |

27 | price: {item.price} 28 |

29 |
30 | ); 31 | }) 32 | } 33 |
34 |
35 | ); 36 | } 37 | } -------------------------------------------------------------------------------- /assets/src/app/list/index.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | 5 | export default class List extends Component { 6 | 7 | state = { 8 | data: this.props.data || [] 9 | }; 10 | 11 | render() { 12 | const { data } = this.state; 13 | return ( 14 |
15 |

ReactList

16 |
17 | { 18 | data.map((item, idx) => { 19 | return ( 20 | 21 | 22 |

{item.title}

23 |

24 | price: {item.price} 25 |

26 |
27 | ); 28 | }) 29 | } 30 |
31 |
32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /assets/src/app/list/index.vue.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | 3 | 'use strict'; 4 | 5 | export default { 6 | 7 | props: ['data'], 8 | 9 | render(h) { 10 | return ( 11 |
12 |

VueList

13 |
14 | { 15 | this.data.map((item, idx) => { 16 | return ( 17 | 18 | 19 |

{item.title}

20 |

21 | price: {item.price} 22 |

23 |
24 | ); 25 | }) 26 | } 27 |
28 |
29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /assets/src/app/list/styles.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default { 4 | container: { 5 | width: '900px', 6 | margin: '30px auto', 7 | border: '1px solid #eee', 8 | padding: '20px' 9 | }, 10 | list: { 11 | overflow: 'hidden' 12 | }, 13 | item: { 14 | float: 'left', 15 | width: '175px', 16 | marginRight: '20px' 17 | }, 18 | itemImg: { 19 | width: '100%', 20 | height: '100px' 21 | }, 22 | itemTitle: { 23 | height: '30px', 24 | lineHeight: '30px' 25 | }, 26 | itemPrice: { 27 | height: '30px', 28 | lineHeight: '30px' 29 | } 30 | } -------------------------------------------------------------------------------- /assets/src/client.hyperapp.js: -------------------------------------------------------------------------------- 1 | import { app } from 'hyperapp'; 2 | 3 | import { init, view } from './app/index.hyperapp'; 4 | 5 | app({ init, view, node: document.getElementById('container') }); 6 | -------------------------------------------------------------------------------- /assets/src/client.inferno.js: -------------------------------------------------------------------------------- 1 | 2 | import { hydrate } from 'inferno-hydrate'; 3 | 4 | import App from './app/index.inferno'; 5 | 6 | hydrate(, document.getElementById('container')); 7 | -------------------------------------------------------------------------------- /assets/src/client.marko.js: -------------------------------------------------------------------------------- 1 | import MyTemplate from "./app/index.marko"; 2 | 3 | MyTemplate.renderSync({...window.GLOBAL}).replaceChildrenOf(document.getElementById('container')); -------------------------------------------------------------------------------- /assets/src/client.preact.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import { h, render, Component } from 'preact'; 3 | 4 | import App from './app/index.preact'; 5 | 6 | render(, document.getElementById('container')); -------------------------------------------------------------------------------- /assets/src/client.rax.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | 3 | 'use strict'; 4 | 5 | import { createElement, Component, render, hydrate } from 'rax'; 6 | import * as driverDom from 'driver-dom'; 7 | 8 | import App from './app/index.rax'; 9 | 10 | render(, document.getElementById('container'), { driver: driverDom, hydrate: true}); 11 | -------------------------------------------------------------------------------- /assets/src/client.react.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import ReactDom from 'react-dom'; 4 | 5 | import App from './app/index.react'; 6 | 7 | ReactDom.hydrate(, document.getElementById('container')); 8 | -------------------------------------------------------------------------------- /assets/src/client.vue.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import App from './app/index.vue'; 4 | 5 | new Vue({ 6 | el: '#container', 7 | render (h) { 8 | return ( 9 | 10 | ) 11 | } 12 | }) -------------------------------------------------------------------------------- /assets/src/server.hyperapp.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./app/index.hyperapp'); 2 | -------------------------------------------------------------------------------- /assets/src/server.inferno.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./app/index.inferno'); 2 | -------------------------------------------------------------------------------- /assets/src/server.preact.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./app/index.preact'); -------------------------------------------------------------------------------- /assets/src/server.rax.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./app/index.rax'); -------------------------------------------------------------------------------- /assets/src/server.react.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./app/index.react'); -------------------------------------------------------------------------------- /assets/src/server.vue.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./app/index.vue'); -------------------------------------------------------------------------------- /assets/static/index.css: -------------------------------------------------------------------------------- 1 | .container { 2 | font-size: 19px; 3 | width: 900px; 4 | margin: 30px auto; 5 | border: 1px solid #eee; 6 | padding: 20px; 7 | } 8 | 9 | .list { 10 | overflow: hidden; 11 | } 12 | 13 | .item { 14 | float: left; 15 | width: 175px; 16 | margin-right: 20px; 17 | } 18 | 19 | .itemImg { 20 | width: 100%; 21 | height: 100px; 22 | margin-bottom: 10px 23 | } 24 | 25 | .itemTitle { 26 | height: 30px; 27 | line-height: 30px; 28 | } 29 | 30 | .itemPrice { 31 | height: '30px'; 32 | line-height: '30px'; 33 | } -------------------------------------------------------------------------------- /benchmarks/ab-request.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # compare req&res by ab cmd 4 | 5 | # before: npm run start 6 | 7 | ab -n1000 -c50 http://127.0.0.1:3300/rax 8 | ab -n1000 -c50 http://127.0.0.1:3300/react 9 | ab -n1000 -c50 http://127.0.0.1:3300/inferno 10 | ab -n1000 -c50 http://127.0.0.1:3300/vue -------------------------------------------------------------------------------- /benchmarks/index.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export NODE_ENV=production 4 | cd benchmarks/ 5 | 6 | echo '-----------compare renderToString----------' 7 | node ./renderToString.js -------------------------------------------------------------------------------- /benchmarks/renderToString.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * compare renderToString 5 | */ 6 | const os = require('os'); 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | const Benchmark = require('benchmark'); 10 | const xtemplate = require('xtemplate'); 11 | const Rax = require('rax'); 12 | const raxRenderToString = require('rax-server-renderer').renderToString; 13 | const React = require('react'); 14 | const ReactDOMServer = require('react-dom/server'); 15 | const Vue = require('vue'); 16 | const vueRenderToString = require('vue-server-renderer').createRenderer().renderToString; 17 | const Preact = require('preact'); 18 | const preactRenderToString = require('preact-render-to-string'); 19 | const InfernoServer = require('inferno-server'); 20 | const infernoCreateElement = require('inferno-create-element'); 21 | const Hyperapp = require('hyperapp-render'); 22 | 23 | const hyperappPkg = require('hyperapp/package.json'); 24 | const reactPkg = require('react/package.json'); 25 | const raxPkg = require('rax/package.json'); 26 | const infernoPkg = require('inferno/package.json'); 27 | const preactPkg = require('preact/package.json'); 28 | const vuePkg = require('vue/package.json'); 29 | const markoPkg = require('marko/package.json'); 30 | const xtemplatePkg = require('xtemplate/package.json'); 31 | 32 | const HyperApp = require('../assets/build/server.hyperapp.bundle'); 33 | const ReactApp = require('../assets/build/server.react.bundle').default; 34 | const RaxApp = require('../assets/build/server.rax.bundle').default; 35 | const VueApp = require('../assets/build/server.vue.bundle').default; 36 | const PreactApp = require('../assets/build/server.preact.bundle').default; 37 | const MarkoApp = require('../assets/build/server.marko.bundle'); 38 | const InfernoApp = require('../assets/build/server.inferno.bundle').default; 39 | 40 | const xtplPath = path.join(__dirname, '../assets/src/app/index.xtpl'); 41 | const xtplString = fs.readFileSync(xtplPath, 'utf8'); 42 | const xtpl = xtemplate.compile(xtplString); 43 | 44 | const data = { 45 | listData: require('../mock/list'), 46 | bannerData: require('../mock/banner') 47 | }; 48 | 49 | const suite = new Benchmark.Suite; 50 | 51 | suite 52 | .add(`Hyperapp(${hyperappPkg.version})#renderToString`, function() { 53 | Hyperapp.renderToString(HyperApp.view(data)); 54 | }) 55 | .add(`React(${reactPkg.version})#renderToString`, function() { 56 | ReactDOMServer.renderToString(React.createElement(ReactApp, data)); 57 | }) 58 | .add(`Rax(${raxPkg.version})#renderToString`, function() { 59 | raxRenderToString(Rax.createElement(RaxApp, data)); 60 | }) 61 | .add(`Inferno(${infernoPkg.version})#renderToString`, function() { 62 | InfernoServer.renderToString(infernoCreateElement.createElement(InfernoApp, data)); 63 | }) 64 | .add(`Preact(${preactPkg.version})#renderToString`, function() { 65 | preactRenderToString(Preact.h(PreactApp, data)); 66 | }) 67 | .add(`Vue(${vuePkg.version})#renderToString`, function(deferred) { 68 | const vueVm = new Vue({ 69 | render(h) { 70 | return h(VueApp, { 71 | attrs: { 72 | listData: data.listData, 73 | bannerData: data.bannerData 74 | } 75 | }); 76 | } 77 | }); 78 | // Do not call deferred.resolve in the callback of renderToString, it will bring more delay time 79 | vueRenderToString(vueVm).then(html => { 80 | deferred.resolve(); 81 | }); 82 | }, {defer: true}) 83 | .add(`Marko(${markoPkg.version})#renderToString`, function() { 84 | MarkoApp.renderToString(data); 85 | }) 86 | .add(`xtemplate(${xtemplatePkg.version})#render`, function(){ 87 | const xtplApp = new xtemplate(xtpl); 88 | xtplApp.render(data); 89 | }) 90 | .on('cycle', function(event) { 91 | console.log(String(event.target)); 92 | // const t = event.target.stats.mean; 93 | // console.log('mean:' + (t*1000000).toFixed(6) + 'μs'); 94 | }) 95 | .on('complete', function() { 96 | console.log(); 97 | console.log('The benchmark was run on:'); 98 | const osInformation = getOSInformation(); 99 | Object.keys(osInformation).map(info => { 100 | console.log(' ' + info.toUpperCase() + ': ' + osInformation[info]); 101 | }); 102 | }) 103 | // run async 104 | .run({ 'async': true }); 105 | 106 | function getOSInformation() { 107 | return { 108 | platform: os.platform() + ' ' + os.release(), 109 | cpu: os.cpus()[0].model, 110 | 'system memory': os.totalmem() / ( 1024 * 1024 * 1024 ) + 'GB', 111 | 'node version': process.version 112 | }; 113 | } -------------------------------------------------------------------------------- /controllers/hyperapp.js: -------------------------------------------------------------------------------- 1 | const { renderToString } = require("hyperapp-render"); 2 | 3 | module.exports = { 4 | home: function*() { 5 | const Hyperapp = require("../assets/build/server.hyperapp.bundle").default; 6 | const pageConfig = { 7 | listData: require("../mock/list"), 8 | bannerData: require("../mock/banner") 9 | }; 10 | yield this.render("page", { 11 | type: "hyperapp", 12 | content: renderToString(Hyperapp.view, Hyperapp.init), 13 | global: JSON.stringify(pageConfig) 14 | }); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /controllers/inferno.js: -------------------------------------------------------------------------------- 1 | const {createElement} = require('inferno-create-element'); 2 | const {renderToString} = require('inferno-server'); 3 | 4 | module.exports = { 5 | 6 | home: function* () { 7 | const InfernoApp = require('../assets/build/server.inferno.bundle').default; 8 | 9 | const pageConfig = { 10 | listData: require('../mock/list'), 11 | bannerData: require('../mock/banner') 12 | }; 13 | 14 | renderToString(createElement(InfernoApp, pageConfig)) 15 | 16 | yield this.render('page', { 17 | type: 'inferno', 18 | content: renderToString( 19 | createElement(InfernoApp, pageConfig) 20 | ), 21 | global: JSON.stringify(pageConfig) 22 | }); 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /controllers/marko.js: -------------------------------------------------------------------------------- 1 | 2 | const pageTemplatePath = require.resolve('../views/page.marko'); 3 | const pageTemplate = require('marko').load(pageTemplatePath, { writeToDisk:false }); 4 | 5 | module.exports = { 6 | 7 | home: function* () { 8 | 9 | const app = require('../assets/build/server.marko.bundle'); 10 | const pageConfig = { 11 | listData: require('../mock/list'), 12 | bannerData: require('../mock/banner') 13 | }; 14 | 15 | this.body = pageTemplate.renderToString({ pageConfig, app, globalData: JSON.stringify(pageConfig) }); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /controllers/preact.js: -------------------------------------------------------------------------------- 1 | 2 | const preact = require('preact'); 3 | const preactRenderToString = require('preact-render-to-string'); 4 | 5 | module.exports = { 6 | 7 | home: function* () { 8 | 9 | const PreactApp = require('../assets/build/server.preact.bundle').default; 10 | const pageConfig = { 11 | listData: require('../mock/list'), 12 | bannerData: require('../mock/banner') 13 | }; 14 | 15 | yield this.render('page', { 16 | type: 'preact', 17 | content: preactRenderToString(preact.h(PreactApp, pageConfig)), 18 | global: JSON.stringify(pageConfig) 19 | }); 20 | 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /controllers/rax.js: -------------------------------------------------------------------------------- 1 | 2 | const Rax = require('rax'); 3 | const raxRenderToString = require('rax-server-renderer').renderToString; 4 | 5 | module.exports = { 6 | 7 | home: function* () { 8 | 9 | const RaxApp = require('../assets/build/server.rax.bundle').default; 10 | const pageConfig = { 11 | listData: require('../mock/list'), 12 | bannerData: require('../mock/banner') 13 | }; 14 | 15 | const element = Rax.createElement(RaxApp, pageConfig); 16 | const content = raxRenderToString(element); 17 | 18 | yield this.render('page', { 19 | type: 'rax', 20 | content, 21 | global: JSON.stringify(pageConfig) 22 | }); 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /controllers/react.js: -------------------------------------------------------------------------------- 1 | 2 | const React = require('react'); 3 | const ReactDOMServer = require('react-dom/server'); 4 | 5 | module.exports = { 6 | 7 | home: function* () { 8 | 9 | const ReactApp = require('../assets/build/server.react.bundle').default; 10 | 11 | const pageConfig = { 12 | listData: require('../mock/list'), 13 | bannerData: require('../mock/banner') 14 | }; 15 | 16 | const element = React.createElement(ReactApp, pageConfig); 17 | const content = ReactDOMServer.renderToString(element); 18 | 19 | yield this.render('page', { 20 | type: 'react', 21 | content, 22 | global: JSON.stringify(pageConfig) 23 | }); 24 | 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /controllers/vue.js: -------------------------------------------------------------------------------- 1 | 2 | const Vue = require('vue'); 3 | const vueRenderToString = require('vue-server-renderer').createRenderer().renderToString; 4 | 5 | module.exports = { 6 | 7 | home: function* () { 8 | 9 | const VueApp = require('../assets/build/server.vue.bundle').default; 10 | const pageConfig = { 11 | listData: require('../mock/list'), 12 | bannerData: require('../mock/banner') 13 | }; 14 | 15 | const vm = new Vue({ 16 | render(h) { 17 | return h(VueApp, { 18 | attrs: { 19 | listData: pageConfig.listData, 20 | bannerData: pageConfig.bannerData 21 | } 22 | }); 23 | } 24 | }); 25 | 26 | const content = yield new Promise((resolve, reject) => { 27 | vueRenderToString(vm, (err, html) => { 28 | if(err) { 29 | return reject(err); 30 | } 31 | resolve(html); 32 | }); 33 | }); 34 | 35 | yield this.render('page', { 36 | type: 'vue', 37 | content: content, 38 | global: JSON.stringify(pageConfig) 39 | }); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /controllers/xtpl.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const xtemplate = require('xtemplate'); 4 | 5 | module.exports = { 6 | 7 | home: function* () { 8 | const pageConfig = { 9 | listData: require('../mock/list'), 10 | bannerData: require('../mock/banner') 11 | }; 12 | 13 | const xtplPath = path.join(__dirname, '../assets/src/app/index.xtpl'); 14 | const xtplString = fs.readFileSync(xtplPath, 'utf8'); 15 | const xtpl = xtemplate.compile(xtplString); 16 | const xtplApp = new xtemplate(xtpl); 17 | 18 | const content = xtplApp.render(pageConfig); 19 | 20 | yield this.render('page', { 21 | type: 'xtpl', 22 | content: content, 23 | global: JSON.stringify(pageConfig) 24 | }); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /marko.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags-dir":"./assets/src/app" 3 | } 4 | -------------------------------------------------------------------------------- /mock/banner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = Array.from({ length: 100}) 4 | .map((undef, i) => { 5 | return { 6 | title: 'banner ' + i, 7 | img: 'https://img.alicdn.com/tps/TB13keMLXXXXXbmXVXXXXXXXXXX-900-500.jpg' 8 | }; 9 | }); -------------------------------------------------------------------------------- /mock/list.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = Array.from({ length: 100}) 4 | .map((undef, i) => { 5 | return { 6 | title: 'Product title ' + i, 7 | img: 'https://img.alicdn.com/tps/TB13keMLXXXXXbmXVXXXXXXXXXX-900-500.jpg', 8 | href: 'https://github.com', 9 | price: 20 10 | }; 11 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server-side-rendering-comparison", 3 | "version": "0.1.0", 4 | "description": "Server-side rendering comparison", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "NODE_ENV=production nodemon app.js", 8 | "webpack:client": "webpack --config webpack.client.config.js", 9 | "webpack:server": "webpack --config webpack.server.config.js", 10 | "webpack": "npm run webpack:client && npm run webpack:server", 11 | "benchmark": "NODE_ENV=production bash ./benchmarks/index.sh" 12 | }, 13 | "keywords": [ 14 | "ssr" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/taobaofed/server-side-rendering-comparison" 19 | }, 20 | "publishConfig": { 21 | "registry": "http://registry.npm.alibaba-inc.com" 22 | }, 23 | "author": { 24 | "name": "imsobear" 25 | }, 26 | "dependencies": { 27 | "driver-dom": "^2.0.0", 28 | "hyperapp": "^2.0.3", 29 | "hyperapp-render": "^3.1.0", 30 | "inferno": "^7.2.1", 31 | "inferno-create-element": "^7.3.0", 32 | "inferno-hydrate": "^7.3.1", 33 | "inferno-server": "^7.2.1", 34 | "koa": "^1.2.4", 35 | "koa-router": "^5.4.0", 36 | "koa-static": "^2.0.0", 37 | "marko": "latest", 38 | "preact": "latest", 39 | "preact-render-to-string": "latest", 40 | "rapscallion": "^2.1.5", 41 | "rax": "latest", 42 | "rax-server-renderer": "latest", 43 | "react": "latest", 44 | "react-dom": "latest", 45 | "vue": "latest", 46 | "vue-server-renderer": "latest", 47 | "xtemplate": "^4.6.0", 48 | "xtpl": "^3.3.0" 49 | }, 50 | "devDependencies": { 51 | "@babel/core": "^7.5.5", 52 | "@babel/plugin-proposal-class-properties": "^7.5.5", 53 | "@babel/plugin-proposal-decorators": "^7.4.4", 54 | "@babel/plugin-transform-react-constant-elements": "^7.5.0", 55 | "@babel/plugin-transform-react-jsx": "^7.3.0", 56 | "@babel/plugin-transform-runtime": "^7.5.5", 57 | "@babel/preset-env": "^7.5.5", 58 | "@babel/preset-react": "^7.0.0", 59 | "@babel/preset-typescript": "^7.3.3", 60 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 61 | "babel-loader": "^8.0.6", 62 | "babel-plugin-inferno": "^6.1.0", 63 | "babel-plugin-syntax-jsx": "^6.18.0", 64 | "babel-plugin-transform-jsx-to-html": "^0.1.0", 65 | "babel-plugin-transform-vue-jsx": "^3.7.0", 66 | "benchmark": "^2.1.3", 67 | "marko-loader": "^1.2.0", 68 | "nodemon": "^1.11.0", 69 | "uglifyjs-webpack-plugin": "^2.1.3", 70 | "webpack": "^4.38.0", 71 | "webpack-cli": "^3.3.6", 72 | "webpack-dev-server": "^3.7.2" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /views/page.marko: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Marko Page 6 | 7 | 10 | 11 | 12 | 21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /views/page.xtpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{type}} Page 6 | 7 | 8 | 9 | 12 | 22 | 23 | 24 | 25 |
{{{ content }}}
26 | 27 | {{#if(type === 'react')}} 28 | 29 | 30 | {{elseif (type === 'rax')}} 31 | 32 | {{elseif (type === 'inferno')}} 33 | 34 | {{elseif (type === 'vue')}} 35 | 36 | {{elseif (type === 'preact')}} 37 | 38 | {{/if}} 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /webpack.client.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var webpack = require('webpack'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | entry: { 8 | 'client.hyperapp': './assets/src/client.hyperapp.js', 9 | 'client.marko': './assets/src/client.marko.js', 10 | 'client.react': './assets/src/client.react.js', 11 | 'client.rax': './assets/src/client.rax.js', 12 | 'client.preact': './assets/src/client.preact.js', 13 | 'client.vue': './assets/src/client.vue.js', 14 | 'client.inferno': './assets/src/client.inferno.js' 15 | }, 16 | output: { 17 | path: path.join(process.cwd(), 'assets/build'), 18 | filename: '[name].bundle.js', 19 | }, 20 | module: { 21 | rules:[ 22 | { 23 | test: /\.hyperapp\.js?$/, 24 | loader: 'babel-loader', 25 | options: { 26 | presets: [ 27 | [require.resolve('@babel/preset-env'), { 28 | targets: { 29 | browsers: ['last 2 versions', 'IE >= 9'] 30 | }, 31 | modules: false, 32 | loose: true 33 | }], 34 | ], 35 | plugins: [ 36 | [require.resolve('@babel/plugin-transform-react-jsx'), {pragma: 'h'}], 37 | ] 38 | } 39 | }, 40 | // react & rax 41 | { 42 | test: /(rax|react|.)\.jsx?$/, 43 | loader: 'babel-loader', 44 | options: { 45 | presets: [ 46 | [require.resolve('@babel/preset-env'), { 47 | targets: { 48 | browsers: ['last 2 versions', 'IE >= 9'] 49 | }, 50 | modules: false, 51 | loose: true 52 | }], 53 | [require.resolve('@babel/preset-react')], 54 | ], 55 | plugins: [ 56 | [require.resolve('@babel/plugin-transform-runtime')], 57 | [require.resolve('@babel/plugin-transform-react-jsx')], 58 | [require.resolve('@babel/plugin-proposal-decorators'), {legacy: true}], 59 | [require.resolve('@babel/plugin-proposal-class-properties'), {loose: true}] 60 | ] 61 | } 62 | }, 63 | { 64 | test: /\.preact\.js?$/, 65 | loader: 'babel-loader', 66 | options: { 67 | presets: [ 68 | [require.resolve('@babel/preset-env'), { 69 | targets: { 70 | browsers: ['last 2 versions', 'IE >= 9'] 71 | }, 72 | modules: false, 73 | loose: true 74 | }], 75 | [require.resolve('@babel/preset-react')], 76 | ], 77 | plugins: [ 78 | [require.resolve('@babel/plugin-transform-runtime')], 79 | [require.resolve('@babel/plugin-transform-react-jsx'), {pragma: 'h'}], 80 | [require.resolve('@babel/plugin-proposal-decorators'), {legacy: true}], 81 | [require.resolve('@babel/plugin-proposal-class-properties'), {loose: true}], 82 | ] 83 | } 84 | }, 85 | { 86 | test: /\.inferno\.js?$/, 87 | loader: 'babel-loader', 88 | options: { 89 | presets: [ 90 | [require.resolve('@babel/preset-env'), { 91 | modules: false, 92 | loose: true 93 | }], 94 | ], 95 | plugins: [ 96 | [require.resolve("babel-plugin-inferno"), {imports: true}], 97 | [require.resolve('@babel/plugin-transform-runtime')], 98 | [require.resolve('@babel/plugin-proposal-decorators'), {legacy: true}], 99 | [require.resolve('@babel/plugin-proposal-class-properties'), {loose: true}], 100 | ] 101 | } 102 | }, 103 | { 104 | test: /\.vue\.js?$/, 105 | loader: 'babel-loader', 106 | options: { 107 | presets: [ 108 | [require.resolve('@babel/preset-env'), { 109 | modules: false, 110 | loose: true 111 | }], 112 | ], 113 | plugins: [ 114 | require.resolve('babel-plugin-transform-vue-jsx') 115 | ] 116 | } 117 | }, 118 | { 119 | test: /\.marko$/, 120 | exclude: /node_modules/, 121 | loader: 'marko-loader' 122 | } 123 | ] 124 | }, 125 | externals: { 126 | 'hyperapp': 'window.hyperapp', 127 | 'react': 'window.React', 128 | 'react-dom': 'window.ReactDOM', 129 | 'rax': 'window.Rax', 130 | 'vue': 'window.Vue', 131 | 'preact': 'window.preact', 132 | 'inferno': 'window.Inferno' 133 | }, 134 | plugins: [ 135 | new webpack.DefinePlugin({ 136 | 'process.env': { 137 | 'NODE_ENV': '"production"' 138 | } 139 | }), 140 | ] 141 | }; -------------------------------------------------------------------------------- /webpack.server.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var webpack = require('webpack'); 4 | 5 | module.exports = { 6 | target: 'node', 7 | mode: 'production', // 'development', 8 | entry: { 9 | 'server.hyperapp': './assets/src/server.hyperapp.js', 10 | 'server.marko': './assets/src/app/index.marko', 11 | 'server.react': './assets/src/server.react.js', 12 | 'server.rax': './assets/src/server.rax.js', 13 | 'server.preact': './assets/src/server.preact.js', 14 | 'server.vue': './assets/src/server.vue.js', 15 | 'server.inferno': './assets/src/server.inferno.js' 16 | }, 17 | output: { 18 | path: path.join(process.cwd(), 'assets/build'), 19 | filename: '[name].bundle.js', 20 | libraryTarget: 'commonjs2' 21 | }, 22 | module: { 23 | rules:[ 24 | { 25 | test: /\.hyperapp\.js?$/, 26 | exclude: /node_modules/, 27 | loader: 'babel-loader', 28 | options: { 29 | presets: [ 30 | [require.resolve('@babel/preset-env'), { 31 | modules: false, 32 | loose: true 33 | }], 34 | ], 35 | plugins: [ 36 | [require.resolve('@babel/plugin-transform-react-jsx'), {pragma: 'h'}], 37 | ] 38 | } 39 | }, 40 | { 41 | test: /\.react.*\.js/, 42 | exclude: /node_modules/, 43 | loader: 'babel-loader', 44 | options: { 45 | presets: [ 46 | [require.resolve('@babel/preset-env'), { 47 | modules: false, 48 | loose: true 49 | }], 50 | [require.resolve('@babel/preset-react')], 51 | ], 52 | plugins: [ 53 | [require.resolve('@babel/plugin-transform-react-jsx')], 54 | [require.resolve('@babel/plugin-proposal-class-properties')], 55 | [require.resolve('@babel/plugin-transform-react-constant-elements')], 56 | ] 57 | } 58 | }, 59 | { 60 | test: /\.rax.*\.js/, 61 | exclude: /node_modules/, 62 | loader: 'babel-loader', 63 | options: { 64 | presets: [ 65 | [require.resolve('@babel/preset-env'), { 66 | modules: false, 67 | loose: true 68 | }], 69 | [require.resolve('@babel/preset-react')], 70 | ], 71 | plugins: [ 72 | [require.resolve('babel-plugin-transform-jsx-to-html')], 73 | [require.resolve('@babel/plugin-transform-react-jsx')], 74 | [require.resolve('@babel/plugin-proposal-class-properties')], 75 | [require.resolve('@babel/plugin-transform-react-constant-elements')], 76 | ] 77 | } 78 | }, 79 | { 80 | test: /\.preact\.js?$/, 81 | exclude: /node_modules/, 82 | loader: 'babel-loader', 83 | options: { 84 | presets: [ 85 | [require.resolve('@babel/preset-env'), { 86 | modules: false, 87 | loose: true 88 | }], 89 | [require.resolve('@babel/preset-react')], 90 | ], 91 | plugins: [ 92 | [require.resolve('@babel/plugin-transform-react-jsx'), {pragma: 'h', pragmaFrag: 'Fragment'}], 93 | [require.resolve('@babel/plugin-proposal-class-properties')], 94 | [require.resolve('@babel/plugin-transform-react-constant-elements')], 95 | ] 96 | } 97 | }, 98 | { 99 | test: /\.inferno\.js?$/, 100 | loader: 'babel-loader', 101 | options: { 102 | presets: [ 103 | [require.resolve('@babel/preset-env'), { 104 | modules: false, 105 | loose: true 106 | }], 107 | ], 108 | plugins: [ 109 | [require.resolve("babel-plugin-inferno"), {imports: true}], 110 | [require.resolve('@babel/plugin-transform-runtime')], 111 | [require.resolve('@babel/plugin-proposal-decorators'), {legacy: true}], 112 | [require.resolve('@babel/plugin-proposal-class-properties'), {loose: true}], 113 | ] 114 | } 115 | }, 116 | { 117 | test: /\.vue\.js?$/, 118 | loader: 'babel-loader', 119 | options: { 120 | presets: [ 121 | [require.resolve('@babel/preset-env'), { 122 | modules: false, 123 | loose: true 124 | }], 125 | ], 126 | plugins: [ 127 | require.resolve('babel-plugin-transform-vue-jsx') 128 | ] 129 | } 130 | }, 131 | { 132 | test: /\.marko$/, 133 | exclude: /node_modules/, 134 | loader: 'marko-loader', 135 | options: { 136 | 'target': 'server' 137 | } 138 | }, 139 | { 140 | test: /\.handlebars$/, 141 | exclude: /node_modules/, 142 | loader: "handlebars-loader" 143 | } 144 | ] 145 | }, 146 | plugins: [ 147 | new webpack.DefinePlugin({ 148 | 'process.env': { 149 | 'NODE_ENV': '"production"', // '"development"', 150 | 'BUNDLE': 'true' 151 | } 152 | }), 153 | ] 154 | }; --------------------------------------------------------------------------------