Coder Social home page Coder Social logo

tehnix / miso-isomorphic-stack Goto Github PK

View Code? Open in Web Editor NEW
44.0 7.0 2.0 64 KB

An example of an "isomorphic" Miso server set up with stack

Home Page: https://github.com/dmjio/miso

License: BSD 3-Clause "New" or "Revised" License

Haskell 65.50% Shell 33.44% Dockerfile 1.07%

miso-isomorphic-stack's Introduction

Miso Isomorphic Example using Stack

💡 NOTE: Since stack has dropped support for GHCJS, I would recommend checking out the Nix-based isomorphic Miso example for an updated example.

This is an expansion of the SPA isomorphic example, which is a minimal example of Miso's isomorphic features.

Get Started

$ ./stack-build.sh # or ./stack-build-docker.sh
$ (cd result && bin/server)

That's all you need to do. The rest of this README is for a deeper dive into the development setup, code structure and more. If you don't want to spend time building GHCJS, you can use ./stack-build-docker.sh instead.

The backend code (left), shared code (middle) and frontend code (right)

Motivation

This example focuses on:

  • Only needing stack, instead of nix (GHCJS is full of nix, so nice with an alternative).
  • Make it play nicely with editor tooling such as HIE.
  • Use hpack to generate .cabal files, keeping common settings in package-lib.yaml.
  • Using the most current version of Miso (at the time of writing, version 0.21.2.0).

Additionally it sets up a nice development environment for VSCode (although it works fine without):

  • Linting, autocomplete, code hints, formatting, etc via HIE (check out how to set it up)
  • Step-through debugger via phoityne
  • A list of recommended extensions accessible via ⌘ ⇧ p and then typing Extension: Show Recommended Extensions
  • Several premade tasks for common things like:
    • Automatially building the whole project on changes, relaunching the server, and copying files to result
    • Building individual packages
    • Running tests, either once or on file changes

For more in-depth information, I recommend checking out the Miso isomorphic example, which links to some great resources.

Running the example

Using stack,

$ ./stack-build.sh
$ (cd result && bin/server)

NOTE: Same as the original isomorphic example, the it expects you start the server standing in result/, so it knows where to find the static files, in result/static/.

If using VSCode and you want to use the phoityne debugger,

$ (cd backend && stack build phoityne-vscode) \
  && (cd common && stack build phoityne-vscode)

Additionally, you might want fswatch, for the runner.sh/rebuild.sh scripts,

$ brew install fswatch # alternatively, sudo apt-get install fswatch

Code Structure

Each folder has a stack.yaml, which tells stack how to build the code. For convenience, you can build the whole project by using the stack-build.sh script, which also copies the files into the result directory.

As mentioned, the executable and JavaScript goes into the result/ directory, with the JS in result/static/all.js and the exec in result/bin/server.

Frontend

The JavaScript gets generated from the GHCJS frontend:

Filename Responsibility Build Type
src/Main.hs The Miso frontend application, which initialises and runs the app Executable

Backend

The Servant backend application, which takes care of routing and serving resources, is located in the backend:

Filename Responsibility Build Type
bin/Main.hs Sets up the WAI/Warp server that runs the servant backend Executable
src/App.hs The Servant backend application, which takes care of routing and serving resources Library
test/Main.hs Configures the hspec test environemnt Test
test/Spec.hs Automatically finds all test files that end on Spec.hs Test
test/Backend/AppSpec.hs Tests for src/App.hs (the default setup just checks that the endpoints return status code 200) Test

Common

Finally, the main bulk of the application is found in common/src/Common/. In this module we have:

Filename Responsibility Build Type
src/Common/Model.hs Contains the Model, initialModel and update Actions Library
src/Common/Routes.hs Sets up the links and servant routing trees Library
src/Common/View.hs Contains all the views, and a view router (viewModel), which takes care of displaying the correct view, or a page404View Library

Project

The project folder is simply a way to get the root files into the workspace, without putting the whole root folder in the workspace. You can cd project and then ln -s ../originalFile lnFile to get your files in here.

Development Environment

To open the project, open the Miso-Project.code-workspace, which is a VSCode Workspace. This will open the three folders, backend, common and frontend as folders in the workspace, and their individual settings will then take effect (this uses VSCodes multi-root workspaces).

NOTE: Currently the focus has been on getting a nice development environment set up for VSCode out-the-box, but there is nothing stopping you from using this with other editors, since it simply uses HIE and stack for everything.

Backend and Common

The backend, along common, can be developed with HIE, by placing a stack.yaml in their respective folders. The backend needs this, but for common we have only put one there to make HIE work nicely with it.

Nothing needs to be done to setup these, as long as you opened the project via Miso-Project.code-workspace and installed the recommend extensions it lists.

Frontend

For the frontend (GHCJS) I haven't find anything really satisfying yet, so I'm settling for running stack build on file changes, with the following,

$ stack --stack-yaml=frontend/stack.yaml build --fast --file-watch

Or run the VSCode task Watch Test Frontend (press F6). You can set this up in an external terminal, or just in VSCode's integrated terminal.

Tasks

Instead of remembering how to build and run and how to build/watch the frontend, some default tasks are set up.

You often just want to run Rebuild/Copy/Launch Everything!, which will start three separate tasks, so you don't have to do anything.

Name Command Description Keybinding
Build Project ./stack-build.sh Builds the whole project ⌘ ⇧ b, F7 or F6 task menu
Build Project and Launch Server ./stack-build.sh && cd result && bin/server Builds the whole project and launches the server F6 task menu
Relaunch Server on Change ./project/runner.sh bin/server Relaunches the server, every time the result/bin/server executable changes F6 task menu
Copy Build Files on Change ./project/rebuild.sh Copies the build files to result/.. every time the stack build artifacts change F6 task menu
Relaunch Server & Copy Build Files on Change ./project/rebuild.sh & ./project/runner.sh bin/server A combination of the relauncher and rebuilder (copier) F6 task menu
Build Backend stack --stack-yaml=backend/stack.yaml build --fast Builds the backend F6 task menu
Build Frontend stack --stack-yaml=frontend/stack.yaml build --fast Builds the frontend F6 task menu
Watch Test Backend stack --stack-yaml=backend/stack.yaml test --fast --haddock-deps --file-watch Runs tests for the backend on file changes F6 task menu
Watch Test Frontend stack --stack-yaml=frontend/stack.yaml test frontend --fast --haddock-deps --file-watch Runs tests for the frontend on file changes F6 task menu
Watch Build Frontend with Problem Matcher stack --stack-yaml=frontend/stack.yaml build frontend --fast --file-watch Builds the frontend on file changes, and reports the errors at the file location they were found F6 task menu
Watch Test Common stack --stack-yaml=common/stack.yaml test --fast --haddock-deps --file-watch Runs tests for common on file changes F6 task menu
Test Backend stack --stack-yaml=backend/stack.yaml test --fast Runs tests for the backend F8 or F6 task menu
Test Frontend stack --stack-yaml=frontend/stack.yaml test frontend --fast Runs tests for the frontend F6 task menu
Test Common stack --stack-yaml=common/stack.yaml test --fast Runs tests for the frontend F6 task menu

They all run in the correct directory. You can configure these in backend/.vscode/tasks.json, common/.vscode/tasks.json and frontend/.vscode/tasks.json.

Using Docker

If you don't want to waste time building GHCJS, then you can use the Dockerfile to build the frontend.

First build the image,

$ docker build -t ghcjs:lts-9.21 .
Sending build context to Docker daemon    626MB
Step 1/4 : FROM tehnix/ghcjs-docker:lts-9.21
...
 ---> 6ef295b59aaf
Successfully built 6ef295b59aaf

Next we need to copy the stack global root folder out into the project directory, so that we don't loose our build progress after each docker run (alternatively we would have to commit the file every time),

$ docker run -v $(pwd):/src -it ghcjs:lts-9.21 cp -R /root/.stack /src/.stack-docker

Then, to build the project, run,

$ docker run -v $(pwd):/src -it ghcjs:lts-9.21 stack --stack-root /src/.stack-docker --stack-yaml=frontend/stack.yaml build

And your project should build the frontend. This is all done automatically, if you use ./stack-build-docker.sh.

Without VSCode

There are four main pieces of this that I would recommend running in your terminal (each in their own),

$ stack --stack-yaml=backend/stack.yaml build --fast --file-watch
$ stack --stack-yaml=frontend/stack.yaml build --fast --file-watch
$ ./project/rebuild.sh & ./project/runner.sh bin/server

That should let you edit your files, and automatically build the backend/frontend, copy the files over and relaunch the server.

Miscellaneuous

Setting up HIE

The recommended way to setup HIE is to clone it down and then run make build-copy-compiler-tool,

$ git clone https://github.com/haskell/haskell-ide-engine.git \
  && cd haskell-ide-engine \
  && make build-copy-compiler-tool

This will, at the time of writing, install HIE for GHC 8.0.2, 8.2.1 and 8.2.2.

You can then find the locations with stack exec -- which hie, which will make sure to pick the right hie executable for your project (GHC versions need to match).

Using the Debugger

The documentation on phoityne is a bit sparse, but the setup here should work. It works by running backend/test/Spec.hs, and then follows through the code paths you activate here.

You can for example try and set a breakpoint on inside a view in common/src/Common/View.hs (e.g. inside homeView) and then press F5 to initialize the debugger and then press continue (or F5 again). You should now be at the breakpoint you just set.

Clean up File Tree Clutter

You can filter out files in the file tree that you rarely, if ever, access. Go into Settings -> Workspace Settings, and uncomment the lines you want/add new lines to the "files.exclude" object.

Default Enabled Extensions

By default a slew of extensions are enabled. This is mainly inspired by Alexis King's writeup An opinionated guide to Haskell in 2018, which makes good arguments for enabling these.

TODO

There's still some things that would be nice to have included here.

  • Database setup and some simple CRUD operations in the backend (e.g. using presistent12)
  • XHR requests in the frontend, do show how to communicate with the backend

miso-isomorphic-stack's People

Contributors

tehnix 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

ebenpack silky

miso-isomorphic-stack's Issues

Stack new template via URL

An update on stack new shows that its working on URLs commercialhaskell/stack#2681 (comment).

This means we could make a .hsfiles template in this repo and then point to that. I just tested it out on the simple template,

stack new TestServ https://raw.githubusercontent.com/commercialhaskell/stack-templates/master/simple.hsfiles

which works like one would expect.

Initial view not removed from DOM

The initial SSR view isn't being removed from the DOM when the client-side SPA loads. This causes two copies of the initial view to be stacked on the page initially, and the initial view continues to persist on the page even as the SPA view changes. The initial view is not interactive (e.g. clicking the 'Go to /flipped' has no effect on either copy of the view).

Initial view:
screen shot 2018-08-30 at 10 50 11 am

After interacting with the app:
screen shot 2018-08-30 at 11 20 03 am

This seems to be related to the addition of JSaddle. Reverting the JSaddle specific changes in frontend/Main.hs from the latest commit seem to resolve the issue. I'm not sure exactly what the intention is for using JSaddle here, but it seems like it fixes an issue I had previously where it wasn't really possible to specify a mountPoint for the app, so hopefully there's a better solution than just reverting.

I'm going to poke at this a bit, and see if I can't submit a PR, but tbh the solution is not immediately apparent to me.

Multi-line errors are created as multiple problems

The problemMatcher in VSCode unfortunately cannot handle multi-line errors (microsoft/vscode#9635). This means that currently, if the problem matching tasks is used, it'll look something like this,

screenshot 2018-04-06 01 26 35

While this is digestable, preferably this would only be one line.

An idea, because I doubt VSCode is gonna fix this any time soon, would be to send the stack output through a parser, which would convert the problem text to a single line, and leave everything else in tact.

A couple of regex playgrounds for the test log below,

Ignoring that the GHCJS boot package "aeson" has a different version, 1.1.1.0, than the resolver's wanted version, 1.0.2.1
lens-4.15.1: configure
lens-4.15.1: build
lens-4.15.1: copy/register
common-0.1.0.0: configure (lib)
common-0.1.0.0: build (lib)
common-0.1.0.0: copy/register
frontend-0.1.0.0: configure (lib + exe + test)
Configuring frontend-0.1.0.0...
frontend-0.1.0.0: build (lib + exe + test)
Preprocessing library frontend-0.1.0.0...
[1 of 2] Compiling Frontend.Update  ( src/Frontend/Update.hs, .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/Frontend/Update.js_o )

/Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/src/Frontend/Update.hs:3:1: warning: [-Wunused-imports]
    The import of ‘makeLenses’ from module ‘Control.Lens’ is redundant
[2 of 2] Compiling Paths_frontend   ( .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/autogen/Paths_frontend.hs, .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/Paths_frontend.js_o )
Preprocessing executable 'frontend' for frontend-0.1.0.0...
[1 of 1] Compiling Main             ( bin/Main.hs, .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/frontend/frontend-tmp/Main.js_o ) [Frontend.Update changed]
Linking .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/frontend/frontend.jsexe (Main)
Preprocessing test suite 'spec' for frontend-0.1.0.0...
[1 of 2] Compiling Frontend.UpdateSpec ( test/Frontend/UpdateSpec.hs, .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/spec/spec-tmp/Frontend/UpdateSpec.js_o ) [Frontend.Update changed]

/Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/test/Frontend/UpdateSpec.hs:5:1: warning: [-Wunused-imports]
    The import of ‘Common.Model’ is redundant
      except perhaps to import instances from ‘Common.Model’
    To import instances alone, use: import Common.Model()

/Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/test/Frontend/UpdateSpec.hs:6:1: warning: [-Wunused-imports]
    The import of ‘Frontend.Update’ is redundant
      except perhaps to import instances from ‘Frontend.Update’
    To import instances alone, use: import Frontend.Update()

/Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/test/Frontend/UpdateSpec.hs:13:7: warning: [-Wtype-defaults]
    • Defaulting the following constraints to type ‘[Char]’
        (Show a0)
          arising from a use of ‘shouldBe’
          at test/Frontend/UpdateSpec.hs:13:7-60
        (Eq a0)
          arising from a use of ‘shouldBe’
          at test/Frontend/UpdateSpec.hs:13:7-60
        (Data.String.IsString a0)
          arising from the literal ‘"Not implemented yet"’
          at test/Frontend/UpdateSpec.hs:13:7-27
    • In a stmt of a 'do' block:
        "Not implemented yet" `shouldBe` "Not implemented yet"
      In the second argument of ‘($)’, namely
        ‘do { "Not implemented yet" `shouldBe` "Not implemented yet" }’
      In the second argument of ‘($)’, namely
        ‘it "just works..."
         $ do { "Not implemented yet" `shouldBe` "Not implemented yet" }’
[2 of 2] Compiling Main             ( test/Spec.hs, .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/spec/spec-tmp/Main.js_o ) [Test.Hspec changed]
Linking .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/spec/spec.jsexe (Frontend.UpdateSpec,Main)
frontend-0.1.0.0: copy/register
Installing library in
/Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/.stack-work/install/x86_64-osx/lts-8.11/ghcjs-0.2.1.9008011_ghc-8.0.2/lib/x86_64-osx-ghcjs-0.2.1.9008011-ghc8_0_2/frontend-0.1.0.0-HK9srDkIBqEEHrW37I149C
Installing executable(s) in
/Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/.stack-work/install/x86_64-osx/lts-8.11/ghcjs-0.2.1.9008011_ghc-8.0.2/bin
Warning: the following files would be used as linker inputs, but linking is not being done: .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/frontend/frontend
Registering frontend-0.1.0.0...
frontend-0.1.0.0: test (suite: spec)

Progress: 3/4
Frontend.UpdateSpec
  Just a blank test
    just works...

Finished in 0.0200 seconds
1 example, 0 failures

frontend-0.1.0.0: Test suite spec passed
Completed 4 action(s).
ExitSuccess
Type help for available commands. Press enter to force a rebuild.

Log files have been written to: /Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/.stack-work/logs/
ExitSuccess
Type help for available commands. Press enter to force a rebuild.
Ignoring that the GHCJS boot package "aeson" has a different version, 1.1.1.0, than the resolver's wanted version, 1.0.2.1
frontend-0.1.0.0: unregistering (local file changes: src/Frontend/Update.hs)
frontend-0.1.0.0: build (lib + exe)
Log files have been written to: /Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/.stack-work/logs/

--  While building custom Setup.hs for package frontend-0.1.0.0 using:
      /Users/tehnix/.stack/setup-exe-cache/x86_64-osx/Cabal-simple_mPHDZzAJ_1.24.2.0_ghcjs-0.2.1.9008011_ghc-8.0.2 --builddir=.stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs build lib:frontend exe:frontend --ghcjs-options " -ddump-hi -ddump-to-file"
    Process exited with code: ExitFailure 1
    Logs have been written to: /Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/.stack-work/logs/frontend-0.1.0.0.log

    Preprocessing library frontend-0.1.0.0...
    [2 of 2] Compiling Frontend.Update  ( src/Frontend/Update.hs, .stack-work/dist/x86_64-osx/Cabal-1.24.2.0_ghcjs/build/Frontend/Update.js_o )

    /Users/tehnix/GitHub/Tehnix/miso-isomorphic-stack/frontend/src/Frontend/Update.hs:17:43: error:
        • Variable not in scope: ri :: Miso.URI
        • Perhaps you meant one of these:
            ‘uri’ (line 17), ‘pi’ (imported from Prelude),
            ‘Common.uri’ (imported from Common.Model)
Type help for available commands. Press enter to force a rebuild.

stack-build.sh fails with error msg

fatal: cabal-install program /home/std/.stack/snapshots/x86_64-linux/lts-9.21/8.0.2/bin/cabal does not support --allow-boot-library-installs (requires version 2.0.0.0 or newer)

Ubuntu 16.04 LTS, stack --version: Version 1.6.5, Git revision 24ab0d6ff07f28276e082c3ce74dfdeb1a2ca9e9 (5514 commits) x86_64 hpack-0.20.0

To be more precise, stack-build.sh actually runs without error message but no JS is created due to above error.

Is it impossible to use with stack 2?

I tried running this and it failed saying GHCJS is no longer supported by Stack. Turns out stack-2 does not support ghcjs.

Does this mean I can't use this with stack 2?

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.