Skip to content

Commit d94708a

Browse files
committed
react.js support fix #745
1 parent d6b0c37 commit d94708a

File tree

14 files changed

+662
-39
lines changed

14 files changed

+662
-39
lines changed

jooby-assets-react/pom.xml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4+
5+
<parent>
6+
<groupId>org.jooby</groupId>
7+
<artifactId>jooby-project</artifactId>
8+
<version>1.1.1-SNAPSHOT</version>
9+
</parent>
10+
11+
<modelVersion>4.0.0</modelVersion>
12+
<artifactId>jooby-assets-react</artifactId>
13+
14+
<name>react.js module</name>
15+
16+
<build>
17+
<plugins>
18+
<!-- sure-fire -->
19+
<plugin>
20+
<groupId>org.apache.maven.plugins</groupId>
21+
<artifactId>maven-surefire-plugin</artifactId>
22+
<configuration>
23+
<includes>
24+
<include>**/*Test.java</include>
25+
<include>**/*Feature.java</include>
26+
<include>**/Issue*.java</include>
27+
</includes>
28+
</configuration>
29+
</plugin>
30+
</plugins>
31+
</build>
32+
33+
<dependencies>
34+
<!-- Jooby -->
35+
<dependency>
36+
<groupId>org.jooby</groupId>
37+
<artifactId>jooby-assets-rollup</artifactId>
38+
<version>${project.version}</version>
39+
</dependency>
40+
41+
<!-- Test dependencies -->
42+
<dependency>
43+
<groupId>org.jooby</groupId>
44+
<artifactId>jooby</artifactId>
45+
<version>${project.version}</version>
46+
<scope>test</scope>
47+
<classifier>tests</classifier>
48+
</dependency>
49+
50+
<dependency>
51+
<groupId>junit</groupId>
52+
<artifactId>junit</artifactId>
53+
<scope>test</scope>
54+
</dependency>
55+
56+
<dependency>
57+
<groupId>org.easymock</groupId>
58+
<artifactId>easymock</artifactId>
59+
<scope>test</scope>
60+
</dependency>
61+
62+
<dependency>
63+
<groupId>org.powermock</groupId>
64+
<artifactId>powermock-api-easymock</artifactId>
65+
<scope>test</scope>
66+
</dependency>
67+
68+
<dependency>
69+
<groupId>org.powermock</groupId>
70+
<artifactId>powermock-module-junit4</artifactId>
71+
<scope>test</scope>
72+
</dependency>
73+
74+
<dependency>
75+
<groupId>org.jacoco</groupId>
76+
<artifactId>org.jacoco.agent</artifactId>
77+
<classifier>runtime</classifier>
78+
<scope>test</scope>
79+
</dependency>
80+
81+
</dependencies>
82+
83+
</project>
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.jooby.assets;
20+
21+
import java.io.File;
22+
import java.io.FileNotFoundException;
23+
import java.io.IOException;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.nio.file.Paths;
27+
import java.util.ArrayList;
28+
import java.util.Collection;
29+
import java.util.HashMap;
30+
import java.util.HashSet;
31+
import java.util.Map;
32+
import java.util.Optional;
33+
import java.util.Set;
34+
import java.util.function.BiFunction;
35+
36+
import org.jooby.Route;
37+
38+
import com.google.common.collect.ImmutableList;
39+
import com.google.common.collect.ImmutableMap;
40+
import com.google.common.collect.ImmutableSet;
41+
42+
/**
43+
* <h1>react</h1>
44+
* <p>
45+
* Write <a href="https://facebook.github.io/react">React</a> applications easily in the JVM.
46+
* </p>
47+
*
48+
* <h2>usage</h2>
49+
* <p>
50+
* Download <a href="https://unpkg.com/react@15/dist/react.js">react.js</a> and
51+
* <a href="https://unpkg.com/react-dom@15/dist/react-dom.js">react-dom.js</a> into
52+
* <code>public/js/lib</code> folder.
53+
* </p>
54+
*
55+
* <p>
56+
* Then add the react processor to <code>conf/assets.conf</code>:
57+
* </p>
58+
* <pre>{@code
59+
* assets {
60+
* fileset {
61+
* index: index.js
62+
* }
63+
*
64+
* pipeline {
65+
* dev: [react]
66+
* dist: [react]
67+
* }
68+
* }
69+
* }</pre>
70+
*
71+
* <p>
72+
* Write some react code <code>public/js/index.js</code>:
73+
* </p>
74+
* <pre>{@code
75+
* import React from 'react';
76+
* import ReactDOM from 'react-dom';
77+
*
78+
* const Hello = () => (
79+
* <p>Hello React</p>
80+
* )
81+
*
82+
* ReactDOM.render(<Hello />, document.getElementById('root'));
83+
* }</pre>
84+
*
85+
* <p>
86+
* Choose one of the available
87+
* <a href="http://jooby.org/doc/parser-and-renderer/#template-engines">template engines</a> add the
88+
* <code>index.js</code> to the page:</code>
89+
*
90+
* <pre>{@code
91+
* <!doctype html>
92+
* <html lang="en">
93+
* <head>
94+
* <meta charset="utf-8">
95+
* <meta name="viewport" content="width=device-width, initial-scale=1">
96+
* <title>React App</title>
97+
* </head>
98+
* <body>
99+
* <div id="root"></div>
100+
* {{ index_scripts | raw}}
101+
* </body>
102+
* </html>
103+
* }</pre>
104+
*
105+
* <p>
106+
* The <code>{{ index_scripts | raw}}</code> here is <a href="jooby.org/doc/pebble">pebble
107+
* expression</a>. Open an browser and try it.
108+
* </p>
109+
*
110+
* <h2>how it works?</h2>
111+
* <p>
112+
* This module give you a ready to use react environment with: <code>ES6</code> and <code>JSX</code>
113+
* support via <a href="http://babeljs.io">babel.js</a> and
114+
* <a href="https://github.com/rollup/rollup">rollup.js</a>.
115+
* </p>
116+
* <p>
117+
* You don't need to install anything <code>node.js</code>, <code>npm</code>, ... nothing,
118+
* <a href="http://babeljs.io">babel.js</a> and
119+
* <a href="https://github.com/rollup/rollup">rollup.js</a> run on top of
120+
* <a href="https://github.com/eclipsesource/J2V8">j2v8</a> as part of the JVM process.
121+
* </p>
122+
*
123+
* <h2>options</h2>
124+
* <h3>react-router</h3>
125+
* <p>
126+
* Just drop the
127+
* <a href=
128+
* "https://unpkg.com/react-router-dom/umd/react-router-dom.js">react-router-dom.js</a>
129+
* into the <code>public/js/lib</code> folder and use it.
130+
* </p>
131+
*
132+
* <h3>rollup</h3>
133+
* <p>
134+
* It supports all the option of <a href="http://jooby.org/doc/assets-rollup/">rollup.js</a>
135+
* processor.
136+
* </p>
137+
*
138+
* @author edgar
139+
* @since 1.1.1
140+
*/
141+
public class React extends Rollup {
142+
143+
public React() {
144+
set("basedir", "public");
145+
set("generate", ImmutableMap.of("format", "iife"));
146+
}
147+
148+
@SuppressWarnings("unchecked")
149+
@Override
150+
public Map<String, Object> options() throws Exception {
151+
Map<String, Object> options = super.options();
152+
BiFunction<Map<String, Object>, String, Map<String, Object>> option = (src, key) -> {
153+
Map<String, Object> value = (Map<String, Object>) src.get(key);
154+
if (value == null) {
155+
value = new HashMap<>();
156+
src.put(key, value);
157+
}
158+
return value;
159+
};
160+
Map<String, Object> plugins = option.apply(options, "plugins");
161+
162+
Path basedir = Paths.get(get("basedir").toString());
163+
// react.js and react-dom.js
164+
Path react = getFile(basedir, "react.js");
165+
Path reactDom = getFile(basedir, "react-dom.js");
166+
Optional<Path> reactRouterDom = findFile(basedir, "react-router-dom.js");
167+
168+
/**
169+
* Legacy: export default for react and react-dom
170+
*/
171+
Map<String, Object> legacy = option.apply(plugins, "legacy");
172+
Set<String> babelExcludes = new HashSet<>();
173+
174+
legacy.putIfAbsent(Route.normalize(react.toString()), "React");
175+
legacy.putIfAbsent(Route.normalize(reactDom.toString()), "ReactDOM");
176+
177+
ImmutableSet.of(react.getParent(), reactDom.getParent()).stream()
178+
.map(it -> Route.normalize(it.toString()))
179+
.forEach(exclude -> {
180+
babelExcludes.add(exclude + File.separator + "*.js");
181+
babelExcludes.add(exclude + File.separator + "**" + File.separator + "*.js");
182+
});
183+
184+
reactRouterDom.ifPresent(path -> {
185+
186+
legacy.putIfAbsent(Route.normalize(path.toString()),
187+
ImmutableMap.of("ReactRouterDOM", ImmutableList.of(
188+
"BrowserRouter",
189+
"HashRouter",
190+
"Link",
191+
"MemoryRouter",
192+
"NavLink",
193+
"Prompt",
194+
"Redirect",
195+
"Route",
196+
"Router",
197+
"StaticRouter",
198+
"Switch",
199+
"matchPath",
200+
"withRouter")));
201+
});
202+
203+
/**
204+
* Alias:
205+
*/
206+
Map<String, Object> alias = option.apply(plugins, "alias");
207+
if (!alias.containsKey("react")) {
208+
alias.putIfAbsent("react", Route.normalize(react.toString()));
209+
alias.putIfAbsent("react-dom", Route.normalize(reactDom.toString()));
210+
}
211+
212+
reactRouterDom.ifPresent(path -> {
213+
alias.putIfAbsent("react-router-dom", Route.normalize(path.toString()));
214+
});
215+
216+
/**
217+
* Babel:
218+
*/
219+
Map<String, Object> babel = option.apply(plugins, "babel");
220+
if (!babel.containsKey("presets")) {
221+
babel.put("presets", ImmutableList
222+
.of(ImmutableList.of("es2015", ImmutableMap.of("modules", false)), "react"));
223+
}
224+
Optional.ofNullable(babel.get("excludes")).ifPresent(it -> {
225+
if (it instanceof Collection) {
226+
babelExcludes.addAll((Collection<? extends String>) it);
227+
} else {
228+
babelExcludes.add(it.toString());
229+
}
230+
});
231+
babel.put("excludes", new ArrayList<>(babelExcludes));
232+
233+
/**
234+
* context
235+
*/
236+
options.putIfAbsent("context", "window");
237+
238+
/**
239+
* Base dir
240+
*/
241+
options.remove("basedir");
242+
return options;
243+
}
244+
245+
private Path getFile(final Path basedir, final String filename) throws IOException {
246+
return findFile(basedir, filename)
247+
.orElseThrow(() -> new FileNotFoundException(filename + " at " + basedir.toAbsolutePath()));
248+
}
249+
250+
private Optional<Path> findFile(final Path basedir, final String filename) throws IOException {
251+
return Files.walk(basedir)
252+
.filter(it -> it.toString().endsWith(filename))
253+
.findFirst()
254+
.flatMap(it -> Optional.of(basedir.relativize(it)));
255+
}
256+
257+
}

0 commit comments

Comments
 (0)