@@ -67,10 +65,6 @@ public class LitTemplateParserImpl implements LitTemplateParser {
private static final LitTemplateParser INSTANCE = new LitTemplateParserImpl();
- private final HashMap cache = new HashMap<>();
- private final ReentrantLock templateSourceslock = new ReentrantLock();
- private JsonObject jsonStats;
-
/**
* The default constructor. Protected in order to prevent direct
* instantiation, but not private in order to allow mocking/overrides for
@@ -107,14 +101,7 @@ public TemplateData getTemplateContent(Class extends LitTemplate> clazz,
}
String url = dependency.getUrl();
- String source = getSourcesFromTemplate(tag, url);
- if (source == null) {
- try {
- source = getSourcesFromStats(service, url);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
+ String source = getSourcesFromTemplate(service, tag, url);
if (source == null) {
continue;
}
@@ -173,6 +160,8 @@ private boolean dependencyHasTagName(Dependency dependency, String tag) {
/**
* Finds the JavaScript sources for given tag.
*
+ * @param service
+ * the Vaadin service
* @param tag
* the value of the {@link com.vaadin.flow.component.Tag}
* annotation, e.g. `my-component`
@@ -184,9 +173,23 @@ private boolean dependencyHasTagName(Dependency dependency, String tag) {
* @return the .js source which declares given custom element, or null if no
* such source can be found.
*/
- protected String getSourcesFromTemplate(String tag, String url) {
- InputStream content = getClass().getClassLoader()
- .getResourceAsStream(url);
+ protected String getSourcesFromTemplate(VaadinService service, String tag,
+ String url) {
+ InputStream content = getResourceStream(service, url);
+ if (content == null) {
+ // Attempt to get the sources from dev server, if available
+ content = FrontendUtils.getFrontendFileFromDevModeHandler(service,
+ url);
+ }
+ if (content == null) {
+ // In production builds, template sources are stored in
+ // META-INF/VAADIN/config/templates
+ String pathWithoutPrefix = url.replaceFirst("^\\./", "");
+ String vaadinDirectory = Constants.VAADIN_SERVLET_RESOURCES
+ + Constants.TEMPLATE_DIRECTORY;
+ String resourceUrl = vaadinDirectory + pathWithoutPrefix;
+ content = getResourceStream(service, resourceUrl);
+ }
if (content != null) {
getLogger().debug(
"Found sources from the tag '{}' in the template '{}'", tag,
@@ -196,67 +199,19 @@ protected String getSourcesFromTemplate(String tag, String url) {
return null;
}
- private String getSourcesFromStats(VaadinService service, String url)
- throws IOException {
- templateSourceslock.lock();
- try {
- if (isStatsFileReadNeeded(service)) {
- String content = FrontendUtils.getStatsContent(service);
- if (content != null) {
- resetCache(content);
- }
- }
- if (!cache.containsKey(url) && jsonStats != null) {
- cache.put(url, BundleLitParser.getSourceFromStatistics(url,
- jsonStats, service));
+ private InputStream getResourceStream(VaadinService service, String url) {
+ ResourceProvider resourceProvider = service.getContext()
+ .getAttribute(Lookup.class).lookup(ResourceProvider.class);
+ URL resourceUrl = resourceProvider.getApplicationResource(url);
+ if (resourceUrl != null) {
+ try {
+ return resourceUrl.openStream();
+ } catch (IOException e) {
+ getLogger().warn("Exception accessing resource " + resourceUrl,
+ e);
}
- return cache.get(url);
- } finally {
- templateSourceslock.unlock();
- }
- }
-
- /**
- * Check status to see if stats.json needs to be loaded and parsed.
- *
- * Always load if jsonStats is null, never load again when we have a bundle
- * as it never changes, always load a new stats if the hash has changed and
- * we do not have a bundle.
- *
- * @param service
- * the Vaadin service.
- * @return {@code true} if we need to re-load and parse stats.json, else
- * {@code false}
- */
- private boolean isStatsFileReadNeeded(VaadinService service)
- throws IOException {
- assert templateSourceslock.isHeldByCurrentThread();
- DeploymentConfiguration config = service.getDeploymentConfiguration();
- if (jsonStats == null) {
- return true;
- } else if (usesBundleFile(config)) {
- return false;
}
- return !jsonStats.get("hash").asString()
- .equals(FrontendUtils.getStatsHash(service));
- }
-
- /**
- * Check if we are running in a mode without dev server and using a pre-made
- * bundle file.
- *
- * @param config
- * deployment configuration
- * @return true if production mode or disabled dev server
- */
- private boolean usesBundleFile(DeploymentConfiguration config) {
- return config.isProductionMode() && !config.enableDevServer();
- }
-
- private void resetCache(String fileContents) {
- assert templateSourceslock.isHeldByCurrentThread();
- cache.clear();
- jsonStats = BundleLitParser.parseJsonStatistics(fileContents);
+ return null;
}
private Logger getLogger() {
diff --git a/flow-lit-template/src/test/java/com/vaadin/flow/component/littemplate/internal/LitTemplateParserImplTest.java b/flow-lit-template/src/test/java/com/vaadin/flow/component/littemplate/internal/LitTemplateParserImplTest.java
index 7a68be037fc..4138fd42801 100644
--- a/flow-lit-template/src/test/java/com/vaadin/flow/component/littemplate/internal/LitTemplateParserImplTest.java
+++ b/flow-lit-template/src/test/java/com/vaadin/flow/component/littemplate/internal/LitTemplateParserImplTest.java
@@ -39,9 +39,6 @@
import com.vaadin.flow.server.MockVaadinServletService;
import com.vaadin.flow.server.frontend.FrontendUtils;
-import static com.vaadin.flow.server.Constants.STATISTICS_JSON_DEFAULT;
-import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES;
-
public class LitTemplateParserImplTest {
private MockVaadinServletService service;
@@ -76,10 +73,10 @@ public void init() {
ResourceProvider resourceProvider = service.getContext()
.getAttribute(Lookup.class).lookup(ResourceProvider.class);
- Mockito.when(resourceProvider.getApplicationResource(
- VAADIN_SERVLET_RESOURCES + STATISTICS_JSON_DEFAULT))
- .thenReturn(LitTemplateParserImplTest.class.getResource(
- "/" + VAADIN_SERVLET_RESOURCES + "config/stats.json"));
+ Mockito.when(
+ resourceProvider.getApplicationResource(Mockito.anyString()))
+ .thenAnswer(invoc -> LitTemplateParserImpl.class
+ .getClassLoader().getResource(invoc.getArgument(0)));
}
@Test
@@ -180,9 +177,6 @@ public void getTemplateContent_sourceNotFoundInStatsFile_returnsNull() {
public void getTemplateContent_sourceFileWithFaultyTemplateGetter_returnsNull() {
// If the template getter can not be found it should result in no
// template element children
- Mockito.when(configuration.getStringProperty(Mockito.anyString(),
- Mockito.anyString()))
- .thenReturn(VAADIN_SERVLET_RESOURCES + "config/stats.json");
LitTemplateParser.TemplateData templateContent = LitTemplateParserImpl
.getInstance()
.getTemplateContent(MyFaulty.class, "my-element", service);
@@ -194,9 +188,6 @@ public void getTemplateContent_sourceFileWithFaultyTemplateGetter_returnsNull()
public void getTemplateContent_renderIsDefinedInSuperClass_returnsNull() {
// If the template getter can not be found it should result in no
// template element children
- Mockito.when(configuration.getStringProperty(Mockito.anyString(),
- Mockito.anyString()))
- .thenReturn(VAADIN_SERVLET_RESOURCES + "config/stats.json");
LitTemplateParser.TemplateData templateContent = LitTemplateParserImpl
.getInstance().getTemplateContent(MyFaulty.class,
"my-super-lit-element", service);
@@ -206,9 +197,6 @@ public void getTemplateContent_renderIsDefinedInSuperClass_returnsNull() {
@Test
public void getTemplateContent_nonLocalTemplate_rootElementParsed() {
- Mockito.when(configuration.getStringProperty(Mockito.anyString(),
- Mockito.anyString()))
- .thenReturn(VAADIN_SERVLET_RESOURCES + "config/stats.json");
LitTemplateParser.TemplateData templateContent = LitTemplateParserImpl
.getInstance().getTemplateContent(HelloWorld.class,
HelloWorld.class.getAnnotation(Tag.class).value(),
@@ -223,9 +211,6 @@ public void getTemplateContent_nonLocalTemplate_rootElementParsed() {
@Test
public void getTemplateContent_nonLocalTemplateInTargetFolder_rootElementParsed() {
- Mockito.when(configuration.getStringProperty(Mockito.anyString(),
- Mockito.anyString()))
- .thenReturn(VAADIN_SERVLET_RESOURCES + "config/stats.json");
LitTemplateParser.TemplateData templateContent = LitTemplateParserImpl
.getInstance().getTemplateContent(HelloWorld2.class,
HelloWorld2.class.getAnnotation(Tag.class).value(),
@@ -240,10 +225,6 @@ public void getTemplateContent_nonLocalTemplateInTargetFolder_rootElementParsed(
@Test
public void severalJsModuleAnnotations_theFirstFileDoesNotExist_fileWithContentIsChosen() {
- Mockito.when(configuration.getStringProperty(Mockito.anyString(),
- Mockito.anyString()))
- .thenReturn(VAADIN_SERVLET_RESOURCES + "config/stats.json");
-
LitTemplateParser instance = LitTemplateParserImpl.getInstance();
LitTemplateParser.TemplateData templateContent = instance
.getTemplateContent(BrokenJsModuleAnnotation.class,
@@ -256,10 +237,6 @@ public void severalJsModuleAnnotations_theFirstFileDoesNotExist_fileWithContentI
@Test
public void severalJsModuleAnnotations_parserSelectsByName() {
- Mockito.when(configuration.getStringProperty(Mockito.anyString(),
- Mockito.anyString()))
- .thenReturn(VAADIN_SERVLET_RESOURCES + "config/stats.json");
-
LitTemplateParser instance = LitTemplateParserImpl.getInstance();
LitTemplateParser.TemplateData templateContent = instance
.getTemplateContent(SeveralJsModuleAnnotations.class,
@@ -320,7 +297,7 @@ public class HelloWorld2 extends LitTemplate {
}
@Tag("my-lit-element-view")
- @JsModule("./frontend/non-existant.js")
+ @JsModule("./frontend/non-existent.js")
@JsModule("./frontend/my-lit-element-view.js")
public class BrokenJsModuleAnnotation extends LitTemplate {
}
diff --git a/flow-lit-template/src/test/resources/META-INF/VAADIN/config/stats.json b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/stats.json
deleted file mode 100644
index eadcddbc6c6..00000000000
--- a/flow-lit-template/src/test/resources/META-INF/VAADIN/config/stats.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "hash": "64bb80639ef116681818",
- "assetsByChunkName" :{
- "bundle": "build/vaadin-bundle-1111.cache.js",
- "export": "build/vaadin-export-2222.cache.js"
- },
- "modules": [
- {
- "name": "../node_modules/@vaadin/flow-frontend/src/hello-world-lit.js",
- "source": "// Import an element\nimport { LitElement, html } from 'lit';\n\n// Define an element class\n export class HelloWorld extends LitElement {\n\n // Define the element's template\n render() {\n return html`\n \n
Tag name doesn't match the JS module name
inner
Web components like you, too.
\n `;\n }\n}\n\n// Register the element with the browser\ncustomElements.define('hello-world-lit', HelloWorld);"
- },
- {
- "name": "../target/flow-frontend/src/hello-world2.js",
- "source": "// Import an element\nimport { LitElement, html } from 'lit';\n\n// Define an element class\n export class HelloWorld extends LitElement {\n\n // Define the element's template\n render() {\n return html`\n \n
Tag name doesn't match the JS module name
inner
Web components like you, too.
\n `;\n }\n}\n\n// Register the element with the browser\ncustomElements.define('hello-world-lit', HelloWorld);"
- },
- {
- "name": "./frontend/MyElementFaultyMethods.js",
- "source": "// Import an element\nimport { LitElement, html } from 'lit';\n\n// Define an element class\nexport class MyLitElement extends LitElement {\n\n // Define public API properties\n // Define the element's template\n render() {\n return `\n \n
Web components like you, too.
\n `;\n }\n}\n\n// Register the element with the browser\ncustomElements.define('my-element', MyLitElement);"
- },
- {
- "name": "./frontend/MySuperLitElement.js",
- "source": "// Import an element\nimport { LitElement, html } from 'lit'; \nimport { SimpleLitTemplateShadowRoot } from './MyLitElement.js';\n export class MySuperLitElement extends MyLitElement { createRenderRoot() { return this; }} customElements.define('my-super-lit-element', MySuperLitElement);"
- },
- {
- "id": "./frontend/my-form.ts",
- "name": "./frontend/my-form.ts",
- "source": "import { html, LitElement } from 'lit';\r\nimport { customElement } from 'lit/decorators.js';\r\n// @customElement(\"my-form\")\r\nexport class MyFormElement extends LitElement {\r\n render() {\r\n return html `\n
Hello
\n \n `;\r\n }\r\n}\r\ncustomElements.define(\"my-form\", MyFormElement);\r\n"
- }
- ]
- ,
-
- "chunks" : [
- {
- "modules": [
- {
- "name": "./frontend/MyLitElement.js",
- "source": "// Import an element\nimport { LitElement, html } from 'lit';\n\n// Define an element class\n export class MyLitElement extends LitElement {\n\n // Define the element's template\n render() {\n return html`\n \n
Tag name doesn't match the JS module name
inner
Web components like you, too.
\n `;\n }\n}\n\n// Register the element with the browser\ncustomElements.define('my-element', MyLitElement);"
- }
- ,
- {
- "name": "./frontend/MyGreedyLitElement.js",
- "source": "// Import an element\nimport { LitElement, html } from 'lit';\n\n// Define an element class\n export class MyGreedyLitElement extends LitElement {\n\n // Define the element's template\n render() {\n return html`\n \n
\\`Tag name doesn't match the JS module name
inner
greedy
\n `;}\n static get styles() { return css`:host { background-color: pink } incorrect content`; }\n}\n\n// Register the element with the browser\ncustomElements.define('my-greedy-element', MyGreedyLitElement);"
- }
- ]
- }
- ]
-}
diff --git a/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MyElementFaultyMethods.js b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MyElementFaultyMethods.js
new file mode 100644
index 00000000000..d96d821d7ac
--- /dev/null
+++ b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MyElementFaultyMethods.js
@@ -0,0 +1,24 @@
+// Import an element
+import { LitElement, html } from 'lit';
+
+// Define an element class
+export class MyLitElement extends LitElement {
+
+ // Define public API properties
+ // Define the element's template
+ render() {
+ return `
+
+
Web components like you, too.
+ `;
+ }
+}
+
+// Register the element with the browser
+customElements.define('my-element', MyLitElement);
diff --git a/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MyGreedyLitElement.js b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MyGreedyLitElement.js
new file mode 100644
index 00000000000..88880f52d70
--- /dev/null
+++ b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MyGreedyLitElement.js
@@ -0,0 +1,23 @@
+// Import an element
+import { LitElement, html } from 'lit';
+
+// Define an element class
+export class MyGreedyLitElement extends LitElement {
+
+ // Define the element's template
+ render() {
+ return html`
+
+
\`Tag name doesn't match the JS module name
inner
greedy
+ `;}
+ static get styles() { return css`:host { background-color: pink } incorrect content`; }
+}
+
+// Register the element with the browser
+customElements.define('my-greedy-element', MyGreedyLitElement);
diff --git a/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MyLitElement.js b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MyLitElement.js
new file mode 100644
index 00000000000..56ef44e3da5
--- /dev/null
+++ b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MyLitElement.js
@@ -0,0 +1,23 @@
+// Import an element
+import { LitElement, html } from 'lit';
+
+// Define an element class
+export class MyLitElement extends LitElement {
+
+ // Define the element's template
+ render() {
+ return html`
+
+
Tag name doesn't match the JS module name
inner
Web components like you, too.
+ `;
+ }
+}
+
+// Register the element with the browser
+customElements.define('my-element', MyLitElement);
diff --git a/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MySuperLitElement.js b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MySuperLitElement.js
new file mode 100644
index 00000000000..71d7e2fd981
--- /dev/null
+++ b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/MySuperLitElement.js
@@ -0,0 +1,4 @@
+// Import an element
+import { LitElement, html } from 'lit';
+import { SimpleLitTemplateShadowRoot } from './MyLitElement.js';
+export class MySuperLitElement extends MyLitElement { createRenderRoot() { return this; }} customElements.define('my-super-lit-element', MySuperLitElement);
diff --git a/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/my-form.ts b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/my-form.ts
new file mode 100644
index 00000000000..543c6bb2c21
--- /dev/null
+++ b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/my-form.ts
@@ -0,0 +1,12 @@
+import { html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+// @customElement("my-form")
+export class MyFormElement extends LitElement {
+ render() {
+ return html `
+
Hello
+
+ `;
+ }
+}
+customElements.define("my-form", MyFormElement);
diff --git a/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/src/hello-world-lit.js b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/src/hello-world-lit.js
new file mode 100644
index 00000000000..346e87687e5
--- /dev/null
+++ b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/src/hello-world-lit.js
@@ -0,0 +1,23 @@
+// Import an element
+import { LitElement, html } from 'lit';
+
+// Define an element class
+export class HelloWorld extends LitElement {
+
+ // Define the element's template
+ render() {
+ return html`
+
+
Tag name doesn't match the JS module name
inner
Web components like you, too.
+ `;
+ }
+}
+
+// Register the element with the browser
+customElements.define('hello-world-lit', HelloWorld);
diff --git a/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/src/hello-world2.js b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/src/hello-world2.js
new file mode 100644
index 00000000000..346e87687e5
--- /dev/null
+++ b/flow-lit-template/src/test/resources/META-INF/VAADIN/config/templates/src/hello-world2.js
@@ -0,0 +1,23 @@
+// Import an element
+import { LitElement, html } from 'lit';
+
+// Define an element class
+export class HelloWorld extends LitElement {
+
+ // Define the element's template
+ render() {
+ return html`
+
+
Tag name doesn't match the JS module name
inner
Web components like you, too.
+ `;
+ }
+}
+
+// Register the element with the browser
+customElements.define('hello-world-lit', HelloWorld);
diff --git a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java
index 19bd6ed0eed..29d89bd8340 100644
--- a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java
+++ b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java
@@ -155,12 +155,9 @@ public static void prepareFrontend(PluginAdapterBase adapter)
.withHomeNodeExecRequired(adapter.requireHomeNodeExec())
.setJavaResourceFolder(adapter.javaResourceFolder())
.withProductionMode(adapter.productionMode());
- // If building a jar project copy jar artifact contents now as we
- // might not be able to read files from jar path.
- if (adapter.isJarProject()) {
- builder.copyResources(adapter.getJarFiles());
- }
+ // Copy jar artifact contents in TaskCopyFrontendFiles
+ builder.copyResources(adapter.getJarFiles());
try {
builder.build().execute();
@@ -300,7 +297,7 @@ public static void runNodeUpdater(PluginAdapterBuild adapter)
.enablePackagesUpdate(true)
.useByteCodeScanner(adapter.optimizeBundle())
.withFlowResourcesFolder(flowResourcesFolder)
- .copyResources(jarFiles)
+ .copyResources(jarFiles).copyTemplates(true)
.copyLocalResources(
adapter.frontendResourcesDirectory())
.enableImportsUpdate(true)
diff --git a/flow-polymer-template/src/main/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParser.java b/flow-polymer-template/src/main/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParser.java
index 68f1d85ba91..e8a08451800 100644
--- a/flow-polymer-template/src/main/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParser.java
+++ b/flow-polymer-template/src/main/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParser.java
@@ -19,14 +19,11 @@
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import org.apache.commons.io.FilenameUtils;
-import org.jsoup.UncheckedIOException;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,17 +31,15 @@
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.di.Lookup;
import com.vaadin.flow.di.ResourceProvider;
-import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.internal.AnnotationReader;
import com.vaadin.flow.internal.Pair;
+import com.vaadin.flow.server.Constants;
import com.vaadin.flow.server.DependencyFilter;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.frontend.FrontendUtils;
import com.vaadin.flow.shared.ui.Dependency;
import com.vaadin.flow.shared.ui.LoadMode;
-import elemental.json.JsonObject;
-
/**
* Npm template parser implementation.
*
@@ -75,10 +70,6 @@ public class NpmTemplateParser implements TemplateParser {
private static final TemplateParser INSTANCE = new NpmTemplateParser();
- private final HashMap cache = new HashMap<>();
- private final ReentrantLock lock = new ReentrantLock();
- private JsonObject jsonStats;
-
/**
* The default constructor. Protected in order to prevent direct
* instantiation, but not private in order to allow mocking/overrides for
@@ -117,13 +108,6 @@ public TemplateData getTemplateContent(
String url = dependency.getUrl();
String source = getSourcesFromTemplate(service, tag, url);
- if (source == null) {
- try {
- source = getSourcesFromStats(service, url);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
if (source == null) {
continue;
}
@@ -195,16 +179,20 @@ private boolean dependencyHasTagName(Dependency dependency, String tag) {
*/
protected String getSourcesFromTemplate(VaadinService service, String tag,
String url) {
- Lookup lookup = service.getContext().getAttribute(Lookup.class);
- ResourceProvider resourceProvider = lookup
- .lookup(ResourceProvider.class);
- InputStream content = null;
- try {
- URL appResource = resourceProvider.getApplicationResource(url);
- content = appResource == null ? null : appResource.openStream();
- } catch (IOException exception) {
- getLogger().warn("Coudln't get resource for the template '{}'", url,
- exception);
+ InputStream content = getResourceStream(service, url);
+ if (content == null) {
+ // Attempt to get the sources from dev server, if available
+ content = FrontendUtils.getFrontendFileFromDevModeHandler(service,
+ url);
+ }
+ if (content == null) {
+ // In production builds, template sources are stored in
+ // META-INF/VAADIN/config/templates
+ String pathWithoutPrefix = url.replaceFirst("^\\./", "");
+ String vaadinDirectory = Constants.VAADIN_SERVLET_RESOURCES
+ + Constants.TEMPLATE_DIRECTORY;
+ String resourceUrl = vaadinDirectory + pathWithoutPrefix;
+ content = getResourceStream(service, resourceUrl);
}
if (content != null) {
getLogger().debug(
@@ -215,67 +203,19 @@ protected String getSourcesFromTemplate(VaadinService service, String tag,
return null;
}
- private String getSourcesFromStats(VaadinService service, String url)
- throws IOException {
- try {
- lock.lock();
- if (isStatsFileReadNeeded(service)) {
- String content = FrontendUtils.getStatsContent(service);
- if (content != null) {
- resetCache(content);
- }
+ private InputStream getResourceStream(VaadinService service, String url) {
+ ResourceProvider resourceProvider = service.getContext()
+ .getAttribute(Lookup.class).lookup(ResourceProvider.class);
+ URL resourceUrl = resourceProvider.getApplicationResource(url);
+ if (resourceUrl != null) {
+ try {
+ return resourceUrl.openStream();
+ } catch (IOException e) {
+ getLogger().warn("Exception accessing resource " + resourceUrl,
+ e);
}
- if (!cache.containsKey(url) && jsonStats != null) {
- cache.put(url, BundleParser.getSourceFromStatistics(url,
- jsonStats, service));
- }
- return cache.get(url);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Check status to see if stats.json needs to be loaded and parsed.
- *
- * Always load if jsonStats is null, never load again when we have a bundle
- * as it never changes, always load a new stats if the hash has changed and
- * we do not have a bundle.
- *
- * @param service
- * the Vaadin service.
- * @return {@code true} if we need to re-load and parse stats.json, else
- * {@code false}
- */
- protected boolean isStatsFileReadNeeded(VaadinService service)
- throws IOException {
- assert lock.isHeldByCurrentThread();
- DeploymentConfiguration config = service.getDeploymentConfiguration();
- if (jsonStats == null) {
- return true;
- } else if (usesBundleFile(config)) {
- return false;
}
- return !jsonStats.get("hash").asString()
- .equals(FrontendUtils.getStatsHash(service));
- }
-
- /**
- * Check if we are running in a mode without dev server and using a pre-made
- * bundle file.
- *
- * @param config
- * deployment configuration
- * @return true if production mode or disabled dev server
- */
- private boolean usesBundleFile(DeploymentConfiguration config) {
- return config.isProductionMode() && !config.enableDevServer();
- }
-
- private void resetCache(String fileContents) {
- assert lock.isHeldByCurrentThread();
- cache.clear();
- jsonStats = BundleParser.parseJsonStatistics(fileContents);
+ return null;
}
private Logger getLogger() {
diff --git a/flow-polymer-template/src/main/java/com/vaadin/flow/component/polymertemplate/PolymerTemplate.java b/flow-polymer-template/src/main/java/com/vaadin/flow/component/polymertemplate/PolymerTemplate.java
index e83e4bb9fe5..0ef47c9a755 100644
--- a/flow-polymer-template/src/main/java/com/vaadin/flow/component/polymertemplate/PolymerTemplate.java
+++ b/flow-polymer-template/src/main/java/com/vaadin/flow/component/polymertemplate/PolymerTemplate.java
@@ -24,6 +24,7 @@
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.template.Id;
import com.vaadin.flow.dom.Element;
+import com.vaadin.flow.internal.Template;
import com.vaadin.flow.internal.UsageStatistics;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.templatemodel.TemplateModel;
@@ -55,7 +56,7 @@
*/
@Deprecated
public abstract class PolymerTemplate
- extends AbstractTemplate {
+ extends AbstractTemplate implements Template {
static {
UsageStatistics.markAsUsed("flow/PolymerTemplate", null);
diff --git a/flow-polymer-template/src/test/java/com/vaadin/flow/component/polymertemplate/BundleParserTest.java b/flow-polymer-template/src/test/java/com/vaadin/flow/component/polymertemplate/BundleParserTest.java
index ac0f8126f7f..5e5ede3bb9b 100644
--- a/flow-polymer-template/src/test/java/com/vaadin/flow/component/polymertemplate/BundleParserTest.java
+++ b/flow-polymer-template/src/test/java/com/vaadin/flow/component/polymertemplate/BundleParserTest.java
@@ -1,20 +1,14 @@
package com.vaadin.flow.component.polymertemplate;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.stream.Stream;
-import org.apache.commons.io.IOUtils;
import org.jsoup.nodes.Element;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
-
import com.vaadin.flow.di.Instantiator;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.server.MockVaadinServletService;
@@ -23,27 +17,11 @@
import elemental.json.Json;
import elemental.json.JsonObject;
-import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES;
-
public class BundleParserTest {
- private static final String statsFile = VAADIN_SERVLET_RESOURCES
- + "config/stats.json";
-
- private static JsonObject stats;
-
private MockVaadinServletService service;
private DeploymentConfiguration configuration;
- @BeforeClass
- public static void initClass() throws IOException {
- InputStream stream = BundleParserTest.class.getClassLoader()
- .getResourceAsStream(statsFile);
- String statsFileContents = IOUtils.toString(stream,
- StandardCharsets.UTF_8);
- stats = BundleParser.parseJsonStatistics(statsFileContents);
- }
-
@Before
public void init() {
configuration = Mockito.mock(DeploymentConfiguration.class);
@@ -70,51 +48,6 @@ public void init() {
service.init(instantiator);
}
- @Test
- public void nonLocalTemplate_sourcesShouldBeFound() {
- final String source = BundleParser.getSourceFromStatistics(
- "./src/hello-world.js", stats, service);
- Assert.assertNotNull("Source expected in stats.json", source);
- }
-
- @Test
- public void nonLocalTemplate_sourcesShouldBeFoundInTargetFolder() {
- final String source = BundleParser.getSourceFromStatistics(
- "./src/hello-world2.js", stats, service);
- Assert.assertNotNull("Source expected in stats.json", source);
- }
-
- @Test
- public void nonLocalTemplate_windowsPath_sourcesShouldBeFoundInTargetFolder() {
- Mockito.when(configuration.getFlowResourcesFolder()).thenReturn(
- "target\\" + FrontendUtils.DEFAULT_FLOW_RESOURCES_FOLDER);
- final String source = BundleParser.getSourceFromStatistics(
- "./src/hello-world2.js", stats, service);
- Assert.assertNotNull("Source expected in stats.json", source);
- }
-
- @Test
- public void frontendPrefix_sourcesShouldBeFound() {
- final String source = BundleParser.getSourceFromStatistics(
- "./frontend/src/hello-world.js", stats, service);
- Assert.assertNotNull("Source expected in stats.json", source);
- }
-
- @Test
- public void typeScriptExtension_sourcesShouldBeFound() {
- final String source = BundleParser.getSourceFromStatistics(
- "./frontend/my-form.ts", stats, service);
- Assert.assertNotNull("TypeScript sources expected in stats.json",
- source);
- }
-
- @Test
- public void frontendProtocol_sourcesShouldBeFound() {
- final String source = BundleParser.getSourceFromStatistics(
- "frontend:///src/hello-world.js", stats, service);
- Assert.assertNotNull("Source expected in stats.json", source);
- }
-
@Test
public void startsWithSingleLetterDirector_sourcesShouldNotBeFound() {
// This test exposes a common error in String#replaceFirst (unescaped
diff --git a/flow-polymer-template/src/test/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParserTest.java b/flow-polymer-template/src/test/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParserTest.java
index bc6e40dfddd..7fd52dd408a 100644
--- a/flow-polymer-template/src/test/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParserTest.java
+++ b/flow-polymer-template/src/test/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParserTest.java
@@ -41,9 +41,6 @@
import com.vaadin.flow.server.frontend.FrontendUtils;
import com.vaadin.flow.templatemodel.TemplateModel;
-import static com.vaadin.flow.server.Constants.STATISTICS_JSON_DEFAULT;
-import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES;
-
public class NpmTemplateParserTest {
private MockVaadinServletService service;
@@ -86,7 +83,7 @@ public void init() throws Exception {
}
@Test
- public void should_FindCorrectDataInStats() {
+ public void should_FindCorrectDataInTemplate() {
Mockito.when(configuration.isProductionMode()).thenReturn(true);
TemplateParser instance = NpmTemplateParser.getInstance();
TemplateParser.TemplateData templateContent = instance
@@ -108,14 +105,6 @@ public void should_FindCorrectDataInStats() {
@Test
public void getTemplateContent_polymer2TemplateStyleInsertion_contentParsedCorrectly() {
- ResourceProvider resourceProvider = service.getContext()
- .getAttribute(Lookup.class).lookup(ResourceProvider.class);
- Mockito.when(resourceProvider.getApplicationResource(
- VAADIN_SERVLET_RESOURCES + STATISTICS_JSON_DEFAULT))
- .thenReturn(NpmTemplateParser.class
- .getResource("/" + VAADIN_SERVLET_RESOURCES
- + "config/no-html-template.json"));
-
TemplateParser parser = NpmTemplateParser.getInstance();
TemplateData data = parser.getTemplateContent(
NoHtmlTemplateContent.class, "no-html-template", service);
@@ -128,14 +117,6 @@ public void getTemplateContent_polymer2TemplateStyleInsertion_contentParsedCorre
@Test
public void getTemplateContent_polymer2TemplateStyleInsertion_severalDomModules_correctTemplateContentIsChosen() {
- ResourceProvider resourceProvider = service.getContext()
- .getAttribute(Lookup.class).lookup(ResourceProvider.class);
- Mockito.when(resourceProvider.getApplicationResource(
- VAADIN_SERVLET_RESOURCES + STATISTICS_JSON_DEFAULT))
- .thenReturn(NpmTemplateParser.class
- .getResource("/" + VAADIN_SERVLET_RESOURCES
- + "config/no-html-template.json"));
-
TemplateParser parser = NpmTemplateParser.getInstance();
TemplateData data = parser.getTemplateContent(
SeveralDomModulesTemplateContent.class,
@@ -148,7 +129,7 @@ public void getTemplateContent_polymer2TemplateStyleInsertion_severalDomModules_
}
@Test
- public void shouldnt_UseStats_when_LocalFileTemplateExists() {
+ public void should_use_LocalFileTemplate() {
TemplateParser instance = NpmTemplateParser.getInstance();
TemplateParser.TemplateData templateContent = instance
.getTemplateContent(LikeableView.class, "likeable-element-view",
@@ -187,18 +168,6 @@ public void getTypescriptTemplateContent_templateExists_getTemplateContent() {
@Test(expected = IllegalStateException.class)
public void should_throwException_when_LocalFileNotFound() {
- ResourceProvider resourceProvider = service.getContext()
- .getAttribute(Lookup.class).lookup(ResourceProvider.class);
- Mockito.when(resourceProvider.getApplicationResource(
- VAADIN_SERVLET_RESOURCES + STATISTICS_JSON_DEFAULT))
- .thenReturn(NpmTemplateParser.class
- .getResource("/META-INF/resources/foo-bar.json"));
- TemplateParser instance = NpmTemplateParser.getInstance();
- instance.getTemplateContent(FooView.class, "foo-view", service);
- }
-
- @Test(expected = IllegalStateException.class)
- public void should_throwException_when_ResourceNotFoundInStatsFile() {
TemplateParser instance = NpmTemplateParser.getInstance();
instance.getTemplateContent(FooView.class, "foo-view", service);
}
@@ -248,14 +217,7 @@ public void nonLocalTemplate_shouldParseCorrectly() {
}
@Test
- public void bableStats_shouldAlwaysParseCorrectly() {
- ResourceProvider resourceProvider = service.getContext()
- .getAttribute(Lookup.class).lookup(ResourceProvider.class);
- Mockito.when(resourceProvider.getApplicationResource(
- VAADIN_SERVLET_RESOURCES + STATISTICS_JSON_DEFAULT))
- .thenReturn(NpmTemplateParser.class
- .getResource("/" + VAADIN_SERVLET_RESOURCES
- + "config/babel_stats.json"));
+ public void shouldParseTemplateCorrectly() {
TemplateParser instance = NpmTemplateParser.getInstance();
TemplateParser.TemplateData templateContent = instance
.getTemplateContent(MyComponent.class, "my-component", service);
@@ -308,13 +270,6 @@ public void bableStats_shouldAlwaysParseCorrectly() {
*/
@Test
public void hierarchicalTemplate_templateHasChild_childHasCorrectPosition() {
- ResourceProvider resourceProvider = service.getContext()
- .getAttribute(Lookup.class).lookup(ResourceProvider.class);
- Mockito.when(resourceProvider.getApplicationResource(
- VAADIN_SERVLET_RESOURCES + STATISTICS_JSON_DEFAULT))
- .thenReturn(NpmTemplateParser.class
- .getResource("/" + VAADIN_SERVLET_RESOURCES
- + "config/template-in-template-stats.json"));
TemplateParser instance = NpmTemplateParser.getInstance();
TemplateParser.TemplateData templateContent = instance
.getTemplateContent(ParentTemplate.class, "parent-template",
diff --git a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/babel_stats.json b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/babel_stats.json
deleted file mode 100644
index 782aeae5c76..00000000000
--- a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/babel_stats.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "hash": "e251827e9f087691baf2",
- "modules": [
- {
- "name": "./my-component.js?babel-target=es6",
- "source": "import { PolymerElement } from '@polymer/polymer/polymer-element.js';\nimport { html } from '@polymer/polymer/lib/utils/html-tag.js';\n\nclass MyComponentElement extends PolymerElement {\n static get template() {\n return html`\n \n \n`;\n }\n\n static get is() {\n return 'my-component';\n }\n\n}\n\ncustomElements.define(MyComponentElement.is, MyComponentElement);"
- }
- ]
-}
\ No newline at end of file
diff --git a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/no-html-template.json b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/no-html-template.json
deleted file mode 100644
index 78424661fd6..00000000000
--- a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/no-html-template.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "modules": [
- {
- "name": "./no-html-template.js",
- "source": "/* innerHtml = ``; */ import { PolymerElement } from '@polymer/polymer/polymer-element.js'; const $_documentContainer = document.createElement('template'); $_documentContainer.innerHTML = `
`;\n }\n\n static get is() {\n return 'hello-world';\n }\n\n}\n\ncustomElements.define(HelloWorld.is, HelloWorld);"
- },
- {
- "name": "../target/flow-frontend/src/hello-world2.js",
- "source": "import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';\nimport '@polymer/paper-input/paper-input.js';\n\nclass HelloWorld extends PolymerElement {\n static get template() {\n return html`\n
\n \n \n
[[greeting]]
\n
`;\n }\n\n static get is() {\n return 'hello-world';\n }\n\n}\n\ncustomElements.define(HelloWorld.is, HelloWorld);"
- },
- {
- "name": "./frontend/LikeableElementBrokenHtml.js",
- "source": "// Import an element\nimport '@polymer/paper-checkbox/paper-checkbox.js';\n\n// Import the PolymerElement base class and html helper\nimport {PolymerElement, html} from '@polymer/polymer';\n\n// Define an element class\nclass LikeableElement extends PolymerElement {\n\n // Define public API properties\n static get properties() { return { liked: Boolean }}\n\n // Define the element's template\n static get template() {\n return html`\n \n I like web components!\n\n
Web components like you, too.
\n `;\n }\n}\n\n// Register the element with the browser\ncustomElements.define('likeable-element', LikeableElement);"
- },
- {
- "id": "./frontend/my-form.ts",
- "name": "./frontend/my-form.ts",
- "source": "import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';\n\nclass MyFormElement extends PolymerElement {\n static get template() {\n return html`\n
Hello
\n `;\n }\n\n static get is() {\n return 'my-form';\n }\n\n}\n\ncustomElements.define(MyFormElement.is, MyFormElement);\r\n"
- }
- ]
- ,
-
- "chunks" : [
- {
- "modules": [
- {
- "name": "./frontend/LikeableElement.js",
- "source": "// Import an element\nimport '@polymer/paper-checkbox/paper-checkbox.js';\n\n// Import the PolymerElement base class and html helper\nimport {PolymerElement, html} from '@polymer/polymer';\n\n// Define an element class\nclass LikeableElement extends PolymerElement {\n\n // Define public API properties\n static get properties() { return { liked: Boolean }}\n\n // Define the element's template\n static get template() {\n return html`\n \n
Tag name doesn't match the JS module name
I like web components!\n\n
Web components like you, too.
\n `;\n }\n}\n\n// Register the element with the browser\ncustomElements.define('likeable-element', LikeableElement);"
- }
- ]
- }
- ]
-}
diff --git a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/template-in-template-stats.json b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/template-in-template-stats.json
deleted file mode 100644
index a4dfcb7970a..00000000000
--- a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/template-in-template-stats.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "hash": "a60ab8b9c567ee11738c",
- "modules": [
- {
- "name": "./ParentTemplate.js",
- "source": "import { PolymerElement } from '@polymer/polymer/polymer-element.js';\nimport { html } from '@polymer/polymer/lib/utils/html-tag.js';\nimport './ChildTemplate.js';\n\nclass ParentTemplate extends PolymerElement {\n static get is() {\n return 'parent-template';\n }\n\n static get template() {\n return html`\n
Parent Template
\n
\n
Placeholder
\n \n \n \n
\n \n `;\n }\n\n}\n\ncustomElements.define(ParentTemplate.is, ParentTemplate);"
- }
- ]
-}
\ No newline at end of file
diff --git a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/ParentTemplate.js b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/ParentTemplate.js
new file mode 100644
index 00000000000..c651357c658
--- /dev/null
+++ b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/ParentTemplate.js
@@ -0,0 +1,30 @@
+import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+import { html } from '@polymer/polymer/lib/utils/html-tag.js';
+import './ChildTemplate.js';
+
+class ParentTemplate extends PolymerElement {
+ static get is() {
+ return 'parent-template';
+ }
+
+ static get template() {
+ return html`
+
Parent Template
+
+
Placeholder
+
+
+
+
+
+ `;
+ }
+
+}
+
+customElements.define(ParentTemplate.is, ParentTemplate);
diff --git a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/LikeableElement.js b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/LikeableElement.js
new file mode 100644
index 00000000000..d6daa4abc5e
--- /dev/null
+++ b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/LikeableElement.js
@@ -0,0 +1,31 @@
+// Import an element
+import '@polymer/paper-checkbox/paper-checkbox.js';
+
+// Import the PolymerElement base class and html helper
+import {PolymerElement, html} from '@polymer/polymer';
+
+// Define an element class
+class LikeableElement extends PolymerElement {
+
+ // Define public API properties
+ static get properties() { return { liked: Boolean }}
+
+ // Define the element's template
+ static get template() {
+ return html`
+
+
Tag name doesn't match the JS module name
I like web components!
+
+
Web components like you, too.
+ `;
+ }
+}
+
+// Register the element with the browser
+customElements.define('likeable-element', LikeableElement);
diff --git a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/LikeableElementBrokenHtml.js b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/LikeableElementBrokenHtml.js
new file mode 100644
index 00000000000..a176b99a587
--- /dev/null
+++ b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/LikeableElementBrokenHtml.js
@@ -0,0 +1,30 @@
+// Import an element
+import '@polymer/paper-checkbox/paper-checkbox.js';
+
+// Import the PolymerElement base class and html helper
+import {PolymerElement, html} from '@polymer/polymer';
+
+// Define an element class
+class LikeableElement extends PolymerElement {
+
+ // Define public API properties
+ static get properties() { return { liked: Boolean }}
+
+ // Define the element's template
+ static get template() {
+ return html`
+
+ I like web components!
+
+
Web components like you, too.
+ `;
+ }
+}
+
+// Register the element with the browser
+customElements.define('likeable-element', LikeableElement);
diff --git a/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/my-form.ts b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/my-form.ts
new file mode 100644
index 00000000000..7226de3fdae
--- /dev/null
+++ b/flow-polymer-template/src/test/resources/META-INF/VAADIN/config/templates/frontend/my-form.ts
@@ -0,0 +1,16 @@
+import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
+
+class MyFormElement extends PolymerElement {
+ static get template() {
+ return html`
+
`;
+ }
+
+ static get is() {
+ return 'hello-world';
+ }
+
+}
+
+customElements.define(HelloWorld.is, HelloWorld);
diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/DevModeHandler.java b/flow-server/src/main/java/com/vaadin/flow/internal/DevModeHandler.java
index 04b7d0f6db5..0181dc61c5b 100644
--- a/flow-server/src/main/java/com/vaadin/flow/internal/DevModeHandler.java
+++ b/flow-server/src/main/java/com/vaadin/flow/internal/DevModeHandler.java
@@ -17,6 +17,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
@@ -72,4 +73,11 @@ boolean serveDevModeRequest(HttpServletRequest request,
* Stop the dev-server.
*/
void stop();
+
+ /**
+ * Gets the project root folder.
+ *
+ * @return the project root folder
+ */
+ File getProjectRoot();
}
diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/Template.java b/flow-server/src/main/java/com/vaadin/flow/internal/Template.java
new file mode 100644
index 00000000000..68a24546c91
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/internal/Template.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2000-2021 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.internal;
+
+import java.io.Serializable;
+
+/**
+ * Marker interface for (Lit and Polymer) templates. All frontend files linked
+ * by implementors (with {@link com.vaadin.flow.component.dependency.JsModule})
+ * will be copied to {@code META-INF/VAADIN/config/templates}.
+ *
+ * @author Vaadin Ltd
+ */
+public interface Template extends Serializable {
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/Constants.java b/flow-server/src/main/java/com/vaadin/flow/server/Constants.java
index 2ee7d62ef0e..004d18210f5 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/Constants.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/Constants.java
@@ -225,6 +225,13 @@ public final class Constants implements Serializable {
public static final String STATISTICS_JSON_DEFAULT = Constants.VAADIN_CONFIGURATION
+ "stats.json";
+ /**
+ * Default resource directory to place template sources in. This is used
+ * used for Vite production mode instead of a stats.json file.
+ */
+ public static final String TEMPLATE_DIRECTORY = Constants.VAADIN_CONFIGURATION
+ + "templates/";
+
/**
* Name of the npm main file.
*/
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java
index d94a5d1ad59..0ecceaafc01 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java
@@ -17,6 +17,7 @@
import java.io.ByteArrayInputStream;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
@@ -438,52 +439,6 @@ public static ProcessBuilder createProcessBuilder(List command) {
return processBuilder;
}
- /**
- * Gets the content of the stats.json file produced by webpack.
- *
- * Note: Caches the stats.json when external stats is enabled
- * or stats.json is provided from the class path. To clear the
- * cache use {@link #clearCachedStatsContent(VaadinService)}.
- *
- * @param service
- * the vaadin service.
- * @return the content of the file as a string, null if not found.
- * @throws IOException
- * on error reading stats file.
- */
- public static String getStatsContent(VaadinService service)
- throws IOException {
- DeploymentConfiguration config = service.getDeploymentConfiguration();
- InputStream content = null;
-
- try {
- if (!config.isProductionMode() && config.enableDevServer()) {
- Optional devModeHandler = DevModeHandlerManager
- .getDevModeHandler(service);
- if (!devModeHandler.isPresent()) {
- throw new WebpackConnectionException(
- "DevModeHandlerManager implementation missing. Include the "
- + "com.vaadin:vaadin-dev-server dependency.");
- }
- content = getStatsFromWebpack(devModeHandler.get());
- }
-
- if (config.isStatsExternal()) {
- content = getStatsFromExternalUrl(config.getExternalStatsUrl(),
- service.getContext());
- }
-
- if (content == null) {
- content = getStatsFromClassPath(service);
- }
- return content != null
- ? IOUtils.toString(content, StandardCharsets.UTF_8)
- : null;
- } finally {
- IOUtils.closeQuietly(content);
- }
- }
-
/**
* Clears the stats.json cache within this
* {@link VaadinContext}.
@@ -549,44 +504,6 @@ private static InputStream getFileFromClassPath(VaadinService service,
return stream;
}
- /**
- * Get the latest hash for the stats file in development mode. This is
- * requested from the webpack-dev-server.
- *
- * In production mode and disabled dev server mode an empty string is
- * returned.
- *
- * @param service
- * the Vaadin service.
- * @return hash string for the stats.json file, empty string if none found
- * @throws IOException
- * if an I/O error occurs while creating the input stream.
- */
- public static String getStatsHash(VaadinService service)
- throws IOException {
- Optional devModeHandler = DevModeHandlerManager
- .getDevModeHandler(service);
- if (devModeHandler.isPresent()) {
- HttpURLConnection statsConnection = devModeHandler.get()
- .prepareConnection("/stats.hash", "GET");
- if (statsConnection
- .getResponseCode() != HttpURLConnection.HTTP_OK) {
- throw new WebpackConnectionException(String.format(
- NO_CONNECTION, "getting the stats content hash."));
- }
- return streamToString(statsConnection.getInputStream())
- .replaceAll("\"", "");
- }
-
- return "";
- }
-
- private static InputStream getStatsFromWebpack(
- DevModeHandler devModeHandler) throws IOException {
- return getResourceFromWebpack(devModeHandler, "/stats.json",
- "downloading stats.json");
- }
-
private static InputStream getResourceFromWebpack(
DevModeHandler devModeHandler, String resource,
String exceptionMessage) throws IOException {
@@ -699,6 +616,59 @@ private static InputStream getFileFromDevModeHandler(
.getInputStream();
}
+ /**
+ * Get the contents of a frontend file from the running dev server.
+ *
+ * @param service
+ * the Vaadin service.
+ * @param path
+ * the file path.
+ * @return an input stream for reading the file contents; null if there is
+ * no such file or the dev server is not running.
+ */
+ public static InputStream getFrontendFileFromDevModeHandler(
+ VaadinService service, String path) {
+ Optional devModeHandler = DevModeHandlerManager
+ .getDevModeHandler(service);
+ if (devModeHandler.isPresent()) {
+ try {
+ File frontendFile = resolveFrontendPath(
+ devModeHandler.get().getProjectRoot(), path);
+ return frontendFile == null ? null
+ : new FileInputStream(frontendFile);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Error reading file " + path, e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Looks up the front file at the given path. If the path starts with
+ * {@code ./}, first look in {@code projectRoot/frontend}, then in
+ * {@code projectRoot/node_modules/@vaadin/flow-frontend}. If the path does
+ * not start with {@code ./}, look in {@code node_modules} instead.
+ *
+ * @param projectRoot
+ * the project root folder.
+ * @param path
+ * the file path.
+ * @return an existing {@link File} , or null if the file doesn't exist.
+ */
+ public static File resolveFrontendPath(File projectRoot, String path) {
+ File localFrontendFolder = new File(projectRoot,
+ FrontendUtils.FRONTEND);
+ File nodeModulesFolder = new File(projectRoot, NODE_MODULES);
+ File flowFrontendFolder = new File(nodeModulesFolder,
+ "@vaadin/" + DEFAULT_FLOW_RESOURCES_FOLDER);
+ List candidateParents = path.startsWith("./")
+ ? Arrays.asList(localFrontendFolder, flowFrontendFolder)
+ : Arrays.asList(nodeModulesFolder, localFrontendFolder,
+ flowFrontendFolder);
+ return candidateParents.stream().map(parent -> new File(parent, path))
+ .filter(File::exists).findFirst().orElse(null);
+ }
+
/**
* Load the asset chunks from stats.json. We will only read the
* file until we have reached the assetsByChunkName json and return that as
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java
index b0e6e769b93..17643fb5c0f 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java
@@ -110,6 +110,8 @@ public static class Builder implements Serializable {
private boolean requireHomeNodeExec;
+ private boolean copyTemplates = false;
+
/**
* Directory for npm and folders and files.
*/
@@ -348,6 +350,20 @@ public Builder copyResources(Set jars) {
return this;
}
+ /**
+ * Sets whether copy templates to
+ * {@code META-INF/VAADIN/config/templates}.
+ *
+ * @param copyTemplates
+ * whether to copy templates
+ *
+ * @return the builder
+ */
+ public Builder copyTemplates(boolean copyTemplates) {
+ this.copyTemplates = copyTemplates;
+ return this;
+ }
+
/**
* Sets whether to collect and package
* {@link com.vaadin.flow.component.WebComponentExporter} dependencies.
@@ -675,7 +691,8 @@ protected FeatureFlags getFeatureFlags() {
TaskUpdateWebpack.class,
TaskUpdateVite.class,
TaskUpdateImports.class,
- TaskUpdateThemeImport.class
+ TaskUpdateThemeImport.class,
+ TaskCopyTemplateFiles.class
));
// @formatter:on
@@ -803,6 +820,11 @@ private NodeTasks(Builder builder) {
frontendDependencies.getThemeDefinition(),
builder.frontendDirectory, builder.fusionClientAPIFolder));
}
+
+ if (builder.copyTemplates) {
+ commands.add(new TaskCopyTemplateFiles(classFinder,
+ builder.npmFolder, builder.resourceOutputDirectory));
+ }
}
private void addBootstrapTasks(Builder builder) {
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskCopyTemplateFiles.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskCopyTemplateFiles.java
new file mode 100644
index 00000000000..719c2e49f2f
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskCopyTemplateFiles.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2000-2021 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.server.frontend;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.vaadin.flow.component.dependency.JsModule;
+import com.vaadin.flow.internal.Template;
+import com.vaadin.flow.server.Constants;
+import com.vaadin.flow.server.ExecutionFailedException;
+import com.vaadin.flow.server.frontend.scanner.ClassFinder;
+
+/**
+ * Copies template files to the target folder so as to be available for parsing
+ * at runtime in production mode.
+ *
+ * For internal use only. May be renamed or removed in a future release.
+ */
+public class TaskCopyTemplateFiles implements FallibleCommand {
+
+ private final ClassFinder classFinder;
+ private final File projectDirectory;
+ private final File resourceOutputDirectory;
+
+ TaskCopyTemplateFiles(ClassFinder classFinder, File projectDirectory,
+ File resourceOutputDirectory) {
+ this.classFinder = classFinder;
+ this.projectDirectory = projectDirectory;
+ this.resourceOutputDirectory = resourceOutputDirectory;
+ }
+
+ @Override
+ public void execute() throws ExecutionFailedException {
+ Set> classes = new HashSet<>();
+ classes.addAll(classFinder.getSubTypesOf(Template.class));
+ for (Class> clazz : classes) {
+ for (JsModule jsmAnnotation : clazz
+ .getAnnotationsByType(JsModule.class)) {
+ String path = jsmAnnotation.value();
+ File source = FrontendUtils
+ .resolveFrontendPath(projectDirectory, path);
+ if (source == null) {
+ throw new ExecutionFailedException(
+ "Unable to locate file " + path);
+ }
+ File templateDirectory = new File(resourceOutputDirectory,
+ Constants.TEMPLATE_DIRECTORY);
+ File target = new File(templateDirectory, path).getParentFile();
+ target.mkdirs();
+ try {
+ FileUtils.copyFileToDirectory(source, target);
+ } catch (IOException e) {
+ throw new ExecutionFailedException(e);
+ }
+ }
+ }
+ }
+
+ Logger log() {
+ return LoggerFactory.getLogger(getClass());
+ }
+}
diff --git a/flow-server/src/main/resources/webpack.generated.js b/flow-server/src/main/resources/webpack.generated.js
index e0a62c4740d..7a4d34e57c3 100644
--- a/flow-server/src/main/resources/webpack.generated.js
+++ b/flow-server/src/main/resources/webpack.generated.js
@@ -236,15 +236,9 @@ module.exports = {
devServer: {
hot: false, // disable HMR
client: false, // disable wds client as we handle reloads and errors better
- // webpack-dev-server serves ./ , webpack-generated, and java webapp
+ // webpack-dev-server serves ./, webpack-generated, and java webapp
static: [outputFolder, path.resolve(__dirname, 'src', 'main', 'webapp')],
onAfterSetupMiddleware: function (devServer) {
- devServer.app.get(`/stats.json`, function (req, res) {
- res.json(stats);
- });
- devServer.app.get(`/stats.hash`, function (req, res) {
- res.json(stats.hash.toString());
- });
devServer.app.get(`/assetsByChunkName`, function (req, res) {
res.json(stats.assetsByChunkName);
});
diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendUtilsTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendUtilsTest.java
index bd4be8caded..34c8303fd35 100644
--- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendUtilsTest.java
+++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendUtilsTest.java
@@ -289,42 +289,6 @@ public void parseManifestJson_returnsValidPaths() {
manifestPaths.contains("/index.html"));
}
- @Test
- public void getStatsContent_getStatsFromClassPath_delegateToGetApplicationResource()
- throws IOException {
- VaadinServletService service = mockServletService();
-
- ResourceProvider provider = mockResourceProvider(service);
-
- FrontendUtils.getStatsContent(service);
-
- VaadinServlet servlet = service.getServlet();
-
- Mockito.verify(provider).getApplicationResource("foo");
- }
-
- @Test
- public void getStatsContent_getStatsFromDevServerWithNoImplementation_throwsException() {
- VaadinServletService service = mockServletService();
-
- DeploymentConfiguration config = Mockito
- .mock(DeploymentConfiguration.class);
-
- Mockito.when(service.getDeploymentConfiguration()).thenReturn(config);
-
- Mockito.when(config.isProductionMode()).thenReturn(false);
- Mockito.when(config.enableDevServer()).thenReturn(true);
-
- WebpackConnectionException exception = Assert.assertThrows(
- WebpackConnectionException.class,
- () -> FrontendUtils.getStatsContent(service));
-
- Assert.assertEquals(
- "DevModeHandlerManager implementation missing. Include "
- + "the com.vaadin:vaadin-dev-server dependency.",
- exception.getMessage());
- }
-
@Test
public void getStatsAssetsByChunkName_getStatsFromClassPath_delegateToGetApplicationResource()
throws IOException {
@@ -339,54 +303,6 @@ public void getStatsAssetsByChunkName_getStatsFromClassPath_delegateToGetApplica
Mockito.verify(provider).getApplicationResource("foo");
}
- @Test
- public void getStatsContent_getStatsFromClassPath_populatesStatsCache()
- throws IOException, ServiceException {
- VaadinService service = setupStatsAssetMocks("ValidStats.json");
-
- assertNull("Stats cache should not be present",
- service.getContext().getAttribute(CACHE_KEY));
-
- // Populates cache
- FrontendUtils.getStatsContent(service);
-
- assertNotNull("Stats cache should be created",
- service.getContext().getAttribute(CACHE_KEY));
- }
-
- @Test // #10893
- public void newLineCharacterInJsonStats_readStreamIsJsonParsable()
- throws IOException, ServiceException {
- VaadinService service = setupStatsAssetMocks(
- "specialCharacterValue.json");
-
- assertNull("Stats cache should not be present",
- service.getContext().getAttribute(CACHE_KEY));
-
- // Load file from classpath and populate cache
- String statsContent = FrontendUtils.getStatsContent(service);
-
- try {
- // Json parsing should not throw for loaded stats.
- Json.parse(statsContent);
- } catch (JsonException jsonException) {
- Assert.fail("Json loaded from class path was not parsable json");
- }
-
- assertNotNull("Stats cache should be created",
- service.getContext().getAttribute(CACHE_KEY));
-
- // Load cached stats.
- statsContent = FrontendUtils.getStatsContent(service);
-
- try {
- // Json parsing should not throw for cached stats.
- Json.parse(statsContent);
- } catch (JsonException jsonException) {
- Assert.fail("Json loaded from cache was not parsable json");
- }
- }
-
@Test
public void getStatsAssetsByChunkName_getStatsFromClassPath_populatesStatsCache()
throws IOException, ServiceException {
@@ -413,7 +329,7 @@ public void clearCachedStatsContent_clearsCache()
FrontendUtils.clearCachedStatsContent(service);
// Populates cache
- FrontendUtils.getStatsContent(service);
+ FrontendUtils.getStatsAssetsByChunkName(service);
// Clears cache
FrontendUtils.clearCachedStatsContent(service);
diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksExecutionTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksExecutionTest.java
index 0dfd08afdde..5c17434030e 100644
--- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksExecutionTest.java
+++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksExecutionTest.java
@@ -32,6 +32,7 @@ public void init() throws Exception {
NodeTasks.Builder builder = new NodeTasks.Builder(
Mockito.mock(Lookup.class), null, TARGET);
builder.useV14Bootstrap(true);
+ builder.withProductionMode(false);
nodeTasks = builder.build();
diff --git a/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java b/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java
index 7391fc85f40..686703f3cd8 100644
--- a/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java
+++ b/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java
@@ -222,6 +222,7 @@ protected Stream getExcludedPatterns() {
"com\\.vaadin\\.flow\\.server\\.frontend\\.TaskInstallWebpackPlugins",
"com\\.vaadin\\.flow\\.server\\.frontend\\.TaskUpdateThemeImport",
"com\\.vaadin\\.flow\\.server\\.frontend\\.EndpointGeneratorTaskFactory",
+ "com\\.vaadin\\.flow\\.server\\.frontend\\.TaskCopyTemplateFiles",
// Node downloader classes
"com\\.vaadin\\.flow\\.server\\.frontend\\.installer\\.DefaultArchiveExtractor",
diff --git a/flow-tests/test-frontend/addon-with-templates/pom.xml b/flow-tests/test-frontend/addon-with-templates/pom.xml
new file mode 100644
index 00000000000..09f52ee41b1
--- /dev/null
+++ b/flow-tests/test-frontend/addon-with-templates/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+
+
+ com.vaadin
+ test-frontend
+ 9.0-SNAPSHOT
+
+
+ addon-with-templates
+ Add-on containing a Lit template-based component
+
+
+
+
+ com.vaadin
+ flow-bom
+ ${project.version}
+ pom
+ import
+
+
+
+
+
+
+ com.vaadin
+ flow-server
+
+
+ com.vaadin
+ flow-lit-template
+
+
+
+
+
+
diff --git a/flow-tests/test-frontend/addon-with-templates/src/main/java/org/vaadin/example/addon/AddonLitComponent.java b/flow-tests/test-frontend/addon-with-templates/src/main/java/org/vaadin/example/addon/AddonLitComponent.java
new file mode 100644
index 00000000000..d01fc160ad5
--- /dev/null
+++ b/flow-tests/test-frontend/addon-with-templates/src/main/java/org/vaadin/example/addon/AddonLitComponent.java
@@ -0,0 +1,21 @@
+package org.vaadin.example.addon;
+
+import com.vaadin.flow.component.Tag;
+import com.vaadin.flow.component.dependency.JsModule;
+import com.vaadin.flow.component.html.Span;
+import com.vaadin.flow.component.littemplate.LitTemplate;
+import com.vaadin.flow.component.template.Id;
+
+@Tag(AddonLitComponent.TAG)
+@JsModule("./AddonLitComponent.ts")
+public class AddonLitComponent extends LitTemplate {
+
+ public static final String TAG = "addon-lit-component";
+
+ @Id("label")
+ private Span label;
+
+ public void setLabel(String value) {
+ label.setText(value);
+ }
+}
diff --git a/flow-tests/test-frontend/addon-with-templates/src/main/resources/META-INF/frontend/AddonLitComponent.ts b/flow-tests/test-frontend/addon-with-templates/src/main/resources/META-INF/frontend/AddonLitComponent.ts
new file mode 100644
index 00000000000..8487dea0311
--- /dev/null
+++ b/flow-tests/test-frontend/addon-with-templates/src/main/resources/META-INF/frontend/AddonLitComponent.ts
@@ -0,0 +1,12 @@
+import { html, LitElement } from 'lit';
+
+class AddonLitComponent extends LitElement {
+ render() {
+ return html`