Skip to content

Commit

Permalink
Doc
Browse files Browse the repository at this point in the history
  • Loading branch information
ia3andy committed Aug 10, 2023
1 parent 7f5444c commit 9c0c865
Show file tree
Hide file tree
Showing 27 changed files with 323 additions and 142 deletions.
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@ Create full-stack web apps and components with this Quarkus extension. It offers

No need to install NodeJs, it relies on a Java wrapped version of [esbuild](https://esbuild.github.io/) to bundle scripts (js, ts, jsx, tsx) and styles (css, scss, sass) and make them easily available in the templates.

* [*] Production build
* [*] Awesome Dev experience
* [*] Integrated with NPM dependencies through [mvnpm](https://mvnpm.org) or [webjars](https://www.webjars.org/).
* [*] Server Side Web Components (Qute template + Script + Style)
* [x] Production build
* [x] Awesome Dev experience
* [x] Integrated with NPM dependencies through [mvnpm](https://mvnpm.org) or [webjars](https://www.webjars.org/).
* [x] Server Side Web Components (Qute template + Script + Style)

### Examples


- https://github.com/ia3andy/quarkus-bundler-react (quarkus, web-bundler, react, react-bootstrap)
- https://github.com/ia3andy/renotes (quarkus, web-bundler, renarde, htmx)
- https://github.com/ia3andy/web-bundler-jquery (quarkus, web-bundler, jquery, bootstrap, bootstrap-icons)
- https://github.com/ia3andy/bundler-gradle-jquery (quarkus, web-bundler, gradle, jquery, bootstrap, bootstrap-icons)
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
public class ConfiguredEntryPoint implements WebBundlerConfig.EntryPointConfig {

private final String id;
private final String entryPointKey;
private final String key;
private final String dir;

public ConfiguredEntryPoint(String dir, String id, String entryPointKey) {
public ConfiguredEntryPoint(String dir, String id, String key) {
this.dir = dir;
this.id = id;
this.entryPointKey = entryPointKey;
this.key = key;
}

@Override
public boolean enabled() {
return true;
}

@Override
Expand All @@ -21,8 +26,8 @@ public Optional<String> dir() {
}

@Override
public Optional<String> entryPointKey() {
return Optional.of(entryPointKey);
public Optional<String> key() {
return Optional.of(key);
}

public String id() {
Expand All @@ -36,12 +41,12 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass())
return false;
ConfiguredEntryPoint that = (ConfiguredEntryPoint) o;
return Objects.equals(id, that.id) && Objects.equals(entryPointKey, that.entryPointKey)
return Objects.equals(id, that.id) && Objects.equals(key, that.key)
&& Objects.equals(dir, that.dir);
}

@Override
public int hashCode() {
return Objects.hash(id, entryPointKey, dir);
return Objects.hash(id, key, dir);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,20 @@ void collect(ApplicationArchivesBuildItem applicationArchives,

final Map<String, List<BundleWebAsset>> bundleAssets = new HashMap<>();
for (Map.Entry<String, EntryPointConfig> e : entryPointsConfig.entrySet()) {
final String entryPointKey = e.getValue().effectiveEntryPointKey(e.getKey());
bundleAssets.putIfAbsent(entryPointKey, new ArrayList<>());
final String dirFromWebRoot = config.fromWebRoot(e.getValue().effectiveDir(e.getKey()));
final List<WebAsset> assets = resourcesScanner.scan(dirFromWebRoot, SCRIPTS.glob(), config.charset());
final Optional<WebAsset> entryPoint = assets.stream()
.filter(w -> w.resourceName().startsWith(join(dirFromWebRoot, "index.")))
.findAny();
for (WebAsset webAsset : assets) {
BundleType bundleType = entryPoint
.map(ep -> webAsset.equals(ep) ? BundleType.ENTRYPOINT : BundleType.MANUAL)
.orElse(BundleType.AUTO);
bundleAssets.get(entryPointKey).add(new BundleWebAsset(webAsset, bundleType));
if (e.getValue().enabled()) {
final String entryPointKey = e.getValue().effectiveKey(e.getKey());
bundleAssets.putIfAbsent(entryPointKey, new ArrayList<>());
final String dirFromWebRoot = config.fromWebRoot(e.getValue().effectiveDir(e.getKey()));
final List<WebAsset> assets = resourcesScanner.scan(dirFromWebRoot, SCRIPTS.glob(), config.charset());
final Optional<WebAsset> entryPoint = assets.stream()
.filter(w -> w.resourceName().startsWith(join(dirFromWebRoot, "index.")))
.findAny();
for (WebAsset webAsset : assets) {
BundleType bundleType = entryPoint
.map(ep -> webAsset.equals(ep) ? BundleType.ENTRYPOINT : BundleType.MANUAL)
.orElse(BundleType.AUTO);
bundleAssets.get(entryPointKey).add(new BundleWebAsset(webAsset, bundleType));
}
}
}
final WebAssetsLookupDevContext context = new WebAssetsLookupDevContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ interface PresetsConfig {
* Configuration preset to allow defining the web app with scripts and styles to bundle.
* - {web-root}/app/**\/*
*
* If an index.js/ts is detected, it will be used as entrypoint for your app.
* If not found the entrypoint will be auto-generated with all the files in the app directory.
* If an index.js/ts is detected, it will be used as entry point for your app.
* If not found the entry point will be auto-generated with all the files in the app directory.
*
* => processed and added to bundled/[key].js and bundled/[key].css (key is "main" by default)
* => processed and added to static/[key].js and static/[key].css (key is "main" by default)
*/
PresetConfig app();

Expand All @@ -79,7 +79,7 @@ interface PresetsConfig {
* - /{web-root}/components/[name]/[name].scss/css
* - /{web-root}/components/[name]/[name].html (Qute tag)
*
* => processed and added to bundled/[key].js and bundled/[key].css (key is "main" by default)
* => processed and added to static/[key].js and static/[key].css (key is "main" by default)
*/
PresetConfig components();

Expand Down Expand Up @@ -115,24 +115,33 @@ interface WebDependenciesConfig {
}

interface EntryPointConfig {

/**
* Enable or disable this entry point.
* You can use this to use the map key as key and dir for this entry point.
*/
@WithParentName
@WithDefault("true")
boolean enabled();

/**
* The directory for this entrypoint under the web root.
* The directory for this entry point under the web root.
* By default, it will use the bundle map key.
*/
Optional<String> dir();

/**
* The key for this entrypoint
* The key for this entry point
* By default, it will use the bundle map key.
*/
Optional<String> entryPointKey();
Optional<String> key();

default String effectiveDir(String mapKey) {
return dir().filter(not(String::isBlank)).orElse(effectiveEntryPointKey(mapKey));
return dir().filter(not(String::isBlank)).orElse(effectiveKey(mapKey));
}

default String effectiveEntryPointKey(String mapKey) {
return entryPointKey().filter(not(String::isBlank)).orElse(mapKey);
default String effectiveKey(String mapKey) {
return key().filter(not(String::isBlank)).orElse(mapKey);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@
import io.quarkiverse.web.bundler.deployment.items.WebDependenciesBuildItem;
import io.quarkiverse.web.bundler.deployment.staticresources.GeneratedStaticResourceBuildItem;
import io.quarkiverse.web.bundler.deployment.staticresources.GeneratedStaticResourceBuildItem.WatchMode;
import io.quarkiverse.web.bundler.runtime.WebBundlerBuild;
import io.quarkiverse.web.bundler.runtime.Bundled;
import io.quarkiverse.web.bundler.runtime.WebBundlerBuildRecorder;
import io.quarkiverse.web.bundler.runtime.qute.BundleTag;
import io.quarkiverse.web.bundler.runtime.qute.WebBundlerQuteContextRecorder;
import io.quarkiverse.web.bundler.runtime.qute.WebBundlerQuteContextRecorder.WebBundlerQuteContext;
import io.quarkiverse.web.bundler.runtime.qute.WebBundlerQuteEngineObserver;
Expand Down Expand Up @@ -251,18 +250,20 @@ void initQuteTags(
syntheticBeans.produce(SyntheticBeanBuildItem.configure(WebBundlerQuteContext.class)
.supplier(recorder.createContext(tags, templates))
.done());
additionalBeans.produce(new AdditionalBeanBuildItem(BundleTag.class));
}

@BuildStep
@Record(STATIC_INIT)
void initBundlerBuild(BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
void initBundler(
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
GeneratedBundleBuildItem generatedBundle,
WebBundlerBuildRecorder recorder) {
final Map<String, String> bundle = generatedBundle != null ? generatedBundle.getBundle() : Map.of();
syntheticBeans.produce(SyntheticBeanBuildItem.configure(WebBundlerBuild.class)
syntheticBeans.produce(SyntheticBeanBuildItem.configure(Bundled.Mapping.class)
.supplier(recorder.createContext(bundle))
.done());
additionalBeans.produce(new AdditionalBeanBuildItem(Bundled.class));
}

private static void makeWebAssetPublic(
Expand Down
8 changes: 7 additions & 1 deletion docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
* xref:index.adoc[Quarkus Web Bundler]
include::./includes/attributes.adoc[]
:config-file: application.properties

* xref:index.adoc[Getting started]
* xref:main-concepts.adoc[Main Concepts]
* xref:advanced-guides.adoc[Advanced Guides]
* xref:examples.adoc[Examples]
* xref:config-reference.adoc[Config Reference]
117 changes: 111 additions & 6 deletions docs/modules/ROOT/pages/advanced-guides.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,36 @@

include::./includes/attributes.adoc[]

== Web Root

The Web Root is `src/main/resources/web`, this is where the Web Bundler will look for stuff to bundle and serve.

== Public

Files in `src/main/resources/web/public` will be served statically.

== Bundling

== What is bundled
=== Presets

The Web Bundler is pre-configured with presets to make it easy to use out of the box.

==== App

Directory `src/main/resources/web/app` is destined to contain the scripts and styles for your app.

[#server-side-web-components]
==== Server Side Web Components (Qute)

This is not always needed but if you need to add specific script and/or style to your {quarkus-guides-url}/qute-reference#user_tags[Qute tags] (Server Side Web Components). This preset will help you do it elegantly.

By convention, your component will be defined in `src/main/resources/web/components/[name]/[name].js,ts,css,scss,html`. The scripts and styles will be bundled, the html template will be usable as a {quarkus-guides-url}/qute-reference#user_tags[Qute tag].

The Web Bundler will automatically generate an entrypoint index from all the scripts and styles found in the directory.
This way all your components scripts/styles will be bundled and you tags will be available in your templates.

=== What is bundled

The Web Bundler will automatically generate an index from all the scripts and styles found in the bundle directory.

You can provide an `index.js,ts,jsx,tsx` and manually import what's needed. Example:

Expand All @@ -19,11 +44,91 @@ import './my-style.scss'

NOTE: The Web Bundler will take care of the fonts and images imported in your script (serve and load).

== Multiple entrypoints
=== Split the bundle

The web-bundler can be configured to bundle multiple entrypoints. Let's say you want an app with different pages with different scripts and styles, you can split the bundle in named output and a common for all your pages.
The Web Bundler can be configured to split the resulting bundle. This is perfect if you create an app with different pages using different scripts, libraries and styles.

[#server-side-web-components]
== Server Side Web (Qute) Components
Shared code is split off into a separate file. That way if the user first browses to one page and then to another page, they don't have to download all of the JavaScript for the second page from scratch if the shared part has already been downloaded and cached by their browser. The name of the shared code is `chunk`.

By default the Web Bundler will **NOT** split the code and bundle all into `main`. You can configure splitting in the configuration:

.application.properties
[source,properties]
----
quarkus.web-bundler.bundle.page-1=true // <1>
quarkus.web-bundler.bundle.page-2=true // <2>
----
<1> Bundle `src/main/resources/web/page-1/...`
<2> Bundle `src/main/resources/web/page-2/...`

You may also split the bundle for the `app` and the `component` presets (by default they will be bundled together in `main`):
.application.properties
[source,properties]
----
quarkus.web-bundler.presets.app.key=app // <1>
quarkus.web-bundler.presets.components.key=components // <2>
----
<1> The app will be bundled in `app-[hash].js`
<2> The components will be bundled in `components-[hash].js`

== Bundled Files

In production, it is a good practise to have a hash inserted in the scripts and styles file names (E.g.: `main-XKHKUJNQ.js`) to differentiate builds. This way they can be cached without a risk of missing the most recent builds.

In the Web Bundler, we also do it in dev mode, this way the app is as close as possible to production. You won't have surprise when deploying a new version of your application.

To make it easy there are several ways to resolve the bundled files from the templates and the code.

[#bundle-tag]
=== {#bundle /} tag

From any Qute template you can use the `{#bundle /}` tag. examples:

[source,html]
----
{#bundle /}
Output:
<script type="text/javascript" src="/static/main-[hash].js"></script>
<link rel="stylesheet" media="screen" href="/static/main-[hash].css">
{#bundle key="components"/}
Output:
<script type="text/javascript" src="/static/components-[hash].js"></script>
<link rel="stylesheet" media="screen" href="/static/components-[hash].css">
{#bundle tag="script"/}
Output:
<script type="text/javascript" src="/static/main-[hash].js"></script>
{#bundle tag="style"/}
Output:
<link rel="stylesheet" media="screen" href="/static/main-[hash].css">
{#bundle key="components" tag="script"/}
Output:
<script type="text/javascript" src="/static/components-[hash].js"></script>
----

=== `Bundled` class

This can be injected in the code:

[source,java]
----
@Inject
Bundled bundled;
...
System.out.println(bundled.resolveScript("main"));
System.out.println(bundled.resolveStyles("main"));
----


or in a Qute template:
[source,html]
----
{inject:bundled.resolveScript("main")}
{inject:bundled.resolveStyle("main")}
----
9 changes: 9 additions & 0 deletions docs/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
= Quarkus image:logo.svg[width=25em] Web Bundler - Examples

include::./includes/attributes.adoc[]

- https://github.com/ia3andy/quarkus-blast[Quarkus Blast]: Web Bundler, Renarde, htmx, hyperscript, Bootstrap
- https://github.com/ia3andy/quarkus-bundler-react[React]: Web Bundler, React, React Bootstrap
- https://github.com/ia3andy/renotes[Renotes]: Web Bundler, Renarde, htmx, Bootstrap
- https://github.com/ia3andy/web-bundler-jquery[jQuery]: Web Bundler, jQuery, Bootstrap
- https://github.com/ia3andy/bundler-gradle-jquery[Gradle jQuery]: Gradle, Web Bundler, jQuery, Bootstrap
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/includes/attributes.adoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
:project-version: 1.0.0-RC1
:project-version: 1.0.0-CR2

:examples-dir: ./../examples/
Loading

0 comments on commit 9c0c865

Please sign in to comment.