├── priv
└── touch
├── test
├── test_helper.exs
└── myweb_test.exs
├── web
├── css
│ └── app.css
├── package.json
├── layout.html.eex
├── components
│ ├── TodoForm.js
│ ├── TodoList.js
│ └── App.js
└── webpack.config.js
├── config
└── config.exs
├── .gitignore
├── README.md
├── mix.exs
├── lib
└── reaxt_example.ex
└── mix.lock
/priv/touch:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/web/css/app.css:
--------------------------------------------------------------------------------
1 | .App {
2 | font-family: sans-serif;
3 | text-align: center;
4 | }
5 |
--------------------------------------------------------------------------------
/test/myweb_test.exs:
--------------------------------------------------------------------------------
1 | defmodule MywebTest do
2 | use ExUnit.Case
3 |
4 | test "the truth" do
5 | assert 1 + 1 == 2
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | config :reaxt, [
4 | otp_app: :reaxt_example,
5 | hot: Mix.env == :dev,
6 | pool_size: if Mix.env == :dev do 1 else 10 end
7 | ]
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /_build
2 | /deps
3 | /web/node_modules
4 | erl_crash.dump
5 | *.ez
6 | *.swp
7 |
8 |
9 | /priv/react_servers/**
10 | /priv/static/**
11 | /priv/stderr.log
12 | /priv/webpack.stats.json
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Reaxt Example
2 | =============
3 |
4 | Test it now (need npm and elixir):
5 |
6 | ```elixir
7 | git clone https://github.com/awetzel/reaxt-example
8 | cd reaxt-example
9 | mix deps.get
10 | iex -S mix
11 | ```
12 |
13 | Then :
14 | - go to http://localhost:8099 to see an example application,
15 | - you can test hot loading by changing a react template in `web/components/xx`,
16 | or the css in `web/css/app.css`.
17 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule ReaxtExample.Mixfile do
2 | use Mix.Project
3 |
4 | def project, do: [
5 | app: :reaxt_example,
6 | version: "0.1.0",
7 | elixir: ">= 1.3.0",
8 | compilers: [:reaxt_webpack] ++ Mix.compilers,
9 | build_embedded: Mix.env == :prod,
10 | start_permanent: Mix.env == :prod,
11 | deps: deps()
12 | ]
13 |
14 | def application, do: [
15 | mod: { ReaxtExample.App, [] },
16 | extra_applications: [:logger]
17 | ]
18 |
19 | defp deps, do: [
20 | {:plug_cowboy, "~> 2.5"},
21 | {:reaxt, "~> 4.0"},
22 | ]
23 | end
24 |
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "react": "^16.12.0",
4 | "react-dom": "^16.12.0",
5 | "webpack": "^5.1.3",
6 | "webpack-cli": "4.1.0"
7 | },
8 | "devDependencies": {
9 | "@material-ui/core": "3.3.2",
10 | "@material-ui/icons": "3.0.1",
11 | "@babel/core": "^7.12.1",
12 | "@babel/preset-env": "^7.12.1",
13 | "@babel/preset-react": "^7.12.1",
14 | "babel-loader": "^8.1.0",
15 | "css-loader": "^5.0.0",
16 | "css-minimizer-webpack-plugin": "^1.1.5",
17 | "mini-css-extract-plugin": "^1.1.1",
18 | "null-loader": "^4.0.1"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/web/layout.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World
6 |
7 |
8 | <%= WebPack.header %>
9 |
10 |
11 |
12 | <%= render.html %>
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/web/components/TodoForm.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react'
2 | import TextField from '@material-ui/core/TextField'
3 |
4 | function useInputState(init) {
5 | const [value, setValue] = useState(init)
6 |
7 | return {
8 | value,
9 | onChange: e => setValue(e.target.value),
10 | reset: () => setValue(init),
11 | }
12 | }
13 |
14 | export default function TodoForm({saveTodo}) {
15 | const {value, onChange, reset} = useInputState('')
16 |
17 | return (
18 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/web/components/TodoList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import List from '@material-ui/core/List'
3 | import ListItem from '@material-ui/core/ListItem'
4 | import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
5 | import ListItemText from '@material-ui/core/ListItemText'
6 | import Checkbox from '@material-ui/core/Checkbox'
7 | import IconButton from '@material-ui/core/IconButton'
8 | import DeleteIcon from '@material-ui/icons/Delete'
9 |
10 | export default function TodoList({todos, deleteTodo}) {
11 | return (
12 |
13 | {
14 | todos.map((todo, index) => (
15 |
16 |
17 |
18 |
19 | deleteTodo(index)}
22 | >
23 |
24 |
25 |
26 |
27 | ))
28 | }
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/lib/reaxt_example.ex:
--------------------------------------------------------------------------------
1 | defmodule ReaxtExample.App do
2 | use Application
3 |
4 | def start(_type, _args) do
5 | children = [
6 | {
7 | Plug.Cowboy,
8 | scheme: :http,
9 | plug: ReaxtExample.Api,
10 | options: [
11 | port: 8099,
12 | ]
13 | }
14 | ]
15 | _ = IO.puts("Starting server at http://localhost:8099")
16 |
17 | Reaxt.reload()
18 | Supervisor.start_link(children, [strategy: :one_for_one, name: __MODULE__])
19 | end
20 | end
21 |
22 | defmodule ReaxtExample.Api do
23 | require EEx
24 | use Plug.Router
25 |
26 | if Mix.env == :dev do
27 | use Plug.Debugger
28 | plug WebPack.Plug.Static, at: "/public", from: :reaxt_example
29 | else
30 | plug Plug.Static, at: "/public", from: :reaxt_example
31 | end
32 |
33 | plug :match
34 | plug :dispatch
35 |
36 | EEx.function_from_file :defp, :layout, "web/layout.html.eex", [:render]
37 |
38 | get "*_" do
39 | data = %{path: conn.request_path, cookies: conn.cookies, query: conn.params, headers: conn.req_headers}
40 | render = Reaxt.render!(:App, data, 30_000)
41 |
42 | conn
43 | |> put_resp_header("content-type", "text/html;charset=utf-8")
44 | |> send_resp(render.param || 200, layout(render))
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/web/components/App.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react'
2 | import ReactDOM from 'react-dom'
3 | import Typography from '@material-ui/core/Typography'
4 | import '../css/app.css'
5 |
6 | import TodoForm from './TodoForm'
7 | import TodoList from './TodoList'
8 |
9 | function useTodoState(init) {
10 | const [todos, setTodos] = useState(init)
11 |
12 |
13 | return {
14 | todos,
15 | addTodo: x => setTodos(xs => [...xs, x]),
16 | removeTodo: index => setTodos(xs => xs.filter((x, i) => i !== index)),
17 | }
18 | }
19 |
20 | function App() {
21 | const {todos, addTodo, removeTodo} = useTodoState([])
22 |
23 | const saveTodo = todoText => {
24 | const trimmedText = todoText.trim()
25 |
26 | if (trimmedText.length > 0) {
27 | addTodo(trimmedText)
28 | }
29 | }
30 |
31 | return (
32 |
33 |
34 | Todos
35 |
36 |
37 |
38 |
39 |
40 | )
41 | }
42 |
43 | export default {
44 | reaxt_server_render(params, render) { // server side call, should call render(ReactComp)
45 | render()
46 | },
47 | reaxt_client_render(initialProps, render) { // initial client side call, should call render(ReactComp)
48 | render()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/web/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
4 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
5 |
6 | const client_config = {
7 | entry: "reaxt/client_entry_addition",
8 | mode: (process.env.MIX_ENV != 'prod') ? 'development' : 'production',
9 | target: 'web',
10 | // devtool: (process.env.MIX_ENV != 'prod') ? 'source-map' : false,
11 | devtool: "inline-source-map",
12 | output: {
13 | path: path.join(__dirname, '../priv/static'), //typical output on the default directory served by Plug.Static
14 | filename: '[name].[fullhash].js', // dynamic name for long term caching, or code splitting, use WebPack.file_of(:main) to get it
15 | publicPath: '/public/'
16 | },
17 | // use cacheGroups to aggregate all css chunks into one main.css file
18 | optimization: {
19 | splitChunks: {cacheGroups: {styles: {name: 'styles', test: /\.css$/, chunks: 'all', enforce: true}}},
20 | minimizer: (process.env.MIX_ENV != 'prod') ? [`...`] : [`...`, new CssMinimizerPlugin()],
21 | },
22 | plugins: [
23 | new MiniCssExtractPlugin({insert: "", filename: 'css/[name].css'}),
24 | ],
25 | module: {
26 | rules: [
27 | {
28 | test: /.+\.js$/,
29 | exclude: /node_modules/,
30 | use: {
31 | loader: 'babel-loader',
32 | options: {
33 | cacheDirectory: true,
34 | presets: [
35 | ['@babel/preset-env', {targets: 'defaults'}],
36 | '@babel/preset-react',
37 | ],
38 | sourceType: 'unambiguous',
39 | },
40 | },
41 | },
42 | ],
43 | }
44 | }
45 |
46 | const server_config = Object.assign(Object.assign({},client_config),{
47 | target: "node",
48 | entry: "reaxt/react_server",
49 | externals: {},
50 | output: {
51 | // typical output on the default directory served by Plug.Static
52 | path: path.join(__dirname, '../priv/react_servers'),
53 | filename: 'server.js',
54 | chunkFilename: 'chunk/server.[id].js',
55 | },
56 | })
57 |
58 | // optimisation : ONLY EMIT files for client compilation, all file-loader should not emit files on server compilation
59 | server_config.module = {
60 | rules: server_config.module.rules.map(rule => {
61 | return {
62 | ...rule,
63 | use: ((Array.isArray(rule.use)) ? rule.use : [rule.use]).map(use => ({
64 | ...use,
65 | options: (use.loader === 'file-loader') ? {...use.options, emitFile: false} : use.options,
66 | }))
67 | }
68 | }),
69 | }
70 |
71 | // css management : MiniCssExtractPlugin on client build but ignore on server side
72 |
73 | client_config.module.rules.push(
74 | {test: /\.css$/, use: [{loader: MiniCssExtractPlugin.loader}, {loader: 'css-loader'}]}
75 | )
76 |
77 | server_config.module.rules.push(
78 | {test: /\.css$/, use: [{loader: 'null-loader'}]}
79 | )
80 |
81 | module.exports = [client_config,server_config]
82 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
3 | "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
4 | "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
5 | "exos": {:hex, :exos, "2.0.0", "4f0dfd3f69201d7731f8824101747f9cdce979fa0324bd34e5ed4236b47be0be", [:mix], [], "hexpm", "426714ba75ad55d408617b5b40e729592a4934b3710893f1aa18bf4c761bd25e"},
6 | "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
7 | "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
8 | "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
9 | "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
10 | "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
11 | "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
12 | "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
13 | "reaxt": {:hex, :reaxt, "4.0.2", "cd7d66a4adf3d8342b981d1daa0e4abf63d2f4d9a9f5948243b91477301e6a4e", [:mix], [{:exos, "~> 2.0", [hex: :exos, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 4.0", [hex: :poison, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5.0", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "1cb81837ed88e4dfac5e5d4138c5bcf817a7a72a8972effd8d3c3984fda65a1a"},
14 | "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
15 | }
16 |
--------------------------------------------------------------------------------