Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Renderer: use the resource resolver API to locate the server bundle #833

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ This Webpack config does several things:

* It polyfills APIs that lack a native implementation in the GraalJS engine.
* It ensures the output is a native Javascript module.
* It names the result `ssr-components.mjs` which is the only name Micronaut React SSR accepts. All components must be in one server side bundle currently.
* It names the result `ssr-components.mjs`. You can use any name, but it's looked for under this name in the `views` resource directory by default. All components must be in one server side bundle.
* It makes the `SERVER` variable be statically true when the Javascript is being bundled for server-side rendering. This allows you to include/exclude code blocks at bundle optimization time.

You can use such a config by running `npx webpack --mode production --config webpack.server.js`. Add the `--watch` flag if you want the bundle to be recreated whenever an input file changes. Micronaut React SSR will notice if the bundle file has changed on disk and reload it (see <<react-dev-mode,Development>>).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ React SSR needs some Micronaut application properties to be set.
[configuration]
----
micronaut:
# Point to client and server JS
# Point to the server-side JS. This value is the default.
views:
folder: classes/views
react:
server-bundle-path: "classpath:views/ssr-components.mjs"

router:
static-resources:
js:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@
package io.micronaut.views.react;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.io.ResourceResolver;
import io.micronaut.views.ViewsConfiguration;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import org.graalvm.polyglot.Source;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.file.Path;
import java.util.Optional;

import static java.lang.String.format;

Expand All @@ -34,21 +39,37 @@
@Singleton
@Internal
class JSBundlePaths {
// Source code file name, for JS stack traces.
final String bundleFileName;

// URL of bundle file, could be a file:// or in a classpath jar.
final URL bundleURL;

// If a file:// (during development), the path of that file. Used for hot reloads.
@Nullable
final Path bundlePath;

@Inject
JSBundlePaths(ViewsConfiguration viewsConfiguration, ReactViewsRendererConfiguration reactConfiguration) throws IOException {
var folder = viewsConfiguration.getFolder();
bundlePath = Path.of(folder).resolve(reactConfiguration.getServerBundlePath()).toAbsolutePath().normalize();
if (!Files.exists(bundlePath)) {
throw new FileNotFoundException(format("Server bundle %s could not be found. Check your %s property.", bundlePath, ReactViewsRendererConfiguration.PREFIX + ".server-bundle-path"));
JSBundlePaths(
ViewsConfiguration viewsConfiguration,
ReactViewsRendererConfiguration reactConfiguration,
ResourceResolver resolver
) throws IOException {
Optional<URL> bundlePathOpt = resolver.getResource(reactConfiguration.getServerBundlePath());
if (bundlePathOpt.isEmpty()) {
throw new FileNotFoundException(format("Server bundle %s could not be found. Check your %s property.", reactConfiguration.getServerBundlePath(), ReactViewsRendererConfiguration.PREFIX + ".server-bundle-path"));
}
bundleURL = bundlePathOpt.get();
bundleFileName = bundleURL.getFile();
if (bundleURL.getProtocol().equals("file")) {
bundlePath = Path.of(bundleURL.getPath());
} else {
bundlePath = null;
}
bundleFileName = bundlePath.getFileName().toString();
}

Source readServerBundle() throws IOException {
try (var reader = Files.newBufferedReader(bundlePath)) {
try (var reader = new BufferedReader(new InputStreamReader(bundleURL.openStream()))) {
return Source.newBuilder("js", reader, bundleFileName)
.mimeType("application/javascript+module")
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ synchronized void release(JSContext jsContext) {

@Override
public synchronized void onApplicationEvent(FileChangedEvent event) {
if (event.getPath().equals(paths.bundlePath) && event.getEventType() != WatchEventType.DELETE) {
if (paths.bundlePath != null && event.getPath().equals(paths.bundlePath) && event.getEventType() != WatchEventType.DELETE) {
LOG.info("Reloading Javascript bundle due to file change.");
versionCounter++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public interface ReactViewsRendererConfiguration {
String DEFAULT_CLIENT_BUNDLE_URL = "/static/client.js";

/** The default value for {@link #getServerBundlePath()}. */
String DEFAULT_SERVER_BUNDLE_PATH = "ssr-components.mjs";
String DEFAULT_SERVER_BUNDLE_PATH = "classpath:views/ssr-components.mjs";

/** The default value for {@link #getRenderScript()}. */
String DEFAULT_RENDER_SCRIPT = "classpath:/io/micronaut/views/react/react.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import spock.lang.Specification
* provide.
*/
@MicronautTest(startApplication = false)
@Property(name = "micronaut.views.folder", value = "src/test/resources/views")
@Property(name = "micronaut.views.react.client-bundle-url", value = "/static/client.preact.js")
@Property(name = "micronaut.views.react.server-bundle-path", value = "ssr-components.preact.mjs")
@Property(name = "micronaut.views.react.server-bundle-path", value = "classpath:views/ssr-components.preact.mjs")
@Property(name = "micronaut.views.react.render-script", value = "classpath:/io/micronaut/views/react/preact.js")
class PreactViewRenderSpec extends Specification {
@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import jakarta.inject.Inject
import spock.lang.Specification

@MicronautTest(startApplication = false, rebuildContext = true)
@Property(name = "micronaut.views.folder", value = "src/test/resources/views")
@Property(name = "micronaut.views.react.server-bundle-path", value = "classpath:views/ssr-components.mjs")
class ReactViewRenderSpec extends Specification {
@Inject
ReactViewsRenderer<?> renderer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import spock.lang.FailsWith
import spock.lang.Specification

@MicronautTest(startApplication = false, rebuildContext = true)
@Property(name = "micronaut.views.folder", value = "src/test/resources/views")
@Property(name = "micronaut.views.react.server-bundle-path", value = "classpath:views/ssr-components.mjs")
@Property(name = "micronaut.views.react.sandbox", value = "true")
class SandboxReactRenderSpec extends Specification {
@Inject
Expand Down
Loading