From 7431bfd56dcf7751c69a24bba1fd6f6bb4ad3510 Mon Sep 17 00:00:00 2001 From: Jessie James Cosare Date: Mon, 24 Jun 2019 16:59:07 +0800 Subject: [PATCH 1/6] - migrated bootstrap from v3 to v4 Signed-off-by: Jessie James Cosare --- .../client/components/Bootstrap.scala | 10 ++++---- .../client/components/BootstrapStyles.scala | 23 +++++++++++-------- .../spatutorial/client/components/Motd.scala | 2 +- .../client/components/TodoList.scala | 4 ++-- .../spatutorial/client/modules/MainMenu.scala | 10 ++++---- .../spatutorial/client/modules/TODO.scala | 12 ++++++---- 6 files changed, 34 insertions(+), 27 deletions(-) diff --git a/client/src/main/scala/spatutorial/client/components/Bootstrap.scala b/client/src/main/scala/spatutorial/client/components/Bootstrap.scala index ce044df..d7a28e3 100644 --- a/client/src/main/scala/spatutorial/client/components/Bootstrap.scala +++ b/client/src/main/scala/spatutorial/client/components/Bootstrap.scala @@ -42,15 +42,15 @@ object Bootstrap { def apply() = component } - object Panel { + object Card { case class Props(heading: String, style: CommonStyle.Value = CommonStyle.default) - val component = ScalaComponent.builder[Props]("Panel") + val component = ScalaComponent.builder[Props]("Card") .renderPC((_, p, c) => - <.div(bss.panelOpt(p.style), - <.div(bss.panelHeading, p.heading), - <.div(bss.panelBody, c) + <.div(bss.cardOpt(p.style), + <.div(bss.cardHeading, p.heading), + <.div(bss.cardBody, c) ) ).build diff --git a/client/src/main/scala/spatutorial/client/components/BootstrapStyles.scala b/client/src/main/scala/spatutorial/client/components/BootstrapStyles.scala index a17a546..59994f0 100644 --- a/client/src/main/scala/spatutorial/client/components/BootstrapStyles.scala +++ b/client/src/main/scala/spatutorial/client/components/BootstrapStyles.scala @@ -27,19 +27,19 @@ class BootstrapStyles(implicit r: mutable.Register) extends StyleSheet.Inline()( val button = buttonOpt(default) - val panelOpt = commonStyle(csDomain, "panel") + val cardOpt = commonStyle(csDomain, "card") - val panel = panelOpt(default) + val card = cardOpt(default) - val labelOpt = commonStyle(csDomain, "label") + val badgeOpt = commonStyle(csDomain, "badge") - val label = labelOpt(default) + val badge = badgeOpt(default) val alert = commonStyle(contextDomain, "alert") - val panelHeading = styleWrap("panel-heading") + val cardHeading = styleWrap("card-header") - val panelBody = styleWrap("panel-body") + val cardBody = styleWrap("card-body") // wrap styles in a namespace, assign to val to prevent lazy initialization object modal { @@ -61,14 +61,19 @@ class BootstrapStyles(implicit r: mutable.Register) extends StyleSheet.Inline()( } val _listGroup = listGroup - val pullRight = styleWrap("pull-right") - val buttonXS = styleWrap("btn-xs") + val floatRight = styleWrap("float-right") + val buttonSM = styleWrap("btn-sm") + val buttonSecondary = styleWrap("btn-secondary") val close = styleWrap("close") - val labelAsBadge = style(addClassName("label-as-badge"), borderRadius(1.em)) + val badgePill = style(addClassName("badge-pill"), borderRadius(1.em)) val navbar = styleWrap("navbar-nav", "mr-auto") val formGroup = styleWrap("form-group") val formControl = styleWrap("form-control") + + val spacingMR1 = styleWrap("mr-1") + val spacingMT3 = styleWrap("mt-3") + } diff --git a/client/src/main/scala/spatutorial/client/components/Motd.scala b/client/src/main/scala/spatutorial/client/components/Motd.scala index 4780e56..ffb7aeb 100644 --- a/client/src/main/scala/spatutorial/client/components/Motd.scala +++ b/client/src/main/scala/spatutorial/client/components/Motd.scala @@ -16,7 +16,7 @@ object Motd { // create the React component for holding the Message of the Day val Motd = ScalaComponent.builder[ModelProxy[Pot[String]]]("Motd") .render_P { proxy => - Panel(Panel.Props("Message of the day"), + Card(Card.Props("Message of the day"), // render messages depending on the state of the Pot proxy().renderPending(_ > 500, _ => <.p("Loading...")), proxy().renderFailed(ex => <.p("Failed to load")), diff --git a/client/src/main/scala/spatutorial/client/components/TodoList.scala b/client/src/main/scala/spatutorial/client/components/TodoList.scala index 425f33c..24863c7 100644 --- a/client/src/main/scala/spatutorial/client/components/TodoList.scala +++ b/client/src/main/scala/spatutorial/client/components/TodoList.scala @@ -31,8 +31,8 @@ object TodoList { <.input.checkbox(^.checked := item.completed, ^.onChange --> p.stateChange(item.copy(completed = !item.completed))), <.span(" "), if (item.completed) <.s(item.content) else <.span(item.content), - Button(Button.Props(p.editItem(item), addStyles = Seq(bss.pullRight, bss.buttonXS)), "Edit"), - Button(Button.Props(p.deleteItem(item), addStyles = Seq(bss.pullRight, bss.buttonXS)), "Delete") + Button(Button.Props(p.editItem(item), addStyles = Seq(bss.floatRight, bss.buttonSM, bss.buttonSecondary)), "Edit"), + Button(Button.Props(p.deleteItem(item), addStyles = Seq(bss.floatRight, bss.buttonSM, bss.buttonSecondary, bss.spacingMR1)), "Delete") ) } <.ul(style.listGroup)(p.items toTagMod renderItem) diff --git a/client/src/main/scala/spatutorial/client/modules/MainMenu.scala b/client/src/main/scala/spatutorial/client/modules/MainMenu.scala index d13865b..228a242 100644 --- a/client/src/main/scala/spatutorial/client/modules/MainMenu.scala +++ b/client/src/main/scala/spatutorial/client/modules/MainMenu.scala @@ -18,19 +18,19 @@ object MainMenu { case class Props(router: RouterCtl[Loc], currentLoc: Loc, proxy: ModelProxy[Option[Int]]) - private case class MenuItem(idx: Int, label: (Props) => VdomNode, icon: Icon, location: Loc) + private case class MenuItem(idx: Int, badge: (Props) => VdomNode, icon: Icon, location: Loc) // build the Todo menu item, showing the number of open todos private def buildTodoMenu(props: Props): VdomElement = { val todoCount = props.proxy().getOrElse(0) <.span( - <.span("Todo "), - <.span(bss.labelOpt(CommonStyle.danger), bss.labelAsBadge, todoCount).when(todoCount > 0) + <.span(" Todo "), + <.span(bss.badgeOpt(CommonStyle.danger), bss.badgePill, todoCount).when(todoCount > 0) ) } private val menuItems = Seq( - MenuItem(1, _ => "Dashboard", Icon.dashboard, DashboardLoc), + MenuItem(1, _ => " Dashboard", Icon.dashboard, DashboardLoc), MenuItem(2, buildTodoMenu, Icon.check, TodoLoc) ) @@ -44,7 +44,7 @@ object MainMenu { // build a list of menu items menuItems.toVdomArray(item => <.li(^.key := item.idx, if(props.currentLoc == item.location) (^.className := "nav-item active") else (^.className := "nav-item"), - props.router.link(item.location)(item.icon, "", item.label(props))(^.`class` := "nav-link") + props.router.link(item.location)(item.icon, "", item.badge(props))(^.`class` := "nav-link") )) ) } diff --git a/client/src/main/scala/spatutorial/client/modules/TODO.scala b/client/src/main/scala/spatutorial/client/modules/TODO.scala index 6636d4a..746cae1 100644 --- a/client/src/main/scala/spatutorial/client/modules/TODO.scala +++ b/client/src/main/scala/spatutorial/client/modules/TODO.scala @@ -14,6 +14,8 @@ import spatutorial.shared._ import scalacss.ScalaCssReact._ object Todo { + // shorthand for styles + @inline private def bss = GlobalStyles.bootstrapStyles case class Props(proxy: ModelProxy[Pot[Todos]]) @@ -41,13 +43,13 @@ object Todo { } def render(p: Props, s: State) = - Panel(Panel.Props("What needs to be done"), <.div( + Card(Card.Props("What needs to be done"), <.div( p.proxy().renderFailed(ex => "Error loading"), p.proxy().renderPending(_ > 500, _ => "Loading..."), p.proxy().render(todos => TodoList(todos.items, item => p.proxy.dispatchCB(UpdateTodo(item)), item => editTodo(Some(item)), item => p.proxy.dispatchCB(DeleteTodo(item)))), - Button(Button.Props(editTodo(None)), Icon.plusSquare, " New")), - // if the dialog is open, add it to the panel + Button(Button.Props(editTodo(None), addStyles = Seq(bss.buttonSecondary, bss.spacingMT3)), Icon.plusSquare, " New")), + // if the dialog is open, add it to the card if (s.showTodoForm) TodoForm(TodoForm.Props(s.selectedItem, todoEdited)) else // otherwise add an empty placeholder VdomArray.empty()) @@ -103,9 +105,9 @@ object TodoForm { val headerText = if (s.item.id == "") "Add new todo" else "Edit todo" Modal(Modal.Props( // header contains a cancel button (X) - header = hide => <.span(<.button(^.tpe := "button", bss.close, ^.onClick --> hide, Icon.close), <.h4(headerText)), + header = hide => React.Fragment(<.h4(headerText), <.button(^.tpe := "button", bss.close, ^.onClick --> hide, Icon.close)), // footer has the OK button that submits the form before hiding it - footer = hide => <.span(Button(Button.Props(submitForm() >> hide), "OK")), + footer = hide => <.span(Button(Button.Props(submitForm() >> hide, addStyles = Seq(bss.buttonSecondary)), "OK")), // this is called after the modal has been hidden (animation is completed) closed = formClosed(s, p)), <.div(bss.formGroup, From 8584d1e9156c93eeae3f1204ce43c2436ec55f9d Mon Sep 17 00:00:00 2001 From: Jessie James Cosare Date: Mon, 24 Jun 2019 19:23:43 +0800 Subject: [PATCH 2/6] - updated english docs based on migrated code Signed-off-by: Jessie James Cosare --- doc/en/getting-started.md | 17 ++++- doc/en/integrating-javascript-components.md | 33 +++++----- doc/en/main-menu.md | 30 ++++----- doc/en/production-build.md | 14 ++-- doc/en/routing.md | 5 +- doc/en/sbt-build-definition.md | 71 +++++++++++---------- doc/en/the-client.md | 14 +++- doc/en/using-resources-from-webjars.md | 39 +++++------ project/Settings.scala | 17 +---- 9 files changed, 129 insertions(+), 111 deletions(-) diff --git a/doc/en/getting-started.md b/doc/en/getting-started.md index be095ed..c8b82c8 100644 --- a/doc/en/getting-started.md +++ b/doc/en/getting-started.md @@ -18,13 +18,24 @@ changing for example the chart data in `Dashboard.scala` and reloading the web p ## Requirements -SPA Tutorial uses Play 2.5 which depends on Java 8, so make sure you are using JVM 8 or later. +SPA Tutorial uses Play 2.7 which depends on Java 8, so make sure you are using JVM 8 or later. Running client tests requires [Node.js](https://nodejs.org/) and `jsdom` to be installed. After installing `node` and its package manager `npm` you can -install `jsdom` into your project folder with: +install `jsdom` into your root folder with: ``` -npm install jsdom +npm install ``` +`npm install` will refer to your package.json to install necessary dependencies: + +`jsdom` for client tests + +`sassify` for sass compilation Make sure to add `node_modules` directory to your `.gitignore` file! + +If you have installed node via nvm and you have issues with running tests about node being missing, run this command to resolve the issue: + +``` +sudo ln -s "$(which node)" /usr/bin/node +``` diff --git a/doc/en/integrating-javascript-components.md b/doc/en/integrating-javascript-components.md index 5589296..3430bf4 100644 --- a/doc/en/integrating-javascript-components.md +++ b/doc/en/integrating-javascript-components.md @@ -27,14 +27,15 @@ to define it through child component(s). object Button { case class Props(onClick: Callback, style: CommonStyle.Value = CommonStyle.default, addStyles: Seq[StyleA] = Seq()) - + val component = ScalaComponent.builder[Props]("Button") - .renderPC((_, props, children) => - <.button(bss.buttonOpt(props.style), props.addStyles.toTagMod, ^.tpe := "button", ^.onClick --> props.onClick, children) + .renderPC((_, p, c) => + <.button(bss.buttonOpt(p.style), p.addStyles.toTagMod, ^.tpe := "button", ^.onClick --> p.onClick, c) ).build - - def apply(props: Props, children: ReactNode*) = component(props)(children: _*) + + def apply(props: Props, children: VdomNode*) = component(props)(children: _*) def apply() = component + } ``` @@ -42,26 +43,28 @@ This time the render method gets two parameters: The properties and the children button using Bootstrap CSS and binds `onClick` to the handler we defined in the properties. Finally the children are rendered within the button tag. -Defining a Bootstrap Panel is about as simple. +Defining a Bootstrap Card is about as simple. ```scala -object Panel { - case class Props(heading: String, style: CommonStyle.Value = CommonStyle.default) +object Card { - val component = ScalaComponent.builder[Props]("Panel") + case class Props(heading: String, style: CommonStyle.Value = CommonStyle.default) + + val component = ScalaComponent.builder[Props]("Card") .renderPC((_, p, c) => - <.div(bss.panelOpt(p.style), - <.div(bss.panelHeading, p.heading), - <.div(bss.panelBody, c) + <.div(bss.cardOpt(p.style), + <.div(bss.cardHeading, p.heading), + <.div(bss.cardBody, c) ) ).build - - def apply(props: Props, children: ReactNode*) = component(props)(children: _*) + + def apply(props: Props, children: VdomNode*) = component(props)(children: _*) def apply() = component + } ``` -The panel provides no interactivity but this time we define a separate `heading` in addition to using the children property. +The card provides no interactivity but this time we define a separate `heading` in addition to using the children property. ## Icons diff --git a/doc/en/main-menu.md b/doc/en/main-menu.md index c36f357..a56bfee 100644 --- a/doc/en/main-menu.md +++ b/doc/en/main-menu.md @@ -5,21 +5,21 @@ statically within the class itself, because the referred locations are anyway al a dynamic system, but static is just fine here. ```scala -case class Props(ctl: RouterCtl[Loc], currentLoc: Loc, proxy: ModelProxy[Option[Int]]) +case class Props(router: RouterCtl[Loc], currentLoc: Loc, proxy: ModelProxy[Option[Int]]) -case class MenuItem(idx: Int, label: (Props) => VdomNode, icon: Icon, location: Loc) +private case class MenuItem(idx: Int, badge: (Props) => VdomNode, icon: Icon, location: Loc) // build the Todo menu item, showing the number of open todos private def buildTodoMenu(props: Props): VdomElement = { val todoCount = props.proxy().getOrElse(0) - Seq( - <.span("Todo "), - <.span(bss.labelOpt(CommonStyle.danger), bss.labelAsBadge, todoCount).when(todoCount > 0) + <.span( + <.span(" Todo "), + <.span(bss.badgeOpt(CommonStyle.danger), bss.badgePill, todoCount).when(todoCount > 0) ) } private val menuItems = Seq( - MenuItem(1, _ => "Dashboard", Icon.dashboard, DashboardLoc), + MenuItem(1, _ => " Dashboard", Icon.dashboard, DashboardLoc), MenuItem(2, buildTodoMenu, Icon.check, TodoLoc) ) ``` @@ -30,20 +30,18 @@ the label is simple text, but for Todo we also render the number of open todos, To render the menu we just loop over the items and create appropriate tags. For links we need to use the `RouterCtl` provided in the properties. ```scala -private class Backend(t: BackendScope[Props, _]) { - def mounted(props: Props) = { +private class Backend($: BackendScope[Props, Unit]) { + def mounted(props: Props) = // dispatch a message to refresh the todos - Callback.ifTrue(props.proxy.value.isEmpty, props.proxy.dispatchCB(RefreshTodos)) - } - + Callback.when(props.proxy.value.isEmpty)(props.proxy.dispatchCB(RefreshTodos)) + def render(props: Props) = { <.ul(bss.navbar)( // build a list of menu items - for (item <- menuItems) yield { - <.li(^.key := item.idx, (^.className := "active").when(props.currentLoc == item.location), - props.router.link(item.location)(item.icon, " ", item.label(props)) - ) - } + menuItems.toVdomArray(item => + <.li(^.key := item.idx, if(props.currentLoc == item.location) (^.className := "nav-item active") else (^.className := "nav-item"), + props.router.link(item.location)(item.icon, "", item.badge(props))(^.`class` := "nav-link") + )) ) } } diff --git a/doc/en/production-build.md b/doc/en/production-build.md index beb8408..032322d 100644 --- a/doc/en/production-build.md +++ b/doc/en/production-build.md @@ -16,12 +16,13 @@ We need to define a separate JS dependencies for the production build, using the ```scala /** Dependencies for external JS libs that are bundled into a single .js file according to dependency order */ val jsDependencies = Def.setting(Seq( - "org.webjars.bower" % "react" % versions.react / "react-with-addons.js" minified "react-with-addons.min.js" commonJSName "React", - "org.webjars.bower" % "react" % versions.react / "react-dom.js" minified "react-dom.min.js" dependsOn "react-with-addons.js" commonJSName "ReactDOM", - "org.webjars" % "jquery" % versions.jQuery / "jquery.js" minified "jquery.min.js", - "org.webjars" % "bootstrap" % versions.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "jquery.js", - "org.webjars" % "chartjs" % versions.chartjs / "Chart.js" minified "Chart.min.js", - "org.webjars" % "log4javascript" % versions.log4js / "js/log4javascript_uncompressed.js" minified "js/log4javascript.js" + "org.webjars.npm" % "react" % v.react / "umd/react.development.js" minified "umd/react.production.min.js" commonJSName "React", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom.development.js" minified "umd/react-dom.production.min.js" dependsOn "umd/react.development.js" commonJSName "ReactDOM", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom-server.browser.development.js" minified "umd/react-dom-server.browser.production.min.js" dependsOn "umd/react-dom.development.js" commonJSName "ReactDOMServer", + "org.webjars.npm" % "jquery" % v.jQuery / "dist/jquery.js" minified "jquery.min.js", + "org.webjars.npm" % "bootstrap" % v.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "dist/jquery.js", + "org.webjars.bower" % "chartjs" % v.chartjs / "Chart.js" minified "Chart.min.js", + "org.webjars" % "log4javascript" % v.log4js / "js/log4javascript_uncompressed.js" minified "js/log4javascript.js" )) ``` @@ -49,7 +50,6 @@ lazy val ReleaseCmd = Command.command("release") { "set elideOptions in client := Seq()" :: state } -} ``` and enable it in the `server` project diff --git a/doc/en/routing.md b/doc/en/routing.md index bcf8f3b..c005118 100644 --- a/doc/en/routing.md +++ b/doc/en/routing.md @@ -36,12 +36,13 @@ uses Bootstrap CSS to provide a nice looking layout, but you can use whatever CS ```scala import japgolly.scalajs.react.vdom.html_<^._ val todoCountWrapper = SPACircuit.connect(_.todos.map(_.items.count(!_.completed)).toOption) +// base layout for all pages def layout(c: RouterCtl[Loc], r: Resolution[Loc]) = { <.div( // here we use plain Bootstrap class names as these are specific to the top level layout defined here - <.nav(^.className := "navbar navbar-inverse navbar-fixed-top", + <.nav(^.className := "navbar navbar-dark bg-dark fixed-top navbar-expand-md", <.div(^.className := "container", - <.div(^.className := "navbar-header", <.span(^.className := "navbar-brand", "SPA Tutorial")), + <.span(^.className := "navbar-brand", "SPA Tutorial"), <.div(^.className := "collapse navbar-collapse", // connect menu to model, because it needs to update when the number of open todos changes todoCountWrapper(proxy => MainMenu(c, r.page, proxy)) diff --git a/doc/en/sbt-build-definition.md b/doc/en/sbt-build-definition.md index bf873ca..0736b63 100644 --- a/doc/en/sbt-build-definition.md +++ b/doc/en/sbt-build-definition.md @@ -19,21 +19,20 @@ and resources shared between the client and server. In the context of this tutor In a more realistic application you would have your data models etc. defined here. ```scala -lazy val shared = (crossProject.crossType(CrossType.Pure) in file("shared")) +lazy val shared = (crossProject(JSPlatform, JVMPlatform).crossType(CrossType.Pure) in file("shared")) .settings( - scalaVersion := Settings.versions.scala, - libraryDependencies ++= Settings.sharedDependencies.value + scalaVersion := Settings.v.scala, + libraryDependencies ++= Settings.sharedDependencies.value ) // set up settings specific to the JS project - .jsConfigure(_ enablePlugins ScalaJSPlay) - .jsSettings(sourceMapsBase := baseDirectory.value / "..") + .jsConfigure(_ enablePlugins ScalaJSWeb) ``` The shared dependencies include libraries used by both client and server such as `autowire` and `boopickle` for client/server communication. ```scala val sharedDependencies = Def.setting(Seq( - "com.lihaoyi" %%% "autowire" % versions.autowire, - "me.chrons" %%% "boopickle" % versions.booPickle + "com.lihaoyi" %%% "autowire" % v.autowire, + "io.suzaku" %%% "boopickle" % v.booPickle )) ``` @@ -45,8 +44,8 @@ Client is defined as a normal Scala.js project by enabling the `ScalaJSPlugin` o lazy val client: Project = (project in file("client")) .settings( name := "client", - version := Settings.version, - scalaVersion := Settings.versions.scala, + version := Settings.v.app, + scalaVersion := Settings.v.scala, scalacOptions ++= Settings.scalacOptions, libraryDependencies ++= Settings.scalajsDependencies.value, // by default we do development build, no eliding @@ -54,27 +53,25 @@ lazy val client: Project = (project in file("client")) scalacOptions ++= elideOptions.value, jsDependencies ++= Settings.jsDependencies.value, // RuntimeDOM is needed for tests - jsDependencies += RuntimeDOM % "test", + jsEnv in Test := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv, // yes, we want to package JS dependencies skip in packageJSDependencies := false, // use Scala.js provided launcher code to start the client app scalaJSUseMainModuleInitializer := true, scalaJSUseMainModuleInitializer in Test := false, - // must specify source maps location because we use pure CrossProject - sourceMapsDirectories += sharedJS.base / "..", // use uTest framework for tests - testFrameworks += new TestFramework("utest.runner.Framework") + testFrameworks += new TestFramework("utest.runner.Framework"), + dependencyOverrides ++= Settings.dependencyOverrides.value ) - .enablePlugins(ScalaJSPlugin, ScalaJSPlay) + .enablePlugins(ScalaJSPlugin, ScalaJSWeb) .dependsOn(sharedJS) ``` First few settings are normal Scala settings, but let's go through the remaining settings to explain what they do. ```scala - // by default we do development build, no eliding - elideOptions := Seq(), - scalacOptions ++= elideOptions.value, + // use eliding to drop some debug code in the production build + lazy val elideOptions = settingKey[Seq[String]]("Set limit for elidable functions") ``` Eliding is used to remove code that is not needed in the production build, such as debug logging. This setting is empty by default, but is enabled in the `release` command. @@ -82,7 +79,7 @@ the `release` command. ```scala jsDependencies ++= Settings.jsDependencies.value, // RuntimeDOM is needed for tests - jsDependencies += RuntimeDOM % "test", + jsEnv in Test := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv, // yes, we want to package JS dependencies skip in packageJSDependencies := false, ``` @@ -98,18 +95,17 @@ Make sure you have installed [PhantomJS](http://phantomjs.org/) before running t This setting informs Scala.js plugin to generate a special `launcher.js` file, which is loaded last and invokes your `main` method. Using a launcher keeps your HTML template clean, as you don't need to specify the `main` function there. -```scala - // must specify source maps location because we use pure CrossProject - sourceMapsDirectories += sharedJS.base / "..", -``` -Because we are using a pure `CrossProject`, the source map directories have to be manually adjusted to reflect where the source files can be found. - ```scala // use uTest framework for tests testFrameworks += new TestFramework("utest.runner.Framework") ``` Lets SBT know that we are using uTest framework for tests. +```scala + dependencyOverrides ++= Settings.dependencyOverrides.value +``` +Fixes unresolved deps issue: https://github.com/webjars/webjars/issues/1789 + ```scala .enablePlugins(ScalaJSPlugin, ScalaJSPlay) .dependsOn(sharedJS) @@ -126,16 +122,21 @@ plugin, which is automatically included to all projects using `PlayScala` plugin lazy val server = (project in file("server")) .settings( name := "server", - version := Settings.version, - scalaVersion := Settings.versions.scala, + version := Settings.v.app, + scalaVersion := Settings.v.scala, scalacOptions ++= Settings.scalacOptions, - libraryDependencies ++= Settings.jvmDependencies.value, + libraryDependencies ++= Settings.jvmDependencies.value, + libraryDependencies += guice, commands += ReleaseCmd, + // triggers scalaJSPipeline when using compile or continuous compilation + compile in Compile := ((compile in Compile) dependsOn scalaJSPipeline).value, // connect to the client project scalaJSProjects := clients, - pipelineStages := Seq(scalaJSProd), + pipelineStages in Assets := Seq(scalaJSPipeline), + pipelineStages := Seq(digest, gzip), // compress CSS - LessKeys.compress in Assets := true + SassKeys.cssStyle in Assets := Minified, + dependencyOverrides ++= Settings.dependencyOverrides.value ) .enablePlugins(PlayScala) .disablePlugins(PlayLayoutPlugin) // use the standard directory layout instead of Play's custom @@ -152,15 +153,21 @@ We define a new SBT command `release` to run a sequence of commands to produce a ```scala // connect to the client project scalaJSProjects := clients, - pipelineStages := Seq(scalaJSProd), + pipelineStages in Assets := Seq(scalaJSPipeline), + pipelineStages := Seq(digest, gzip), ``` Let the plugin know where our client project is and enable Scala.js processing in the pipeline. ```scala // compress CSS - LessKeys.compress in Assets := true, + SassKeys.cssStyle in Assets := Minified, +``` +This instructs the `sbt-sassify` plugin to minify the produced CSS. + +```scala + dependencyOverrides ++= Settings.dependencyOverrides.value ``` -This instructs the `sbt-less` plugin to minify the produced CSS. +Fixes unresolved deps issue: https://github.com/webjars/webjars/issues/1789 ```scala diff --git a/doc/en/the-client.md b/doc/en/the-client.md index b3a5c30..932910f 100644 --- a/doc/en/the-client.md +++ b/doc/en/the-client.md @@ -28,10 +28,18 @@ Once the browser has loaded all the resources, it will call the `SPAMain().main( entry point of the application. The class itself is very simple, ```scala -@JSExport("SPAMain") -object SPAMain extends JSApp { +object SPAMain { + + @JSExportTopLevel("SPAMain") + protected def getInstance(): this.type = this + @JSExport - def main(): Unit = { + def main(args: Array[String]): Unit = { + log.warn("Application starting") + // send log messages also to the server + log.enableServerLogging("/logging") + log.info("This message goes to server as well") + // create stylesheet GlobalStyles.addToDocument() // create the router diff --git a/doc/en/using-resources-from-webjars.md b/doc/en/using-resources-from-webjars.md index 91b342b..df267a3 100644 --- a/doc/en/using-resources-from-webjars.md +++ b/doc/en/using-resources-from-webjars.md @@ -13,12 +13,13 @@ configuration in the `build.scala` file: ```scala /** Dependencies for external JS libs that are bundled into a single .js file according to dependency order */ val jsDependencies = Def.setting(Seq( - "org.webjars.bower" % "react" % versions.react / "react-with-addons.js" minified "react-with-addons.min.js" commonJSName "React", - "org.webjars.bower" % "react" % versions.react / "react-dom.js" minified "react-dom.min.js" dependsOn "react-with-addons.js" commonJSName "ReactDOM", - "org.webjars" % "jquery" % versions.jQuery / "jquery.js" minified "jquery.min.js", - "org.webjars" % "bootstrap" % versions.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "jquery.js", - "org.webjars" % "chartjs" % versions.chartjs / "Chart.js" minified "Chart.min.js", - "org.webjars" % "log4javascript" % versions.log4js / "js/log4javascript_uncompressed.js" minified "js/log4javascript.js" + "org.webjars.npm" % "react" % v.react / "umd/react.development.js" minified "umd/react.production.min.js" commonJSName "React", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom.development.js" minified "umd/react-dom.production.min.js" dependsOn "umd/react.development.js" commonJSName "ReactDOM", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom-server.browser.development.js" minified "umd/react-dom-server.browser.production.min.js" dependsOn "umd/react-dom.development.js" commonJSName "ReactDOMServer", + "org.webjars.npm" % "jquery" % v.jQuery / "dist/jquery.js" minified "jquery.min.js", + "org.webjars.npm" % "bootstrap" % v.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "dist/jquery.js", + "org.webjars.bower" % "chartjs" % v.chartjs / "Chart.js" minified "Chart.min.js", + "org.webjars" % "log4javascript" % v.log4js / "js/log4javascript_uncompressed.js" minified "js/log4javascript.js" )) ``` @@ -27,36 +28,36 @@ JavaScript file is selected. ## WebJar CSS/LESS -For extracting CSS files from WebJars you could use the method described below, but there is bit more convenient method that gives you [LESS](http://lesscss.org/) -processing as a bonus. First we'll need to add the [sbt-less](https://github.com/sbt/sbt-less) plugin into our `plugins.sbt` +For extracting CSS files from WebJars you could use the method described below, but there is bit more convenient method that gives you [SASS](https://sass-lang.com/) +processing as a bonus. First we'll need to add the [sbt-sassify](https://github.com/irundaia/sbt-sassify) plugin into our `plugins.sbt` ```scala -addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.6") +addSbtPlugin("org.irundaia.sbt" % "sbt-sassify" % "1.4.13") ``` -The server project automatically enables the `sbt-web` and `sbt-less` plugins because it uses the `PlayScala` plugin. +The server project automatically enables the `sbt-web` and `sbt-sassify` plugins because it uses the `PlayScala` plugin. -We'll be storing LESS files under `src/main/assets/stylesheets` to keep them separated from directly copied resources. +We'll be storing SASS files under `src/main/assets/stylesheets` to keep them separated from directly copied resources. ```scala -LessKeys.compress in Assets := true, +SassKeys.cssStyle in Assets := Minified, ``` This tells the LESS compiler to minify the produced CSS. Next step is to create a `main.less` (yes, it has to be named exactly that) with references to CSS/LESS files inside the WebJars. ```css -@import "lib/bootstrap/less/bootstrap.less"; -@import "lib/font-awesome/less/font-awesome.less"; +@import "lib/bootstrap/scss/bootstrap.scss"; +@import "lib/fontawesome/scss/font-awesome.scss"; ``` -In this case we just import Bootstrap and Font Awesome LESS files because all other CSS styles are defined using ScalaCSS. Depending on the WebJar, -it may or may not contain LESS files in addition to the CSS file. With the LESS files you can easily -[configure the library](http://getbootstrap.com/css/#less) to your liking by defining CSS variables in your `main.less` file. +In this case we just import Bootstrap and Font Awesome SASS files because all other CSS styles are defined using ScalaCSS. Depending on the WebJar, +it may or may not contain SASS files in addition to the CSS file. With the SASS files you can easily +[configure the library](https://getbootstrap.com/docs/4.0/getting-started/theming/) to your liking by defining CSS variables in your `main.scss` file. ```css -@import "lib/bootstrap/less/bootstrap.less"; -@import "lib/font-awesome/less/font-awesome.less"; +@import "lib/bootstrap/scss/bootstrap.scss"; +@import "lib/fontawesome/scss/font-awesome.scss"; @brand-danger: #00534f; ``` diff --git a/project/Settings.scala b/project/Settings.scala index ffb29cc..6109e79 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -81,20 +81,9 @@ object Settings { /** Dependencies for external JS libs that are bundled into a single .js file according to dependency order */ val jsDependencies = Def.setting(Seq( - "org.webjars.npm" % "react" % v.react - / "umd/react.development.js" - minified "umd/react.production.min.js" - commonJSName "React", - "org.webjars.npm" % "react-dom" % v.react - / "umd/react-dom.development.js" - minified "umd/react-dom.production.min.js" - dependsOn "umd/react.development.js" - commonJSName "ReactDOM", - "org.webjars.npm" % "react-dom" % v.react - / "umd/react-dom-server.browser.development.js" - minified "umd/react-dom-server.browser.production.min.js" - dependsOn "umd/react-dom.development.js" - commonJSName "ReactDOMServer", + "org.webjars.npm" % "react" % v.react / "umd/react.development.js" minified "umd/react.production.min.js" commonJSName "React", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom.development.js" minified "umd/react-dom.production.min.js" dependsOn "umd/react.development.js" commonJSName "ReactDOM", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom-server.browser.development.js" minified "umd/react-dom-server.browser.production.min.js" dependsOn "umd/react-dom.development.js" commonJSName "ReactDOMServer", "org.webjars.npm" % "jquery" % v.jQuery / "dist/jquery.js" minified "jquery.min.js", "org.webjars.npm" % "bootstrap" % v.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "dist/jquery.js", "org.webjars.bower" % "chartjs" % v.chartjs / "Chart.js" minified "Chart.min.js", From 4fca8c7143093b22e2a419ac101e1fd20757b83a Mon Sep 17 00:00:00 2001 From: Jessie James Cosare Date: Mon, 24 Jun 2019 19:23:43 +0800 Subject: [PATCH 3/6] - updated scala version - replaced node version from latest to lts - added nvm node symlink to be used by client tests Signed-off-by: Jessie James Cosare --- .travis.yml | 8 +-- doc/en/getting-started.md | 17 ++++- doc/en/integrating-javascript-components.md | 33 +++++----- doc/en/main-menu.md | 30 ++++----- doc/en/production-build.md | 14 ++-- doc/en/routing.md | 5 +- doc/en/sbt-build-definition.md | 71 +++++++++++---------- doc/en/the-client.md | 14 +++- doc/en/using-resources-from-webjars.md | 39 +++++------ project/Settings.scala | 17 +---- 10 files changed, 133 insertions(+), 115 deletions(-) diff --git a/.travis.yml b/.travis.yml index adcc1a1..083fb6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: scala scala: -- 2.11.8 +- 2.12.8 sudo: false jdk: - oraclejdk8 @@ -16,7 +16,7 @@ cache: - $HOME/.sbt/boot/ install: - . $HOME/.nvm/nvm.sh - - nvm install stable - - nvm use stable + - nvm install --lts + - nvm use --lts - npm install - - npm install jsdom + - ln -s "$(which node)" $HOME/.local/bin/node diff --git a/doc/en/getting-started.md b/doc/en/getting-started.md index be095ed..c8b82c8 100644 --- a/doc/en/getting-started.md +++ b/doc/en/getting-started.md @@ -18,13 +18,24 @@ changing for example the chart data in `Dashboard.scala` and reloading the web p ## Requirements -SPA Tutorial uses Play 2.5 which depends on Java 8, so make sure you are using JVM 8 or later. +SPA Tutorial uses Play 2.7 which depends on Java 8, so make sure you are using JVM 8 or later. Running client tests requires [Node.js](https://nodejs.org/) and `jsdom` to be installed. After installing `node` and its package manager `npm` you can -install `jsdom` into your project folder with: +install `jsdom` into your root folder with: ``` -npm install jsdom +npm install ``` +`npm install` will refer to your package.json to install necessary dependencies: + +`jsdom` for client tests + +`sassify` for sass compilation Make sure to add `node_modules` directory to your `.gitignore` file! + +If you have installed node via nvm and you have issues with running tests about node being missing, run this command to resolve the issue: + +``` +sudo ln -s "$(which node)" /usr/bin/node +``` diff --git a/doc/en/integrating-javascript-components.md b/doc/en/integrating-javascript-components.md index 5589296..3430bf4 100644 --- a/doc/en/integrating-javascript-components.md +++ b/doc/en/integrating-javascript-components.md @@ -27,14 +27,15 @@ to define it through child component(s). object Button { case class Props(onClick: Callback, style: CommonStyle.Value = CommonStyle.default, addStyles: Seq[StyleA] = Seq()) - + val component = ScalaComponent.builder[Props]("Button") - .renderPC((_, props, children) => - <.button(bss.buttonOpt(props.style), props.addStyles.toTagMod, ^.tpe := "button", ^.onClick --> props.onClick, children) + .renderPC((_, p, c) => + <.button(bss.buttonOpt(p.style), p.addStyles.toTagMod, ^.tpe := "button", ^.onClick --> p.onClick, c) ).build - - def apply(props: Props, children: ReactNode*) = component(props)(children: _*) + + def apply(props: Props, children: VdomNode*) = component(props)(children: _*) def apply() = component + } ``` @@ -42,26 +43,28 @@ This time the render method gets two parameters: The properties and the children button using Bootstrap CSS and binds `onClick` to the handler we defined in the properties. Finally the children are rendered within the button tag. -Defining a Bootstrap Panel is about as simple. +Defining a Bootstrap Card is about as simple. ```scala -object Panel { - case class Props(heading: String, style: CommonStyle.Value = CommonStyle.default) +object Card { - val component = ScalaComponent.builder[Props]("Panel") + case class Props(heading: String, style: CommonStyle.Value = CommonStyle.default) + + val component = ScalaComponent.builder[Props]("Card") .renderPC((_, p, c) => - <.div(bss.panelOpt(p.style), - <.div(bss.panelHeading, p.heading), - <.div(bss.panelBody, c) + <.div(bss.cardOpt(p.style), + <.div(bss.cardHeading, p.heading), + <.div(bss.cardBody, c) ) ).build - - def apply(props: Props, children: ReactNode*) = component(props)(children: _*) + + def apply(props: Props, children: VdomNode*) = component(props)(children: _*) def apply() = component + } ``` -The panel provides no interactivity but this time we define a separate `heading` in addition to using the children property. +The card provides no interactivity but this time we define a separate `heading` in addition to using the children property. ## Icons diff --git a/doc/en/main-menu.md b/doc/en/main-menu.md index c36f357..a56bfee 100644 --- a/doc/en/main-menu.md +++ b/doc/en/main-menu.md @@ -5,21 +5,21 @@ statically within the class itself, because the referred locations are anyway al a dynamic system, but static is just fine here. ```scala -case class Props(ctl: RouterCtl[Loc], currentLoc: Loc, proxy: ModelProxy[Option[Int]]) +case class Props(router: RouterCtl[Loc], currentLoc: Loc, proxy: ModelProxy[Option[Int]]) -case class MenuItem(idx: Int, label: (Props) => VdomNode, icon: Icon, location: Loc) +private case class MenuItem(idx: Int, badge: (Props) => VdomNode, icon: Icon, location: Loc) // build the Todo menu item, showing the number of open todos private def buildTodoMenu(props: Props): VdomElement = { val todoCount = props.proxy().getOrElse(0) - Seq( - <.span("Todo "), - <.span(bss.labelOpt(CommonStyle.danger), bss.labelAsBadge, todoCount).when(todoCount > 0) + <.span( + <.span(" Todo "), + <.span(bss.badgeOpt(CommonStyle.danger), bss.badgePill, todoCount).when(todoCount > 0) ) } private val menuItems = Seq( - MenuItem(1, _ => "Dashboard", Icon.dashboard, DashboardLoc), + MenuItem(1, _ => " Dashboard", Icon.dashboard, DashboardLoc), MenuItem(2, buildTodoMenu, Icon.check, TodoLoc) ) ``` @@ -30,20 +30,18 @@ the label is simple text, but for Todo we also render the number of open todos, To render the menu we just loop over the items and create appropriate tags. For links we need to use the `RouterCtl` provided in the properties. ```scala -private class Backend(t: BackendScope[Props, _]) { - def mounted(props: Props) = { +private class Backend($: BackendScope[Props, Unit]) { + def mounted(props: Props) = // dispatch a message to refresh the todos - Callback.ifTrue(props.proxy.value.isEmpty, props.proxy.dispatchCB(RefreshTodos)) - } - + Callback.when(props.proxy.value.isEmpty)(props.proxy.dispatchCB(RefreshTodos)) + def render(props: Props) = { <.ul(bss.navbar)( // build a list of menu items - for (item <- menuItems) yield { - <.li(^.key := item.idx, (^.className := "active").when(props.currentLoc == item.location), - props.router.link(item.location)(item.icon, " ", item.label(props)) - ) - } + menuItems.toVdomArray(item => + <.li(^.key := item.idx, if(props.currentLoc == item.location) (^.className := "nav-item active") else (^.className := "nav-item"), + props.router.link(item.location)(item.icon, "", item.badge(props))(^.`class` := "nav-link") + )) ) } } diff --git a/doc/en/production-build.md b/doc/en/production-build.md index beb8408..032322d 100644 --- a/doc/en/production-build.md +++ b/doc/en/production-build.md @@ -16,12 +16,13 @@ We need to define a separate JS dependencies for the production build, using the ```scala /** Dependencies for external JS libs that are bundled into a single .js file according to dependency order */ val jsDependencies = Def.setting(Seq( - "org.webjars.bower" % "react" % versions.react / "react-with-addons.js" minified "react-with-addons.min.js" commonJSName "React", - "org.webjars.bower" % "react" % versions.react / "react-dom.js" minified "react-dom.min.js" dependsOn "react-with-addons.js" commonJSName "ReactDOM", - "org.webjars" % "jquery" % versions.jQuery / "jquery.js" minified "jquery.min.js", - "org.webjars" % "bootstrap" % versions.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "jquery.js", - "org.webjars" % "chartjs" % versions.chartjs / "Chart.js" minified "Chart.min.js", - "org.webjars" % "log4javascript" % versions.log4js / "js/log4javascript_uncompressed.js" minified "js/log4javascript.js" + "org.webjars.npm" % "react" % v.react / "umd/react.development.js" minified "umd/react.production.min.js" commonJSName "React", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom.development.js" minified "umd/react-dom.production.min.js" dependsOn "umd/react.development.js" commonJSName "ReactDOM", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom-server.browser.development.js" minified "umd/react-dom-server.browser.production.min.js" dependsOn "umd/react-dom.development.js" commonJSName "ReactDOMServer", + "org.webjars.npm" % "jquery" % v.jQuery / "dist/jquery.js" minified "jquery.min.js", + "org.webjars.npm" % "bootstrap" % v.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "dist/jquery.js", + "org.webjars.bower" % "chartjs" % v.chartjs / "Chart.js" minified "Chart.min.js", + "org.webjars" % "log4javascript" % v.log4js / "js/log4javascript_uncompressed.js" minified "js/log4javascript.js" )) ``` @@ -49,7 +50,6 @@ lazy val ReleaseCmd = Command.command("release") { "set elideOptions in client := Seq()" :: state } -} ``` and enable it in the `server` project diff --git a/doc/en/routing.md b/doc/en/routing.md index bcf8f3b..c005118 100644 --- a/doc/en/routing.md +++ b/doc/en/routing.md @@ -36,12 +36,13 @@ uses Bootstrap CSS to provide a nice looking layout, but you can use whatever CS ```scala import japgolly.scalajs.react.vdom.html_<^._ val todoCountWrapper = SPACircuit.connect(_.todos.map(_.items.count(!_.completed)).toOption) +// base layout for all pages def layout(c: RouterCtl[Loc], r: Resolution[Loc]) = { <.div( // here we use plain Bootstrap class names as these are specific to the top level layout defined here - <.nav(^.className := "navbar navbar-inverse navbar-fixed-top", + <.nav(^.className := "navbar navbar-dark bg-dark fixed-top navbar-expand-md", <.div(^.className := "container", - <.div(^.className := "navbar-header", <.span(^.className := "navbar-brand", "SPA Tutorial")), + <.span(^.className := "navbar-brand", "SPA Tutorial"), <.div(^.className := "collapse navbar-collapse", // connect menu to model, because it needs to update when the number of open todos changes todoCountWrapper(proxy => MainMenu(c, r.page, proxy)) diff --git a/doc/en/sbt-build-definition.md b/doc/en/sbt-build-definition.md index bf873ca..0736b63 100644 --- a/doc/en/sbt-build-definition.md +++ b/doc/en/sbt-build-definition.md @@ -19,21 +19,20 @@ and resources shared between the client and server. In the context of this tutor In a more realistic application you would have your data models etc. defined here. ```scala -lazy val shared = (crossProject.crossType(CrossType.Pure) in file("shared")) +lazy val shared = (crossProject(JSPlatform, JVMPlatform).crossType(CrossType.Pure) in file("shared")) .settings( - scalaVersion := Settings.versions.scala, - libraryDependencies ++= Settings.sharedDependencies.value + scalaVersion := Settings.v.scala, + libraryDependencies ++= Settings.sharedDependencies.value ) // set up settings specific to the JS project - .jsConfigure(_ enablePlugins ScalaJSPlay) - .jsSettings(sourceMapsBase := baseDirectory.value / "..") + .jsConfigure(_ enablePlugins ScalaJSWeb) ``` The shared dependencies include libraries used by both client and server such as `autowire` and `boopickle` for client/server communication. ```scala val sharedDependencies = Def.setting(Seq( - "com.lihaoyi" %%% "autowire" % versions.autowire, - "me.chrons" %%% "boopickle" % versions.booPickle + "com.lihaoyi" %%% "autowire" % v.autowire, + "io.suzaku" %%% "boopickle" % v.booPickle )) ``` @@ -45,8 +44,8 @@ Client is defined as a normal Scala.js project by enabling the `ScalaJSPlugin` o lazy val client: Project = (project in file("client")) .settings( name := "client", - version := Settings.version, - scalaVersion := Settings.versions.scala, + version := Settings.v.app, + scalaVersion := Settings.v.scala, scalacOptions ++= Settings.scalacOptions, libraryDependencies ++= Settings.scalajsDependencies.value, // by default we do development build, no eliding @@ -54,27 +53,25 @@ lazy val client: Project = (project in file("client")) scalacOptions ++= elideOptions.value, jsDependencies ++= Settings.jsDependencies.value, // RuntimeDOM is needed for tests - jsDependencies += RuntimeDOM % "test", + jsEnv in Test := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv, // yes, we want to package JS dependencies skip in packageJSDependencies := false, // use Scala.js provided launcher code to start the client app scalaJSUseMainModuleInitializer := true, scalaJSUseMainModuleInitializer in Test := false, - // must specify source maps location because we use pure CrossProject - sourceMapsDirectories += sharedJS.base / "..", // use uTest framework for tests - testFrameworks += new TestFramework("utest.runner.Framework") + testFrameworks += new TestFramework("utest.runner.Framework"), + dependencyOverrides ++= Settings.dependencyOverrides.value ) - .enablePlugins(ScalaJSPlugin, ScalaJSPlay) + .enablePlugins(ScalaJSPlugin, ScalaJSWeb) .dependsOn(sharedJS) ``` First few settings are normal Scala settings, but let's go through the remaining settings to explain what they do. ```scala - // by default we do development build, no eliding - elideOptions := Seq(), - scalacOptions ++= elideOptions.value, + // use eliding to drop some debug code in the production build + lazy val elideOptions = settingKey[Seq[String]]("Set limit for elidable functions") ``` Eliding is used to remove code that is not needed in the production build, such as debug logging. This setting is empty by default, but is enabled in the `release` command. @@ -82,7 +79,7 @@ the `release` command. ```scala jsDependencies ++= Settings.jsDependencies.value, // RuntimeDOM is needed for tests - jsDependencies += RuntimeDOM % "test", + jsEnv in Test := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv, // yes, we want to package JS dependencies skip in packageJSDependencies := false, ``` @@ -98,18 +95,17 @@ Make sure you have installed [PhantomJS](http://phantomjs.org/) before running t This setting informs Scala.js plugin to generate a special `launcher.js` file, which is loaded last and invokes your `main` method. Using a launcher keeps your HTML template clean, as you don't need to specify the `main` function there. -```scala - // must specify source maps location because we use pure CrossProject - sourceMapsDirectories += sharedJS.base / "..", -``` -Because we are using a pure `CrossProject`, the source map directories have to be manually adjusted to reflect where the source files can be found. - ```scala // use uTest framework for tests testFrameworks += new TestFramework("utest.runner.Framework") ``` Lets SBT know that we are using uTest framework for tests. +```scala + dependencyOverrides ++= Settings.dependencyOverrides.value +``` +Fixes unresolved deps issue: https://github.com/webjars/webjars/issues/1789 + ```scala .enablePlugins(ScalaJSPlugin, ScalaJSPlay) .dependsOn(sharedJS) @@ -126,16 +122,21 @@ plugin, which is automatically included to all projects using `PlayScala` plugin lazy val server = (project in file("server")) .settings( name := "server", - version := Settings.version, - scalaVersion := Settings.versions.scala, + version := Settings.v.app, + scalaVersion := Settings.v.scala, scalacOptions ++= Settings.scalacOptions, - libraryDependencies ++= Settings.jvmDependencies.value, + libraryDependencies ++= Settings.jvmDependencies.value, + libraryDependencies += guice, commands += ReleaseCmd, + // triggers scalaJSPipeline when using compile or continuous compilation + compile in Compile := ((compile in Compile) dependsOn scalaJSPipeline).value, // connect to the client project scalaJSProjects := clients, - pipelineStages := Seq(scalaJSProd), + pipelineStages in Assets := Seq(scalaJSPipeline), + pipelineStages := Seq(digest, gzip), // compress CSS - LessKeys.compress in Assets := true + SassKeys.cssStyle in Assets := Minified, + dependencyOverrides ++= Settings.dependencyOverrides.value ) .enablePlugins(PlayScala) .disablePlugins(PlayLayoutPlugin) // use the standard directory layout instead of Play's custom @@ -152,15 +153,21 @@ We define a new SBT command `release` to run a sequence of commands to produce a ```scala // connect to the client project scalaJSProjects := clients, - pipelineStages := Seq(scalaJSProd), + pipelineStages in Assets := Seq(scalaJSPipeline), + pipelineStages := Seq(digest, gzip), ``` Let the plugin know where our client project is and enable Scala.js processing in the pipeline. ```scala // compress CSS - LessKeys.compress in Assets := true, + SassKeys.cssStyle in Assets := Minified, +``` +This instructs the `sbt-sassify` plugin to minify the produced CSS. + +```scala + dependencyOverrides ++= Settings.dependencyOverrides.value ``` -This instructs the `sbt-less` plugin to minify the produced CSS. +Fixes unresolved deps issue: https://github.com/webjars/webjars/issues/1789 ```scala diff --git a/doc/en/the-client.md b/doc/en/the-client.md index b3a5c30..932910f 100644 --- a/doc/en/the-client.md +++ b/doc/en/the-client.md @@ -28,10 +28,18 @@ Once the browser has loaded all the resources, it will call the `SPAMain().main( entry point of the application. The class itself is very simple, ```scala -@JSExport("SPAMain") -object SPAMain extends JSApp { +object SPAMain { + + @JSExportTopLevel("SPAMain") + protected def getInstance(): this.type = this + @JSExport - def main(): Unit = { + def main(args: Array[String]): Unit = { + log.warn("Application starting") + // send log messages also to the server + log.enableServerLogging("/logging") + log.info("This message goes to server as well") + // create stylesheet GlobalStyles.addToDocument() // create the router diff --git a/doc/en/using-resources-from-webjars.md b/doc/en/using-resources-from-webjars.md index 91b342b..df267a3 100644 --- a/doc/en/using-resources-from-webjars.md +++ b/doc/en/using-resources-from-webjars.md @@ -13,12 +13,13 @@ configuration in the `build.scala` file: ```scala /** Dependencies for external JS libs that are bundled into a single .js file according to dependency order */ val jsDependencies = Def.setting(Seq( - "org.webjars.bower" % "react" % versions.react / "react-with-addons.js" minified "react-with-addons.min.js" commonJSName "React", - "org.webjars.bower" % "react" % versions.react / "react-dom.js" minified "react-dom.min.js" dependsOn "react-with-addons.js" commonJSName "ReactDOM", - "org.webjars" % "jquery" % versions.jQuery / "jquery.js" minified "jquery.min.js", - "org.webjars" % "bootstrap" % versions.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "jquery.js", - "org.webjars" % "chartjs" % versions.chartjs / "Chart.js" minified "Chart.min.js", - "org.webjars" % "log4javascript" % versions.log4js / "js/log4javascript_uncompressed.js" minified "js/log4javascript.js" + "org.webjars.npm" % "react" % v.react / "umd/react.development.js" minified "umd/react.production.min.js" commonJSName "React", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom.development.js" minified "umd/react-dom.production.min.js" dependsOn "umd/react.development.js" commonJSName "ReactDOM", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom-server.browser.development.js" minified "umd/react-dom-server.browser.production.min.js" dependsOn "umd/react-dom.development.js" commonJSName "ReactDOMServer", + "org.webjars.npm" % "jquery" % v.jQuery / "dist/jquery.js" minified "jquery.min.js", + "org.webjars.npm" % "bootstrap" % v.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "dist/jquery.js", + "org.webjars.bower" % "chartjs" % v.chartjs / "Chart.js" minified "Chart.min.js", + "org.webjars" % "log4javascript" % v.log4js / "js/log4javascript_uncompressed.js" minified "js/log4javascript.js" )) ``` @@ -27,36 +28,36 @@ JavaScript file is selected. ## WebJar CSS/LESS -For extracting CSS files from WebJars you could use the method described below, but there is bit more convenient method that gives you [LESS](http://lesscss.org/) -processing as a bonus. First we'll need to add the [sbt-less](https://github.com/sbt/sbt-less) plugin into our `plugins.sbt` +For extracting CSS files from WebJars you could use the method described below, but there is bit more convenient method that gives you [SASS](https://sass-lang.com/) +processing as a bonus. First we'll need to add the [sbt-sassify](https://github.com/irundaia/sbt-sassify) plugin into our `plugins.sbt` ```scala -addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.6") +addSbtPlugin("org.irundaia.sbt" % "sbt-sassify" % "1.4.13") ``` -The server project automatically enables the `sbt-web` and `sbt-less` plugins because it uses the `PlayScala` plugin. +The server project automatically enables the `sbt-web` and `sbt-sassify` plugins because it uses the `PlayScala` plugin. -We'll be storing LESS files under `src/main/assets/stylesheets` to keep them separated from directly copied resources. +We'll be storing SASS files under `src/main/assets/stylesheets` to keep them separated from directly copied resources. ```scala -LessKeys.compress in Assets := true, +SassKeys.cssStyle in Assets := Minified, ``` This tells the LESS compiler to minify the produced CSS. Next step is to create a `main.less` (yes, it has to be named exactly that) with references to CSS/LESS files inside the WebJars. ```css -@import "lib/bootstrap/less/bootstrap.less"; -@import "lib/font-awesome/less/font-awesome.less"; +@import "lib/bootstrap/scss/bootstrap.scss"; +@import "lib/fontawesome/scss/font-awesome.scss"; ``` -In this case we just import Bootstrap and Font Awesome LESS files because all other CSS styles are defined using ScalaCSS. Depending on the WebJar, -it may or may not contain LESS files in addition to the CSS file. With the LESS files you can easily -[configure the library](http://getbootstrap.com/css/#less) to your liking by defining CSS variables in your `main.less` file. +In this case we just import Bootstrap and Font Awesome SASS files because all other CSS styles are defined using ScalaCSS. Depending on the WebJar, +it may or may not contain SASS files in addition to the CSS file. With the SASS files you can easily +[configure the library](https://getbootstrap.com/docs/4.0/getting-started/theming/) to your liking by defining CSS variables in your `main.scss` file. ```css -@import "lib/bootstrap/less/bootstrap.less"; -@import "lib/font-awesome/less/font-awesome.less"; +@import "lib/bootstrap/scss/bootstrap.scss"; +@import "lib/fontawesome/scss/font-awesome.scss"; @brand-danger: #00534f; ``` diff --git a/project/Settings.scala b/project/Settings.scala index ffb29cc..6109e79 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -81,20 +81,9 @@ object Settings { /** Dependencies for external JS libs that are bundled into a single .js file according to dependency order */ val jsDependencies = Def.setting(Seq( - "org.webjars.npm" % "react" % v.react - / "umd/react.development.js" - minified "umd/react.production.min.js" - commonJSName "React", - "org.webjars.npm" % "react-dom" % v.react - / "umd/react-dom.development.js" - minified "umd/react-dom.production.min.js" - dependsOn "umd/react.development.js" - commonJSName "ReactDOM", - "org.webjars.npm" % "react-dom" % v.react - / "umd/react-dom-server.browser.development.js" - minified "umd/react-dom-server.browser.production.min.js" - dependsOn "umd/react-dom.development.js" - commonJSName "ReactDOMServer", + "org.webjars.npm" % "react" % v.react / "umd/react.development.js" minified "umd/react.production.min.js" commonJSName "React", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom.development.js" minified "umd/react-dom.production.min.js" dependsOn "umd/react.development.js" commonJSName "ReactDOM", + "org.webjars.npm" % "react-dom" % v.react / "umd/react-dom-server.browser.development.js" minified "umd/react-dom-server.browser.production.min.js" dependsOn "umd/react-dom.development.js" commonJSName "ReactDOMServer", "org.webjars.npm" % "jquery" % v.jQuery / "dist/jquery.js" minified "jquery.min.js", "org.webjars.npm" % "bootstrap" % v.bootstrap / "bootstrap.js" minified "bootstrap.min.js" dependsOn "dist/jquery.js", "org.webjars.bower" % "chartjs" % v.chartjs / "Chart.js" minified "Chart.min.js", From e481e506320bf0b0db5c630aa169a7256196436d Mon Sep 17 00:00:00 2001 From: Jessie James Cosare Date: Mon, 24 Jun 2019 20:34:52 +0800 Subject: [PATCH 4/6] - fixed issue on installing latest lts - updated docs on creating node symlink without sudo Signed-off-by: Jessie James Cosare --- .travis.yml | 2 +- doc/en/getting-started.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 083fb6f..e9494f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ cache: - $HOME/.sbt/boot/ install: - . $HOME/.nvm/nvm.sh - - nvm install --lts + - nvm install --latest-npm --lts=Dubnium - nvm use --lts - npm install - ln -s "$(which node)" $HOME/.local/bin/node diff --git a/doc/en/getting-started.md b/doc/en/getting-started.md index c8b82c8..ba2205a 100644 --- a/doc/en/getting-started.md +++ b/doc/en/getting-started.md @@ -37,5 +37,5 @@ Make sure to add `node_modules` directory to your `.gitignore` file! If you have installed node via nvm and you have issues with running tests about node being missing, run this command to resolve the issue: ``` -sudo ln -s "$(which node)" /usr/bin/node +ln -s "$(which node)" ~/.local/bin/node ``` From 423f0c718f858e69cd3afe31c36f9ebc182764c9 Mon Sep 17 00:00:00 2001 From: Jessie James Cosare Date: Mon, 24 Jun 2019 20:40:59 +0800 Subject: [PATCH 5/6] - removed travis creating node symlink Signed-off-by: Jessie James Cosare --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e9494f6..acca7f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,3 @@ install: - nvm install --latest-npm --lts=Dubnium - nvm use --lts - npm install - - ln -s "$(which node)" $HOME/.local/bin/node From a05da5a239bfe43f01e71750b880700c623617b9 Mon Sep 17 00:00:00 2001 From: Jessie James Cosare Date: Mon, 24 Jun 2019 23:26:12 +0800 Subject: [PATCH 6/6] - removed unused sbt plugins Signed-off-by: Jessie James Cosare --- project/plugins.sbt | 2 -- 1 file changed, 2 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 71bca1e..d9393d3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,8 +6,6 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.28") addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.0.9-0.6") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.0") -addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "0.6.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.7") addSbtPlugin("org.irundaia.sbt" % "sbt-sassify" % "1.4.13")