web_modules 0.5.1

Pure-Rust, buildless toolchain for ES modules and Web Components
Documentation
[package]
name = "web_modules"
version = "0.5.1"
authors = ["Stefan Grönke <stefan@gronke.net>"]
homepage = "https://github.com/gronke/web_modules"
repository = "https://github.com/gronke/web_modules"
documentation = "https://docs.rs/web_modules"
description = "Pure-Rust, buildless toolchain for ES modules and Web Components"
categories = ["development-tools", "web-programming"]
keywords = ["typescript", "esm", "importmap", "web_modules", "buildless"]
license = "MIT"
edition = "2021"
# MSRV tracks oxc (the transform/minify toolchain), which raises it as it evolves.
rust-version = "1.95"
# Leading slashes anchor to the crate root — bare patterns (gitignore semantics) match at any
# depth and would pull in stray files like examples/tauri/README.md.
include = ["/src/**/*.rs", "/Cargo.toml", "/README.md", "/LICENSE"]
publish = true

# docs.rs: document the full lean toolchain so the whole API renders. The heavy, opt-in
# `bundle`/rolldown tree stays out (it's a thin wrapper, described in prose).
[package.metadata.docs.rs]
features = ["full"]

[features]
# Specialised for building web modules + simple web apps: TypeScript, SCSS and
# Tera are on by default. None of these pull in a server runtime, so a `build.rs`
# that only enables `default` stays free of axum/tokio.
default = ["typescript", "scss", "tera", "builder", "symlink-move"]

# TypeScript / modern-JS → browser JS via oxc (incl. legacy decorators for Lit).
typescript = ["dep:oxc_allocator", "dep:oxc_ast", "dep:oxc_ast_visit", "dep:oxc_parser", "dep:oxc_semantic", "dep:oxc_transformer", "dep:oxc_codegen", "dep:oxc_span"]
# SCSS → CSS via grass.
scss = ["dep:grass"]
# HTML templating (e.g. importmap injection) via Tera.
tera = ["dep:tera"]
# Production JS minification via oxc_minifier (reuses the oxc parse/codegen stack).
minify = ["typescript", "dep:oxc_minifier"]
# Generate favicons / app icons from a source PNG.
icons = ["dep:image", "dep:ico"]
# Emit TypeScript declaration files (.d.ts) via oxc_isolated_declarations.
dts = ["typescript", "dep:oxc_isolated_declarations"]
# Read/merge XLIFF localisation files (e.g. lit-localize interchange).
i18n = ["dep:quick-xml"]
# Pre-compress emitted assets to `.gz` sidecars (gzip), for static serving with `Content-Encoding`.
compress = ["dep:flate2"]
# Axum web-server integration: a `Frontend` router factory that serves the built
# frontend from baked-in (embedded) assets, or — with `dev` — compiled on the fly.
# Embedded serving pulls no compiler, so release binaries stay lean. (Other web
# servers can be added as parallel features later.)
axum = ["dep:axum", "dep:tokio", "dep:mime_guess", "dep:include_dir"]
# Dev server on top of `axum`: compile TS/SCSS on the fly, watch the source tree,
# live-reload the browser.
dev = ["axum", "typescript", "scss", "dep:notify", "dep:tower-livereload"]
# The `web-modules` CLI binary (dev / build / vendor / ci / npm). `npm-utils/cli` powers the
# `npm` passthrough; it stays behind this opt-in feature so the library path never pulls clap.
# `tera` is pulled in so a plain `--features cli` build renders `.tera` in both `dev` and `build`
# (they must behave identically); the released binary's `full` already has it.
cli = ["dev", "minify", "tera", "dep:clap", "npm-utils/cli"]
# Opt-in: let the `web-modules build` subcommand read its config from `WEB_MODULES_*` environment
# variables (clap's `env`). Off by default so a plain `--features cli` install stays minimal — clap's
# env codegen is only compiled when asked for. `clap?/env` flips the feature on only if clap is
# already enabled (it is, via `cli`'s `dep:clap`), so `--features env` alone never pulls a bin-less
# clap. Pair with `cli` — it's part of `full`, which the released binary is built with.
env = ["clap?/env"]
# Re-export `npm-utils` as `web_modules::npm`. `npm-utils` is always compiled (the core `vendor`
# path needs it); this flag only exposes the public re-export, so consumers can reach the npm API
# (resolve / install a node_modules tree / npm ci) without taking a separate dependency.
npm = []
# CommonJS→ESM bundling via rolldown (the embedded, oxc-based Rust bundler): bundle an app plus its
# `node_modules/` (CommonJS and all) into one browser ESM file. Heavy and **opt-in** — the default
# toolchain stays a buildless, no-bundler ESM workflow; pull this in only when you need CJS→ESM.
# Independent of `npm`: pair with `npm` (→ `web_modules::npm`) for the install→bundle pipeline.
bundle = ["dep:rolldown", "dep:tokio"]
# Fluent `web_modules::Build` / `Dev` builders — the promoted, developer-facing entry that wraps the
# `BuildOptions` / `build()` and dev-server structs underneath. On by default; opt out for the bare
# struct API. Dependency-free; the builders are present only alongside the features that supply the
# structs they wrap (`typescript` for `Build`, `dev` for `Dev`).
builder = []
# Emit `tracing::warn!` for paths dropped by the reject list (built output / serving / bundling).
# Off by default (no `tracing` dep on the lean path); a no-op without a subscriber when on.
tracing = ["dep:tracing"]
# The redirect symlink modes (`--symlinks redirect|move`): serving answers 307/308 with the link
# content as the Location. Default-on so the modes are an opt-in CLI/API choice; strip with
# `--no-default-features` for a build where a symlink can never become a redirect.
symlink-move = []
# Everything in the lean toolchain (incl. `env`, so CI's `--features full` lint/test covers the
# env-gated CLI arms). The heavy, opt-in `bundle` is intentionally NOT included, so `full` (and
# `--features full`) builds stay free of the rolldown / second-oxc tree.
full = ["typescript", "scss", "tera", "minify", "icons", "dts", "i18n", "compress", "axum", "dev", "cli", "env", "npm", "builder", "tracing", "symlink-move"]

[dependencies]
# Core — vendoring npm packages + composing import maps + a transitive `node_modules/`
# install (npm_utils::install, incl. install::from_lockfile for `web-modules ci`).
# Also `npm_utils::resolve` (0.6.1+), which backs `npm://` symlink-asset resolution.
npm-utils = "0.6.1"
# The import map derives its `{ "imports": … }` wire shape; serde_json parses and prints it.
serde = { version = "1", features = ["derive"] }
serde_json = "1"
walkdir = "2"
semver = "1"
# The WHATWG URL parser — module-specifier classification asks it whether a specifier
# parses as an absolute URL, the browser's own first resolution step.
url = "2"

# oxc toolchain (TypeScript transform + minify). Optional; pulled by `typescript`/`minify`.
oxc_allocator = { version = "0.138", optional = true }
oxc_ast = { version = "0.138", optional = true }
oxc_ast_visit = { version = "0.138", optional = true }
oxc_parser = { version = "0.138", optional = true }
oxc_semantic = { version = "0.138", optional = true }
oxc_transformer = { version = "0.138", optional = true }
oxc_codegen = { version = "0.138", optional = true }
oxc_span = { version = "0.138", optional = true }
oxc_minifier = { version = "0.138", optional = true }
oxc_isolated_declarations = { version = "0.138", optional = true }

# SCSS + templating. grass is used as a library only (SCSS→CSS); `default-features = false` drops its
# `commandline` default (a clap-based CLI we never invoke) so the lean default/library path stays free
# of the clap stack. `random` is kept (it's in grass's default — Sass `random()`), so SCSS is unchanged.
grass = { version = "0.13", optional = true, default-features = false, features = ["random"] }
tera = { version = "1", optional = true }

# Icons (favicon / app icons) + i18n (XLIFF).
image = { version = "0.25", default-features = false, features = ["png"], optional = true }
ico = { version = "0.5", optional = true }
quick-xml = { version = "0.41", optional = true }
flate2 = { version = "1", optional = true }

# Axum server integration (embedded serving + dev server) + CLI. Optional; off the
# build-only path.
axum = { version = "0.8", optional = true }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net"], optional = true }
include_dir = { version = "0.7", optional = true }
notify = { version = "8", optional = true }
tower-livereload = { version = "0.10", optional = true }
mime_guess = { version = "2", optional = true }
clap = { version = "4", features = ["derive"], optional = true }

# CommonJS→ESM bundling (the `bundle` feature): the embedded, oxc-based Rust bundler. Heavy
# (pulls a second oxc + the rolldown tree), so it's optional and off by default.
rolldown = { version = "1", optional = true }

# Structured logging for reject-list drops (the `tracing` feature). Library-only usage — no
# subscriber pulled — so consumers wire their own. `default-features = false` keeps it lean.
tracing = { version = "0.1", default-features = false, features = ["std"], optional = true }

[[bin]]
name = "web-modules"
path = "src/bin/web-modules.rs"
required-features = ["cli"]

[dev-dependencies]
tempfile = "3"
# Drive the axum routers in integration tests without binding a port
# (`ServiceExt::oneshot` + read the response body). Both are already in the tree via
# axum, dev-only, and not part of the published crate's dependencies.
tower = { version = "0.5", features = ["util"] }
http-body-util = "0.1"
tokio = { version = "1", features = ["rt", "macros"] }
# Bake an embedded (`include_dir!`) root in the traversal tests. Needed directly (not via the
# crate's re-export) because the `include_dir!` macro emits `include_dir::`-qualified paths.
include_dir = "0.7"

[workspace]
members = ["examples/*"]
# Excluded from the `examples/*` glob — each lacks a Cargo.toml the glob would otherwise fail to load:
# - tauri: a nested crate at examples/tauri/src-tauri with its own [workspace] (and a heavyweight
#   webkit2gtk tree), so it can't be a workspace member; it has a dedicated CI job.
# - gh-pages: a pure-frontend demo (TS/SCSS/HTML), built by `web-modules build` in pages.yml, not cargo.
exclude = ["examples/tauri", "examples/gh-pages"]