DenoPress: My attempt to remake WordPress in JavaScript — Part 1

As any of my handful of followers knows, when it comes to coding, I’m a nut for simplicity. I’m a fan of PHP, and I use a lot of WordPress.

With the advent of Gutenberg, using WordPress got easier — but customizing it got harder. Suddenly, you needed both PHP and node.js servers to develop custom blocks. And I hate the deprecation hell that comes with Node.js. And I know there are lots of people who frankly prefer JavaScript/TypeScript to PHP.

Well, what about Deno — the wonderful remake by the same guy who developed Node.js? Could I create “DenoPress” and serve an existing WordPress database without any PHP? In any event, it will be a chance to learn something new! Here is how I think this will go:

  • Part 1 — Display existing WordPress menus and text without PHP, in an attractive but super simple style.
  • Part 2 — Properly display images, YouTube embeds and more.
  • Part 3— Create the functionality of wp_admin, and successfully create posts and pages that will still work in real WordPress.
  • Part 4— Integrate Gutenberg functionality, hopefully based on the existing opensource Gutenberg Editor

Others have already worked out how to put Deno apps in Google Cloud based Docker containers, so if I succeed one can establish cloud-based DenoPress sites.

Themes and plugins are one reason people love WordPress. Those items are written in PHP. So… they could conceivably be translated into JavaScript, but actually there are just too many themes and plugins anyway. Better to create an easy-to-customize theme which is what I’ll use here.

You are welcome to play along with me. I’ve put everything into a public repository here.

The WordPress database scheme is very simple. Perhaps too simple. To generate a menu, you need a query across 4 tables, two of them twice! Almost everything else you need to display the content is in the wp_posts file or the wp_options table.

Fortunately, someone else has documented the table scheme — you can find the basic information here. But to really understand where things are, you need to actually browse through the data.

I’m working with a local installation of WordPress and MariaDB, but you could do the same on a public server if you have access to the database through phpMyAdmin or phpMiniAdmin (which I prefer) to peek into the tables.

Site settings: I need the site name and site description. These are in wp_options. The table is designed to hold anything your theme or plugin desires in fields option_name and option_value. As testimony to the beginning of WordPress, these have option_name blogname and blogdescription.

Custom menus: The early versions of WordPress did not have multiple menus — posts were parents of other posts (and pages are just a special flavor of post), and the menu built it from the pages. I don’t think anyone does it that way anymore. These days themes have a custom menu which, hopefully is responsive and handles dropdowns. The information you need is:

  • There are usually multiple menus these days, and you’ll need the id of the one you want. Since they vary by theme (and I’m not going to use themes), I just showed them all and picked the long one from this query:
  • To recreate a drop-down menu, you need the label for the menu item, the post id that it will link to, the order of the menu items and the “parent” id for each item, which tells you if you’re in the same <ul> block or not. You get all this one query across five tables, including one of them twice!
select a.ID as id,a.post_title as title,b.post_title as alt,c.meta_value as link,d.meta_value as parent,a.menu_order as ordfrom wp_posts a, wp_posts b, wp_postmeta c, wp_postmeta d, wp_term_relationships ewhere a.ID=e.object_id and term_taxonomy_id=896 and c.post_id=a.ID and a.post_type='nav_menu_item' and c.meta_key='_menu_item_object_id' and b.ID=c.meta_value and d.post_id=a.ID and d.meta_key='_menu_item_menu_item_parent'order by ord

This is nice, as it gives you the items in the order they will be turned into lines of html.

Interesting Oddity: The label in the menu item might be in one of two places. If the post defining the menu item as a blank post_title, that means use the post_title from the target post — that’s why wp_posts appears twice in the query.

Deno is new. The big advantage over Node.js is that you import what you need via URLs, not via npm. Generally, you can find anything you want in Deno.Land itself, either its built in functionality (preferred, although a fair bit of this is “unstable”) or its standard library or third party items curated on the site. These are in TypeScript, which makes them pretty robust.

If you use VSCode, you will need to add the Deno plugin and then you’ll need to manually edit .vscode/settings.json to add “deno.path”=”/path to the deno command” or VSCode will mark lots lots of errors. If you’ve cloned my repository, that last bit should be in my code but you may need to change it.

Because I wanted to keep the repository public, you’ll need to put your MySQL wordpress User and Password into the environment variable WPU and WPP.

Deno now has a built in web server object, which is not really well documented yet, so to get going I’ll use the Drash package, which has one of the simplest servers for dealing with dynamic content and static files: in app.ts:

import { Drash } from "https://deno.land/x/drash@v1.4.4/mod.ts";
import HomeResource from "./home_resource.ts";
const server = new Drash.Http.Server({
directory: "/home/johncoonrod/denopress",
resources: [HomeResource],
response_output: "text/html",
static_paths: ["/static"]
});
server.run({
hostname: "localhost",
port: 8000
});

All the dynamic work gets done in home_resource.ts. It needs to be quite asynchronous as it responds to HTML requests but also must await the data coming in from the mysql server.

import { Drash } from "https://deno.land/x/drash@v1.4.4/mod.ts";
import { Client } from "https://deno.land/x/mysql@v2.9.0/mod.ts";
// connect to the databaseconst db = await new Client().connect({
hostname: "127.0.0.1",
username: Deno.env.get('WPU'),
db: "wordpress",
password: Deno.env.get('WPP'),
});
// CODE GOES HERE TO READ IN BASIC SITE INFO AND MENU
// THAT GOES INTO THE TEMPLATE FURTHER DOWN - SEE REPOSITORY....
// OK - now the fun part. For now, we just parse one optional
// path parameter for the post_id I want to show.
export default class HomeResource extends Drash.Http.Resource {
static paths = ["/:p?"];
// the GET function departs from drash samples as it must by asyncpublic async GET() {
var post;
var contents=["Title","Body"]; // just defaults
const param = this.request.getPathParam("p");
if(param) { //... then read it in!
query=`select post_title,post_content
from wp_posts where ID=${param}`;
post=await db.query(query); // this returns an array of 1 row
contents=Object.values(post[0]);
}
// bunch of code to paste everything into a template
this.response.body = `...`
return this.response;
}
}

Click here for Part Two!