如何使用ParcelJS在Spring Boot應用程式中打包前端 - codecentric AG Blog
在整合前端程式碼時,我們經常需要處理多種內容,例如:資源,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="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <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>true</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>true</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>true</generateGitPropertiesFile> <!-- enables other plugins to use git properties --> <injectAllReactorProjects>true</injectAllReactorProjects> </configuration> </plugin> </plugins> </pluginManagement> </build> </project> |
2.API模組
acme-example-api模組中的GreetingController
@Slf4j @RestController @RequestMapping("/api/greetings") class GreetingController { @GetMapping Object greet(@RequestParam(defaultValue = "world") String name) { Map<String, Object> data = Map.of("greeting", "Hello " + name, "time", System.currentTimeMillis()); log.info("Returning: {}", data); return data; } } |
pom.xml配置:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <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> |
APP模組
acme-example-app模組的App類是Spring Boot啟動類
package com.acme.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } |
對於我們的應用程式,我們希望從Spring Boot應用程式中提供前端資源。因此,我們在cme-example-app模組中WebMvcConfiga定義以下ResourceHandler和ViewController內容:
package com.acme.app.web; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import lombok.RequiredArgsConstructor; @Configuration @RequiredArgsConstructor class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/app/**").addResourceLocations("classpath:/public/"); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/app/").setViewName("forward:/app/index.html"); } } |
為了讓這個例子更逼真,我們將使用/acme一個自定義的context-path,配置application.yml:
server: servlet: context-path:/ acme |
我們acme-example-app模組的Maven pom.xml看起來有點羅嗦,因為它將其他模組拉到一起:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <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>true</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> |
UI模組
現在出現了一個有趣的部分:acme-example-ui包含我們的前端程式碼的Maven模組。
該acme-example-ui模組在pom.xml使用com.github.eirslett:frontend-maven-pluginMaven外掛觸發標準的前端構建工具,在這種情況下使用node和yarn。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <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> <!-- this calls yarn install --> <arguments>install</arguments> </configuration> </execution> <execution> <id>yarn build</id> <goals> <goal>yarn</goal> </goals> <configuration> <!-- this 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> |
在目錄/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="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Acme App</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="./style/main.css"> </head> <body> <h1>Acme App</h1> <button id="btnGetData">Fetch data</button> <div id="responseText"></div> <script src="./main/main.js" defer></script> </body> </html> |
main.js中javascript程式碼呼叫之前的REST GreetingController :
import "@babel/polyfill"; function main(){ console.log("Initializing app...") btnGetData.onclick = async () => { const resp = await fetch("../api/greetings"); const payload = await resp.json(); console.log(payload); responseText.innerText=JSON.stringify(payload); }; } main(); |
這裡使用了ES7語法,在main.css中CSS:
body { --main-fg-color: red; --main-bg-color: yellow; } h1 { color: var(--main-fg-color); } responseText { background: var(--main-bg-color); } |
請注意,我正在使用“新”原生CSS變數支援。
注意package.json配置:
{ "name": "acme-example-ui-plain", "version": "1.0.0.0-SNAPSHOT", "private": true, "license": "Apache-2.0", "scripts": { "clean": "rm -rf target/classes/public", "start": "parcel --public-url ./ -d target/classes/public src/main/frontend/index.html", "watch": "parcel watch --public-url ./ -d target/classes/public src/main/frontend/index.html", "build": "parcel build --public-url ./ -d target/classes/public src/main/frontend/index.html" }, "devDependencies": { "@babel/core": "^7.0.0-0", "@babel/plugin-proposal-async-generator-functions": "^7.2.0", "babel-preset-latest": "^6.24.1", "parcel": "^1.11.0" }, "dependencies": { "@babel/polyfill": "^7.2.5" } } |
為了支援ES7特性,比如async,我們需要透過.babelrc檔案配置babel transpiler :
{ "presets": [ ["latest"] ], "plugins": [] } |
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 --public-url ./ -d target/classes/public src/main/frontend/index.html Built in 585ms. $ ll target/classes/public 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="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Acme App</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="main.d4190f58.css"> <script src="main.d4190f58.js"></script></head> <body> <h1>Acme App</h1> <button id="btnGetData">Fetch data</button> <div id="responseText"></div> <script src="main.0632549a.js" defer=""></script> </body> </html> |
下一個技巧是隻使用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即可:
interface Person { firstName: string; lastName: string; } function greet(person: Person) { return "Hello, " + person.firstName + " " + person.lastName; } let user = { firstName: "Buddy", lastName: "Holly" }; console.log(greet(user)); |
然後在index.html引用:
<script src="./main/hello.ts" defer></script> |
由於我們正在執行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可能非常適合你。
相關文章
- 使用Axon重播投射事件 - codecentric AG Blog事件
- 在Spring Boot應用程式中使用Kubernetes ConfigMapSpring Boot
- 如何在Spring Boot應用程式中啟用GZIP壓縮? | 前端後端Spring Boot前端後端
- Spring Boot中如何使用Ostara監控應用?Spring Boot
- Spring Boot 應用程式中的 QueryDSLSpring Boot
- 在IntelliJ idea中使用docker除錯Spring Boot應用程式IntelliJIdeaDocker除錯Spring Boot
- 將Spring Boot應用變成GraalVM本機映象快速執行 - codecentricSpring BootLVM
- 在Spring Boot應用啟動時如何執行程式碼? -DukesletterSpring Boot行程
- Spring Boot(十二):Spring Boot 如何測試打包部署Spring Boot
- 最佳化Spring Boot應用的Docker打包速度Spring BootDocker
- Spring boot應用如何支援httpsSpring BootHTTP
- Spring Boot應用程式中的常用註釋列表Spring Boot
- Spring Boot + Kotlin + Coroutines應用演示程式Spring BootKotlin
- Spring Boot應用程式事件教程 - reflectoringSpring Boot事件
- Spring Boot應用程式有哪些功能?Spring Boot
- 如何預熱Spring Boot應用? - sebsteinSpring Boot
- Parceljs和Webpack在React專案上打包速度對比JSWebReact
- Spring Boot 2.5.x能支援Java 17了 - codecentricSpring BootJava
- Spring Boot 應用程式啟動流程分析Spring Boot
- Guava Cache本地快取在 Spring Boot應用中的實踐Guava快取Spring Boot
- 如何優雅地停止 Spring Boot 應用?Spring Boot
- 你知道如何自動儲存 Spring Boot 應用程式號嗎Spring Boot
- Spring Boot 和 Spring Cloud 應用記憶體如何管理?Spring BootCloud記憶體
- Spring Boot 中 10 行程式碼構建 RESTful 風格應用Spring Boot行程REST
- 使用谷歌Skaffold在Kubernetes上進行Spring Boot應用程式的CI / CD工作流程 - foojay谷歌Spring Boot
- 使用Prometheus和Grafana監控Spring Boot應用PrometheusGrafanaSpring Boot
- C#—使用InstallerProjects打包桌面應用程式C#Project
- 在spring boot3中使用native imageSpring Boot
- 在 Spring Boot 中使用 RedisSpring BootRedis
- 使用 @Audited 增強Spring Boot 應用程式的資料審計能力Spring Boot
- 自然語言處理工具包 HanLP在 Spring Boot中的應用自然語言處理HanLPSpring Boot
- Spring Boot package打包失敗Spring BootPackage
- 在GraalVM中部署執行Spring Boot應用 - Indrek OtsLVMSpring Boot
- 使用Cordova將您的前端JavaScript應用打包成手機原生應用前端JavaScript
- spring boot中zookeeper使用Spring Boot
- spring boot中redis使用Spring BootRedis
- 使用 SAP BTP 建立一個 Spring Boot Java 應用Spring BootJava
- 使用混沌候攻擊測試Spring Boot應用Spring Boot