Tips for writting and loading Javascript
Because we live in the era of frameworks like Vue.js, Next.js, Svelte, Solid, and Qwik, we are accustomed to writing and splitting our code in a completely different way than with client-side-only JavaScript frameworks (like jQuery) or vanilla JS.
However, if you don’t use a component-based framework that splits assets automagically, you will probably need to find your own way of loading and splitting code.
The tips mentioned in this section are focused on helping you write maintainable JavaScript in the “old school way.” They are universal, so you can use them even if you don’t use Signalize.
Selectors
Selectors we add to elements should describe what they are used for.
Let’s start with prefixes. Prefixes are good for separating “logical and state selectors” from “styling only” or “descriptive” selectors:
js-
: Use this prefix for selectors that will be used within JavaScript =>js-toggle-button
. These selectors should be only in templates and JavaScript files.s-
: Use this prefix for setting some state of an element, e.g.,s-active
,s-toggled
. These classes can be styled within CSS and can be set by JavaScript.
To improve searching for selectors in the project and to instantly see which classes belong together, it’s better to keep the naming in “sequence”:
- Let’s say we have a
js-tabs
component. - Our class for a single tab will be
js-tabs-tab
. - The class for the toggle button will be
js-tabs-tab-toggle
notjs-toggle-tab
. - Now, if we search for
js-tabs
we instantly see which selectors work together in which files.
Directives such as data-
can be used to “keep a state” directly on a DOM element and at the same time select an element:
data-js-
: can be used to define, for example,data-js-tab="first"
,data-js-tab-toggle="first"
. It’s easy to understand what these directives do. Thanks to the js prefix, we know it’s going to be used by JavaScript and that the button toggles a tab with a given ID.data-s
: this directive can be used to maintain a state on an element, such asdata-s-tab-toggled="false"
. This directive can also be used within CSS for styling toggled or hidden elements.
Splitting Code
Splitting assets is always a challenge.
There are various ways to load JS and CSS. The following tips work for me, so I have decided to share them:
- Layout CSS and JS - Magic that is used anywhere.
- Page CSS and JS - Only for one page.
- Lazyloaded chunks after an event - dialogs, dropdowns, cart. Use Signalize
resolve
function, native import or embed the path to the script into the head of your page.
Individual logic should be split into files. Do not place all logic into one file. You can use one of the following tips to split the code:
Directory Structure 1 - Keep CSS and JS in separate directories:
- assets
- js
- components
- layout navigation.js
- dropdown.js
- layout.js
- index.js
- blog.js
- components
- css
- …
- js
Directory Structure 2 - Keep CSS and JS together:
- assets
- ui
- components
- layout
- navigation.js
- navigation.css
- dropdown.js
- dropdown.css
- layout
- layout.js
- layout.css
- index.js
- index.css
- components
- ui
Use ES modules
ES modules are supported in most modern browsers and they have a lot of features:
- They are imported and executed only once.
- Dependencies are resolved automatically. No need for bundling.
- With import maps, you can easily orchestrate your import paths. JavaScript frameworks like Astro don’t even need import maps in most cases.
- It’s easy to split functionality into various ES modules and import them only when necessary using dynamic imports.
- Etc…
<script type="module">
import '/path/to/script.js';
</script>
Loading Code
The “basic goal” to write optimized JavaScript is to delay its loading and execution until it’s really necessary and load only what is necessary.
For example, if you have multiple carousels on your page, you might want to load the script for them after a click on a button that will trigger the slide effect to the left/right. When the script is loaded, it should only initialize those carousels that are currently in the viewport by checking visibility using intersection observer. This can be applied to any other scripts and logic => dialogs, content loaded within dropdowns and filters, etc.
Harry Roberts had a nice talk Get Your Head Straight (slides are here) where he talked about the order of elements within the head tag and how to load scripts and styles. To summarize, JavaScript and CSS should be split into three locations:
Head start CSS
: Right after the title element. For example, for Critical CSS, Layout, and Page CSS.Body after priority content JS
: For example, before the footer or after the last product list. Any high-priority JS, like navigation, filter, or analytics (e.g., GTM and FB).Body end
: Low-priority JS. Those that the user will probably not use immediately after the page loads, like dialogs, dropdowns, and JS for content in toggleable sections.
<html>
<head>
<!-- Set charset -->
<meta charset="utf-8" />
<!-- Set viewport to prevent loading of unnecessary assets -->
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<!-- Base64 favicon, so the user sees, that something is loading -->
<link rel="icon" href="data:image/png:base64,.." sizes="any" type="image/png">
<title>...</title>
<!-- CSS -->
<!-- Custom head content -->
</head>
<body>
<!-- Page content -->
<!-- Js after priority content -->
<!-- Other content -->
<!-- Low priority JS at the body end -->
</body>
</html>
Use the Web Platform
Modern browsers provide a lot of native functionality that you might want to check and consider before creating your own solution:
- Dialog Element - Good for dialogs and all kinds of notifications.
- Details Element - Dropdowns or toggleable navigations.
- Popover API - Side menu, mobile menu, or additional info messages.
- CSS Toggles - Use CSS to toggle things instead of JavaScript.
- CSS Scroll Snap - Use CSS to create carousels, galleries, etc.
- Form Validation - Native form validation attributes.
- Lazyload Attribute - Native lazy loading instead of custom JS.
- View Transitions Meta Tag - This one meta tag (I hope) will be able to deprecate any “SPA library” one day.
- Cross Document View Transition - Probably somewhere in the future an alternative to complex Single Page Applications.
YAGNI & KISS principles
You Aren’t Gonna Need It + Keep It Simple, Stupid.
Think about what your clients/customers want and if the script you write is really necessary. A few examples:
- Does the form need to be reactive, or can it be validated only before it is sent?
- Do I need a super cool carousel with ninja animations, or am I fine with native scroll snap?
- Is there any need for an “SPA” besides it just “looks cool”? Does any customer need that?