This document contains my coding guidelines and best practices for software projects. It is meant to be general purpose and should be a starting point. If any guidelines here conflict with a project's specific guidelines, then the project's specific guidelines should take precedence.
Write secure code.
frontend folder at the root of the project
<head> since it's usually shared across pages) and then the body of the page be a LIT component that will be hydrated on the client-sidebackend folder at the root of the project
scripts folder at the root of the project
<!-- frontend/_components/page-head.webc -->
<head webc:is="fw-head" webc:nokeep>
<meta charset="UTF-8" />
<title @text="title"></title>
<link rel="icon" href="/logo.svg" />
<link rel="apple-touch-icon" href="/logo-180.png" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
<meta name="description" :content="description" />
<link rel="manifest" href="/app.webmanifest" />
<!-- Control the behavior of search engine crawling and indexing -->
<!-- All Search Engines -->
<meta name="robots" content="index,follow" />
<!-- Helps prevent duplicate content issues -->
<link rel="canonical" :href="canonical" />
<!-- Facebook Open Graph meta -->
<meta property="og:url" :content="canonical" />
<meta property="og:type" content="website" />
<meta property="og:title" :content="title" />
<meta property="og:image" :content="og_image" />
<meta property="og:description" :content="description" />
<meta property="og:site_name" :content="title" />
<meta property="og:locale" content="en_US" />
<script
src="https://cdn.usefathom.com/script.js"
defer
:data-site="fathom_site_id"
webc:keep
></script>
<script type="module">
navigator.serviceWorker.register("/sw.js");
</script>
</head>
// frontend/_data/site.js
const url = "https://app-url-here.com";
export default {
title: "My App",
description: "My App description here",
url,
canonicalUrl: `${url}/`,
copyrightName: "My App",
isProdEnv: process.env.ELEVENTY_ENV === "production",
fathom_site_id: "my-fathom-site-id-here",
currentYear: new Date().getFullYear(),
build_time: new Date().toISOString(),
};
<!-- frontend/includes/layout.webc -->
<html lang="en">
<head>
<meta
webc:is="page-head"
:title="page.title || site.title"
:description="page.description || site.description"
:fathom_site_id="site.fathom_site_id"
:canonical="site.url + page.url"
/>
<!-- inline the webc component styles -->
<style @raw="getBundle('css')" webc:keep></style>
<!-- inline the webc component js -->
<script type="module" @raw="getBundle('js')" webc:keep></script>
<!-- other stuff -->
</head>
</html>
We will use cloudflare workers for hosting. The frontend will be static assets served from cloudflare workers, and the backend will be serverless functions served from cloudflare workers.
if you can build something with HTML and CSS, favor doing that over using javascript
use progressive enhancements as often as possible
Use modern CSS for styling (no preprocessors, no frameworks, no css-in-js)
Use grid and flexbox for layout
use semantic elements when possible, only use divs and spans when there is no other semantically-appropriate element
prefer nested css selectors, element and ID selectors over class selectors. For example, instead of <header><h1 class="title">The title</h1></header>, use <header><h1>The title</h1></header> and then use a selector like header { & h1 { ... } } instead of .title { ... }. Because if we are writing accessible, semantic HTML then there is only one h1 in the header.
avoid inline styles in html, figure out a proper selector and use that to select and style the element.
For a card component: use an article element. And instead of using div class="footer" in the card,use a <footer> element. Same for the header, use a <header> element. For the body use a <section> element.
if you can't write a selector based on an element tag, you can use an ID if that element is unique inside the shadow root of that component.
use REM over pixels for font sizes and spacing.
// @ts-check at the top of the file to enable type-checking in .js files@typedef, @param, @returns, and @type annotations to express types in JSDoc@type {string[]}) when inference is not obvious.d.ts files. Data models are an example of a shared type.d.ts files into our code with jsdoc comments, like this:/** @import { User } from '../../models/User' */
when using lit as a dependency add it as a devDependency for typing at DX time, and use an import map "lit": "https://cdn.jsdelivr.net/gh/lit/dist@3.3.1/core/lit-core.min.js" to load it at runtime.
declare properties in the static properties object, and then set their default value and type in the class with field initializers, like this:
static properties = {
page_name: { type: String },
stuff: {type: Array<string>},
some_state {type: String, state: true}
};
/** @type {string} */
page_name = 'new/edit';
/** @type {string[]} */
stuff = [];
/** @type {string} */
some_state = 'initial';
export class MyThing extends LitElement {
// ...
}
if (!customElements.get("my-thing")) {
customElements.define("my-thing", MyThing);
}
# prefix for private class fields and methodsany type completely - never use any in production codeas unknown as T - this pattern indicates poor type designT[] instead of Array<T> - follow consistent array type syntaxType | null instead of {} as Typepages/list.js -> pages/list.ui.test.js).*.ui.test.js.await expect(page.getByRole('heading')).toContainText('New solution'); should be await expect(page.getByRole('heading', { name: "New solution" })).toBeVisible();.waitForTimeout in tests. Playwright has implicit waits built into all locator logic. Instead of arbitrary timeouts, wait for the expected UI state changes (e.g., wait for a specific element to appear, text to change, or button state to update). This makes tests more reliable and faster.This applies to both non-UI client-side code and backend code. This includes unit/functional tests and scenario-based tests.
node:test) for non-ui tests.*.test.js.filename.test.js.",tests-e2e/ folder at the highest common ancestor of the files being tested.import/export (ESM) syntax — no CommonJS.assert, assert.strictEqual, and similar functions from Node's built-in assert module.test() and describe() blocks from node:test.If there is a file sum.js with the following code:
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
export function sum(a, b) {
return a + b;
}
Then we want our test next to in the same directory, named sum.test.js with the following code:
import { test } from "node:test";
import assert from "node:assert";
import { sum } from "./sum.js";
test("sum", () => {
assert.strictEqual(sum(1, 2), 3);
});
__fixtures__ directory.const response_with_no_highlights = {...test_response_from_fixture, highlights: []}SolutionFilter)get_solutions)ListPage)MAX_SOLUTIONS)max_solutions)max_solutions)const and let over var.Only apply these guidelines to projects that are built with Eleventy.
<script webc:setup> and render with <template webc:root><script webc:keep> to tell webc to keep the script tag around after build-timerender method. Ideally try to avoid this by breaking down objects into scalar values and passing them as attributes._(this) function to access the props.<script webc:setup>
// @ts-check
/** @typedef Props
* @prop {string} name The name of the setting from settings.js
*/
/**
* @param {Props} props
*/
function _(props) {
// @ts-ignore - settings is available at build time
const setting = settings[props.name];
if (!setting) {
throw new Error(`Setting "${props.name}" not found in settings.js`);
}
return {
storage_key: setting.storage_key,
label: setting.label,
default_value: setting.default_value,
};
}
</script>
<fw-setting-toggle
:data-storage-key="_(this).storage_key"
:data-default-value="_(this).default_value"
:data-label="_(this).label"
>
<label>
<input
type="checkbox"
:data-storage-key="_(this).storage_key"
:checked="_(this).default_value"
/>
<span id="label" @text="_(this).label"></span>
</label>
</fw-setting-toggle>
npm run format:fix to format the code before making a commit.