wednesday is a front-end web framework that aims to simplify things:
- No Node.js server, no Deno, no Bun, no server! Build your own if you need one.
- No virtual DOM, no re-renders, no extra calls, no extra bundle size. The DOM is already there.
- No mixed client-server code, no headaches.
For a more stable release you can take a look at the latest release available on github. Just unzip it and run the installation script.
If you have troubles running the script try to
chmod +x ./<filename>before running it
Or to automate it:
- UNIX (Linux, MacOS):
curl -sSfL https://raw.githubusercontent.com/DazFather/Wednesday/main/scripts/install.sh | sh - Windows: via PowerShell
Invoke-RestMethod https://raw.githubusercontent.com/DazFather/Wednesday/main/scripts/install.ps1 | Invoke-Expression
If you encouner problems running the script try to
Set-ExecutionPolicy RemoteSigned -Scope CurrentUserbefore running it
If you want to compile straight from the source is recommanded to install it by cloning the repo like so:
git clone https://github.com/DazFather/lxl.git
cd WednesdayThen simply run the related installation script depending on your os
If you have troubles running the script try to
chmod +x ./scripts/<OS>-compile-install.shbefore running it
- Linux
./scripts/linux-compile-install.sh - Windows
.\scripts\windows-compile-install.bat - MacOS
./scripts/darwin-compile-install.sh
By running the script the go binary will be shrinked and have a more coherent wed --version.
On top of that on Linux and MacOS there will also install the related man pages for quick consultation
If you want a quick way and don't care about man's manuals or chunky binary, just run:
go install github.com/DazFather/Wednesday/cmd/wedFor more info simply follow the official Go (mini) tutorial here, as with any other Go project.
To initialize a directory as a new Wednesday project. Use the command
wed init <directory>It will create all necessary files and directories, which you can customize later via the
wed-settings.json. If no directory is specified, current working directory will be used
If you already have a settings file, you can initialize the project accordingly by passing the path to the file using the "settings" (or the alias "s") flag.
For example:
wed init -s=path/to/my-settings-file.jsonThis can be a great way to create a new project with the same structure of another one
To verify everything is in place, simply serve it using wed serve and visit http://localhost:8080; you should see a welcome page.
You can specify the port using the "port" (or "p") flag. More on this later.
You can create a component by creating a file ending with .wed.html in any subdirectory of your project. In this file, you can specify the following top-level tags:
This will host your CSS styles scoped for this component only (.my-component-name.wed-component). All .wed-component have a display: content;
Within the <html> tag, you can put the HTML structure of your component. This will be wrapped in a <div class="my-component-name wed-component">.
WED relies on the Go template engine. It will be executed only once, when the project is built. Both commands
wed buildandwed servewill build the project. Thanks to this, the default WED component has zero extra runtime cost. Use them as much as you like; the client won't incur additional load.
Let's see how to use them in some practical examples:
The first scenario where the template system is useful is when we want to use a component.
To do this, we can simply use {{ use "my-component-name" }}, and the content of the HTML for the specified component will be inserted there.
You can also pass a list of values between templates by using the provided args function. For example:
<html>
<h1>Concept Bucket</h1>
<strong>List of ideas that came to my mind:</strong>
{{ use "child" props "pippo" 3.14 "banana" false }}
</html>
parent.wed.html
<html>
<p>
First thought: <mark>{{ .Props.pippo . }}</mark><br>
Second: <em>{{ .Props.banana }}</em>
</p>
<html>
child.wed.html
Another useful feature from the templating language is the ability to define additional templates that can be used inside and outside your components.
Note: When used outside, they will not carry the same style since they will be out of scope.
<style>
.magic-word { color: indigo; }
</style>
<html>
<h1>{{ block "magic-word" "please" }}<strong class="magic-word">{{ . }}</strong>{{ end }}</h1>
<strong>The importance of saying "{{ template "magic-word" }}"</strong>
{{ template "child" }}
</html>
parent.wed.html
<style>
.magic-word { color: limegreen; }
</style>
<html>
<p>It's important for good communication to say {{ template "magic-word" }}</p>
<html>
child.wed.html
Sometimes web pages need to add components dynamically, such as when a user click on a button or after an HTTP call.
Wednesday gots you cover, simply add the attribute type="dynamic" to the html tag
By default Wednesday will interpret an empty or missing type attribute as static This is a precise strategy to improve client speed and reduce state management to only when necessary
In this way the html code will be wrapped again on a template tag having as the only attribute an id with the component name as the value such as <template id="pippo">...</template>.
This template can be insert anywhere just by using {{ dynamic "component-name" }}
In praticular this template call accepts a variadic number of template names. It's possible to pass only the needed template_s_ or nothing to import them all
At this point by manipulating the DOM in the standard way is possible to insert the template content anywhere
There is also an utility function useTemplate that might cover many cases and help reduce the boilerplate
A dynamic template can include static templates, share datas and snippets with others and do all other action allowed by the template engine
Here you can add JavaScript logic that will run once the page is fully loaded (defer).
This script is shared across all components, giving access to helpful utilities that WED provides to enhance component reactivity.
The import order of the components is currently alphabetical based on the file name but it might change in the future.
If a component script require some definitions form another one it's possible to use the require attribute giving as value a space-spareted components name (without the file extension)
When you want to update text on the screen:
<html>
<p class="display">loading...</p><!-- expected: Hello World! -->
<html>
<script>
const show = useDisplay(".display", t => t + " World!")
console.log(show("Hello")) // expected: Hello World!
</script>
app.wed.html
Runs a callback whenever the value changes:
<html>
<p class="display">loading...</p><!-- expected: 24,6 -->
<html>
<script>
const arr = useEffect([], (() => {
const show = useDisplay(".display")
return v => { show(v.map(e => "" + (e * 2)).join(",")); return v }
})())
arr.value = [12,3]
</script>
app.wed.html
A sibling of useDisplay, it provides controlled access to properties of a DOM element, allowing you to specify one or a list of properties. This can prevent accident where, especially on large codebases, a person might change the value of something is not supposed to.
Binds properties between elements and an object, updating them in response to changes.
use the bind HTML attribute on the elements you want to bind and then put the following values separated by ':'. Only the first is required:
- The property of the element that you need to bind (ex. "innerText")
Notice that even though "class" is an HTML attribute, in JS to modify it directly you need to use "className". This is just an examples, there are other exceptions too (like "style")
- (optional) The corresponding name (by default is the same of the property) of the bind object on the JS side.
This can be useful if you want to bind under the same name different elements properties
- (optional) An event name that you want to listen to and cause the value to be recomputed
If you are binding the "value" property on an "input" tag you might wanna listen to the "input" event in order to update the value on user input
You can binds multiple properties of the same element by separating them with a (or more) spaces like this: bind="value:color:input innerText className:theme".
A more complete example below:
<style>
pre { display: inline-block; }
h5 { color: indianred; }
</style>
<html>
<form id="sign-up-form" action="./sign-up" method="POST">
<h3>Welcome <span bind="innerText:user"></span>!</h3>
<input type="text" autocomplete="off" id="kawa" name="kawa" bind="value:user:input" placeholder="Write your name here">
<input type="text" autocomplete="off" id="bonga" name="bonga" bind="value:password:input" placeholder="Write your password here">
<h5 hidden>Your password "<code><pre bind="innerText:password"></pre></code>" is too weak!</h5>
<input type="submit" value="Sign UP" disabled>
</form>
</html>
<script>
const weak = useMirror("#sign-up-form h5", 'hidden')
const submit = useMirror("#sign-up-form input[type=submit]", 'disabled')
const form = useBinds("#sign-up-form", (val, key) => {
if (key === 'password') {
weak.hidden = val === '' || !(submit.disabled = val.length <= 5)
}
return val
})
form.user = "Guest"
</script>
app.wed.html
Retrieves a template by it's id and provides methods to facilitate content insertion into the DOM upon initialization.
<style>
input[readonly] {
border: none;
outline: none;
background: transparent;
text-decoration: line-through;
}
</style>
<html type="dynamic">
<div>
<input type="checkbox" bind="checked:done:input">
<input type="text" bind="value:task:input readOnly:done">
</div>
</html>
<script>
const { clone: newItem } = useTemplate("todo-item", templ => {
templ._binds = useBinds(templ)
return templ
})
</script>
todo-item.wed.html
<html>
{{ import "dynamics" }}
<div id="todo-list">
<h1>TO DO LIST</h1>
<div>
<input type="text" placeholder="Write your task here">
<input type="button" value=" + " onclick="handleItemAdd()">
</div>
</div>
</html>
<script require="todo-item">
const holder = select("#todo-list")
const input = useMirror(holder.querySelector("input[type=text]"), 'value')
const handleItemAdd = () => {
const item = newItem()
item._binds.task = input.value
holder.appendChild(item)
input.value = ''
}
</script>
app.wed.html
With your project set up, you're ready to build your site. Wednesday compiles your components, scripts, and styles into a static, deployable format. Use the following command to compile your project:
wed buildThis command processes all
.wed.htmlcomponents and.tmplpages recursively inside the input directoy.
The command will output on terminal the relative path to the directory containing the "index.html" You can now open this file with your browser and everything should be working! No server needed (well unless you actually do needed it)
Arrange your components files however suits your needs. As stated previously the build process is recursive.
You might for example store your components in a
/componentsfolder. Or in any other way, Wednesday doesn't really care
For the assets, you can put them wherever you want on the output_dir specified in your settings file. Again the choice is yours
But what if you want to edit the way your build is generated or specify the input directory, you can customize them using the JSON settings file:
- input_dir: Define the finput directoy for all wed compoents and templates (default: current working directoy)
- output_dir: Define the output directory where the project will be built and eventually served (default:
build)
You can also specify the settings file (default is wed-settings.json) using the "settings" (or "s") flag
Example:
wed build --settings=path/to/my/settings.jsonFlag must be prefixed by one or two '-' and value can be included in '"'
As stated previously there is no need for a server. The site should be accessible simply by opening the index.html from the browser. This is a design choice and one of the main difference between Wednesday and the majority of the frameworks out there.
Why not to:
- The majority of sites doesn't really needs a back-end for front-end and just end
- Often the codebase is confusing where you don't know where the code will be executed on server or client or both
- Modifying elements on screen is the reason why we have JavaScript on browsers. There is no point to add more complexity with http calls to load and re-render components
- If your project is small you can include a static server for the site on the same server where your other APIs lives
- If you receive too many requests then you can decide to scorporate the two by simply having another static server
Why there is a command:
- To have an easy endpoint available on your browser instead of looking for your project folder
- Provides an easy way to serve statically without relying on external tools
- To check your changes "live" when you are doing frequent changes, more of that later
Only for these reasons:
wed serveThe command will build the site and then serve the output_dir statically
This command accepts the "settings" flags and the optional second argument "mount" like wed build, plus:
- "port" flag (or "p") to specify the port you want to serve your site on (default:
:8080)Example:
wed serve --port=":8081" - "live" flag (or "l") to rebuild the application each specified time interval or no option for change detection on files
Example:
wed serve -live=3s
Wednesday can also help with CI/CD pipelines when a project grows on size thanks to some settings property
You can define custom data in your project settings file using the vars key.
These values are injected into your templates and can be accessed via the {{ var <name> }} syntax.
Example
{
"vars": {
"title": "My Portfolio",
"author": "Jane Doe",
"year": 2025
}
}Usage example in a component
<h1>{{ var "title" }}</h1>
<p>Created by {{ var "author" }} - {{ var "year" }}</p>The wed run command allows you to automate workflow steps by executing a list of commands specified in the commands property of your settings file.
The property value must be an array that lists each command to be executed in order. Example:
{
// ...
"commands": {
"zip": [
"wed build",
"zip -r build.zip ./build"
],
"serve-stable": [
"git stash",
"git checkout master",
"git pull",
"wed serve -p :4200",
]
}
}To execute these commands sequentially, simply run:
wed run <command>Example:
wed run zip
This setup lets you automate and customize your project’s workflows with ease.
You can easily integrates wed in your github workflow by simply using this simple github action.