Skip to content

Commit 2e8e076

Browse files
committed
Release 0.4.0
1 parent 0928af0 commit 2e8e076

5 files changed

Lines changed: 94 additions & 64 deletions

File tree

CHANGELOG.md

Lines changed: 89 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,128 @@
11
# Changelog
22

3-
## 0.3.0
3+
## 0.4.0
44

5-
### Code Splitting
5+
### External Globals
66

7-
Dynamic `import()` expressions are detected during the dependency walk and
8-
split into separate async chunks. Shared modules between the entry chunk and
9-
async chunks are extracted into a common chunk to avoid duplication.
7+
External imports now generate proper global variable access in the IIFE output
8+
instead of being silently stripped. Supports both auto-derived and explicit names:
109

11-
```typescript
12-
const admin = await import('./admin') // → app-admin-c3d4e5f6.js
10+
```elixir
11+
config :volt, external: ["vue"]
12+
# import { ref } from 'vue' → const { ref } = Vue;
13+
14+
config :volt, external: %{"vue" => "MyVue"}
15+
# import { ref } from 'vue' → const { ref } = MyVue;
1316
```
1417

15-
### External Modules
18+
### CSS `@import` Inlining
1619

17-
New `:external` option excludes specifiers from the bundle. Essential for
18-
Phoenix apps where `phoenix`, `phoenix_html`, and `phoenix_live_view` are
19-
provided by the framework:
20+
CSS files with `@import` rules are bundled via LightningCSS's Bundler.
21+
Imports are resolved recursively from disk with proper `@media`/`@supports`/`@layer` wrapping
22+
and `url()` rebasing.
2023

21-
```elixir
22-
config :volt, external: ~w(phoenix phoenix_html phoenix_live_view)
24+
### HTML Entry Points
25+
26+
Entry files can now be HTML — `<script src="...">` tags are extracted
27+
via Floki and used as JS entry points:
28+
29+
```bash
30+
mix volt.build --entry index.html
2331
```
2432

25-
### Centralized Configuration
33+
### `import.meta.glob()`
2634

27-
All config now lives under `config :volt` in your standard `config/*.exs` files,
28-
following the same pattern as `phoenix`, `esbuild`, and `tailwind` hex packages:
35+
Glob patterns are expanded at build time via OXC AST:
2936

30-
```elixir
31-
config :volt,
32-
entry: "assets/js/app.ts",
33-
target: :es2020,
34-
external: ~w(phoenix phoenix_html phoenix_live_view),
35-
aliases: %{"@" => "assets/src"},
36-
tailwind: [css: "assets/css/app.css", sources: [...]]
37+
```typescript
38+
const pages = import.meta.glob('./pages/*.ts')
39+
// → { "./pages/home.ts": () => import("./pages/home.ts"), ... }
40+
41+
const eager = import.meta.glob('./pages/*.ts', { eager: true })
42+
// → static imports with namespace bindings
3743
```
3844

39-
Mix tasks and the DevServer plug read from config automatically.
40-
CLI flags override for one-off use.
45+
### Module Preload
4146

42-
### Plugin System
47+
New `Volt.Preload.tags/2` generates `<link rel="modulepreload">` tags
48+
from the build manifest for production chunk preloading.
4349

44-
New `Volt.Plugin` behaviour with four optional hooks:
45-
- `resolve/2` — remap import specifiers
46-
- `load/1` — provide virtual module content
47-
- `transform/2` — modify compiled output
48-
- `render_chunk/2` — modify final output chunks
50+
### Build Size Reporting
4951

50-
### CSS Modules
52+
Build output now shows gzip sizes:
5153

52-
`.module.css` files are scoped using LightningCSS (via Vize 0.7.0).
53-
Class names, IDs, keyframes, and custom identifiers are properly scoped
54-
through a real CSS parser — no regex.
54+
```
55+
app.js 128.4 KB (gzip: 38.2 KB)
56+
```
5557

56-
### Static Asset Handling
58+
### Bug Fixes
59+
60+
- **HMR**: Watcher cache lookup used mtime 0, so granular Vue SFC
61+
change detection (style-only updates) never worked. Fixed.
62+
- **Vendor URLs**: Scoped packages (`@vue/shared`) had lossy URL encoding
63+
that broke round-trips. Now uses reversible encoding.
64+
- **CSS errors**: Pipeline `compile_css` had no error clause and would
65+
crash on invalid CSS instead of returning an error.
66+
- **`.env` parser**: Replaced hand-rolled parser with Dotenvy for correct
67+
handling of multiline values, variable expansion, and escaping.
68+
- **IIFE injection**: External globals preamble injection now uses OXC AST
69+
to find the function body offset instead of fragile string splitting.
70+
- **Chunk URLs**: Dynamic import rewriting matches by path suffix instead of
71+
basename to avoid collisions between same-named files in different directories.
72+
73+
### Internal Improvements
74+
75+
- Tailwind GenServer lazily initializes QuickBEAM runtime on first call
76+
instead of on application start
77+
- Deduplicated `content_hash`, `file_mtime`, `derive_global_name`,
78+
`extract_vue_imports` across modules
79+
- Vendor cache dir respects `MIX_BUILD_PATH`
80+
- Tailwind bundle path uses `Application.app_dir` instead of compile-time
81+
`:code.priv_dir`
82+
- HTML parsing uses Floki instead of regex
83+
- Dependencies: oxc ~> 0.5.2, vize ~> 0.8.0, floki ~> 0.38, dotenvy ~> 1.1
5784

58-
Images, fonts, SVGs, and other non-code files are handled automatically.
59-
Small files (< 4KB) are inlined as base64 data URIs. Larger files are
60-
served directly in dev and copied with content-hashed filenames in prod.
85+
## 0.3.0
6186

62-
### JSON Imports
87+
### Code Splitting
6388

64-
`import data from './data.json'` compiles to `export default {...}`.
89+
Dynamic `import()` expressions are detected during the dependency walk and
90+
split into separate async chunks. Shared modules between the entry chunk and
91+
async chunks are extracted into a common chunk to avoid duplication.
6592

66-
### Environment Variables
93+
### External Modules
6794

68-
`.env` files are loaded and variables prefixed with `VOLT_` are available
69-
as `import.meta.env.VOLT_*` in source code. Built-in `MODE`, `DEV`, and
70-
`PROD` variables are always available.
95+
New `:external` option excludes specifiers from the bundle.
96+
97+
### Centralized Configuration
7198

72-
### Import Aliases
99+
All config now lives under `config :volt` in your standard `config/*.exs` files:
73100

74101
```elixir
75-
config :volt, aliases: %{"@" => "assets/src"}
102+
config :volt,
103+
entry: "assets/js/app.ts",
104+
target: :es2020,
105+
external: ~w(phoenix phoenix_html phoenix_live_view),
106+
aliases: %{"@" => "assets/src"},
107+
tailwind: [css: "assets/css/app.css", sources: [...]]
76108
```
77109

78-
`import { Button } from '@/components/Button'` resolves to the configured path.
110+
### Plugin System
79111

80-
### Multiple Entry Points
112+
`Volt.Plugin` behaviour with resolve, load, transform, render_chunk hooks.
81113

82-
`--entry` flag is now repeatable for multi-page apps. Each entry produces
83-
its own bundle and manifest entries.
114+
### CSS Modules
84115

85-
### Builder Refactor
116+
`.module.css` scoped via LightningCSS. No regex.
86117

87-
`Volt.Builder` split into focused submodules:
88-
- `Volt.Builder.Resolver` — specifier resolution chain
89-
- `Volt.Builder.Collector` — dependency graph walk with import type detection
90-
- `Volt.Builder.Output` — chunk bundling, file writing, manifest
91-
- `Volt.ChunkGraph` — chunk assignment from module dependency map
118+
### Static Assets, JSON Imports, Env Variables, Import Aliases
92119

93-
### Other Changes
120+
See README for full details.
121+
122+
### Builder Refactor
94123

95-
- Config values use atoms (`:es2020`, `:production`) instead of strings
96-
- DevServer serves static assets with correct MIME types
97-
- `.json` recognized as compilable extension in DevServer and Builder
98-
- Vize upgraded to 0.7.0 for CSS Modules support
124+
Split into `Volt.Builder.Resolver`, `Volt.Builder.Collector`,
125+
`Volt.Builder.Output`, and `Volt.ChunkGraph`.
99126

100127
## 0.2.0
101128

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Built on Rust NIFs: [OXC](https://hex.pm/packages/oxc) for JS/TS, [Vize](https:/
2626

2727
```elixir
2828
def deps do
29-
[{:volt, "~> 0.3.0"}]
29+
[{:volt, "~> 0.4.0"}]
3030
end
3131
```
3232

examples/demo/config/config.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ config :volt,
3535
outdir: "priv/static/assets",
3636
target: :es2020,
3737
resolve_dirs: ["deps"],
38+
# external: ~w(phoenix phoenix_html phoenix_live_view),
3839
tailwind: [
3940
css: "assets/css/app.css",
4041
sources: [

examples/demo/mix.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
"castore": {:hex, :castore, "1.0.17", "4f9770d2d45fbd91dcf6bd404cf64e7e58fed04fadda0923dc32acca0badffa2", [:mix], [], "hexpm", "12d24b9d80b910dd3953e165636d68f147a31db945d2dcb9365e441f8b5351e5"},
44
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
55
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
6+
"dotenvy": {:hex, :dotenvy, "1.1.1", "00e318f3c51de9fafc4b48598447e386f19204dc18ca69886905bb8f8b08b667", [:mix], [], "hexpm", "c8269471b5701e9e56dc86509c1199ded2b33dce088c3471afcfef7839766d8e"},
67
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
78
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
89
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
910
"fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"},
11+
"floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"},
1012
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
1113
"hex_solver": {:hex, :hex_solver, "0.2.3", "0d2ee20fbceb251d573f03ef34852e325529c2874ab66d1b576384021318996c", [:mix], [], "hexpm", "9daeae2ea6b8ad3dc7f51a10484f6bc1d0f705c31063383830a7167ab93b887b"},
1214
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Volt.MixProject do
22
use Mix.Project
33

4-
@version "0.3.0"
4+
@version "0.4.0"
55
@source_url "https://github.com/elixir-volt/volt"
66

77
def project do

0 commit comments

Comments
 (0)