If that’s the case, your app may benefit from extending Remake’s backend code.
The backend of Remake contains the
]]>As you’re building your web app, you may encounter times where you need to do more with your app than what Remake does for you.
If that’s the case, your app may benefit from extending Remake’s backend code.
The backend of Remake contains the JavaScript code that runs on your server, which is different from the frontend code that runs in your users' web browsers.
The backend code gives us the ability to integrate our server with other Node libraries and web APIs, allowing us greater control over Remake’s power, and opening up new app possibilities! ✨
Important: If you want to use Remake's built-in hosting service (i.e. with the command remake deploy
), you won't be able to deploy your modified backend code. So, if you to work with modified backend code, you need to host Remake yourself.
The project we will build is a statistics panel, which will give us some stats about how our web app is being used.
Note: As with all web apps, user data privacy is important, so remember to follow any applicable user data privacy laws. Privacy law compliance is a topic outside the scope of this tutorial, but do not forget that it is also a very important topic for all web app developers to understand!
After this tutorial, you should feel more comfortable extending Remake’s backend with your own backend code.
Want to skip to the code? All the source code from this tutorial
💡 If you get stuck and a Google search doesn’t get you the relevant information, feel free to swing by Remake's community chat on Discord and ask for help. We’re very friendly!
We’ll start with the default Remake project, which we’ll generate the normal way using the remake-cli utility.
In your terminal, create a new remake project using the following command:
npx remake create backend_project
When asked to pick a starter template, choose Default Starter.
Now inside the backend_project
folder, we have our new project files.
You can make sure the application was generated correctly by changing directory into the backend_project
folder and running npm run dev
:
cd backend_project
npm run dev
Once the server starts, navigate to http://localhost:3000/ in your web browser while it’s running.
Since there’s not much there, let’s fill our app/pages/app-index.hbs
file with the default todo list starter code:
<div object>
<ul array key="todos" sortable>
{{#for todo in todos}}
<li
object
key:text="@innerText"
edit:text
>{{default todo.text "New todo item"}}</li>
{{/for}}
</ul>
<button new:todo>Add Todo</button>
</div>
Refresh your page and you should see a basic todo list app after you create an account and log in.
To extend Remake, we’ll first start with a look at _remake/main.js
, and the files inside _remake/lib/
as well. In order to modify Remake, we first have to know a bit about how it works!
Remake’s backend uses the Node Express framework for routing, Passport.js for authentication, and json for storing data on the server. In the main.js
file, you’ll find 2 function calls beneath a comment called “REMAKE CORE FRAMEWORK”, called initApiNew
and initApiSave
. Those are the ones we’ll be adding callbacks to in this tutorial.
Remake’s frontend and backend communicate in two ways: initially when pages load, and also after the page is loaded, through endpoints. API endpoints are just URLs the backend provides for the frontend to call. The files that do this are within the _remake/lib
folder. Don’t be afraid to open them up and look at what they do!
Here are the endpoints of interest to us:
/new/
endpoint
/save/
endpoint
The easiest way to hook into Remake’s behavior on the backend would be to use callbacks. Unfortunately, Remake doesn’t yet have backend callbacks. So let’s add them right now!
In the file _remake/lib/init-api-new.js
, add the following code to the function definition of initApiNew:
export function initApiNew ({app}, callback) {
Inside this function there is an app.post
call. We need to call our callback at the end of this function provided to app.post
, right below the final res.json
call:
if(callback != null)
callback({
app,
user: {
name: currentUser.details.username,
email: currentUser.details.email
},
data
});
This callback will cause Remake to provide us with the user details (name and email) and copy of the data when a user creates a new entry. We’re also passing the app itself as the first parameter, since it’s always useful to have.
Now in your _remake/lib/main.js
, we’ll provide our newly changed code with our own callback to log the data mentioned above:
initApiNew({ app }, ({app, user, data}) => {
console.log(“initApiNew callback data: ”, data);
});
Save and test the above code using npm run dev. Using your web app, add a new item to the todo list. In the server console, you’ll notice we now see the data logged.
Callbacks allow code separation, so we can more easily maintain our application’s custom behavior across remake updates. But they sometimes aren’t enough. So let’s explore other customization options.
Let’s do the same thing for the save endpoint in init-api-save.js
.
export function initApiSave ({app}, callback) {
And underneath res.json({success: true}));
, add the following code:
if(callback != null)
callback({
app,
user: {
name: currentUser.details.username,
email: currentUser.details.email
},
data: {
newData: existingData,
oldData
}
});
We’ll also need to get a copy of the old data, since it’s changed in this function. On line 36, underneath the declaration of oldData
, add:
let oldData = {...existingData};
I’ve chosen to provide not just the data in the callback, but the old data as well. This won’t be used in our application, but would be useful for purposes where you need to know the data that changed.
Let’s call the callback to check that it works. Do this in main.js
:
initApiSave({ app }, ({app, user, data}) => {
console.log(“initApiNew callback data: ”, data);
});
Since we are not using file uploads, we will not be adding a callback to the upload endpoint. But you could do so if you have a need for it.
Combining what we know from the changes above, we can replace the 3 init function calls in main.js with initBackend({app})
, a function we’ll write and store in backend.js, which we’ll put inside our app folder in the project directory.
First, in Remake’s main.js file, let’s import our backend. Beneath the other imports, add this:
let backend = null;
try {
backend = require("../app/backend"); // optional
} catch (err) {
if (err.code != “MODULE_NOT_FOUND”) {
throw err;
}
}
We’re going to make this import optional since requiring the backend file would break Remake if it wasn’t there.
Then, we’ll replace the 2 initApi
calls in _remake/lib/main.js
with this code. We’re also going to add an optional init function for our backend and have it run prior to the initRenderedRoutes
function, which will allow us to override Remake’s default routes.
// REMAKE CORE FRAMEWORK
initUserAccounts({app});
initApiNew({app}, backend.onNew);
initApiSave({app}, backend.onSave);
initApiUpload({app});
if (backend.init != null) {
backend.init({app});
}
initRenderedRoutes({app});
Now create a new file backend.js in your app folder with the contents. Notice we are moving our callbacks into this file:
const onNew = ({app, user, data}) => {
console.log("onSave callback data:",
"\nUser:", user.name,
"\nData:", data.data
);
};
const onSave = ({app, user, data}) => {
console.log("onSave callback data:",
"\nUser:", user.name,
"\nOld Data:", data.oldData,
"\nNew Data:", data.newData
);
};
const init = ({app}) => {
console.log("Custom backend initialized...");
}
const run = ({app}) => {
console.log("Custom backend running...");
};
export { init, onNew, onSave, run };
We’ve also added another function, run, which we’ll call from Remake after it has started.
Inside of Remake’s main.js
at the bottom of the the app.listen
callback, call our run function:
app.listen(PORT, () => {
console.log('\n');
showConsoleSuccess(`Visit your Remake app: http://localhost:${PORT}`);
showConsoleSuccess(`Check this log to see the requests made by the app, as you use it.`);
console.log('\n');
if (process.send) {
process.send("online");
}
backend.run({app});
});
Now would be a good time to test your application the same way as before, using npm run dev
. You’ll notice that our logging still works, but it has now been moved to our own file.
Now that our backend code is running, it would be nice to do something useful besides logging, and this is where your creativity can come in!
For this tutorial, we’ll create a statistics page which will show us some useful information about how our web app is being used.
We’ll measure:
Let’s use our backend callbacks to record some information about user activity. Let’s count the activity of each user by counting the number of calls to new and save.
The counting is rather simple. It’s just the incrementing of a value that we’ll store in json. We’re also going to use Date.now() to get a timestamp of the last activity, which we’ll also store.
Tip: If you’re using JSON as a database, as Remake does, an important thing to remember is to be careful when using async/await or callbacks. If you’re reading in data, changing it, and writing data, there is a real chance the database gets opened and changed in another place in your code as well, opening up the real risk of data loss.
For this reason we’re using synchronous calls when working with the JSON and being careful to make sure node is not allowed to do a context switch while in the middle of working with a file. For more information about this concept, research transactional data and asynchronous programming.
For our project, we’re going to generate a new json file inside Remake’s json database folder called stats.json. Once populated, the json file will look something like this:
{
"userActivity":{
"[email protected]": {
"activity": 12,
"lastUseTimestamp": 1610088558699
}
},
"lastUseTimestamp": 1610088558699,
"lastUser": "[email protected]"
}
As you can see, we will have an entry per-user, with an activity count and a date of the last modification on the user account. This will allow us to see the most active users and the last time each user used the app.
We’re also tracking the last use timestamp out of all users and the last active user’s email.
We’re going to make our new stats file create itself if it doesn’t exist, and fill in fields that don’t exist as we need them.
Since we’re counting both additions and modifications, we’ll need to add our code to both endpoints: new and save. Since this will involve the same code, let’s create a function for incrementing the count.
The following code examples in this section will occur inside a new function inside of backend.js
called incrementUserActivity
, which will take one object parameter containing the name and email of the user that did the activity.
Here is the stub:
const incrementUserActivity = ({name, email}) => {
// our new code will go here...
};
Before we start writing this function, let’s add two imports and set the location for our stats.json
file we’ll use at the top of the file:
const jsonfile = require("jsonfile");
const path = require("upath");
const statsFile = path.join(__dirname, "data/database/stats.json");
The code below shows how to read a JSON file synchronously. If the file does not exist, an exception will be thrown, and if that happens, we will create it. Put the following code inside our new incrementUserActivity
function.
// open or create a stats.json file if it doesn't exist
let stats = {};
try {
stats = jsonfile.readFileSync(statsFile);
} catch (err) {
if (err instanceof Error && err.code == “ENOENT”) {
// file not found? create it!
jsonfile.writeFileSync(statsFile, {});
} else {
// unknown error, rethrow
throw err;
}
}
At the first run of our application, our stats.json won’t exist. The code we wrote previously will create it, but it will still start empty. Because of this, default values are needed to be filled in if they don’t exist.
Similarly, if an entry for a specific user doesn’t yet exist, we’ll create that too and set the initial activity counter to 0:
// create user activity entry in stats.json if it doesn't exist
if(stats.userActivity === undefined) {
stats.userActivity = {};
}
if(stats.userActivity[email] === undefined) {
stats.userActivity[email] = {};
}
if(stats.userActivity[email].activity === undefined) {
stats.userActivity[email].activity = 0;
}
As we talked about before, we’ll increment the activity counter stored in the current user and keep a timestamp of when it happened in two places, as well as the email of the last user to use the app.
stats.userActivity[email].activity += 1;
let timestamp = Date.now();
stats.userActivity[email].lastUseTimestamp = timestamp;
stats.lastUseTimestamp = timestamp;
stats.lastUser = email;
Writing our data into the stats.json file at the end of the function is simple, as it involves only one call:
jsonfile.writeFileSync(statsFile, stats, {spaces:2});
The spaces option is provided so that the json is formatted with 2 spaces per “tab” so we can read it easily.
Our incrementUserActivity
function should be called from both endpoints: new and save.
Here’s how we call our function:
const onNew = ({app, user, data}) => {
incrementUserActivity(user);
};
const onSave = ({app, user, data}) => {
incrementUserActivity(user);
};
Before you test, make sure to cause some activity to happen by adding or changing some items and viewing stats.json
to see the changes.
To make our app display the stats, we need to add a new /stats/
route inside our backend’s init function.
The most quick and dirty approach to displaying the states would be logging them to the page and to the server console. Let’s try that first:
const init = ({app}) => {
// Create the route for our page at /stats
app.get("/stats", (req, res) => {
res.set('Content-Type', 'text/plain'); // plain text page
res.write("Stats:\n\n");
try {
const stats = jsonfile.readFileSync(statsFile);
console.log(stats);
res.write(JSON.stringify(stats, null, 2));
} catch (err) {
res.write("{}");
}
res.end();
});
};
Now would be a good time to test. Firstly, make sure there is some activity in your todo list to log. Then, navigate to https://localhost:3000/stats/.
Since this isn’t a frontend tutorial, and only one user will see this page, we won’t be implementing any fancy template rendering. Instead we’ll render a very simple stats page which we’ll restrict to a single user called admin.
When you test this code, remember to be logged in as admin, or change the code below to match your username.
Begin by wrapping the route code in the following if statement. We’ll also set the page to plain text, since that’s how we’ll render the stats.
app.get("/stats", (req, res) => {
res.set(“Content-Type”, “text/plain”); // plain text page
if(req.isAuthenticated() && req.user.details.username === "admin") {
// only admin user can see this...
}else{
res.status(403); // not authorized
res.end();
}
// ...
Inside the above if statement, we’ll read the json stats similarly to how we did it before, and we’ll check to see the stats are empty:
let stats = {};
try {
stats = jsonfile.readFileSync(statsFile);
} catch (err) {
// ignore file not found, otherwise throw
if(!(err instanceof Error) || err.code !== "ENOENT")
throw err;
}
if(stats == null || stats.userActivity == null) {
res.write(“No stats recorded!”);
res.end();
return;
}
This will read from our stats file and render a message to us if there are no stats recorded.
We can compute the most active user by looping through our stats object, checking each user’s activity, and keeping the most active user found and the activity level. We’ll also add up the total activity level.
let mostActiveUser = null;
let totalActivity = 0;
let mostActivity = 0;
if(stats.userActivity != null) {
for(var email in stats.userActivity) {
let user = stats.userActivity[email];
if(user.activity != null) {
if(user.activity > mostActivity) {
mostActivity = user.activity;
mostActiveUser = email;
}
totalActivity += user.activity;
}
}
}
Here we’ll render each of the stats that we have available:
if(totalActivity !== null)
res.write("Total Activity: " + totalActivity + "\n");
if(stats.lastUseTimestamp != null && stats.lastUser != null)
res.write("Last Activity: " +
(new Date(stats.lastUseTimestamp)).toString() +
" by " +
stats.lastUser + "\n"
);
if(mostActiveUser !== null)
res.write("Most Active User: " +
mostActiveUser +
" (" + mostActivity + ")\n"
);
res.end();
We show the total activity, last activity, and the most active user.
Our stats page at https://localhost:3000/stats should look something like this:
Stats
Total Activity: 18
Last Activity: Fri Jan 08 2021 01:19:50 GMT-0800 (Pacific Standard Time)
by [email protected]
Most Active User: [email protected] (14)
Want to view all the code at once? All the source code from this tutorial
If the code provided in this tutorial isn’t working for you, be sure to check the repository linked at the top of the page. This is where you’ll find the full example listing. If you’re looking for the stats, make sure your username is admin.
Thanks for reading this tutorial! I hope you learned that the Remake backend can be customized and expanded to open up new server-side possibilities. In the future, Remakes backend customization should become easier, but the integration should be similar. Be sure to watch Remake’s development closely as new things are being added frequently!
Bye for now! 😎
]]>My first problem was a problem of my own creation. I read a lot of blog posts online, many of them so good that I save them for later, to re-read and share. I started with a simple page on
]]>This is a story of two problems and two solutions.
My first problem was a problem of my own creation. I read a lot of blog posts online, many of them so good that I save them for later, to re-read and share. I started with a simple page on my website where I just kept a list of those links, and why I enjoyed them. But eventually, the list became slow and annoying to update manually — I wanted an app.
That’s when I came upon my second problem, which was very much not my own: Even for something super, super simple, like I want a page on the Internet where I can share an updated list of links, it takes a lot of work to get something working. The road from idea to prototype is covered with excuses not to build, and I’m sure you’ve felt the same problem.
Remake sweeps away those excuses. I took a few hours on a Friday to sketch out an idea for Shelf.page and build it with Remake.
Shelf.page is representative of a really common kind of app. Every user gets a profile or account page at a unique URL, with a bunch of fields to customize their page and edit it themselves.
Remake shines at building these kinds of apps.
Normally, I would spend the first hour or two of starting a project like this: installing tools, setting up a database, and connecting all the components together, but Remake let me build a full-stack app while focusing on just frontend code. This, combined with one-liner deploys and fast builds, makes Remake rocket fuel for building these kinds of single-page apps — I finished Shelf.page in about half the time I expected it to take!
The rest of this post is about Remake, and how it could cut down your time-to-launch to help you build faster, too.
If you already know what Remake is, and want to just get started with code, feel free to skip down to the Create Remake section.
Remake lets you write an app by describing your idea in annotated templates, not writing boilerplate code.
When you get down to it, Remake is a framework. And frameworks are always about abstracting out some repetitive, common part of projects. So a good way to understand Remake is to ask, what does Remake abstract for you?
In my experience, Remake is built on three big ideas.
The headline feature of Remake is that you can describe your entire app in a few templates. Remake looks at the shape of your data, and some annotations you add to your template, and takes care of making data editable and interactive in your page.
We’ll dive deeper into Remake’s annotations (attributes) later in the post, but here’s a taste. Let’s say you want every user to have an editable “name” field on their account. You can write a template with some attributes, like
<div class="user-name"
object
key:name="@innerText"
edit:name
>{{ name }}</div>
This tells Remake that:
object
)key:name
)edit:name
)There are a few more directives that combine together to add a layer of editability over your app that’s smart enough to know how you store your data. Combined with Handlebars templates, Remake gives you a way to create fully interactive, editable web pages with just templates sprinkled with annotations.
Write templates that describe how your data relates to your UI, and Remake does the rest. This is a core idea of Remake.
I’ve made lots of side projects over the years, but every time I start a new one, I always need to stop and ask myself if I want to support user accounts, because authentication and account management is an evergreen hassle. Even for prototypes and quick hacks, setting up a database, creating login flows, copy-pasting code from some other project to remember how to implement login securely … all of this takes time, and none of it adds to the actual functionality of the idea I’m trying to bring to life.
Remake apps know how to set up and manage accounts, so in building my own Shelf.page app, I never had to worry about setting up an authentication system. Because I also deployed through Remake’s CLI and platform, user accounts just worked — password resets, login, account pages, the whole thing.
There’s a tradeoff to this zero-configuration setup: There’s not a whole lot you can customize about the login process. It doesn’t support logging in with Google or Apple accounts, for example, and doesn’t support two-factor authentication. But until your idea grows out of the early phase of getting some traction, having to worry little about account management will probably save me hours off each project, and add years to my life.
A lot of the strengths of Remake like zero-config accounts, smart templates, and easy deploys are possible because Remake is opinionated about how your project should be set up.
Like Ruby on Rails or Create React App, Remake comes with a CLI that helps you set up a Remake project. If you follow Remake’s conventions about where files go and how pages are rendered, in return Remake’s CLI also gives you a local development server out of the box and a way to deploy a production app to Remake’s deployment service with a CLI one-liner, remake deploy.
A second benefit of an opinionated design is that, if you have multiple Remake projects, you’ll never have to open up an old project and sit there trying to remind yourself where you put the exact file or page template you’re looking for — every project is roughly structured the same.
Taken altogether, by having a clear, opinionated model of describing how your data is related to your UI, Remake lets you write an app by just describing your idea, not wiring together lots of boilerplate code.
Let’s see how these pillars of Remake come together to actually help build a real app.
Every Remake project starts the same:
npx remake create shelf
npx is a package runner for NPM libraries — it helps you run commands from NPM packages without installing it globally.
Here, I wanted to create an app called shelf, but you can pick your own name. Once we run the command, Remake will make a new folder named shelf (or a name you pick) with a starter Remake app inside. If you’ve used Create React App, this might feel familiar.
To test out the starter app, we can cd into the new project and run the dev server:
cd shelf
npm run dev
Remake will take a second to build the app, and start running the app at localhost:3000
. Visit the URL on your computer to see the starter app, which is a Trello clone. You can also find this demo on the Remake website. It should look like this.
Try using the starter app to get a feel for how Remake apps work. Try adding, removing, and saving changes. Here are a few things I noticed in my brief tour.
This is no Netflix, but there’s enough here to build many kinds of apps. Adding, removing, and changing things are the building blocks for everything from blogs and todo lists to personal dashboards.
Let’s see what code makes this app possible by visiting app/pages/app-index.hbs
.
In the app-index.hbs
file, which is a Handlebars template, we’ll find an HTML template sprinkled with attributes like object
and edit:
. You might have a guess as to what some of these attributes do, but for now, all we need to know is that these attributes are key to how Remake associates your app’s data with the template.
When you change the template and reload the page in your browser, you should see any changes in the template now reflected in the page. Try tweaking a few things before we explore the rest of Remake, like changing the “Add stack” button’s text.
Before diving into building an app with Remake, we should understand what goes where in a Remake project.
Every web app needs to serve assets like images, JavaScript files, and CSS stylesheets. These are saved in app/assets
under their respective folders.
If you’ve been following along thus far, you might have a good guess about what these files do — they’re templates for pages in your app.
For now, we only need to worry about app-index.hbs
, but here are what the other pages do.
index.hbs
: The “index page” of your app, when the user isn’t logged in (as opposed to app-index, for when the user is logged in).user/
templates: Pages related to account management, like login/sign up pages and password reset.Most of the time, you’ll be editing app-index.hbs
. You can also add other pages next to app-index.hbs
to create new static or dynamic (templated) pages for each user. This might be useful if your app has multiple pages or a sub-page for a particular piece of data, for example.
You might have noticed that pages in app/pages don’t contain HTML boilerplate code like the page head. This is the responsibility of app/layouts/default.hbs
, which defines the app “shell” into which all your pages are rendered by Remake.
Remake stores user data in data/database/user-app-data
as JSON files. For example, for my user account with the username “thesephist”, Remake will create a thesephist.json
in which to store all my account data.
If you’re used to storing data in a relational database like Postgres, this tradeoff means a few things.
For the kinds of apps I’m making for myself and a small number of users, most of the time, the advantages end up outweighing the costs. Under app/data, we can define a default user data file that every new user will inherit, when they make a new account.
Remake started “clicking” for me when I learned that most of building a Remake app is finding a way to map a user’s data to parts of the user interface. I call this thinking in Remake.
When a user requests a page from a running Remake app, what actually happens?
After authenticating the user, Remake…
{user-name}.json
) for this userA key piece of the puzzle to understand is that the template tells Remake how to render the page from the data; Remake attributes tell Remake how to save data back from the page’s UI.
Because of this, a good first step to building a Remake app is to map out how your user’s data connects to your UI. For my Shelf.page app, I started with a schema like this:
{
displayName: 'Linus',
bio: 'Student, writer, fan of the 90s',
topics: [{
name: 'Tech',
links: [
{label: 'Remake', url: 'remaketheweb.com'},
{label: 'Mozilla', url: 'mozilla.com'},
],
}, {
name: 'Community',
links: [
{label: 'Get Together', url: 'gettogether.world'},
],
}],
}
A user has a display name and a short bio, and owns a list of topics. Each topic has a name for the topic, and a list of links under it with a label and a URL.
This shape of data maps really nicely to Shelf.page’s UI.
Once we’ve sketched this out, the work that remains is to express this in code! First, we write a Handlebars template for this page — this lets Remake render our page.
Second, we add Remake attributes to our template, so Remake can save any edits. This is the topic of our next section.
Remake attributes have a lot of flexibility, but to get started, you’ll probably use them in a handful of useful combinations.
Most commonly, you’ll want to make a text field editable. In our example, we might want the user to be able to edit the displayName
property of our user data object. This takes three attributes.
<div
object
key:display-name="@innerText"
edit:display-name:without-remove
>{{ display_name }}</div>
Here, we tell Remake we’re talking about a property on a JSON object (object
), specifically the property display_name (key:display-name
). Then we make this field editable, but not removable, with edit:display-name:without-remove
.
The next common case is a list of things. For example, you might want to add a task onto a todo list. If we have data that looks like below…
{
topics: [
{ name: ‘Computer science’ },
{ name: ‘Writing’ }
]
}
...and we want to show a list of editable topics, we’ll use these attributes.
<div
array
key="topics"
>
{{#for topic in topics}}
<section
object
key:name="@search"
>
<div
target:name
edit:name
>{{ topic.name }}</div>
</section>
{{/for}}
</div>
There’s a lot going on here, so let’s break it down. As you follow along, consider how the hierarchy of the template matches the hierarchy of our data.
array
) of things at the property “topics” (key="topics"
). Within this div, Remake will consider the array the “root” of our data.object
. key:name
tells Remake to “link” this element to a particular editable field, which will come in handy next.edit:name
. What happens when we delete this field? Deleting this field will delete the whole topic (the whole section element) because the topic is the closest JSON object that Remake could find in our data.The target:name
Remake attributes are the key to this pattern. It tells Remake to link one editable field to another part of our template.
The last pattern to know is a button to add more items to a list. Given our template for a list from before, adding a button is straightforward.
<div array key="topics">
{{#for topic in topics}}
...
{{/for}}
</div>
<button new:topic>New topic</button>
Here, we added an element, outside of our loop with the Remake attribute new:topic
, which tells Remake that clicking this button should add a topic item to our templated list of topics.
These patterns were enough for me to build Shelf.page, and will help you get started making Remake apps. But if you’re interested in the full breadth of attributes offered by Remake, you can check out Remake’s data attributes documentation: saving data, updating data, reacting to data.
Once we have the basics of the app working with a template, there are a few finishing touches you might want to add. You might also run into some bugs you’re not sure how to start fixing. Here are a few tips I picked up shipping my first Remake project.
assets/
directory. app/layouts/default.hbs
to add any stylesheets, script tags, or metadata you need.data/database/user-app-data
, to see how Remake is saving your changes. If the data in the user JSON file isn’t shaped the way you expect, you could narrow down your search for the buggy parts of the template.Once you have a working Remake app, deploying is fast and simple on Remake’s deployment platform. From the root of your project folder, simply run the command
npx remake deploy
and Remake will copy the right files up and spin up your service! If this is your first time deploying, Remake might ask you to create an account.
That’s it! You’ve deployed your Remake app, hassle-free. Try it out by going to your new domain and creating an account!
We’ve explored the basics of Remake in this post, but Remake is still growing and improving. For Remake the framework, a cleaner project layout is in the works, with nicer error messages and better documentation. For Remake the tool and deployment platform, you can expect new features like custom domain support in the future.
I’m really excited by how much faster I am at building simple apps when building with Remake, and I’m looking forward to how Remake grows in the future. If you, like me, have a few too many ideas for the hours in a day, consider building out your next hack on Remake.
Guest post written by Linus Lee. I'd highly recommend following him for more hacks, insights, and cool projects.
]]>You know how sometimes you have an idea for a web app and you think it will take 3 days to complete and then you're sitting there 3 years later about to release it?
Remake prevents that from happening.
It lets you release your full web app idea within your original timeline: 3 days.
Let's say your big idea is for a daily journal web app. How long would it take you to transform an HTML & CSS design of it into a fully-functional web app using a modern stack like React/Express/PostgreSQL?
Let's start with the first component: the title of a journal entry, which will be displayed at the top of every journal page.
And that's only half the battle: the front-end. You still have the backend.
Here's what the backend would require:
Ok, so that was at least 4 hours of work... but now you're done!
Except... that's only for a single component.
Now you have to go back and do all sixteen steps again for just about every other component in your app...
Even for a simple app, just the process of hooking things up — not making things look nice or behave as expected or fixing inevitable bugs — can take weeks, if not months!
And — don't forget — since you're working with a modern stack and building a web app from scratch, you're bound to run into even more issues:
These things seem small individually, but over time they pile up and sap your time and motivation. Before you know it, that small bug that was supposed to take an hour is still sitting in your backlog a week later.
That's a lot of time and energy down the drain for a project that seemed so simple at first.
What if there was a better way?
What if, when someone saw your HTML & CSS design, they could imagine exactly how it would work — and there was a framework that saw it the same way?
With Remake's straightforward syntax designed specifically for building web apps, things are simple.
In order to build an entire web app — not just a single component — with Remake, this is all you need to do:
And that's it! It's really that simple to build a full web app! 🚀
What you don't need to think about when using Remake:
And, to top it off, you'll be less likely to create bugs of your own because Remake has created a simpler model for what a web app is — making it much easier to think about.
To truly understand what makes Remake so awesome, you'll have to try it out for yourself. You can learn it in a few hours and deploy a web app today!
Next time you're glued to the computer at 4am working on some random bug with your build process, take a step back and think about who you're doing this for.
Will your future users really care if your build process works? Or if your database has the right schema?
Or do they just want to try out your idea?
Remake is the perfect tool for getting imperfect ideas out of your head and into the world, so you can see if they're any good. Save your engineering expertise for after you've found some users for your product.
]]>If you’re more interested in learning how to use the framework and what you can do with it, the Intro to Remake series
]]>Are you curious about why Remake was created, what its mission is, and why its founder spends nights and weekends working on it? Read on!
If you’re more interested in learning how to use the framework and what you can do with it, the Intro to Remake series is a better place to start.
Let’s jump right into the interview with David Miranda, the founder of Remake!
That’s a difficult question. It depends on who they are.
If they’re a full-stack developer, I’d probably tell them to use another framework. They’re used to having a ton of flexibility and Remake isn’t good for that. Remake’s focus is on simplicity and speed to market.
If they’re a no-code enthusiast, I’d probably tell them to go with Bubble or Glide Apps to build their app. They’re used to interacting using visual builders to build things — and Remake won’t give them that. Instead, Remake lets you create that no-code experience for other people.
However, for better fits, I’d have a different answer 😁
My dream for Remake is that it will help more people launch successful startups.
The problem is, right now, there’s too many “right ways” to do web development — and they’re all hard. They suck away your time until you’re only spending 10% on your actual product.
I want Remake to be the framework that people reach for when they just want to build something.
It will never be able to do everything, but if it can help someone get an extra 3-4 ideas out into the world every year, I’d be happy.
No.
Prototypes are meant to test the look and feel of an application without actually building one — but they don’t help you test the experience of actually using a full web app.
Remake lets you build a real web app that has real user accounts and a database — and the web apps you build with it actually work.
It lets you test full experiences.
Remake is meant to be replaced after a year or two, but only after you’ve gotten some traction with your product and figured out if it’s something people want.
If you’re building a web app that lets people login and edit the contents of a website then Remake will be a perfect fit for a long time.
Remake will soon be adding first-class support for anonymous form submissions, filtering/searching data, and collaborating on editing web pages.
If these features are enough for you, you won’t need to switch away from Remake ever.
However, if you need advanced social features or real-time updates across users, you’ll probably need to switch to another framework (or build it yourself by customizing Remake’s backend).
However, it won’t be hard to switch, even if you need to. Remake is 90% just the normal HTML and CSS you’d have to write anyway, no matter which framework you’re using, so removing it and switching to something else is as simple as removing a few custom attributes from your HTML.
“If I can build a website, I should be able to build a web app.”
This thought keeps me up at night.
I think it’s absurd that web technology has gotten so complex that it’s virtually impossible for a beginner to build a working web app in under a year.
Over and over again, I’ve come up with a design, coded it in HTML and CSS, and felt like I was 99% done even though I was only really 30% done.
It was disheartening to have to go through the painstaking process of building a backend over and over again — when I knew that I was reinventing the wheel every time.
I just kept thinking: “building a backend should be a solved problem by now.”
And that’s why I made Remake: I wanted people to be able to build things just for fun, working just on the front-end and focusing just on the interface people will see, but also to be able to release a fully working product.
Remake is a Meta CMS. There’s no other good way to explain it.
With a normal CMS like Wordpress or Webflow, you can deploy a single editable website for a single client. In order to serve multiple clients, you’ll have to deploy another copy of the CMS.
A Meta CMS, on the other hand, lets you serve multiple customers with a single template. You build it once, but an infinite number of customers can login and edit their own copy of it.
I searched for months looking for a Meta CMS, but came up with nothing.
The business model made sense to me though:
No one was doing this. I found it weird that there was this gap in the market.
So, I decided to build my own Meta CMS. Not on purpose, at first... I was just experimenting. I started out by just brainstorming some of the simplest ways to achieve this.
My first idea for Remake was:
I still think this is a genius idea 😁
Since most JS plugins just let you modify front-end HTML, why not just save the resulting HTML to the current user’s account in a database and call it a day? That’s basically a web app — an editable website. (You could even do some funky things with using CSS selectors in place of SQL statements to sync data between pages, but that’s another story).
However, I soon switched over to the current model of Remake (tagging elements with labels and converting HTML into JSON), which made it a lot easier to save data, label it, and share it across pages.
At the time, I thought I was just building a small tool for RequestCreative, but the technology turned out to be so fun and fast to work with that I wanted to bring it to a wider audience.
I started live streaming the development of a frontend framework called Remake.js in 2019, which eventually developed into the full stack framework that Remake is today.
Making a web app should be as simple as publishing a blog post.
My dream would be that Remake would be part of the browser. There would be a “New Web App” button right under the “New Tab” button in the browser’s main menu.
This would make it easy for beginners to get started with web development.
As a kid, you could start playing around with making a web app and have something figured out in maybe a few hours. Within a day or two of experimenting as a total beginner, you could have a working web app that you made yourself.
You could share it with friends and see what they think of it. Before long, you might have taught your cousin how to make web apps too. And you could build one for your uncle, aunt, or mother.
The ability to make a website is one thing, but the power to build something interactive that other people can contribute to (a web app) is an entirely different experience. It’s magical to see someone use something you made and get value from using it. I want to bring that experience to as many people as I can.
]]>Have you ever wondered: “What if clicking on a hyperlink could transport me — not just the screen in front of me — to a new location?”
That’s what the founders of Uber wondered. So, they transformed an ordinary link into a request for a ride.
This is called “hacking hypertext”.
Imagine, a link—a button, a menu, a search result—being able to accomplish something in the real world with a single click.
Google is a great example of this. They hacked hypertext in two ways: 1) They realized a hyperlink to a page meant that page is more valuable and 2) They gave everyone access to the most valuable links with the click of a Search button.
Facebook took hyperlinks to the next level by making them represent people and their relationships. Events, messages, profile pages, relationship statuses — all available with a click.
Who else has hacked hypertext?
These companies transform the good ol’ hyperlink into something incredibly powerful.
For most of us, though, links are still just plain, boring links.
As an ordinary person on the internet, I can’t create a link out of thin air that will show a list of my upcoming events or let someone rent my apartment — I rely on Facebook and Airbnb to create these kinds of links.
So, the question is: why isn’t hypertext more powerful by default?
The answer is: it’s wildly expensive to transform an ordinary hyperlink into something more.
Big companies like Google and Facebook spend billions of dollars a year building the infrastructure and hiring the talent necessary to support their hypertext hacks.
Usually, the most an ordinary citizen of the internet can do is use a hyperlink to link to their social media profile or personal website.
But... what if someone decided to democratize the incredible power these big companies have to hack hypertext?
What if anyone could hack hypertext?
More and more companies are buying into the “Low-Code” movement. Companies like Stripe, Webflow, and Shopify are making it easier to create online businesses. And companies like Airtable, Bubble, and Glide are making it possible for ordinary people to build powerful experiences using a set of hypertext hacks they invented.
But the foundation is still lacking. Hypertext itself hasn’t changed that much in the past three decades. It’s still, by default, pretty weak. It’s meant to create links between pages, not links to real-world actions.
I believe there’s a stage after “Low-Code”, a kind of hypertext 2.0. Maybe it comes with virtual reality, or cryptocurrency — or maybe it’s born when someone creates a new type of web browser. I don’t know. But, when it comes, it’s going to let anyone have the power to reshape the real world with a good ol’ hyperlink.
]]>Web applications are notoriously complex to build.
What if they weren't? What if web apps were so simple and easy to build, you wouldn't feel guilty about creating one from scratch and then throwing it away the next day?
Remake's power comes from letting you build just these kinds of web apps. We call them disposable web apps.
Disposable web apps are the opposite of everything wrong with web development today. They don't require a huge investment in development, so you don't need to plan ahead so much by spending time designing, prototyping, and researching.
Disposable web apps don't come with the normal headache associated with starting a big project — you can always just boot up a new web app in a few hours.
They give you the freedom to spend more time experimenting, playing, and talking with real users — and that's the real recipe for building something people want.
Remake lets you build web page builders.
Web page builders are websites that your visitors can modify. They can add items, remove items, upload photos, etc. And each user gets their own copy of the website that they can edit on their own.
Remake's ideal use case isn't making just one website for one client. Tools like Wordpress and Webflow can help you with that. Remake's power comes from using a single website template as the basis for a multi-user application, where each user can modify their own copy of the website and their copy is theirs alone.
Remake is a great fit for any website:
Remake lets you have:
You can build a web app with file uploading, custom style controls, and blog post editing all on the same page if you want — or put each control on a nested page. It's totally up to you!
Remake is really powerful and fast to build with, but it can't build every type of application. In the following lists, you'll fine the types of applications Remake was designed to help you build.
Standard web page builders:
Single-player games and personal productivity tools:
The first production web app built with Remake was RequestCreative. It's an easy-to-setup storefront for freelance creators. You can see some of the advanced features that Remake supports by visiting this website.
→ Full list of Remake app ideas (and the apps you shouldn't use Remake for)
Remake doesn't do so well with:
So, Remake is not a good fit for your web app if:
This means you shouldn't use Remake to build the next big social network, chatbot, analytics platform, multiplayer game, or collaborative document editor.
If these limitations are okay with you, then Remake can turn HTML into one of the most powerful languages for building web apps that you've ever seen.
If these limitations aren't okay with you, sign up for our newsletter. Some of them might change soon. You can also let us know which features we should build next on our public roadmap.
→ Full list of Remake app ideas (and the apps you shouldn't use Remake for)
You should use Remake if your customers want to build websites — and you want to launch really fast.
Use Remake if you know HTML & CSS really well — and you don't want to deal with setting up a backend.
What will you make today? ✨🧙♂️
"Intro to Remake, Part 3" is coming soon. Sign up to hear about it!
]]>If you've developed products before, you know there are two huge traps you can get stuck in because building web apps the traditional way is so damn hard:
Remake solves this predicament by pulling it out at the root: it makes building an app so damn easy that all your reasons for not shipping it disappear.
Remake is a new type of framework whose goal is to make building web apps feel more like doing a quick sketch and less like painting a masterpiece.
What if you could use regular HTML to build a dynamic, editable website in about an hour? Well, we’ve got news for you — Remake makes this a reality.
Remake wants to help you, above all else: Find product/market fit by launching early and often.
Our goal is to speed up this process:
Well, have you ever created a really nice design and wished people could just start using it? Remake lets you do that. Remake provides a simplified structure for what a web app can be, allowing you to take big shortcuts and speed up your workflow.
Remake works by redefining what HTML can do. It transforms HTML into a language that's purpose-built for making interactive websites. Simply put, Remake makes it possible to build a web app entirely in your front-end code.
With Remake, you can transform an HTML + CSS template into a fully-functional web app in minutes.
Remake assumes that most of your app’s data will be:
Remake features powerful, built-in components that will allow your users to create, edit, and delete data from the page, while also creating a direct link between your HTML’s state and your back-end state so it can keep them in sync.
In most apps, HTML is already used for organizing and displaying data. The only thing it’s missing (before it can be used for building dynamic web applications) is the ability to save data in a separate location so the page can be re-rendered later.
Remake adds what's missing from HTML to transform it into a simple, declarative, and powerful language for building web apps.
So, now you can:
And your users will be happy with your Remake web app as well!
With all of these powerful features, it only takes a few minutes to create a delightfully simple web app out of a few HTML + CSS files. With Remake, you can get back to shipping and everything else will be taken care of for you!
What will you make today? ✨🧙♂️
Continue reading: Intro to Remake, Part 2: What You Can and Can't Build
]]>It's amazing how many tools there are that get you from 0% to 100% in building a business, building a community, or changing someone's life for the better.
I want to explore what those tools are and how they can help you. My goal is to be unbiased and present only the best. I've carefully researched each one.
Any tool that saves you hours a day in any of the following categories counts as a no-code/low-code tool in my book:
To cut down on the amount of tools listed here, I have some strict criteria:
Also, each tool is rated with the following statuses:
Tools that let you build apps that work on smartphones, very quickly.
These offerings provide a really simple service, but make it easy to transform an otherwise static site into a dynamic one
Let users sign up for your service and get access to individualized data.
These tools remove concerns about data management, data storage, user management, and file storage.
These give you a head start in developing an app and often come with crisp code, beautiful UI components, admin dashboards, and seamless interfaces.
These make some things easy, but aren't that flexible. I think these are mostly for building back-office apps and not user facing apps. Also might not be mobile friendly.
These give you a head start in starting a full online service business and they usually come with landing pages, payment integration, and some UI components.
If you're a developer who can build useful APIs, but doesn't want to add billing, memberships, and marketing, these can get you set up quickly.
You have an online business, but don't know how to manage customer feedback, handle billing, or integrate a help desk. Start here.
The first generation of all-in-one web app frameworks that made it much easier to create powerful online products.
These solutions try to keep the benefits of modern frameworks (developer ergonomics, real-time updates, component front-ends), while removing the headaches (asset bundling or server-side rendering or too much to keep track of)
Some of the most interesting and revolutionary tools are in this category. These tools allow tons of flexibility while completely removing at least one full layer of the product development stack (e.g. database, back-end, build tool, hand-off).
These are some of the most exciting combos in the front-end framework world.
Uses a database schema to auto-generate a UI that lets your manage users and app data easily.
These UI frameworks come partially pre-assembled or let you build UI with a visual builder
These UI frameworks come with pages and components pre-built, so you can just piece them together like a puzzle to create a great web app.
You still need to take care of the back-end, but these easy-to-use libraries will make your front-end look nice without much work
Connect different apps and their events, so you can create an automated process to run your business instead of doing things manually.
Help customers explore your product and collect leads with automated forms.
A quick and easy way to get started prototyping an idea. Can be used to hand off a small project to a client.
Make beautiful-looking websites using data from a simple spreadsheet. One of the fastest ways to experiment with a new idea.
These tools work as internal admin dashboards by adding advanced capabilities onto the spreadsheet model
These tools focus on speed above all else. They might lose some flexibility, but your ability to get to market fast and test out your idea will more than make up for it.
These tools allow you to generate an API from a database very quickly, potentially saving years of work.
These tools scan 3rd party websites, assemble their info into a structured format, and let you use the data in your own web app
I hesitated to add this section, but I think it's relevant. As design and development become more entangled, everything should start to feel as easy as prototyping.
These tools will help bootstrap your marketing website by giving you the HTML and CSS to get started. You'll need to modify them and host it yourself.
These tools focus more on high-level components, letting you define the content, but not getting into the details
Uses web native tools (like online spreadsheets) that are familiar to users and can easily hook into multiple platforms to serve as a back-end
Makes it easy to manage data, so you can just focus on displaying it.
When you want to share your journey with customers and build a community, which tools do you turn to?
Usually not great for building a full-fledged web app, but great for managing a website builder.
More powerful than the standard CMS
How do you keep track of everything you're working on and share it with other people?
These tools help you set up a marketplace or membership-based website really quickly
These services let you collect visitor information and possibly display it somewhere else.
These tools help you do one thing really well with very little effort.
These tools allow you to build app views from database queries, generating the front-end automatically
Languages that let you do things differently than normal and save a lot of time.
These tools allow you to create a very high-level definition of your app, which is then seamlessly transpiled into the language/framework of your choice.
These tools work one level up from normal website builders, allowing you to make your own, custom website builder.
This seems like a big promise to fulfill, but if they can pull it off it would be amazing.
"Combine Hasura (automatic GraphQL on top of PostgreSQL) with React Admin (low code CRUD apps) and you can build an entire back office admin suite or form app (API endpoints and admin front end) in a matter of hours." — cpursley on HN
"We ended up using AppSync and it is fairly impressive. I highly recommend anyone who is stuck in the AWS ecosystem to check it out. AppSync integrates with a lot of other AWS services (Cognito, S3) very easily and allows you to use Dynamo/Aurora/RDS/Elastic as data sources. On top of this, you can also use Lambda to implement resolvers that need more intense business logic, making the service incredibly powerful." — afvictory on HN
"PostgREST is performant, stable, and transparent. It allows us to bootstrap projects really fast, and to focus on our data and application instead of building out the ORM layer." — Anupam Garg from a testimonial
"As I was looking at some of these workflows, I couldn't help but think how there will be this eventual shift like with Craiglist where companies will spring up that just focus on certain more complex popular workflows. . . . So my advice if you're looking for your next indie software idea. Just observe what these no-coders are automating on Zapier and build a nice UI around it." Kameron Tanseli from a blog post
There's nothing else like it. Hacker News is too harsh. Reddit discourages self-promotion in 90% of its subreddits. Indie Hackers has too much self-promotion.
In all this noise, Product Hunt is an oasis of pure, early-startup excitement about, well,
]]>Let me begin by saying I love Product Hunt.
There's nothing else like it. Hacker News is too harsh. Reddit discourages self-promotion in 90% of its subreddits. Indie Hackers has too much self-promotion.
In all this noise, Product Hunt is an oasis of pure, early-startup excitement about, well, products — and the people who build them.
As an entrepreneur and solo founder, nothing appeals to me more than a community site designed around building new ideas, showing them off to the world, and putting them in people's hands.
It's a wonderful corner of the internet that I enjoy visiting daily.
If you want to skip to the end, where I summarize Product Hunt's UX bugs and tell you how to avoid them, click here.
My first serious attempt at building a product for other people was Artisfy, a marketplace for hiring freelance illustrators for 1 hour at a time.
After spending 3 months designing every page in Sketch, 5 months building out the front-end code, and 6 months implementing a backend with Meteor, someone named Nate posted Artisfy to Product Hunt without telling me.
It was one of the best surprises I've ever received.
At 10am, on November 29, 2016 — 3 days after my birthday — I realized I was getting sign ups from real, actual freelance artists and users. By 10:15am, I realized where the traffic was coming from. Imagine my surprise when I realized Artisfy was featured in the #2 spot on the front page of my favorite website.
I was ecstatic.
After the success of Artisfy's surprise launch, I started talking with my new users. I emailed them, did lots of phone calls and video calls, went to meet them at coffee shops all around Boston and Cambridge, trying to learn what they wanted from Artisfy.
It didn't take long to realize most of them didn't need a new marketplace website. There were thousands of those already and many of the freelancers I talked to were looking to move off of them as soon as possible and recruit their own clients. Gradually, it sunk in for me: freelance illustrators needed a home for their professional services.
So, I set off to work on a brand new product, in the early days of 2017: RequestCreative.
This was a really, really exciting time for me. I was working out of a warm and lively co-working space and I got to focus on building RequestCreative every single day. I felt very lucky.
And, I had a plan for getting some early traction: Product Hunt Ship.
Product Hunt Ship has 2 big features.
If you can't tell, the second feature is, by far, the more exciting one 😆
You see, Product Hunt gets about 5 million visits per month (according to SimilarWeb). So, even if you're only featured 1% of the time (and I think it's more than that), that's a total of 5,000 people per month seeing your product.
That's an incredible value, especially since you know this audience is 1) interested in early-stage products and 2) might subscribe with just the click of the button to hear more about your startup in the future.
So, of course, I signed up right away and
Then I waited...
And over the course of the next three weeks, 2 subscribers trickled in.
They were both people I knew. 😫
It turns out, I had missed a toggle button that was buried deep in the Product Hunt Ship interface: "Promote on Product Hunt", which was off by default.
That's quite a lot of burying for a feature that most users aren't even aware they should be looking for. I personally assumed that — since it was, by far, the most valuable feature of Product Hunt Ship — it would be turned on by default.
This is currently the #1 UX bug on Product Hunt Ship and it ruined my second product launch.
During the launch day, when I didn't know about this little, secret toggle that unlocks 90% of the value of Product Hunt Ship, I was still blaming myself for not being able to get more subscribers.
I thought:
Maybe my copywriting is off
Maybe people just aren't interested in RequestCreative's promise
Maybe there was some secret algorithm that Product Hunt used to determine who was featured in the Upcoming box and I just wasn't chosen...
To be fair, Product Hunt Ship did send me one email with a mention of the "Promote on Product Hunt" toggle.
However, their mention of it was in the second (not the first) onboarding email, hidden way below the fold on mobile devices. To find it, you had to read through a bunch of other (mostly useless) copy and scroll past a big, centered image.
No one has time for that.
And this was the only time in the onboarding they called it out.
I ended up spending $79 on Ship (and a month prepping for the launch) for almost no value — all because I missed this single sentence.
I think the solution to this UX bug is simple:
Or, of course, they could just turn on promoting products by default, since it's literally the #1 reason anyone signs up for Ship and it's absolutely zany to charge for value you're not providing.
If you're curious about how RequestCreative did on launch day... I won't lie and tell you I wasn't disappointed.
It hung out, just off of the front page, all day, right under the "Show More" button.
It was a little heart breaking.
After RequestCreative didn't land with a splash, I was a bit disheartened. I truly thought I had created something useful and amazing — something the Product Hunt community would love.
When it didn't do well, it was hard to keep working on it, even thought it's something I had invested a year of my life into.
For anyone struggling with this right now — the anticipation or reality of a "failed" launch — I'd strongly recommend watching this excellent video by YC Startup School:
"Please don't fall in love with your MVP. It's just step 1 in a journey."
So, anyways, as a way of recovering, I focused on something fun. I started tinkering around with the code behind RequestCreative and slowly extracting it into a web framework.
At first, I told myself it was just for fun. But then I was making notes and taping them to my wall — and drawing diagrams — and seeing if what I had on my hands was truly as groundbreaking as it seemed.
This, eventually, after a year of hard work, became Remake.
Which, kind of, sort of, launched a few weeks ago.
You see, despite visiting Product Hunt nearly every day, I'm still not an expert at it. And, it turns out, I unintentionally sabotaged my launch day with the stupidest mistake possible.
On November 11th, I made the decision to delay Remake's launch by 2 days, from 11/20 to 11/22.
My promo video wasn't finished yet, I had on a couple pages of documentation done, and I the website was only 50% done.
Then on November 18th, with the pressure of a quickly approaching launch day mounting again, and still no website ready yet...
I rescheduled again.
To the past... oops 🤪
Like any truly visionary founder, I moved up my MVP launch date to be as early as possible: November 5th — 2 weeks in the past.
🙃
And the thing that surprised me the most: Product Hunt Support simply responded with, "Sure thing that's changed for you now!"
The next morning, I checked on it and realized my unbelievable mistake and the Product Hunt staff quickly rectified the situation.
Remake was now back to being scheduled for the future — December 5th — and I was going to be ready!
(However, this simple rescheduling-to-the-past mistake would soon come back to haunt me...)
Before we get to the true comedy of errors of this story, this is where Product Hunt's UX mistake #2 comes in.
The only reason I needed to talk to a human to reschedule my post (instead of doing it myself) was because their scheduler only allows you to select a date a few weeks out.
And, although it gives the mistaken impression that you can keep pushing it further out every couple of weeks, indefinitely — eventually you reach a hard limit.
So, the simple solution here would be to allow people to schedule their product whenever they want to.
Ideally, only in the future. 😆
You might wonder: why even schedule your product at all if it turns out to be this complicated — having to reach out to customer support every couple weeks?
Well, it turns out, if you choose not schedule your product's launch, then anyone else can launch your product for you — before you're ready 😳 — even if you have an upcoming product and are paying for Product Hunt Ship.
Launching early, this time around, would've been a disaster — unlike with Artisfy.
You see, at this time, I still thought of Product Hunt as a great source of interested early users and developers and I was really looking forward to knocking it out of the park with a beautiful startup website.
If someone launched my product early, the website would've been the early, very rough, barely coherent documentation site — which provided only a single demo, no email sign up forms, and no substantial information about how to use Remake.
It probably wouldn't have done well — and I wouldn't have been able to launch it again on my own, since it was launched already.
When I spent 3 months of time and energy gearing up for a big Product Hunt release, it simply wasn't worth it to launch early.
Remember when I mistakenly scheduled my post for November 5th — two weeks in the past?
Well, apparently that caused some major issues with Product Hunt's backend and really came back to bite me the day before launch.
I was in the middle of updating the launch website, when I remembered to check in on my Product Hunt post to make sure everything looked good.
That's when I noticed something really bad:
Remake had suddenly reverted to being posted a month ago. 😣
I reached out to support right away.
As usual, they responded in under an hour and rescheduled the post for December 5th — the following day.
Then, while continuing to prepare my post for launch day — adding some images, refining the copy and headlines — I noticed it happened... again. 😳
At this point, it was the evening before the launch — just a few hours out.
I started panicking:
I quickly looked around on the Edit Post page, looking for anything that might have caused the scheduled date to be canceled and cause my product to launch right away.
And that's when I saw it: a simple little magic toggle that I had never truly understood, but had occasionally toggled when I didn't know what else to do.
I know, I know...you really wouldn't think I design web apps for a living... 🤪 The #1 rule for users of software should be, "don't toggle a magic setting you don't understand."
The two options:
With no further explanation.
I assumed, in my moment of (slight) panic, that these options referred to the status of my product post, not the status of my startup.
You see, a "pre-launch startup" is a technical term in Silicon Valley. It means:
As a Boston native, I don't hear it as much, except when I'm watching YC videos.
So, (somewhat my fault here...) it simply didn't register as having anything to do with the status of my product/startup.
I especially didn't think it would be something that would completely decimate my launch.
So, I set the Status to Pre-launch, contacted customer support to reschedule the post, and promptly forgot about the magic little toggle.
This is about the time someone on the Product Hunt staff told me to stop editing my post, because: "when you update the post, it stops it from being scheduled."
😳
Huh?
Well, um.... okay, I guess. 😐
That made no sense to me, but I figured it was probably because of the scheduling-to-the-past thing from earlier (which must have really caused some hard-to-undo database flag to be set).
I told the marketing freelancer I was working with to not touch the post either — it was completely hands-off until launch time.
We both gently stepped away from the keyboard, hoping and expecting a perfectly normal, relatively successful Product Hunt launch.
I sent out the launch emails and went to bed.
I woke up on the morning of the launch and checked on the Product Hunt post. It had gotten 7 upvotes already — more than a lot of the other posts, but fewer than some.
It hadn't made it to the home page yet...
But I was still hopeful.
I went for a walk, recorded a quick launch day video for people interested in the project, and went back inside to start my 9 hour launch live stream 😁
I answered questions and emails, got some serious help from @levelsio on a Twitter post that went viral, posted a couple launch posts to Reddit and Designer News — and checked on Product Hunt about 3 times an hour.
For the whole day, I couldn't shake the feeling that Remake should've reached the front page with the amount of votes it was getting.
Then I got a couple of messages from Product Hunt users that Remake wasn't showing up anywhere on Product Hunt — not even when they searched for it. They said they had to go to the "New" tab and scroll forever just to find it.
I started worrying that maybe something had gone really wrong.
Maybe I really had messed up Product Hunt's database with my rescheduling... Or annoyed the staff enough with my requests for them to shadow ban me from the home page...
To be honest, I had no idea what was happening.
I reached out to customer support, once again, towards the end of the day — after not making it onto the home page despite getting more upvotes than 7 of the other products featured there.
I don't understand why my page failed to reach the front page — even under the Show More button. It launched around the same time as a lot of the products at the bottom and received more upvotes than some, but it never made it to the front page.
Did something malfunction with my post again?
Then I got a little cynical: was it an editorial decision? Did the staff prevent my post from hitting the front page because they thought it wasn't good enough?
I scoured Product Hunt's FAQ for answers.
At the very bottom of the FAQ articles in the "Posting" category, I came across a certain article title — that, when I read it, instantly made me realize my big mistake. 🤦♂️
I suddenly remembered back to that toggle on the Edit Post page.
I opened the FAQ article to read it.
Here's what it said:
We encourage people to submit launched products that are available to play with immediately; however, the occasional crowdfunded or pre-launch submission is acceptable if it provides thorough information about the product (e.g. clear video with a product feature walkthrough) and proof that it's not vaporware. That said, we may remove pre-launch submissions and recommend founders to wait until their product has launched before submitting.
I didn't want to admit it to myself, but I had single-handedly sabotaged my own product launch and been responsible for shadow-banning myself from the Product Hunt home page.
I reached out to customer support one more time.
Indeed, that was it.
They reached out to me shortly after, with a link to the article I had just read.
I'm sure a lot of people who launch on Product Hunt know what Pre-launch means and they, also, might understand that it impacts their eligibility to reach the home page.
But, also, maybe they don't.
It would be very, very polite of Product Hunt to just tell you, on the Edit Post page, exactly what Pre-launch means — so more people like me don't sabotage months of work over a misunderstanding.
So, you might be wondering: what happened to Remake?
Well, actually, thanks to a launch tweet that @levelsio helped me write and share, the launch was actually a big success!
Remake also received a lot of interest on Reddit and Designer News!
All this, despite not receiving any traffic from Product Hunt. Since my product wasn't featured at all or even available in search, Remake ended up sending hundreds of visitors to Product Hunt, but didn't really receive any back.
I'm hoping Product Hunt might consider giving me a redo, but for now I'm happy to just share this, so someone else with a big idea might avoid my mistakes.
For now, Remake is still marked as pre-launch, even though the product, on launch day, was (and has been for months) functional and ready to use!
I only learned what this little dot of invisibility meant on December 6th, after it was too late. If I only I had hovered over it... 👻
So, without further ado:
It will help your launch out so, so much and get you a lot of early email subscribers! It's totally worth (even double) the current cost of Ship.
Hint: It's on your upcoming page, in the sidebar, about halfway down, under a few other options you probably don't care about.
2. If you decide to reschedule your launch at any point, check with a friend or coworker first to make sure the launch date you chose is in the future 😜, because otherwise, Product Hunt might schedule it in the past for you — mark it is as already launched — and that could cause a lot of unexpected, totally weird issues for you down the line.
🤷♂️
Seriously, though: I'd recommend claiming your product's URL by scheduling a post and then ask customer support to schedule your launch way in the future.
This way, you'll have plenty of breathing room, and you won't have to deal with the hassle of reaching out to Customer Support every week or two to push push back your launch into the future.
And you can just launch when you want, with no worries or mishaps.
3. Don't touch the Status dropdown on your Edit Post page. Don't ask what it does or what it means. Just don't touch it.
Thanks for reading :)
Reach out if you want any more tips on Product Hunt launches — I'm (very slowly) becoming competent at using it. 😆
The main challenge that Remake.
]]>The main challenge that Remake.js helps a developer like me overcome is organizing data on the fly. When you’re building a web application, you often want to plan things ahead, but with Remake.js you can let that naturally evolve more than in other frameworks.
Why is that?
With some frameworks, you have to put data into components and then attach behaviors to them. It’s relatively easy to move things around, but it still takes some time to think through it.
With Remake.js, components arise naturally after you attach enough functionality and data to them, but they’re not set in stone. You can easily move the data or behavior to a new element and then start working from there.
Let’s take a quick look at what a component in Remake.js might look like:
<div
class="page-data"
data-switches="disableTimelineQuickTip(no-auto) pageColorBackgroundPicker selectButtonColorPicker"
data-i-choices="hideStars hideMasthead"
data-o-save="pageData"
data-o-type="object"
data-o-key-username="david"
data-o-key-email="[email protected]"
data-o-key-email-confirmed=""
data-o-key-hide-stars=""
data-o-key-hide-masthead="true"
data-o-default-username="david"
>
</div>
This is the top-level component in a real application. I know this is a lot to take in, but without trying to understand it on your own, just read this:
pageData()
Now, if you want, you can move this data around simply by moving a couple of these attributes.
That’s the power of Remake.js. It’s easy to move things around / experiment / and attach powerful functionality with a single data attribute.
These are just a few of the very powerful data attributes that Remake.js has.
Now, the thing I want to talk about in this post is how to make where the data shows up more flexible.
First, here’s how it works most of the time:
<div data-o-type="object" data-o-key-name="David"></div>
That tells Remake.js that this DOM node contains information, and, when parsed, that information will look like this object:
{name: "David"}
Another way to store this information in the DOM is like this:
<div data-o-type="object" data-l-key-name>David</div>
The data-l
attribute is different from the data-o
attribute in that in points somewhere else, instead of its attribute value. The l
stands for location. In the above case, it’s using the default location: the innerText of the current element.
However, you can also specify another location, like this:
<div data-o-type="object" data-l-key-name=".name">
<div class="name">David</div>
</div>
The value .name
means: “search my child elements for a class .name
and, when you find it, get the innerText of that element.
So, remember when I told you components end up arising naturally in Remake.js from the way the data is organized?
Well, let’s go through one more example of a high-level component…
<div
class="bundle"
data-switches="bundleDetails(no-auto) bundleTimeline(no-auto) bundleLegend(no-auto) deleteBundleConfirmation(auto) bundleRevisionsChoice(auto, inlineEdit)" data-o-save-deep="singleBundle"
data-i-choices="majorRevisionsCount minorRevisionsCount"
data-o-type="object"
data-o-key-bundle-id="abc123"
data-o-key-bundle-price="300"
data-o-key-major-revisions-count="2"
data-o-key-major-revisions-hours="1"
data-o-key-minor-revisions-count="2"
data-o-key-minor-revisions-hours=".5"
data-o-default-bundle-price="100"
data-o-default-major-revisions-count="0"
data-o-default-major-revisions-hours="1"
data-o-default-minor-revisions-count="0"
data-o-default-minor-revisions-hours="1"
>
</div>
This one is similar to the .page-data
one in that it contains a lot of information. However, it’s different because instead of global preferences, it’s storing data for the component.
Let’s go through what the attributes above are doing:
singleBundle()
And that’s it!
Now, earlier we mentioned the use of data-l
(location) keys that keep track of data, but store that data inside the DOM — like in the innerText — instead of as the attribute value.
How come we’re not doing that here?
Well, we were at first, but then it turned we needed to do some funky things, including:
With data-l
attributes, we had no way of modifying the data before it was displayed. The displayed data was simply the data and that’s it. There’s no in between.
In order to get around this problem, we store the values in regular data-o
attributes and then use data-w
(watch) attributes to detect changes and modify values whenever anything changes.
So, if the price changes on this .bundle
element, for example, a child nested inside the .bundle
element might have a data-w
(watch) attribute that looks like this: data-w-key-bundle-price="formatPrice"
.
This means: “Hey, if any of my parents data changes and that change happened to a key called bundlePrice
, then please call my function formatPrice
.”
Then, formatPrice()
will use the new value for bundlePrice
, format it like a currency, and — in this case — insert it into the element with the data-w-key-bundle-price
attribute on it.
Pretty neat, right?
For adding the revisions counts together, we do something similar, but attaching BOTH the data-w-key-major-revisions-count
AND data-w-key-minor-revisions-count
attributes to it and using a sumRevisions()
function. sumRevisions
looks at the top-level revisions counts and adds them together, using the sum as the value of the current element.
It’s easy once you get the hang of it.
So, now let’s move on to the main problem I want to address with this post: Theming.
So, to make a theme, we have a few options:
The first option is nice, but it’s a little hard to work with because you might have to add some tricky CSS here or there because the structure of the elements doesn’t match how you would’ve built it if you were starting from scratch.
The second option is perfect, in my view, because it involves changing only a few things — most of the DOM structure is probably fine — and by duplicating things you’re not really causing too much of an issue. You’ll be namespacing the theme using a global class anyways, so it should be relatively easy to hide certain elements depending on which global class is present.
The third option is nice… but it’s a lot harder to maintain. Now, every time you want to change the structure of the original DOM, you have to think about whether you ALSO want to change the other themes’ structures — and this goes for EVERY theme. The problem in this case isn’t really going through every theme, although that’s certainly annoying. It’s that it’s hard to remember. When you’re in the moment, changing something, it’s really hard to remember that the code exists elsewhere and was just kind of duplicated.
Also, another problem with the third option is: what about the case where you don’t need to change the DOM structure? Do you still duplicate all the code? Or do you use a combination of option one AND option two? Things get complicated fast.
Okay, so, with that in mind, I chose option two. It feels a tiny bit hacky because there’ll be a lot of CSS and HTML overlapping each other, but as long as 90-100% of the CSS is namespaced and doesn’t affect the other themes, I think it should be fine and the easiest to work with long term.
So, now we’re talking about duplicating elements inside a parent component and then conditionally hiding or showing them.
Here’s the main question: How do we get the data these elements need to be inside of them?
Well, we just saw and used data-w
(watch) attributes for this and it was pretty easy.
All we’d need to do is attach a data-w
attribute to each of the elements that need its value and then everything should work fine.
I think, now that I’m here talking about it, that works pretty well, and I don’t mind it as a solution. However, I’d like to explore one other solution: data-f
and data-c
attributes.
These are new attributes, not yet introduced into the code, and this post is meant to help me think through if they make sense at all.
The idea would be:
data-f
(format) attributes would compute formatted values based on one or more other values. They’d be kind of like data-w
attributes, but have less power. While data-w
attributes can do anything, data-f
attributes’ purpose is only to provide an intermediate value.data-c
(copy) attributes would copy data from data-o
, data-l
, and data-f
attributes into themselves. Their ONLY purpose would be to copy data — that’s it.Now, I’m a bit skeptical about inventing new data attributes. The ones I have already do a lot and work well enough. I’d prefer to keep this framework from expanding further if possible. However, the question here is whether these new attributes would add enough value in terms of simplicity to be worth the extra intial confusion when learning the framework — and provide much needed helpers to more experienced users.
The #1 good thing about them is they simplify what most data-w
(watch) attributes are already doing into two explicit & simple steps.
Most data-w
attributes aren’t doing anything crazy, they’re copying data or computing a new value from existing data and then copying that into themselves.
However, when you, as a developer, are looking at a data-w
attribute, you have no idea what it’s doing. It could be using data from the current component, from the global scope, or even from an API or other source.
When you look at the top level of a component though and you see a bunch of data-f
attributes, you would know that those computed values are being used inside the element somewhere. And nothing extra is happening. Also, you could pretty safely ignore all data-c
attributes. They’re just copying data into themselves — not modifying the DOM, switching things on or off, adding classes, or anything else JS can do. Even though you can, of course, establish some best practices around how your team uses data-w
attributes, it’s safer to just know what can and cannot be happening just by looking at the page.
Even if we add these new attributes, we’ll still need data-w
attributes for some more complex cases, but if they handle 90% of data copying/formatting cases, then I think they’ll end up making things clearer than they are now.
The other minor benefit (and I say minor because this framework certainly wasn’t designed with performance as the #1 goal) is that you won’t have duplicate data-w
attributes with duplicate the same functions inside them, which will each need to be called separately. You’ll compute the data inside the data-f
attribute AND THEN pass the intermediate value down into the component. So, it will only be computed once. That’s kind of nice.
So, how would it look?
Let’s use a simple example (first, using data-w
attributes):
<div
data-o-type="object"
data-o-key-price1="100"
data-o-key-price2="200"
>
<div
data-w-key-price1="sumPrices"
data-w-key-price2="sumPrices"
>300</div>
</div>
In the above example, we’d store both prices at the top level, and then use data-w
attributes to see when either changed. When they did, we’d sum them and add them to the current element’s innerText.
Now, to rework this using data-f
(format) and data-c
(copy) attributes:
<div
data-o-type="object"
data-o-key-price1="100"
data-o-key-price2="200"
data-f-key-total-price="price1 price2 | sum"
>
<div
data-c-key-total-price
>300</div>
</div>
This is pretty similar, however it’s kind of nice to see ALL the data that’s being passed down into the component, like the total price, upfront — instead of having to look inside the (possibly messy) component and hunt for the place where the total price is being computed.
The thing I don’t like the most is the syntax of the data-f
attribute. I like that it creates a new key, but I don’t like its value’s syntax.
I suppose there are other options, like these:
data-f-key-total-price="sum(price1, price2)"
data-f-key-total-price="price1 price2
In this first option, we’d pass the keys directly to a function, which I guess feels more natural.
And in the second option we’d automatically generate a function to be called named after the new key… something like computeTotalPrice()
. The function name wouldn’t be specified directly in this case. That would be kind of a bummer because it would make what’s happening slightly less explicit.
Anyways… the other thing I don’t like is all the new attributes. They make sense to me, but I worry that introducing them adds an extra layer of complication. It’s nice for more advanced use cases and more experienced users, but for beginners, it’d probably be hard to understand why you’d these new attributes. And, if possible, it’d be great to only have a few attributes to learn to get started and be productive with this framework.
This latter point can be address by simply hiding these attributes from new users and only introducing them long after they’ve learned about the more powerful data-w
attribute.
I’m not sure if it’s worth it.
From my perspective, as a developer, I think I slightly prefer the new data-f
and data-c
attributes. It’s really nice to be able to see ALL the data inside your component with just a single glance. Having deeply nested data-w
attributes doesn’t really provide this. However, as “extra” data that isn’t saved when the element is serialized, I’m not sure how much that data matters.
So, for now, let’s just say this syntax is my favorite for data-f
attributes: data-f-key-total-price="sum(price1, price2)"
and I think data-f
and data-c
attributes are barely worthly of being included in the main library. I’m not sure when or if I’ll add them, but it was fun to think through them and figure out a new way of doing things.
Thanks for reading!
]]>It started out with just a simple positioning library. I wanted to be able to position popovers. Then I added another library that would handle setting element dimensions on
]]>It started out with just a simple positioning library. I wanted to be able to position popovers. Then I added another library that would handle setting element dimensions on popovers automatically.
Then, I added the big one, or what I thought was the big one: Widget.js.
Widget.js (later renamed Switch.js) had been used in many projects at that point, including the original version of Artisfy.
What did it do?
It’s very simple, really. It would let you click a button to activate a widget. If you clicked inside that widget, you could interact with it, but, if you clicked outside of it, it would close.
Very simple stuff. It basically was a very, very short piece of code that stood in for code that I would write over and over and over again.
But then, it got more complicated.
Sometimes I wanted to click somewhere else and have the widget close, but sometimes I didn’t want it to close. Sometimes I wanted to activate multiple widgets at the same time.
Anyways, it turned into a big piece of code that’s actually really elegant in what it does.
This, however, didn’t constitute a framework. All I had was the ability to activate or deactivate some widgets on the page and position them.
It only turned into a framework when I started thinking about DATA.
It started out simply enough.
I had this beautiful idea for a web framework:
What if I could make a webpage, edit that webpage, and then just save the edited webpage?
Isn’t this how all web apps work though?
Well, yes, but this one took things to a whole different level of literalness. Most web apps let you edit DATA and then save the DATA — and then display that data in a visible format.
But, what I was thinking was:
What if I just bundle up everything together and save it all at once? Then there would just be one source of truth: the webpage.
If you’re not understanding yet, this is how it’d work:
But… this is insane… isn’t it?
Well, not really. It’d let you make simple, interactive webpages very, very quickly. All you would need is HTML, CSS, and the ability to change the HTML. No database, no schema, no back and forth with the server. All the data in on the webpage and it’s all saved. What the user sees is what they get the next time around.
You’d only want to give people access who you trusted, of couse, because they could add ANY code they wanted, including external JavaScript that was meant to steal passwords.
But, even that, could be prevented. You could use a content security policy on modern browsers to make sure no external scripts were added. And you could take the extra precaution of stripping out dangerous code with DOMPurify or something.
The elegance of this solution was beautiful to me. As a front-end developer who doesn’t like messing around with databases and sessions, it was perfect for creating quick, one-off projects.
It was as simple as: “Edit HTML, Save HTML.”
What about authentication? Well, the server could handle that.
And when you get an admin user, you add certain classes to the HTML that show them the edit controls.
And, when you get a regular user, you strip all the admin classes out of the document.
It was pretty sleek.
… But I ran into a problem: What about using data in more than one place?
What if I have a webpage with a username and then on the very next page I want to display the same username? Do I force the user to change it twice? That’s very unintuitive… People have gotten used to thinking in terms of settings and preferences that are persistant and global. People don’t really think that webpages are each just separate documents.
But! There’s a solution for that too! You could use the webpage as a database and query it. So, if you’re looking for the username, just keep track of its element’s selector and pass that into your template… Then you could search through other pages’ data using a document query syntax, just like you’re working on the front-end.
However, it’s not that simple. Here’s why:
Of course, there are ways around the 2nd point.
And the 1st point is like… whatever. It’s still nice to have new things in the world even if other things exist that can do the same thing. You never know if the new thing will be better in some way… eventually.
Anyways, though, I decided against implementing it because it sounded like too much work for me… especially back-end work, with lots of data and complicated configurations and security implications. Stuff I’d rather not be responsible for at the end of the day. The risk of a mistake is too high.
However, it’s still a good idea, especially for lightweight demos:
Boom! Web framework!
So, I think this is something I’d like to work on eventually, some day.
But, let’s get back to my journey to creating a framework.
So, here I was, feeling a little despondent that my idea of saving a while webpage wouldn’t work, especially because of the whole “how do you reference state from two different places?” problem.
And… I came up with an idea.
At the end of the day, I’d be rendering back-end templates anyways, which support variables and interpolation and stuff. So, why not just find a way to markup my pages, so I could quickly extract the data from them… and then feed this into the back-end templates?
I know… I know… I’m not saying anything new again. This is how ALL web apps work… haha.
But, it was a little bit different… because I was thinking about making it automatic, so you wouldn’t even notice… So, it would be almost as simple as just saving a webpage.
Anyways, without further ado, this is what I came up with:
<div data-o-type="list">
<div data-o-type="object" data-o-key-name="David"></div>
<div data-o-type="object" data-o-key-name="John"></div>
<div data-o-type="object" data-o-key-name="Sam"></div>
</div>
Making up the HTML with these data-o-type
attributes would allow a custom parser I’d write to quickly and easily extract the data from the DOM that was necessary to render it again…
Basically, it would capture everything about a webpage that the server needed to render that page.
If you’re curious about what the o
in the data-o
stands for, it’s “output”. That’s because it’s data that’s being output from the DOM, as opposed to inputted by the user (we’ll get to that later).
So, let’s talk about what the above piece of HTML would look like as data.
First, we define a list with data-o-type="list"
.
Then, inside of that are some objects, with data-o-type="object"
on them.
That means we’ve got some objects inside a list.
Pretty easy, right?
The key thing to notice is how the structure of the data corresponded to the structure of the DOM. If there’s a nested DOM node, it most likely belongs inside of the DOM node above it. Or, at least, at the same level. So, that’s what this library (Remake.js) assumes.
Here’s what the above elements look like as actual data:
[
{name: "David"},
{name: "John"},
{name: "Sam"}
]
And, here’s what the back-end template that would render this might look like:
div(data-o-type="list")
each val in ["David", "John", "Sam"]
div(data-o-type="object" data-o-key-name=val)
(This is pugjs btw).
How useful is though? I mean you’re storing data in the attribute, but it’s not on the actual page… It’s not displayed anywhere…
Well, in order to address that, we have data-l-key-*
attributes, which, by default, look for their value in the innerText of the current element (but can be told to look elsewhere).
So, now, instead of the original HTML example, you could use something like this:
<div data-o-type="list">
<div data-o-type="object" data-l-key-name>David</div>
<div data-o-type="object" data-l-key-name>John</div>
<div data-o-type="object" data-l-key-name>Sam</div>
</div>
And this will give you the exact same output:
[
{name: "David"},
{name: "John"},
{name: "Sam"}
]
Which you can feed into your back-end template, which now looks like this:
div(data-o-type="list")
each val in ["David", "John", "Sam"]
div(data-o-type="object" data-o-key-name)=val
And that’s it!
Now you have the ability to write some HTML, attach a few data attributes that make that HTML serializeable, so you can extract the data from your HTML, and feed it back into your templates. It’s a really lightweight framework… kind of.
:)
Next time we’ll talk about some features I’ve been thinking about building. Also, we’ve barely scratched the surface of this project and its capabilities. In fact, I build an entire application using this framework which is in production right now.
And, while it does have its faults (it’s getting a little hard to put all the pieces together sometimes and it does some hacky things like storing all data as strings…), it’s been a real pleasure to work with and made it 100% faster for me to build new features.
However, the next features I’m building for it are meant to address some of the problems I’ve run into when thinking about adding themes to my application.
I’ll talk more about that in the next post.
]]>