Using Pkl Language to Generate Configurations
How Pkl Language Improved Our UI Builder.
Feb 23, 2024
To enhance efficiency, reduce complexity, and streamline processes, we developed a config based UI builder. This is what we call our 'Dashboard UI Builder'. By leveraging configurations in either YAML or JSON, we have unlocked a method to rapidly render and customize user interfaces. The core principle of our UI builder is straightforward: Define your UI in a configuration file, and let the builder do the rest. We have a set of utilities (network,storage,etc) and components (UI) that can be controlled via the config itself.
However, all the supported capabilities come from highly complex configurations. As the complexity grows, the configurations become unreadable, and worse - unmaintainable. This complexity created a perfect breeding ground for bugs.. Bugs that were hard to squish and difficult to replicate. This illustrates why config-based tools are a double-edged sword: they're quick to start with but have multiple ways to achieve an outcome. However, there is only 1 correct way to do that and it remains an unknown to the end user of the tool. Developers should not, and in my opinion, cannot afford to make assumptions when building a config-based tool with such capabilities.
Challenges with v0 Config
Here's what it looks like.
{
"endPoints":{
// all endpoints config
},
"pages":{
// all page level configs
},
"sidebar": [
// all sidebar options...this is an Array<SidebarItems>
],
"storage":{
// storage config
}
}
Initially, we built the product to consume configs in JSON.
- On average, it required 500 lines of valid JSON to render a screen with static text, a few CTAs and a table section that would depend on the responses from services.
- The code reuse was 0. For similar and repetitive definitions, the end users were forced to copy-paste previously used configs. This led to bugs.
- Early adopters of the tool are the teams who wanted to spin up an Admin console for their services rapidly, This copy-pasting approach increased the risk of errors, especially if dependencies/parameters were not updated to match the current context.
We added YAML support but only for better readability and a few lines of code reduced but other problems remained. So in config v0, we had a single file (.yaml or .json) containing every info that the tool required.
Improvements in v1 Config
We tweaked a few properties and split the configs basis what they were for. So we had multiple config files, example: sidebar.yaml, network.yaml, pages.yaml etc. This improved readability but reusability and validating the configs were still a problem.
We built static validators that ran everytime the user made changes to the config. These validators evaluated the files, adding a safety check before the config was passed to the tool's parser. This reduced the incidents of broken configs to an extent but did not solve it. However, code reuse was still a huge problem
The v2 Config with Pkl Lang
On February 01, 2024 Apple announced Pkl Lang. And 3 days later we stumbled upon their Github repo. This discovery was a game-changer. Our static validator attempted to solve the same problems that Pkl had already addressed. Pkl did more! So we could not wait but started exploring the lang.
Pkl's inherent support for modularity & reusability filled the void for us!
- Pkl's type and validation system allows for catching configuration errors before deploying an application. This is particularly useful in builder context, where detecting and resolving configuration errors early saves significant time and resources
- Pkl's support for modules allows for better organization of configuration code. Common UI components or configurations can be modularized and reused across different parts.
Here's an example of what the code looks like. In reality, we have templates in a different file and the config in a seperate one.
// endpoints.config.pkl
class Endpoint {
origin: String
endPoint: String
method: "GET"|"PUT"|"POST"|"DELETE"
}
endPoints = new Mapping {
["getCountries"] = new Endpoint {
origin = "api.example.com"
endPoint = "/countries"
method = "GET"
}
["getInfoForACountry"] = new Endpoint {
origin = "api.example.com"
endPoint = "/country/:countryID"
method = "GET"
}
}
this gets converted to
// endpoints.config.yaml
endPoints:
getCountries:
origin: api.example.com
endPoint: /countries
method: GET
getInfoForACountry:
origin: api.example.com
endPoint: /country/:countryID
method: GET
Since we adopted Pkl Lang within days of announcement, the official docs and threads are the only resources that we have. Integrating PKL into our config-based UI builder has significantly enhanced efficiency, safety, modularity, and the overall user experience, making it an exceptionally powerful tool.