Skip to content

Commit

Permalink
feat: support vite in v14 bootstrap (#12259)
Browse files Browse the repository at this point in the history
Support vite build in the v14 bootstrap to have exported webcomponents working.

Closes #12057
  • Loading branch information
caalador committed Nov 11, 2021
1 parent aaa680f commit 97d4815
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 10 deletions.
Expand Up @@ -126,6 +126,7 @@ public static FeatureFlags get(final VaadinContext context) {
context.getAttribute(Lookup.class));
featureFlags.configuration = ApplicationConfiguration
.get(context);
featureFlags.loadProperties();
attribute = new FeatureFlagsWrapper(featureFlags);
context.setAttribute(attribute);
}
Expand Down
Expand Up @@ -39,6 +39,8 @@
import java.util.ServiceLoader;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -53,6 +55,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.experimental.FeatureFlags;
import com.vaadin.flow.component.PushConfiguration;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.page.Inline;
Expand Down Expand Up @@ -876,6 +879,43 @@ private void setupFrameworkLibraries(Element head,

private void appendNpmBundle(Element head, VaadinService service,
BootstrapContext context) throws IOException {
if (FeatureFlags.get(service.getContext())
.isEnabled(FeatureFlags.VITE)) {

if (!service.getDeploymentConfiguration().isProductionMode()) {
Element script = createJavaScriptElement(
"VAADIN/@vite/client", false);
head.appendChild(script.attr("type", "module"));
return;
}

// Get the index.html to get vite generated bundles
String index = FrontendUtils.getIndexHtmlContent(service);

// Get and add all javascriptbundles
Matcher scriptMatcher = Pattern
.compile("src=\\\"VAADIN\\/build\\/(.*\\.js)\\\"")
.matcher(index);
while (scriptMatcher.find()) {
Element script = createJavaScriptElement(
"VAADIN/build/" + scriptMatcher.group(1), false);
head.appendChild(
script.attr("type", "module").attr("async", true)
// Fixes basic auth in Safari #6560
.attr("crossorigin", true));
}

// Get and add all css bundle links
Matcher cssMatcher = Pattern
.compile("href=\\\"VAADIN\\/build\\/(.*\\.css)\\\"")
.matcher(index);
while (cssMatcher.find()) {
Element link = createStylesheetElement(
"VAADIN/build/" + cssMatcher.group(1));
head.appendChild(link);
}
return;
}
String content = FrontendUtils.getStatsAssetsByChunkName(service);
if (content == null) {
StringBuilder message = new StringBuilder(
Expand Down
Expand Up @@ -493,13 +493,19 @@ private static String getFileContent(VaadinService service, String path)

private static InputStream getFileFromClassPath(VaadinService service,
String filePath) {
InputStream stream = service.getClassLoader()
.getResourceAsStream(VAADIN_WEBAPP_RESOURCES + filePath);
if (stream == null) {
final URL resource = service.getContext().getAttribute(Lookup.class)
.lookup(ResourceProvider.class)
.getApplicationResource(VAADIN_WEBAPP_RESOURCES + filePath);
if (resource == null) {
getLogger().error("Cannot get the '{}' from the classpath",
filePath);
return null;
}
try {
return resource.openStream();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return stream;
}

private static InputStream getResourceFromWebpack(
Expand Down
@@ -1,19 +1,25 @@
package com.vaadin.flow.server;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import org.apache.commons.io.IOUtils;
import org.hamcrest.CoreMatchers;
Expand All @@ -28,6 +34,7 @@
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;

import com.vaadin.experimental.FeatureFlags;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.Tag;
Expand All @@ -54,6 +61,9 @@
import com.vaadin.flow.router.TestRouteRegistry;
import com.vaadin.flow.server.BootstrapHandler.BootstrapContext;
import com.vaadin.flow.server.MockServletServiceSessionSetup.TestVaadinServletService;
import com.vaadin.flow.server.startup.ApplicationConfiguration;
import com.vaadin.flow.server.startup.DefaultApplicationConfigurationFactory;
import com.vaadin.flow.shared.ApplicationConstants;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.shared.VaadinUriResolver;
import com.vaadin.flow.shared.communication.PushMode;
Expand All @@ -62,9 +72,12 @@
import com.vaadin.tests.util.MockDeploymentConfiguration;

import static com.vaadin.flow.server.Constants.VAADIN_MAPPING;
import static com.vaadin.flow.server.Constants.VAADIN_WEBAPP_RESOURCES;
import static com.vaadin.flow.server.frontend.FrontendUtils.INDEX_HTML;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

public class BootstrapHandlerTest {

Expand Down Expand Up @@ -1640,6 +1653,106 @@ public void synchronizedHandleRequest_requestPathInfoNull_works()
response.getErrorCode());
}

@Test
public void runViteFeatureDevMode_viteClientAddedToHead()
throws IOException {
initUI(testUI);

enableViteFeature(false);

final Document bootstrapPage = pageBuilder.getBootstrapPage(context);
Assert.assertTrue("@vite/client should be added to head.", bootstrapPage
.head().toString().contains("VAADIN/@vite/client"));
}

@Test
public void runViteFeatureProdMode_bundleAddedToHead() throws IOException {
initUI(testUI);

enableViteFeature(true);

deploymentConfiguration.setProductionMode(true);

final Lookup lookup = service.getContext().getAttribute(Lookup.class);
ResourceProvider resourceProvider = lookup
.lookup(ResourceProvider.class);

URL resource = Mockito.mock(URL.class);
Mockito.when(resourceProvider
.getApplicationResource(VAADIN_WEBAPP_RESOURCES + INDEX_HTML))
.thenReturn(resource);

when(resource.openStream())
.thenReturn(new ByteArrayInputStream(("<html lang=\"en\">\n"
+ "<head>\n" + " <meta charset=\"UTF-8\" />\n"
+ " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
+ " <style>\n" + " body, #outlet {\n"
+ " height: 100vh;\n" + " width: 100%;\n"
+ " margin: 0;\n" + " }\n" + " </style>\n"
+ " <script async type=\"module\" crossorigin src=\"VAADIN/build/main.d253dd35.js\"></script>\n"
+ " <link rel=\"stylesheet\" href=\"VAADIN/build/main.688a5538.css\">\n"
+ "</head>").getBytes()));

final Document bootstrapPage = pageBuilder.getBootstrapPage(context);
Assert.assertFalse("@vite/client should not be added in productionMode",
bootstrapPage.head().toString()
.contains("VAADIN/@vite/client"));
Assert.assertTrue(
"Bundle should be gotten from index and added to bootstrap page",
bootstrapPage.head().toString()
.contains("src=\"VAADIN/build/main.d253dd35.js\""));
Assert.assertTrue(
"Bundled css should be gotten from index and added to bootstrap page",
bootstrapPage.head().toString()
.contains("href=\"VAADIN/build/main.688a5538.css\""));
}

private void enableViteFeature(boolean productionMode) throws IOException {
VaadinContext vaadinContext = Mockito.mock(VaadinContext.class);

final Lookup lookup = Mockito.mock(Lookup.class);
ResourceProvider resourceProvider = Mockito
.mock(ResourceProvider.class);
Mockito.when(lookup.lookup(ResourceProvider.class))
.thenReturn(resourceProvider);

Mockito.when(resourceProvider.getClientResourceAsStream(
"META-INF/resources/" + ApplicationConstants.CLIENT_ENGINE_PATH
+ "/compile.properties"))
.thenReturn(getClass().getClassLoader()
.getResourceAsStream("META-INF/resources/"
+ ApplicationConstants.CLIENT_ENGINE_PATH
+ "/compile.properties"));

Mockito.when(vaadinContext.getAttribute(Lookup.class))
.thenReturn(lookup);
service.setContext(vaadinContext);

ApplicationConfiguration configuration = Mockito
.mock(ApplicationConfiguration.class);
Mockito.when(configuration.isProductionMode()).thenReturn(false);
Mockito.when(configuration.getJavaResourceFolder())
.thenReturn(tmpDir.getRoot());

Mockito.when(lookup.lookup(ApplicationConfiguration.class))
.thenReturn(configuration);
Mockito.when(vaadinContext.getAttribute(ApplicationConfiguration.class))
.thenReturn(configuration);
Mockito.when(vaadinContext.getAttribute(
Mockito.eq(ApplicationConfiguration.class), Mockito.any()))
.thenReturn(configuration);

final FeatureFlags featureFlags = FeatureFlags
.get(testUI.getSession().getService().getContext());
Mockito.when(vaadinContext.getAttribute(FeatureFlags.class))
.thenReturn(featureFlags);

featureFlags.setEnabled(FeatureFlags.VITE.getId(), true);

Mockito.when(configuration.isProductionMode())
.thenReturn(productionMode);
}

public static Location requestToLocation(VaadinRequest request) {
return new Location(request.getPathInfo(),
QueryParameters.full(request.getParameterMap()));
Expand Down
Expand Up @@ -17,9 +17,11 @@

import javax.servlet.http.HttpServletRequest;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -40,11 +42,13 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.internal.JavaScriptBootstrapUI;
import com.vaadin.flow.di.Lookup;
import com.vaadin.flow.di.ResourceProvider;
import com.vaadin.flow.internal.DevModeHandler;
import com.vaadin.flow.internal.DevModeHandlerManager;
import com.vaadin.flow.internal.UsageStatistics;
Expand All @@ -70,9 +74,12 @@
import elemental.json.JsonObject;

import static com.vaadin.flow.component.internal.JavaScriptBootstrapUI.SERVER_ROUTING;
import static com.vaadin.flow.server.Constants.VAADIN_WEBAPP_RESOURCES;
import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_FRONTEND_DIR;
import static com.vaadin.flow.server.frontend.FrontendUtils.INDEX_HTML;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class IndexHtmlRequestHandlerTest {
private static final String SPRING_CSRF_ATTRIBUTE_IN_SESSION = "org.springframework.security.web.csrf.CsrfToken";
Expand Down Expand Up @@ -127,14 +134,23 @@ public void serveIndexHtml_requestWithRootPath_serveContentFromTemplate()
@Test
public void serveNotFoundIndexHtml_requestWithRootPath_failsWithIOException()
throws IOException {
ClassLoader mockLoader = Mockito.mock(ClassLoader.class);
VaadinServletService vaadinService = Mockito
.mock(VaadinServletService.class);
Mockito.when(vaadinService.getDeploymentConfiguration())
.thenReturn(deploymentConfiguration);
Mockito.when(vaadinService.getClassLoader()).thenReturn(mockLoader);
Mockito.when(mockLoader.getResourceAsStream(Mockito.anyString()))
.thenReturn(null);

Mockito.when(vaadinService.getContext()).thenReturn(context);
final Lookup lookup = Mockito.mock(Lookup.class);
ResourceProvider resourceProvider = Mockito
.mock(ResourceProvider.class);
Mockito.when(context.getAttribute(Lookup.class)).thenReturn(lookup);
Mockito.when(lookup.lookup(ResourceProvider.class))
.thenReturn(resourceProvider);
URL resource = Mockito.mock(URL.class);
Mockito.when(resourceProvider
.getApplicationResource(VAADIN_WEBAPP_RESOURCES + INDEX_HTML))
.thenReturn(resource);
when(resource.openStream()).thenReturn(null);

VaadinServletRequest vaadinRequest = Mockito
.mock(VaadinServletRequest.class);
Expand Down Expand Up @@ -507,14 +523,24 @@ public DevModeHandler getDevModeHandler() {
}

};
Lookup lookup = Lookup.of(devModeHandlerManager,
DevModeHandlerManager.class);
ResourceProvider resourceProvider = Mockito
.mock(ResourceProvider.class);
Lookup lookup = Lookup.compose(
Lookup.of(devModeHandlerManager, DevModeHandlerManager.class),
Lookup.of(resourceProvider, ResourceProvider.class));
Mockito.when(context.getAttribute(Lookup.class)).thenReturn(lookup);

ApplicationConfiguration appConfig = Mockito
.mock(ApplicationConfiguration.class);
mockApplicationConfiguration(appConfig);

URL resource = Mockito.mock(URL.class);
Mockito.when(resourceProvider
.getApplicationResource(VAADIN_WEBAPP_RESOURCES + INDEX_HTML))
.thenReturn(resource);
when(resource.openStream()).thenReturn(new ByteArrayInputStream(
"<html><head></head></html>".getBytes()));

// Send the request
indexHtmlRequestHandler.synchronizedHandleRequest(session,
createVaadinRequest("/"), response);
Expand Down

0 comments on commit 97d4815

Please sign in to comment.