Changes
23 changed files (+855/-2)
-
-
@@ -19,3 +19,13 @@ # nixfmt does not support tab indentation, as Nix's (the language) multiline string# cannot handle tab indentation at this moment. indent_style = space indent_size = 2 [*.{zig,zon}] # Zig cannot handle tab indentations. indent_style = space indent_size = 4 [*.elm] # Elm cannot handle tab indentations, and elm-format cannot configure indent size. indent_style = space indent_size = 4
-
-
.gitignore (new)
-
@@ -0,0 +1,16 @@# Copyright 2026 Shota FUJI # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. # # SPDX-License-Identifier: MPL-2.0 # Zig build internal artifacts. .zig-cache # Default output directory for Zig build. zig-out # Elm build internal artifacts. elm-stuff
-
-
Caddyfile (new)
-
@@ -0,0 +1,14 @@# Copyright 2026 Shota FUJI # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. # # SPDX-License-Identifier: MPL-2.0 :8080 { encode root * zig-out try_files {path} /index.html file_server }
-
-
build.zig (new)
-
@@ -0,0 +1,67 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 const std = @import("std"); const CustomElements = @import("./build/CustomElements.zig"); const Elm = @import("./build/Elm.zig"); pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const custom_elements_compiler = b.option( []const u8, "custom-elements-compiler", "Path to custom elements compiler binary", ) orelse "custom_elements_compiler"; const elm_main = elm_main: { const compile = Elm.init(b, .{ .optimize = optimize, }); compile.addElmSourceFile(b.path("src/Main.elm")); break :elm_main compile.output_js; }; const elements_js = elements_js: { const compile = CustomElements.init(b, .{ .entrypoint = b.path("src/elements/main.js"), .exe = custom_elements_compiler, }); break :elements_js compile.output_js; }; const index_html = index_html: { const html = b.path("src/index.html"); break :index_html html; }; const dist = dist: { const dir = b.addWriteFiles(); _ = dir.addCopyFile(index_html, "index.html"); _ = dir.addCopyFile(elm_main, "main.js"); _ = dir.addCopyFile(elements_js, "elements.js"); break :dist dir; }; // "zig build" (default step) { const install = b.addInstallDirectory(.{ .source_dir = dist.getDirectory(), .install_dir = .{ .custom = "" }, .install_subdir = "", }); b.default_step.dependOn(&install.step); } }
-
-
build/CustomElements.zig (new)
-
@@ -0,0 +1,33 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 const CustomElements = @This(); const std = @import("std"); compiler: *std.Build.Step.Run, output_js: std.Build.LazyPath, pub const InitOptions = struct { exe: []const u8, entrypoint: std.Build.LazyPath, }; pub fn init(b: *std.Build, opts: InitOptions) CustomElements { const compiler = b.addSystemCommand(&.{opts.exe}); const output_js = compiler.addPrefixedOutputFileArg("-outfile=", "elements.js"); _ = compiler.addPrefixedDepFileOutputArg("-depfile=", "elements.d"); compiler.addPrefixedFileArg("-entrypoint=", opts.entrypoint); return .{ .compiler = compiler, .output_js = output_js, }; }
-
-
-
@@ -0,0 +1,10 @@# Copyright 2026 Shota FUJI # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. # # SPDX-License-Identifier: MPL-2.0 # Built binary custom_elements_compiler
-
-
-
@@ -0,0 +1,30 @@# Copyright 2026 Shota FUJI # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. # # SPDX-License-Identifier: MPL-2.0 { buildGoModule, lib, }: buildGoModule { name = "custom-elements-compiler"; src = with lib.fileset; toSource { root = ./.; fileset = unions [ ./main.go ./go.mod ./go.sum ]; }; vendorHash = "sha256-p6gqavG8fbTdAy91VCViwP8/e03vlzCegvAdSGH+dtg="; meta.mainProgram = "custom-elements-compiler"; }
-
-
-
@@ -0,0 +1,15 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 module custom_elements_compiler go 1.25.7 require github.com/evanw/esbuild v0.27.4 require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
-
-
-
@@ -0,0 +1,4 @@github.com/evanw/esbuild v0.27.4 h1:8opEixKkH9EDsdjxC/aPmpk1KPwQOcyknDo5m5xIFxI= github.com/evanw/esbuild v0.27.4/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-
-
-
@@ -0,0 +1,7 @@Copyright 2026 Shota FUJI This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. SPDX-License-Identifier: MPL-2.0
-
-
-
@@ -0,0 +1,133 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 package main import ( "flag" "fmt" "os" "strings" "github.com/evanw/esbuild/pkg/api" ) func main() { outfile := flag.String("outfile", "", "Path to the output file") depfile := flag.String("depfile", "", "Path to the depfile") entrypoint := flag.String("entrypoint", "", "Path to the input file") flag.Parse() if *outfile == "" { fmt.Fprintln(os.Stderr, "outfile must be set") os.Exit(1) } if *entrypoint == "" { fmt.Fprintln(os.Stderr, "entrypoint must be set") os.Exit(1) } infiles := make([]string, 0, 64) recordInfilePlugin := api.Plugin{ Name: "record_infile", Setup: func(build api.PluginBuild) { build.OnLoad(api.OnLoadOptions{Filter: ".*"}, func(args api.OnLoadArgs) (api.OnLoadResult, error) { infiles = append(infiles, args.Path) return api.OnLoadResult{}, nil }) }, } saneCssPlugin := api.Plugin{ Name: "css_string", Setup: func(build api.PluginBuild) { build.OnLoad(api.OnLoadOptions{Filter: "\\.css$"}, func(args api.OnLoadArgs) (api.OnLoadResult, error) { text, err := os.ReadFile(args.Path) if err != nil { return api.OnLoadResult{}, err } result := api.Transform(string(text), api.TransformOptions{ Loader: api.LoaderCSS, MinifyWhitespace: true, MinifyIdentifiers: true, MinifySyntax: true, }) if len(result.Errors) > 0 { msg := api.FormatMessages(result.Errors, api.FormatMessagesOptions{ Kind: api.ErrorMessage, Color: false, }) fmt.Printf("%s\n", strings.Join(msg, "\n")) return api.OnLoadResult{}, fmt.Errorf("CSS minification failed") } contents := string(result.Code) return api.OnLoadResult{ Contents: &contents, Loader: api.LoaderText, }, nil }) }, } result := api.Build(api.BuildOptions{ EntryPoints: []string{*entrypoint}, Bundle: true, Outfile: *outfile, Write: true, MinifyWhitespace: true, MinifyIdentifiers: true, MinifySyntax: true, Plugins: []api.Plugin{recordInfilePlugin, saneCssPlugin}, }) if len(result.Errors) > 0 { for _, err := range result.Errors { fmt.Fprintln(os.Stderr, err.Text) } os.Exit(1) } if len(result.OutputFiles) == 0 { fmt.Fprintln(os.Stderr, "No output files were generated") os.Exit(1) } if len(result.OutputFiles) > 1 { fmt.Fprintln(os.Stderr, "More than one file were generated") os.Exit(1) } if *depfile != "" { outfile := result.OutputFiles[0] depfile, err := os.OpenFile(*depfile, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { fmt.Fprintf(os.Stderr, "Failed to generate depfile: %v\n", err) os.Exit(2) } defer depfile.Close() fmt.Fprintf(depfile, "%s:", outfile.Path) for _, infile := range infiles { fmt.Fprintf(depfile, " \\\n\t%s", infile) } fmt.Fprint(depfile, "\n") } os.Exit(0) }
-
-
build/Elm.zig (new)
-
@@ -0,0 +1,57 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 const Elm = @This(); const std = @import("std"); elm_make: *std.Build.Step.Run, output_js: std.Build.LazyPath, pub const InitOptions = struct { /// `Debug` enables Elm's time-travel UI. `Release*` runs ClosureCompiler over /// built JavaScript files. optimize: std.builtin.OptimizeMode = .ReleaseFast, }; pub fn init(b: *std.Build, opts: InitOptions) Elm { const elm_make = b.addSystemCommand(&.{ "elm", "make" }); if (opts.optimize == .Debug) { elm_make.addArg("--debug"); } else { elm_make.addArg("--optimize"); } elm_make.expectExitCode(0); const output_js = elm_make.addPrefixedOutputFileArg("--output=", "elm.js"); if (opts.optimize == .Debug) { return .{ .elm_make = elm_make, .output_js = output_js, }; } else { const closure_compiler = b.addSystemCommand(&.{"closure-compiler"}); closure_compiler.addPrefixedFileArg("--js=", output_js); const minified_js = closure_compiler.addPrefixedOutputFileArg("--js_output_file=", "elm.min.js"); return .{ .elm_make = elm_make, .output_js = minified_js, }; } } /// Add Elm source code file. You have to add every source files directly or indirectly imported /// by a main module. pub fn addElmSourceFile(self: Elm, path: std.Build.LazyPath) void { self.elm_make.addFileArg(path); }
-
-
default.nix (new)
-
@@ -0,0 +1,54 @@# Copyright 2026 Shota FUJI # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. # # SPDX-License-Identifier: MPL-2.0 { # via overlay (Flake.) # https://github.com/jeslie0/mkElmDerivation mkElmDerivation, lib, closurecompiler, elmPackages, zig, custom-elements-compiler, }: mkElmDerivation { name = "watch-strap-template-builder"; nativeBuildInputs = [ custom-elements-compiler # > Delightful language for reliable webapps # https://elm-lang.org/ elmPackages.elm # > Tool for making JavaScript download and run faster # https://developers.google.com/closure/compiler/ closurecompiler # > General-purpose programming language and toolchain for maintaining robust, optimal, # > and reusable software # https://ziglang.org/ zig ]; src = with lib.fileset; toSource { root = ./.; fileset = unions [ ./src ./build.zig ./build ./elm.json ]; }; buildPhase = '' zig build "-j$NIX_BUILD_CORES" -Doptimize=ReleaseFast -Dcustom-elements-compiler=${lib.getExe custom-elements-compiler} --prefix $out ''; }
-
-
-
@@ -13,6 +13,18 @@ "newLineKind": "lf","json": {}, // "A stricter gofmt" // github.com/mvdan/gofumpt "gofumpt": {}, // Oxfmt: Prettier-compatible formatter // https://oxc.rs/ "oxc": {}, // Configurable, smart and fast CSS, SCSS, Sass and Less formatter // https://github.com/g-plane/malva "malva": {}, "exec": { "cwd": "${configDir}",
-
@@ -21,11 +33,26 @@ {"command": "nixfmt", "exts": ["nix"], }, { "command": "zig fmt --stdin", "exts": ["zig"], }, { "command": "zig fmt --zon --stdin", "exts": ["zon"], }, { "command": "elm-format --stdin", "exts": ["elm"], }, ], }, "plugins": [ "https://plugins.dprint.dev/json-0.21.1.wasm", "https://plugins.dprint.dev/exec-0.6.0.json@a054130d458f124f9b5c91484833828950723a5af3f8ff2bd1523bd47b83b364", "https://plugins.dprint.dev/jakebailey/gofumpt-v0.0.7.wasm", "https://plugins.dprint.dev/oxc-0.12.0.wasm", "https://plugins.dprint.dev/g-plane/malva-v0.15.2.wasm", ], }
-
-
elm.json (new)
-
@@ -0,0 +1,24 @@{ "type": "application", "source-directories": [ "src" ], "elm-version": "0.19.1", "dependencies": { "direct": { "elm/browser": "1.0.2", "elm/core": "1.0.5", "elm/html": "1.0.1", "elm/url": "1.0.0" }, "indirect": { "elm/json": "1.1.4", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.5" } }, "test-dependencies": { "direct": {}, "indirect": {} } }
-
-
elm.json.license (new)
-
@@ -0,0 +1,7 @@Copyright 2026 Shota FUJI This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. SPDX-License-Identifier: MPL-2.0
-
-
-
@@ -1,5 +1,71 @@{ "nodes": { "elm-spa": { "inputs": { "nixpkgs": [ "mkElmDerivation", "nixpkgs" ] }, "locked": { "lastModified": 1706301604, "narHash": "sha256-n6LDjnPCTLbKTrRgeZhlLTfY6V45xNYcb4NYEMuO4jg=", "owner": "jeslie0", "repo": "elm-spa", "rev": "4c82e18d5fcf9d4c027f0ef0e89204dd87584f7f", "type": "github" }, "original": { "owner": "jeslie0", "repo": "elm-spa", "type": "github" } }, "elm-watch": { "inputs": { "nixpkgs": [ "mkElmDerivation", "nixpkgs" ], "npm-fix": "npm-fix", "npmlock2nix": "npmlock2nix" }, "locked": { "lastModified": 1706304401, "narHash": "sha256-992cypnhoRbsGkDc5/X241rafBML4EP0EuT6cBcaY/8=", "owner": "jeslie0", "repo": "elm-watch", "rev": "2f1c6c0e69b163c15e2ce66f543c38021b2a0ea3", "type": "github" }, "original": { "owner": "jeslie0", "repo": "elm-watch", "type": "github" } }, "mkElmDerivation": { "inputs": { "elm-spa": "elm-spa", "elm-watch": "elm-watch", "nixpkgs": [ "nixpkgs" ] }, "locked": { "lastModified": 1773542168, "narHash": "sha256-eELYk26ZnvzykrMt5t+w+sANKwT3dhl0xNb13/1XXa4=", "owner": "jeslie0", "repo": "mkElmDerivation", "rev": "d1c2a8ac6fb78c1316bd0ef9e31c66905b45cdd2", "type": "github" }, "original": { "owner": "jeslie0", "repo": "mkElmDerivation", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1773597492,
-
@@ -16,8 +82,47 @@ "repo": "nixpkgs","type": "github" } }, "npm-fix": { "inputs": { "nixpkgs": [ "mkElmDerivation", "elm-watch", "nixpkgs" ] }, "locked": { "lastModified": 1706304213, "narHash": "sha256-XN9ESRSOANR0iFbEMMY1C1jvgZlYJsXQYVAHxxRmn+c=", "owner": "jeslie0", "repo": "npm-lockfile-fix", "rev": "e9851274afa12b04d98e694ed089aa9cde8d7349", "type": "github" }, "original": { "owner": "jeslie0", "repo": "npm-lockfile-fix", "type": "github" } }, "npmlock2nix": { "flake": false, "locked": { "lastModified": 1673447413, "narHash": "sha256-sJM82Sj8yfQYs9axEmGZ9Evzdv/kDcI9sddqJ45frrU=", "owner": "nix-community", "repo": "npmlock2nix", "rev": "9197bbf397d76059a76310523d45df10d2e4ca81", "type": "github" }, "original": { "owner": "nix-community", "repo": "npmlock2nix", "type": "github" } }, "root": { "inputs": { "mkElmDerivation": "mkElmDerivation", "nixpkgs": "nixpkgs" } }
-
-
-
@@ -9,10 +9,19 @@{ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; mkElmDerivation = { url = "github:jeslie0/mkElmDerivation"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs }: { self, nixpkgs, mkElmDerivation, }: let systems = [ "x86_64-linux"
-
@@ -48,7 +57,26 @@ # https://ziglang.org/zig ]; in { rec { packages = forEachSystems ( { system, ... }: let pkgs = import nixpkgs { inherit system; overlays = [ mkElmDerivation.overlays.default (final: prev: { custom-elements-compiler = prev.callPackage ./build/CustomElements { }; }) ]; }; in { default = pkgs.callPackage ./default.nix { }; custom-elements-compiler = pkgs.custom-elements-compiler; } ); formatter = forEachSystems ( { pkgs, ... }: pkgs.buildFHSEnv {
-
@@ -69,6 +97,38 @@dprint = { type = "app"; program = pkgs.lib.getExe pkgs.dprint; }; # Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS # https://caddyserver.com/ caddy = { type = "app"; program = pkgs.lib.getExe pkgs.caddy; }; } ); devShells = forEachSystems ( { pkgs, system }: { default = pkgs.mkShell { packages = with pkgs; [ # > Language server implementation for Elm # https://github.com/elm-tooling/elm-language-server elmPackages.elm-language-server # > Official language server for the Go language # https://github.com/golang/tools/tree/master/gopls gopls # > Zig LSP implementation + Zig Language Server # https://github.com/zigtools/zls zls ] ++ packages.${system}.default.nativeBuildInputs ++ packages.${system}.custom-elements-compiler.nativeBuildInputs; }; } );
-
-
src/Main.elm (new)
-
@@ -0,0 +1,96 @@-- Copyright 2026 Shota FUJI -- -- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at https://mozilla.org/MPL/2.0/. -- -- SPDX-License-Identifier: MPL-2.0 module Main exposing (main) import Browser import Browser.Navigation exposing (Key) import Html exposing (node, text) import Url exposing (Url) main : Program Flags Model Msg main = Browser.application { init = init , view = view , update = update , subscriptions = subscriptions , onUrlRequest = UrlRequested , onUrlChange = UrlChanged } -- FLAGS type alias Flags = () -- MODEL type alias Model = { url : Url , key : Key } init : Flags -> Url -> Key -> ( Model, Cmd Msg ) init _ url key = ( { url = url, key = key }, Cmd.none ) -- UPDATE type Msg = NoOp | UrlRequested Browser.UrlRequest | UrlChanged Url update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of NoOp -> ( model, Cmd.none ) UrlRequested (Browser.Internal url) -> ( model, Browser.Navigation.replaceUrl model.key (Url.toString url) ) UrlRequested (Browser.External href) -> ( model, Browser.Navigation.load href ) UrlChanged url -> ( { model | url = url }, Cmd.none ) -- VIEW view : Model -> Browser.Document Msg view _ = { title = "" , body = [ node "x-text" [] [ text "Hello, World!" ] ] } -- SUBSCRIPTIONS subscriptions : Model -> Sub Msg subscriptions _ = Sub.none
-
-
src/elements/main.js (new)
-
@@ -0,0 +1,11 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 import { XText } from "./x-text.js"; customElements.define("x-text", XText);
-
-
src/elements/x-text.css (new)
-
@@ -0,0 +1,19 @@/* * Copyright 2026 Shota FUJI * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. * * SPDX-License-Identifier: MPL-2.0 */ :host { background-color: tomato; } .text { font-weight: bold; color: white; }
-
-
src/elements/x-text.js (new)
-
@@ -0,0 +1,30 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 import css from "./x-text.css"; export class XText extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: "open", }); const style = document.createElement("style"); style.textContent = css; shadow.appendChild(style); const span = document.createElement("span"); span.classList.add("text"); shadow.appendChild(span); const slot = document.createElement("slot"); span.appendChild(slot); } }
-
-
src/index.html (new)
-
@@ -0,0 +1,24 @@<!DOCTYPE html> <!-- Copyright 2026 Shota FUJI This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. SPDX-License-Identifier: MPL-2.0 --> <html lang="en-US"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="color-scheme" content="light dark" /> <title>Watch Strap Template Builder</title> <script src="/main.js"></script> <script type="module"> import("./elements.js").then(() => { Elm.Main.init(); }); </script> </head> </html>
-