├── .babelrc ├── .eslintrc ├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── app ├── assets │ ├── javascripts │ │ ├── Greeter.jsx │ │ └── app.jsx │ └── stylesheets │ │ └── style.scss ├── controllers │ └── Application.scala └── views │ ├── index.scala.html │ └── main.scala.html ├── build.sbt ├── conf ├── application.conf ├── logback.xml └── routes ├── package.json ├── project ├── Webpack.scala ├── build.properties └── plugins.sbt ├── public ├── images │ └── favicon.png └── stylesheets │ └── main.css ├── test ├── ApplicationSpec.scala └── IntegrationSpec.scala ├── webpack.config.js └── webpack.sbt /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins": [ 4 | "react-require" 5 | ] 6 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "node": true 5 | }, 6 | "rules": { 7 | "array-bracket-spacing": [2, "never"], 8 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 9 | "camelcase": [2, { "properties": "never" }], 10 | "comma-dangle": [2, "never"], 11 | "comma-spacing": [2, { "before": false, "after": true }], 12 | "consistent-return": 0, 13 | "curly": [2, "multi-line"], 14 | "dot-notation": [2, { "allowKeywords": true, "allowPattern": "^[a-z]+(_[a-z]+)+$" }], 15 | "eol-last": 0, 16 | "eqeqeq": [2, "allow-null"], 17 | "handle-callback-err": 2, 18 | "indent": [2, 4, { "SwitchCase": 1 }], 19 | "key-spacing": [1, { "beforeColon": false, "afterColon": true }], 20 | "linebreak-style": [2, "unix"], 21 | "max-len": [1, 140, 4], 22 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 23 | "new-parens": 0, 24 | "no-alert": 0, 25 | "no-array-constructor": 2, 26 | "no-caller": 2, 27 | "no-case-declarations": 2, 28 | "no-catch-shadow": 0, 29 | "no-cond-assign": [2, "except-parens"], 30 | "no-console": 0, 31 | "no-constant-condition": 2, 32 | "no-control-regex": 2, 33 | "no-debugger": 2, 34 | "no-delete-var": 2, 35 | "no-dupe-args": 2, 36 | "no-dupe-keys": 2, 37 | "no-duplicate-case": 2, 38 | "no-empty": 2, 39 | "no-empty-character-class": 2, 40 | "no-empty-label": 2, 41 | "no-empty-pattern": 2, 42 | "no-eval": 2, 43 | "no-ex-assign": 2, 44 | "no-extend-native": 2, 45 | "no-extra-bind": 2, 46 | "no-extra-boolean-cast": 2, 47 | "no-extra-parens": 0, 48 | "no-fallthrough": 2, 49 | "no-floating-decimal": 2, 50 | "no-func-assign": 2, 51 | "no-implied-eval": 2, 52 | "no-inner-declarations": 2, 53 | "no-invalid-regexp": 2, 54 | "no-iterator": 2, 55 | "no-label-var": 2, 56 | "no-labels": 0, 57 | "no-lone-blocks": 0, 58 | "no-loop-func": 0, 59 | "no-mixed-spaces-and-tabs": [2, false], 60 | "no-multi-spaces": 2, 61 | "no-multi-str": 0, 62 | "no-multiple-empty-lines": [2, { "max": 2 }], 63 | "no-native-reassign": 2, 64 | "no-negated-in-lhs": 2, 65 | "no-new": 0, 66 | "no-new-func": 2, 67 | "no-new-object": 2, 68 | "no-new-wrappers": 2, 69 | "no-obj-calls": 2, 70 | "no-octal": 2, 71 | "no-octal-escape": 2, 72 | "no-process-exit": 0, 73 | "no-proto": 2, 74 | "no-redeclare": 2, 75 | "no-regex-spaces": 2, 76 | "no-restricted-syntax": [2, "WithStatement"], 77 | "no-return-assign": [2, "always"], 78 | "no-script-url": 2, 79 | "no-sequences": 2, 80 | "no-shadow": 0, 81 | "no-shadow-restricted-names": 2, 82 | "no-spaced-func": 2, 83 | "no-sparse-arrays": 2, 84 | "no-trailing-spaces": [2, { "skipBlankLines": false }], 85 | "no-undef": 2, 86 | "no-undef-init": 2, 87 | "no-underscore-dangle": 0, 88 | "no-unused-expressions": 0, 89 | "no-unused-vars": 2, 90 | "no-use-before-define": [1, "nofunc"], 91 | "no-with": 2, 92 | "object-curly-spacing": [2, "always"], 93 | "one-var": [2, "never"], 94 | "quote-props": 0, 95 | "quotes": [2, "single", "avoid-escape"], 96 | "semi": 2, 97 | "semi-spacing": 0, 98 | "space-before-blocks": [2, "always"], 99 | "space-before-function-paren": [2, { "anonymous": "always", "named": "never" }], 100 | "space-infix-ops": 2, 101 | "space-return-throw-case": 2, 102 | "space-unary-ops": 0, 103 | "spaced-comment": [1, "always"], 104 | "strict": 0, 105 | "use-isnan": 2, 106 | "valid-typeof": 2, 107 | "yoda": [2, "never"] 108 | } 109 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | target 3 | /.idea 4 | /.idea_modules 5 | /.classpath 6 | /.project 7 | /.settings 8 | /RUNNING_PID 9 | app/assets/javascripts/build 10 | node_modules 11 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "-W099": true, // allowed mixed tabs and spaces 3 | "-W030": true, // allowed mixed tabs and spaces 4 | "asi": true // allowed mixed tabs and spaces 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the Apache 2 license, quoted below. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with 4 | the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 5 | 6 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 7 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 8 | language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Play framework, ReactJs and webpack 2 | =================================== 3 | 4 | This is a simple project to illustrate how to setup playframework to work with react js, es6 and webpack 5 | 6 | Just checkout the project and do ```npm install```to install all dependencies or rebuild it step by step by following instructions below. 7 | 8 | # Initialize npm project 9 | 10 | ``` 11 | npm init 12 | ``` 13 | 14 | # Install react 15 | 16 | ``` 17 | npm install react react-dom --save 18 | ``` 19 | 20 | 21 | # Install dependencies for dev 22 | 23 | ``` 24 | npm install webpack --save-dev 25 | npm install babel-core babel-loader babel-preset-react babel-preset-es2015 babel-plugin-transform-react-jsx --save-dev 26 | npm install babel-plugin-react-require --save-dev 27 | ``` 28 | 29 | # .babelrc content : 30 | 31 | ``` 32 | { 33 | "presets": ["es2015", "react"], 34 | "plugins": [ 35 | "react-require" 36 | ] 37 | } 38 | `` 39 | 40 | ## For linting 41 | 42 | ``` 43 | npm install eslint eslint-loader --save-dev 44 | ``` 45 | 46 | ## For handling CSS and Sass 47 | 48 | ``` 49 | npm install node-sass css-loader sass-loader style-loader --save-dev 50 | ``` 51 | 52 | "jsx-loader": "^0.13.2", 53 | "node-sass": "^3.4.2", 54 | "style-loader": "^0.13.0", 55 | "webpack": "^1.12.9", 56 | "webpack-dev-server": "^1.14 57 | -------------------------------------------------------------------------------- /app/assets/javascripts/Greeter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Greeter extends React.Component { 4 | render() { 5 | return (

Hello, {this.props.name}

) 6 | } 7 | } 8 | 9 | export default Greeter; -------------------------------------------------------------------------------- /app/assets/javascripts/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import Greeter from './Greeter.jsx'; 4 | 5 | import '../stylesheets/style.scss' 6 | 7 | render(( 8 |
9 |

Playframework, React JS, ES 6 and webpack

10 | 11 |
), document.getElementById("app")); -------------------------------------------------------------------------------- /app/assets/stylesheets/style.scss: -------------------------------------------------------------------------------- 1 | html { 2 | $primary-color: skyblue; 3 | 4 | body { 5 | background-color: $primary-color; 6 | } 7 | } -------------------------------------------------------------------------------- /app/controllers/Application.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api._ 4 | import play.api.mvc._ 5 | 6 | class Application extends Controller { 7 | def index = Action { 8 | Ok(views.html.index("Your new application is ready.")) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/views/index.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String) 2 | 3 | @main("Play, React JS and webpack") { 4 |
5 | 6 | 7 | } 8 | -------------------------------------------------------------------------------- /app/views/main.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(content: Html) 2 | 3 | 4 | 5 | 6 | 7 | @title 8 | 9 | 10 | 11 | 12 | @content 13 | 14 | 15 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import play.PlayImport.PlayKeys.playRunHooks 2 | 3 | name := """play-react-webpack""" 4 | 5 | version := "1.0-SNAPSHOT" 6 | 7 | lazy val root = (project in file(".")).enablePlugins(PlayScala) 8 | 9 | scalaVersion := "2.11.6" 10 | 11 | libraryDependencies ++= Seq( 12 | jdbc, 13 | cache, 14 | ws, 15 | specs2 % Test, 16 | "com.codeborne" % "phantomjsdriver" % "1.2.1" 17 | ) 18 | 19 | playRunHooks <+= baseDirectory.map(Webpack.apply) 20 | 21 | routesGenerator := InjectedRoutesGenerator 22 | 23 | excludeFilter in (Assets, JshintKeys.jshint) := "*.js" 24 | 25 | watchSources ~= { (ws: Seq[File]) => 26 | ws filterNot { path => 27 | path.getName.endsWith(".js") || path.getName == ("build") 28 | } 29 | } 30 | 31 | pipelineStages := Seq(digest, gzip) 32 | 33 | resolvers += "scalaz-bintray" at "http://dl.bintray.com/scalaz/releases" 34 | 35 | 36 | -------------------------------------------------------------------------------- /conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # 8 | # This must be changed for production, but we recommend not changing it in this file. 9 | # 10 | # See http://www.playframework.com/documentation/latest/ApplicationSecret for more details. 11 | play.crypto.secret = "changeme" 12 | 13 | # The application languages 14 | # ~~~~~ 15 | play.i18n.langs = [ "en" ] 16 | 17 | # Router 18 | # ~~~~~ 19 | # Define the Router object to use for this application. 20 | # This router will be looked up first when the application is starting up, 21 | # so make sure this is the entry point. 22 | # Furthermore, it's assumed your route file is named properly. 23 | # So for an application router like `my.application.Router`, 24 | # you may need to define a router file `conf/my.application.routes`. 25 | # Default to Routes in the root package (and conf/routes) 26 | # play.http.router = my.application.Routes 27 | 28 | # Database configuration 29 | # ~~~~~ 30 | # You can declare as many datasources as you want. 31 | # By convention, the default datasource is named `default` 32 | # 33 | # db.default.driver=org.h2.Driver 34 | # db.default.url="jdbc:h2:mem:play" 35 | # db.default.username=sa 36 | # db.default.password="" 37 | 38 | # Evolutions 39 | # ~~~~~ 40 | # You can disable evolutions if needed 41 | # play.evolutions.enabled=false 42 | 43 | # You can disable evolutions for a specific datasource if necessary 44 | # play.evolutions.db.default.enabled=false 45 | -------------------------------------------------------------------------------- /conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %coloredLevel - %logger - %message%n%xException 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | # Home page 6 | GET / controllers.Application.index 7 | 8 | # Map static resources from the /public folder to the /assets URL path 9 | GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "play-react-webpack", 3 | "version": "1.0.0", 4 | "description": "Playframework + React Js + webpack", 5 | "main": "index.js", 6 | "author": "Nouhoum Traoré", 7 | "license": "ISC", 8 | "directories": { 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/nouhoum/play-react-webpack.git" 17 | }, 18 | "keywords": [ 19 | "play", 20 | "reactjs", 21 | "webpack" 22 | ], 23 | "bugs": { 24 | "url": "https://github.com/nouhoum/play-react-webpack/issues" 25 | }, 26 | "homepage": "https://github.com/nouhoum/play-react-webpack#readme", 27 | "dependencies": { 28 | "react": "^0.14.5", 29 | "react-dom": "^0.14.5" 30 | }, 31 | "devDependencies": { 32 | "babel-core": "^6.4.0", 33 | "babel-loader": "^6.2.1", 34 | "babel-plugin-react-require": "^2.1.0", 35 | "babel-plugin-transform-react-jsx": "^6.4.0", 36 | "babel-preset-es2015": "^6.3.13", 37 | "babel-preset-react": "^6.3.13", 38 | "css-loader": "^0.23.1", 39 | "eslint": "^1.10.3", 40 | "eslint-loader": "^1.2.0", 41 | "jsx-loader": "^0.13.2", 42 | "node-sass": "^3.4.2", 43 | "sass-loader": "^3.1.2", 44 | "style-loader": "^0.13.0", 45 | "webpack": "^1.12.10" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /project/Webpack.scala: -------------------------------------------------------------------------------- 1 | import java.net.InetSocketAddress 2 | import play.sbt.PlayRunHook 3 | import sbt._ 4 | 5 | object Webpack { 6 | def apply(base: File): PlayRunHook = { 7 | object WebpackHook extends PlayRunHook { 8 | var process: Option[Process] = None 9 | 10 | override def beforeStarted() = { 11 | process = Option( 12 | Process("webpack", base).run() 13 | ) 14 | } 15 | 16 | override def afterStarted(addr: InetSocketAddress) = { 17 | process = Option( 18 | Process("webpack --watch", base).run() 19 | ) 20 | } 21 | 22 | override def afterStopped() = { 23 | process.foreach(_.destroy()) 24 | process = None 25 | } 26 | } 27 | 28 | WebpackHook 29 | } 30 | } -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | #Activator-generated Properties 2 | #Wed Jan 06 22:02:48 CET 2016 3 | template.uuid=37a0ffc3-739a-47a4-95a1-2100413d0180 4 | sbt.version=0.13.8 5 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.6") 2 | 3 | addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.3") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.7") 6 | 7 | addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.1.0") 8 | 9 | addSbtPlugin("com.typesafe.sbt" % "sbt-mocha" % "1.1.0") 10 | 11 | addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.0") -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nouhoum/play-react-webpack/cdf8504bc958764d53fe53142cf9fd0730823bc2/public/images/favicon.png -------------------------------------------------------------------------------- /public/stylesheets/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nouhoum/play-react-webpack/cdf8504bc958764d53fe53142cf9fd0730823bc2/public/stylesheets/main.css -------------------------------------------------------------------------------- /test/ApplicationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.specs2.mutable._ 2 | import org.specs2.runner._ 3 | import org.junit.runner._ 4 | 5 | import play.api.test._ 6 | import play.api.test.Helpers._ 7 | 8 | /** 9 | * Add your spec here. 10 | * You can mock out a whole application including requests, plugins etc. 11 | * For more information, consult the wiki. 12 | */ 13 | @RunWith(classOf[JUnitRunner]) 14 | class ApplicationSpec extends Specification { 15 | 16 | "Application" should { 17 | 18 | "send 404 on a bad request" in new WithApplication{ 19 | route(FakeRequest(GET, "/boum")) must beSome.which (status(_) == NOT_FOUND) 20 | } 21 | 22 | "render the index page" in new WithApplication{ 23 | val home = route(FakeRequest(GET, "/")).get 24 | 25 | status(home) must equalTo(OK) 26 | contentType(home) must beSome.which(_ == "text/html") 27 | contentAsString(home) must contain ("Play, React JS and webpack") 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/IntegrationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.openqa.selenium.phantomjs.PhantomJSDriver 2 | import org.openqa.selenium.remote.DesiredCapabilities 3 | import org.specs2.mutable._ 4 | import org.specs2.runner._ 5 | import org.junit.runner._ 6 | 7 | import play.api.test._ 8 | 9 | /** 10 | * add your integration spec here. 11 | * An integration test will fire up a whole play application in a real (or headless) browser 12 | */ 13 | @RunWith(classOf[JUnitRunner]) 14 | class IntegrationSpec extends Specification { 15 | 16 | "Application" should { 17 | 18 | "work from within a browser" in new WithBrowser { 19 | 20 | override val webDriver = { 21 | val capabilities = new DesiredCapabilities() 22 | capabilities.setJavascriptEnabled(true) 23 | new PhantomJSDriver(capabilities) 24 | } 25 | 26 | browser.goTo("http://localhost:" + port) 27 | 28 | browser.pageSource must contain("Ninja") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'), 4 | jsPath = 'app/assets/javascripts', 5 | path = require('path'), 6 | srcPath = path.join(__dirname, 'app/assets/javascripts'); 7 | 8 | var config = { 9 | target: 'web', 10 | entry: { 11 | app: path.join(srcPath, 'app.jsx') 12 | //, common: ['react-dom', 'react'] 13 | }, 14 | resolve: { 15 | alias: {}, 16 | root: srcPath, 17 | extensions: ['', '.js'], 18 | modulesDirectories: ['node_modules', jsPath] 19 | }, 20 | output: { 21 | path:path.resolve(__dirname, jsPath, 'build'), 22 | publicPath: '', 23 | filename: '[name].js', 24 | pathInfo: true 25 | }, 26 | 27 | module: { 28 | noParse: [], 29 | loaders: [ 30 | { 31 | test: /\.jsx?$/, 32 | exclude: /node_modules/, 33 | loader: 'babel' 34 | }, 35 | { 36 | test: /\.scss$/, 37 | include: /\/app\/assets/, 38 | loader: 'style!css!sass' 39 | } 40 | ] 41 | }, 42 | plugins: [ 43 | //new webpack.optimize.CommonsChunkPlugin('common', 'common.js'), 44 | new webpack.optimize.UglifyJsPlugin({ 45 | compress: { warnings: false }, 46 | output: { comments: false } 47 | }), 48 | new webpack.NoErrorsPlugin() 49 | ] 50 | }; 51 | 52 | module.exports = config; -------------------------------------------------------------------------------- /webpack.sbt: -------------------------------------------------------------------------------- 1 | lazy val webpack = TaskKey[Unit]("Run webpack when packaging the application") 2 | 3 | def runWebpack(file: File) = { 4 | Process("webpack", file) ! 5 | } 6 | 7 | webpack := { 8 | if(runWebpack(baseDirectory.value) != 0) throw new Exception("Something goes wrong when running webpack.") 9 | } 10 | 11 | dist <<= dist dependsOn webpack 12 | 13 | stage <<= stage dependsOn webpack 14 | 15 | test <<= (test in Test) dependsOn webpack --------------------------------------------------------------------------------