GithubHelp home page GithubHelp logo

kbrw / reaxt Goto Github PK

View Code? Open in Web Editor NEW
372.0 13.0 38.0 344 KB

Use React template into your Elixir application for server rendering

License: MIT License

Elixir 60.37% JavaScript 38.32% HTML 1.31%
elixir webpack javascript react server-side-rendering

reaxt's Introduction

Reaxt

Use your React components into your elixir application, using webpack compilation, so :

  • An isomorphic ready library (SEO/JS are now nice together), but with Elixir on the server side
  • Just a Library, with a minimum constraint about your application organization and layout :
    • use any javascript compiled language
    • use any javascript routing logic or library
    • you can use JS React rendered component only for parts of your webpage
  • Nice fluent dev workflow, with :
    • combined stacktrace : elixir | javascript
    • hot loading on both server and browser
    • NPM/Webpack as the only config for respectively dependencies/compilation
    • A cool UI to have an overview of your compiled javascript application
    • You do not have to think about the server side Javascript configuration, just write a webpack conf for the browser, and it is ready to use.

TODO List :

  • Handle Source map in server side for combined stacktrace generation
  • Add Source map in client side
  • Currently the compiler compile in parallel server entry and client entries, but server side compilation does not handle cacheable and slow down very much the compilation
  • handle css loader for reaxt/style loader, currently it is ignored in server side, which is a problem for URL remapping in CSS.

Usage

See https://github.com/awetzel/reaxt-example for a ready to use example application, but lets look into details and requirements.

In your mix.exs, add the dependency and the custom compiler for webpack:

  • Add the :reaxt dependency to your project.deps and application.applications
  • Add compilers: [:reaxt_webpack] ++ Mix.compilers to your project, (:reaxtWebpack for elixir < v1.0.3)

In your config/config.exs, link the reaxt application to the application containing the JS web app

  • config :reaxt,:otp_app,:yourapp

Create the good directory and file layout:

  • MIXROOT/web
  • MIXROOT/web/package.json containing your app NPM dependencies
  • MIXROOT/web/webpack.config.js containing only the client side logic, use "reaxt/style" instead of "style" loader to load your css. A typical output path is ../priv/static.
  • MIXROOT/web/components containing modules exporting React components

In your elixir code generating HTML :

  • add WebPack.header in the <head>
  • add a script with src /your/public/path/<%= WebPack.file_of(:entry_name) %>

Then render your server side HTML :

# if web/components/thefile exports the react component
Reaxt.render!(:thefile,%{it: "is", the: "props"})

# if web/components/thefile exports an object containing a react component
# at the key "component_key"

Reaxt.render!({:thefile,:component_key},%{it: "is", the: "props"})

The function return a %{html: html,css: css,js_render: js_render}, you have to add in the html :

  • the css <style><%= render.css %></style>
  • the html in an identified block (<div id="myblockid"><%= render.html %></div>)
  • the client side rendering call with <script><%= render.js_render %>("myblockid")</script>

For example, if you want a page entirely generated by the react component exported at web/components/app.js, then in your elixir web server, send :

EEx.eval_string("""
  <html>
  <head> <%= WebPack.header %>
    <style><%= render.css %></style>
  </head>
  <body>
    <div id="content"><%= render.html %></div>
    <script src="/public/<%= WebPack.file_of(:main) %>"></script>
    <script><%= render.js_render %>("content")</script>
  </body>
  </html>
""",render: Reaxt.render!(:app,%{my: "props"}))

Finally, you have to serve files generated by webpack :

plug Plug.Static, at: "/public", from: :yourapp

Then iex -S mix and enjoy, but the best is to come.

Custom Plug : Live reloading and WebPack web UI

When you serve files generated by webpack, use the plug WebPack.Plug.Static instead of Plug.Static, it contains an elixir implementation of webpack-dev-server, and a nice UI.

  if Mix.env == :dev do 
    use Plug.Debugger
    plug WebPack.Plug.Static, at: "/public", from: :myweb
  else
    plug Plug.Static, at: "/public", from: :myweb
  end

Then go to http://yourhost/webpack to see a beautiful summary of your compiled js application.

Then configure in your application configuration :

  • config :reaxt,:hot,true to enable that:
    • server and client side JS will be compiled each time you change files
    • server side renderers will be restarted at each compilation
    • client browser page will be reloaded, a desktop notification will be triggered
    • The /webpack UI will be automatically reloaded if it is on your browser
  • config :reaxt,:hot,:client to enable the same hot loading, but with webpack module hot loading on browser to avoid full page reload
    • use the webpack loader react-hot-loader to load your component to enable automatic browser hot reloading of your components
    • the reaxt/style loader for your css enable hot reloading of your css

Dynamic Handler and customize rendering (useful with react-router)

See a full example in reaxt-example

Reaxt provides facilities to easily customize the rendering process at the server and the client side : this is done by attaching reaxt_server_render and/or reaxt_client_render to the module or object referenced by the first argument of Reaxt.render!(.

  • reaxt_server_render(arg,render) will
    • take arg from the second argument of Reaxt.render,
    • have to execute render(component,param) when the wanting handler and props are determined. param is any stringifyable object passed to client rendering
  • reaxt_client_render(props,render,param) have to render the good selected component on the client side.
    • props is the initial props used in server rendering,
    • render is function you have to call to make the client react rendering
    • param is the deserialized version of the third parameter of the callback in reaxt_server_render

To understand how they work, let's look at the default implementation of these functions (what happened when they are not implemented).

// default server rendering only take the exported module as the
// handler to render and the argument as the props
default_reaxt_server_render = function(arg,render){
  render(<this {...arg}/>,null)
}
// default client rendering only take the exported module as the
// handler to render, the param is ignored
default_reaxt_client_render = function(props,render,param){
  render(<this {...props}/>)
}

Now let's see an example usage of these functions : react-router integration (Reaxt.render second argument is the Path): See a full example in reaxt-example

Reaxt.render!(:router_handler,full_path(conn))
var App = require("./app")
var Router = require("react-router")
var Routes = require("./routes")
module.exports = {
  reaxt_server_render: function(path,render){
    Router.run(Routes, path,function (Handler, state) {
      render(<Handler/>)
    })
  },
  reaxt_client_render: function(props,render){
    Router.run(Routes,Router.HistoryLocation,function(Handler,state){
      render(<Handler {...props}/>)
    })
  }
}

Error management

JS exceptions and stacktraces during rendering are converted into Elixir one with a fake stacktrace pointing to the generated javascript file.

This is really nice because you can see javascript stacktrace in the Plug.Debugger UI on exception.

Global Configuration

You can define a term in Elixir using Application env global_config which will be available globally in the server and the client side with require('reaxt/config').

config :reaxt,:global_config, %{
  some_config1: "value1",
  some_config2: "value2"
}

This configuration is static once the :reaxt application has started. So if you want to change this configuration at runtime, you need to reload all :reaxt renderer with Reaxt.reload. Remember : this is a costly reload, do not use it to maintain a state at real time but only for configuration purpose.

Application.put_env :reaxt, :global_config, %{
  some_config1: "value3",
  some_config2: "value4"
}
Reaxt.reload

Then in your javascript component, you can use this config using :

require('reaxt/config').some_config1

Perfs and pool management

The NodeJS renderers are managed in a pool (to obtain "multicore" JS rendering), so :

  • config :reaxt,:pool_size configure the number of worker running permanently
  • config :reaxt,:pool_max_overflow configure the maximum extension of the pool when query happens an it is full

A clever configuration could be :

config :reaxt,:pool_size, if(Mix.env == :dev, do: 1, else: 10)

For minification, remember that webpack compilation is launched by Mix, so you can use process.env.MIX_ENV in your webpack config.

{
  externals: { react: "React" },
  plugins: (function(){
    if(process.env.MIX_ENV == "prod") 
      return [new webpack.optimize.UglifyJsPlugin({minimize: true})]
    else
      return []
  })()
}

CONTRIBUTING

Hi, and thank you for wanting to contribute. Please refer to the centralized informations available at: https://github.com/kbrw#contributing

reaxt's People

Contributors

antoinekbrw avatar awetzel avatar emilingerslev avatar half-shell avatar imnotavirus avatar julien29121998 avatar krapaince avatar oxydros avatar shakadak avatar simonj40 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reaxt's Issues

Failed attempt to run the application

Hello, do you have any idea what to do about it?

Error: Cannot find module '/Users/me/reaxt-example/priv/server'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:902:15)
    at Function.Module._load (internal/modules/cjs/loader.js:746:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}

Compiling multiple entry files with reaxt

I got an elixir app where I need multiple react apps. That can be done with different react.render :<target>, but it will stil use the same js file.
Is it somehow possible to make separate js files for different parts of the app?

reaxt not found in Phoenix

Adding reaxt to a Phoenix app and starting the server results in this error:

** (File.Error) could not read file web/node_modules/reaxt/package.json: no such file or directory
    (elixir) lib/file.ex:245: File.read!/1
    lib/tasks.ex:44: Mix.Tasks.Compile.ReaxtWebpack.run/1
    (elixir) lib/enum.ex:977: anonymous fn/3 in Enum.map/2
    (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/enum.ex:977: Enum.map/2
    (mix) lib/mix/tasks/compile.all.ex:15: Mix.Tasks.Compile.All.run/1
    (mix) lib/mix/tasks/compile.ex:64: Mix.Tasks.Compile.run/1
    (mix) lib/mix/task.ex:251: Mix.Task.run_task/3

I poked around and found the commonjs_reaxt/ directory.

I was able to clear up this error message by this action:

cd path-to-commonjs_reaxt/
npm link
cd path-to-phoenix-app/web/ # where webpack.conf.js lives
npm link reaxt

Now node seems to be able to find reaxt and things move along.

This works for development, but isn't ideal. Any hunches as to what might be going wrong?

web/node_modules/reaxt/package.json not found

Hi Arnaud,

I wanted to try your reaxt lib and play around, but could not get it to compile the reaxt-example.

~> git clone https://github.com/awetzel/reaxt-example
~> mix deps.get
~> mix compile

It complains to not find a node module reaxt (see below). npm install reaxt was not an option. I did not find any node module on github.

** (File.Error) could not read file web/node_modules/reaxt/package.json: no such file or directory
    (elixir) lib/file.ex:244: File.read!/1
    lib/tasks.ex:105: Mix.Tasks.Compile.ReaxtWebpack.run/1
    (elixir) lib/enum.ex:1088: Enum."-map/2-lists^map/1-0-"/2
    (mix) lib/mix/tasks/compile.all.ex:19: anonymous fn/1 in Mix.Tasks.Compile.All.run/1
    (mix) lib/mix/tasks/compile.all.ex:37: Mix.Tasks.Compile.All.with_logger_app/1
    (mix) lib/mix/tasks/compile.ex:88: Mix.Tasks.Compile.run/1
    (mix) lib/mix/tasks/app.start.ex:39: Mix.Tasks.App.Start.run/1
    (mix) lib/mix/tasks/run.ex:63: Mix.Tasks.Run.run/1

I have attached the mix.lock. I am running elixir 1.2.5 on erl 7.2.1.

Please advise.
mix.lock.txt

Custom web directory layout

So I was trying to get flux-react-router-example working with this and it seems to have a different directory structure and a different webpack setup. Any idea if something like this could be supported?

It seems that according to client_entry_addition.js that this folder is hard coded. Could this be something worth customizing or am I going about the process all wrong?

Integration with Phoenix server

reaxt seems to be able to integrate with phoenix, since both example and phoenix is based on cowboy.

There is though a collision between the chosen node directory for reaxt, web and how phoenix use the same directory.
I'll suggest making the web folders name/location configurable through an environment setting.
Also, I don't now if there are any other issues getting reaxt to run with phoenix

Sample version

Got this issue on a Ubuntu running as Linux Subsystem on windows. Trying the test project

Getting reaxt (Hex package)
Checking package (https://repo.hex.pm/tarballs/reaxt-0.3.2.tar)
Fetched package
** (MatchError) no match of right hand side value: {:error, :enoent}
(hex) src/hex_erl_tar.erl:1668: :hex_erl_tar.write_file/2
(hex) src/hex_erl_tar.erl:1618: :hex_erl_tar.create_regular/4
(hex) src/hex_erl_tar.erl:1583: :hex_erl_tar.write_extracted_element/3
(hex) src/hex_erl_tar.erl:148: :hex_erl_tar.extract2/4
(hex) src/hex_erl_tar.erl:138: :hex_erl_tar.extract1/4
(hex) src/hex_erl_tar.erl:1391: :hex_erl_tar.foldl_read1/5
(hex) src/hex_erl_tar.erl:1357: :hex_erl_tar.foldl_read0/4
(hex) src/hex_erl_tar.erl:1332: :hex_erl_tar.foldl_read/4

Whats the best way to deal with this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.