用Java、Maven和JBake生成静态网站

273 阅读7分钟

你注意到了吗?上周,我们将整个www.optaplanner.org网站(1399个文件)迁移到用Java和Maven构建,而不是Ruby和Rake。表面上看,没有什么变化。但在源代码中,对于我们的Java开发者团队来说,这是一个游戏规则的改变。

我们的Java团队现在可以轻松地对网站做出贡献。在完成迁移的几个小时内,已经有一个我们的开发者提交了一个宁可用十英尺长的柱子也不碰以前的源代码

我们建立了这个网站。
我们在Java和Maven上建立了这个网站。
我们建立了这个网站。
我们在JBake和Freemarker上建立了这个网站。

为什么使用静态网站生成器?

静态网站生成器将模板和内容文件转化为静态HTML/JS/CSS网站。对于像我们这样的项目,这比内容管理系统(CMS)有很多优势。

  • 托管费很便宜。GitHub页面甚至可以免费托管静态网站。
  • 源文件进入Git进行备份记录
  • 源文件是纯文本的。
    • 更改以Pull Request的形式进入,以便进行适当的审查和CI验证。
    • 源文件在我们的IDE中是开放的,这鼓励我们在代码旁边重构它们。这导致了更少的陈旧内容。

多年来,Awestruct一直为我们服务。但由于缺乏活动,是时候升级了。

为什么是JBake?

因为我们是Java程序员。

有几个好的静态网站生成器,如Jekyll(Ruby)和Hugo(Go)。我们选择JBake(Java),因为。

  1. 我们的网站现在用Maven构建(mvn generate-resources)。

    *不需要安装任何东西。甚至不需要JBake。*每个人都用相同版本的JBake构建,正如在pom.xml

    而且速度很快:即使是150个输出页面的mvn clean 构建,在我的机器上也只需要20秒。

  2. 下面都是Java

    编写条件表达式是很直接的。API(String.substring(), ...)是熟悉的。日期格式化(d MMMM yyyy)和正则表达式的行为符合预期。

    而最重要的是,错误信息很清晰。

8年来,我一直用Awestruct(Ruby)编写网站。但我从来没有花时间好好学习Ruby,所以每一次修改都需要花费数小时的时间进行试验和错误。我不能只是阅读错误信息并修复它。这不是Ruby的错。而是因为我从来没有花过几天时间来真正学习Ruby。有了JBake,我在很短的时间内就能修复错误:不再试错了。

什么是JBake?

JBake是一个静态网站生成器,有很多选项。

  • 用Maven或Gradle构建。

    我们选择Maven,因为我们所有的仓库都用Maven构建(尽管两个OptaPlanner Quickstarts也用Gradle构建,因为OptaPlanner也支持Gradle)。

  • 用Asciidoc、Markdown或HTML编写内容。

    我们选择Asciidoc是因为它比Markdown更丰富、更可靠。而且,我们所有的文档都是用Asciidoc写的。

  • 用Freemarker、Thymeleaf或Groovy创建模板。

    我们选择Freemarker是因为它是一个强大的、经过战斗考验的模板引擎。

技巧和窍门

这些是建立一个高级静态网站的常见任务,以及如何在JBake-Freemarker中实现每个任务。你甚至可以把这些称为JBake设计模式

使用宏来渲染共享内容

几乎我们所有的模板都显示相同的最新发布面板。

一个Freemarker模板是完美的,可以避免重复自己(DRY)。

  1. 用一个输出HTML的宏来创建templates/macros.ftl

    1

    2

    3

    4

    5

    6

    <#macro latestReleases>

        <div class="panel panel-default">

            <div class="panel-heading">Latest release</div>

            ...

        </div>

    </#macro>

  2. 然后在*.ftl 模板中使用它。

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    <#import "macros.ftl" as macros>

    ...

    <div class="row">

        <div class="col-md-9">

            ...

        </div>

        <div class="col-md-3">

            <@macros.latestReleases/>

        </div>

    </div>

使用数据文件来添加视频、事件或其他不稳定的数据

有些数据变化太频繁,无法在内容或模板文件中维护。

一个数据文件,例如一个简单的*.yml 文件,可以很好地保存这种不稳定的数据。

  1. 创建data/videos.yml

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    11

    - youtubeId: blK7gxqu2B0

    title: "Unit testing constraints"

    ...

    - youtubeId: gIaHtATz6n8

    title: "Maintenance scheduling"

    ...

    - youtubeId: LTkoaBk-P6U

    title: "Vaccination appointment scheduling"

    ...

  2. 然后在ftl 模板中使用它。

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    11

    12

    13

    14

    <#assign videos = data.get('videos.yml').data>

    <div class="panel panel-default">

        <div class="panel-heading">Latest videos</div>

        <div class="panel-body">

            <ul>

                <#list videos[0..6] as video>

                    <li>

                    </li>

                </#list>

            </ul>

        </div>

    </div>

布局的继承性

所有的HTML页面通常共享相同的HTML头部(元数据)、标题(导航)和页脚。这些很适合于base.ftl 布局,并由所有其他模板扩展。

即使大多数内容使用normalBase.ftl ,但所有的用例页面都有单独的useCaseBase.ftl 模板,如车辆路由问题(VRP)维护调度轮班安排

使用带有<\#nested> 的宏来建立布局继承。

  1. 创建templates/base.ftl

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    11

    12

    13

    14

    15

    16

    <#macro layout>

        <html>

            <head>

              ...

            </head>

            <body>

                <div>

                    ... <#-- header -->

                </div>

                <#nested>

                <div>

                  ... <#-- footer -->

                </div>

            </body>

        </html>

    </#macro>

  2. templates/useCaseBase.ftl 中扩展它并引入自定义属性related_tag

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    11

    12

    13

    14

    15

    16

    17

    18

    <#import "base.ftl" as parent>

    <@layout>${content.body}</@layout>

    <#macro layout>

        <@parent.layout>

            <h1>${content.title}</h1>

            <#nested>

            <h2>Related videos</h2>

            <#assign videos = data.get('videos.yml').data>

            <#assign relatedVideos = videos?filter(video -> video.tags.contains(content.related_tag))>

            <ul>

                <#list relatedVideos as video>

                    <li><a href="youtu.be/${video.youtubeId}">${video.title}</a></li>

                </#list>

            </ul>

        </@parent.layout>

    </#macro>

  3. 创建使用该模板的用例页content/vehicleRoutingProblem.adoc ,并设置该related_tag 属性。

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    = Vehicle Routing Problem

    :jbake-type: useCaseBase

    :jbake-related_tag: vehicle routing

    The Vehicle Routing Problem (VRP) optimizes the routes of delivery trucks,

    cargo lorries, public transportation (buses, taxis and airplanes)

    or technicians on the road, by improving the order of the visits.

    This routing optimization heavily reduces driving time and fuel consumption compared to manual planning:

    ...

开始吧

自己试试吧。要建立www.optaplanner.org网站,运行这些命令。

1

2

3

4

5

6

$ git clone https:

...

$ cd optaplanner-website

$ mvn clean generate-resources

...

$ firefox target/website/index.html

或者看一下源代码