如何使用ParcelJS在Spring Boot应用程序中打包前端 - codecentric AG Blog

栏目: Java · 发布时间: 5年前

内容简介:在集成前端代码时,我们经常需要处理多种内容,例如:资源,HTML,CSS,JavaScript,打字稿,缩小等等 - 通常是通过复杂生成的构建脚本来实现,这些脚本很难调试。我一直在寻找一个简单的快速实验解决方案......现在我偶然发现了ParcelJS,它通过使用约定优于配置解决了部分问题。听起来很有希望?然后继续阅读!

在集成前端代码时,我们经常需要处理多种内容,例如:资源,HTML,CSS,JavaScript,打字稿,缩小等等 - 通常是通过复杂生成的构建脚本来实现,这些脚本很难调试。我一直在寻找一个简单的快速实验解决方案......现在我偶然发现了ParcelJS,它通过使用约定优于配置解决了部分问题。

ParcelJS 是一个简单的Web应用程序捆绑器,它可以将您的前端代码打包为理想的默认值,这些默认值 可以满足 您的需求 - 至少在大多数情况下都是如此。非常适合小型和简单的项目或演示应用程序。在下面的文章中,我将描述如何在Spring Boot应用程序中捆绑和提供前端代码,而无需使用任何代理,专用开发服务器或复杂的构建系统!而且你还可以免费获得压缩,缩小和实时重载等酷炫功能。

听起来很有希望?然后继续阅读!

对于不耐烦的人,你可以在这里找到GitHub上的所有代码: thomasdarimont / spring-boot-micro-frontend-example

示例应用

示例应用程序使用Maven,由包含在第四个父模块中的三个模块组成:

  • acme-example-api
  • acme-example-ui
  • acme-example-app
  • spring-boot-micro-frontend-example (父)

第一个模块是acme-example-api包含后端API的,后端API只是一个简单的带@RestController注释的Spring MVC控制器。我们的第二个模块acme-example-ui包含我们的前端代码,并将Maven与Parcel结合使用来打包应用程序位。下一个模块acme-example-app托管实际的Spring Boot应用程序并将其他两个模块连接在一起。最后,该spring-boot-starter-parent模块用作聚合器模块并提供默认配置。

1.父模块

父模块本身使用spring-boot-starter-parentas parent并继承一些托管依赖项和默认配置。

<project xmlns=<font>"http://maven.apache.org/POM/4.0.0"</font><font>
    xmlns:xsi=</font><font>"http://www.w3.org/2001/XMLSchema-instance"</font><font>
    xsi:schemaLocation=</font><font>"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</font><font>>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.github.thomasdarimont.training</groupId>
    <artifactId>acme-example</artifactId>
    <version>1.0.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
 
    <modules>
        <module>acme-example-api</module>
        <module>acme-example-ui</module>
        <module>acme-example-app</module>
    </modules>
 
    <properties>
        <java.version>11</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <maven.compiler.release>${java.version}</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional><b>true</b></optional>
        </dependency>
    </dependencies>
 
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.github.thomasdarimont.training</groupId>
                <artifactId>acme-example-api</artifactId>
                <version>${project.version}</version>
            </dependency>
 
            <dependency>
                <groupId>com.github.thomasdarimont.training</groupId>
                <artifactId>acme-example-ui</artifactId>
                <version>${project.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
 
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <executable><b>true</b></executable>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>build-info</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>pl.project13.maven</groupId>
                    <artifactId>git-commit-id-plugin</artifactId>
                    <configuration>
                        <generateGitPropertiesFile><b>true</b></generateGitPropertiesFile>
                        <!-- enables other plugins to use git properties -->
                        <injectAllReactorProjects><b>true</b></injectAllReactorProjects>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
</font>

2.API模块

acme-example-api模块中的GreetingController

@Slf4j
@RestController
@RequestMapping(<font>"/api/greetings"</font><font>)
<b>class</b> GreetingController {
 
    @GetMapping
    Object greet(@RequestParam(defaultValue = </font><font>"world"</font><font>) String name) {
        Map<String, Object> data = Map.of(</font><font>"greeting"</font><font>, </font><font>"Hello "</font><font> + name, </font><font>"time"</font><font>, System.currentTimeMillis());
        log.info(</font><font>"Returning: {}"</font><font>, data);
        <b>return</b> data;
    }
}
</font>

pom.xml配置:

<?xml version=<font>"1.0"</font><font> encoding=</font><font>"UTF-8"</font><font>?>
<project xmlns=</font><font>"http://maven.apache.org/POM/4.0.0"</font><font>
    xmlns:xsi=</font><font>"http://www.w3.org/2001/XMLSchema-instance"</font><font>
    xsi:schemaLocation=</font><font>"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</font><font>>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.github.thomasdarimont.training</groupId>
        <artifactId>acme-example</artifactId>
        <version>1.0.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>acme-example-api</artifactId>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
</project>
</font>

APP模块

acme-example-app模块的App类是Spring Boot启动类

<b>package</b> com.acme.app;
 
<b>import</b> org.springframework.boot.SpringApplication;
<b>import</b> org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
<b>public</b> <b>class</b> App {
 
    <b>public</b> <b>static</b> <b>void</b> main(String[] args) {
        SpringApplication.run(App.<b>class</b>, args);
    }
}

对于我们的应用程序,我们希望从Spring Boot应用程序中提供前端资源。因此,我们在cme-example-app模块中WebMvcConfiga定义以下ResourceHandler和ViewController内容:

<b>package</b> com.acme.app.web;
 
<b>import</b> org.springframework.context.annotation.Configuration;
<b>import</b> org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
<b>import</b> org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
<b>import</b> org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
<b>import</b> lombok.RequiredArgsConstructor;
 
@Configuration
@RequiredArgsConstructor
<b>class</b> WebMvcConfig implements WebMvcConfigurer {
 
    @Override
    <b>public</b> <b>void</b> addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler(<font>"/app/**"</font><font>).addResourceLocations(</font><font>"classpath:/public/"</font><font>);
    }
 
    @Override
    <b>public</b> <b>void</b> addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController(</font><font>"/app/"</font><font>).setViewName(</font><font>"forward:/app/index.html"</font><font>);
    }
}
</font>

为了让这个例子更逼真,我们将使用/acme一个自定义的context-path,配置application.yml:

server:
  servlet:
    context-path:/ acme

我们acme-example-app模块的Maven pom.xml看起来有点罗嗦,因为它将其他模块拉到一起:

<project xmlns=<font>"http://maven.apache.org/POM/4.0.0"</font><font>
    xmlns:xsi=</font><font>"http://www.w3.org/2001/XMLSchema-instance"</font><font>
    xsi:schemaLocation=</font><font>"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</font><font>>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.github.thomasdarimont.training</groupId>
        <artifactId>acme-example</artifactId>
        <version>1.0.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>acme-example-app</artifactId>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
 
        <dependency>
            <groupId>com.github.thomasdarimont.training</groupId>
            <artifactId>acme-example-api</artifactId>
        </dependency>
 
        <dependency>
            <groupId>com.github.thomasdarimont.training</groupId>
            <artifactId>acme-example-ui</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional><b>true</b></optional>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
</font>

UI模块

现在出现了一个有趣的部分:acme-example-ui包含我们的前端代码的Maven模块。

该acme-example-ui模块在pom.xml使用com.github.eirslett:frontend-maven-pluginMaven插件触发标准的前端构建工具,在这种情况下使用node和yarn。

<project xmlns=<font>"http://maven.apache.org/POM/4.0.0"</font><font>
    xmlns:xsi=</font><font>"http://www.w3.org/2001/XMLSchema-instance"</font><font>
    xsi:schemaLocation=</font><font>"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</font><font>>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.github.thomasdarimont.training</groupId>
        <artifactId>acme-example</artifactId>
        <version>1.0.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>acme-example-ui</artifactId>
 
    <properties>
        <node.version>v10.15.1</node.version>
        <yarn.version>v1.13.0</yarn.version>
        <frontend-maven-plugin.version>1.6</frontend-maven-plugin.version>
    </properties>
 
    <build>
        <plugins>
            <plugin>
                <groupId>pl.project13.maven</groupId>
                <artifactId>git-commit-id-plugin</artifactId>
                <!-- config inherited from parent -->
            </plugin>
 
            <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <version>${frontend-maven-plugin.version}</version>
                <configuration>
                    <installDirectory>target</installDirectory>
                    <workingDirectory>${basedir}</workingDirectory>
                    <nodeVersion>${node.version}</nodeVersion>
                    <yarnVersion>${yarn.version}</yarnVersion>
                </configuration>
 
                <executions>
                    <execution>
                        <id>install node and yarn</id>
                        <goals>
                            <goal>install-node-and-yarn</goal>
                        </goals>
                    </execution>
 
                    <execution>
                        <id>yarn install</id>
                        <goals>
                            <goal>yarn</goal>
                        </goals>
                        <configuration>
                                                        <!-- <b>this</b> calls yarn install -->
                            <arguments>install</arguments>
                        </configuration>
                    </execution>
 
                    <execution>
                        <id>yarn build</id>
                        <goals>
                            <goal>yarn</goal>
                        </goals>
                        <configuration>
                                                        <!-- <b>this</b> calls yarn build -->
                            <arguments>build</arguments>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
 
        <pluginManagement>
            <plugins>
                <!--This plugin's configuration is used to store Eclipse m2e settings 
                    only. It has no influence on the Maven build itself. -->
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>com.github.eirslett</groupId>
                                        <artifactId>frontend-maven-plugin</artifactId>
                                        <versionRange>[0,)</versionRange>
                                        <goals>
                                            <goal>install-node-and-yarn</goal>
                                            <goal>yarn</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <!-- ignore yarn builds triggered by eclipse -->
                                        <ignore />
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
</font>

在目录/acme-example-ui/src/main/frontend下前端结构:

└── frontend
    ├── index.html
    ├── main
    │   └── main.js
    └── style
        └── main.css

index.html只包含纯HTML引用我们的JavaScript代码和资产:

<!DOCTYPE html>
<html>
<head>
    <meta charset=<font>"utf-8"</font><font>>
    <meta http-equiv=</font><font>"X-UA-Compatible"</font><font> content=</font><font>"IE=edge"</font><font>>
    <title>Acme App</title>
    <meta name=</font><font>"description"</font><font> content=</font><font>""</font><font>>
    <meta name=</font><font>"viewport"</font><font> content=</font><font>"width=device-width, initial-scale=1"</font><font>>
    <link rel=</font><font>"stylesheet"</font><font> href=</font><font>"./style/main.css"</font><font>>
</head>
<body>
    <h1>Acme App</h1>
 
    <button id=</font><font>"btnGetData"</font><font>>Fetch data</button>
    <div id=</font><font>"responseText"</font><font>></div>
    <script src=</font><font>"./main/main.js"</font><font> defer></script>
</body>
</html>
</font>

main.js中javascript代码调用之前的REST GreetingController :

<b>import</b> <font>"@babel/polyfill"</font><font>;
 
function main(){
    console.log(</font><font>"Initializing app..."</font><font>)
 
    btnGetData.onclick = async () => {
 
        <b>const</b> resp = await fetch(</font><font>"../api/greetings"</font><font>);
        <b>const</b> payload = await resp.json();
        console.log(payload);
 
        responseText.innerText=JSON.stringify(payload);
    };
}
 
main();
</font>

这里使用了ES7语法,在main.css中CSS:

body {
    --main-fg-color: red;
    --main-bg-color: yellow;
}
 
h1 {
    color: <b>var</b>(--main-fg-color);
}
 
#responseText {
    background: <b>var</b>(--main-bg-color);
}

请注意,我正在使用“新”原生CSS变量支持。

注意package.json配置:

{
    <font>"name"</font><font>: </font><font>"acme-example-ui-plain"</font><font>,
    </font><font>"version"</font><font>: </font><font>"1.0.0.0-SNAPSHOT"</font><font>,
    </font><font>"private"</font><font>: <b>true</b>,
    </font><font>"license"</font><font>: </font><font>"Apache-2.0"</font><font>,
    </font><font>"scripts"</font><font>: {
        </font><font>"clean"</font><font>: </font><font>"rm -rf target/classes/public"</font><font>,
        </font><font>"start"</font><font>: </font><font>"parcel --public-url ./ -d target/classes/public src/main/frontend/index.html"</font><font>,
        </font><font>"watch"</font><font>: </font><font>"parcel watch --public-url ./ -d target/classes/public src/main/frontend/index.html"</font><font>,
        </font><font>"build"</font><font>: </font><font>"parcel build --public-url ./ -d target/classes/public src/main/frontend/index.html"</font><font>
    },
    </font><font>"devDependencies"</font><font>: {
        </font><font>"@babel/core"</font><font>: </font><font>"^7.0.0-0"</font><font>,
        </font><font>"@babel/plugin-proposal-async-generator-functions"</font><font>: </font><font>"^7.2.0"</font><font>,
        </font><font>"babel-preset-latest"</font><font>: </font><font>"^6.24.1"</font><font>,
        </font><font>"parcel"</font><font>: </font><font>"^1.11.0"</font><font>
    },
    </font><font>"dependencies"</font><font>: {
        </font><font>"@babel/polyfill"</font><font>: </font><font>"^7.2.5"</font><font>
    }
}
</font>

为了支持ES7特性,比如async,我们需要通过.babelrc文件配置babel transpiler :

{
   <font>"presets"</font><font>: [
      [</font><font>"latest"</font><font>]
   ],
   </font><font>"plugins"</font><font>: []
}
</font>

ParcelJS 设置

我们定义了一些脚本clean,start,watch并且build,这是为了能够通过`yarn`或`npm`调用它们。

下一个技巧是parcel的配置。让我们看一个具体的例子来看看这里发生了什么:

parcel build --public-url ./ -d target/classes/public src/main/frontend/index.html

这行做了几件事:

  • --public-url ./这指示parcel生成相对于我们将从中提供应用程序资源的路径的链接。
  • -d target/classes/public这告诉Parcel将前端工件放在target/classes/public文件夹中,它们可以在类路径中找到
  • src/main/frontend/index.html最后一部分是显示Parcel,在这种情况下,我们的应用程序的入口点src/main/frontend/index.html。请注意,您可以在此处定义多个入口点。

下一个技巧是将此配置与Parcel的监视模式相结合,可以通过parcel watch命令启动。与许多其他Web应用程序捆绑 工具 一样,watch允许在我们更改代码时自动且透明地重新编译和重新打包前端工件。

因此,我们要做的就是拥有一个流畅的前端开发人员体验,就是在/acme-example-ui文件夹中启动`yarn watch`进程。

生成的资源将显示在下面target/classes/public,如下所示:

$ yarn watch                          
yarn run v1.13.0
$ parcel watch --<b>public</b>-url ./ -d target/classes/<b>public</b> src/main/frontend/index.html
 Built in 585ms.

$ ll target/classes/<b>public</b>            
total 592K
drwxr-xr-x. 2 tom tom 4,0K  8. Feb 22:59 ./
drwxr-xr-x. 3 tom tom 4,0K  8. Feb 22:59 ../
-rw-r--r--. 1 tom tom  525  8. Feb 23:02 index.html
-rw-r--r--. 1 tom tom 303K  8. Feb 23:02 main.0632549a.js
-rw-r--r--. 1 tom tom 253K  8. Feb 23:02 main.0632549a.map
-rw-r--r--. 1 tom tom  150  8. Feb 23:02 main.d4190f58.css
-rw-r--r--. 1 tom tom 9,5K  8. Feb 23:02 main.d4190f58.js
-rw-r--r--. 1 tom tom 3,6K  8. Feb 23:02 main.d4190f58.map

$ cat target/classes/public/index.html:

<!DOCTYPE html>
<html>
    <head>
        <meta charset=<font>"utf-8"</font><font>>
        <meta http-equiv=</font><font>"X-UA-Compatible"</font><font> content=</font><font>"IE=edge"</font><font>>
        <title>Acme App</title>
        <meta name=</font><font>"description"</font><font> content=</font><font>""</font><font>>
        <meta name=</font><font>"viewport"</font><font> content=</font><font>"width=device-width, initial-scale=1"</font><font>>
        <link rel=</font><font>"stylesheet"</font><font> href=</font><font>"main.d4190f58.css"</font><font>>
    <script src=</font><font>"main.d4190f58.js"</font><font>></script></head>
    <body>
        <h1>Acme App</h1>
 
        <button id=</font><font>"btnGetData"</font><font>>Fetch data</button>
        <div id=</font><font>"responseText"</font><font>></div>
        <script src=</font><font>"main.0632549a.js"</font><font> defer=</font><font>""</font><font>></script>
    </body>
</html>
</font>

下一个技巧是只使用Spring Boot devtools启用了Live-reload。如果您访问任何前端代码,这将自动重新加载包内容。您可以启动com.acme.app.AppSpring Boot应用程序并通过http://localhost:8080/acme/app/在浏览器中输入URL 来访问应用程序。

添加Typescript 

现在我们的设置工作正常,我们可能想要使用Typescript而不是纯JavaScript。使用Parcel这很容易。只需在src/main/frontend/main下添加新文件hello.ts即可:

<b>interface</b> Person {
    firstName: string;
    lastName: string;
}
 
function greet(person: Person) {
    <b>return</b> <font>"Hello, "</font><font> + person.firstName + </font><font>" "</font><font> + person.lastName;
}
 
let user = { firstName: </font><font>"Buddy"</font><font>, lastName: </font><font>"Holly"</font><font> };
 
console.log(greet(user));
</font>

然后在index.html引用:

<script src=<font>"./main/hello.ts"</font><font> defer></script>
</font>

由于我们正在运行yarn watch,parcel工具将发现我们需要一个基于.ts我们引用文件的文件扩展名的Typescript编译器。因此ParcelJS会自动添加"typescript": "^3.3.3"到我们devDependencies的package.json文件中。

使用less用于CSS

我们现在可能想要使用less而不是普通css。同样,所有我们在这里做的是重新命名main.css,以main.less并参考它在index.html通过的文件

<link rel="stylesheet" href="./style/main.less">

ParcelJS将自动添加"less": "^3.9.0"到我们的产品中,devDependencies并为您提供随时可用的配置。

请注意, 默认情况下ParcelJS支持许多其他资产类型

最后:你可以做一个maven verify,它会自动建立你acme-example-api和acme-example-ui模块和acme-example-app的可执行文件打包的JAR包

​​​​​​​下次你想快速构建一些东西或者只是稍微破解一下,那么ParcelJS和Spring Boot可能非常适合你。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python for Everyone

Python for Everyone

Cay S. Horstmann、Rance D. Necaise / John Wiley & Sons / 2013-4-26 / GBP 181.99

Cay Horstmann's" Python for Everyone "provides readers with step-by-step guidance, a feature that is immensely helpful for building confidence and providing an outline for the task at hand. "Problem S......一起来看看 《Python for Everyone》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具