GithubHelp home page GithubHelp logo

faucet-pipeline / faucet-pipeline-spring-boot-starter Goto Github PK

View Code? Open in Web Editor NEW
17.0 6.0 6.0 975 KB

faucet-pipeline for Spring Boot

License: Apache License 2.0

JavaScript 5.28% CSS 0.02% Java 68.26% HTML 16.69% Kotlin 9.04% SCSS 0.71%
faucet-pipeline faucet spring-boot web-mvc turbolinks webflux spring-boot-starter

faucet-pipeline-spring-boot-starter's Introduction

faucet-pipeline-spring-boot-starter

GitHub GitHub Workflow Status (with branch) Maven Central

A Spring Boot starter and auto-configuration for the faucet-pipeline:

tl;dr: faucet-pipeline is a framework-independent, pluggable asset pipeline that takes the pain out of preprocessing JavaScript, CSS and associated files (e.g. images or fonts). It simplifies the process of converting modern JavaScript (ES6) to support older browsers (ES5), or Sass to CSS - eliminating typical low-level configuration nightmares.

The faucet-pipeline bundles your application files, fingerprints them and creates a manifest for them. It also can be used to watch the configured files and rerun the process during development.

Introduction

This starter is needed when you want to use faucet with your Spring Boot project. The following issues have to be tackled:

  • The resource processed through the pipeline ("assets") should not be part of the regular Java / Groovy / Kotlin sources and other resources of the project. As such, the assets would be copied by the build system (either Maven or Gradle) itself. You have to provide a place for the assets, this is not something the starter can do for you.

  • The processed assets need to be in the class path of the Spring Application. This is also a build step, that the starter cannot do for you.

What the starter does however are the following tasks:

  • It checks whether a faucet-manifests exists (defaults to classpath:/manifest.json) and if so, loads it

  • It checks whether the application is either a servlet or reactive web application. If not, the starter does nothing.

  • For a web application it registers a ResourceResolver that is able to retrieve internal urls that might are the output of finger printing and map them to external urls.

This works for servlet and reactive Spring applications.

tl;dr

git clone [email protected]:faucet-pipeline/faucet-pipeline-spring-boot-starter.git
cd faucet-pipeline-spring-boot-starter
./mvnw clean install
cd demo-webmvc
FAUCETPIPELINE_CACHEMANIFEST=false ./mvnw spring-boot:run

Usage and configuration

Runtime

Just include the starter in your pom.xml:

For Spring Boot 2.x.x

<dependency>
    <groupId>org.faucet-pipeline</groupId>
    <artifactId>faucet-pipeline-spring-boot-starter</artifactId>
    <version>1.3.0</version>
</dependency>

or in your build.gradle:

implementation 'org.faucet-pipeline:faucet-pipeline-spring-boot-starter:1.3.0'

For Spring Boot 3.0.x

<dependency>
    <groupId>org.faucet-pipeline</groupId>
    <artifactId>faucet-pipeline-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

or in your build.gradle:

implementation 'org.faucet-pipeline:faucet-pipeline-spring-boot-starter:2.0.0'

The starter can only work if Springs resource chain is active. The starter won’t activate this for you, so please configure

spring.web.resources.chain.enabled=true

The resource resolver will be mapped to /**, so basically that’s it.

Compile time

Make sure that your assets end up into /classes (maven) or /resources/main (gradle). This project contains two demos, demo-webmvc and demo-webflux. The following approach is from demo-webmvc.

Configure your faucet-pipeline

Follow the instructions from the website:

npm init # gets you a fresh package.json
npm install --save\ # Installs the pipeline for you
      faucet-pipeline-js\
      faucet-pipeline-sass\
      faucet-pipeline-static

In your package.json add the following scripts:

"scripts": {
    "compile": "faucet --fingerprint --compact",
    "watch": "faucet --no-fingerprint --watch"
}

The compact-switch is optional.

Create a faucet.config.js next to package.json. Here’s the one from demo-webmvc`

let targetBaseDir = "./target/classes/static"
const path = require('path');

module.exports = {
    js: [{
        source: "./src/main/assets/javascripts/application.js",
        target: targetBaseDir + "/javascripts/application.js"
    }],
    sass: [{
        source: "./src/main/assets/stylesheets/application.scss",
        target: targetBaseDir + "/stylesheets/application.css"
    }],
    static: [{
        source: "./src/main/assets/images",
        target: targetBaseDir + "/images"
    }],
    manifest: {
        target: "./target/classes/manifest.json",
        key: 'short',
        webRoot: targetBaseDir
    }
};

You’ll notice that it puts the all processed assets into ./target/classes/static. That is where Spring Boot looks for static files by default. Pushing it directly into the classes folder allows dynamic reloading later on. An alternative would be going through generated-resources.

As the above configuration writes the assets into subdirectories, you have to configure your Spring application to include those path patterns:

faucet-pipeline.path-patterns = /javascripts/**, /stylesheets/**, /images/**

Add frontend-maven-plugin (maven)

frontend-maven-plugin is ``Maven-node-grunt-gulp-npm-node-plugin to end all maven-node-grunt-gulp-npm-plugins.'':

With the package.json and faucet-configuration in place, add the following configuration:

<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>1.6</version>
    <executions>
        <execution>
            <id>install-node-and-npm</id>
            <goals>
                <goal>install-node-and-npm</goal>
            </goals>
            <phase>generate-resources</phase>
            <configuration>
                <nodeVersion>v18.14.2</nodeVersion>
            </configuration>
        </execution>
        <execution>
            <id>install-node-dependencies</id>
            <goals>
                <goal>npm</goal>
            </goals>
        </execution>
        <execution>
            <id>run-faucet-pipeline</id>
            <goals>
                <goal>npm</goal>
            </goals>
            <configuration>
                <arguments>run compile --fingerprint</arguments>
            </configuration>
        </execution>
    </executions>
</plugin>

This downloads Node and NPM and installs all dependencies via package.json and executes the pipeline during build. Assuming that your Spring Boot application has the Spring Boot Maven plugin configured like so

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

you can run the application with mvn spring-boot:run. When you use a supported template language like Thymeleaf and the URL-helper they offer, links to assets will contain the finger printed resources automatically. Those links

<link th:href="@{/stylesheets/application.css}" rel="stylesheet" data-turbolinks-track="reload">
<script th:src="@{/javascripts/application.js}" data-turbolinks-track="reload"></script>

Will be turned into

<link href="/stylesheets/stylesheets/application-70d5f3dc18d122548efadcedfc0874f0.css" rel="stylesheet" data-turbolinks-track="reload">
<script src="/javascripts/javascripts/application-8af210bcc164a457cb381a627729320b.js" data-turbolinks-track="reload"></script>

With gradle:

Add

buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath "com.moowork.gradle:gradle-node-plugin:1.2.0"
    }
}
apply plugin: "com.moowork.node"

to your build.gradle to being able to execute npm/yarn.
Then add a frontend build task and let the bootRun task depend on it:

task buildFrontend(type: YarnTask) {
    args = ['run', 'compile']
}

bootRun.dependsOn buildFrontend

Now you can run gradle bootRun to run your application.

Automatic restart, manifest caching

Use spring-boot-devtools to automatically reload the application when things change:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

The manifest is cached by default but that can be turned off via faucet-pipeline.cache-manifest = false. One easy way to do this without hardcoding it into a properties file is as an environment variable:

Run the demo in one window like so:

FAUCETPIPELINE_CACHEMANIFEST=false ./mvnw spring-boot:run

And in another terminal

npm run watch

And you’ll see the assets being processed and refreshed in the app.

About the demo application

Both demos - for WebMVC and Webflux - collect ideas. They use Turbolinks for quick navigation between server side rendered sites. Turbolinks come from Ruby on Rails.

The demo is a Bootstrap-based site branded with the INNOQ-theme and it looks like this:

Homepage WebMVC demo

Going reactive

The demo-webflux Version is a fully reactive, Spring 5 + Kotlin based application. Please start this one directly as JAR, the Maven Spring Boot Plugin seems to configure stuff slightly differently.

faucet-pipeline-spring-boot-starter's People

Contributors

michael-simons avatar raphiz avatar reabmax avatar rvullriede avatar torstenmandry avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

faucet-pipeline-spring-boot-starter's Issues

Tests fail due to JaCoCo error

Hi,

I've tried to do the tl;dr section, namely executing ./mwnw clean install. It fails on the tests stage due to:

Maven Build error

(pwd and $HOME are replaced by my local paths)

[ERROR] org.apache.maven.surefire.booter.SurefireBooterForkException: The forked VM terminated without properly saying goodbye. VM crash or System.exit called?
[ERROR] Command was /bin/sh -c cd `pwd`/faucet-pipeline-spring-boot-starter/faucet-pipeline-spring-boot-autoconfigure && /Library/Java/JavaVirtualMachines/openjdk-11.0.1.jdk/Contents/Home/bin/java -javaagent:$HOME/.m2/repository/org/jacoco/org.jacoco.agent/0.8.1/org.jacoco.agent-0.8.1-runtime.jar=destfile=`pwd`/faucet-pipeline-spring-boot-starter/faucet-pipeline-spring-boot-autoconfigure/target/jacoco.exec -jar `pwd`/faucet-pipeline-spring-boot-starter/faucet-pipeline-spring-boot-autoconfigure/target/surefire/surefirebooter5822704573787709978.jar `pwd`/faucet-pipeline-spring-boot-starter/faucet-pipeline-spring-boot-autoconfigure/target/surefire 2018-11-26T23-37-06_082-jvmRun1 surefire714121902207739922tmp surefire_011385807132639718567tmp
[ERROR] Error occurred in starting fork, check output in log
[ERROR] Process Exit Code: 134
[ERROR] 	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:669)
[ERROR] 	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:282)
[ERROR] 	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:245)
[ERROR] 	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1183)
[ERROR] 	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:1011)
[ERROR] 	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:857)
[ERROR] 	at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:137)
[ERROR] 	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208)
[ERROR] 	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:154)
[ERROR] 	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:146)
[ERROR] 	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
[ERROR] 	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81)
[ERROR] 	at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:56)
[ERROR] 	at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
[ERROR] 	at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:305)
[ERROR] 	at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:192)
[ERROR] 	at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:105)
[ERROR] 	at org.apache.maven.cli.MavenCli.execute(MavenCli.java:956)
[ERROR] 	at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:290)
[ERROR] 	at org.apache.maven.cli.MavenCli.main(MavenCli.java:194)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[ERROR] 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR] 	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[ERROR] 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR] 	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
[ERROR] 	at org.apache.maven.wrapper.BootstrapMainStarter.start(BootstrapMainStarter.java:39)
[ERROR] 	at org.apache.maven.wrapper.WrapperExecutor.execute(WrapperExecutor.java:122)
[ERROR] 	at org.apache.maven.wrapper.MavenWrapperMain.main(MavenWrapperMain.java:60)

due to some error with JaCoCo. It looks a bit similar to this one.

Workaround

I could work around it by removing the JaCoCo step from the pom.xml, namely removing the jacoco-maven-plugin there entirely.

Logfiles

The error is reproducable on my end, if I re-enable the section again, it fails again.
Here is the log that is dumped to faucet-pipeline-spring-boot-autoconfigure/target/surefire-reports/2018-11-27T21-43-36_524-jvmRun1.dumpstream

# Created at 2018-11-27T21:43:37.178
Corrupted STDOUT by directly writing to native stream in forked JVM 1. Stream 'FATAL ERROR in native method: processing of -javaagent failed'.
java.lang.IllegalArgumentException: Stream stdin corrupted. Expected comma after third character in command 'FATAL ERROR in native method: processing of -javaagent failed'.
    at org.apache.maven.plugin.surefire.booterclient.output.ForkClient$OperationalData.<init>(ForkClient.java:507)
    at org.apache.maven.plugin.surefire.booterclient.output.ForkClient.processLine(ForkClient.java:210)
    at org.apache.maven.plugin.surefire.booterclient.output.ForkClient.consumeLine(ForkClient.java:177)
    at org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer$Pumper.run(ThreadedStreamConsumer.java:88)
    at java.base/java.lang.Thread.run(Thread.java:834)

and here is most of what is written to faucet-pipeline-spring-boot-autoconfigure/target/surefire-reports/2018-11-27T21-43-36_524.dumpstream:

# Created at 2018-11-27T21:43:37.177
Exception in thread "main" java.lang.reflect.InvocationTargetException

# Created at 2018-11-27T21:43:37.177
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

# Created at 2018-11-27T21:43:37.178
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

# Created at 2018-11-27T21:43:37.179
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

# Created at 2018-11-27T21:43:37.179
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)

# Created at 2018-11-27T21:43:37.179
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:513)

# Created at 2018-11-27T21:43:37.180
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:525)

# Created at 2018-11-27T21:43:37.181
Caused by: java.lang.RuntimeException: Class java/lang/UnknownError could not be instrumented.

# Created at 2018-11-27T21:43:37.181
    at org.jacoco.agent.rt.internal_c13123e.core.runtime.ModifiedSystemClassRuntime.createFor(ModifiedSystemClassRuntime.java:140)

# Created at 2018-11-27T21:43:37.181
    at org.jacoco.agent.rt.internal_c13123e.core.runtime.ModifiedSystemClassRuntime.createFor(ModifiedSystemClassRuntime.java:101)

# Created at 2018-11-27T21:43:37.182
    at org.jacoco.agent.rt.internal_c13123e.PreMain.createRuntime(PreMain.java:55)

# Created at 2018-11-27T21:43:37.182
    at org.jacoco.agent.rt.internal_c13123e.PreMain.premain(PreMain.java:47)

# Created at 2018-11-27T21:43:37.182
    ... 6 more

# Created at 2018-11-27T21:43:37.182
Caused by: java.lang.NoSuchFieldException: $jacocoAccess

# Created at 2018-11-27T21:43:37.182
    at java.base/java.lang.Class.getField(Class.java:2000)

# Created at 2018-11-27T21:43:37.183
    at org.jacoco.agent.rt.internal_c13123e.core.runtime.ModifiedSystemClassRuntime.createFor(ModifiedSystemClassRuntime.java:138)

# Created at 2018-11-27T21:43:37.183
    ... 9 more

From what I understand it tries to instrumentalize the JVM on startup and this fails. JaCoCo wants to be the first to load the UUID class in order to be able to instrumentalize it… for some reason (I am absolutely sure for the best reasons, at least that's what I hope).
I haven't add any additional javaagent (to the best of my knowledge) which could potentially load the UUID class first. But maybe it is some contextual thing or something that broke in a later Java version?

I have to say I haven't used java agents usually.
My suggestion: Why I do see some benefits in having a complete example in the first place. Maybe the inclusion of JaCoCo is a bit oversized here? At least if it is causing problems?

But maybe it's just my local setup?

Support for Spring Boot 3

Spring Boot 3 is currently not supported for two major reasons:

  1. The javax.* namespace is no longer supported and need to be replaced with jakarta.*.
  2. The AutoConfiguration mechanism via spring.factories is no longer supported.
  3. In addition, certain dependencies are not compatible with Java 17.
    4

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.