Code Monkey home page Code Monkey logo

miso-isomorphic-example's Introduction

Single page isomorphic example

This is a minimal example of Miso's Isomorphic feature. This example is focused on answering the questions:

  • What is isomorphism?
  • How do I get it to work with Miso?

There's a more expanded example available here, it uses stack to build the project instead of nix, and it shows how to set up a developer environment.

About isomorphism

Chances are that you happen to know what the mathematical definition of isomorphism is. If you do, forget that definition right now and treat it as a new term altogether. That'll be easier.

Isomorphic Javascript is the idea of having a web application "run" on both client and the server. The goal is to make single page javascript applications load faster. In frameworks without isomorphism, the server sends an html page with a mostly empty body, along with a big javascript file that fills in the body upon loading. This means that the app user won't see the app until the big javascript file is downloaded and run.

In applications with isomorphism, the body of the html page is rendered on the server, by running a minimal part of the app serverside. This way, app users will see a page even before the big javascript file loads. The big javascript file, once loaded, takes over the rendering and adds interactivity.

Required reading

  • The Miso framework - The awesome framework of which this is an example.
  • The Elm Architecture - Miso uses the same architectural concepts. If you're not familiar yet, the linked page should quickly get you up to speed about the core idea.
  • Servant - A Haskell web DSL. Used extensively in this example. Specifically, the introduction and first two sections of the tutorial should cover most of the usage in this example.

The example explained

This example consists of a server, running the app on port 3003, and a ghcjs client. Three Haskell files define both:

For quick overview, here's which parts of the application are defined where:

Server Client Common
Top level Html Update function(s) Model data type
Servant routes subscriptions Initial model
Action data type
View functions
Servant routes

Many important parts of the application are common to both client and server. It's more interesting, though, to look at what isn't common: The update function(s) and subscriptions live clientside only. This means that the server cannot add interactivity to the app. It can only render the actual Html page, using the initial model and the View functions. Any Actions referred to in the View functions are ignored by the server.

So basically it renders the initial Html page, sends it off to the client which then adds interactivity.

Note that "Servant routes" is listed both in Common and in Server. The routes in Common are linked to pages in the app itself, in this example just the top level / home route. The routes in Server include those defined in Common, but also define other routes, in our case the /static/ route which serves the all.js of the clientside app.

Running the example

Using nix:

cd $(nix-build) && bin/server

Note: The current working directory is important when running the server. The server won't be able to find the clientside part of the app when running the server from some place other than the folder with bin and static. This will prevent the javascript from loading and make the buttons not work. In other news, that's a great way of looking at the part of the app sent by the server.

Developing the project

You can work on the different sub-projects (client, common and server) using nix-shell and cabal.

common:

cd common
nix-shell
cabal build
...
exit

client:

cd client
nix-shell
cabal build --ghcjs
...
exit

After the client has been built, you can make a reference to it in the server. The server will need to know where to find the compiled client in order to serve it:

cd server
mkdir static
ln -sf ../$(find ../client -name all.js) static/

Lastly, building and running the server:

cd server
nix-shell
cabal build
cabal run server
...
exit

miso-isomorphic-example's People

Contributors

adetokunbo avatar askalexsharov avatar fptje avatar juliendehos 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

miso-isomorphic-example's Issues

Views that change the tag structure

Imagine to have a view with three buttons, and another view with four buttons (I only added another "+"-button):

-- View function of the Home route
homeView :: Model -> View Action
homeView m =
    div_ []
      [ div_
        []
        [ button_ [ onClick SubtractOne ] [ text "-" ]
        , text $ Miso.ms $ show $ _counterValue m
        , button_ [ onClick AddOne ] [ text "+" ]
        , button_ [ onClick AddOne ] [ text "+" ]
        ]
      , button_ [ onClick $ ChangeURI flippedLink ] [ text "Go to /flipped" ]
      ]

-- View function of the Home route
flippedView :: Model -> View Action
flippedView m =
    div_ []
      [ div_
        []
        [ button_ [ onClick AddOne ] [ text "+" ]
        , text $ Miso.ms $ show $ _counterValue m
        , button_ [ onClick SubtractOne ] [ text "-" ]
        ]
      , button_ [ onClick $ ChangeURI homeLink ] [ text "Go to /" ]
      ]

But when you try this, then you see the second "+" button in both views, although it should vanish in flippedView.

Add explicit import lists

There are a few benefits of explicit import lists for this project.

  1. Makes it easy to find out which package/module things come from (probably biggest benefit, as currently the lack of this feature is slowing down my development speed).

  2. Decreases the likelihood of future breakage in the case of a package adding new exports.

  3. Makes it easier to copy paste imports from the example, as those who want explicit imports can copy directly, and those who don't can just not copy the part in parenthesis.

The main downside of explicit import lists (you have to update them as you add new features) seems pretty negligible in the context of a fairly stable example repo.

compilation error

I ran nix-build in the root directory and got a compilation error:

Building miso-isomorphic-example-0.1.0.0...
Preprocessing executable 'server' for miso-isomorphic-example-0.1.0.0...
[1 of 2] Compiling Common           ( shared/Common.hs, dist/build/server/server-tmp/Common.dyn_o )
[2 of 2] Compiling Main             ( server/Main.hs, dist/build/server/server-tmp/Main.dyn_o )

server/Main.hs:80:9: error:
    • Couldn't match expected type ‘m3 b0 -> L.HtmlT m ()’
                  with actual type ‘L.HtmlT m2 ()’
    • The first argument of ($) takes one argument,
      but its type ‘L.HtmlT m2 ()’ has none
      In the expression:
        L.doctype_
        $ do { L.head_
               $ do { L.title_ "Miso isomorphic example";
                      L.meta_ [...];
                      .... };
               L.body_ (L.toHtml x) }
      In an equation for ‘toHtml’:
          toHtml (HtmlPage x)
            = L.doctype_
              $ do { L.head_
                     $ do { L.title_ "Miso isomorphic example";
                            .... };
                     L.body_ (L.toHtml x) }
    • Relevant bindings include
        toHtml :: HtmlPage a -> L.HtmlT m () (bound at server/Main.hs:79:5)
builder for '/nix/store/z5g8jmv8a2r6i050m3vapadzzvslli4n-miso-isomorphic-example-0.1.0.0.drv' failed with exit code 1
cannot build derivation '/nix/store/s1vdj4di6m51kg1d2b475c6cqfz3ypxp-miso-ismorphic-example.drv': 1 dependencies couldn't be built
error: build of '/nix/store/s1vdj4di6m51kg1d2b475c6cqfz3ypxp-miso-ismorphic-example.drv' failed

Extend the example to include how to handle multiple URLs

Once you move from one to multiple URLs you now need to make sure that browsing to each URL gives you the correct local state (both from the server and from the generated javascript), and you also need to make sure that links you click on locally work properly and without a full page reload. So it would be nice to see what the correct way to do the above is; perhaps two urls, a root one and /flipped where the buttons are flipped.

install problems

I had two problems with installation of client. (I tried stack than nix also).
At the end I've got success with following

  • in .cabal file I delete "ghc-option: -dedupe" for client executable because this flag was added only for ghcjs-0.2.1
  • in client/Main.hs comment out TypeApplications and change "Proxy @x" to "Proxy :: Proxy X" everywhere

Consider adding this example to `miso/examples`

It seems like it would make sense to add it in there, for more accessibility and visibility, and so that you can easily test any changes to miso itself by seeing how it affects this example.

I'm not suggesting getting rid of this separate repo, as it is nice to have a separate place to discuss miso isomorphic idiomatic design, and it also shows users how to setup a miso app outside of miso's examples directory.

Having trouble making the client available to the server

Hello!

As a learning experience, I'm trying to see if I can finangle your awesome example out to be a simple blog.

But I have trouble with the last part where you have to make the client available to the server, by doing the following:

cd server
mkdir static
ln -sf ../$(find ../client -name all.js) static/

I just get:

ln: static/..: cannot overwrite directory

Can you help with this? Thank you in advance!

servant-client-ghcjs

I want to add servant-client-ghcjs to this project but I do not know how because this package is not available in nix, Also because the Haskell nix tutorials have a different setup in which Haskell packages are defined with mkDerivation (by using cabal2nix).

Can you help me?

Why Nix seems to be a hard dependency?

I'd like to say that it would be really good to add instructions without considering nix as a hard dependency in the dev flow, doing so you are excluding people which don't want to use Nix.
Thanks.

client build fails with "C compiler cannot create executables"

Here's the error after running nix-shell from within client:

configure: error: in `/System/Volumes/Data/opt/nix/store/ar0m5kmms9giads8kd49wvcy32pq8nhr-configured-ghcjs-src/ghc':
configure: error: C compiler cannot create executables
See `config.log' for more details
builder for '/nix/store/kf72r23dcjxpcafxq6bfcq2xgip5kcg5-configured-ghcjs-src.drv' failed with exit code 77
cannot build derivation '/nix/store/9600mckh18mpqgyxdg5my0fp4jh02h4s-ghcjs-8.6.0.1.drv': 1 dependencies couldn't be built
error: build of '/nix/store/9600mckh18mpqgyxdg5my0fp4jh02h4s-ghcjs-8.6.0.1.drv', '/nix/store/bikzyam1c50zqd37v0klwsh3lpdd11gq-ghcjs-8.6.0.1-with-packages.drv' failed

Full failure log at https://gist.github.com/pseudo-heath/0f39639dd6da1323ce322b704528cd1e

$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15
BuildVersion:	19A602

Nix and MacOS Catalina - cannot build server or client with cabal?

Hello!

So I just reinstalled my Mac with MacOS Catalina, and did the work around to install Nix on it, and it seems to be working fine.

I then wanted to try to see if I could manage to not install anything globally and just get by with using Nix environments. So I don't have GHC, Cabal or GHCJS installed anywhere globally, only depending on the nix files.

But when I try to build both the server and the client, it doesn't work, here's what I do:

  1. Go to the server directory and nix-shell with this nix-shell -p cabal-install ghc. I'm not sure why I need ghc there, because I thought the nix-pkgs.nix file specified that I wanted ghc available, but that doesn't work.
  2. Then when I try to build, it says this:
[nix-shell:~/dev/haskell/miso-isomorphic-example/server]$ cabal build
Resolving dependencies...
cabal: Could not resolve dependencies:
[__0] trying: server-0.1.0.0 (user goal)
[__1] unknown package: common (dependency of server)
[__1] fail (backjumping, conflict set: common, server)
After searching the rest of the dependency tree exhaustively, these were the
goals I've had most trouble fulfilling: server, common

I've also tried running cabal --enable-nix build to see if it was because it was not configured to work with nix, but that doesn't work either.
3. When I try to go build the client, again without GHCJS installed globally, I get this:

[nix-shell:~/dev/haskell/miso-isomorphic-example/client]$ cabal build
cabal: Cannot build the package client-0.1.0.0 because none of the components
are available to build: the executable 'client' is marked as 'buildable:
False'


[nix-shell:~/dev/haskell/miso-isomorphic-example/client]$ 

And if I try to use the config file in the directory, it say this:

[nix-shell:~/dev/haskell/miso-isomorphic-example/client]$ cabal --enable-nix --config-file=cabal.config build
Warning: cabal.config: Unrecognized field packages on line 2
cabal: The program 'ghcjs' version >=0.1 is required but it could not be
found.

How do I proceed, thank you in advance.

Stray html tag in doctype

See: dmjio/miso#429
and: dmjio/miso@9378329

Describe the bug
https://github.com/FPtje/miso-isomorphic-example/blob/master/server/Main.hs
block beginning on line 77 produces an HTML validation error.

instance L.ToHtml a => L.ToHtml (HtmlPage a) where
    toHtmlRaw = L.toHtml
    toHtml (HtmlPage x) =
        L.doctypehtml_ $ do
          L.head_ $ do

Causes an Error:
Start tag in generated HTML5 missing lang attribute, then it is added but wrapped in a duplicate html tag.

Extra html tag opened and closed after ending head and before body

<!DOCTYPE HTML><html><html lang="en"><head><title>  </head></html><body>

Your "wrapper implementation" fails to implement lucid output as prescribed in the lucid documentation.

To Reproduce

  1. Run project
  2. View source
  3. Inspect DOCTYPE
  4. submit generated HTML to https://validator.w3.org/

Expected behavior
Output should be:

<!DOCTYPE HTML><html lang="en"><head><title> </title><meta charset="utf-8"><script defer async src="/static/all.js"></script></head><body>

refactored code:

    toHtml (HtmlPage x) = do
      L.doctype_
      L.html_ [ L.lang_ "en" ] $ do

Additional context
Using lucid directly produces the proper output.

page :: Html () 
   page =
      html_ [lang_ "en"]
         (do head_  
             body_

produces:

   <html lang="en"><head>  </head><body>

Can't build on ubuntu

I cloned master, then followed the basic nix installation instructions, (including modifying my config to allow for "unfree" packages), and then ran nix-build, and I got the following error:

...
...
...
[83 of 84] Compiling Distribution.Simple ( Distribution/Simple.hs, /tmp/nix-build-Cabal-1.24.0.0.drv-0/Distribution/Simple.js_o )
[84 of 84] Compiling Main             ( Setup.hs, /tmp/nix-build-Cabal-1.24.0.0.drv-0/Main.js_o )
Linking Setup.jsexe (Distribution.Compat.Binary,Distribution.Compat.CopyFile,Distribution.Compat.CreatePipe,Distribution.Compat.Environment,Distribution.Compat.Exception,Distribution.Compat.GetShortPathName,Distribution.Compat.Internal.TempFile,Distribution.Compat.MonadFail,Distribution.Compat.ReadP,Distribution.Compat.Semigroup,Distribution.Compiler,Distribution.GetOpt,Distribution.InstalledPackageInfo,Distribution.Lex,Distribution.License,Distribution.ModuleName,Distribution.Package,Distribution.PackageDescription,Distribution.PackageDescription.Check,Distribution.PackageDescription.Configuration,Distribution.PackageDescription.Parse,Distribution.PackageDescription.Utils,Distribution.ParseUtils,Distribution.ReadE,Distribution.Simple,Distribution.Simple.Bench,Distribution.Simple.Build,Distribution.Simple.Build.Macros,Distribution.Simple.Build.PathsModule,Distribution.Simple.BuildPaths,Distribution.Simple.BuildTarget,Distribution.Simple.CCompiler,Distribution.Simple.Command,Distribution.Simple.Compiler,Distribution.Simple.Configure,Distribution.Simple.GHC,Distribution.Simple.GHC.IPI642,Distribution.Simple.GHC.IPIConvert,Distribution.Simple.GHC.ImplInfo,Distribution.Simple.GHC.Internal,Distribution.Simple.GHCJS,Distribution.Simple.Haddock,Distribution.Simple.HaskellSuite,Distribution.Simple.Hpc,Distribution.Simple.Install,Distribution.Simple.InstallDirs,Distribution.Simple.JHC,Distribution.Simple.LHC,Distribution.Simple.LocalBuildInfo,Distribution.Simple.PackageIndex,Distribution.Simple.PreProcess,Distribution.Simple.PreProcess.Unlit,Distribution.Simple.Program,Distribution.Simple.Program.Ar,Distribution.Simple.Program.Builtin,Distribution.Simple.Program.Db,Distribution.Simple.Program.Find,Distribution.Simple.Program.GHC,Distribution.Simple.Program.HcPkg,Distribution.Simple.Program.Hpc,Distribution.Simple.Program.Internal,Distribution.Simple.Program.Ld,Distribution.Simple.Program.Run,Distribution.Simple.Program.Script,Distribution.Simple.Program.Strip,Distribution.Simple.Program.Types,Distribution.Simple.Register,Distribution.Simple.Setup,Distribution.Simple.SrcDist,Distribution.Simple.Test,Distribution.Simple.Test.ExeV10,Distribution.Simple.Test.LibV09,Distribution.Simple.Test.Log,Distribution.Simple.UHC,Distribution.Simple.UserHooks,Distribution.Simple.Utils,Distribution.System,Distribution.TestSuite,Distribution.Text,Distribution.Utils.NubList,Distribution.Verbosity,Distribution.Version,Language.Haskell.Extension,Main)
/nix/store/qqv45akr154xxgmra5lnp873njcxkp98-stdenv/setup: line 1051: 25322 Killed                  ghcjs $setupCompileFlags --make -o Setup -odir $TMPDIR -hidir $TMPDIR $i
builder for ‘/nix/store/1k7ilr74kajwfbiljifzajyjh6yw0d62-Cabal-1.24.0.0.drv’ failed with exit code 137
fetching path ‘/nix/store/5vq325w5ibfzahmzd1z8fnnsfvc8v2s5-servant-server-0.11’...
cannot build derivation ‘/nix/store/yc4mrrpx968rg18b7cj0cygm1fpp9pcw-hscolour-1.24.1.drv’: 1 dependencies couldn't be built
cannot build derivation ‘/nix/store/kilpnk0sh9h3ckcqkmplq3y946n7pgmy-Cabal-1.24.0.0.drv’: 1 dependencies couldn't be built
cannot build derivation ‘/nix/store/kn4g0xdyj6bgzkk91pmihgg4fsdq02rw-aeson-1.2.2.0.drv’: 1 dependencies couldn't be built
cannot build derivation ‘/nix/store/iasnivh34x422m48vkngf4qmpirc48jh-lens-4.15.4.drv’: 1 dependencies couldn't be built
cannot build derivation ‘/nix/store/jhw947vsc1dx9rcnsswj4ngggfgcpl99-miso-0.12.0.0.drv’: 1 dependencies couldn't be built
cannot build derivation ‘/nix/store/h5rn7vrp89kmrh3qyya070nmajax748f-servant-0.11.drv’: 1 dependencies couldn't be built
cannot build derivation ‘/nix/store/hxgks1nn0c7fwx2108k5c1hp6lzl29qi-miso-isomorphic-example-0.1.0.0.drv’: 1 dependencies couldn't be built
killing process 25434
cannot build derivation ‘/nix/store/9hqx1lcy9l34rjmycyr2iib02r3hagfc-miso-ismorphic-example.drv’: 1 dependencies couldn't be built
error: build of ‘/nix/store/9hqx1lcy9l34rjmycyr2iib02r3hagfc-miso-ismorphic-example.drv’ failed

Any idea what is going on / how to fix it?

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.