-
Notifications
You must be signed in to change notification settings - Fork 132
Integrate Start UI inside JHipster
Guide to start a JHipster project with Start UI in the same repository without having to serve 2 applications in production. This will show you how to setup your repository to make JHipster Java server serve the frontend as static files in production.
This is still a work in progress, the content of this page be incorrect or outdated. If you catch a mistake in this document, you may open an issue in this repository.
The result of this tutorial can be seen in this repository. It is a result of this guide followed on the 10 of August 2022 (with JHipster 7.9.2 and Start UI from commit 5a28e62).
mkdir my-project
cd my-project
npx generator-jhipster --skip-client
Choose the following when needed
- Monolithic application
- JWT
In order to serve the webapp in production you will need to update and add the following files in java folder (src/main/java/com/mycompany/myapp
)
In the filterChain
function, add the following lines below the .antMatchers(HttpMethod.OPTIONS, "/**")
...
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
...
Add imports
import static java.net.URLDecoder.decode;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
Add the WebServerFactoryCustomizer
interface to the WebConfigurer
as below
public class WebConfigurer implements ServletContextInitializer, **WebServerFactoryCustomizer<WebServerFactory>** {
Add the customize
, setLocationForStaticAssets
and resolvePathPrefix
methods
/**
* Customize the Servlet engine: Mime types, the document root, the cache.
*/
@Override
public void customize(WebServerFactory server) {
// When running in an IDE or with ./mvnw spring-boot:run, set location of the static web assets.
setLocationForStaticAssets(server);
}
private void setLocationForStaticAssets(WebServerFactory server) {
if (server instanceof ConfigurableServletWebServerFactory) {
ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server;
File root;
String prefixPath = resolvePathPrefix();
root = new File(prefixPath + "target/classes/static/");
if (root.exists() && root.isDirectory()) {
servletWebServer.setDocumentRoot(root);
}
}
}
/**
* Resolve path prefix to static resources.
*/
private String resolvePathPrefix() {
String fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8);
String rootPath = Paths.get(".").toUri().normalize().getPath();
String extractedPath = fullExecutablePath.replace(rootPath, "");
int extractionEndIndex = extractedPath.indexOf("target/");
if (extractionEndIndex <= 0) {
return "";
}
return extractedPath.substring(0, extractionEndIndex);
}
With the following content
package com.mycompany.myapp.config;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import tech.jhipster.config.JHipsterConstants;
import tech.jhipster.config.JHipsterProperties;
@Configuration
@Profile({ JHipsterConstants.SPRING_PROFILE_PRODUCTION })
public class StaticResourcesWebConfiguration implements WebMvcConfigurer {
protected static final String[] RESOURCE_LOCATIONS = new String[] {
"classpath:/static/",
"classpath:/static/content/",
"classpath:/static/i18n/",
};
protected static final String[] RESOURCE_PATHS = new String[] {
"/*.js",
"/*.css",
"/*.svg",
"/*.png",
"*.ico",
"/content/**",
"/i18n/*",
};
private final JHipsterProperties jhipsterProperties;
public StaticResourcesWebConfiguration(JHipsterProperties jHipsterProperties) {
this.jhipsterProperties = jHipsterProperties;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
ResourceHandlerRegistration resourceHandlerRegistration = appendResourceHandler(registry);
initializeResourceHandler(resourceHandlerRegistration);
}
protected ResourceHandlerRegistration appendResourceHandler(ResourceHandlerRegistry registry) {
return registry.addResourceHandler(RESOURCE_PATHS);
}
protected void initializeResourceHandler(ResourceHandlerRegistration resourceHandlerRegistration) {
resourceHandlerRegistration.addResourceLocations(RESOURCE_LOCATIONS).setCacheControl(getCacheControl());
}
protected CacheControl getCacheControl() {
return CacheControl.maxAge(getJHipsterHttpCacheProperty(), TimeUnit.DAYS).cachePublic();
}
private int getJHipsterHttpCacheProperty() {
return jhipsterProperties.getHttp().getCache().getTimeToLiveInDays();
}
}
With the following code
package com.mycompany.myapp.web.rest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ClientForwardController {
/**
* Forwards any unmapped paths (except those containing a period) to the client {@code index.html}.
* @return forward to client {@code index.html}.
*/
@GetMapping(value = "/**/{path:[^\\.]*}")
public String forward() {
return "forward:/";
}
}
In the pom.xml
file, below the lines
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
add the following lines :
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<configuration>
<workingDirectory>webapp</workingDirectory>
</configuration>
<executions>
<execution>
<id>install node and yarn</id>
<goals>
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v16.14.0</nodeVersion>
<yarnVersion>v1.22.15</yarnVersion>
</configuration>
</execution>
<execution>
<id>yarn install</id>
<goals>
<goal>yarn</goal>
</goals>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<execution>
<id>yarn build</id>
<goals>
<goal>yarn</goal>
</goals>
<configuration>
<arguments>build</arguments>
</configuration>
</execution>
</executions>
</plugin>
Add the following lines to the root .gitignore
#######################
# Maven frontend plugin
#######################
/webapp/node
Add http://localhost:3000
and https://localhost:3000
tojhipster.cors.allowed-origins
allowed-origins: 'http://localhost:8100,https://localhost:8100,http://localhost:9000,https://localhost:9000,http://localhost:3000,https://localhost:3000'
Update the jhipster.mail.base-url
with http://localhost:3000/app
mail: # specific JHipster mail property, for standard properties see MailProperties
base-url: http://localhost:3000/app
As of JHipster 7.7.0, if your project uses localization features, but does not provide an English translation, some tests in [EmailServiceIT.java](http://EmailServiceIT.java)
might fail. To correct them, update line in testSendEmailFromTemplate
user.setLangKey("en");
with
user.setLangKey(Constants.DEFAULT_LANGUAGE);
Add the following lines at the end of the scripts
property inside package.json
.
{
...
"scripts": {
...
"prettier:format": "prettier --write \"{,src/**/}*.{md,json,yml,html,java}\"",
"webapp:build": "cd webapp && yarn build",
"webapp:dev": "cd webapp && yarn dev",
"webapp:install": "cd webapp && yarn install",
"webapp:storybook": "cd webapp && yarn storybook",
"webapp:test": "cd webapp && yarn test"
},
...
}
At the root of the project run the following command (you need node 14+)
npx create-start-ui --web --no-git-init webapp
Update the scripts
and the lint-staged
sections with the following code
"scripts": {
"postinstall": "yarn build:info && yarn theme:generate-typing",
"test": "cypress open",
"test:ci": "cypress run --component",
"dev": "yarn docs:build && next dev",
"build": "yarn build:info && yarn docs:build && NEXT_PUBLIC_API_BASE_URL=/api next build && next export && mkdir -p ../target/classes/static && mv out/* ../target/classes/static",
"build:info": "node .build-info.generate.js",
"pretty": "prettier -w .",
"lint": "eslint ./src --fix && tsc --noEmit",
"lint:staged": "tsc --noEmit && eslint --cache --fix",
"storybook": "start-storybook -p 6006",
"storybook:build": "build-storybook && mv ./storybook-static ./public/storybook",
"theme:generate-typing": "chakra-cli tokens ./src/theme/theme.ts",
"theme:generate-icons": "svgr --config-file src/components/Icons/svgr.config.js src/components/Icons/svg-sources",
"docs:build": "swagger-cli bundle src/mocks/openapi/openapi.yaml -t json --outfile public/open-api.json"
},
"lint-staged": {
"*.{ts,tsx,js,jsx,json}": "prettier --write",
"*.{ts,tsx,js,jsx}": "yarn lint:staged",
"src/mocks/**/*.{yaml,yml}": "yarn docs:build"
},
NEXT_PUBLIC_API_BASE_URL=http://localhost:8080/api
Change line
NEXT_PUBLIC_API_BASE_URL: z.string().url().optional(),
with
NEXT_PUBLIC_API_BASE_URL: z.string().optional(),
In two terminals run the following commands
./mvnw
cd webapp && yarn dev
The web app will be available on http://localhost:3000
./mvnw -Pprod clean verify