惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
O
OpenAI News
AI
AI
Cloudbric
Cloudbric
Security Archives - TechRepublic
Security Archives - TechRepublic
H
Heimdal Security Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Threat Research - Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
腾讯CDC
S
SegmentFault 最新的问题
宝玉的分享
宝玉的分享
Cyberwarzone
Cyberwarzone
博客园 - 【当耐特】
TaoSecurity Blog
TaoSecurity Blog
The Cloudflare Blog
V
Visual Studio Blog
Forbes - Security
Forbes - Security
Apple Machine Learning Research
Apple Machine Learning Research
Spread Privacy
Spread Privacy
L
LINUX DO - 最新话题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
K
Kaspersky official blog
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
The Last Watchdog
The Last Watchdog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园_首页
Cisco Talos Blog
Cisco Talos Blog
Security Latest
Security Latest
量子位
W
WeLiveSecurity
V
V2EX
L
Lohrmann on Cybersecurity
S
Security @ Cisco Blogs
小众软件
小众软件
The Hacker News
The Hacker News
阮一峰的网络日志
阮一峰的网络日志
H
Hacker News: Front Page
博客园 - 聂微东
T
Troy Hunt's Blog
S
Schneier on Security
有赞技术团队
有赞技术团队
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
Webroot Blog
Webroot Blog
P
Privacy International News Feed
Jina AI
Jina AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

Bin Wang - My Personal Blog

Travel Back to China: 2026 Edition | Bin Wang TCode: An AI Coding Agent Leverages Neovim and Tmux | Bin Wang My 2025 in Review | Bin Wang Music Video Generation with AI | Bin Wang Home Network Setup with OpenWrt and VLANs | Bin Wang Fix ZFS Linux Kernel Dependency on Arch Linux | Bin Wang A Rust CLI Program, Use It with LLM and Convert It to Web UI | Bin Wang My First Rust Project | Bin Wang Download Message Images from Seesaw | Bin Wang My Workflow to Review Articles with LLMs | Bin Wang Why Consensus Shortcuts Fail in Distributed Systems | Bin Wang Improve Books Section of My Blog | Bin Wang Use OpenAPI Instead of MCP for LLM Tools | Bin Wang Travel Back To China: 2025 Edition | Bin Wang Replace A Dead Node in My High Availability Cluster | Bin Wang A 2-Year Reflection for 2023 and 2024 | Bin Wang Jepsen Test on Patroni: A PostgreSQL High Availability Solution | Bin Wang My MacOS Essentials | Bin Wang Source Code of RSS Brain is Available | Bin Wang
SBT Task to Build Frontend Components | Bin Wang
2024-09-13 · via Bin Wang - My Personal Blog

Table of Contents

  1. Frontend Package Management and Build
  2. SBT Task to Trigger Build and Package Dist Files
  3. Serve Resource Files in Http Server

ScalaSBTJavascriptCSSfrontendwebpacknpm

Even when writing a website using something other than Javascript to render content from the server, sometimes it’s inevitable to have some Javascript or CSS code. So managing Javascript dependencies and build packages is needed. The easiest way may be to just not use any tool: download all the dependency files into a directory and import them in the html file directly. That’s what I was doing for RSS Brain before. But it gets messy pretty quickly and it’s hard to keep track of the dependencies. So it’s time for me to resolve the problem. Since the project is written in Scala, I’ll note down how I do it with Scala’s build tool SBT.

Frontend Package Management and Build

I put all the frontend related code into a separate sub-directory and treat it like a frontend project. This makes things much easier and less hacky. I use npm to manage the dependencies and use webpack to build it. Here is a simplified example of the code tree structure from my project RSS Brain:

▾ js/
  ▾ css/
      google-fonts.css
      main.css
      pico.jade.min.css
  ▾ dist/
      f20305dee9d396fea5c7.ttf
      f5ef242406fdcf40a232.otf
      main.css
      main.js
      main.js.LICENSE.txt
  ▾ fonts/
      google-material-icons-outlined.otf
      google-material-icons.ttf
  ▸ node_modules/
  ▾ src/
      boolean-checkbox.js
      error-handler.js
      global-htmx.js
      index.js
      match-id.js
      popover-menu.js
      register-service-worker.js
      service-worker.js
      set-theme.js
      source-images.js
    package-lock.json
    package.json
    readme.md
    webpack.config.js
▸ project/
▸ src/
  build.sbt
  LICENSE.txt
	readme.md

You can see other than the js directory, it’s a pretty standard structure for a Scala project managed by SBT.

When you look into the js directory, it’s a frontend project managed by npm and built with webpack.

js/src/index.js bundles all the dependencies in node modules and local files. Here is an example:

// css

import 'somment/somment.css';
import 'lite-youtube-embed/src/lite-yt-embed.css';
import 'toastify-js/src/toastify.css';
import '../css/google-fonts.css';
import '../css/pico.jade.min.css';
import '../css/main.css';

// js
import './boolean-checkbox.js';

import 'htmx.org';
import './global-htmx.js';

import Alpine from 'alpinejs';
window.Alpine = Alpine;

import * as FloatingUIDOM from '@floating-ui/dom';
window.FloatingUIDOM = FloatingUIDOM;

import 'lite-youtube-embed';
import '@splidejs/splide';
import Toastify from 'toastify-js';
window.Toastify = Toastify;

import DOMPurify from 'dompurify';
window.DOMPurify = DOMPurify;

import 'imgs-html';
import 'somment';

import './error-handler.js';
import './popover-menu.js';
import './match-id.js';
import './set-theme.js';
import './source-images.js';
import './register-service-worker.js';

Alpine.start();

Here is an example of webpack.config.js:

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      {
        // If you enable `experiments.css` or `experiments.futureDefaults`, please uncomment line below
        // type: "javascript/auto",
        test: /\.(sa|sc|c)ss$/i,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
        ],
      },
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

Since this is more related to frontend tech and is very basic, I will not go too much into details. But the point is, when you run npx webpack under the js directory, it will build bundled files into js/dist. We will write a SBT task to trigger this command and copy the dist files into resources to package.

SBT Task to Trigger Build and Package Dist Files

SBT is very flexible since you can basically write Scala code to define the tasks. Here we define the first task to install npm dependencies and trigger webpack build (in build.sbt):

lazy val webpack = taskKey[Unit]("Run webpack in js directory")
webpack :=  {
  val workDir = new File("./js")
  Process("npm" :: "install" :: Nil, workDir) #&& Process("npx" :: "webpack" :: Nil, workDir) !
}

It defines a task called webpack, so when you run sbt webpack, it will run npm install && npx webpack under js.

Then we define another task to copy all the dist files to generated resource directory:

Compile / resourceGenerators += Def.task {
  webpack.value
  val file = (Compile / resourceManaged).value / "webview" / "static" / "dist"
  IO.copyDirectory(new File("./js/dist"), file, overwrite = true)
  IO.listFiles(file).toSeq
}.taskValue

Here we added some steps when SBT generates resource files: first we let it run the webpack task we defined above, then copy all the files under js/dist to webview/static/dist under generated resources. Here resources means Java resource files, like the files under src/main/resources, but auto-generated to target/scala-2.13/resource_managed and will be packaged together as resource files.

So when you run sbt package here, the generated jar package will include all those files as resource files. For example, in my project, the generated jar package has these if you open it with vim (which can view zipped packages):

81663 webview/static/dist/f20305dee9d396fea5c7.ttf
81664 webview/static/dist/f5ef242406fdcf40a232.otf
81665 webview/static/dist/main.css
81666 webview/static/dist/main.js
81667 webview/static/dist/main.js.LICENSE.txt

Serve Resource Files in Http Server

Now you can serve the files under webview/static/dist with your web server. Different web servers or frameworks do it differently. Here is an example of http4s:

// include the following route into the http4s web server
// IMPORTANT: every resource file under `/webview` will be publicly accessible
val assetsRoutes = resourceServiceBuilder[IO]("/webview").toRoutes

Then you can use them in HTML:

<link rel="stylesheet" href="/static/dist/main.css">
<script src="/static/dist/main.js" defer="defer"></script>