The Modulo Tutorial: Part 2
In Part 2 of this tutorial, we'll explore three more features: Customizing
with Props, substituting with Template variables, and finally building your
finished project into a single faster-loading file. Prerequisites: HTML, CSS,
and Part 1.
#
1. Props
In the previous section, we were mostly concerned with defining
components. Recall that components are defined once, but can be used many
times. The purpose of Props is to allow more flexibility in that
second step: Props CPart defines the properties that can be
customized about a component each time it is reused. We already know that
"slots" can be used to customize components with extra content, which is a good
start. However, what if we wanted even more options for users to customize the
components, such as by using HTML properties? Thus, to start our discussion of
Props we'll first get more practice reusing components.
Other people's components
Before we learn how to use the Props CPart itself, let's first get
acquainted with a concept we haven't done before: Reusing components that were
coded by someone else! In a realistic scenario of an organization building a
web app, it's likely that most of the components you will be using won't
actually be written by you personally, but instead by someone else on some sort
of internal "component library" team. Similarly, even if working by yourself,
you'll need to learn how to component libraries written by others so you can
use components already defined for you to accomplish common tasks. Don't
reinvent the wheel!
In this next activity, we'll practice reusing components.
Try it now
Context: The <x-ExampleBtn>
button component has
already been defined for you. It was defined to accept two so-called
"props": label
, and shape
. We'll cover
how it was defined to use a Props CPart later on. For now, we'll
practice only with using the x-ExampleBtn
.
- Examine the code below. Examine the preview on the right. Do you
see how the code in turn uses the
x-ExampleBtn
in two
locations, with a different shape and label each? We can call each of
these a component instance. Each component instance can
receive different or customized props attributes.
- Note the
label="..."
prop attribute that is on each
x-ExampleBtn
. Test it out by editing the contents of this
attribute on one or both of the buttons and click RUN to see the result
on the right.
- Note the
shape="..."
prop that is on each button. It's
impossible to have known this without reading the CSS of
x-ExampleBtn
, but it accepts either "square" or "round".
Try changing one to the other and see the results.
Trying out the button...
Another button...
Comprehension Challenge 1: Based on the patterns you see
here, add a third button to this example that is round and contains the
word "Hello".
Comprehension Challenge 2: See if you can think up what
props might be attached to different types of widgets, e.g. a "modal
pop-up" widget might specify the title, or an Open Street Map widget might
specify latitude and longitude for the map being displayed. What would a
text input need? What about a tabbed navigation interface? How about a
component that shows a chess board with pieces in a specified setup?
Why use Props?
Components are "write once, use everywhere". That is to say, you only need to
define a component once to use it throughout your pages. The advantages of
"write once, use everywhere" are clear: By writing "DRY" (non-repetitive)
code, you can make changes to the single component definition and see those
changes take effect wherever that component might be used.
However, sometimes you want each instance of a component to have different
content or modified behavior. This is where Props come into play: They
allow you to customize differences in content or behavior between multiple uses
of the same component.
Defining props
Let's "peel back the layers" and examine out how this ExampleBtn was
written.
In order for a component to be able to "receive" props when it is reused, we
must define a Props CPart, which is conventionally placed as the first
thing in a component definition. Props CParts are defined somewhat like
previous CParts, except they have no contents and are just an opening tag with
attributes, followed by a closing tag. A Props CPart might look like
this:
<Props name device frequency></Props>
The recommended style for Modulo code is to add a newline after each prop
name for easier reading. HTML syntax doesn't care either way, so the above
might be improved for readability if it were written as follows:
<Props
name
device
frequency
></Props>
Once you have defined which props you expect a component to receive by using
the Props CPart, you can then reference the values of those props
elsewhere within the component: Either in the Template CPart (what
we'll cover next), or in the Script CPart (covered later).
#
2. Templating
Can I use props in CSS?
No. Note that the Style CPart intentionally does not support
template substitution, or any such "live" modification whatsoever. If you want
to directly insert props as CSS values, this should be done in the template by
using an inline style=
attribute. For more complicated CSS
transformations, consider setting CSS variables on the style=
attribute that are then used by the Style CPart, by using the
var()
CSS function.
Our discussion of props gives us a chance to explore the Template
CPart a little bit deeper. Previously, we have only used a Template
CPart to display static, unchanging HTML. That's quite limiting. The typical
purpose of "templating" or "a templating language" is to allow customization,
substitution, and transformation of HTML content. Templating isn't unique to
web development: If you've used template files with word processors such
Microsoft Word, or email templates within marketing or sales software, you'll
know that with templating you can include placeholders where we want to insert
data or values, in order to personalize each instance.
Using props within a template
Within a Template CPart, we can insert data or values using a
special "placeholder" syntax that is as follows:
{{ myVariableName }}
.
We can also use a dot (".") in this syntax to access properties.
Thus, you will see this placeholder syntax more often used like this:
{{ props.device }}
. So, combining this information with the 3
props that we have above, we can create a template that has 3 placeholders that
get filled in with specific data when the component gets reused. Examine the
below code and try to guess at what it might do:
<Template>
Hi {{ props.name }}, your {{ props.device }} is at {{ props.frequency }}hz.
</Template>
In this case, if we were to use our component with the attributes
name="Tux"
, device="Radio"
and
frequency="2600"
, then the following text would be rendered:
Hi Tux, your Radio is at 2600hz.
The "Modulo templating language" was modeled after Django, Jinja2, or
Liquid, and thus is quite powerful, being capable of much than the simple
substitutions we're learning here. We'll learn more features later.
Combining props with template HTML
In the previous example, we showed how text can be rendered using
substitutions. Next let's see how such substitutions can be used to generate
more complex HTML code.
Examine the following component definition. This component definition is the
same definition for the x-ExampleBtn
that was referenced in the
previous challenge (with the exception of some CSS being omitted for
simplicity):
At this point, we can finally begin to understand the
<x-ExampleBtn>
component that we were using previously. In
this case, the value of the label="Example Button"
prop gets
inserted as the text content of the button element, and the
shape="round"
prop value gets inserted in the class=
attribute of the button element. Thus, if the shape prop receives the value of
"round", the button gets the CSS class of .my-btn__round
, and
similarly if it received the value of "square", it'd get the class
.my-btn__square
. These CSS classes, as you may have guessed, are
what controls the look of the button, either giving it rounded edges or a more
rectangular, towering look.
Try it now
Okay, let's take this new concept and apply it to our previous
HelloWorld component. Go back to the project you were working on
previously, and follow these steps to practice adding props to your
component:
- Examine your
HelloWorld
component definition. Near the
top, just below the "<component..." opening tag, add a
Props CPart, just like the examples before.
- Experiment with specifying props. For example, to keep building
this tutorial app, give it the props of
goal
and
amount
.
- If done correctly, your component definition will start something
like this:
- Update the template to now use these props somewhere. For the
tutorial example, we'll be building a little "clicker" that will count
up to a number (the "goal") by an increment amount (the "amount"). So,
for now, let's just display the goal prop and amount prop:
- Refresh the web browser to view the results. Assuming you still
have the component displaying somewhere on your page, if done
correctly, you will see your component update to reflect the changes
you made to your template, possibly something like "Goal: "
and "Amount: ".
- Comprehension question: Why doesn't it show any text after
"Goal:" and "Amount:"?
- Answer: If you are seeing blanks, it's probably because you
are not passing in the Props that are required.
- To fix this, we need to specify the props in the location where we
are reusing or actually displaying the component on the page.
This is done by adding attributes with the names "goal" and "amount" to
the component tag instances.
- If you have made a lot of changes since Part 1 of this
tutorial or did not follow the naming suggestions, your code may end
looking different than this. However, if that's not the case, your code
might end up looking like this:
Bonus Challenge: Read ahead about template
filters, and try using those here.
Template filters
The Modulo templating language has two more big features: filters
(for formatting values), and template-tags (for control-flow). We'll
briefly introduce filters now, but for a more thorough introduction to
the Modulo templating language, you should read
the Templating section.
Template filters format or otherwise transform template variables.
The template filter syntax consists of taking a template variable and adding a
vertical bar followed the name of a filter (e.g.
varName|filterName
). The following example will transform the text
contained in the props.name
template variable to make it all
uppercase:
Hello {{ props.name|upper }}
Some filters can also take extra modifiers or options. This is called the
template filter argument. Do you recall how our previous ExampleBtn
component only supported either "round" or "square" as CSS classes? In this
next example, we are going to use the |allow
template filter to
ensure that only "round" or "square" are permitted:
<button class="my-btn my-btn__{{ props.shape|allow:"round,square" }}">
(... snip ...)
</button>
Note how the argument is separated from the filter with a colon: The general
syntax is varName|filterName:"argument"
. Thus, the
|allow:"round,square"
filter instructs Modulo to only
output the property of props.shape
if it exactly matches the text
"round" or "square".
You can do more than this with filters: You can string together the dozens
of available filters for more powerful combinations, and by using JavaScript
script tags (covered in Part 3) you can easily author your own.
The full Templating Reference has
examples of all filters available.
Template tags
In addition to filters, the Modulo templating language also support powerful
"template tags", which allow for more complicated custom behavior. This
includes the "if" template-tag, which allows for conditional rendering (e.g.
"only show the submit button if a form is filled correctly"), and the "for"
template-tag, which allows for HTML to be repeated for each item of some given
data (e.g. "every blog post get's it's own <li>
element").
The full Templating Reference has
examples of the different template tags available.
#
3. Build
Why build?
Let's start with a "thought experiment", where we fast forward into the
future. Imagine that as you create more and more components, you begin
splitting them up into more component libraries. Also, as mentioned previously,
perhaps you find some nice component libraries that others have made that you
want to use in your project. In the end, you might find yourself with dozens of
HTML files being loaded, scattered in different directories.
In this situation you will have too many <Library>
tags,
meaning your page may take a long time to start-up. As it's downloading all
the different files, the browser will be showing the ugly, unformatted text in
the mean-time. On slow connections, this could be end up being a frustrating
experience for users. This is why building to one file is useful.
We've got one more topic for Part 2 of the Modulo tutorial:
How to keep your project manageable and loading fast as you accumulate more and
more Component definitions, and they require more and more files.
Library
In Part 1, we learned we can use -src=
to split
Template and Style Component Parts into separate HTML and CSS
files respectively. However, what if our component definition files themselves
get too big to manage? That is, what if we define so many components, that we
need to start organizing the components themselves?
This is where the Library definition tag becomes useful. It allows
us to cluster our components into individual Component Libraries, that
then get imported into different Component namespaces.
First, re-examine our original Modulo import:
<script Modulo
src="https://unpkg.com/mdu.js"
-src="/libraries/my-stuff.html"
></script>
To use a Library definition tag, we move the
-src="/libraries/my-stuff.html"
and add a
namespace
to one or more
<Library>
tags in the Modulo script tag. This ends
up looking like the following:
<script Modulo src="https://unpkg.com/mdu.js">
<Library
-src="/libraries/my-stuff.html"
namespace="mylib"
></Library>
</script>
Why use namespaces?
Namespaces allow different component library files to have
conflicting component names. This is especially useful when using
third-party component libraries or while working in a big team:
That way, if both you and another developer define a component with
some common name (eg name="Button"
), there won't be a
conflict as long as you load each into different namespaces.
Let's break this down:
-
-src="./libraries/my-stuff.html"
The -src
attribute specifies the source of the
component library file. This file can be anywhere that is
accessible to your web-browser or web-server. Ideally, it should be
in the same place as your CSS and static media files, such as a
static/
directory, or whatever the equivalent is for
your set-up.
The component library itself (my-stuff.html
in this
example) should consist of an HTML file filled with
<Component>
definitions.
-
namespace="mylib"
The namespace
attribute specifies the
namespace prefix, which is combined with a dash and the
component name in order to create the component's full
name.
Example: If my-stuff.html
has a component
defined like <Component name="MyThing">
imported with namespace="mylib"
, then the
resulting full name would be mylib-MyThing
, and
we'd use the component like
<mylib-MyThing></mylib-MyThing>
.
- Multiple libraries: As we add more and more component
libraries to a project, we can simply add additional Library
definition tags into the Modulo script tag. If
we accumulate too many Library definition tags that it becomes
unmanageable to copy and paste them between HTML files, the solution is
simple: Go back to the same boilerplate you learned in Part 1 of the
tutorial, except point to a new file that in turn contains all your
libraries imported and set up as expected. In other words, the
Modulo -src="/libraries/my-stuff.html"
can either point
to a file containing your components, or a file that contains
Library definition tags that point in turn to files containing
your components.
- Where to put it: The Modulo tag can go anywhere in
your HTML. For neatness, consider putting them either within the
<head>
tag, or near the </body>
closing tag. After being built, it won't matter: The JS script tag will be
put before the </body>
.
However, there is a downside to splitting up your files: Each new file you
create will require an additional request to load it. This means that each
additional individual file you include in a page will cause the page to take a
little longer to loader. So, if -src=
causes it to load slower, how
can we stay neat during development, but still have a fast site? This is where
"building" comes into play.
Building: Packing it all up
"Building" is a feature of Modulo where it packs up all the components,
JavaScript, and CSS you have loaded on a particular page into single
.js
and .css
files, respectively. This single file
file, called a build, contains all the code from all your
components (including CParts that were split off using -src=
).
These "builds" are fully independent: Once the "build" file is included
using a <script src="...">
, all your components on that
page will work, without any need for including Modulo or your libraries specifically.
In other words, this tag replaces all the boilerplate you have on your page,
and you can remove all the importing-type code (e.g.
<script Modulo>
) from your HTML files, replacing it
only with the single script tag. Not only that, it will also pre-compile
JavaScript code, removing the need for "eval", and reducing file sizes. This
causes the components to load much faster.
Generally, developers create builds before "launching" their site to
"production" (e.g. publishing their site for the world to see), since it
results in the fastest possible loading time. Note that you should only attempt
to edit your original, source files: Builds are only for releasing or
publishing, but not for editing. Never directly edit builds; instead, create
new ones.
Pre-rendering HTML for instant loading
modulocli If you truly want to build using terminal or
NPM-based tools, Modulo supports that as well with modulocli
.
However, Modulo is still in alpha stages, and so the browser-focused
workflow is the best documented, tested, and least likely to change with
updates. That said, it's usable: It's what was used to build this website! To
get going, use the create-modulo scaffolding tool, by running the
following: npm init modulo
Even with built JavaScript code, you might notice that there is a
"flicker" effect, where the page remains unstructured and unstyled until all
your components finish rendering their Templates. Depending on how
complicated your page is, this could be brief and forgivable, or could take a
while and create a bad user experience. One option is to add a "Spinner" to
your page, that is removed once things are loaded. Another option is to use
pre-rendered HTML.
Modulo can pre-render HTML as well, and output a brand new HTML file that can
be published instead of your original one. A "pre-rendered HTML" file is a
special, processed HTML file which basically "freezes" the result of loading
the page for the first time. You can use this messy, processed HTML file as a
drop-in replacement to your previous HTML page when launching, as it will
already have JavaScript and CSS tags included for your build!
To build a project, we will need to use the Modulo Command Menu.
The console is a feature of all web browsers. As a web developer you
likely have used it: It's a panel, hidden by default, where JS, CSS, and other
such error messages are displayed. While most JS frameworks require NPM-based
tools to build, Modulo can be built right from your browser's console, simply
clicking on the build
commands in the COMMAND
menu,
that is visible in your browser developer tools console.
Build files will look like: modulo-build-xx4bz9v4.js
,
modulo-build-x9f2za71.css
. Note the so-called "hash" (e.g.
“xx4bz9v4”): These unique IDs identify each JS and CSS file that is
generated by Modulo. If you change something in your components, you will
cause one (or both) of these hashes to change, if they end up affecting the
component's behavior (.js
) or it's appearance
(.css
).
Try it now
- Open up your HTML file in your browser (e.g. Firefox or
Chrome/Chromium). Do not open your component library HTML files: Be
sure to open the file that is doing the importing and
using the components, not the file(s) that contain the
component definitions themselves.
- Bring up the console: Press
Control+Shift+J
(Linux,
Windows) or Command+Option+J
(macOS) on your keyboard to
open the Console. Alternatively, you can right-click with your mouse
and select "Inspect", and then go to the Console tab.
- Optional: Within the newly opened developer tools,
navigate for a moment to the "Network" tab. Force refresh your
browser. You should see a list of "requests" being sent, including one
for each component library that needs loading. For example, if we
named our component library "./libraries/my-component-lib.html", we'll
see an additional request for this HTML file. If we had further split
our files using "-src=", we'll see even more requests.
- Do you see a Modulo logo (
%
), with the word
COMMANDS
? Click on COMMANDS
, and possibly one
more time (on Firefox), and you should see a command menu, containing
commands like build
and test
. To build,
simply click the "build
" command.
Note: Look in the upper-right hand of your browser. You might
see a warning, prompting you about "Allowing multiple downloads".
You should allow it, otherwise it will block the generated files
from being downloaded. If you see no warnings about this, then just
continue to the next step.
- Your browser should reload, and then offer to download an HTML,
CSS, and JS file, or perhaps offer a way to save them. These are the
"build" files, and contain the contents of all libraries that were
loaded on this page, with underlying JavaScript pre-compiled for speed
and security. Save them all together in some spot.
- The HTML "build" is a copy of the HTML page you were working on,
with two important differences:
- Difference #1: It's been modified to include the built versions
of the CSS and JS: A link tag to include the CSS file is inserted
before the
</head>
, and a script tag inserted
before the </body>
tag to include the JS
file.
- Difference #2: It contains the page's current state,
e.g., the result of an initial render. This means that when that
HTML page is loaded, it will be "prerendered", and thus appear to
load much faster, and components will render correctly, even before
the JavaScript finishes loading.
- Now, open up your "build" file. If all went well, it should look
and behave exactly as before, except load much faster.
Tip: If all went well after building, you should see a link
to the command you just ran. Clicking on the link will refresh the
page, causing it to build again. You can also just hit refresh (Ctrl+R
or Command+R) to rebuild. Consider keeping this window open, or as
another tab, and resuming work in another window. Then, to rebuild,
simply switch back to this window, and either click the button or hit
refresh!
Important: Don't edit your builds! Built files are
disposable. Don't edit the HTML, JS, or CSS files produced. Instead,
continue working on your original "source" files, and then build again. The
purpose of these new "built" files is to only share them when you are done
with your website. These should only be used when "launching" your site.
Your development should continue on the "source" files.
This can easily trip up beginners: The build "freezes" the component
library in time, and you'll have to run build again if you change anything.
A common mistake is forgetting that you are editing the original
.html
file while using the built .js
version,
causing much confusion as to why your changes don't take effect!
Integrating builds with existing projects
What can Modulo integrate with? Modulo was developed to be
easy-to-integrate with existing code. In other words, it doesn't matter if you
use WordPress or Django, Drupal or Rails, Jekyll or Hugo, Modulo components can
be embedded into your page to add JavaScript-based interactivity and more
convenient HTML development. Although probably less useful, you could
conceivably even use it with JavaScript-based generators, such as Next.js or
Gatsby—in the end, as long as JS and HTML is getting sent to a browser,
Modulo components can be in the mix!
If you are experienced with other JavaScript build systems, such as those
that are NPM-based (e.g. Webpack, Parcel, Rollup), you might be curious as to
how to practically use Modulo's browser-based build system. Even if you aren't
familiar with other build systems, you might (correctly!) think that in
practice it must get hard to keep track of which JS files, CSS files, and
pre-rendered HTML files should be included in the published version of the
site, and further more that re-building after every change on every single page
on larger sites could get tedious. Also, you might wonder how to integrate this
process with pre-existing web apps, or apply it in a real-life web development
team, as we've thus far only been talking about developing Modulo components in
isolation. This is where workflow comes into play, i.e. the patterns
and procedures you follow to keep a project humming along.
One useful workflow for integrating Modulo with existing projects is to have
a sort of sandbox "testing page" or hidden internal-only "showcase" HTML page
(i.e. similar to a "story book" or "design guide" of components). On this page,
you develop and showcase your re-usable components, both to ease development,
and also to serve as a demonstration for other members of your development team
or organization.
From this "showcase" page, you can then run build
to generate
built JS files and CSS files whenever you have completed sufficient work on
your components that you want to integrate and release into the rest of the
project. The "self-packing" nature of these builds make the integration no
harder than adding any other script or link tag. This works especially well for
small projects, and avoids the need for a complicated automated build process
for component libraries that you might only occasionally change. One downside
of this workflow is that you might not be able to pre-render any of the pages
you are integrating with, so there might be slight delays in mounting your
components.
For example, see below for a complete page showing including a bundle in a
hypothetical existing PHP project:
Part 2: Summary
In this tutorial, we learned how to specify props as attributes to use and
configure other people's components and how to define the Props CPart
which allows our components to be configured via attributes when used. We
learned how to use Modulo's templating language to include variables and format
values using filters, and finally wrapped up with trying Modulo's self-building
feature.
Key terms
- Props - A CPart used to specify attributes, that the user
of a component can use to configure that Component
- Template variable and template filter -
Modulo's Templating language can include template variables (e.g.
{{
props.name }}
) mixed in with the HTML, and possibly modified or
reformatted by attaching template filters (e.g. {{ props.name|upper
}}
)
- Library - Allows grouping of Components into library files
that can be "siloed" under a certain component namespace or configuration.
- namespace - The prefix that replaces the default "x" and
goes before the name of a component to form the complete component name, e.g.
namespace
maps
joins with
<Component name="India">
to form a
<maps-India>
component.
- build - Combining all your JS and CSS together into a
single file that loads faster, and pre-render the HTML so there's no
flicker.
- hash - These unique IDs identify each JS and CSS file that
is generated by Modulo.
Next step
At this point, you've learned enough to be dangerous! If you intend to
mostly use Modulo as a template developer, refactoring HTML into template-based
web components, then you are all ready to get going. Feel free to try using
Modulo to refactor your HTML, and be sure to test on all target browsers
(Modulo is still under development, after all), and then finally use the
self-packing feature to include HTML/CSS/JS that loads quickly. To improve your
template development skills, you might want to skip learning about Script
CParts (Part 3), and instead read deeper on the capabilities of the Modulo templating language, which you can use
to build more complicated re-usable HTML.
However, if you are at least a little familiar with JavaScript and/or
React-style SPA frameworks, and want to learn how to make more complex,
interactive applications using State and Script, then get going with Part 3: State, Directives, and
Scripting
Part 3: State, Directives, and Scripting »