├── .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
--------------------------------------------------------------------------------