An Epic Journey About Being a WordPress Services Reseller Using WPMU DEV

A story of one WordPress developer’s experience – from when a client arrives, to working on web development, billing — and beyond.

This article will take you through the entire journey, from when a client first contacts you to the final result, with a quick demo of getting their site set up simply and easily, through to configurations, hosting, billing, and more. Watch as our web developer, Bob, helps Stephanie’s carwash website come alive, with all essentials included.

Image of Bob and a carwash.
You’ll be getting to know Bob well in this article.

See how your journey compares, and whether you’re taking advantage of all of the tools at your disposal as a WordPress services reseller, here at WPMU DEV.

We’ll be showing how Bob:

Of course, every client, site, and situation will be different. This is a very streamlined example with Bob, where we’ll assume all goes well without back-and-forth client feedback, tweaks, and more. For the sake of a brief presentation, we’ll keep Bob’s demo fairly simple and cover the basics.

To join in on the fun, if you’re not a WPMU DEV member, give us a try for free. You’ll be able to follow along with Bob and check out hosting reselling for yourself!

The Journey Begins…

Meet Bob.

Image of Bob.
“Hi!”

Bob runs a WordPress development agency. Just recently, he added hosting reselling to his services with the help of WPMU DEV.

One afternoon, as Bob was working on some business online, he heard a “ding” and noticed an email had arrived from Stephanie. Stephanie just opened up a new carwash, called That’s Awash Carwash, and she wants a new website.

Bob scrolls through the brief Stephanie provided. Some of the vital details include:

  • Hosting
  • Visuals (supplied by the client)
  • Checkout Form for Monthly Carwash Membership
  • Monthly Reports and Maintenance

Perfect. Knowing what the client wants, Bob puts together a quote and sends it to Stephanie. A day later, another email comes in. The quote’s been approved, and Stephanie is eager to have Bob create the That’s Awash Carwash website!

Now, it’s time for Bob to roll up his sleeves and get to work.

Get Started With Client Billing

Bob gets started by adding Stephanie as a client in Client Billing. Client Billing is a full-scale automated, and free billing system for WPMU DEV members and clients. It takes just a few moments to get her added (see how easy it is here).

Once she’s in the system, he sets up an invoice to collect an initial payment for his services.

Obviously, every designer has their own payment methods. Bob decided to charge $1200 for the full development of this site. He wants a payment of $800 to get the project started, and the other $400 once completed.

Then, when it’s all set up, he’ll be using Client Billing to put Stephanie on a website maintenance/hosting program that will bill her automatically each month (which we’ll get into later).

To get Stephanie her bill, he does it all through The Hub under Client Billing. It can be accessed directly from a site, The Hub overview, or the Client Billing tab. Then, Bob just clicks Bill Client.

Where to send a bill from in Client Billing.
Bob can send a bill in a few clicks from here.

When it comes to creating the invoice, it’s completely white-labeled, so Bob adds his own branding, information, and also a little memo about what this bill is for. His branding is saved for future use with other clients, too.

This has all the essentials, like cost, description, and that it’s due.

When he’s all set, it’s sent off to Stephanie’s inbox.

Not too long after, Bob gets an email notification that Stephanie has paid the bill (boy, that was quick!). She did it all online because he enabled her access to her bill, which makes it convenient and easy to pay and get paid.

This paid bill is also reflected in Client Billing where it’s highlighted green and marked PAID.

An image of the paid invoice.
The paid invoice reflects the date, amount, and more detailed information about the payment.

For further detailed information on how to set up invoices, add clients, and more, check out this article.

With money in the bank, he’s ready to get into the nitty-gritty of creating the site.

Easily Set Up a Site From a Premade Template

As a WPMU DEV member on the Freelancer or Agency plan, Bob has instant one-click access where he can easily set up and configure dedicated, fully optimized, white-label managed WordPress hosting accounts for all of his clients (with domain name registrations coming soon) — all from his own Hub!

So, with Stephanie’s WordPress site creation, Bob determines what type of design and site would be best for a carwash business after doing a little research.

He then considers his options. Bob can create one from scratch, clone an existing site, or use a premade template. All of this is done from The Hub.

After careful consideration, he browses through WPMU DEV’s Premade Templates (which can be found under My Sites>Site Template>WPMU DEV Templates), and decides the Business page builder is the way to go.

Where you get the business template.
This page builder is sleek and perfect for business.

It’s all set up, and he can implement some of the images the client provided quickly and easily. Plus, its layout is straightforward and perfect for Stephanie’s carwash business. Score!

So now Bob needs to create a new hosted site and start building from the foundation that this page builder provides.

He clicks on the plus sign to Create a Site and picks the Clone option to implement the business layout.

Where you clone a site.
The Clone option makes it simple as ever to get a new site started.

Going through the process will create a temporary site URL. To see how to set up a cloned site in detail, be sure to check out this article.

At this point, Bob can set up a staging environment if he’d like or use the temp URL. Setting up a staging environment is beneficial if there’s no previous site or domain.

Considering he’s using a premade template, he’ll knock this one out rather quickly, so he is opting for just the temp URL and doesn’t feel a need for staging.

Add In Client’s Branding

Bob’s going to go ahead and replace the premade images in the template with the clients’, along with adding necessary information. It doesn’t take long before the new site starts to come together!

Here’s a look at the homepage…

Image of That's Awash Carwash homepage.
I want to visit That’s Awash Carwash already!

Once he has all of this implemented, he’s ready to tweak and use some of the Pro plugins that come included with every WPMU DEV membership.

Implement Plugins From The Hub

When you create a new site with a premade template, all necessary plugins are already installed and activated for you. There’s nothing else that Bob needs to do to get the essentials.

He can view all of them from The Hub in the Plugins area.

The preinstalled plugins.
All of these plugins are already installed and activated.

Of course, there will be some adjustments to be made here. Also, all of the other Pro plugins can be activated for free at any time from The Hub (e.g. Branda, Hustle, etc.).

For now, Bob has decided to create a Forminator subscription form for carwashes. That’s easy to do with the Forminator Stripe Subscription Add-On.

Where you set up a Stripe subscription.
It’s easy as ever to get subscriptions set up and implemented.

It doesn’t take much effort for Bob to get this feature implemented so that customers can subscribe to Stephanie’s monthly carwash service.

He includes fields for customers’ name, address, phone number, and more. Plus, a customer can subscribe to the monthly subscription service by entering their credit card information.

Subscription form from Forminator.
A subscription form is a cinch to create.

If Stephanie wants any changes, Bob can always implement coupons & discounts, e-signatures, and more with Forminator.

Learn more about how easy it is to include Forminator subscriptions in this article.

Other Plugins…

Bob also connects Beehive Pro with Google for analytics, implements Defender Pro’s security recommendations, and runs a quick setup for SEO with SmartCrawl Pro.

Also, he can start setting up emails for That’s Awash Carwash (each account hosted with WPMU DEV gets ten free email accounts – and more are available). Find out how to create email accounts and learn more about it here.

Just a Short Time Later…

Before he knew it, Bob had the web design in a good place. “I think she’ll like it,” he says, as he sips his coffee.

He sent the temp URL to his client for feedback.

Stephanie gets back with a thumbs-up. That’s always a good sign.

Move a Site From a Temp URL to Domain

Bob goes ahead and moves the site from a temp URL to its actual domain.

All of this is done from the site>Hosting>Domains. It’s just a matter of adding a domain, updating the DNS, and a few more steps to make your custom domain primary.

Where you add DNS to make your site live.
It’s easy as ever to get a temp domain to your custom domain – and make it live!

When a site is ready to go live, Bob is in business. It’s easy to do thanks to this checklist in WPMU DEV’s documentation. Plus, if he has any difficulties, he can always chat with support 24/7.

He gives Stephanie all of her company’s new email account information and a link to the live URL, so she can start marketing it immediately.

Nicely Done!

He’s pretty happy about how quick and easy the process of setting up a carwash website was. It made him want to visit Stephanie’s carwash himself and wash his car, so after finishing his coffee, he did.

Car wash image.
Bob is eagerly awaiting his clean car.

Now that all is up and running, it’s time to…

Set Up Subscription-Based Billing

To make things easy for his client, he’s going to put her on a subscription-based billing system. This will cover her monthly hosting, service, and maintenance.

Like the initial invoice, all of these features can be run from Client Billing.

The billing screen.
This overview of Bob’s client, Stephanie, shows what’s been paid, her contact information, and a lot of other detailed information.

Bob’s charging $75 per month for site maintenance and $15 a month for hosting. This gives him a monthly recurring revenue (MRR) of $90.

He has all of these set up as subscriptions, so it’s automatically billed every month.

The subscriptions in client billing.
The two subscriptions that Stephanie has for That’s Awash Carwash.

To set up subscriptions in Client Billing, all Bob had to do was set up a new service and apply it to Stephanie. Stephanie will get the option to pay her bill in her client portal, or she can mail a check, and Bob can manually mark it as paid once it’s received.

Bob also sends her the final balance of $400 for the completion of her site development.

If he had any issues with Stephanie paying (she seems cool, so I doubt it), one of the steps Bob can take is password protection, so the client can’t log in until paid. Also, he’ll get notifications of any past due invoices, if they were to occur.

To learn how Bob set up subscriptions, produced invoices, and more, check out our article on how to become a hosting reseller.

Now that Bob’s getting paid, the service is all set up, and all looks good, it’s essential for him to…

Monitor That’s Awash Carwash and Reporting

As mentioned, one of the services Bob is doing for his client is site maintenance, which includes monitoring the site to ensure that it’s always up and running.

Luckily, in The Hub, that’s as simple as ever with The Hub’s Overview. From here, he can monitor security, uptime, check for updates, clear cache, view storage, and tons more.

The Hub overview.
The Overview has Bob covered with detailed information about Stephanie’s website.

He can even get a glimpse of his income from Stephanie from the Client Billing area, which makes for a nice shortcut and glance of what’s come in (instead of going to the actual Client Billing section).

Client billing area in The Hub.
As you can see, his MRR from Stephanie is $90.

Plus, Bob can schedule white labeled reports to send to Stephanie about analytics, security, performance, and more – giving her peace of mind that all is well with her site. Stephanie requested this in the brief, so it’s great that it’s so easy to implement.

Example of a white labeled report.
Bob putting together a white-labeled report for That’s Awash Carwash.

Once finished, a copy of the report will be emailed to Stephanie, where she can review all of the details and sleep well knowing that her site is maintained.

Job Well Done!

Bob can pat himself on the back. The site is great, it’s maintained well, and he has a great client relationship with Stephanie. Her carwash business is booming and a ton of sales for her carwash subscription service are coming from her website.

Bob did so great, he noticed a 5-star review on Yelp! from Stephanie!

Image of Bob with a 5-star review.
Bob’s pretty happy.

Over time, his good reviews started piling up, and Bob’s WordPress business took-off. He was easily able to manage hundreds of clients from The Hub, and all of his site management tools (plugins, reports, etc.) were at his disposal (for free!).

Sell Your WordPress Services Under One Roof

So, how do your WordPress agency or services compare when it comes to getting from A (a client contacts you) to Z (site management and billing)?

Hopefully, you saw how efficiently Bob’s journey all came together to create an awesome WordPress site for Stephanie’s carwash that got him a 5-star review.

To recap, here’s what he did to run a profitable “one-stop” WordPress services business:

  • Hosting: He uses WPMU DEV’s fully dedicated, white-labeled, fast, and supported hosting accounts for clients.
  • Client Billing: WPMU DEV members have free access to white-labeled billing that creates invoices, subscription payments, allows clients to pay online, and more, which Bob used for all of his financial management.
  • Site Creation Tools: He used templates, cloning, configs, and more for creating WordPress sites quickly and easily.
  • Support: When he needs help, he has 24/7 expert support for any WordPress issues.
  • Talent and Skillset: This is where Bob shined, by offering professional WordPress services!

We realize this was a basic example and that being a website developer does take time, knowledge, and dedication to nail any web design. However, with all that’s included with a WPMU DEV membership, the process is simplified.

Want to be like Bob?

Possibly, in this example, you picked up on some features that you may have not been aware of, or aren’t implementing. Or, maybe you totally relate to Bob and use the same WordPress management methods that this story covered!

Either way, just know that we’re constantly improving. There’s much more ahead for client billing, The Hub, our plugins, and more — to help keep your WordPress business growing more efficiently than ever.

And as mentioned at the beginning of this article, give us a try for free if you’re not a member yet. We not only provide the building blocks (hosting, website, plugins, etc.), we also give you all the tools you need to run your WordPress business (Client Billing, 24/7 support, reports, etc.).

You too can have a shiny (like a washed car shiny) new hosting reselling and complete WordPress business under one roof in no time.

8 Reasons Why NOT To Develop WordPress Locally

Yep, we’re going there. While we haven’t completely abandoned local development, we believe that in today’s current WordPress environment, online staging is the way to go.

Local development in WordPress is really good―in theory. While you could spout a list of its pros, they could easily be flipped to cons.

Up for a good debate? Taking one point at a time, we’ll explain why we think online hosting is the better option in developing environments.

Continue reading, or jump ahead using these links:

Ok, let’s look into the particulars.

A Coat of Primer

First, we’ll cover a few definitions, as they can be used differently, and we want to unify context.

Localhost is used by most people when they’re referring to their PC. But all tech with access to the internet has a localhost―from smart refrigerators to hosted servers. For more clarity, we’ll use online versus local.

Staging is a copy of your website where you do edits/test/changes and the like.

Production is your live website.

Both staging and production can be online or local.

We could wrangle over the meaning of these words, but kindly consider them as outlined above, at least as it pertains to this article. 😏

Now on to the heart of the matter.

Deconstructing the Environment

You can get started with your own environment in WordPress using one of two approaches: local development, or remote hosted development (online).

Local or online dev
Local development (your PC) versus online (remote hosted server).

Because we are taking the stance of pro online development in this article, we’ll posit the following points that express developing locally isn’t the best way to go.

1. Dinghy vs Cruiser

It’s much more likely for you to experience issues on your own PC, as opposed to an online, hosted server. So with local development, there’s a greater risk of losing progress made during any given session, or even the full lot of your work.

In online development, the environment can be handled by industry professionals (a reliable host), leaving you to focus on the work itself.

2. Resource Rift

Your own PC rarely equals that of an online server, meaning the same code will run very differently in each environment.

Since your local system might give unlimited access to resources, the site and code will process much faster and with greater freedom (i.e. not hitting any kinds of limits). Not so in an online server, especially with lower resources. Imagine a 64 gig personal computer vs a 1 gig hosting plan.

With online development, staging is pretty much exactly the same as production environments, in terms of specs. That means you can properly test your code, and know with relative certainty it will act the same in both. There is no confusion for you in regard to what works and what doesn’t.

To be more specific, you might have 10 minutes of execution locally, while a server might have 300s PHP execution (e.g. 5 minutes of a code running). If it doesn’t finish it’s going to error out. Hence the same code would run properly locally, but won’t work on a production server.

This may sound counter to the argument, pointing out that local resources far surpass those of online servers, but in this case it’s not about more voluminous specs. It’s vital that in staging (development), you always have equal or lesser specifications than production. In this way, you can test your code/site/etc., and know that if it handles well with smaller resources (e.g. a 1 GB server), it won’t have issues with larger resources (e.g. a 64 GB PC). The same can’t be said of the reverse.

3. (Not Han) Solo Setup

On local, you have to set everything up yourself, which can become a tangled mess pretty quickly, even with 1-click apps. Unless you’re an advanced dev/techie, you aren’t likely to find easy solutions, and are likely to spend a lot of time on trial and error.

4. Needles in a Haystack… Or More Accurately… Code in a Dev Stack

It’s easier to just edit a WP site on a staging environment that is pre-set to work with your server, than do it locally and have to try to replace your database back and forth manually between local -> online.

Consider the following… You create a new post on your site, and attach 2 images to it. This means multiple files (because WP generates thumbnails from the images as well), and multiple database entries in various tables.

You have to know what you are doing to get those changes from your local site to an online one, much like a migration. You either replace the whole site from the ground up, or you have to pinpoint the necessary changes behind the scenes and move those over. It’s usually easier to just create the post online again, than trying to navigate those changes. Why double your efforts?

5. Theme Threats & Plugin Problems

The same goes for themes and plugins. Why not just make changes in an online environment, and when it works, sync from staging to production within a matter of seconds? Avoid needing to upload all those things and do all the configuration from scratch. Sidestep the likelihood of forgetting something in the re-setup.

You can’t completely validate in your local environment anyway. Even for simple theme changes, you won’t be able to run a GTMetrix scan without first pushing it somewhere online, then running the tests. Again, this begs the question, why not do it in an online staging environment straight out of the gate, and remove the extra step?

6. Alternate Access & Redirect Rules

As stated previously, a local setup can be very different from a hosted, online one.

For example: AMP stacks use an Apache server, while other hosts/servers use Nginx, LiteSpeed, etc. These use different redirect rules via the .htaccess file. So any plugins set to use Apache locally, won’t work properly when you push that site to a server with Nginx, (or LightSpeed, etc). In this case, they’d all have to be re-setup.

For this reason alone it’s preferable to develop online. If you have a staging option which is essentially built on the same (or equal) system, it will simply work in production, since it’s 100% compatible. You know exactly how your site/plugins/themes etc. are going to behave.

7. (Not Harry) Potter-ing Past

For some folks, developing locally is a leftover remnant from an era of slow-as-molasses dial-ups. These were unstable and costly, which made it easier to set up a site locally and push everything online in one go. With today’s vastly superior connectivity options, this is no longer the case.

8. Epic Ecosystem

Big, heavy projects can involve all kinds of development. They are rarely local, almost always on a 100% similar copied staging server that includes Git and other development tools―which are much more complicated if you’re not fully versed in them.

Paired Platforms

There’s another route you could choose. That is, using a hosting provider-associated platform for development, like DevKinsta (>> Kinsta), or Local (>> Flywheel or WP Engine).

These offer great ease of use (no intimate coding knowledge required), and run on your PC, with online and localhost environments to match your preferences.

Local and DevKinsta are free to use. However, you will incur costs if you use their hosting when you eventually deploy your site. If you opt out of paying for their services in lieu of another company, you’re likely to run into those compatibility issues we discussed earlier, when you’re ready to push to production. If you are interested in using Flywheel, this is a helpful article we wrote about it.

Instead, you could select a hosting company to begin with that offers a simple staging-to-live online solution. For example, WPMU DEV offers the convenience and ease of a hosted staging platform on our servers, so you can work out all the kinks, then go live with one-click sync.

wpmudev 1-click sync staging to production
Pick, click – slick! (Featured in WPMU DEV’s hosting options.)

(AMP)le Coverage

If you’ve read through the full article, thanks for hearing us out! Hopefully we’ve presented a clear, compelling case for why we prefer online (over local) development, while still respecting those who might choose the latter.

We recognize there are decent resources available for developing locally in WordPress. You’ve got your free AMP (Apache-MySQL-PHP) stacks, such as XAMPP, MAMP, & WAMP, which simulate what managed WordPress hosts would provide for you on their web servers.

WP AMP stack
AMP stacks for developing locally in WordPress.

While these are devised to work with a selection of other software, tools, & operating systems, they also entail installing, configuring, and updating them yourself. This is a time-consuming, on-going task, made that much greater should you be unfamiliar with them.

If you’re still of the mind to go the local route, we have quite a few helpful articles on our blog with valuable information on the subject:

Chances are you have enough to do building and managing your sites, without the added hassle of fixing the unexpected outcomes that tend to come with an unassisted move from local to online.

If your website is revenue producing (for you personally, or your clients), you’re probably going to go for a quality hosting service anyway. It makes sense to use one to begin with that incorporates an all-in-one solution, with a smooth, clean sync for staging to production.

Website development can be a joy or a hardship. In the end, you should opt for the environment that best suits your needs and skill level, and syncs easily on a reliable server.

The HeroPress Network Launches Find It WP, a Cooperative Resource Archive for WordPress

Screenshot of the Find It WP homepage, which lists the most recent WordPress-related resources in a four-column grid.

Two weeks ago, Cate and Topher DeRosia launched the HeroPress Network, a centralized website that would host various projects for helping people with WordPress. Today, the duo announced the opening of Find It WP, a new site that is part of the collective. It will serve as a database or archive of all things WordPress.

The goal is ambitious, and it is powered by submissions from the community. The site is open to anyone who wants to share a WordPress-related resource. “Resources” in this sense is literally anything. It can be a plugin, agency, podcast, theme shop, or even more. And, if there is not a category available, submitters can recommend a new one.

“This new way to search WordPress resources helps both veteran and beginner users by saving them time and frustration as they try to find what they need,” wrote Cate DeRosia in the announcement post. “Often, I’ve seen someone tweet out that they’re looking for a WooCommerce specialist or have a friend ask me if I know of a plugin that does x. Now everyone has a new place to start: Find It WP.”

All of the submissions are manually approved to make sure the content is appropriate. This will add to the team’s workload. DeRosia jokingly said her role has become “Chief Approval Officer.”

Clip of the Find It WP resource submission form where users can enter information and upload an image.
Partial view of the resource submission form.

“And this really highlights the reason we’ll be reaching out for funding,” she said. “There’s a lot to maintain with the new additions to the HeroPress Network, resources that seem like they’ll be a real asset to the growth of the WordPress community. Instead of each company trying to do this on their own, let’s pool our resources and support it together.”

The current plan is to open some funding options for the HeroPress Network on October 24. DeRosia said in an earlier announcement that this would be similar to Patreon, where users could fund the mission. However, they will also have a corporate sponsorship program for WordPress businesses.

“I think of it like the local library,” she said. “Not everyone needs to be in there checking out books and shelving things, but somebody does, and they have to buy groceries, too.”

DeRosia believes this will benefit individuals as well as smaller and larger agencies.

“You never know where a customer is going to come from or what up and coming agency could give you unexpected competition, so this is a simple way to see what all is going on in the WordPress ecosystem,” she said. “It’s also an ideal opportunity to find smaller products and business larger companies may want to acquire.”

I asked her whether including plugins and themes was worth it because there are already dedicated directories and marketplaces for those types of resources.

“We had the same thoughts as you about whether it was worth including plugins and themes, particularly free ones,” she responded. She listed four primary reasons they thought it was worth doing:

  • If it’s for “all things WordPress,” we need to include all things WordPress.
  • We liked that free and paid plugins were searchable alongside each other.
  • The WordPress repositories don’t have our advanced searching. We’re not a replacement for them, just an additional option.
  • Reviews can negatively impact products that don’t always deserve them. We wanted to level the playing field there.

DeRosia said the idea for this project and WP Podcasts came along at about the same time (note: the WP Tavern podcast is listed on the site). The latter was less ambitious in scope, so they launched it first as a case study.

“From the beginning, the driving force behind the HeroPress expansion was to create bridges between the various WordPress communities, and Find It WP seemed like a natural element to that,” said DeRosia. “It’s really difficult to be successful and stay on top of an ever-growing ecosystem like WordPress. With Find It WP, we can simplify it all, for both searchers and those looking to be found.

“I’ve spent the last 18 months looking at ways people can get visibility in the WordPress Community, and it’s nearly impossible. It’s so large and spread out that, even if you have the most amazing new product, it’s hard to get noticed. Find It WP simplifies all of that and provides equitable opportunities for anyone.”

Equity is a a crucial part of this project, leveling the playing field for creators regardless of their size. There is no special attention given to major players. The resources are simply there for people to search for and find what they are looking for.

At the moment, there are currently 42 listings spread across multiple categories. DeRosia said she was getting back to approving more as we ended our chat. As the site grows, it should offer opportunities for more businesses and people to connect.

“The idea for Find It WP did come out of our experiences with HeroPress and expanding into the HeroPress Network,” she said. “We’ve needed help along the way and have been thrilled by the talent we’ve come across. If we hadn’t been looking or had the connections we had, we wouldn’t have found them. We didn’t want to keep that to ourselves. We want everyone to be able to experience the benefits that come from working together as a community.”

Topher DeRosia also published a Find It WP announcement on the HeroPress blog.

How to build a simple Whatsapp bot on php

Hi, everybody.
I write bots and decided to share my experience of writing bots on the [Chat API] platform, maybe it will be interesting to someone

Well tell you how to write a simple bot on PHP using API WhatsApp.

A demo bot will react on commands of ordinary Whatsapp messages to answer them. Now our demo bot features the following functions:

Displaying the list of commands
Displaying the ID of the chat
Displaying the current bot server time
Displaying your name
Sending files of different formats (pdf, jpg, doc, mp3, etc.)
Sending pre-recorded voice messages
Sending geo-coordinates (geo-locations)
Creating a conference (group)

Attention: to make the bot running, the phone must be connected to the Internet and mustnt be used for Whatsapp Web. Its more convenient to use a separate smartphone for coding.

Preparatory work
Authorization of Whatsapp via QR code

At the very beginning, we need to connect whatsapp with our script, so as we write the code, we check its operation. To do this, go to your personal account and get a QR code there. Next, open WhatsApp on your smartphone, go to Settings -> WhatsApp Web -> Scan a QR code.

Now we need to indicate a WebHook URL so the server can run the scrip when new messages arrive. Indicate a direct link to your script, e.g. https://domain.com/PHP/whatsappbot.php. You cant indicate server IP only, but you can indicate the port.

Now lets produce a whatsappbot.php file to make a following class: class whatsAppBot

Create variables to put the APIUrl and the token. They can be found in your personal account.

{var $APIurl = 'https://api.chat-api.com/instanceYYYYY/';
                            var $token = 'abcdefgh12345678';}

Now lets indicate the function __construct(), which will be executed automatically every time the script is run. The ChatAPI server will access the bot when new messages arrive (you can find more information below), sending information about the new message in JSON format. We immediately catch this data at the beginning of the function and put it into variables.

{public function __construct(){ $json = file_get_contents('php://input');
                            $decoded = json_decode($json,true);}}

Were continuing to write the code of the function. You can save the data received to the file to analyze and checkout if needed. To make it, well use an output buffer.

{ ob_start();
                        var_dump($decoded);
                        $input = ob_get_contents();
                        ob_end_clean();
                        file_put_contents('input_requests.log',$input.PHP_EOL,FILE_APPEND);}

Then well write the processing of incoming messages and execution of the corresponding functions. Therell be a lot of nested code, but well sort it line by line.

{ if(isset($decoded['messages'])){
                        foreach($decoded['messages'] as $message){
                        $text = explode(' ',trim($message['body']));
                        if(!$message['fromMe']){
                        switch(mb_strtolower($text[0],'UTF-8')){
                        case 'hi':  {$this->welcome($message['chatId'],false); break;}
                            case 'chatId': {$this->showchatId($message['chatId']); break;}
                            case 'time':   {$this->time($message['chatId']); break;}
                            case 'me':     {$this->me($message['chatId'],$message['senderName']); break;}
                            case 'file':   {$this->file($message['chatId'],$text[1]); break;}
                            case 'ptt':     {$this->ptt($message['chatId']); break;}
                            case 'geo':    {$this->geo($message['chatId']); break;}
                            case 'group':  {$this->group($message['author']); break;}
                            default:        {$this->welcome($message['chatId'],true); break;}
                            }}}}}

So:

if(isset($decoded['messages']))
Notifications such as "user left the chat" are also sent by the server, but they wont have an array of messages. This check prevents the Undefined index error.

foreach($decoded['messages'] as $message)
There is an array of messages, so you can get several of them; the bot must react to every and each.

$text = explode(' ',trim($message['body']));
Well divide the message body into the single words. The first one is a command, the rest are parameters.

if(!$message['fromMe'])
This check is necessary so that the bot wont recourse. The mark "fromMe" means that the message was sent by the bot itself. Therefore, we continue the execution only for incoming messages.

switch(mb_strtolower($text[0],'UTF-8'))
Switch block will identify the command (using the first word). The command must be given in the lower register so the bot react to it, no matter HOW It WiLL bE WriTTen.

case 'hi': {$this->welcome($message['chatId'],false)}
Actually, the execution of the appropriate command depends on the first word. We transfer chatId from the message to the function of execution, so that the sending will take place in the corresponding chat. Technially, all the following lines are the same, but pay attention to:

case 'file': {$this->file($message['chatId'],$text[1])}
Here we give one more parameter which is indicated with the second word. Everything will be discussed below. Also, pay attention to:

case 'me': {$this->me($message['chatId'],$message['senderName'])}
Here the second parameter is the name of the interlocutor, taken from the message data. And in default we execute a function that displays a list of commands, but with the true parameter, which means getting the wrong command.

Weve finished writing the __construct() function. Lets turn to the functions executed by the commands from the above-mentioned switch block. In the part of the functions, the sendMessage () function is executed, in another part - the sendRequest () function. In the script, these functions are below, but we will tell about them immediately:

The sendRequest() function directly queries the ChatAPI server for sending messages and various media. It takes 2 parameters, $method and $data.

$method determines which chatAPI method should be executed
$data contains the data required for transfer.

{ public function sendRequest($method,$data){
                        $url = $this->APIurl.$method.'?token='.$this->token;
                        if(is_array($data)){ $data = json_encode($data);}
                        $options = stream_context_create(['http' => [
                        'method'  => 'POST',
                        'header'  => 'Content-type: application/json',
                        'content' => $data
                        ]]);
                        $response = file_get_contents($url,false,$options);
                        file_put_contents('requests.log',$response.PHP_EOL,FILE_APPEND);}

Let us examine in more detail: In $url we form a valid URL containing the APIUrl, method and token. Then we check the incoming data. If it is an array, convert it to JSON. If not, then the conversion to JSON has already been implemented in the executed function. $options set HTTP headers. Then through file_get_contents we execute the request for the generated URL, passing the data. The last line is optional, it simply writes the ChatAPI server's response to a file for debugging and logging.

The sendMessage() function is just a shell for sending simple text messages. It forms the correct data array and passes it to the abovementioned sendRequest() function with the "message" method.

{ public function sendMessage($chatId, $text){
                        $data = array('chatId'=>$chatId,'body'=>$text);
                        $this->sendRequest('message',$data);}}

Now well create control functions from the switch block. Functions that send a simple text message, usually execute sendMessage() with certain text. Functions that send various media form their data arrays and execute sendRequest() with other methods.

Welcome() function will display the list of available commands.

{ public function welcome($chatId, $noWelcome = false){
                        $welcomeString = ($noWelcome) ? "Incorrect command\n" : "WhatsApp Demo Bot PHP\n";
                        $this->sendMessage($chatId,
                        $welcomeString.
                        "Commands:\n".
                        "1. chatId - show ID of the current chat\n".
                        "2. time - show server time\n".
                        "3. me - show your nickname\n".
                        "4. file [format] - get a file. Available formats: doc/gif/jpg/png/pdf/mp3/mp4\n".
                        "5. ptt - get a voice message\n".
                        "6. geo - get a location\n".
                        "7. group - create a group with the bot");}}

If the $noWelcome parameter is false, then the first line of the message will be the greeting displayed by the "hi" command. If its true, the greeting will be replaced with a message to the wrong command.

The showchatId() function displays the current chat ID using the "chatId" command.

{ public function showchatId($chatId){
                        $this->sendMessage($chatId,'chatId: '.$chatId);}}

The time() function display the current server time using the "time" command.

{ public function time($chatId){
                        $this->sendMessage($chatId,date('d.m.Y H:i:s'));}}

The me() function display the name of the interlocutor using the "me" command.

{
                        public function me($chatId,$name){
                        $this->sendMessage($chatId,$name);
                        }}

The file() function sends a file using the "file" command. This function is most interesting, since it works with the parameter. The parameter is the format of the file to be sent.

{ public function file($chatId,$format){
                        $availableFiles = array(
                        'doc' => 'document.doc',
                        'gif' => 'gifka.gif',
                        'jpg' => 'jpgfile.jpg',
                        'png' => 'pngfile.png',
                        'pdf' => 'presentation.pdf',
                        'mp4' => 'video.mp4',
                        'mp3' => 'mp3file.mp3'
                        );
                        if(isset($availableFiles[$format])){
                        $data = array(
                        'chatId'=>$chatId,
                        'body'=>'https://domain.com/PHP/'.$availableFiles[$format],
                        'filename'=>$availableFiles[$format],
                        'caption'=>'Get your file '.$availableFiles[$format]
                        );
                        $this->sendRequest('sendFile',$data);}}}

Lets explore it:

$availableFiles is an array in which the keys are the function parameters and the values are the file names. Naturally, files with names from the array must be present on the server. In this example, they are in the same place as the bot script, but you can put them in another folder.
if (isset ($availableFiles [$format])) checks the existence of an array key with the received parameter. If it exists, then we form an array of data, and pass it to sendRequest() with the "sendFile" method. The following data should be in the data array:
chatId as usual, the chat ID to which the response is sent.
body is a direct link to the file on your server. Please note that SSL must be enabled on the server!
filename is the file name, you can specify any
caption accompanies this file message.
Ptt() function sends a voice message using the "ptt" command. The voice message must be an .ogg file on your server.

{ public function ptt($chatId){
                        $data = array(
                        'audio'=>'https://domain.com/PHP/ptt.ogg',
                        'chatId'=>$chatId
                        );
                        $this->sendRequest('sendAudio',$data);}}

Here, as in the previous function, we form an array of data: chatId - chat ID audio - a direct link to the .ogg file, SSL is again required. And well pass it to the sendRequest function with the "sendAudio" method.

The geo() function sends geo-coordinates with the "geo" command

{ public function geo($chatId){
                        $data = array(
                        'lat'=>51.51916,
                        'lng'=>-0.139214,
                        'address'=>'Your address',
                        'chatId'=>$chatId
                        );
                        $this->sendRequest('sendLocation',$data);
                        }}

The process is the same as for the previous two functions. The array should contain the following data: lat and lng - coordinates; address is an address, but you can write any string; and of course chatId.

The function group() creates a conference for you and the bot with the "group" command.

{ public function group($author){
                        $phone = str_replace('@c.us','',$author);
                        $data = array(
                        'groupName'=>'Group with the bot PHP',
                        'phones'=>array($phone),
                        'messageText'=>'It is your group. Enjoy'
                        );
                        $this->sendRequest('group',$data);}}

Here we need to specify the phone numbers of users who will be added to the conference. In the first line, we retrieve the user's phone number from his personal ID, which looks like 79991234567@c.us Then we form an array:

groupName is the name of the conference;
phones is an array of phone numbers;
messageText is the text of the first message in the group;
Please note that this is the only function where you DONOT NEED to send the chatId. And we pass the array to sendRequest().

Now that we have finished working with functions, write a string after the closing bracket class: new whatsAppBot();

So the class will be executed automatically at the call to a script.

The final code will look like this.

{
                        class whatsAppBot{
                        //specify instance URL and token
                        var $APIurl = 'https://api.chat-api.com/instanceYYYYY/';
                        var $token = '**************************';

                        public function __construct(){
                        //get the JSON body from the instance
                        $json = file_get_contents('php://input');
                        $decoded = json_decode($json,true);

                        //write parsed JSON-body to the file for debugging
                        ob_start();
                        var_dump($decoded);
                        $input = ob_get_contents();
                        ob_end_clean();
                        file_put_contents('input_requests.log',$input.PHP_EOL,FILE_APPEND);

                        if(isset($decoded['messages'])){
                        //check every new message
                        foreach($decoded['messages'] as $message){
                        //delete excess spaces and split the message on spaces. The first word in the message is a command, other words are parameters
                        $text = explode(' ',trim($message['body']));
                        //current message shouldn't be send from your bot, because it calls recursion
                        if(!$message['fromMe']){
                        //check what command contains the first word and call the function
                        switch(mb_strtolower($text[0],'UTF-8')){
                        case 'hi':  {$this->welcome($message['chatId'],false); break;}
                            case 'chatId': {$this->showchatId($message['chatId']); break;}
                            case 'time':   {$this->time($message['chatId']); break;}
                            case 'me':     {$this->me($message['chatId'],$message['senderName']); break;}
                            case 'file':   {$this->file($message['chatId'],$text[1]); break;}
                            case 'ptt':     {$this->ptt($message['chatId']); break;}
                            case 'geo':    {$this->geo($message['chatId']); break;}
                            case 'group':  {$this->group($message['author']); break;}
                            default:        {$this->welcome($message['chatId'],true); break;}
                            }}}}}

                        //this function calls function sendRequest to send a simple message
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        //@param $text [string] [required] - text of the message
                        public function welcome($chatId, $noWelcome = false){
                        $welcomeString = ($noWelcome) ? "Incorrect command\n" : "WhatsApp Demo Bot PHP\n";
                        $this->sendMessage($chatId,
                        $welcomeString.
                        "Commands:\n".
                        "1. chatId - show ID of the current chat\n".
                        "2. time - show server time\n".
                        "3. me - show your nickname\n".
                        "4. file [format] - get a file. Available formats: doc/gif/jpg/png/pdf/mp3/mp4\n".
                        "5. ptt - get a voice message\n".
                        "6. geo - get a location\n".
                        "7. group - create a group with the bot"
                        );
                        }

                        //sends Id of the current chat. it is called when the bot gets the command "chatId"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        public function showchatId($chatId){
                        $this->sendMessage($chatId,'chatId: '.$chatId);
                        }
                        //sends current server time. it is called when the bot gets the command "time"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        public function time($chatId){
                        $this->sendMessage($chatId,date('d.m.Y H:i:s'));
                        }
                        //sends your nickname. it is called when the bot gets the command "me"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        //@param $name [string] [required] - the "senderName" property of the message
                        public function me($chatId,$name){
                        $this->sendMessage($chatId,$name);
                        }
                        //sends a file. it is called when the bot gets the command "file"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        //@param $format [string] [required] - file format, from the params in the message body (text[1], etc)
                        public function file($chatId,$format){
                        $availableFiles = array(
                        'doc' => 'document.doc',
                        'gif' => 'gifka.gif',
                        'jpg' => 'jpgfile.jpg',
                        'png' => 'pngfile.png',
                        'pdf' => 'presentation.pdf',
                        'mp4' => 'video.mp4',
                        'mp3' => 'mp3file.mp3'
                        );

                        if(isset($availableFiles[$format])){
                        $data = array(
                        'chatId'=>$chatId,
                        'body'=>'https://domain.com/PHP/'.$availableFiles[$format],
                        'filename'=>$availableFiles[$format],
                        'caption'=>'Get your file '.$availableFiles[$format]
                        );
                        $this->sendRequest('sendFile',$data);}}

                        //sends a voice message. it is called when the bot gets the command "ptt"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        public function ptt($chatId){
                        $data = array(
                        'audio'=>'https://domain.com/PHP/ptt.ogg',
                        'chatId'=>$chatId
                        );
                        $this->sendRequest('sendAudio',$data);}

                        //sends a location. it is called when the bot gets the command "geo"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        public function geo($chatId){
                        $data = array(
                        'lat'=>51.51916,
                        'lng'=>-0.139214,
                        'address'=>' ',
                        'chatId'=>$chatId
                        );
                        $this->sendRequest('sendLocation',$data);}

                        //creates a group. it is called when the bot gets the command "group"
                        //@param chatId [string] [required] - the ID of chat where we send a message
                        //@param author [string] [required] - "author" property of the message
                        public function group($author){
                        $phone = str_replace('@c.us','',$author);
                        $data = array(
                        'groupName'=>'Group with the bot PHP',
                        'phones'=>array($phone),
                        'messageText'=>'It is your group. Enjoy'
                        );
                        $this->sendRequest('group',$data);}

                        public function sendMessage($chatId, $text){
                        $data = array('chatId'=>$chatId,'body'=>$text);
                        $this->sendRequest('message',$data);}

                        public function sendRequest($method,$data){
                        $url = $this->APIurl.$method.'?token='.$this->token;
                        if(is_array($data)){ $data = json_encode($data);}
                        $options = stream_context_create(['http' => [
                        'method'  => 'POST',
                        'header'  => 'Content-type: application/json',
                        'content' => $data]]);
                        $response = file_get_contents($url,false,$options);
                        file_put_contents('requests.log',$response.PHP_EOL,FILE_APPEND);}}
                        //execute the class when this file is requested by the instance
                        new whatsAppBot();}

Youll only need to substitute your token from your personal account into the $ token variable and instance number

Thank you for reading, I hope you will find it useful

Kotlin equivalence of Java try-with-resource statement

Introduction

If you are a Java developer coming to Kotlin, you might have wondered how to use a language construct that is similar to the try-with-resource statement in Java to automatically close Autocloseable/Closeable resources for you.

Luckily, Kotlin provides the inline extension function use() that provides similar functionality to the try-with-resource block.

In this tutorial, we will learn how to use the use() extension function with Autocloseable resources.

Goals

At the end of the tutorial, you would have learned:

  1. How to use the use() extension function.
  2. How it differs from the Java try-with-resource statement.
Prerequisite Knowledge
  1. Basic Kotlin.
  2. Basic Java IO/NIO concepts.
Tools Required
  1. A Kotlin IDE such as IntelliJ Community Edition.
Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Kotlin project, using JDK 16 as the project JDK and Gradle 7.2 as the build tool.

  2. Copy and paste the configuration code below into your build.gradle.kts file.

     import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
    
     plugins {
        kotlin("jvm") version "1.5.31"
     }
    
     group = "com.example"
     version = "1.0-SNAPSHOT"
    
     repositories {
        mavenCentral()
     }
    
     dependencies {
        testImplementation(kotlin("test"))
     }
    
     tasks.test {
        useJUnitPlatform()
     }
    
     tasks.withType<KotlinCompile>() {
        kotlinOptions.jvmTarget = "11"
     }
  3. Under src/main/kotlin, create a new package called com.example.

  4. Under src/main/kotlin/com/example, create a new Kotlin file called Entry.kt.

  5. Create the main() function inside this file.

  6. Under src/main, create a new source set to contain Java code.

  7. Under src/main/java, create a new package called com.example.

  8. Under src/main/java/com/example, Create a new empty class called JavaTest.

Java try-with-resource Overview

To provide some context, we will start with reviewing the try-with-resource statement in a very simplistic way.

Java has a language construct called try-with-resource statement to automatically call the close() method on resources declared in its declaration statement. The resources that are declared must implement Autocloseable/Closeable or there will be a compiler error.

In the JavaTest.java class, copy and paste the code snippet below:

    package com.example;

    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;

    public class JavaTest {
       public void tryWithResource() {
           try(var reader = new BufferedReader(new FileReader(""))){ //1
               //do nothing
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
    }

The method tryWithResource() in the code snippet is only meant to show usage of the try-with-resource statement syntax. On line 1, we declared the BufferedReader resource, which implements the Closeable interface. We did not have to call close() manually because the try-with-resource statement will automatically do that for us.

Kotlin use() function

Kotlin actually does not have a language construct like try-with-resource. The recommended way to automatically close resources is to use the inline extension function use(). Its function signature is:

inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R

Firstly, let us deconstruct the function signature so we can understand how to use this function:

  1. inline: indicates that this function is an inline function.
  2. T : AutoCloseable?: this part of the generics means T must implement AutoCloseable and a T reference is nullable.
  3. R: this is the return type of the lambda block.
  4. T.use: T is the receiver type here. This means that this use() function can be called from any T(Autocloseable) reference.
  5. block: (T) -> R: the function type. The lambda expression that implements this function type will receive the Closeable resource T to operate on.

In simple terms, we can call the use() function from any AutoCloseable reference. We must pass an implementation of the function type (T) -> R as an argument. The most popular way to do that would be to use the passing-trailing-lambda syntax.

Now that we have understood the anatomy of the use() function, it is time to write some code.

In the Entry.kt file, create a Test class that implements Autocloseable using the code snippet below.

    class Test: AutoCloseable { //1

       fun doThrow(): Nothing = throw RuntimeException("${hashCode()} throwing from doThrow()") //2

       override fun close() = throw IOException("${hashCode()} throwing from close()") //3

    }

The Autocloseable interface only requires us to implement one abstract method, which is

public void close()

On line 3, we satisfy the contract by providing an implementation for it.

override fun close() = throw IOException("${hashCode()} throwing from close()") //3

Line 2 is not part of the Autocloseable contract, but it is there only to show how Suppressed Exceptions are propagated.

We can now use this Test class in main() with the use() function like below.

Test().use { //1
   it.doThrow() //2
}

On line 1, we instantiate Test, and because it implements Closeable, we can use the extension function use() from it.

Line 2 is where we purposely throw a RuntimeException just to see whether the suppressed exception shows up.

The code prints the stack trace below.

Exception in thread "main" java.lang.RuntimeException: 1149319664 throwing from doThrow()
    at com.example.Test.doThrow(Entry.kt:20)
    at com.example.EntryKt.main(Entry.kt:8)
    at com.example.EntryKt.main(Entry.kt)
    Suppressed: java.io.IOException: 1149319664 throwing from close()
        at com.example.Test.close(Entry.kt:22)
        at com.example.Test.close(Entry.kt:18)
        at kotlin.jdk7.AutoCloseableKt.closeFinally(AutoCloseable.kt:64)
        ... 2 more

So we indeed do see the IOException thrown from the close() function as a suppressed exception. We can tell whether it is the close() or the doThrow() function that is printing the stack trace from the messages passed to the Exception constructors:

1149319664 throwing from doThrow()
1149319664 throwing from close()
use() function with multiple resources

In the previous section, we were only declaring one resource for the use() function. In Java, it is possible to declare multiple resources in the same try-with-resources statement. I have added this method into the JavaTest file to show that it is valid Java syntax and will compile.

public void tryWithResources(){
   try(var reader = new BufferedReader(new FileReader(""));
   var reader2 = new BufferedReader(new FileReader("/"))){ //1
       //do nothing
   } catch (IOException e) {
       e.printStackTrace();
   }
}

So how do we do the same thing in Kotlin? Unfortunately, Kotlin does not provide such a construct, so it is not possible, at least not in a clean way like Javas. There are a lot of discussions around this topic here: https://discuss.kotlinlang.org/t/kotlin-needs-try-with-resources/214/46.

I do not want to divert too much of your attention into unofficial solutions in this tutorial, and I do not think it is a good idea to use them because if Kotlin releases such a language feature in the future, the compiler might not be able to detect and convert these unofficial language features into your code.

To play safe, I would stick with nested use() blocks because I think the compiler can easily find those in the code in the future and assist with refactoring.

It is very simple to use the nested use blocks, which allows you to access both Closeable resources in the same scope. Go ahead and uncomment the previous function calls in main() and add the nested use() blocks below.

Test().use { //3
   Test().use { //4
       it.doThrow() //5
   }
}

The code prints:

Exception in thread "main" java.lang.RuntimeException: 1149319664 throwing from doThrow()
    at com.example.Test.doThrow(Entry.kt:20)
    at com.example.EntryKt.main(Entry.kt:13)
    at com.example.EntryKt.main(Entry.kt)
    Suppressed: java.io.IOException: 1149319664 throwing from close()
        at com.example.Test.close(Entry.kt:22)
        at com.example.Test.close(Entry.kt:18)
        at kotlin.jdk7.AutoCloseableKt.closeFinally(AutoCloseable.kt:64)
        ... 2 more
    Suppressed: java.io.IOException: 935044096 throwing from close()
        at com.example.Test.close(Entry.kt:22)
        at com.example.Test.close(Entry.kt:18)
        at kotlin.jdk7.AutoCloseableKt.closeFinally(AutoCloseable.kt:64)
        ... 2 more

And we can see that Kotlin tried to close both of the resources for us.

Solution Code

JavaTest.java

package com.example;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class JavaTest {
   public void tryWithResource() {
       try(var reader = new BufferedReader(new FileReader(""))){ //1
           //do nothing
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

   public void tryWithResources(){
       try(var reader = new BufferedReader(new FileReader(""));
       var reader2 = new BufferedReader(new FileReader("/"))){ //1
           //do nothing
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}

Entry.kt

package com.example

import java.io.IOException

fun main(){

//    Test().use { //1
//        it.doThrow() //2
//    }

   Test().use { //3
       Test().use { //4
           it.doThrow() //5
       }
   }
}

class Test: AutoCloseable { //1

   fun doThrow(): Nothing = throw RuntimeException("${hashCode()} throwing from doThrow()") //2

   override fun close() = throw IOException("${hashCode()} throwing from close()") //3

}
Summary

We have learned how to utilize the inline function use() in Kotlin to automatically close resources in places where we normally would use a try-with-resources in Java. The full project code can be found here: https://github.com/dmitrilc/DaniwebKotlinTWRuse

How to create Intersection Types in Java

Introduction

In Java, a common way to express a type that is a combination of two or more types is to just create an interface that extends the other types. The problem with this approach is that your code might be littered with interface declarations, polluting your code and namespace.

But there is a solution to this problem, and that is the usage of Intersection Type. The Intersection Type is somewhat of an elusive language feature in Java, so in this tutorial, we will learn how to use it on our code.

Goals

At the end of the tutorial, you would have learned:

  1. How to use intersection types.
Prerequisite Knowledge
  1. Basic Java.
  2. Basic Java Generics.
Tools Required
  1. A Java IDE such as IntelliJ Community Edition.
Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Java project.
  2. Create a package com.example.
  3. Create a class called Entry.
  4. Create the main() method inside Entry.java.
Not using Intersection Types

Before going into Intersection Types, let us look at how it is done without it. Add these two top-level interfaces into the Entry.java file.

interface Human { //1
   void talk();
}

interface Wolf { //2
   void howl();
}

In the code snippet above, we have two interfaces, Human and Wolf. But what if we want a new type that is both a human and a wolf? To do that, we add the top-level HumanWolf interface into Entry.java like below.

interface HumanWolf extends Wolf, Human {} //3

Also, in the Entry class, we need to add a method that will use the HumanWolf interface.

private static void boilerplate(HumanWolf werewolf){
   werewolf.howl();
   werewolf.talk();
}

I named the method boilerplate because we had to declare a completely new interface just to represent a type that is both Human and Wolf. In main(), we can call it using:

var werewolf = new HumanWolf(){
   @Override
   public void howl() {
       System.out.println("howls");
   }

   @Override
   public void talk() {
       System.out.println("talks");
   }
};

boilerplate(werewolf);

The code prints:

howls
talks
Using Intersection Types

Declaring a new sub-interface might seem okay. But there is another way to create our hybrid type, and that is using Intersection Types. To declare an intersection type, we would need to use an Ampersand(&) between the types in the generics parameter section on the method signature. Still inside the Entry.java class, create another method from the code below.

private static <T extends Wolf & Human> void noBoilerplate(T werewolf){
   werewolf.howl();
   werewolf.talk();
}

In the method above, we declare that T must be some type that is BOTH a Wolf and a Human. We did not need to declare the HumanWolf sub-interface at all. We can test our theory by attempting to use the method with the code below in main().

var wolf = (Wolf)() -> System.out.println("howls");
noBoilerplate(wolf);

var human = (Human)() -> System.out.println("Hello");
noBoilerplate(human);

In the code snippet above, we tried to pass either a Wolf or a Human instance into the noBoilerplate() method, but the code refuses to compile with the errors:

reason: no instance(s) of type variable(s) exist so that Wolf conforms to Human
reason: no instance(s) of type variable(s) exist so that Human conforms to Wolf

because the noBoilerplate() needs a type that implements both Wolf and Human.

We still need to declare a class that implements both Wolf and Human though, and the anonymous class syntax will not allow implementing more than one interface at once. In the Entry class, create a new inner static class using the code below.

private static class Werewolf implements Wolf, Human {
   @Override
   public void talk() {
       System.out.println("howls");
   }

   @Override
   public void howl() {
       System.out.println("talks");
   }
}

Finally, we can call the method in main() with:

noBoilerplate(new Werewolf());
Using Intersection Type to cast Lambdas

Intersection types can also be used to cast lambdas into a new type that conforms to two or more types. The only thing to watch out for when using this feature is whether the lambda can implement both types at once without any conflicting methods. This feature is commonly used to cast a lambda to a marker interface (an empty interface) because the marker interface does not introduce any conflict.

Let us create a new top-level interface called Alpha.

interface Alpha {}

In main(), add another method to test this feature. This method will check if a Wolf is also an Alpha.

private static boolean isAlpha(Wolf wolf){
   return wolf instanceof Alpha; //Wolf and Alpha are not related.
}

In main(), we call the code:

var wolf = (Wolf)() -> System.out.println("howls");
System.out.println(isAlpha(wolf));

var alphaWerewolf = (Alpha & Wolf)() -> System.out.println("Alpha howls");
System.out.println(isAlpha(alphaWerewolf));

And the output would be:

false
true

The variable declaration alphaWerewolf is where we casted the lambda into both an implementation of Wolf and an implementation of Alpha(it is empty so there is nothing to override).

Solution Code
package com.example;

public class Entry {
   public static void main(String[] args){
       var werewolf = new HumanWolf(){
           @Override
           public void howl() {
               System.out.println("howls");
           }

           @Override
           public void talk() {
               System.out.println("talks");
           }
       };

       boilerplate(werewolf);

//        var wolf = (Wolf)() -> System.out.println("howls");
//        noBoilerplate(wolf);
//
//        var human = (Human)() -> System.out.println("Hello");
//        noBoilerplate(human);

       noBoilerplate(new Werewolf());

       var wolf = (Wolf)() -> System.out.println("howls");
       System.out.println(isAlpha(wolf));

       var alphaWerewolf = (Alpha & Wolf)() -> System.out.println("Alpha howls");
       System.out.println(isAlpha(alphaWerewolf));
   }

   private static void boilerplate(HumanWolf werewolf){
       werewolf.howl();
       werewolf.talk();
   }

   private static <T extends Wolf & Human> void noBoilerplate(T werewolf){
       werewolf.howl();
       werewolf.talk();
   }

   private static class Werewolf implements Wolf, Human {
       @Override
       public void talk() {
           System.out.println("howls");
       }

       @Override
       public void howl() {
           System.out.println("talks");
       }
   }

   private static boolean isAlpha(Wolf wolf){
       return wolf instanceof Alpha; //Wolf and Alpha are not related.
   }
}

interface Human { //1
   void talk();
}

interface Wolf { //2
   void howl();
}

interface HumanWolf extends Wolf, Human {} //3

interface Alpha {}
Summary

We have learned how to use intersection types in Java. The full project code can be found here: https://github.com/dmitrilc/DaniwebJavaIntersectionType

Software Freedom Conservancy Takes On Vizio in Lawsuit Alleging GPL Violations

Inside of a Vizio TV – Model V435-J01 – photo credit: Software Freedom Conservancy

The Software Freedom Conservancy announced that it is suing Vizio, an American TV manufacturer, for what it alleges are “repeated failures to fulfill even the basic requirements of the General Public License (GPL).”

The 501(c)(3) charity organization is a non-profit that provides infrastructure support for free and open source software projects, defending users’ rights under copyleft licenses and the GPL. A few of its member projects include BusyBox, Git, Homebrew, OpenWrt, and phpMyAdmin. As part of its mission, the Conservancy assists member projects in enforcing the terms of FLOSS licenses, including through litigation.

The Software Freedom Conservancy contends that Vizio’s TV products contain software with copyleft licenses but the company refused to provide the source code after multiple attempts at contact since 2018. Vizio also did not inform its customers about the rights included with the software it was bundling.

“We filed suit as a purchaser of TVs to stand up for copyleft,” Software Freedom Conservancy Executive Director Karen Sandler said. “There was no source or offer for source even after we’d been working with them for a long time on older products. Copyleft can help us take control of our tech in a meaningful way, it’s a huge benefit to consumers.”

In a new twist on GPL enforcement, the organization is presenting this lawsuit on behalf of individual consumers, as opposed to the traditional path of defending copyright holders of the GPL code in question.

The Software Freedom Conservancy contends that Vizio’s alleged noncompliance prevents consumers from being able to repair their devices, improve and control them, as well as curtail surveillance and ads through modifications. The full text of the complaint includes examples of how consumers might benefit from having access to the source code:

Vizio is unlikely to unilaterally implement features that prevent the collection of such user data, as such user data is valuable to Vizio. Access to the Source Code of the Linux kernel, the other SmartCast Programs at Issue, and for the Library Linking Programs, as used on Vizio smart TVs, would enable software developers to preserve useful but obsolete features. It would also allow software developers to maintain and update the operating system should Vizio or its successor ever decide to abandon it or go out of business. In these ways, purchasers of Vizio smart TVs can be confident that their devices would not suffer from software-induced obsolescence, planned or otherwise.

In the organization’s announcement of the lawsuit, Sandler highlighted the environmental impact of denying consumers access to the code.

“The global supply chain shortages that have affected everything from cars to consumer electronics underscore one of the reasons why it is important to be able to repair products we already own,” says Sandler. “Even without supply chain challenges, the forced obsolescence of devices like TVs isn’t in the best interest of the consumer or even the planet. This is another aspect of what we mean by ‘ethical technology.’ Throwing away a TV because its software is no longer supported by its manufacturer is not only wasteful, it has dire environmental consequences. Consumers should have more control over this, and they would if companies like Vizio played by the rules.”

The complaint alleges that Vizio is in breach of the GPLv2 and the LGPLv2.1 every time they distribute a smart TV without the source code or a written offer to provide it. It claims that none of the smart TVs Vizio has introduced in the US market in the past four years have been accompanied by the source code.

Open source leaders from affiliate organizations commented on the unique approach of this lawsuit, where the Conservancy is acting on behalf of consumers as the plaintiff, as opposed to prevailing upon the copyright holder to defend its users. The complaint states:

No one other than the purchaser has both the information and motive to enforce the Source Code Provision. Purchasers will both know whether the Source Code Provision has been honored and have a desire to examine and further develop the corresponding source code.

“If this consumer-rights-based approach succeeds, the main excuse for copyright assignments to organizations like the FSF will be gone and we can unambiguously treat a CLA as a red flag,” Open Source Initiative Director of Standards/Policy Simon Phipps said.

The complaint states that the “Plaintiff and other members of the class of persons intended to benefit from the GPLv2 and LGPLv2.1 have been damaged in an amount that cannot be readily determined.” Vizio’s alleged noncompliance has effectively stunted the source code’s potential for improvements from consumers for years. It has also robbed consumers of the control of their electronics and given Vizio an unfair advantage that protects their corporate interests at the expense of users’ freedoms. The Conservancy is not seeking an exact amount for damages but rather leaving it to the judgment of the court.

“This lawsuit has the potential to empower many consumers, radically changing the landscape of consumer technology,” Open Source Initiative Executive Director Stefano Maffulli said.

The Conservancy’s case will be an important one to watch, as its decision could set a precedent for GPL enforcement in the future. If nothing else, it should signal to companies benefiting from GPL-licensed code that they must also respect consumers’ rights. If consumers are the intended third-party beneficiaries of the license agreement, then they have a right to the source code.

Twilio Engage Enables Scalable Personalized Content Delivery

Today at Twilio SIGNAL 2021, the cloud communication specialist’s annual developer conference, Twilio announced a new marketing platform that merges the company’s communications APIs with Twilio Segment’s Customer Data Platform (CDP). The new platform, Twilio Engage, is meant to provide B2C organizations with a simplified path to producing personalized omnichannel campaigns.

338: With Lynn Fisher

Lynn Fisher is my guest this week! You might know her as @lynnandtonic on CodePen and most other platforms. We get to talk about her A Single Div project and all the CSS magic that goes into those, other creative projects and why those are so satisfying, a recent transition over to Netlify, and the fact that we’ve worked together on an illustration project without ever having met.

Time Jumps

  • 00:49 Guest introduction
  • 02:06 Web illustrations in a single div
  • 07:40 Background tools
  • 10:28 Sponsor: VideoPress
  • 12:27 Workflow for creating single divs
  • 17:00 Changing from client work to product work
  • 21:45 What are you excited about?
  • 26:11 Illustrations for flexbox guide

Sponsor: VideoPress

There is a bit of a rebirth of VideoPress that just happened! If you run a self-hosted WordPress site like we do, VideoPress is a major upgrade to hosting videos in content. You get VideoPress through Jetpack. If you buy Jetpack Complete, you’ve got it, otherwise, it’s an ala-carte purchase. Meaning if VideoPress is the only feature of Jetpack you want, no problem, it’s literally the only thing you need to pay for and use. To name a few things… with VideoPress you get a nice customized video player, cloud-hosted optimized video delivery, playback speed control, and the videos will look good on mobile without you having to manually create a poster.

The post 338: With Lynn Fisher appeared first on CodePen Blog.

7 eCommerce Design Trends You Need to Know for 2022

With more and more people depending on online stores to get the goods and services they need, ecommerce companies need websites that can keep up with demand. The only problem is that the ecommerce landscape is constantly changing, so what may have made for a great ecommerce experience just a year or two ago might now feel inefficient and outdated. 

As a web designer, how do you keep up with this? 

One thing you can do to ensure that your clients’ ecommerce sites are able to compete in ecommerce is to stay on top of ecommerce design trends. Another thing you can do is use tools like BeTheme that make it easy to align your process with the latest and greatest trends — no matter how quickly and frequently they change. 

7 ecommerce design trends for 2022

Start preparing now for what’s coming to ecommerce in 2022: 

1. Textured backgrounds

People are spending more time online, and not just for entertainment purposes. They’re also doing more online shopping. 

That said, despite consumers becoming more immersed in the digital retail space, we’re going to see a resurgence in an old design trend: Skeuomorphism. Or at least the part of it that can be repurposed for 2022. 

In the coming year, expect to see more ecommerce websites bring real-world textures to their designs. Unlike the cheesy way in which they were used in the early 2010s (remember the wood-grain panels?), these textures will be much subtler and softer. 

You can see a beautiful example of this towards the bottom of the BeSurfing 2 pre-built website: 

Think of this ecommerce design trend as a way of bridging the divide between digital and physical. Do it right and shoppers should feel less disconnected from the ecommerce brands they buy from.

2. Light mode

Over the last couple of years, dark mode has been a very popular design trend. However, we’re going to see less and less of it in 2022. 

In its place will be “light mode” designs. Think of all-white and neutral tone backgrounds. 

The home page on BeTravel 2 is a nice example of this design trend in action: 

There are still some darker elements along the way, but they’re scarce compared to how heavy dark mode designs have been in recent years.

Similar to how textured backgrounds remove some of the coldness and disconnection from ecommerce, light mode is going to bring more light and weightlessness to the online shopping experience. This is a much-needed change up for consumers who’ve been staring at screens much more than usual the last year and a half.

3. Micro animations

Even though people have grown comfortable with doing more online, that doesn’t mean they want to devote more of their time to discovering, researching, and buying products and services. One way designers can help shoppers get exactly what they need and quickly is with well-placed micro animations. 

Micro animations allow shoppers to easily find the interactive parts of the store. Hover effects, in particular, are also helpful when it comes to product search. They can instantly reveal more views or variations of products. They can also provide shoppers with shortcuts to popular actions — like add-to-cart, share, favorite, etc. 

You can see an example of this on the Shop page on the BeTheme Store pre-built site: 

Hover effects essentially allow shoppers to quickly consider and compare products right from the product search page. 

4. Better filtering controls

As demand for online shopping grows, we’re going to see digital inventories grow as well. As stores grow their product offering — often with many lookalike products — customers are going to need a better way to search through all the options. 

Product filters already help with this. However, 2022 is all about further streamlining and improving the online shopping experience. That’s why we’re going to see product filters go the way of contact forms, whereby each field is designed specifically for the fastest input. 

For example, the BeClothingStore pre-built site doesn’t use a bunch of checkboxes or dropdowns for each filter option: 

Instead, filters perfectly fit the input type. This will make the shopping and product filtering process much more intuitive.

5. Live search

Out of the box, WordPress comes with a basic search function you can add to the header of the site. It can also be added as an internal widget, like on the ecommerce sidebar. Many times with a large ecommerce site, it’s just not enough. 

After installing a pre-built site like BeHome 3, open up a preview of the site and try searching for something. This is how the basic WordPress search works:

It’s convenient enough. The search bar is in the top-right corner of the site where shoppers expect it to be. And the search bar pops open into a larger space for easier typing. 

With BeTheme, you can activate the live search option in just a few seconds:

It will then transform your on-site search feature into this: 

In the coming year, we’re going to see more sites designed with a live search function in both the header as well as the product sidebar. This’ll enable shoppers to more quickly find the exact items they’re looking for. 

6. App-like ecommerce features on mobile

While the number of customers shopping on their smartphones has long surpassed those on desktop, they’ve been quite shy about converting on mobile. But as more people depend on online stores these days, we’re going to see them become less hesitant about buying on mobile. 

In order to encourage smartphone shoppers to do this, we’re going to see more ecommerce sites designed with mobile app-like features. Since consumers already spend the majority of their digital time in mobile apps, this type of interface and functionality will make them feel more comfortable on a mobile site. 

The easiest way to do this is to add sticky elements — headers, bottom bars, floating buttons, etc. 

You can see one example of how this might look on this modified version of the BeFlower 2 pre-built site: 

This is the responsive editor view in the Muffin Live Builder. With just a few sticky header options configured in BeTheme’s settings, we’re able to make both the top navigation bar and bottom ecommerce bar stick in place, giving the site an app-like feel.

7. Faster page speeds

Thanks to Google’s Core Web Vitals algorithm update in 2021, mobile page speeds have become a very important ranking factor for websites. So, designers are going to need to learn how to design lightning-fast websites so their ecommerce clients can do well in search results. 

While there are a variety of tools you can use for this — like fast web hosting, a caching plugin, and so on — BeTheme provides you with a ton of optimizations straight out the gate.

One of the most important optimization settings here is the Google Fonts “Load from Google” setting. According to a case study done by KeyCDN a few years ago, fonts hosted on Google’s CDN as opposed to locally load much more quickly: 

Not every web designer will have this kind of performance optimized tool readily available to them, so consider this a competitive advantage when it comes to ecommerce design trends.

Staying ahead of design trends and consumer expectations with BeTheme

BeTheme is a comprehensive website-building solution for WordPress. It comes with: 

  • 650+ pre-built websites — Professionally designed websites for any niche you can imagine
  • The Muffin Builder — An intuitive block builder for designing websites on the backend of your WordPress site
  • The Live Builder — A drag-and-drop live editor for designing websites on the front end of your site
  • The WooCommerce Builder — An ecommerce builder and widgets you can use to customize your layouts, products, and store

It’s not just the tools included within BeTheme that make it so useful in staying ahead of ecommerce design trends. Be’s developers are constantly working to push out important updates, design new pre-built sites, and provide designers with more innovative WordPress design tools — all so you can work faster and smarter.

The post 7 eCommerce Design Trends You Need to Know for 2022 appeared first on Codrops.

Creating Your Own Bragdoc With Eleventy

No matter what stage you’re at as a developer, the tasks we complete—whether big or small—make a huge impact in our personal and professional growth. Unfortunately, those tasks aren’t always recognized because they can easily get lost in the sea of other things that need to get done.

The unnoticed tasks we do fall under what is known as “invisible work,” a concept I stumbled across from a talk titled “Getting Credit for Invisible Work” by Ryan T. Harter. This type of work seeps into the cracks because our brains are not wired to remember things. Yet come review time, we find ourselves repeatedly stuck when trying to recall what we did over the past 6 or 12 months.

To solve this long-established problem, Julia Evans wrote an article suggesting that we keep our own “brag document.” A brag document is exactly what it sounds like. It’s a document where you give yourself permission to brag about all the valuable work you did. Whether it be:

  • How you contributed to a project
  • Helping others
  • Improving existing processes
  • Giving talks or running workshops
  • What you learned
  • Extra-curricular activities (e.g. blogging, talks, personal projects)
  • Awards and career progression

There is no one way to write a brag document, but that didn’t stop Jonny Burch and the team at Progression from building bragdocs.com.

Using their site to build one is a great idea, but what better way to brag about your work than to create your own brag document from scratch?

Today I want to show you how I re-created bragdocs.com using the static site generator Eleventy. With a little bit of JavaScript and CSS, you can get your own up and running!

What are we going to build?

Below is the end result of following this tutorial. You can find the live demo here. It imitates bragdocs.com as a starting point for you to create one from scratch and make it your own.

Requirements

  • Installing packages in Node.js (version 10 or higher)
  • General understanding of HTML and CSS
  • Markdown, Nunjucks templating, and JavaScript (all are optional, but helpful)
  • Basic programming concepts, including if statements, loops, and accessing variables in JSON

What is Eleventy?

Eleventy is a static site generator. This means that rather than building a full-stack website (front-end and back-end), you have flexibility to write content in any of the following templating languages accepted by Eleventy: HTML, Markdown, Liquid, Nunjucks, Mustache, etc. The content is then processed (using custom templates if you like) to generate static HTML pages, ready for hosting as a fully functioning site.

Setting up our “Hello, World!” Eleventy project

In this tutorial, the repository I’ll be referring to is eleventy-bragdoc, and the final product we’re working towards will be referred to as a “bragdoc.”

With a GitHub repository created with a README.md and .gitignore file for Node, I started setting up an Eleventy project.

Creating a new project

Inside eleventy-bragdoc, I began with the following files:

eleventy-bragdoc
├── README.md
└── .gitignore // .gitignore for node

With the terminal navigated inside of eleventy-bragdoc, I initialized the project by running the following command:

npm init -y

This created a package.json file for my node packages.

eleventy-bragdoc
├── package.json // new file
├── README.md
└── .gitignore

Next, I installed Eleventy.

npm install @11ty/eleventy

This gave me the following list of files and folders:

eleventy-bragdoc
├── node_modules  // new folder
├── package.json
├── package-lock.json  // new file
├── README.md
└── .gitignore

Configuring the Eleventy project

With Eleventy installed, I updated the scripts in the package.json file to include the following commands:

  • The start command serves the project during development which runs Browsersync for hot reload.
  • The build command creates production ready HTML files so that it can be hosted onto a server.
{
  // ...
  "scripts": {
    "start": "eleventy --serve",
    "build": "eleventy"
  },
 //  ...
}

Next, I created the required configuration file called .eleventy.js to specify the custom input and output directories.

eleventy-bragdoc
├── .eleventy.js  // new file
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

Inside .eleventy.js, I told Eleventy that it’s going to reference what’s inside of the src folder to build the HTML files. The output is then stored inside a folder called public:

module.exports = function(eleventyConfig) {
  return {
    dir: {
      input: "src",
      output: "public"
    }
  }
}

Creating front-facing content

To make my first page, I created the src folder that I declared as the input directory in .eleventy.js . Inside it, I added my first page, a Markdown file called index.md

Eleventy works with many templating languages that you can mix and match: HTML, Markdown, Liquid, Nunjucks, JavaScript, Handlebars, Mustache, EJS, Haml, Pug.

eleventy-bragdoc
├── src
│   └── index.md  // new file
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

In Eleventy, any key value pairs written between the dashes (---) above and below is considered front matter.

In index.md , I included a title property with the value “11ty x Bragdocs” and some test content underneath the front matter.

---
title: "11ty x Bragdocs"
---

This is the home page.

Building templates

Next, I created a folder which Eleventy expects, called _includes inside of src. This is where the templates, or what Eleventy refers to as layouts, must live. Within that folder, I created a subfolder called layouts for my first template, base.njk

The .njk filetype refers to the templating language Nunjucks.

eleventy-bragdoc
├── src
│   ├── _includes  // new folder
│   │   └── layouts  // new folder
│   │       └── base.njk  // new file
│   └── index.md
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

I added an HTML5 boilerplate inside base.njk:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
    
</body>
</html>

Creating pages with templates and front matter

In base.njk , between the <title> tags, I wanted to pull in the title property defined in the front matter of index.md, so I used double curly braces, i.e. {{title}}, to access this variable. Similarly, in the body, I added <h1> tags and set it with the same title property.

Next, I brought in the rest of the body content from index.md using the content property. Using the provided safe filter, I told Eleventy to render instead of escape any HTML that lives inside the content of the Markdown file.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ title }}</title>
</head>
<body>
  <h1>{{ title }}</h1>
  {{ content | safe }}
</body>
</html>

I then jumped back to index.md and added a layout property to the front matter and referenced base.njk

---
title: "11ty x Bragdocs"
layout: "layouts/base.njk"
---

This is the home page.

To give you an idea of what happens when we run the build, the template specified in the layout front matter property is used to wrap the Markdown content. In this example, the compiled HTML will look like what is shown below:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>11ty x Bragdocs</title>
</head>
<body>
  <h1>11ty x Bragdocs</h1>
  <p>This is the home page.</p>
</body>
</html>

Connecting CSS and image folders in build

While this part might not be necessary for all Eleventy projects, CSS and self-hosted images are always good features to add. So, I created two folders in the src directory: css and images.

eleventy-bragdoc
├── src
│   ├── css  // new folder
│   ├── images  // new folder
│   ├── _includes
│   │   └── layouts
│   │       └── base.njk
│   └── index.md
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

Then, in .eleventy.js, since I wanted the content inside these folders to be accessible when hosted, I referenced these folders by adding the following configurations:

  • addWatchTarget tells Eleventy that it should recompile when we make a change to a file in this directory (e.g. styles.css in the css folder).
  • addPassthroughCopy tells Eleventy that once the files are compiled, to take the contents of the directory and pass it through to the public directory.

You can read more about how passthrough file copy works in the documentation.

Since I was using the Nunjucks templating system, I added the markdownTemplateEngine property and set it to njk to make sure that it knows to go through Nunjucks first before anything else.

module.exports = function(eleventyConfig) {
  eleventyConfig.addWatchTarget("./src/css/")
  eleventyConfig.addWatchTarget("./src/images/")
  eleventyConfig.addPassthroughCopy("./src/css/")
  eleventyConfig.addPassthroughCopy("./src/images/")

  return {
    dir: {
      input: "src",
      output: "public"
    },
    markdownTemplateEngine: "njk"
  }
}

Then I created a styles.css file in the css folder and gave it something to test with to make sure it worked.

* {
  color: teal;
}

Since I already configured the css and images folders in .eleventy.js, I was able to reference these files using Eleventy’s URL filter.

To access these self-hosted files I used Eleventy’s URL filters in the href and src property of the css and image tags, respectively.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ title }}</title>

  <link rel="stylesheet" href="{{ '/css/styles.css' | url }}">

</head>
<body>
  <h1>{{ title }}</h1>

  <img src="{{ '/images/test_image.jpg' | url }}">

  {{ content | safe }}
</body>
</html>

Now I was ready to serve my Eleventy project.

Serving Eleventy in development

Since I had already defined the custom development scripts in package.json, I was able to run the following command:

npm start

This compiled index.md in the src directory and generated a HTML file in the public folder. Additionally, it launched a hot reload server through Browsersync where I could see the result at http://localhost:8080/

The result so far

With Eleventy running in development, I could start building the rest of the bragdoc.

Building the bragdoc system

With a base Eleventy project in a folder structure similar to what’s shown below, I began building out my bragdoc.

eleventy-bragdoc
├── src
│   ├── css
│   │   └── styles.css
│   ├── images
│   │   └── test_image.jpg
│   ├── _includes
│   │   └── layouts
│   │       └── base.njk
│   └── index.md
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

Creating a collection for bragdoc entries

Eleventy has the ability to create collections that group similar content together. Therefore, I created a folder called posts for my bragdoc entries. Inside that folder, I created multiple Markdown files to represent each entry.

The filenames post-1.md, post-2.md, post-3.md don’t affect anything that is rendered on the webpage

eleventy-bragdoc
├── src
│   ├── posts
│   │   ├── post-1.md  // new file
│   │   ├── post-2.md  // new file
│   │   └── post-3.md  // new file
│   ├── css
│   │   └── styles.css
│   ├── images
│   │   └── test_image.jpg
│   ├── _includes
│   │   └── layouts
│   │       └── base.njk
│   └── index.md
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

The custom properties that I thought would be useful to include:

  • Title
  • Date (by default, posts are sorted chronologically)
  • Categories (a list of values to organize entries)
  • Public / Private (a boolean value—true or false—to determine whether you want to show it on the bragdoc )
  • Icon (a Notion-inspired design element to visually organize entries)

I decided that the description for each entry would be the body content of the Markdown file, as this would give me freedom to add paragraphs, images, code blocks, etc. Additionally, I was not limited to Markdown elements as I could also include HTML and style it using CSS.

Below is an example of a bragdoc entry in a Markdown file:

---
title: Build my own Bragdoc using Eleventy
date: 2021-09-19
categories:
  - Learning
  - Eleventy
public: True
icon: 🎈
---

I learned how to use Eleventy to build my own bragdoc!

Some things to note:

  • Links written in Markdown by default do not open in a new blank window. So after some research, I stumbled upon a snippet by Mark Thomas Miller, which I added just before the closing <body> tag in base.njk. This might not be your thing (it’s definitely not Chris’ thing) but just in case you need it:
<script>
// Making all external links open in new tabs
// Snippet by Mark Thomas Miller

(function () {
  const links = document.querySelectorAll("a[href^='https://'], a[href^='http://']")
  const host = window.location.hostname

  const isInternalLink = link => new URL(link).hostname === host

  links.forEach(link => {
    if (isInternalLink(link)) return

    link.setAttribute("target", "_blank")
    link.setAttribute("rel", "noopener")
  })
})()
</script>
  • The date front matter property must be written in YYYY-MM-DD format.
  • You can assign as many custom front matter properties as you’d like. Just make sure that if you plan on accessing the property in the template, that the property exists in all of the Markdown files using the same template; otherwise it may break the build.
  • Lists in front matter can be written in multiple ways (e.g. an array or single line).

Assigning front matter properties to a collection

Instead of repeatedly assigning front matter properties with the same value in each Markdown file, I created a data directory JSON file to assign the same key-value pair only once across a collection.

To create a data directory file, it must have the same name as the collection, i.e. posts.json. Additionally, the file must also be placed inside the collection folder, i.e. the posts folder.

eleventy-bragdoc
├── src
│   ├── posts
│   │   ├── posts.json  // new file
│   │   ├── post-1.md
│   │   ├── post-2.md
│   │   └── post-3.md
│   ├── css
│   │   └── styles.css
│   ├── images
│   │   └── test_image.jpg
│   ├── _includes
│   │   └── layouts
│   │       └── base.njk
│   └── index.md
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

At this point, the posts for the bragdoc had not been defined as a collection yet. To do this, I added the tags property in posts.json. Here I assigned the value “posts” to that property so that I could access the collection by calling collections.posts

And since I didn’t need each post to have its own page, i.e. http://localhost:8080/posts/post-1/, I switched off it’s auto-generated permalink.

{
  "tags": "posts",
  "permalink": false
}

Listing bragdoc entries

Simply put, the bragdoc is a page made up of the entries in the posts collection. To access the front matter properties and body content of the Markdown files, the entries are looped through via Nunjucks.

To do this, I went back to index.md and changed the filetype from Markdown to Nunjucks, i.e. index.njk

eleventy-bragdoc
├── src
│   ├── posts
│   │   ├── posts.json
│   │   ├── post-1.md
│   │   ├── post-2.md
│   │   └── post-3.md
│   ├── css
│   │   └── styles.css
│   ├── images
│   │   └── test_image.jpg
│   ├── _includes
│   │   └── layouts
│   │       └── base.njk
│   └── index.njk  // changed filetype
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

Next, I replaced the content of index.njk with a Nunjucks for loop.

A Nunjucks function (for loop, if statement, etc.) must include start and end tags.

Since the order of posts by default was in chronological order (oldest first), I added the reverse filter to show the most recent at the top.

To access front matter and render it in HTML (such as the date and title of a post), I had to go through another “data” layer. Accessing properties in front matter requires double curly braces.

---
title: "11ty x Bragdocs"
layout: "layouts/base.njk"
---

{% for post in collections.posts | reverse %}
  <p>
    {{ post.data.date }} - {{ post.data.title }}
  </p>
{% endfor %}
A little more progress

Filtering bragdoc entries

To filter certain entries, I used the front matter data to check if the public property was set to True. If the property was set to False, the entry did not appear in the bragdoc.

Similarly, when accessing front matter properties, such as public through a Nunjucks function, I again needed to go through another “data” layer.

---
title: "11ty x Bragdocs"
layout: "layouts/base.njk"
---

{% for post in collections.posts | reverse %}
  {% if post.data.public %}
    <p>
      {{ post.data.date }} - {{ post.data.title }}
    </p>
  {% endif %}
{% endfor %}
The posts are ordered with the title.

Adding custom data filters

By default, the date property renders something that we’re generally unfamiliar with. So, after some research, I found a custom filter written by Phil Hawksworth. To use the filter, I created a file called dates.js and placed it in a new folder called _filters

eleventy-bragdoc
├── src
│   ├── _filters  // new folder
│   │   └── dates.js  // new file
│   ├── posts
│   │   ├── posts.json
│   │   ├── post-1.md
│   │   ├── post-2.md
│   │   └── post-3.md
│   ├── css
│   │   └── styles.css
│   ├── images
│   │   └── test_image.jpg
│   ├── _includes
│   │   └── layouts
│   │       └── base.njk
│   └── index.njk
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

Then, inside dates.js, I added the following:

/*
A date formatter filter for Nunjucks 
Written by Phil Hawksworth
*/
module.exports = function(date, part) {
  var d = new Date(date);
  if(part == 'year') {
    return d.getUTCFullYear();
  }
  var month = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
  ];
  var ordinal = {
    1 : "st",
    2 : "nd",
    3 : "rd",
    21 : "st",
    22 : "nd",
    23 : "rd",
    31 : "st"
  };
  return month[d.getMonth()] + " " + d.getDate() + (ordinal[d.getDate()] || "th") + " " +d.getUTCFullYear();
}

To access the date filter in the project, I added a new filter in .eleventy.js where I can call it using the custom name dateDisplay

module.exports = function (eleventyConfig) {

  // Add filter
  eleventyConfig.addFilter("dateDisplay", require("./src/_filters/dates.js") );
  
  eleventyConfig.addPassthroughCopy("./src/css/")
  eleventyConfig.addPassthroughCopy("./src/images/")
  eleventyConfig.addWatchTarget("./src/css/")
  eleventyConfig.addWatchTarget("./src/images/")

  return {
    dir: {
      input: "src",
      output: "public"
    },
    markdownTemplateEngine: "njk"
  }
}

In index.njk, I assigned the dateDisplay filter to the date variable, rendering it in a human-readable format.

---
title: "11ty x Bragdocs"
layout: "layouts/base.njk"
---

{% for post in collections.posts | reverse %}
  {% if post.data.public %}
    <p>
      {{ post.data.date | dateDisplay }} - {{ post.data.title }}
    </p>
  {% endif %}
{% endfor %}

The server needs to be restarted every time you change something in the configuration file.

The posts with updated date formatting.

To return the body content of a post, I called templateContent and added the safe filter so that it rendered any HTML in the Markdown file rather than escaping it.

---
title: "11ty x Bragdocs"
layout: "layouts/base.njk"
---

{% for post in collections.posts | reverse %}
  {% if post.data.public %}
    <p>
      {{ post.data.date | dateDisplay }} - {{ post.data.title }} 
      <br/>
      {{ post.templateContent | safe }}
    </p>
    <br/>
  {% endif %}
{% endfor %}
The posts with body content.

Finally, I included another for loop to list the values in the categories front matter property.

---
title: "11ty x Bragdocs"
layout: "layouts/base.njk"
---

{% for post in collections.posts | reverse %}
  {% if post.data.public %}
    <p>
      {{ post.data.date | dateDisplay }} - {{ post.data.title }}
      <br/>
      {{ post.templateContent | safe }}
      {% for category in post.data.categories %}
        <span># {{category}}</span>
      {% endfor %}
    </p>
    <br/>
  {% endif %}
{% endfor %}

Having finished extracting data from the posts collection, it was time to build out the HTML structure.

Structuring the bragdoc

Partials in Eleventy allow us to repeatably use bits of HTML or templating. This also simplifies the code from one massive template file to manageable pieces that fit together.

Inside the <body> tags of base.njk , I removed everything except the content and snippet.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ title }}</title>

  <link rel="stylesheet" href="{{ '/css/styles.css' | url }}">
</head>
<body>
  {{ content | safe }}
  <script>
    (function () {
      const links = document.querySelectorAll("a[href^='https://'], a[href^='http://']")
      const host = window.location.hostname

      const isInternalLink = link => new URL(link).hostname === host

      links.forEach(link => {
        if (isInternalLink(link)) return

        link.setAttribute("target", "_blank")
        link.setAttribute("rel", "noopener")
      })
    })()
  </script>
</body>
</html>

Next, I created bragdoc-entry.njk which lives inside a new folder called partials

eleventy-bragdoc
├── src
│   ├── _filters
│   │   └── dates.js
│   ├── posts
│   │   ├── posts.json
│   │   ├── post-1.md
│   │   ├── post-2.md
│   │   └── post-3.md
│   ├── css
│   │   └── styles.css
│   ├── images
│   │   └── test_image.jpg
│   ├── _includes
│   │   ├── partials  // new folder
│   │   │   └── bragdoc-entry.njk  // new file
│   │   └── layouts
│   │       └── base.njk
│   └── index.njk
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

Inside bragdoc-entry.njk, I brought over the content that make up the bragdoc entry, written in index.njk. Notice that it doesn’t require any front matter since it is treated as a snippet.

Partials do not extend a template, so they do not need any front matter.

<p>
  {{ post.data.date | dateDisplay }} - {{ post.data.title }}
  <br/>
  {{ post.templateContent | safe }}
  {% for category in post.data.categories %}
      <span># {{category}}</span>
  {% endfor %}
</p>
<br/>

Then, between the if statement in index.njk, I added an include tag that references the bragdoc-entry.njk partial. By doing this, the content inside bragdoc-entry.njk is repeatably added until the for loop finishes.

---
title: "11ty x Bragdocs"
layout: "layouts/base.njk"
---

{% for post in collections.posts | reverse %}
  {% if post.data.public %}
    {% include 'partials/bragdoc-entry.njk' %}
  {% endif %}
{% endfor %}

Next, I wrapped the entire for loop with some custom HTML, including a header, profile container and footer. At this point, I also included a profile picture in the images folder and referenced it in the custom HTML using Eleventy’s URL filter.

---
title: "11ty x Bragdocs"
layout: "layouts/base.njk"
---

<div class="bragdoc__section" id="bragdoc__section">
<h1 class="bragdoc__header">{{ title }}</h1>
<div class="bragdoc__container">
  <div class="bragdoc__profile">
    <img class="bragdoc__photo" src="{{ '/images/profile_picture.jpg' | url }}">
    <h1 class="bragdoc__name">Emily Y Leung</h1>
    <div class="role">Computational Designer</div>
  </div>
  {% for post in collections.posts | reverse %}
    {% if post.data.public -%}
      {% include 'partials/bragdoc-entry.njk' %}
    {% endif %}
  {% endfor %}
  </div>
  <footer>
    <div><a target="_blank" href="https://www.bragdocs.com/">Bragdocs</a> inspired theme built with <a target="_blank" href="https://www.11ty.dev/">11ty</a></div>
    <div>Made with ♥ by <a target="_blank" href="https://emilyyleung.github.io/">Emily Y Leung</a></div>
  </footer>
</div>

Then, inside bragdoc-entry.njk, I updated the HTML structure and included classes for styling:

<div class="bragdoc__entry">
  <div class="bragdoc__entry-milestone"></div>
  <div class="bragdoc__entry-block">
    <span class="bragdoc__entry-date">
      {{ post.data.date | dateDisplay }}
    </span>
    <br/>
    <h2 class="bragdoc__entry-title"><span class="bragdoc__icon">{{ post.data.icon }}</span> {{ post.data.title }}</h2>
    <div class="bragdoc__entry-content">
        {{ post.templateContent | safe }}
    </div>
  </div>
  <div class="bragdoc__taglist">
  {% for category in post.data.categories %}
    <span># {{category}}</span>
  {% endfor %}
  </div>
</div>

Accessing global data

A good way to understand global data is to imagine building a HTML template that someone could use as a base for their website. Rather than searching for specific HTML tags to replace the text, they only need to replace certain values in an external file which then updates the content. This is one of the many things a global data file can do for us.

Eleventy can access global data files written in JSON when they are placed in a folder called _data. So, I created a data.json file that is accessible when I call {{data}} and then pick out whatever properties I had provided in the JSON object.

eleventy-bragdoc
├── src
│   ├── _data  // new folder
│   │   └── data.json  // new file
│   ├── _filters
│   │   └── dates.js
│   ├── posts
│   │   ├── posts.json
│   │   ├── post-1.md
│   │   ├── post-2.md
│   │   └── post-3.md
│   ├── css
│   │   └── styles.css
│   ├── images
│   │   ├── profile_picture.jpg
│   │   └── test_image.jpg
│   ├── _includes
│   │   ├── partials
│   │   │   └── bragdoc-entry.njk
│   │   └── layouts
│   │       └── base.njk
│   └── index.njk
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

Inside data.json, I included properties that were reused throughout the project:

{
  "mywebsite": "https://emilyyleung.github.io/",
  "myname": "Emily Y Leung",
  "myrole": "Computational Designer"
}

One great use case was to replace the content in the profile and footer in index.njk

<!-- Profile -->
<div class="bragdoc__profile">
  <img class="bragdoc__photo" src="{{ '/images/profile_picture.jpg' | url }}">
  <h1 class="bragdoc__name">{{ data.myname }}</h1>
  <div class="role">{{ data.myrole }}</div>
</div>
<!-- Footer -->
<footer>
  <div><a target="_blank" href="https://www.bragdocs.com/">Bragdocs</a> inspired theme built with <a target="_blank" href="https://www.11ty.dev/">11ty</a></div>
  <div>Made with ♥ by <a target="_blank" href="{{ data.mywebsite }}">{{ data.myname }}</a></div>
</footer>

Styling the bragdoc

With the bragdoc structure completed, I updated the styling in styles.css

To imitate bragdocs.com, I selected some of their colors and stored them in a root variable.

Additionally, I wanted to create multiple themes, so I added a custom data-theme property on top of the :root variable. In this case, the default color theme is “light” regardless of whether data-theme is assigned to the <html> tag. But that also means that if I wanted to create a “dark” theme, I could create a new selector html[data-theme="dark"] in my CSS, and assign alternative colors to the same variables as specified in :root

:root, html[data-theme="light"] {
  --logo: black;
  --name: black;
  --entry-title: black;
  --date: #BDBDBD;
  --text: #676a6c;
  --entry-line: #f1f1f1;
  --entry-circle: #ddd;
  --background: white;
  --text-code: grey;
  --code-block: rgba(0,0,0,0.05);
  --link-text: #676a6c;
  --link-hover: orange;
  --quote-block-edge: rgba(255, 165, 0, 0.5);
  --quote-block-text: #676a6c;
  --table-border: #676a6c;
  --footer: #BDBDBD;
  --tag: #BDBDBD;
}

To reference root variables, call var() where the argument is the name of the property.

Here is an example of how we can use root variables to style the color of text in a <p> tag:

:root {
  --text: teal;
}

p {
  color: var(--text)
}

For fun, I added a dark version inspired by Google Material.

html[data-theme="dark"] {
  --logo: #FFF;
  --name: #FFF;
  --entry-title: #dedede;
  --date: rgba(255,255,255,0.3);
  --text: #999999;
  --entry-line: rgba(255,255,255,0.2);
  --entry-circle: rgba(255,255,255,0.3);
  --background: #121212;
  --code-text: rgba(255,255,255,0.5);
  --code-block: rgba(255,255,255,0.1);
  --link-text: rgba(255,255,255,0.5);
  --link-hover: orange;
  --quote-block-edge: rgb(255, 165, 0);
  --quote-block-text: rgba(255, 165, 0,0.5);
  --table-border: #999999;
  --footer: rgba(255,255,255,0.3);
  --tag: rgba(255,255,255,0.3);
}

To control what theme you want to use, add the data-theme property to the <html> tag in base.njk. From there, assign the value associated to the corresponding CSS selector, i.e. “light” or “dark.”

<!DOCTYPE html>
<html lang="en" data-theme="light">

Next, I added styling to the <body>, <footer>, bragdoc section, and logo.

body {
  font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 13px;
  color: var(--text);
  background-color: var(--background);
  margin: 0;
  height: 100vh;
}

footer {
  margin: 0 auto;
  max-width: 500px;
  padding-bottom: 1.5em;
  text-align: center;
  color: var(--footer);
  padding-top: 2em;
  margin-top: 2em;
}

/* Bragdoc Logo */

.bragdoc__header {
  margin: 0;
  padding: 1em;
  font-size: 1.5em;
  color: var(--logo)
}

/* Bragdoc Body */

.bragdoc__section {
  height: 100%;
  display: grid;
  grid-template-rows: auto 1fr auto;
  margin: 0;
  padding: 0;
}

At this point, the custom tags and classes in the HTML made it simple to replicate the bragdoc layout.

/* Bragdoc User Profile */

.bragdoc__profile {
  padding-top: 3em;
  padding-bottom: 2em;
}

.bragdoc__photo {
  width: 8em;
  border-radius: 100%;
  padding: 0;
  height: 8em;
  object-fit: cover;
}

.bragdoc__name {
  color: var(--name);
  margin-bottom: 0.25em;
}

.bragdoc__icon {
  font-family: "Segoe UI Emoji", Times, serif;
}

.bragdoc__container {
  max-width: 800px;
  margin: 0 0 0 30em;
  height: 100%;
}

.bragdoc__profile-role {
  margin: 0;
}

Next, I styled the entries to replicate the bragdocs.com timeline design.

/* Individual Bragdoc Entry Blocks */

.bragdoc__entry {
  position: relative;
}

.bragdoc__entry:first-child {
  margin-top: 0;
}

.bragdoc__entry:before {
  height: 100%;
  position: absolute;
  background-color: var(--entry-line);
  width: 2px;
  content: "";
  top: 30px;
}

.bragdoc__entry:last-child:before {
  background-color: var(--background);
}

.bragdoc__taglist {
  margin-left: 1em;
  padding: 1em;
}

.bragdoc__taglist > * {
  border: 1px solid var(--tag);
  padding: 0.25em 0.5em 0.25em 0.5em;
  border-radius: 0.5em;
  margin-right: 1em;
}

/* Entry Content */

.bragdoc__entry-block {
  margin-left: 1em;
  padding: 1em;
}

.bragdoc__entry-title {
  margin-top: 4px;
  color: var(--entry-title);
  font-size: 1.5em;
}

.bragdoc__entry-date {
  line-height: 3em;
  color: var(--date);
}

/* Bragdoc milestone circle */

.bragdoc__entry-milestone {
  position: absolute;
  height: 5px;
  width: 5px;
  border: 2px solid var(--entry-circle);
  background-color: var(--background);
  left: 0;
  top: 30px;
  margin-top: -2px;
  margin-left: -3px;
  border-radius: 100px;
}

/* Bragdoc Entry Content */

.bragdoc__entry-content > * {
  margin-bottom: 0.5em;
  margin-left: 0;
}

.bragdoc__entry-content > h1 {
  font-size: 1.15em;
}

.bragdoc__entry-content > h2, h3, h4, h5, h6 {
  font-size: 1em;
  color: var(--text);
}

Using CSS media queries, I could also control the size of text as well as the positioning of HTML elements. This makes it work well when viewed on mobile.

/* Make it responsive */

@media only screen and (max-width: 1400px) {

  .bragdoc__container {
    /* Center the bragdoc*/
    margin: 0 auto;
  }

  .bragdoc__entry-title {
    font-size: 1.25em;
  }
}

@media only screen and (max-width: 870px) {

  .bragdoc__container {
    padding-left: 2em;
    padding-right: 2em;
  }

  .bragdoc__entry-title {
    font-size: 1.15em;
  }
}

The final touches to the design needed to account for the description (i.e. the Markdown body content) in each entry, which you can find in this Gist.

Given that the CSS has been structured with reference to root variables, we can continue to create more themes. Have a crack at exploring color palettes from Color Hunt or Cooolers.

Deploying the bragdoc to GitHub Pages

Building a project from scratch is fantastic, but sharing it with the world is even better!

While there are a myriad of ways to host a bragdoc, I decided to host it on GitHub Pages. This meant I could use the base URL of my GitHub account and add /eleventy-bragdoc/ to the end of it.

At this point, I had been working from the eleventy-bragdoc repository and had already created a gh-pages branch.

Follow this tutorial for information on how to set up GitHub Pages for your repository.

Configuring the URL path

To configure the URL path for deployment, I included a pathPrefix in .eleventy.js to define the route relative to the base URL.

Without specifying a pathPrefix, the value by default is /, which links to the base URL, i.e. https://emilyyleung.github.io/

Since I already had content on the base URL, I wanted to host it on a sub-page, i.e. https://emilyyleung.github.io/eleventy-bragdoc/

To set the pathPrefix for sub-pages, it must start and end with a slash:

module.exports = function (eleventyConfig) {
  // ...
  return {
    dir: {
      input: "src",
      output: "public"
    },
    markdownTemplateEngine: "njk",
    pathPrefix: "/eleventy-bragdoc/"
  }
}

Adding the GitHub Pages dependency

After configuration, I installed GitHub Pages using the terminal:

npm install gh-pages --save-dev

This automatically adds the dependency to package.json

{
  // ...  
  "devDependencies": {
    "gh-pages": "^3.2.3"
  },
  // ...
}

Adding a custom terminal script

To deploy the public folder, I added a deploy script and referenced the public folder:

{
  // ...
  "scripts": {
    "start": "eleventy --serve",
    "build": "eleventy",
    "deploy": "gh-pages -d public"
  }
  // ...
}

Running the build

Just like in development, I navigated my terminal to the eleventy-bragdoc folder. But this time, I ran the following command to rebuild the files into the public folder:

npm run-script build

Then, to deploy to GitHub Pages, I ran the following command:

npm run deploy

Granting access to deploy

At this point, the terminal may ask you to log in via the terminal or through the GitHub Desktop application. If the login fails, the terminal may ask you to generate a token of authentication to use instead of a password. Here is a guide on how to create one.

With a successful response from the terminal, I could see my bragdoc live!

Maintaining your bragdoc

Unlike reports and books, a bragdoc must be maintained continuously as a live record of your progress and achievements. Think of your bragdoc like a garden, where tending requires regular attention and care. While you may not see the benefits straight away, time invested in tending to your document will lead to far greater returns. Instant recall and the ability to share what you’ve done are some of the upsides in forming this habit.

While you may not be able to note down everything as it happens, Julia Evans suggests setting a block of time to review your progress and update the document. Perhaps even making it a bi-weekly group activity to celebrate all wins, big and small.

For many, the less time it takes to do something, the better. With this bragdoc setup, adding new entries and rebuilding the site doesn’t take long at all! Just to give you an idea of how simple this is, I’ll walk you through the process of adding another entry to round out the tutorial.

Add a new bragdoc entry

Continuing from my last deployment, I’ll first add a new Markdown file in my posts folder.

eleventy-bragdoc
├── src
│   ├── _data
│   │   └── data.json
│   ├── _filters
│   │   └── dates.js
│   ├── posts
│   │   ├── posts.json
│   │   ├── post-1.md
│   │   ├── post-2.md
│   │   ├── post-3.md
│   │   └── post-4.md  // new entry goes here
│   ├── css
│   │   └── styles.css
│   ├── images
│   │   ├── profile_picture.jpg
│   │   └── test_image.jpg
│   ├── _includes
│   │   ├── partials
│   │   │   └── bragdoc-entry.njk
│   │   └── layouts
│   │       └── base.njk
│   └── index.njk
├── .eleventy.js
├── node_modules
├── package.json
├── package-lock.json
├── README.md
└── .gitignore

Inside post-4.md, I’ll add in my front matter and description content.

---
title: Working towards publishing my first article on CSS-Tricks
date: 2021-10-02
categories:
  - Writing
  - Eleventy
public: True
icon: ✍🏻
---

Since re-creating [bragdocs.com](https://www.bragdocs.com/) using Eleventy, I am now in the process of writing the steps on how I did it.

Run the build

With the entries added and saved, I’m ready to tell Eleventy to reference my Markdown files from src to generate static HTML files in the public folder. So I navigate the terminal to eleventy-bragdoc where I run the following command:

npm run-script build

Run deploy

Since I’ve already deployed once before, my GitHub credentials should grant me immediate access for deployment when running the following command:

npm run deploy

Those changes are then reflected on my website at the same configured URL.

What’s next?

Well first off, congratulations on putting together your very own bragdoc from scratch! It’s yours to keep, to tend and to share.

While this tutorial has only scratched the surface of what’s possible with Eleventy, a small step can lead you to all sorts of directions. To fuel your curiosity, check out what others are doing with Eleventy.

Feel free to reach out, I’d love to see what you come up with!


The post Creating Your Own Bragdoc With Eleventy appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

how to convert this into a relative path

how to convert this into relative path code, since if im gonna install my system to other computer this line of code will get an error since the path of the data source doesnt match the current path if im gonna install it to other computer

Screenshot_2021-10-20_195847.png

How to Build Customer Loyalty in WordPress with Gamification

Do you want to build customer loyalty in WordPress with gamification?

Gamification lets you reward your loyal customers and users with points and other incentives. This can help to build customer loyalty and increase engagement and conversions on your website.

In this article, we’ll show you how you can build customer loyalty in WordPress with gamification.

How to build customer loyalty in WordPress with gamification (2 ways)

Why Build Customer Loyalty with Gamification in WordPress?

Adding gamification to WordPress lets you reward visitors with different incentives when they take certain actions on your website, like leaving a review, commenting, or buying a product.

This can help to improve customer loyalty, since your customers will feel more engaged with your WordPress website when they’re rewarded with discounts, redeemable points, and more.

Using gamification to reward customer loyalty can be a great way to get more sales in your online store or increase conversions on any type of website.

With that said, let’s show you two different ways you can build customer loyalty with gamification in WordPress. Simply use the quick links below to jump straight to the method you want to use.

Method 1. Add a Gamified Customer Loyalty Program to Your Online Store in WordPress

Before getting started, you need to have a functioning WooCommerce store already set up. If you’re still building your store, then check out our step by step guide on how to start an online store.

You’ll also want to make sure you choose high quality WooCommerce hosting and a professional WooCommerce theme.

After that, you can add a gamified customer loyalty program to your store.

The easiest way to do this is by using the Advanced Coupons plugin. It’s the best coupon code plugin for WordPress used by over 10,000 online stores.

Note: You can purchase the Advanced Coupons and Loyalty Program plugins together by getting the ‘All-In-One Bundle’. At the very least, you need to get the Loyalty Program plugin to create your own loyalty program.

First thing you need to do is install and activate the plugin. For more details, see our beginner’s guide on how to install a WordPress plugin.

Once you’ve done that, you need to go to Coupons » Loyalty Program in your WordPress admin dashboard and then click on the ‘Settings’ menu option.

This brings you to a screen where you can change how much your rewards points are worth.

Loyalty Program plugin settings

You can set the ‘Price to points earned ratio’ where a user will earn 5 points for every dollar spent. The ‘Points to price redeemed ratio’ is how many points it takes to reach a dollar. In this case, 10 points will equal one dollar.

You can also change the name if you want points to be called something else in your store.

Next, click on the ‘Points Earning’ menu option. Here you can choose the settings for how your points will be earned.

Points earning settings

If you scroll down to the ‘Points Amounts’ section, then you can choose the actions that will earn points.

Simply click the toggle for any action you want to earn points.

Point amounts toggle

You can also set the amount of points a user can earn per action on this page as well.

After that, click the ‘Redemption & Expiry’ menu option.

We’ll leave the default settings, but you can customize the minimum number of points before redemption, the expiry time for points, and more.

Redemption and expiry settings

As you make changes to your rewards program, it’ll save automatically, so you don’t have to worry about saving your changes.

Now your users can earn points whenever they purchase items, leave a review, complete their first order, and more.

Earn points example

Method 2. Build Loyalty with Gamified Popups in WordPress

Another way you can add gamification to your WordPress site and reward your loyal customers is with a spin to win popup. You can use this kind of popup to share discounts, special offers, and other perks as a reward.

Here’s how it can look to your visitors:

OptinMonster spin to win popup example

This type of popup can encourage your visitors to take action in your online store or website by enticing customers with an exclusive discount code or deal.

The easiest way to add spin to win and gamified popups to WordPress is with OptinMonster. It’s the best lead generation plugin for WordPress used by over 1.2 million websites.

OptinMonster

For more details, see our guide on how to add spin to win optins in WordPress and WooCommerce.

After you activate and setup the plugin you can fully customize what your gamified popups will look like, and who they will display for.

OptinMonster edit spin the wheel popup

We hope this article helped you learn how to build customer loyalty in WordPress with gamification. You may also want to see our expert picks of the best domain name registrars, and our step by step guide on how to start your own podcast.

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post How to Build Customer Loyalty in WordPress with Gamification appeared first on WPBeginner.

Building The SSG I’ve Always Wanted: An 11ty, Vite And JAM Sandwich

I don’t know about you, but I’ve been overwhelmed by all the web development tools we have these days. Whether you like Markdown, plain HTML, React, Vue, Svelte, Pug templates, Handlebars, Vibranium — you can probably mix it up with some CMS data and get a nice static site cocktail.

I’m not going to tell you which UI development tools to reach for because they’re all great — depending on the needs of your project. This post is about finding the perfect static site generator for any occasion; something that lets us use JS-less templates like markdown to start, and bring in “islands” of component-driven interactivity as needed.

I’m distilling a year’s worth of learnings into a single post here. Not only are we gonna talk code (aka duct-taping 11ty and Vite together), but we’re also going to explore why this approach is so universal to Jamstackian problems. We’ll touch on:

  • Two approaches to static site generation, and why we should bridge the gap;
  • Where templating languages like Pug and Nunjucks still prove useful;
  • When component frameworks like React or Svelte should come into play;
  • How the new, hot-reloading world of Vite helps us bring JS interactivity to our HTML with almost zero configs;
  • How this complements 11ty’s data cascade, bringing CMS data to any component framework or HTML template you could want.

So without further ado, here’s my tale of terrible build scripts, bundler breakthroughs, and spaghetti-code-duct-tape that (eventually) gave me the SSG I always wanted: an 11ty, Vite and Jam sandwich called Slinkity!

A Great Divide In Static Site Generation

Before diving in, I want to discuss what I’ll call two “camps” in static site generation.

In the first camp, we have the “simple” static site generator. These tools don’t bring JavaScript bundles, single-page apps, and any other buzzwords we’ve come to expect. They just nail the Jamstack fundamentals: pull in data from whichever JSON blob of CMS you prefer, and slide that data into plain HTML templates + CSS. Tools like Jekyll, Hugo, and 11ty dominate this camp, letting you turn a directory of markdown and liquid files into a fully-functional website. Key benefits:

  • Shallow learning curve
    If you know HTML, you’re good to go!
  • Fast build times
    We’re not processing anything complex, so each route builds in a snap.
  • Instant time to interactive
    There’s no (or very little) JavaScript to parse on the client.

Now in the second camp, we have the “dynamic” static site generator. These introduce component frameworks like React, Vue, and Svelte to bring interactivity to your Jamstack. These fulfill the same core promise of combining CMS data with your site’s routes at build time. Key benefits:

  • Built for interactivity
    Need an animated image carousel? Multi-step form? Just add a componentized nugget of HTML, CSS, and JS.
  • State management
    Something like React Context of Svelte stores allow seamless data sharing between routes. For instance, the cart on your e-commerce site.

There are distinct pros to either approach. But what if you choose an SSG from the first camp like Jekyll, only to realize six months into your project that you need some component-y interactivity? Or you choose something like NextJS for those powerful components, only to struggle with the learning curve of React, or needless KB of JavaScript on a static blog post?

Few projects squarely fit into one camp or the other in my opinion. They exist on a spectrum, constantly favoring new feature sets as a project’s need evolve. So how do we find a solution that lets us start with the simple tools of the first camp, and gradually add features from the second when we need them?

Well, let’s walk through my learning journey for a bit.

Note: If you’re already sold on static templating with 11ty to build your static sites, feel free to hop down to the juicy code walkthrough. 😉

Going From Components To Templates And Web APIs

Back in January 2020, I set out to do what just about every web developer does each year: rebuild my personal site. But this time was gonna be different. I challenged myself to build a site with my hands tied behind my back, no frameworks or build pipelines allowed!

This was no simple task as a React devotee. But with my head held high, I set out to build my own build pipeline from absolute ground zero. There’s a lot of poorly-written code I could share from v1 of my personal site... but I’ll let you click this README if you’re so brave. 😉 Instead, I want to focus on the higher-level takeaways I learned starving myself of my JS guilty pleasures.

Templates Go A Lot Further Than You Might Think

I came at this project a recovering JavaScript junky. There are a few static-site-related needs I loved using component-based frameworks to fill:

  1. We want to break down my site into reusable UI components that can accept JS objects as parameters (aka “props”).
  2. We need to fetch some information at build time to slap into a production site.
  3. We need to generate a bunch of URL routes from either a directory of files or a fat JSON object of content.

List taken from this post on my personal blog.

But you may have noticed... none of these really need clientside JavaScript. Component frameworks like React are mainly built to handle state management concerns, like the Facebook web app inspiring React in the first place. If you’re just breaking down your site into bite-sized components or design system elements, templates like Pug work pretty well too!

Take this navigation bar for instance. In Pug, we can define a “mixin” that receives data as props:

// nav-mixins.pug
mixin NavBar(links)
    // pug's version of a for loop
    each link in links
        a(href=link.href) link.text

Then, we can apply that mixin anywhere on our site.

// index.pug
// kinda like an ESM "import"
include nav-mixins.pug
html
  body
    +NavBar(navLinksPassedByJS)
    main
      h1 Welcome to my pug playground 🐶

If we “render” this file with some data, we’ll get a beautiful index.html to serve up to our users.

const html = pug.render('/index.pug', { navLinksPassedByJS: [
    { href: '/', text: 'Home' },
    { href: '/adopt', text: 'Adopt a Pug' }
] })
// use the NodeJS filesystem helpers to write a file to our build
await writeFile('build/index.html', html)

Sure, this doesn’t give niceties like scoped CSS for your mixins, or stateful JavaScript where you want it. But it has some very powerful benefits over something like React:

  1. We don’t need fancy bundlers we don’t understand.
    We just wrote that pug.render call by hand, and we already have the first route of a site ready-to-deploy.
  2. We don’t ship any JavaScript to the end-user.
    Using React often means sending a big ole runtime for people’s browsers to run. By calling a function like pug.render at build time, we keep all the JS on our side while sending a clean .html file at the end.

This is why I think templates are a great “base” for static sites. Still, being able to reach for component frameworks where we really benefit from them would be nice. More on that later. 🙃

Recommended Reading: How To Create Better Angular Templates With Pug by Zara Cooper

You Don’t Need A Framework To Build Single Page Apps

While I was at it, I also wanted some sexy page transitions on my site. But how do we pull off something like this without a framework?

Crossfade with vertical wipe transition. (Large preview)

Well, we can’t do this if every page is its own .html file. The whole browser refreshes when we jump from one HTML file to the other, so we can’t have that nice cross-fade effect (since we’d briefly show both pages on top of each other).

We need a way to “fetch” the HTML and CSS for wherever we’re navigating to, and animate it into view using JavaScript. This sounds like a job for single-page apps! I used a simple browser API medley for this:

  1. Intercept all your link clicks using an event listener.
  2. fetch API: Fetch all the resources for whatever page you want to visit, and grab the bit I want to animate into view: the content outside the navbar (which I want to remain stationary during the animation).
  3. web animations API: Animate the new content into view as a keyframe.
  4. history API: Change the route displaying in your browser’s URL bar using window.history.pushState({}, 'new-route'). Otherwise, it looks like you never left the previous page!

For clarity, here’s a visual illustration of that single page app concept using a simple find-and-replace (source article):

Step-by-step clientside routing process: 1. Medium rare hamburger is returned, 2. We request a well done burger using the fetch API, 3. We massage the response, 4. We pluck out the 'patty' element and apply it to our current page. (Large preview)

Note: You can also visit the source code from my personal site.

Sure, some pairing of React et al and your animation library of choice can do this. But for a use case as simple as a fade transition... web APIs are pretty dang powerful on their own. And if you want more robust page transitions on static templates like Pug or plain HTML, libraries like Swup will serve you well.

What 11ty Brought To The Table

I was feeling pretty good about my little SSG at this point. Sure it couldn’t fetch any CMS data at build-time, and didn’t support different layouts by page or by directory, and didn’t optimize my images, and didn’t have incremental builds.

Okay, I might need some help.

Given all my learnings from v1, I thought I earned my right to drop the “no third-party build pipelines” rule and reach for existing tools. Turns out, 11ty has a treasure trove of features I need!

If you’ve tried out bare-bones SSGs like Jekyll or Hugo, you should have a pretty good idea of how 11ty works. Only difference? 11ty uses JavaScript through-and-through.

11ty supports basically every template library out there, so it was happy to render all my Pug pages to .html routes. It’s layout chaining option helped with my foe-single-page-app setup too. I just needed a single script for all my routes, and a “global” layout to import that script:

// _includes/base-layout.html
<html>
<body>
  <!--load every page's content between some body tags-->
  {{ content }}
  <!--and apply the script tag just below this-->
  <script src="main.js"></script>
</body>
</html>

// random-blog-post.pug
---
layout: base-layout
---

article
  h2 Welcome to my blog
  p Have you heard the story of Darth Plagueis the Wise?

As long as that main.js does all that link intercepting we explored, we have page transitions!

Oh, And The Data Cascade

So 11ty helped clean up all my spaghetti code from v1. But it brought another important piece: a clean API to load data into my layouts. This is the bread and butter of the Jamstack approach. Instead of fetching data in the browser with JavaScript + DOM manipulation, you can:

  1. Fetch data at build-time using Node.
    This could be a call to some external API, a local JSON or YAML import, or even the content of other routes on your site (imagine updating a table-of-contents whenever new routes are added 🙃).
  2. Slot that data into your routes. Recall that .render function we wrote earlier:
const html = pug.render('/index.pug', { navLinksPassedByJS: [
    { href: '/', text: 'Home' },
    { href: '/adopt', text: 'Adopt a Pug' }
] })

...but instead of calling pug.render with our data every time, we let 11ty do this behind-the-scenes.

Sure, I didn’t have a lot of data for my personal site. But it felt great to whip up a .yaml file for all my personal projects:

# _data/works.yaml
- title: Bits of Good Homepage
  hash: bog-homepage
  links:
    - href: https://bitsofgood.org
      text: Explore the live site
    - href: https://github.com/GTBitsOfGood/bog-web
      text: Scour the Svelt-ified codebase
  timeframe: May 2019 - present
  tags:
    - JAMstack
    - SvelteJS
- title: Dolphin Audio Visualizer
...

And access that data across any template:

// home.pug
.project-carousel
  each work in works
    h3 #{title}
    p #{timeframe}
    each tag in tags
    ...

Coming from the world of “clientside rendering” with create-react-app, this was a pretty big revelation. No more sending API keys or big JSON blobs to the browser. 😁

I also added some goodies for JavaScript fetching and animation improvements over version 1 of my site. If you’re curious, here’s where my README stood at this point.

I Was Happy At This Point But Something Was Missing

I went surprisingly far by abandoning JS-based components and embracing templates (with animated page transitions to boot). But I know this won’t satisfy my needs forever. Remember that great divide I kicked us off with? Well, there’s clearly still that ravine between my build setup (firmly in camp #1) and the haven of JS-ified interactivity (the Next, SvelteKit, and more of camp #2). Say I want to add:

  • a pop-up modal with an open/close toggle,
  • a component-based design system like Material UI, complete with scoped styling,
  • a complex multi-step form, maybe driven by a state machine.

If you’re a plain-JS-purist, you probably have framework-less answers to all those use cases. 😉 But there’s a reason JQuery isn’t the norm anymore! There’s something appealing about creating discrete, easy-to-read components of HTML, scoped styles, and pieces of JavaScript “state” variables. React, Vue, Svelte, etc. offer so many niceties for debugging and testing that straight DOM manipulation can’t quite match.

So here’s my million dollar question:

Can we use straight HTML templates to start, and gradually add React/Vue/Svelte components where we want them?

The answer is yes. Let’s try it.

11ty + Vite: A Match Made In Heaven ❤️

Here's the dream that I’m imagining here. Wherever I want to insert something interactive, I want to leave a little flag in my template to “put X React component here.” This could be the shortcode syntax that 11ty supports:

# Super interesting programming tutorial

Writing paragraphs has been fun, but that's no way to learn. Time for an interactive code example!

{% react './components/FancyLiveDemo.jsx' %}

But remember, the one-piece 11ty (purposely) avoids: a way to bundle all your JavaScript. Coming from the OG guild of bundling, your brain probably jumps to building Webpack, Rollup, or Babel processes here. Build a big ole entry point file, and output some beautiful optimized code right?

Well yes, but this can get pretty involved. If we’re using React components, for instance, we’ll probably need some loaders for JSX, a fancy Babel process to transform everything, an interpreter for SASS and CSS module imports, something to help with live reloading, and so on.

If only there were a tool that could just see our .jsx files and know exactly what to do with them.

Enter: Vite

Vite’s been the talk of the town as of late. It’s meant to be the all-in-one tool for building just about anything in JavaScript. Here’s an example for you to try at home. Let’s make an empty directory somewhere on our machine and install some dependencies:

npm init -y # Make a new package.json with defaults set
npm i vite react react-dom # Grab Vite + some dependencies to use React

Now, we can make an index.html file to serve as our app’s “entry point.” We’ll keep it pretty simple:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>Hello Vite! (wait is it pronounced "veet" or "vight"...)</h1>
  <div id="root"></div>
</body>
</html>

The only interesting bit is that div id="root" in the middle. This will be the root of our React component in a moment!

If you want, you can fire up the Vite server to see our plain HTML file in your browser. Just run vite (or npx vite if the command didn’t get configured in your terminal), and you’ll see this helpful output:

vite vX.X.X dev server running at:

> Local: http://localhost:3000/
> Network: use `--host` to expose

ready in Xms.

Much like Browsersync or other popular dev servers, the name of each .html file corresponds to a route on our server. So if we renamed index.html to about.html, we would visit http://localhost:3000/about/ (yes, you’ll need a trailing slash!)

Now let’s do something interesting. Alongside that index.html file, add a basic React component of some sort. We’ll use React’s useState here to demonstrate interactivity:

// TimesWeMispronouncedVite.jsx
import React from 'react'

export default function TimesWeMispronouncedVite() {
  const [count, setCount] = React.useState(0)
  return (
    <div>
      <p>I've said Vite wrong {count} times today</p>
      <button onClick={() => setCount(count + 1)}>Add one</button>
    </div>
  )
}

Now, let’s load that component onto our page. This is all we have to add to our index.html:

<!DOCTYPE html>
...
<body>
  <h1>Hello Vite! (wait is it pronounced "veet" or "vight"...)</h1>
  <div id="root"></div>
  <!--Don't forget type="module"! This lets us use ES import syntax in the browser-->
  <script type="module">
    // path to our component. Note we still use .jsx here!
    import Component from './TimesWeMispronouncedVite.jsx';
    import React from 'react';
    import ReactDOM from 'react-dom';
    const componentRoot = document.getElementById('root');
    ReactDOM.render(React.createElement(Component), componentRoot);
  </script>
</body>
</html>

Yep, that’s it. No need to transform our .jsx file to a browser-ready .js file ourselves! Wherever Vite sees a .jsx import, it’ll auto-convert that file to something browsers can understand. There isn’t even a dist or build folder when working in development; Vite processes everything on the fly — complete with hot module reloading every time we save our changes. 🤯

Okay, so we have an incredibly capable build tool. How can we bring this to our 11ty templates?

Running Vite Alongside 11ty

Before we jump into the good stuff, let’s discuss running 11ty and Vite side-by-side. Go ahead and install 11ty as a dev dependency into the same project directory from last section:

npm i -D @11ty/eleventy # yes, it really is 11ty twice

Now let’s do a little pre-flight check to see if 11ty’s working. To avoid any confusion, I’d suggest you:

  1. Delete that index.html file from earlier;
  2. Move that TimesWeMispronouncedVite.jsx inside a new directory. Say, components/;
  3. Create a src folder for our website to live in;
  4. Add a template to that src directory for 11ty to process.

For example, a blog-post.md file with the following contents:

# Hello world! It’s markdown here

Your project structure should look something like this:

src/
  blog-post.md
components/
  TimesWeMispronouncedVite.jsx

Now, run 11ty from your terminal like so:

npx eleventy --input=src

If all goes well, you should see an build output like this:

_site/
  blog-post/
    index.html

Where _site is our default output directory, and blog-post/index.html is our markdown file beautifully converted for browsing.

Normally, we’d run npx eleventy --serve to spin up a dev server and visit that /blog-post page. But we’re using Vite for our dev server now! The goal here is to:

  1. Have eleventy build our markdown, Pug, nunjucks, and more to the _site directory.
  2. Point Vite at that same _site directory so it can process the React components, fancy style imports, and other things that 11ty didn’t pick up.

So a two-step build process, with 11ty handing off the Vite. Here’s the CLI command you’ll need to start 11ty and Vite in “watch” mode simultaneously:

(npx eleventy --input=src --watch) & npx vite _site

You can also run these commands in two separate terminals for easier debugging. 😄

With any luck, you should be able to visit http://localhost:3000/blog-post/ (again, don’t forget the trailing slash!) to see that processed Markdown file.

Partial Hydration With Shortcodes

Let’s do a brief rundown on shortcodes. Time to revisit that syntax from earlier:

{% react '/components/TimesWeMispronouncedVite.jsx' %}

For those unfamiliar with shortcodes: they’re about the same as a function call, where the function returns a string of HTML to slide into your page. The “anatomy” of our shortcode is:

  • {% … %}
    Wrapper denoting the start and end of the shortcode.
  • react
    The name of our shortcode function we’ll configure in a moment.
  • '/components/TimesWeMispronouncedVite.jsx'
    The first (and only) argument to our shortcode function. You can have as many arguments as you’d like.

Let’s wire up our first shortcode! Add a .eleventy.js file to the base of your project, and add this config entry for our react shortcode:

// .eleventy.js, at the base of the project
module.exports = function(eleventyConfig) {
  eleventyConfig.addShortcode('react', function(componentPath) {
   // return any valid HTML to insert
   return `<div id="root">This is where we'll import ${componentPath}</div>`
  })

  return {
    dir: {
      // so we don't have to write `--input=src` in our terminal every time!
      input: 'src',
    }
  }
}

Now, let’s spice up our blog-post.md with our new shortcode. Paste this content into our markdown file:

# Super interesting programming tutorial

Writing paragraphs has been fun, but that's no way to learn. Time for an interactive code example!

{% react '/components/TimesWeMispronouncedVite.jsx' %}

And if you run a quick npx eleventy, you should see this output in your _site directory under /blog-post/index.html:

<h1>Super interesting programming tutorial</h1>

<p>Writing paragraphs has been fun, but that's no way to learn. Time for an interactive code example!</p>

<div id="root">This is where we'll import /components/TimesWeMispronouncedVite.jsx</div>

Writing Our Component Shortcode

Now let’s do something useful with that shortcode. Remember that script tag we wrote while trying out Vite? Well, we can do the same thing in our shortcode! This time we’ll use the componentPath argument to generate the import, but keep the rest pretty much the same:

// .eleventy.js
module.exports = function(eleventyConfig) {
  let idCounter = 0;
  // copy all our /components to the output directory
  // so Vite can find them. Very important step!
  eleventyConfig.addPassthroughCopy('components')

  eleventyConfig.addShortcode('react', function (componentPath) {
      // we'll use idCounter to generate unique IDs for each "root" div
      // this lets us use multiple components / shortcodes on the same page 👍
      idCounter += 1;
      const componentRootId = `component-root-${idCounter}`
      return `
  <div id="${componentRootId}"></div>
  <script type="module">
    // use JSON.stringify to
    // 1) wrap our componentPath in quotes
    // 2) strip any invalid characters. Probably a non-issue, but good to be cautious!
    import Component from ${JSON.stringify(componentPath)};
    import React from 'react';
    import ReactDOM from 'react-dom';
    const componentRoot = document.getElementById('${componentRootId}');
    ReactDOM.render(React.createElement(Component), componentRoot);
  </script>
      `
    })

  eleventyConfig.on('beforeBuild', function () {
    // reset the counter for each new build
    // otherwise, it'll count up higher and higher on every live reload
    idCounter = 0;
  })

  return {
    dir: {
      input: 'src',
    }
  }
}

Now, a call to our shortcode (ex. {% react '/components/TimesWeMispronouncedVite.jsx' %}) should output something like this:

<div id="component-root-1"></div>
<script type="module">
    import Component from './components/FancyLiveDemo.jsx';
    import React from 'react';
    import ReactDOM from 'react-dom';
    const componentRoot = document.getElementById('component-root-1');
    ReactDOM.render(React.createElement(Component), componentRoot);
</script>

Visiting our dev server using (npx eleventy --watch) & vite _site, we should find a beautifully clickable counter element. ✨

Buzzword Alert — Partial Hydration And Islands Architecture

We just demonstrated “islands architecture” in its simplest form. This is the idea that our interactive component trees don’t have to consume the entire website. Instead, we can spin up mini-trees, or “islands,” throughout our app depending on where we actually need that interactivity. Have a basic landing page of links without any state to manage? Great! No need for interactive components. But do you have a multi-step form that could benefit from X React library? No problem. Use techniques like that react shortcode to spin up a Form.jsx island.

This goes hand-in-hand with the idea of “partial hydration.” You’ve likely heard the term “hydration” if you work with component-y SSGs like NextJS or Gatsby. In short, it’s a way to:

  1. Render your components to static HTML first.
    This gives the user something to view when they initially visit your website.
  2. “Hydrate” this HTML with interactivity.
    This is where we hook up our state hooks and renderers to, well, make button clicks actually trigger something.

This 1-2 punch makes JS-driven frameworks viable for static sites. As long as the user has something to view before your JavaScript is done parsing, you’ll get a decent score on those lighthouse metrics.

Well, until you don’t. 😢 It can be expensive to “hydrate” an entire website since you’ll need a JavaScript bundle ready to process every last DOM element. But our scrappy shortcode technique doesn’t cover the entire page! Instead, we “partially” hydrate the content that’s there, inserting components only where necessary.

Don’t Worry, There’s A Plugin For All This: Slinkity

Let’s recap what we discovered here:

  1. Vite is an incredibly capable bundler that can process most file types (jsx, vue, and svelte to name a few) without extra config.
  2. Shortcodes are an easy way to insert chunks of HTML into our templates, component-style.
  3. We can use shortcodes to render dynamic, interactive JS bundles wherever we want using partial hydration.

So what about optimized production builds? Properly loading scoped styles? Heck, using .jsx to create entire pages? Well, I’ve bundled all of this (and a whole lot more!) into a project called Slinkity. I’m excited to see the warm community reception to the project, and I’d love for you, dear reader, to give it a spin yourself!

🚀 Try the quick start guide

Astro’s Pretty Great Too

Readers with their eyes on cutting-edge tech probably thought about Astro at least once by now. 😉 And I can’t blame you! It’s built with a pretty similar goal in mind: start with plain HTML, and insert stateful components wherever you need them. Heck, they’ll even let you start writing React components inside Vue or Svelte components inside HTML template files! It’s like MDX Xtreme edition. 🤯

There’s one pretty major cost to their approach though: you need to rewrite your app from scratch. This means a new template format based on JSX (which you might not be comfortable with), a whole new data pipeline that’s missing a couple of niceties right now, and general bugginess as they work out the kinks.

But spinning up an 11ty + Vite cocktail with a tool like Slinkity? Well, if you already have an 11ty site, Vite should bolt into place without any rewrites, and shortcodes should cover many of the same use cases as .astro files. I’ll admit it’s far from perfect right now. But hey, it’s been useful so far, and I think it’s a pretty strong alternative if you want to avoid site-wide rewrites!

Wrapping Up

This Slinkity experiment has served my needs pretty well so far (and a few of y’all’s too!). Feel free to use whatever stack works for your JAM. I’m just excited to share the results of my year of build tool debauchery, and I’m so pumped to see how we can bridge the great Jamstack divide.

Further Reading

Want to dive deeper into partial hydration, or ESM, or SSGs in general? Check these out:

  • Islands Architecture
    This blog post from Jason Format really kicked off a discussion of “islands” and “partial hydration” in web development. It’s chock-full of useful diagrams and the philosophy behind the idea.
  • Simplify your static with a custom-made static site generator
    Another SmashingMag article that walks you through crafting Node-based website builders from scratch. It was a huge inspiration to me!
  • How ES Modules have redefined web development
    A personal post on how ES Modules have changed the web development game. This dives a little further into the “then and now” of import syntax on the web.
  • An introduction to web components
    An excellent walkthrough on what web components are, how the shadow DOM works, and where web components prove useful. Used this guide to apply custom components to my own framework!

Repetitive Typography Animation

After using a typography animation as page transition, my next endeavour incorporating kinetic typography ideas into a web design, is based on this fantastic animation by Domagoj Štrok for HOLOGRAPHIK®:

This is actually a really interesting challenge: take a fun typography animation like this and try making it part of a web design! Challenge accepted 🙂

The aim was to duplicate the words just like in the video and then, at a specific point, use the result as part of the design.

So, here is the whole flow of the animation:

Okay, this was just for fun really, so don’t take this concept too seriously! It’s not really usable but I hope it serves as an inspiration for what can be done. Keep in mind that this is only a mockup (not scrollable, not responsive) that serves as a proof-of-concept.

I hope you enjoy it and let me know what you think via Twitter @crnacura or @codrops.

The post Repetitive Typography Animation appeared first on Codrops.