Skip to content

Commit

Permalink
fix: Store and reuse index.html modifications for dev bundles (#16794)
Browse files Browse the repository at this point in the history
Fixes #16792
  • Loading branch information
Artur- authored May 16, 2023
1 parent fbb4751 commit 8282506
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -49,7 +48,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.io.IOUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.DataNode;
import org.jsoup.nodes.Document;
Expand All @@ -70,6 +68,7 @@
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.internal.AnnotationReader;
import com.vaadin.flow.internal.BootstrapHandlerHelper;
import com.vaadin.flow.internal.JsonUtils;
import com.vaadin.flow.internal.ReflectTools;
import com.vaadin.flow.internal.UsageStatisticsExporter;
import com.vaadin.flow.router.InvalidLocationException;
Expand All @@ -96,6 +95,7 @@
import elemental.json.JsonObject;
import elemental.json.JsonValue;
import elemental.json.impl.JsonUtil;

import static com.vaadin.flow.server.Constants.VAADIN_MAPPING;
import static com.vaadin.flow.server.frontend.FrontendUtils.EXPORT_CHUNK;
import static java.nio.charset.StandardCharsets.UTF_8;
Expand Down Expand Up @@ -1595,17 +1595,15 @@ protected static void setupPwa(Document document, VaadinService service) {
setupPwa(document, service.getPwaRegistry());
}

protected static void addJavaScriptEntryPoints(
protected static void addGeneratedIndexContent(
DeploymentConfiguration config, Document targetDocument)
throws IOException {
URL statsJsonUrl = DevBundleUtils
.findBundleFile(config.getProjectFolder(), "config/stats.json");
Objects.requireNonNull(statsJsonUrl,
String statsJson = DevBundleUtils
.findBundleStatsJson(config.getProjectFolder());
Objects.requireNonNull(statsJson,
"Frontend development bundle is expected to be in the project"
+ " or on the classpath, but not found.");
String statsJson = IOUtils.toString(statsJsonUrl,
StandardCharsets.UTF_8);
addEntryScripts(targetDocument, Json.parse(statsJson));
addGeneratedIndexContent(targetDocument, Json.parse(statsJson));
}

/**
Expand Down Expand Up @@ -1676,31 +1674,30 @@ private static Element getStyleTag(String themeName, String fileName,
return element;
}

private static void addEntryScripts(Document targetDocument,
private static void addGeneratedIndexContent(Document targetDocument,
JsonObject statsJson) {
boolean addIndexHtml = true;
Element indexHtmlScript = null;
JsonArray entryScripts = statsJson.getArray("entryScripts");
for (int i = 0; i < entryScripts.length(); i++) {
String entryScript = entryScripts.getString(i);
Element elm = new Element(SCRIPT_TAG);
elm.attr("type", "module");
elm.attr("src", entryScript);
targetDocument.head().appendChild(elm);
JsonArray indexHtmlGeneratedRows = statsJson
.getArray("indexHtmlGenerated");
List<String> toAdd = new ArrayList<>();

if (entryScript.contains("indexhtml")) {
indexHtmlScript = elm;
}
Optional<String> webComponentScript = JsonUtils
.stream(statsJson.getArray("entryScripts"))
.map(value -> value.asString())
.filter(script -> script.contains("webcomponenthtml"))
.findFirst();

if (entryScript.contains("webcomponenthtml")) {
addIndexHtml = false;
}
if (webComponentScript.isPresent()) {
Element elm = new Element(SCRIPT_TAG);
elm.attr("type", "module");
elm.attr("src", webComponentScript.get());
toAdd.add(elm.outerHtml());
} else {
toAdd.addAll(JsonUtils.stream(indexHtmlGeneratedRows)
.map(value -> value.asString()).toList());
}

// If a reference to webcomponenthtml is present, the embedded
// components are used, thus we don't need to serve indexhtml script
if (!addIndexHtml && indexHtmlScript != null) {
indexHtmlScript.remove();
for (String row : toAdd) {
targetDocument.head().append(row);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ private static Document getIndexHtmlDocument(VaadinService service)
// When running without a frontend server, the index.html comes
// directly from the frontend folder and the JS entrypoint(s) need
// to be added
addJavaScriptEntryPoints(config, indexHtmlDocument);
addGeneratedIndexContent(config, indexHtmlDocument);
}
modifyIndexHtmlForVite(indexHtmlDocument);
return indexHtmlDocument;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public Document getBootstrapPage(BootstrapContext context) {
// directly from the frontend folder and the JS
// entrypoint(s) need
// to be added
addJavaScriptEntryPoints(deploymentConfiguration, document);
addGeneratedIndexContent(deploymentConfiguration, document);
}

// Specify the application ID for scripts of the
Expand Down
31 changes: 27 additions & 4 deletions flow-server/src/main/resources/vite.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ const bundleSizeFile = path.resolve(statsFolder, 'bundle-size.html');
const nodeModulesFolder = path.resolve(__dirname, 'node_modules');
const webComponentTags = '#webComponentTags#';

const projectIndexHtml = path.resolve(frontendFolder, 'index.html');

const projectStaticAssetsFolders = [
path.resolve(__dirname, 'src', 'main', 'resources', 'META-INF', 'resources'),
path.resolve(__dirname, 'src', 'main', 'resources', 'static'),
Expand Down Expand Up @@ -229,15 +231,35 @@ function statsExtracterPlugin(): PluginOption {
.sort()
.filter((value, index, self) => self.indexOf(value) === index);
const npmModuleAndVersion = Object.fromEntries(npmModules.map((module) => [module, getVersion(module)]));
const cvdls = Object.fromEntries(npmModules.filter((module) => getCvdlName(module) != null)
.map((module) => [module, {name: getCvdlName(module),version: getVersion(module)}]));
const cvdls = Object.fromEntries(
npmModules
.filter((module) => getCvdlName(module) != null)
.map((module) => [module, { name: getCvdlName(module), version: getVersion(module) }])
);

mkdirSync(path.dirname(statsFile), { recursive: true });
const projectPackageJson = JSON.parse(readFileSync(projectPackageJsonFile, { encoding: 'utf-8' }));

const entryScripts = Object.values(bundle)
.filter((bundle) => bundle.isEntry)
.map((bundle) => bundle.fileName);

const generatedIndexHtml = path.resolve(buildOutputFolder, 'index.html');
const customIndexData: string = readFileSync(projectIndexHtml, { encoding: 'utf-8' });
const generatedIndexData: string = readFileSync(generatedIndexHtml, {
encoding: 'utf-8'
});

const customIndexRows = new Set(customIndexData.split(/[\r\n]/).filter((row) => row.trim() !== ''));
const generatedIndexRows = generatedIndexData.split(/[\r\n]/).filter((row) => row.trim() !== '');

const rowsGenerated: string[] = [];
generatedIndexRows.forEach((row) => {
if (!customIndexRows.has(row)) {
rowsGenerated.push(row);
}
});

//After dev-bundle build add used Flow frontend imports JsModule/JavaScript/CssImport

const parseImports = (filename: string, result: Set<string>): void => {
Expand Down Expand Up @@ -343,7 +365,8 @@ function statsExtracterPlugin(): PluginOption {
entryScripts,
webComponents,
cvdlModules: cvdls,
packageJsonHash: projectPackageJson?.vaadin?.hash
packageJsonHash: projectPackageJson?.vaadin?.hash,
indexHtmlGenerated: rowsGenerated
};
writeFileSync(statsFile, JSON.stringify(stats, null, 1));
}
Expand Down Expand Up @@ -663,7 +686,7 @@ export const vaadinConfig: UserConfigFn = (env) => {
assetsDir: 'VAADIN/build',
rollupOptions: {
input: {
indexhtml: path.resolve(frontendFolder, 'index.html'),
indexhtml: projectIndexHtml,

...(hasExportedWebComponents ? { webcomponenthtml: path.resolve(frontendFolder, 'web-component.html') } : {})
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ public static void createStatsJsonStub(File projectRootFolder)
throws IOException {
String content = "{\"npmModules\": {}, "
+ "\"entryScripts\": [\"foo.js\"], "
+ "\"packageJsonHash\": \"42\"}";
+ "\"packageJsonHash\": \"42\","
+ "\"indexHtmlGenerated\": []}";
createStubFile(projectRootFolder,
Constants.DEV_BUNDLE_LOCATION + "/config/stats.json", content);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background: rgba(173, 216, 230, 1);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2000-2023 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.frontend;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.apache.commons.io.FileUtils;
import org.junit.Assert;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import com.vaadin.flow.server.Constants;
import com.vaadin.flow.testutil.ChromeBrowserTest;

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

public class ViteImportedCSSIT extends ChromeBrowserTest {

@Override
protected String getTestPath() {
return "/view/";
}

@Test
public void cssImportedByVite_availableInApp() throws IOException {
open();
WebElement body = $("body").first();

Assert.assertEquals("rgba(173, 216, 230, 1)",
body.getCssValue("background-color"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PluginOption, UserConfigFn } from 'vite';
import { overrideVaadinConfig } from './vite.generated';

function addCssToIndex(): PluginOption {
return {
name: 'generate-css',
transformIndexHtml: (_html, _conf) => {
const tags = [
{
tag: 'link',
attrs: {
rel: 'stylesheet',
href: '/imported-by-vite-plugin.css'
}
}
];

return tags;
}
};
}

const customConfig: UserConfigFn = (env) => ({
// Here you can add custom Vite parameters
// https://vitejs.dev/config/
plugins: [addCssToIndex()]
});

export default overrideVaadinConfig(customConfig);

0 comments on commit 8282506

Please sign in to comment.