This play module will add support for webpack
and server-side rendering of javascript
with the built-in nashorn
script engine.
Example Project: https://github.com/BowlingX/play-webpack-example
- JDK 8
- scala 2.11 or scala 2.12
- play 2.6
- webpack or anything the generates a JSON file like the one below
Create a file in ~/project/play-webpack.sbt
addSbtPlugin("com.bowlingx" %% "play-webpack-plugin" % "0.1.19")
Add the following dependencies:
libraryDependencies += "com.bowlingx" %% "play-webpack" % "0.1.19"
The plugin will convert a webpack JSON manifest file (generated with https://github.com/kossnocorp/assets-webpack-plugin) to a scala object that can be used directly in play templates for example. The plugin is theoretically not limited to play. I will extend the project to support other frameworks in the future.
Since version 0.17.0 the plugin supports a plain json format (like https://github.com/webdeveric/webpack-assets-manifest
).
To make the assets available in your template file:
TwirlKeys.templateImports += "com.bowlingx.webpack.WebpackManifest"
Sample JSON File (generated by assets-webpack
):
{
"vendor": {
"js": "/assets/compiled/vendor.js",
"css": "/assets/compiled/vendor.css"
},
"server": {
"js": "/assets/compiled/server.js"
},
"main": {
"js": "/assets/compiled/main.js",
"css": "/assets/compiled/main.css"
},
"manifest": {
"js": "/assets/compiled/manifest.js"
}
}
Sample twirl Template:
@(title: String)(content: Html)
<!DOCTYPE html>
<html lang="en">
<head>
<title>@title</title>
@WebpackManifest.main.css.map { file =>
<link rel="stylesheet" media="screen" href="@file">
}
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
</head>
<body>
@content
@Seq(WebpackManifest.manifest.left.js, WebpackManifest.vendor.left.js, WebpackManifest.main.left.js).flatten.map { file =>
<script src="@file" type="text/javascript"></script>
}
</body>
</html>
The plugin defines the following configuration (your application.conf
):
webpack {
# the default bundles to include
prependBundles = ["manifest", "vendor"]
# The object that is generated by the plugin (needs to implement `WebpackManifestType`)
manifestClass = "com.bowlingx.webpack.WebpackManifest$"
# The public path of the assets
publicPath = "/assets/compiled"
# The path where they are stored relative to project root
serverPath = "/public/compiled"
rendering {
forceDisableWatch = false
timeout = 1minute
renderers {
prod = 5
dev = 1
test = 1
}
}
}
The default path of the manifest file (relative to project root) (in your build.sbt
)
webpackManifest := file("conf/webpack-assets.json").some.filter(_.exists).toSeq
You can supply multiple files that will then be merged.
In case you need to prefix the assets, you can do that with (defaults to None
):
webpackAssetPrefix := Some("/prefix/")
The main purpose of this plugin is to provide an easy way to render javascript inside a play action.
After enabling the module in you application.conf
, you can hook up your controller like this:
@Singleton
class YourPlayController @Inject()(engine: Engine, components: ControllerComponents)(implicit context:ExecutionContext) extends AbstractController(components) {
def index: Action[AnyContent] = Action.async {
engine.render("yourGlobalMethod", "any", "list", "of", "arguments") map {
case Success(Some(renderResult)) => Ok(renderResult.toString)
case _ => NotFound
}
}
}
See a full example in src/play-module/src/test
.
Important: Prevent modifying the global state of the JavaScript environment. Due to performance reasons, contexts are reused and any changes are persisted inside the engine if you do so. To prevent memory leaks keep your methods pure.
You can configure the number of rendering actors in webpack.rendering.renderers
:
rendering {
forceDisableWatch = false
timeout = 1minute
renderers {
prod = 5
dev = 1
test = 1
}
}
The more renderers you setup, the more requests you can handle. It defaults to 1 in tests and dev to speed up the init process.
The library makes it possible to use an async result.
Support for setTimeout
and clearTimeout
exists since version 0.1.5
.
All this just requires to return a Promise
in the render function. A simulated Promise
looks like this:
global.yourGlobalMethod = function () {
return {
then: function (resolve, reject) {
setTimeout(function () {
resolve("This is an async resolved String");
}, 100);
}
};
};
If you need Promise
support in your library, use any polyfill available. This library does not ship with a polyfill.
Supported Libraries (tested by the Author)
- reactjs
- react-apollo (https://github.com/apollographql/react-apollo)
Any file changes in dev mode (including the manifest file) are picked up automatically and a recompilation is triggered, so the normal "change and reload" cycle that leads to a faster development experience is kept.
You can force to disable watch mode in dev mode by setting webpack.rendering.forceDisableWatch
to true.