Skip to content

Commit

Permalink
Check fallback chunk data and load fallback chunk if dependency is in…
Browse files Browse the repository at this point in the history
… the data (#6590)

Fixes #6590
  • Loading branch information
Denis authored Oct 4, 2019
1 parent b7000f5 commit ffef3a6
Show file tree
Hide file tree
Showing 25 changed files with 501 additions and 273 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ private void runNodeUpdater() throws ExecutionFailedException {
.enableImportsUpdate(true)
.withEmbeddableWebComponents(
generateEmbeddableWebComponents)
.build().execute();
.withTokenFile(getTokenFile()).build().execute();
}

private void runWebpack() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
Expand All @@ -44,7 +43,6 @@
import com.vaadin.flow.internal.AnnotationReader;
import com.vaadin.flow.internal.ReflectTools;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.startup.DevModeInitializer.VisitedClasses;
import com.vaadin.flow.shared.ApplicationConstants;
import com.vaadin.flow.shared.ui.LoadMode;
import com.vaadin.flow.shared.util.SharedUtil;
Expand Down Expand Up @@ -179,11 +177,6 @@ private static Logger getLogger() {
*/
private static DependencyInfo findDependencies(VaadinService service,
Class<? extends Component> componentClass) {
Optional.ofNullable(
service.getContext().getAttribute(VisitedClasses.class))
.ifPresent(visitedClasses -> visitedClasses
.ensureAllDependenciesVisited(componentClass));

DependencyInfo dependencyInfo = new DependencyInfo();

findDependencies(service, componentClass, dependencyInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand All @@ -35,8 +37,10 @@
import com.vaadin.flow.component.ComponentUtil;
import com.vaadin.flow.component.HasElement;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.component.dependency.JavaScript;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.StyleSheet;
import com.vaadin.flow.component.internal.ComponentMetaData.DependencyInfo;
import com.vaadin.flow.component.internal.ComponentMetaData.HtmlImportDependency;
Expand Down Expand Up @@ -66,10 +70,13 @@
import com.vaadin.flow.router.internal.AfterNavigationHandler;
import com.vaadin.flow.router.internal.BeforeEnterHandler;
import com.vaadin.flow.router.internal.BeforeLeaveHandler;
import com.vaadin.flow.server.VaadinContext;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.server.WebBrowser;
import com.vaadin.flow.server.communication.PushConnection;
import com.vaadin.flow.server.frontend.FallbackChunk;
import com.vaadin.flow.server.frontend.FallbackChunk.CssImportData;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.shared.communication.PushMode;
import com.vaadin.flow.theme.AbstractTheme;
Expand Down Expand Up @@ -200,6 +207,8 @@ public List<Object> getParameters() {

private ExtendedClientDetails extendedClientDetails = null;

private boolean isFallbackChunkLoaded;

/**
* Creates a new instance for the given UI.
*
Expand Down Expand Up @@ -848,18 +857,85 @@ public void addComponentDependencies(
js -> page.addJavaScript(js.value(), js.loadMode()));
} else {
// In npm mode, add external JavaScripts directly to the page.
dependencies.getJavaScripts().stream()
.filter(js -> UrlUtil.isExternal(js.value()))
.forEach(js -> page.addJavaScript(js.value(),
js.loadMode()));
dependencies.getJsModules().stream()
.filter(js -> UrlUtil.isExternal(js.value()))
.forEach(js -> page.addJsModule(js.value(), js.loadMode()));
addExternalDependencies(dependencies);
addFallbackDependencies(dependencies);

}
dependencies.getStyleSheets().forEach(styleSheet -> page
.addStyleSheet(styleSheet.value(), styleSheet.loadMode()));
}

private void addFallbackDependencies(DependencyInfo dependency) {
if (isFallbackChunkLoaded) {
return;
}
VaadinContext context = ui.getSession().getService().getContext();
FallbackChunk chunk = context.getAttribute(FallbackChunk.class);
if (chunk == null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(
"Fallback chunk is not available, skipping fallback dependencies load");
}
return;
}

Set<String> modules = chunk.getModules();
Set<CssImportData> cssImportsData = chunk.getCssImports();
if (modules.isEmpty() && cssImportsData.isEmpty()) {
getLogger().debug(
"Fallback chunk is empty, skipping fallback dependencies load");
return;
}

List<CssImport> cssImports = dependency.getCssImports();
List<JavaScript> javaScripts = dependency.getJavaScripts();
List<JsModule> jsModules = dependency.getJsModules();

if (jsModules.stream().map(JsModule::value)
.anyMatch(modules::contains)) {
loadFallbackChunk();
return;
}

if (javaScripts.stream().map(JavaScript::value)
.anyMatch(modules::contains)) {
loadFallbackChunk();
return;
}

if (cssImports.stream().map(this::buildData)
.anyMatch(cssImportsData::contains)) {
loadFallbackChunk();
return;
}
}

private CssImportData buildData(CssImport imprt) {
Function<String, String> converter = str -> str.isEmpty() ? null : str;
return new CssImportData(converter.apply(imprt.value()),
converter.apply(imprt.id()), converter.apply(imprt.include()),
converter.apply(imprt.themeFor()));
}

private void loadFallbackChunk() {
if (isFallbackChunkLoaded) {
return;
}
ui.getPage()
.addDynamicImport("return window.Vaadin.Flow.loadFallback();");
isFallbackChunkLoaded = true;
}

private void addExternalDependencies(DependencyInfo dependency) {
Page page = ui.getPage();
dependency.getJavaScripts().stream()
.filter(js -> UrlUtil.isExternal(js.value()))
.forEach(js -> page.addJavaScript(js.value(), js.loadMode()));
dependency.getJsModules().stream()
.filter(js -> UrlUtil.isExternal(js.value()))
.forEach(js -> page.addJsModule(js.value(), js.loadMode()));
}

private void addHtmlImport(HtmlImportDependency dependency, Page page) {
// The HTML dependency parser does not consider themes so it can
// cache raw information (e.g. vaadin-button/src/vaadin-button.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,6 @@ public void addHtmlImport(String url, LoadMode loadMode) {
*
*
* @see #addHtmlImport(String)
*
* @param expression
* the JavaScript expression which return a Promise
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ public static class Builder implements Serializable {

private File frontendResourcesDirectory = null;

private Set<String> visitedClasses = null;

private boolean useByteCodeScanner = false;

private JsonObject tokenFileData;

private File tokenFile;

/**
* Directory for for npm and folders and files.
*/
Expand Down Expand Up @@ -278,20 +278,6 @@ public Builder createMissingPackageJson(boolean create) {
return this;
}

/**
* Sets a set to which the names of classes visited when finding
* dependencies will be collected.
*
* @param visitedClasses
* a set to collect class name to, or <code>null</code> to
* not collect visited classes
* @return the builder, for chaining
*/
public Builder collectVisitedClasses(Set<String> visitedClasses) {
this.visitedClasses = visitedClasses;
return this;
}

/**
* Set local frontend files to be copied from given folder.
*
Expand Down Expand Up @@ -330,6 +316,18 @@ public Builder populateTokenFileData(JsonObject object) {
tokenFileData = object;
return this;
}

/**
* Sets the token file (flow-build-info.json) path.
*
* @param tokenFile
* token file path
* @return the builder, for chaining
*/
public Builder withTokenFile(File tokenFile) {
this.tokenFile = tokenFile;
return this;
}
}

private final Collection<FallibleCommand> commands = new ArrayList<>();
Expand Down Expand Up @@ -390,17 +388,13 @@ private NodeTasks(Builder builder) {
}

if (builder.enableImportsUpdate) {
commands.add(new TaskUpdateImports(classFinder,
frontendDependencies,
finder -> getFallbackScanner(builder, finder),
builder.npmFolder, builder.generatedFolder,
builder.frontendDirectory, builder.webpackOutputDirectory,
builder.tokenFileData));

if (builder.visitedClasses != null) {
builder.visitedClasses
.addAll(frontendDependencies.getClasses());
}
commands.add(
new TaskUpdateImports(classFinder, frontendDependencies,
finder -> getFallbackScanner(builder, finder),
builder.npmFolder, builder.generatedFolder,
builder.frontendDirectory, builder.tokenFile,
builder.tokenFileData));

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import elemental.json.Json;
import elemental.json.JsonObject;

import static com.vaadin.flow.server.frontend.TaskUpdatePackages.APP_PACKAGE_HASH;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
import elemental.json.impl.JsonUtil;

import static com.vaadin.flow.server.frontend.FrontendUtils.IMPORTS_NAME;
import static com.vaadin.flow.server.frontend.FrontendUtils.TOKEN_FILE;

/**
* An updater that it's run when the servlet context is initialised in dev-mode
Expand All @@ -73,7 +72,7 @@ public class TaskUpdateImports extends NodeUpdater {
private final File frontendDirectory;
private final FrontendDependenciesScanner fallbackScanner;
private final ClassFinder finder;
private final File webpackOutputDirectory;
private final File tokenFile;
private final JsonObject tokenFileData;

private class UpdateMainImportsFile extends AbstractUpdateImports {
Expand Down Expand Up @@ -278,16 +277,16 @@ File getGeneratedFallbackFile() {
* folder where flow generated files will be placed.
* @param frontendDirectory
* a directory with project's frontend files
* @param webpackOutputDirectory
* the directory to set for webpack to output its build results.
* @param tokenFile
* the token (flow-build-info.json) path, may be {@code null}
*/
TaskUpdateImports(ClassFinder finder,
FrontendDependenciesScanner frontendDepScanner,
SerializableFunction<ClassFinder, FrontendDependenciesScanner> fallBackScannerProvider,
File npmFolder, File generatedPath, File frontendDirectory,
File webpackOutputDirectory) {
File tokenFile) {
this(finder, frontendDepScanner, fallBackScannerProvider, npmFolder,
generatedPath, frontendDirectory, webpackOutputDirectory, null);
generatedPath, frontendDirectory, tokenFile, null);
}

/**
Expand All @@ -305,21 +304,21 @@ File getGeneratedFallbackFile() {
* folder where flow generated files will be placed.
* @param frontendDirectory
* a directory with project's frontend files
* @param webpackOutputDirectory
* the directory to set for webpack to output its build results.
* @param tokenFile
* the token (flow-build-info.json) path, may be {@code null}
* @param tokenFileData
* object to fill with token file data, may be {@code null}
*/
TaskUpdateImports(ClassFinder finder,
FrontendDependenciesScanner frontendDepScanner,
SerializableFunction<ClassFinder, FrontendDependenciesScanner> fallBackScannerProvider,
File npmFolder, File generatedPath, File frontendDirectory,
File webpackOutputDirectory, JsonObject tokenFileData) {
File tokenFile, JsonObject tokenFileData) {
super(finder, frontendDepScanner, npmFolder, generatedPath);
this.frontendDirectory = frontendDirectory;
fallbackScanner = fallBackScannerProvider.apply(finder);
this.finder = finder;
this.webpackOutputDirectory = webpackOutputDirectory;
this.tokenFile = tokenFile;
this.tokenFileData = tokenFileData;
}

Expand Down Expand Up @@ -368,31 +367,28 @@ private AbstractTheme getTheme() {
}

private void updateBuildFile(AbstractUpdateImports updater) {
File tokenFile = getTokenFile();
if (!tokenFile.exists()) {
log().warn("Missing token file. New token file will be created.");
boolean tokenFileExists = tokenFile != null && tokenFile.exists();
if (!tokenFileExists) {
log().warn(
"Token file is not available. Fallback chunk data won't be written.");
}
try {
JsonObject buildInfo;
if (tokenFile.exists()) {
if (tokenFileExists) {
String json = FileUtils.readFileToString(tokenFile,
StandardCharsets.UTF_8);
buildInfo = JsonUtil.parse(json);
} else {
FileUtils.forceMkdirParent(tokenFile);
buildInfo = Json.createObject();
}

populateFallbackData(buildInfo, updater);
if (tokenFileData != null) {
populateFallbackData(tokenFileData, updater);
JsonObject buildInfo = json.isEmpty() ? Json.createObject()
: JsonUtil.parse(json);
populateFallbackData(buildInfo, updater);
FileUtils.write(tokenFile, JsonUtil.stringify(buildInfo, 2),
StandardCharsets.UTF_8);
}

FileUtils.write(tokenFile, JsonUtil.stringify(buildInfo, 2),
StandardCharsets.UTF_8);
} catch (IOException e) {
log().warn("Unable to read token file", e);
}
if (tokenFileData != null) {
populateFallbackData(tokenFileData, updater);
}
}

private void populateFallbackData(JsonObject object,
Expand Down Expand Up @@ -453,7 +449,4 @@ private JsonObject makeCssJson(CssData data) {
return object;
}

private File getTokenFile() {
return new File(webpackOutputDirectory, TOKEN_FILE);
}
}
Loading

0 comments on commit ffef3a6

Please sign in to comment.