Scripting
Loading "Intro to Scripting"
Run locally for transcripts
The web
The browser is pretty capable of doing many things necessary to build web
applications, but there are two significant limitations most web applications
face:
- Navigations (including form submissions) trigger full-page reloads.
- Pending UI is limited and not customizable.
Full-page refreshes are just not acceptable in the modern age. Can you imagine if
"liking" a tweet triggered a full page reload? I know I would like fewer tweets,
that's for sure.
Pending UI in the browser really only amounts to the favicon turning into a
spinner, and in some cases, a mobile browser will have a progress bar.
Completely non-customizable and of limited use.
On top of that, the collection of built-in HTML elements is not sufficient for
most applications. Furthermore, many of those elements are limited in their
customizability (particularly with styling).
To give our users the experience they expect, we can't simply rely on HTML and
CSS to do the job. Even if you're building a simple web application, you'll be
forced to include some client-side JavaScript to ensure it is accessible (look
forward to the Accessibility exercise for more on that).
Additionally, we can use JavaScript to help build faster applications by
anticipating the user's next action and preloading the necessary code and data.
By using JavaScript to avoid a full page reload we can also reduce the amount of
content needed to be sent over the network (particularly when considering an
application the user uses for an extended period of time).
The first version of JavaScript was standardized at the end of 1995, three
months after HTML was standardized and before HTTP and CSS were standardized.
The original standardized version of JavaScript was quite limited compared to
what we can do now, but it did allow for some basic interactivity. It was also
limited by the
π DOM API
which has come a long way since then as well.
Modern-day JavaScript and DOM APIs are powerful enough to make up for
shortcomings in what's available via HTML and CSS alone (though there are
exciting advancements in those areas as well). In general, if you can do it in
HTML and CSS, you should. If you can't, JavaScript is a great way to fill in the
gaps.
To get JavaScript running in your application, you use a
script
tag. With
this, you have a few options:<!-- Basic scripts -->
<script src="path/to/script.js"></script>
<script>
// JavaScript code goes here
</script>
<!-- Modern Modules -->
<script type="module">
// JavaScript code goes here
import { foo } from 'path/to/module.js'
</script>
<script type="module" src="path/to/module.js"></script>
There are important differences between the different types of scripts which we
won't dive into too much today. The
type="module"
scripts are a relatively new
addition to the web platform and are the most powerful. They allow you to use
the
π ES Module
syntax to import and export code. This is the syntax that is used in modern
frameworks and is the recommended way to write JavaScript for the web.There are various other attributes you can add to the
<script>
tag to
customize how it is loaded and executed. For example, you can add a defer
attribute to tell the browser to load the script in the background and execute
it after the page has loaded (this is actually the default for type="module"
).
You can also add a async
attribute to effectively tell the browser to load the
script in the background and execute it as soon as it's ready. You can also add
a crossorigin
attribute to specify how the browser should handle cross-origin
requests. For more on these and other attributes, check out the
π MDN docs.One last thing that's important to call out is that in order to solve the "full
page reload" problem, we have to take a lot of the browser's responsibilities
upon ourselves. We end up using
event.preventDefault()
on <a>
clicks and
<form>
submissions. Unfortunately, the browser does a lot of work for us
that we have to re-implement ourselves when we do this. It's almost like it
would be useful to have a framework to handle this stuff for us...In Remix
When you generate a new Remix project, it comes set up with scripts already. The
entry.client.tsx
file is the entry point for the client-side code. Typically,
this will be the file responsible for
hydrating the application.
For example:import { RemixBrowser } from '@remix-run/react'
import { startTransition } from 'react'
import { hydrateRoot } from 'react-dom/client'
startTransition(() => {
hydrateRoot(document, <RemixBrowser />)
})
The
startTransition
bit ensures that the hydration is done in a way that
doesn't block the main thread. This is important because hydration can take a
while on lower end devices, and we don't want to block the user from interacting
with the page while it's happening. For example, if the HTML loads and the user
starts scrolling the page, the scroll can be interrupted by hydration, leading
to a "janky" experience. By hydrating our root in a transition, we can ensure
that the user can interact with the page while hydration is happening without
being interrupted.Another thing to call out is the fact that we're hydrating the entire
document
rather than just an element in the body
as is common in other frameworks. By
doing this, we have much more control over everything between <html>
and
</html>
which can be very useful.But this is not all that's required to get the client-side code of a Remix app
running. Remember that you're responsible for everything between
<html>
and
</html>
in your app/root.tsx
component. So if you're not rendering any
<script>
elements, then no JavaScript will be loaded. The trick is that
because Remix is building (and fingerprinting) our JavaScript, we don't actually
know what the src
attribute of our <script>
should be. This is why Remix
provides a Scripts
component that you can use to render the necessary
<script>
elements. For example:import { Links, Meta, Scripts } from '@remix-run/react'
// ...
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
{/* ... */}
<Scripts />
</body>
</html>
)
}
// ...
Of course, you can always place a JavaScript file in the
public
directory and
reference it directly in the src
attribute of a <script>
in your root.tsx
(or any other) component. Just keep in mind that those would not be processed by
Remix, so you'll want to make sure they execute in every browser version you
support.Additionally, you can always render an inline
<script>
element if you like.
The same rules apply (they're not processed), but in React, you also have to use
the dangerouslySetInnerHTML
prop to render the contents of the script. For
example:<script
type="module"
dangerouslySetInnerHTML={{
__html: `
// JavaScript code goes here
`,
}}
/>
In general, inline scripts can be tricky to maintain (due to a lack of tooling),
but very useful in some situations.
While it's not recommended to use inline scripts for a lot of code, if you
need to do it, check out the
es6-string-javascript
extension for vscode to get syntax highlighting in that string.