All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkiverse.web.bundler.deployment.DevModeBundlingProcessor Maven / Gradle / Ivy

There is a newer version: 1.8.0
Show newest version
package io.quarkiverse.web.bundler.deployment;

import static io.quarkiverse.web.bundler.deployment.BundlingProcessor.*;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.jboss.logging.Logger;

import io.mvnpm.esbuild.BundleException;
import io.mvnpm.esbuild.Bundler;
import io.mvnpm.esbuild.Watch;
import io.mvnpm.esbuild.model.BundleOptions;
import io.mvnpm.esbuild.model.BundleResult;
import io.quarkiverse.web.bundler.deployment.items.GeneratedBundleBuildItem;
import io.quarkiverse.web.bundler.deployment.items.GeneratedEntryPointBuildItem;
import io.quarkiverse.web.bundler.deployment.items.ReadyForBundlingBuildItem;
import io.quarkiverse.web.bundler.deployment.web.GeneratedWebResourceBuildItem;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem;
import io.quarkus.deployment.builditem.DevServicesResultBuildItem;
import io.quarkus.deployment.builditem.LiveReloadBuildItem;
import io.quarkus.deployment.dev.RuntimeUpdatesProcessor;

public class DevModeBundlingProcessor {

    private static final Logger LOGGER = Logger.getLogger(DevModeBundlingProcessor.class);

    private static final String DEV_SERVICE_NAME = "web-bundler-dev";
    private static volatile DevServicesResultBuildItem.RunningDevService devService;
    private static final AtomicReference watchRef = new AtomicReference<>();

    private static final AtomicReference bundleExceptionRef = new AtomicReference<>();

    @BuildStep(onlyIf = IsDevelopment.class)
    void watch(WebBundlerConfig config,
            ReadyForBundlingBuildItem readyForBundling,
            BuildProducer staticResourceProducer,
            BuildProducer generatedBundleProducer,
            BuildProducer devServices,
            BuildProducer generatedEntryPointProducer,
            LiveReloadBuildItem liveReload,
            CuratedApplicationShutdownBuildItem shutdown) {
        if (readyForBundling == null) {
            return;
        }
        final BundlesBuildContext bundlesBuildContext = liveReload.getContextObject(BundlesBuildContext.class);
        final boolean isLiveReload = liveReload.isLiveReload();
        Watch watch = DevModeBundlingProcessor.watchRef.get();
        if (readyForBundling.started() == null) {
            // no changes
            boolean isRestartWatchNeeded = readyForBundling.enabledBundlingWatch() && (watch == null || !watch.isAlive());
            if (!isRestartWatchNeeded) {
                if (watch != null && watch.isAlive()) {
                    devServices.produce(devService.toBuildItem());
                }
                final BundlesBuildContext newBundlesBuildContext = new BundlesBuildContext(readyForBundling.bundleOptions(),
                        bundlesBuildContext.bundleDistDir());
                liveReload.setContextObject(BundlesBuildContext.class, newBundlesBuildContext);
                handleBundleDistDir(config, generatedBundleProducer, staticResourceProducer,
                        bundlesBuildContext.bundleDistDir(),
                        readyForBundling.started());
                processGeneratedEntryPoints(config, readyForBundling.bundleOptions().workDir(),
                        generatedEntryPointProducer);
                return;
            }
        }

        if (watch != null) {
            shutdownDevService();
        }

        if (!readyForBundling.enabledBundlingWatch()) {
            // We use normal bundling when watch is not enabled
            final BundleResult bundleResult = bundleAndProcess(config, readyForBundling, staticResourceProducer,
                    generatedBundleProducer,
                    generatedEntryPointProducer);
            final BundlesBuildContext newBundlesBuildContext = new BundlesBuildContext(readyForBundling.bundleOptions(),
                    bundleResult.dist());
            liveReload.setContextObject(BundlesBuildContext.class, newBundlesBuildContext);
            return;
        }

        if (!isLiveReload) {
            // Only the first time
            Runnable closeTask = () -> {
                if (devService != null) {
                    shutdownDevService();
                }
            };
            shutdown.addCloseTask(closeTask, true);
        }

        try {
            watch = Bundler.watch(readyForBundling.bundleOptions(), (r) -> {
                if (watchRef.get() == null) {
                    LOGGER.error("Received a bundling event without a watchRef");
                    return;
                }
                LOGGER.debugf("New bundling event received: %s", r);
                if (!r.isSuccess()) {
                    bundleExceptionRef.set(r.bundleException());
                    RuntimeUpdatesProcessor.INSTANCE.setRemoteProblem(r.bundleException());
                } else {
                    resetRemoteProblem();
                    bundleExceptionRef.set(null);
                }
                if (!watchRef.get().isAlive()) {
                    shutdownDevService();
                }
                callNoRestartChangesConsumers(r.isSuccess());
            }, false);
            watchRef.set(watch);
            devService = new DevServicesResultBuildItem.RunningDevService(
                    DEV_SERVICE_NAME, null, watch::stop, new HashMap<>());
            devServices.produce(devService.toBuildItem());
            if (!watch.firstBuildResult().isSuccess()) {
                throw watch.firstBuildResult().bundleException();
            }
            final BundlesBuildContext newBundlesBuildContext = new BundlesBuildContext(readyForBundling.bundleOptions(),
                    watch.dist());
            liveReload.setContextObject(BundlesBuildContext.class, newBundlesBuildContext);
            handleBundleDistDir(config, generatedBundleProducer, staticResourceProducer, watch.dist(),
                    readyForBundling.started());
            processGeneratedEntryPoints(config, readyForBundling.bundleOptions().workDir(), generatedEntryPointProducer);

        } catch (IOException e) {
            shutdownDevService();
            liveReload.setContextObject(BundlesBuildContext.class, new BundlesBuildContext());
            throw new UncheckedIOException(e);
        } catch (Exception e) {
            shutdownDevService();
            liveReload.setContextObject(BundlesBuildContext.class, new BundlesBuildContext());
            throw e;
        }
    }

    private void callNoRestartChangesConsumers(boolean isSuccess) {
        RuntimeUpdatesProcessor.INSTANCE
                .notifyExtensions(Set.of(isSuccess ? "web-bundler/build-success" : "web-bundler/build-error"));
    }

    private static void resetRemoteProblem() {
        if (RuntimeUpdatesProcessor.INSTANCE.getCompileProblem() instanceof BundleException) {
            RuntimeUpdatesProcessor.INSTANCE.setRemoteProblem(null);
        }
    }

    private void shutdownDevService() {
        LOGGER.debug("Web Bundler: shutdownDevService");
        try {
            if (devService != null) {
                devService.close();
            }
        } catch (Throwable e) {
            LOGGER.error("Failed to stop Web Bundler bundling process", e);
        } finally {
            devService = null;
            watchRef.set(null);
            bundleExceptionRef.set(null);
        }
    }

    record BundlesBuildContext(BundleOptions bundleOptions,
            Path bundleDistDir) {

        public BundlesBuildContext() {
            this(null, null);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy