diff --git a/frameworks/keyed/grain/.gitignore b/frameworks/keyed/grain/.gitignore new file mode 100644 index 000000000..30efe1997 --- /dev/null +++ b/frameworks/keyed/grain/.gitignore @@ -0,0 +1,10 @@ +/bower_components/ +/node_modules/ +/.pulp-cache/ +/output/ +/generated-docs/ +/.psc-package/ +/.psc* +/.purs* +/.psa* +/.spago diff --git a/frameworks/keyed/grain/index.html b/frameworks/keyed/grain/index.html new file mode 100644 index 000000000..3e051d400 --- /dev/null +++ b/frameworks/keyed/grain/index.html @@ -0,0 +1,12 @@ + + + + + Grain keyed + + + + +
+ + diff --git a/frameworks/keyed/grain/package.json b/frameworks/keyed/grain/package.json new file mode 100644 index 000000000..9db1c6d19 --- /dev/null +++ b/frameworks/keyed/grain/package.json @@ -0,0 +1,26 @@ +{ + "name": "js-framework-benchmark-grain", + "version": "1.0.0", + "description": "Grain demo", + "main": "index.js", + "js-framework-benchmark": { + "frameworkVersion": "1.0.0" + }, + "scripts": { + "spago": "spago", + "clean": "rm -rf output", + "build-prod": "spago bundle-app -t dist/bundle.js && terser --compress --mangle --enclose --output=dist/main.js -- dist/bundle.js" + }, + "author": "Shinya Takahashi ", + "license": "MIT", + "homepage": "https://github.com/krausest/js-framework-benchmark", + "repository": { + "type": "git", + "url": "https://github.com/krausest/js-framework-benchmark.git" + }, + "dependencies": { + "purescript": "^0.13.8", + "spago": "^0.15.3", + "terser": "^4.8.0" + } +} diff --git a/frameworks/keyed/grain/packages.dhall b/frameworks/keyed/grain/packages.dhall new file mode 100644 index 000000000..5142c8c2e --- /dev/null +++ b/frameworks/keyed/grain/packages.dhall @@ -0,0 +1,131 @@ +{- +Welcome to your new Dhall package-set! + +Below are instructions for how to edit this file for most use +cases, so that you don't need to know Dhall to use it. + +## Warning: Don't Move This Top-Level Comment! + +Due to how `dhall format` currently works, this comment's +instructions cannot appear near corresponding sections below +because `dhall format` will delete the comment. However, +it will not delete a top-level comment like this one. + +## Use Cases + +Most will want to do one or both of these options: +1. Override/Patch a package's dependency +2. Add a package not already in the default package set + +This file will continue to work whether you use one or both options. +Instructions for each option are explained below. + +### Overriding/Patching a package + +Purpose: +- Change a package's dependency to a newer/older release than the + default package set's release +- Use your own modified version of some dependency that may + include new API, changed API, removed API by + using your custom git repo of the library rather than + the package set's repo + +Syntax: +Replace the overrides' "{=}" (an empty record) with the following idea +The "//" or "⫽" means "merge these two records and + when they have the same value, use the one on the right:" +------------------------------- +let overrides = + { packageName = + upstream.packageName // { updateEntity1 = "new value", updateEntity2 = "new value" } + , packageName = + upstream.packageName // { version = "v4.0.0" } + , packageName = + upstream.packageName // { repo = "https://www.example.com/path/to/new/repo.git" } + } +------------------------------- + +Example: +------------------------------- +let overrides = + { halogen = + upstream.halogen // { version = "master" } + , halogen-vdom = + upstream.halogen-vdom // { version = "v4.0.0" } + } +------------------------------- + +### Additions + +Purpose: +- Add packages that aren't already included in the default package set + +Syntax: +Replace the additions' "{=}" (an empty record) with the following idea: +------------------------------- +let additions = + { package-name = + { dependencies = + [ "dependency1" + , "dependency2" + ] + , repo = + "https://example.com/path/to/git/repo.git" + , version = + "tag ('v4.0.0') or branch ('master')" + } + , package-name = + { dependencies = + [ "dependency1" + , "dependency2" + ] + , repo = + "https://example.com/path/to/git/repo.git" + , version = + "tag ('v4.0.0') or branch ('master')" + } + , etc. + } +------------------------------- + +Example: +------------------------------- +let additions = + { benchotron = + { dependencies = + [ "arrays" + , "exists" + , "profunctor" + , "strings" + , "quickcheck" + , "lcg" + , "transformers" + , "foldable-traversable" + , "exceptions" + , "node-fs" + , "node-buffer" + , "node-readline" + , "datetime" + , "now" + ] + , repo = + "https://github.com/hdgarrood/purescript-benchotron.git" + , version = + "v7.0.0" + } + } +------------------------------- +-} + + +let upstream = + https://github.com/purescript/package-sets/releases/download/psc-0.13.8/packages.dhall sha256:0e95ec11604dc8afc1b129c4d405dcc17290ce56d7d0665a0ff15617e32bbf03 + +let overrides = + { grain = + upstream.grain // { dependencies = [ "web-html" ], version = "v1.0.0" } + } + +let additions = {=} + +in upstream // overrides // additions diff --git a/frameworks/keyed/grain/spago.dhall b/frameworks/keyed/grain/spago.dhall new file mode 100644 index 000000000..b3f04dae4 --- /dev/null +++ b/frameworks/keyed/grain/spago.dhall @@ -0,0 +1,9 @@ +{- +Welcome to a Spago project! +You can edit this file as you like. +-} +{ name = "js-framework-benchmark-grain" +, dependencies = [ "grain", "random" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs" ] +} diff --git a/frameworks/keyed/grain/src/Main.purs b/frameworks/keyed/grain/src/Main.purs new file mode 100644 index 000000000..ad8b5eacc --- /dev/null +++ b/frameworks/keyed/grain/src/Main.purs @@ -0,0 +1,260 @@ +module Main where + +import Prelude + +import Control.Monad.Rec.Class (Step(..), tailRecM) +import Data.Array (delete, length, snoc, unsafeIndex, updateAtIndices, (!!)) +import Data.Maybe (Maybe(..)) +import Data.Newtype (class Newtype, over) +import Data.Tuple (Tuple(..)) +import Effect (Effect) +import Effect.Random (randomInt) +import Grain (class GlobalGrain, GProxy(..), VNode, fromConstructor, mount, useFinder, useUpdater, useValue) +import Grain.Markup as H +import Partial.Unsafe (unsafePartial) +import Web.DOM.Element (toNode) +import Web.DOM.ParentNode (QuerySelector(..), querySelector) +import Web.HTML (window) +import Web.HTML.HTMLDocument (toParentNode) +import Web.HTML.Window (document) + +main :: Effect Unit +main = do + maybeEl <- window >>= document <#> toParentNode >>= querySelector (QuerySelector "#main") + case maybeEl of + Nothing -> pure unit + Just el -> + mount view $ toNode el + +view :: VNode +view = + H.div # H.className "container" # H.kids + [ jumbotronView + , rowsView + , H.span + # H.className "preloadicon glyphicon glyphicon-remove" + # H.prop "aria-hidden" "true" + ] + +jumbotronView :: VNode +jumbotronView = H.component do + findState <- useFinder (GProxy :: _ State) + updateState <- useUpdater (GProxy :: _ State) + + let run_ count = do + State s <- findState + state <- addRows count $ State s { rows = [], selectedId = 0 } + updateState \_ -> state + + run = run_ 1000 + + runLots = run_ 10000 + + add = do + state <- findState >>= addRows 1000 + updateState \_ -> state + + updateEveryTenth i rows = + case rows !! i of + Just row -> + updateEveryTenth (i + 10) $ updateAtIndices + [ Tuple i row { label = row.label <> " !!!" } ] + rows + Nothing -> rows + + update_ = updateState $ over State \s -> s + { rows = updateEveryTenth 0 s.rows + } + + clear = + updateState $ over State _ { rows = [], selectedId = 0 } + + swapRows = + updateState $ over State \s -> + case s.rows !! 1, s.rows !! 998 of + Just row1, Just row2 -> + s { rows = + updateAtIndices + [ Tuple 1 row2, Tuple 998 row1 ] + s.rows + } + _, _ -> s + + pure $ H.div # H.className "jumbotron" # H.kids + [ H.div # H.className "row" # H.kids + [ H.div # H.className "col-md-6" # H.kids + [ H.h1 # H.kids [ H.text "Grain keyed" ] + ] + , H.div # H.className "col-md-6" # H.kids + [ H.div # H.className "row" # H.kids + [ buttonView { id: "run", label: "Create 1,000 rows", onClick: run } + , buttonView { id: "runlots", label: "Create 10,000 rows", onClick: runLots } + , buttonView { id: "add", label: "Append 1,000 rows", onClick: add } + , buttonView { id: "update", label: "Update every 10th row", onClick: update_ } + , buttonView { id: "clear", label: "Clear", onClick: clear } + , buttonView { id: "swaprows", label: "Swap Rows", onClick: swapRows } + ] + ] + ] + ] + +rowsView :: VNode +rowsView = H.component do + State { rows, selectedId } <- useValue (GProxy :: _ State) + updateState <- useUpdater (GProxy :: _ State) + + let select row = updateState $ over State _ { selectedId = row.id } + remove row = updateState $ over State \s -> s + { rows = delete row s.rows + } + + pure $ H.table + # H.className "table table-hover table-striped test-data" + # H.kids + [ H.tbody # H.kids + (rows <#> \row -> rowView { row, selected: row.id == selectedId, onSelect: select row, onRemove: remove row }) + ] + +rowView :: { row :: Row, selected :: Boolean, onSelect :: Effect Unit, onRemove :: Effect Unit } -> VNode +rowView { row, selected, onSelect, onRemove } = + let idTxt = show row.id + in H.tr + # H.key idTxt + # H.fingerprint (row.label <> if selected then "t" else "f") + # H.className (if selected then "danger" else "") + # H.kids + [ H.td # H.className "col-md-1" # H.kids [ H.text idTxt ] + , H.td # H.className "col-md-4" # H.kids + [ H.a # H.onClick (\_ -> onSelect) # H.kids [ H.text row.label ] + ] + , H.td # H.className "col-md-1" # H.kids + [ H.a # H.onClick (\_ -> onRemove) # H.kids [ iconView ] + ] + , H.td # H.className "col-md-6" + ] + +buttonView :: { id :: String, label :: String, onClick :: Effect Unit } -> VNode +buttonView { id, label, onClick } = + H.div # H.className "col-sm-6 smallpad" # H.kids + [ H.button + # H.id id + # H.type_ "button" + # H.className "btn btn-primary btn-block" + # H.onClick (\_ -> onClick) + # H.kids [ H.text label ] + ] + +iconView :: VNode +iconView = + H.fingerprint "remove-icon" $ H.span + # H.className "glyphicon glyphicon-remove" + # H.prop "aria-hidden" "true" + +type Row = + { id :: Int + , label :: String + } + +newtype State = State + { nextId :: Int + , rows :: Array Row + , selectedId :: Int + } + +derive instance newtypeState :: Newtype State _ + +instance globalGrainState :: GlobalGrain State where + typeRefOf _ = fromConstructor State + initialState _ = pure $ State + { nextId: 1 + , rows: [] + , selectedId: 0 + } + +addRows :: Int -> State -> Effect State +addRows count (State s) = + tailRecM go { state: s, addedCount: 0 } + where + go { state, addedCount } + | addedCount >= count = + pure $ Done $ State state + | otherwise = do + a <- sample adjectives + c <- sample colours + n <- sample nouns + let id = state.nextId + label = a <> " " <> c <> " " <> n + state' = state + { nextId = state.nextId + 1 + , rows = snoc state.rows { id, label } + } + pure $ Loop + { state: state' + , addedCount: addedCount + 1 + } + +sample :: forall a. Array a -> Effect a +sample xs = do + i <- randomInt 0 $ length xs - 1 + pure $ unsafePartial $ unsafeIndex xs i + +adjectives :: Array String +adjectives = + [ "pretty" + , "large" + , "big" + , "small" + , "tall" + , "short" + , "long" + , "handsome" + , "plain" + , "quaint" + , "clean" + , "elegant" + , "easy" + , "angry" + , "crazy" + , "helpful" + , "mushy" + , "odd" + , "unsightly" + , "adorable" + , "important" + , "inexpensive" + , "cheap" + , "expensive" + , "fancy" + ] + +colours :: Array String +colours = + [ "red" + , "yellow" + , "blue" + , "green" + , "pink" + , "brown" + , "purple" + , "brown" + , "white" + , "black" + , "orange" + ] + +nouns :: Array String +nouns = + [ "table" + , "chair" + , "house" + , "bbq" + , "desk" + , "car" + , "pony" + , "cookie" + , "sandwich" + , "burger" + , "pizza" + , "mouse" + , "keyboard" + ] diff --git a/frameworks/keyed/halogen/.gitignore b/frameworks/keyed/halogen/.gitignore new file mode 100644 index 000000000..2b9d6603c --- /dev/null +++ b/frameworks/keyed/halogen/.gitignore @@ -0,0 +1,11 @@ +# Dependencies +.psci_modules +.spago +bower_components +node_modules + +# Generated files +.psci +.psc* +output + diff --git a/frameworks/keyed/halogen/index.html b/frameworks/keyed/halogen/index.html new file mode 100644 index 000000000..89fa48742 --- /dev/null +++ b/frameworks/keyed/halogen/index.html @@ -0,0 +1,11 @@ + + + + + Halogen v5.0.0 + + + + + + diff --git a/frameworks/keyed/halogen/package.json b/frameworks/keyed/halogen/package.json new file mode 100644 index 000000000..46235b3c5 --- /dev/null +++ b/frameworks/keyed/halogen/package.json @@ -0,0 +1,30 @@ +{ + "name": "js-framework-benchmark-keyed-halogen", + "version": "1.0.0", + "description": "Purescript Halogen JS Benchmark", + "main": "index.js", + "js-framework-benchmark": { + "frameworkVersion": "5.0.0" + }, + "scripts": { + "postinstall": "spago install", + "clean": "rm -rf output .spago node_modules", + "build": "spago build", + "build-prod": "spago bundle-app --to output/bundle.js" + }, + "keywords": [ + "purescript", + "halogen" + ], + "author": "Thomas Honeyman ", + "license": "ISC", + "homepage": "https://github.com/krausest/js-framework-benchmark", + "repository": { + "type": "git", + "url": "https://github.com/krausest/js-framework-benchmark.git" + }, + "devDependencies": { + "purescript": "0.13.6", + "spago": "0.14.0" + } +} diff --git a/frameworks/keyed/halogen/packages.dhall b/frameworks/keyed/halogen/packages.dhall new file mode 100644 index 000000000..46e8ae3c0 --- /dev/null +++ b/frameworks/keyed/halogen/packages.dhall @@ -0,0 +1,11 @@ +let upstream = + https://github.com/purescript/package-sets/releases/download/psc-0.13.6-20200331/packages.dhall sha256:350af1fdc68c91251138198f03ceedc4f8ed6651ee2af8a2177f87bcd64570d4 + +let overrides = + { halogen = + upstream.halogen // { version = "v5.0.0-rc.8" } + } + +let additions = {=} + +in upstream // overrides // additions diff --git a/frameworks/keyed/halogen/spago.dhall b/frameworks/keyed/halogen/spago.dhall new file mode 100644 index 000000000..96b768b17 --- /dev/null +++ b/frameworks/keyed/halogen/spago.dhall @@ -0,0 +1,5 @@ +{ name = "js-framework-benchmark-halogen" +, dependencies = [ "halogen" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs" ] +} diff --git a/frameworks/keyed/halogen/src/Main.js b/frameworks/keyed/halogen/src/Main.js new file mode 100644 index 000000000..97b2ad72f --- /dev/null +++ b/frameworks/keyed/halogen/src/Main.js @@ -0,0 +1,24 @@ +function _random(max) { + return Math.round(Math.random() * 1000) % max; +} + +exports.createRandomNRowsImpl = function( + adjectives, + colours, + nouns, + count, + lastId +) { + var data = []; + for (var i = 0; i < count; i++) + data.push({ + id: ++lastId, + label: + adjectives[_random(adjectives.length)] + + " " + + colours[_random(colours.length)] + + " " + + nouns[_random(nouns.length)] + }); + return data; +}; diff --git a/frameworks/keyed/halogen/src/Main.purs b/frameworks/keyed/halogen/src/Main.purs new file mode 100644 index 000000000..680c54537 --- /dev/null +++ b/frameworks/keyed/halogen/src/Main.purs @@ -0,0 +1,272 @@ +module Main where + +import Prelude + +import Data.Array as Array +import Data.Maybe (Maybe(..)) +import Data.Tuple (Tuple(..)) +import Effect (Effect) +import Effect.Aff (Aff) +import Effect.Uncurried (EffectFn5, runEffectFn5) +import Halogen as H +import Halogen.Aff as HA +import Halogen.HTML as HH +import Halogen.HTML.Elements.Keyed as HK +import Halogen.HTML.Events as HE +import Halogen.HTML.Properties as HP +import Halogen.VDom.Driver (runUI) + +main :: Effect Unit +main = HA.runHalogenAff do + body <- HA.awaitBody + runUI app unit body + +data Action + = Create Int + | AppendOneThousand + | UpdateEveryTenth + | Clear + | Swap + | Remove Int + | Select Int + +type State = + { rows :: Array Row + , lastId :: Int + , selectedId :: Int + } + +type Row = + { id :: Int + , label :: String + } + +app :: forall q i o. H.Component HH.HTML q i o Aff +app = H.mkComponent + { initialState + , render + , eval: H.mkEval $ H.defaultEval { handleAction = handleAction } + } + where + initialState :: _ -> State + initialState _ = { rows: [], lastId: 0, selectedId: 0 } + + render :: forall ps. State -> H.ComponentHTML Action ps Aff + render state = + HH.div + [ class_ "container" ] + [ jumbotron + , HH.table + [ class_ "table table-hover table-striped test-data" ] + [ HK.tbody_ do + state.rows <#> \row -> + Tuple (show row.id) (renderRow state.selectedId row) + ] + , footer + ] + + handleAction :: forall ps. Action -> H.HalogenM State Action ps o Aff Unit + handleAction = case _ of + Create amount -> do + state <- H.get + newRows <- H.liftEffect $ createRandomNRows amount state.lastId + H.modify_ _ { rows = newRows, lastId = state.lastId + amount } + + AppendOneThousand -> do + state <- H.get + let amount = 1000 + newRows <- H.liftEffect $ createRandomNRows amount state.lastId + H.modify_ _ { rows = state.rows <> newRows, lastId = state.lastId + amount } + + UpdateEveryTenth -> do + let + updateLabel ix row = + if ix `mod` 10 == 0 then row { label = row.label <> " !!!" } else row + + H.modify_ \state -> state { rows = Array.mapWithIndex updateLabel state.rows } + + Clear -> + H.modify_ _ { rows = [] } + + Swap -> do + state <- H.get + case swapRows state.rows 1 998 of + Nothing -> pure unit + Just rows -> H.modify_ _ { rows = rows } + + Remove id -> + H.modify_ \state -> + state { rows = Array.filter (\r -> r.id /= id) state.rows } + + Select id -> do + state <- H.get + if state.selectedId == id then + pure unit + else + H.modify_ _ { selectedId = id } + +type ActionButton = { id :: String, label :: String, action :: Action } + +buttons :: Array ActionButton +buttons = + [ { id: "run", label: "Create 1,000 rows", action: Create 1000 } + , { id: "runlots", label: "Create 10,000 rows", action: Create 10000 } + , { id: "add", label: "Append 1,000 rows", action: AppendOneThousand } + , { id: "update", label: "Update every 10th row", action: UpdateEveryTenth } + , { id: "clear", label: "Clear", action: Clear } + , { id: "swaprows", label: "Swap Rows", action: Swap } + ] + +renderActionButton :: forall ps. ActionButton -> H.ComponentHTML Action ps Aff +renderActionButton { id, label, action } = + HH.div + [ class_ "col-sm-6 smallpad" ] + [ HH.button + [ HP.type_ HP.ButtonButton + , class_ "btn btn-primary btn-block" + , HP.id_ id + , HP.attr (HH.AttrName "ref") "text" + , HE.onClick \_ -> Just action + ] + [ HH.text label ] + ] + +renderRow :: forall ps. Int -> Row -> H.ComponentHTML Action ps Aff +renderRow selectedId row = + HH.tr + (if selectedId == row.id then + [ class_ "danger" + , HP.attr (HH.AttrName "selected") "true" + ] + else + [ ] + ) + [ HH.td colMd1 [ HH.text (show row.id) ] + , HH.td colMd4 [ HH.a [ HE.onClick \_ -> Just (Select row.id) ] [ HH.text row.label ] ] + , HH.td colMd1 [ HH.a [ HE.onClick \_ -> Just (Remove row.id) ] removeIcon ] + , spacer + ] + +removeIcon :: forall ps. Array (H.ComponentHTML Action ps Aff) +removeIcon = + [ HH.span + [ class_ "glyphicon glyphicon-remove" + , HP.attr (HH.AttrName "aria-hidden") "true" + ] + [] + ] + +class_ :: forall r i. String -> HH.IProp ( class :: String | r ) i +class_ = HP.class_ <<< HH.ClassName + +colMd1 :: forall r i. Array (HH.IProp ( class :: String | r ) i) +colMd1 = [ class_ "col-md-1" ] + +colMd4 :: forall r i. Array (HH.IProp ( class :: String | r) i) +colMd4 = [ class_ "col-md-4" ] + +spacer :: forall p i. HH.HTML p i +spacer = HH.td [ class_ "col-md-6" ] [] + +footer :: forall ps. H.ComponentHTML Action ps Aff +footer = + HH.span + [ class_ "preloadicon glyphicon glyphicon-remove" + , HP.attr (HH.AttrName "aria-hidden") "true" + ] + [] + +jumbotron :: forall ps. H.ComponentHTML Action ps Aff +jumbotron = + HH.div + [ class_ "jumbotron" ] + [ HH.div + [ class_ "row" ] + [ HH.div + [ class_ "col-md-6" ] + [ HH.h1_ [ HH.text "Halogen 5.0.0 (keyed)" ] ] + , HH.div [ class_ "col-md-6" ] do + map (HH.lazy renderActionButton) buttons + ] + ] + +updateEveryTenth :: Array Row -> Array Row +updateEveryTenth = + Array.mapWithIndex updateRowLabel + where + updateRowLabel ix row = + if ix `mod` 10 == 0 then row { label = row.label <> " !!!" } else row + +swapRows :: Array Row -> Int -> Int -> Maybe (Array Row) +swapRows arr index1 index2 = do + rowA <- arr Array.!! index1 + rowB <- arr Array.!! index2 + arrA <- Array.updateAt index1 rowB arr + arrB <- Array.updateAt index2 rowA arrA + pure arrB + +foreign import createRandomNRowsImpl :: EffectFn5 (Array String) (Array String) (Array String) Int Int (Array Row) + +createRandomNRows :: Int -> Int -> Effect (Array Row) +createRandomNRows n lastId = runEffectFn5 createRandomNRowsImpl adjectives colours nouns n lastId + +adjectives :: Array String +adjectives = + [ "pretty" + , "large" + , "big" + , "small" + , "tall" + , "short" + , "long" + , "handsome" + , "plain" + , "quaint" + , "clean" + , "elegant" + , "easy" + , "angry" + , "crazy" + , "helpful" + , "mushy" + , "odd" + , "unsightly" + , "adorable" + , "important" + , "inexpensive" + , "cheap" + , "expensive" + , "fancy" + ] + +colours :: Array String +colours = + [ "red" + , "yellow" + , "blue" + , "green" + , "pink" + , "brown" + , "purple" + , "brown" + , "white" + , "black" + , "orange" + ] + +nouns :: Array String +nouns = + [ "table" + , "chair" + , "house" + , "bbq" + , "desk" + , "car" + , "pony" + , "cookie" + , "sandwich" + , "burger" + , "pizza" + , "mouse" + , "keyboard" + ] diff --git a/frameworks/non-keyed/grain/.gitignore b/frameworks/non-keyed/grain/.gitignore new file mode 100644 index 000000000..30efe1997 --- /dev/null +++ b/frameworks/non-keyed/grain/.gitignore @@ -0,0 +1,10 @@ +/bower_components/ +/node_modules/ +/.pulp-cache/ +/output/ +/generated-docs/ +/.psc-package/ +/.psc* +/.purs* +/.psa* +/.spago diff --git a/frameworks/non-keyed/grain/index.html b/frameworks/non-keyed/grain/index.html new file mode 100644 index 000000000..c8497b1bd --- /dev/null +++ b/frameworks/non-keyed/grain/index.html @@ -0,0 +1,12 @@ + + + + + Grain non-keyed + + + + +
+ + diff --git a/frameworks/non-keyed/grain/package.json b/frameworks/non-keyed/grain/package.json new file mode 100644 index 000000000..9db1c6d19 --- /dev/null +++ b/frameworks/non-keyed/grain/package.json @@ -0,0 +1,26 @@ +{ + "name": "js-framework-benchmark-grain", + "version": "1.0.0", + "description": "Grain demo", + "main": "index.js", + "js-framework-benchmark": { + "frameworkVersion": "1.0.0" + }, + "scripts": { + "spago": "spago", + "clean": "rm -rf output", + "build-prod": "spago bundle-app -t dist/bundle.js && terser --compress --mangle --enclose --output=dist/main.js -- dist/bundle.js" + }, + "author": "Shinya Takahashi ", + "license": "MIT", + "homepage": "https://github.com/krausest/js-framework-benchmark", + "repository": { + "type": "git", + "url": "https://github.com/krausest/js-framework-benchmark.git" + }, + "dependencies": { + "purescript": "^0.13.8", + "spago": "^0.15.3", + "terser": "^4.8.0" + } +} diff --git a/frameworks/non-keyed/grain/packages.dhall b/frameworks/non-keyed/grain/packages.dhall new file mode 100644 index 000000000..5142c8c2e --- /dev/null +++ b/frameworks/non-keyed/grain/packages.dhall @@ -0,0 +1,131 @@ +{- +Welcome to your new Dhall package-set! + +Below are instructions for how to edit this file for most use +cases, so that you don't need to know Dhall to use it. + +## Warning: Don't Move This Top-Level Comment! + +Due to how `dhall format` currently works, this comment's +instructions cannot appear near corresponding sections below +because `dhall format` will delete the comment. However, +it will not delete a top-level comment like this one. + +## Use Cases + +Most will want to do one or both of these options: +1. Override/Patch a package's dependency +2. Add a package not already in the default package set + +This file will continue to work whether you use one or both options. +Instructions for each option are explained below. + +### Overriding/Patching a package + +Purpose: +- Change a package's dependency to a newer/older release than the + default package set's release +- Use your own modified version of some dependency that may + include new API, changed API, removed API by + using your custom git repo of the library rather than + the package set's repo + +Syntax: +Replace the overrides' "{=}" (an empty record) with the following idea +The "//" or "⫽" means "merge these two records and + when they have the same value, use the one on the right:" +------------------------------- +let overrides = + { packageName = + upstream.packageName // { updateEntity1 = "new value", updateEntity2 = "new value" } + , packageName = + upstream.packageName // { version = "v4.0.0" } + , packageName = + upstream.packageName // { repo = "https://www.example.com/path/to/new/repo.git" } + } +------------------------------- + +Example: +------------------------------- +let overrides = + { halogen = + upstream.halogen // { version = "master" } + , halogen-vdom = + upstream.halogen-vdom // { version = "v4.0.0" } + } +------------------------------- + +### Additions + +Purpose: +- Add packages that aren't already included in the default package set + +Syntax: +Replace the additions' "{=}" (an empty record) with the following idea: +------------------------------- +let additions = + { package-name = + { dependencies = + [ "dependency1" + , "dependency2" + ] + , repo = + "https://example.com/path/to/git/repo.git" + , version = + "tag ('v4.0.0') or branch ('master')" + } + , package-name = + { dependencies = + [ "dependency1" + , "dependency2" + ] + , repo = + "https://example.com/path/to/git/repo.git" + , version = + "tag ('v4.0.0') or branch ('master')" + } + , etc. + } +------------------------------- + +Example: +------------------------------- +let additions = + { benchotron = + { dependencies = + [ "arrays" + , "exists" + , "profunctor" + , "strings" + , "quickcheck" + , "lcg" + , "transformers" + , "foldable-traversable" + , "exceptions" + , "node-fs" + , "node-buffer" + , "node-readline" + , "datetime" + , "now" + ] + , repo = + "https://github.com/hdgarrood/purescript-benchotron.git" + , version = + "v7.0.0" + } + } +------------------------------- +-} + + +let upstream = + https://github.com/purescript/package-sets/releases/download/psc-0.13.8/packages.dhall sha256:0e95ec11604dc8afc1b129c4d405dcc17290ce56d7d0665a0ff15617e32bbf03 + +let overrides = + { grain = + upstream.grain // { dependencies = [ "web-html" ], version = "v1.0.0" } + } + +let additions = {=} + +in upstream // overrides // additions diff --git a/frameworks/non-keyed/grain/spago.dhall b/frameworks/non-keyed/grain/spago.dhall new file mode 100644 index 000000000..b3f04dae4 --- /dev/null +++ b/frameworks/non-keyed/grain/spago.dhall @@ -0,0 +1,9 @@ +{- +Welcome to a Spago project! +You can edit this file as you like. +-} +{ name = "js-framework-benchmark-grain" +, dependencies = [ "grain", "random" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs" ] +} diff --git a/frameworks/non-keyed/grain/src/Main.purs b/frameworks/non-keyed/grain/src/Main.purs new file mode 100644 index 000000000..94f1700d2 --- /dev/null +++ b/frameworks/non-keyed/grain/src/Main.purs @@ -0,0 +1,259 @@ +module Main where + +import Prelude + +import Control.Monad.Rec.Class (Step(..), tailRecM) +import Data.Array (delete, length, snoc, unsafeIndex, updateAtIndices, (!!)) +import Data.Maybe (Maybe(..)) +import Data.Newtype (class Newtype, over) +import Data.Tuple (Tuple(..)) +import Effect (Effect) +import Effect.Random (randomInt) +import Grain (class GlobalGrain, GProxy(..), VNode, fromConstructor, mount, useFinder, useUpdater, useValue) +import Grain.Markup as H +import Partial.Unsafe (unsafePartial) +import Web.DOM.Element (toNode) +import Web.DOM.ParentNode (QuerySelector(..), querySelector) +import Web.HTML (window) +import Web.HTML.HTMLDocument (toParentNode) +import Web.HTML.Window (document) + +main :: Effect Unit +main = do + maybeEl <- window >>= document <#> toParentNode >>= querySelector (QuerySelector "#main") + case maybeEl of + Nothing -> pure unit + Just el -> + mount view $ toNode el + +view :: VNode +view = + H.div # H.className "container" # H.kids + [ jumbotronView + , rowsView + , H.span + # H.className "preloadicon glyphicon glyphicon-remove" + # H.prop "aria-hidden" "true" + ] + +jumbotronView :: VNode +jumbotronView = H.component do + findState <- useFinder (GProxy :: _ State) + updateState <- useUpdater (GProxy :: _ State) + + let run_ count = do + State s <- findState + state <- addRows count $ State s { rows = [], selectedId = 0 } + updateState \_ -> state + + run = run_ 1000 + + runLots = run_ 10000 + + add = do + state <- findState >>= addRows 1000 + updateState \_ -> state + + updateEveryTenth i rows = + case rows !! i of + Just row -> + updateEveryTenth (i + 10) $ updateAtIndices + [ Tuple i row { label = row.label <> " !!!" } ] + rows + Nothing -> rows + + update_ = updateState $ over State \s -> s + { rows = updateEveryTenth 0 s.rows + } + + clear = + updateState $ over State _ { rows = [], selectedId = 0 } + + swapRows = + updateState $ over State \s -> + case s.rows !! 1, s.rows !! 998 of + Just row1, Just row2 -> + s { rows = + updateAtIndices + [ Tuple 1 row2, Tuple 998 row1 ] + s.rows + } + _, _ -> s + + pure $ H.div # H.className "jumbotron" # H.kids + [ H.div # H.className "row" # H.kids + [ H.div # H.className "col-md-6" # H.kids + [ H.h1 # H.kids [ H.text "Grain non-keyed" ] + ] + , H.div # H.className "col-md-6" # H.kids + [ H.div # H.className "row" # H.kids + [ buttonView { id: "run", label: "Create 1,000 rows", onClick: run } + , buttonView { id: "runlots", label: "Create 10,000 rows", onClick: runLots } + , buttonView { id: "add", label: "Append 1,000 rows", onClick: add } + , buttonView { id: "update", label: "Update every 10th row", onClick: update_ } + , buttonView { id: "clear", label: "Clear", onClick: clear } + , buttonView { id: "swaprows", label: "Swap Rows", onClick: swapRows } + ] + ] + ] + ] + +rowsView :: VNode +rowsView = H.component do + State { rows, selectedId } <- useValue (GProxy :: _ State) + updateState <- useUpdater (GProxy :: _ State) + + let select row = updateState $ over State _ { selectedId = row.id } + remove row = updateState $ over State \s -> s + { rows = delete row s.rows + } + + pure $ H.table + # H.className "table table-hover table-striped test-data" + # H.kids + [ H.tbody # H.kids + (rows <#> \row -> rowView { row, selected: row.id == selectedId, onSelect: select row, onRemove: remove row }) + ] + +rowView :: { row :: Row, selected :: Boolean, onSelect :: Effect Unit, onRemove :: Effect Unit } -> VNode +rowView { row, selected, onSelect, onRemove } = + let idTxt = show row.id + in H.tr + # H.fingerprint (row.label <> if selected then "t" else "f") + # H.className (if selected then "danger" else "") + # H.kids + [ H.td # H.className "col-md-1" # H.kids [ H.text idTxt ] + , H.td # H.className "col-md-4" # H.kids + [ H.a # H.onClick (\_ -> onSelect) # H.kids [ H.text row.label ] + ] + , H.td # H.className "col-md-1" # H.kids + [ H.a # H.onClick (\_ -> onRemove) # H.kids [ iconView ] + ] + , H.td # H.className "col-md-6" + ] + +buttonView :: { id :: String, label :: String, onClick :: Effect Unit } -> VNode +buttonView { id, label, onClick } = + H.div # H.className "col-sm-6 smallpad" # H.kids + [ H.button + # H.id id + # H.type_ "button" + # H.className "btn btn-primary btn-block" + # H.onClick (\_ -> onClick) + # H.kids [ H.text label ] + ] + +iconView :: VNode +iconView = + H.fingerprint "remove-icon" $ H.span + # H.className "glyphicon glyphicon-remove" + # H.prop "aria-hidden" "true" + +type Row = + { id :: Int + , label :: String + } + +newtype State = State + { nextId :: Int + , rows :: Array Row + , selectedId :: Int + } + +derive instance newtypeState :: Newtype State _ + +instance globalGrainState :: GlobalGrain State where + typeRefOf _ = fromConstructor State + initialState _ = pure $ State + { nextId: 1 + , rows: [] + , selectedId: 0 + } + +addRows :: Int -> State -> Effect State +addRows count (State s) = + tailRecM go { state: s, addedCount: 0 } + where + go { state, addedCount } + | addedCount >= count = + pure $ Done $ State state + | otherwise = do + a <- sample adjectives + c <- sample colours + n <- sample nouns + let id = state.nextId + label = a <> " " <> c <> " " <> n + state' = state + { nextId = state.nextId + 1 + , rows = snoc state.rows { id, label } + } + pure $ Loop + { state: state' + , addedCount: addedCount + 1 + } + +sample :: forall a. Array a -> Effect a +sample xs = do + i <- randomInt 0 $ length xs - 1 + pure $ unsafePartial $ unsafeIndex xs i + +adjectives :: Array String +adjectives = + [ "pretty" + , "large" + , "big" + , "small" + , "tall" + , "short" + , "long" + , "handsome" + , "plain" + , "quaint" + , "clean" + , "elegant" + , "easy" + , "angry" + , "crazy" + , "helpful" + , "mushy" + , "odd" + , "unsightly" + , "adorable" + , "important" + , "inexpensive" + , "cheap" + , "expensive" + , "fancy" + ] + +colours :: Array String +colours = + [ "red" + , "yellow" + , "blue" + , "green" + , "pink" + , "brown" + , "purple" + , "brown" + , "white" + , "black" + , "orange" + ] + +nouns :: Array String +nouns = + [ "table" + , "chair" + , "house" + , "bbq" + , "desk" + , "car" + , "pony" + , "cookie" + , "sandwich" + , "burger" + , "pizza" + , "mouse" + , "keyboard" + ]