io.quarkiverse.web.bundler.deployment.BundlingProcessor Maven / Gradle / Ivy
package io.quarkiverse.web.bundler.deployment;
import static io.quarkiverse.web.bundler.deployment.StaticWebAssetsProcessor.makePublic;
import static io.quarkiverse.web.bundler.deployment.items.BundleWebAsset.BundleType.GENERATED_ENTRY_POINT;
import static io.quarkiverse.web.bundler.deployment.util.PathUtils.*;
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
import java.util.stream.Stream;
import org.jboss.logging.Logger;
import io.mvnpm.esbuild.Bundler;
import io.mvnpm.esbuild.model.*;
import io.quarkiverse.web.bundler.deployment.items.*;
import io.quarkiverse.web.bundler.deployment.web.GeneratedWebResourceBuildItem;
import io.quarkiverse.web.bundler.deployment.web.GeneratedWebResourceBuildItem.SourceType;
import io.quarkiverse.web.bundler.runtime.Bundle;
import io.quarkiverse.web.bundler.runtime.BundleRedirectHandlerRecorder;
import io.quarkiverse.web.bundler.runtime.WebBundlerBuildRecorder;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
class BundlingProcessor {
private static final Logger LOGGER = Logger.getLogger(BundlingProcessor.class);
@BuildStep(onlyIfNot = IsDevelopment.class)
void bundle(WebBundlerConfig config,
ReadyForBundlingBuildItem readyForBundling,
BuildProducer staticResourceProducer,
BuildProducer generatedBundleProducer,
BuildProducer generatedEntryPointProducer) {
if (readyForBundling == null) {
return;
}
bundleAndProcess(config, readyForBundling, staticResourceProducer, generatedBundleProducer,
generatedEntryPointProducer);
}
static BundleResult bundleAndProcess(WebBundlerConfig config, ReadyForBundlingBuildItem readyForBundling,
BuildProducer staticResourceProducer,
BuildProducer generatedBundleProducer,
BuildProducer generatedEntryPointProducer) {
try {
final long startedBundling = Instant.now().toEpochMilli();
final BundleResult result = Bundler.bundle(readyForBundling.bundleOptions(), false);
if (!result.result().output().isBlank()) {
LOGGER.debugf(result.result().output());
}
handleBundleDistDir(config, generatedBundleProducer, staticResourceProducer, result.dist(), startedBundling);
processGeneratedEntryPoints(config, readyForBundling.bundleOptions().workDir(), generatedEntryPointProducer);
return result;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
static void processGeneratedEntryPoints(WebBundlerConfig config, Path targetDir,
BuildProducer generatedEntryPointProducer) {
try (Stream generatedEPStream = Files.find(targetDir, 1, (path, basicFileAttributes) -> Files.isRegularFile(path)
&& path.getFileName().toString().toLowerCase().endsWith(".js"))) {
generatedEPStream
.forEach(p -> {
final String key = p.getFileName().toString().replace(".js", "");
final DefaultWebAsset wa = new DefaultWebAsset(join(config.webRoot(), p.getFileName().toString()), p,
Charset.defaultCharset());
generatedEntryPointProducer
.produce(new GeneratedEntryPointBuildItem(key, new BundleWebAsset(wa, GENERATED_ENTRY_POINT)));
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
static void handleBundleDistDir(WebBundlerConfig config, BuildProducer generatedBundleProducer,
BuildProducer staticResourceProducer, Path bundleDir, Long started) {
try {
Map bundle = new HashMap<>();
List names = new ArrayList<>();
StringBuilder mappingString = new StringBuilder();
try (Stream stream = Files.find(bundleDir, 20, (p, i) -> Files.isRegularFile(p))) {
stream.forEach(path -> {
final String relativePath = toUnixPath(bundleDir.relativize(path).toString());
final String key = relativePath.replaceAll("-[^-.]+\\.", ".");
final String publicBundleAssetPath = join(config.publicBundlePath(), relativePath);
final String fileName = path.getFileName().toString();
final String ext = fileName.substring(fileName.indexOf("."));
if (Bundle.BUNDLE_MAPPING_EXT.contains(ext)) {
mappingString.append(" ").append(key).append(" => ").append(publicBundleAssetPath).append("\n");
bundle.put(key, publicBundleAssetPath);
}
names.add(publicBundleAssetPath);
if (config.shouldQuarkusServeBundle()) {
// The root-path will already be added by the static resources handler
final String resourcePath = surroundWithSlashes(config.bundlePath()) + relativePath;
makePublic(staticResourceProducer, resourcePath, path.normalize(), SourceType.BUNDLED_ASSET);
}
});
}
if (started != null) {
LOGGER.infof("Bundle generated %d files in %sms", names.size(),
Instant.now().minusMillis(started).toEpochMilli());
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Bundle dir: '%s'\n - %s", bundleDir, names.size(),
String.join("\n - ", names));
}
if (LOGGER.isDebugEnabled() || LaunchMode.current() == LaunchMode.DEVELOPMENT) {
LOGGER.infof("Bundle#mapping:\n%s", mappingString);
}
}
generatedBundleProducer.produce(new GeneratedBundleBuildItem(bundleDir, bundle));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@BuildStep
@Record(STATIC_INIT)
void initBundleBean(
BuildProducer additionalBeans,
BuildProducer syntheticBeans,
GeneratedBundleBuildItem generatedBundle,
WebBundlerBuildRecorder recorder) {
final Map bundle = generatedBundle != null ? generatedBundle.getBundle() : Map.of();
syntheticBeans.produce(SyntheticBeanBuildItem.configure(Bundle.Mapping.class)
.supplier(recorder.createContext(bundle))
.done());
additionalBeans.produce(new AdditionalBeanBuildItem(Bundle.class));
}
@BuildStep
@Record(STATIC_INIT)
void initBundleRedirect(WebBundlerConfig config, BuildProducer routes,
BundleRedirectHandlerRecorder recorder, GeneratedBundleBuildItem generatedBundle) {
if (config.bundleRedirect() && generatedBundle != null) {
routes.produce(RouteBuildItem.builder().route(join(prefixWithSlash(config.bundlePath()), "*"))
.handler(recorder.handler(generatedBundle.getBundle()))
.build());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy