diff --git a/jooby-assets-react/pom.xml b/jooby-assets-react/pom.xml
new file mode 100644
index 0000000000..58aea1a60b
--- /dev/null
+++ b/jooby-assets-react/pom.xml
@@ -0,0 +1,83 @@
+
+
+
+
+ org.jooby
+ jooby-project
+ 1.1.1-SNAPSHOT
+
+
+ 4.0.0
+ jooby-assets-react
+
+ react.js module
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/*Test.java
+ **/*Feature.java
+ **/Issue*.java
+
+
+
+
+
+
+
+
+
+ org.jooby
+ jooby-assets-rollup
+ ${project.version}
+
+
+
+
+ org.jooby
+ jooby
+ ${project.version}
+ test
+ tests
+
+
+
+ junit
+ junit
+ test
+
+
+
+ org.easymock
+ easymock
+ test
+
+
+
+ org.powermock
+ powermock-api-easymock
+ test
+
+
+
+ org.powermock
+ powermock-module-junit4
+ test
+
+
+
+ org.jacoco
+ org.jacoco.agent
+ runtime
+ test
+
+
+
+
+
diff --git a/jooby-assets-react/src/main/java/org/jooby/assets/React.java b/jooby-assets-react/src/main/java/org/jooby/assets/React.java
new file mode 100644
index 0000000000..940bfd04ba
--- /dev/null
+++ b/jooby-assets-react/src/main/java/org/jooby/assets/React.java
@@ -0,0 +1,257 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jooby.assets;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+import org.jooby.Route;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ *
react
+ *
+ * Write React applications easily in the JVM.
+ *
+ *
+ * usage
+ *
+ * Download react.js and
+ * react-dom.js into
+ * public/js/lib
folder.
+ *
+ *
+ *
+ * Then add the react processor to conf/assets.conf
:
+ *
+ * {@code
+ * assets {
+ * fileset {
+ * index: index.js
+ * }
+ *
+ * pipeline {
+ * dev: [react]
+ * dist: [react]
+ * }
+ * }
+ * }
+ *
+ *
+ * Write some react code public/js/index.js
:
+ *
+ * {@code
+ * import React from 'react';
+ * import ReactDOM from 'react-dom';
+ *
+ * const Hello = () => (
+ * Hello React
+ * )
+ *
+ * ReactDOM.render( , document.getElementById('root'));
+ * }
+ *
+ *
+ * Choose one of the available
+ * template engines add the
+ * index.js
to the page:
+ *
+ *
{@code
+ *
+ *
+ *
+ *
+ *
+ * React App
+ *
+ *
+ *
+ * {{ index_scripts | raw}}
+ *
+ *
+ * }
+ *
+ *
+ * The {{ index_scripts | raw}}
here is pebble
+ * expression . Open an browser and try it.
+ *
+ *
+ * how it works?
+ *
+ * This module give you a ready to use react environment with: ES6
and JSX
+ * support via babel.js and
+ * rollup.js .
+ *
+ *
+ * You don't need to install anything node.js
, npm
, ... nothing,
+ * babel.js and
+ * rollup.js run on top of
+ * j2v8 as part of the JVM process.
+ *
+ *
+ * options
+ * react-router
+ *
+ * Just drop the
+ * react-router-dom.js
+ * into the public/js/lib
folder and use it.
+ *
+ *
+ * rollup
+ *
+ * It supports all the option of rollup.js
+ * processor.
+ *
+ *
+ * @author edgar
+ * @since 1.1.1
+ */
+public class React extends Rollup {
+
+ public React() {
+ set("basedir", "public");
+ set("generate", ImmutableMap.of("format", "iife"));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map options() throws Exception {
+ Map options = super.options();
+ BiFunction, String, Map> option = (src, key) -> {
+ Map value = (Map) src.get(key);
+ if (value == null) {
+ value = new HashMap<>();
+ src.put(key, value);
+ }
+ return value;
+ };
+ Map plugins = option.apply(options, "plugins");
+
+ Path basedir = Paths.get(get("basedir").toString());
+ // react.js and react-dom.js
+ Path react = getFile(basedir, "react.js");
+ Path reactDom = getFile(basedir, "react-dom.js");
+ Optional reactRouterDom = findFile(basedir, "react-router-dom.js");
+
+ /**
+ * Legacy: export default for react and react-dom
+ */
+ Map legacy = option.apply(plugins, "legacy");
+ Set babelExcludes = new HashSet<>();
+
+ legacy.putIfAbsent(Route.normalize(react.toString()), "React");
+ legacy.putIfAbsent(Route.normalize(reactDom.toString()), "ReactDOM");
+
+ ImmutableSet.of(react.getParent(), reactDom.getParent()).stream()
+ .map(it -> Route.normalize(it.toString()))
+ .forEach(exclude -> {
+ babelExcludes.add(exclude + File.separator + "*.js");
+ babelExcludes.add(exclude + File.separator + "**" + File.separator + "*.js");
+ });
+
+ reactRouterDom.ifPresent(path -> {
+
+ legacy.putIfAbsent(Route.normalize(path.toString()),
+ ImmutableMap.of("ReactRouterDOM", ImmutableList.of(
+ "BrowserRouter",
+ "HashRouter",
+ "Link",
+ "MemoryRouter",
+ "NavLink",
+ "Prompt",
+ "Redirect",
+ "Route",
+ "Router",
+ "StaticRouter",
+ "Switch",
+ "matchPath",
+ "withRouter")));
+ });
+
+ /**
+ * Alias:
+ */
+ Map alias = option.apply(plugins, "alias");
+ if (!alias.containsKey("react")) {
+ alias.putIfAbsent("react", Route.normalize(react.toString()));
+ alias.putIfAbsent("react-dom", Route.normalize(reactDom.toString()));
+ }
+
+ reactRouterDom.ifPresent(path -> {
+ alias.putIfAbsent("react-router-dom", Route.normalize(path.toString()));
+ });
+
+ /**
+ * Babel:
+ */
+ Map babel = option.apply(plugins, "babel");
+ if (!babel.containsKey("presets")) {
+ babel.put("presets", ImmutableList
+ .of(ImmutableList.of("es2015", ImmutableMap.of("modules", false)), "react"));
+ }
+ Optional.ofNullable(babel.get("excludes")).ifPresent(it -> {
+ if (it instanceof Collection) {
+ babelExcludes.addAll((Collection extends String>) it);
+ } else {
+ babelExcludes.add(it.toString());
+ }
+ });
+ babel.put("excludes", new ArrayList<>(babelExcludes));
+
+ /**
+ * context
+ */
+ options.putIfAbsent("context", "window");
+
+ /**
+ * Base dir
+ */
+ options.remove("basedir");
+ return options;
+ }
+
+ private Path getFile(final Path basedir, final String filename) throws IOException {
+ return findFile(basedir, filename)
+ .orElseThrow(() -> new FileNotFoundException(filename + " at " + basedir.toAbsolutePath()));
+ }
+
+ private Optional findFile(final Path basedir, final String filename) throws IOException {
+ return Files.walk(basedir)
+ .filter(it -> it.toString().endsWith(filename))
+ .findFirst()
+ .flatMap(it -> Optional.of(basedir.relativize(it)));
+ }
+
+}
diff --git a/jooby-assets-react/src/test/java/org/jooby/assets/ReactTest.java b/jooby-assets-react/src/test/java/org/jooby/assets/ReactTest.java
new file mode 100644
index 0000000000..c0be97e4c8
--- /dev/null
+++ b/jooby-assets-react/src/test/java/org/jooby/assets/ReactTest.java
@@ -0,0 +1,103 @@
+package org.jooby.assets;
+
+import static org.junit.Assert.assertEquals;
+
+import java.nio.file.Paths;
+
+import org.junit.Test;
+
+import com.typesafe.config.ConfigFactory;
+
+public class ReactTest {
+
+ @Test
+ public void name() throws Exception {
+ assertEquals("react", new React().name());
+ }
+
+ @Test
+ public void defaults() throws Exception {
+ assertEquals("(function () {\n" +
+ "'use strict';\n" +
+ "\n" +
+ "(function(exports) {\n" +
+ " exports.React = {};\n" +
+ "})(window);\n" +
+ "\n" +
+ "(function(exports) {\n" +
+ " exports.ReactDOM = {};\n" +
+ "})(window);\n" +
+ "\n" +
+ "var Home = function Home() {\n" +
+ " return React.createElement(\n" +
+ " 'div',\n" +
+ " null,\n" +
+ " React.createElement(\n" +
+ " 'h2',\n" +
+ " null,\n" +
+ " 'Home'\n" +
+ " )\n" +
+ " );\n" +
+ "};\n" +
+ "\n" +
+ "ReactDOM.render(React.createElement(Home, null), document.getElementById('root'));\n" +
+ "\n" +
+ "}());\n" +
+ "",
+ new React()
+ .set("basedir", Paths.get("src", "test", "resources").toString())
+ .process("/index.js",
+ "import React from 'react';\n" +
+ "import ReactDOM from 'react-dom';\n" +
+ "\n" +
+ "const Home = () => (\n" +
+ " \n" +
+ "
Home \n" +
+ " \n" +
+ ")\n" +
+ "\n" +
+ "ReactDOM.render( , document.getElementById('root'));",
+ ConfigFactory.empty()));
+ }
+
+ @Test
+ public void importFile() throws Exception {
+ assertEquals("(function () {\n" +
+ "'use strict';\n" +
+ "\n" +
+ "(function(exports) {\n" +
+ " exports.React = {};\n" +
+ "})(window);\n" +
+ "\n" +
+ "(function(exports) {\n" +
+ " exports.ReactDOM = {};\n" +
+ "})(window);\n" +
+ "\n" +
+ "var App = function App() {\n" +
+ " return React.createElement(\n" +
+ " 'div',\n" +
+ " null,\n" +
+ " React.createElement(\n" +
+ " 'h2',\n" +
+ " null,\n" +
+ " 'App'\n" +
+ " )\n" +
+ " );\n" +
+ "};\n" +
+ "\n" +
+ "ReactDOM.render(React.createElement(App, null), document.getElementById('root'));\n" +
+ "\n" +
+ "}());\n" +
+ "",
+ new React()
+ .set("basedir", Paths.get("src", "test", "resources").toString())
+ .process("/index.js",
+ "import React from 'react';\n" +
+ "import ReactDOM from 'react-dom';\n" +
+ "import App from './App';\n" +
+ "\n" +
+ "ReactDOM.render( , document.getElementById('root'));",
+ ConfigFactory.empty()));
+ }
+
+}
diff --git a/jooby-assets-react/src/test/resources/App.js b/jooby-assets-react/src/test/resources/App.js
new file mode 100644
index 0000000000..151f6be6b1
--- /dev/null
+++ b/jooby-assets-react/src/test/resources/App.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+const App = () => (
+
+
App
+
+);
+
+export default App;
diff --git a/jooby-assets-react/src/test/resources/lib/react-dom.js b/jooby-assets-react/src/test/resources/lib/react-dom.js
new file mode 100644
index 0000000000..84407fdd40
--- /dev/null
+++ b/jooby-assets-react/src/test/resources/lib/react-dom.js
@@ -0,0 +1,3 @@
+(function(exports) {
+ exports.ReactDOM = {};
+})(this);
diff --git a/jooby-assets-react/src/test/resources/lib/react.js b/jooby-assets-react/src/test/resources/lib/react.js
new file mode 100644
index 0000000000..6f80f2fa8f
--- /dev/null
+++ b/jooby-assets-react/src/test/resources/lib/react.js
@@ -0,0 +1,3 @@
+(function(exports) {
+ exports.React = {};
+})(this);
diff --git a/jooby-assets-rollup/src/main/java/org/jooby/assets/Rollup.java b/jooby-assets-rollup/src/main/java/org/jooby/assets/Rollup.java
index 672c5abc92..760be5cef2 100644
--- a/jooby-assets-rollup/src/main/java/org/jooby/assets/Rollup.java
+++ b/jooby-assets-rollup/src/main/java/org/jooby/assets/Rollup.java
@@ -22,9 +22,16 @@
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
-import java.util.Optional;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
import org.jooby.MediaType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.eclipsesource.v8.V8;
import com.eclipsesource.v8.V8Array;
@@ -140,6 +147,9 @@ public class Rollup extends AssetProcessor {
static final PathMatcher TRUE = p -> true;
static final PathMatcher FALSE = p -> false;
+ /** The logging system. */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
@Override
public boolean matches(final MediaType type) {
return MediaType.js.matches(type);
@@ -153,35 +163,53 @@ public String process(final String filename, final String source, final Config c
V8Object j2v8 = ctx.hash();
j2v8.add("createFilter", ctx.function((receiver, args) -> {
- PathMatcher includes = at(args, 0)
+ List includes = filter(args, 0).stream()
.map(it -> FileSystems.getDefault().getPathMatcher("glob:" + it))
- .orElse(TRUE);
- PathMatcher excludes = at(args, 1)
+ .collect(Collectors.toList());
+ if (includes.isEmpty()) {
+ includes.add(TRUE);
+ }
+
+ List excludes = filter(args, 1).stream()
.map(it -> FileSystems.getDefault().getPathMatcher("glob:" + it))
- .orElse(FALSE);
+ .collect(Collectors.toList());
+ if (excludes.isEmpty()) {
+ excludes.add(FALSE);
+ }
return ctx.function((self, arguments) -> {
Path path = Paths.get(arguments.get(0).toString());
- if (includes.matches(path)) {
- return !excludes.matches(path);
+ if (includes.stream().filter(it -> it.matches(path)).findFirst().isPresent()) {
+ return !excludes.stream().filter(it -> it.matches(path)).findFirst().isPresent();
}
return false;
});
}));
v8.add("j2v8", j2v8);
- return ctx.invoke("rollup.js", source, options(), filename);
+
+ Map options = options();
+ log.debug("{}", options);
+
+ return ctx.invoke("rollup.js", source, options, filename);
});
}
- private Optional at(final V8Array args, final int i) {
+ @SuppressWarnings("unchecked")
+ private List filter(final V8Array args, final int i) {
if (i < args.length()) {
Object value = V8ObjectUtils.getValue(args, i);
if (value == V8.getUndefined()) {
- return Optional.empty();
+ return Collections.emptyList();
+ }
+ List filter = new ArrayList<>();
+ if (value instanceof Collection) {
+ filter.addAll((Collection extends String>) value);
+ } else {
+ filter.add(value.toString());
}
- return Optional.ofNullable(value);
+ return filter;
}
- return Optional.empty();
+ return Collections.emptyList();
}
}
diff --git a/jooby-assets-rollup/src/main/resources/rollup.js b/jooby-assets-rollup/src/main/resources/rollup.js
index 1f4078378a..b34db99fac 100644
--- a/jooby-assets-rollup/src/main/resources/rollup.js
+++ b/jooby-assets-rollup/src/main/resources/rollup.js
@@ -126,10 +126,22 @@
name: 'legacy',
transform: function (code, id) {
- var name = legacyOptions[id];
- if (name) {
- console.debug('legacy: ', id, ' -> ', name);
- return code + '\nexport default ' + name + ';';
+ var value = legacyOptions[id];
+ if (value) {
+ console.debug('legacy: ', id, ' -> ', value);
+ if ( typeof value === 'string' ) {
+ return code + '\nexport default ' + value + ';';
+ } else {
+ var statements = [];
+ for(k in value) {
+ var array = value[k];
+ for (var i = 0; i < array.length; i++) {
+ statements.push('\nvar ' + array[i] + ' = ' + k + '.' + array[i] + ';\n' +
+ '\nexport {' + array[i] + '};');
+ }
+ }
+ return code + statements.join('\n');
+ }
}
}
};
@@ -163,29 +175,29 @@
var output,
errors = [];
- rollup.rollup({
- entry: filename,
- plugins: plugins,
- }).catch(function (ex) {
- errors.push({
- message: ex.toString()
- });
- }).then(function (bundle) {
- if (bundle) {
- var result = bundle.generate(genopts);
+ options.entry = filename;
+ options.plugins = plugins;
+ rollup.rollup(options)
+ .catch(function (ex) {
+ errors.push({
+ message: ex.toString()
+ });
+ }).then(function (bundle) {
+ if (bundle) {
+ var result = bundle.generate(genopts);
- output = result.code;
+ output = result.code;
- /** inline sourceMap only. */
- if (genopts.sourceMap === 'inline') {
- output += '\n//#sourceMappingURL=' + result.map.toUrl();
+ /** inline sourceMap only. */
+ if (genopts.sourceMap === 'inline') {
+ output += '\n//#sourceMappingURL=' + result.map.toUrl();
+ }
}
- }
- }).catch(function (ex) {
- errors.push({
- message: ex.toString()
+ }).catch(function (ex) {
+ errors.push({
+ message: ex.toString()
+ });
});
- });
return {
output: output,
diff --git a/jooby-assets-rollup/src/test/java/org/jooby/assets/RollupTest.java b/jooby-assets-rollup/src/test/java/org/jooby/assets/RollupTest.java
index 15d4ce3791..11798d562e 100644
--- a/jooby-assets-rollup/src/test/java/org/jooby/assets/RollupTest.java
+++ b/jooby-assets-rollup/src/test/java/org/jooby/assets/RollupTest.java
@@ -6,6 +6,7 @@
import org.junit.Test;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.typesafe.config.ConfigFactory;
@@ -63,9 +64,9 @@ public void babelExcludes() throws Exception {
"hi(\"babel\");\n" +
"",
new Rollup()
- .set("babel", ImmutableMap.of("presets",
+ .set("plugins", ImmutableMap.of("babel", ImmutableMap.of("presets",
Arrays.asList(Arrays.asList("es2015", ImmutableMap.of("modules", false))),
- "excludes", "/lib/*.js"))
+ "excludes", "/lib/*.js")))
.process("/app.js", "import hi from './lib/lib.js';\n"
+ "hi(\"babel\");",
ConfigFactory.empty()));
@@ -114,6 +115,31 @@ public void legacy() throws Exception {
ConfigFactory.empty()));
}
+ @Test
+ public void namedLegacy() throws Exception {
+ assertEquals("(function(exports) {\n" +
+ " exports.Named = {\n" +
+ " foo: 'foo',\n" +
+ " bar: 'bar'\n" +
+ " };\n" +
+ "})(window);\n" +
+ "\n" +
+ "var foo = Named.foo;\n" +
+ "\n" +
+ "var bar = Named.bar;\n" +
+ "\n" +
+ "console.log(foo + bar);\n" +
+ "",
+ new Rollup()
+ .set("context", "window")
+ .set("plugins", ImmutableMap.of("legacy", ImmutableMap.of("/lib/legacy-named.js",
+ ImmutableMap.of("Named", ImmutableList.of("foo", "bar")))))
+ .process("/main.js",
+ "import {foo, bar} from 'lib/legacy-named';\n" +
+ "console.log(foo + bar);",
+ ConfigFactory.empty()));
+ }
+
@Test
public void alias() throws Exception {
assertEquals("var message = (message) => {\n" +
diff --git a/jooby-assets-rollup/src/test/resources/lib/legacy-named.js b/jooby-assets-rollup/src/test/resources/lib/legacy-named.js
new file mode 100644
index 0000000000..78f01bd3f9
--- /dev/null
+++ b/jooby-assets-rollup/src/test/resources/lib/legacy-named.js
@@ -0,0 +1,6 @@
+(function(exports) {
+ exports.Named = {
+ foo: 'foo',
+ bar: 'bar'
+ };
+})(this);
diff --git a/jooby-assets/src/main/java/org/jooby/assets/AssetOptions.java b/jooby-assets/src/main/java/org/jooby/assets/AssetOptions.java
index d13b66b35b..d3322d8b64 100644
--- a/jooby-assets/src/main/java/org/jooby/assets/AssetOptions.java
+++ b/jooby-assets/src/main/java/org/jooby/assets/AssetOptions.java
@@ -52,7 +52,7 @@ public AssetOptions set(final Config options) {
return this;
}
- public Map options() {
+ public Map options() throws Exception {
return options.withoutPath("excludes").root().unwrapped();
}
diff --git a/jooby-assets/src/test/java/org/jooby/assets/AssetProcessorTest.java b/jooby-assets/src/test/java/org/jooby/assets/AssetProcessorTest.java
index 42738e5f61..33ab5a7ed6 100644
--- a/jooby-assets/src/test/java/org/jooby/assets/AssetProcessorTest.java
+++ b/jooby-assets/src/test/java/org/jooby/assets/AssetProcessorTest.java
@@ -17,7 +17,7 @@ public void name() {
@SuppressWarnings("serial")
@Test
- public void options() {
+ public void options() throws Exception {
assertEquals(ImmutableMap.of("str", "str", "bool", true, "map", new HashMap() {
{
put("k", null);
diff --git a/md/doc/assets-react/react.md b/md/doc/assets-react/react.md
new file mode 100644
index 0000000000..62d6d588bf
--- /dev/null
+++ b/md/doc/assets-react/react.md
@@ -0,0 +1,85 @@
+# react
+
+Write React applications easily in the JVM.
+
+{{assets-require.md}}
+
+## dependency
+
+```xml
+
+ org.jooby
+ jooby-react
+ {{version}}
+ provided
+
+```
+
+## usage
+
+Download react.js and react-dom.js into ```public/js/lib``` folder.
+
+Then add the react processor to ```conf/assets.conf```:
+
+```
+assets {
+ fileset {
+
+ index: index.js
+ }
+
+ pipeline {
+
+ dev: [react]
+ dist: [react]
+ }
+
+}
+```
+
+Write some react code ```public/js/index.js```:
+
+```java
+ import React from 'react';
+ import ReactDOM from 'react-dom';
+
+ const Hello = () => (
+ Hello React
+ )
+
+ ReactDOM.render( , document.getElementById('root'));
+```
+
+Choose one of the available template engines add the ```index.js``` to the page:
+
+```java
+
+
+
+
+ {{ index_scripts | raw}}
+
+
+```
+
+The ```{{ index_scripts | raw}}``` here is pebble expression . Open an browser and try it.
+
+## how it works?
+
+This module give you a ready to use react environment with: ```ES6``` and ```JSX``` support via babel.js and rollup.js .
+
+You don't need to install ```node.js```, ```npm```, ... nothing, babel.js and rollup.js run on top of j2v8 as part of the JVM process.
+
+## options
+
+### react-router
+
+Just drop the react-router-dom.js into the ```public/js/lib``` folder and use it.
+
+### rollup
+
+It supports all the option of rollup.js processor.
+
+# see also
+
+{{available-asset-procesors.md}}
diff --git a/pom.xml b/pom.xml
index 8b8fbd6050..b1db2e45ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -101,6 +101,7 @@
jooby-assets-svg-sprites
jooby-assets-svg-symbol
jooby-assets-autoprefixer
+ jooby-assets-react
coverage-report
@@ -473,6 +474,12 @@
${jooby.version}
+
+ org.jooby
+ jooby-assets-react
+ ${jooby.version}
+
+
org.jooby
jooby-assets-svg-sprites