Tag: Rendering

Rendering External API Data in WordPress Blocks on the Back End

This is a continuation of my last article about “Rendering External API Data in WordPress Blocks on the Front End”. In that last one, we learned how to take an external API and integrate it with a block that renders the fetched data on the front end of a WordPress site.

The thing is, we accomplished this in a way that prevents us from seeing the data in the WordPress Block Editor. In other words, we can insert the block on a page but we get no preview of it. We only get to see the block when it’s published.

Let’s revisit the example block plugin we made in the last article. Only this time, we’re going to make use of the JavaScript and React ecosystem of WordPress to fetch and render that data in the back-end Block Editor as well.

Where we left off

As we kick this off, here’s a demo where we landed in the last article that you can reference. You may have noticed that I used a render_callback method in the last article so that I can make use of the attributes in the PHP file and render the content.

Well, that may be useful in situations where you might have to use some native WordPress or PHP function to create dynamic blocks. But if you want to make use of just the JavaScript and React (JSX, specifically) ecosystem of WordPress to render the static HTML along with the attributes stored in the database, you only need to focus on the Edit and Save functions of the block plugin.

  • The Edit function renders the content based on what you want to see in the Block Editor. You can have interactive React components here.
  • The Save function renders the content based on what you want to see on the front end. You cannot have the the regular React components or the hooks here. It is used to return the static HTML that is saved into your database along with the attributes.

The Save function is where we’re hanging out today. We can create interactive components on the front-end, but for that we need to manually include and access them outside the Save function in a file like we did in the last article.

So, I am going to cover the same ground we did in the last article, but this time you can see the preview in the Block Editor before you publish it to the front end.

The block props

I intentionally left out any explanations about the edit function’s props in the last article because that would have taken the focus off of the main point, the rendering.

If you are coming from a React background, you will likely understand what is that I am talking about, but if you are new to this, I would recommend checking out components and props in the React documentation.

If we log the props object to the console, it returns a list of WordPress functions and variables related to our block:

Console log of the block properties.

We only need the attributes object and the setAttributes function which I am going to destructure from the props object in my code. In the last article, I had modified RapidAPI’s code so that I can store the API data through setAttributes(). Props are only readable, so we are unable to modify them directly.

Block props are similar to state variables and setState in React, but React works on the client side and setAttributes() is used to store the attributes permanently in the WordPress database after saving the post. So, what we need to do is save them to attributes.data and then call that as the initial value for the useState() variable.

The edit function

I am going to copy-paste the HTML code that we used in football-rankings.php in the last article and edit it a little to shift to the JavaScript background. Remember how we created two additional files in the last article for the front end styling and scripts? With the way we’re approaching things today, there’s no need to create those files. Instead, we can move all of it to the Edit function.

Full code
import { useState } from "@wordpress/element"; export default function Edit(props) {   const { attributes, setAttributes } = props;   const [apiData, setApiData] = useState(null);     function fetchData() {       const options = {         method: "GET",         headers: {           "X-RapidAPI-Key": "Your Rapid API key",           "X-RapidAPI-Host": "api-football-v1.p.rapidapi.com",         },       };       fetch(         "https://api-football-v1.p.rapidapi.com/v3/standings?season=2021&league=39",           options       )       .then((response) => response.json())       .then((response) => {         let newData = { ...response }; // Deep clone the response data         setAttributes({ data: newData }); // Store the data in WordPress attributes         setApiData(newData); // Modify the state with the new data       })       .catch((err) => console.error(err));     }     return (       <div {...useBlockProps()}>         <button onClick={() => getData()}>Fetch data</button>         {apiData && (           <>           <div id="league-standings">             <div               className="header"               style={{                 backgroundImage: `url($ {apiData.response[0].league.logo})`,               }}             >               <div className="position">Rank</div>               <div className="team-logo">Logo</div>               <div className="team-name">Team name</div>               <div className="stats">                 <div className="games-played">GP</div>                 <div className="games-won">GW</div>                 <div className="games-drawn">GD</div>                 <div className="games-lost">GL</div>                 <div className="goals-for">GF</div>                 <div className="goals-against">GA</div>                 <div className="points">Pts</div>               </div>               <div className="form-history">Form history</div>             </div>             <div className="league-table">               {/* Usage of [0] might be weird but that is how the API structure is. */}               {apiData.response[0].league.standings[0].map((el) => {                                  {/* Destructure the required data from all */}                 const { played, win, draw, lose, goals } = el.all;                   return (                     <>                     <div className="team">                       <div class="position">{el.rank}</div>                       <div className="team-logo">                         <img src={el.team.logo} />                       </div>                       <div className="team-name">{el.team.name}</div>                       <div className="stats">                         <div className="games-played">{played}</div>                         <div className="games-won">{win}</div>                         <div className="games-drawn">{draw}</div>                         <div className="games-lost">{lose}</div>                         <div className="goals-for">{goals.for}</div>                         <div className="goals-against">{goals.against}</div>                         <div className="points">{el.points}</div>                       </div>                       <div className="form-history">                         {el.form.split("").map((result) => {                           return (                             <div className={`result-$ {result}`}>{result}</div>                           );                         })}                       </div>                     </div>                     </>                   );                 }               )}             </div>           </div>         </>       )}     </div>   ); }

I have included the React hook useState() from @wordpress/element rather than using it from the React library. That is because if I were to load the regular way, it would download React for every block that I am using. But if I am using @wordpress/element it loads from a single source, i.e., the WordPress layer on top of React.

This time, I have also not wrapped the code inside useEffect() but inside a function that is called only when clicking on a button so that we have a live preview of the fetched data. I have used a state variable called apiData to render the league table conditionally. So, once the button is clicked and the data is fetched, I am setting apiData to the new data inside the fetchData() and there is a rerender with the HTML of the football rankings table available.

You will notice that once the post is saved and the page is refreshed, the league table is gone. That is because we are using an empty state (null) for apiData‘s initial value. When the post saves, the attributes are saved to the attributes.data object and we call it as the initial value for the useState() variable like this:

const [apiData, setApiData] = useState(attributes.data);

The save function

We are going to do almost the same exact thing with the save function, but modify it a little bit. For example, there’s no need for the “Fetch data” button on the front end, and the apiData state variable is also unnecessary because we are already checking it in the edit function. But we do need a random apiData variable that checks for attributes.data to conditionally render the JSX or else it will throw undefined errors and the Block Editor UI will go blank.

Full code
export default function save(props) {   const { attributes, setAttributes } = props;   let apiData = attributes.data;   return (     <>       {/* Only render if apiData is available */}       {apiData && (         <div {...useBlockProps.save()}>         <div id="league-standings">           <div             className="header"             style={{               backgroundImage: `url($ {apiData.response[0].league.logo})`,             }}           >             <div className="position">Rank</div>             <div className="team-logo">Logo</div>             <div className="team-name">Team name</div>             <div className="stats">               <div className="games-played">GP</div>               <div className="games-won">GW</div>               <div className="games-drawn">GD</div>               <div className="games-lost">GL</div>               <div className="goals-for">GF</div>               <div className="goals-against">GA</div>               <div className="points">Pts</div>             </div>             <div className="form-history">Form history</div>           </div>           <div className="league-table">             {/* Usage of [0] might be weird but that is how the API structure is. */}             {apiData.response[0].league.standings[0].map((el) => {               const { played, win, draw, lose, goals } = el.all;                 return (                   <>                   <div className="team">                     <div className="position">{el.rank}</div>                       <div className="team-logo">                         <img src={el.team.logo} />                       </div>                       <div className="team-name">{el.team.name}</div>                       <div className="stats">                         <div className="games-played">{played}</div>                         <div className="games-won">{win}</div>                         <div className="games-drawn">{draw}</div>                         <div className="games-lost">{lose}</div>                         <div className="goals-for">{goals.for}</div>                         <div className="goals-against">{goals.against}</div>                         <div className="points">{el.points}</div>                       </div>                       <div className="form-history">                         {el.form.split("").map((result) => {                           return (                             <div className={`result-$ {result}`}>{result}</div>                           );                         })}                       </div>                     </div>                   </>                 );               })}             </div>           </div>         </div>       )}     </>   ); }

If you are modifying the save function after a block is already present in the Block Editor, it would show an error like this:

The football rankings block in the WordPress block Editor with an error message that the block contains an unexpected error.

That is because the markup in the saved content is different from the markup in our new save function. Since we are in development mode, it is easier to remove the bock from the current page and re-insert it as a new block — that way, the updated code is used instead and things are back in sync.

This situation of removing it and adding it again can be avoided if we had used the render_callback method since the output is dynamic and controlled by PHP instead of the save function. So each method has it’s own advantages and disadvantages.

Tom Nowell provides a thorough explanation on what not to do in a save function in this Stack Overflow answer.

Styling the block in the editor and the front end

Regarding the styling, it is going to be almost the same thing we looked at in the last article, but with some minor changes which I have explained in the comments. I’m merely providing the full styles here since this is only a proof of concept rather than something you want to copy-paste (unless you really do need a block for showing football rankings styled just like this). And note that I’m still using SCSS that compiles to CSS on build.

Editor styles
/* Target all the blocks with the data-title="Football Rankings" */ .block-editor-block-list__layout  .block-editor-block-list__block.wp-block[data-title="Football Rankings"] {   /* By default, the blocks are constrained within 650px max-width plus other design specific code */   max-width: unset;   background: linear-gradient(to right, #8f94fb, #4e54c8);   display: grid;   place-items: center;   padding: 60px 0;    /* Button CSS - From: https://getcssscan.com/css-buttons-examples - Some properties really not needed :) */   button.fetch-data {     align-items: center;     background-color: #ffffff;     border: 1px solid rgb(0 0 0 / 0.1);     border-radius: 0.25rem;     box-shadow: rgb(0 0 0 / 0.02) 0 1px 3px 0;     box-sizing: border-box;     color: rgb(0 0 0 / 0.85);     cursor: pointer;     display: inline-flex;     font-family: system-ui, -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif;     font-size: 16px;     font-weight: 600;     justify-content: center;     line-height: 1.25;     margin: 0;     min-height: 3rem;     padding: calc(0.875rem - 1px) calc(1.5rem - 1px);     position: relative;     text-decoration: none;     transition: all 250ms;     user-select: none;     -webkit-user-select: none;     touch-action: manipulation;     vertical-align: baseline;     width: auto;     &:hover,     &:focus {       border-color: rgb(0, 0, 0, 0.15);       box-shadow: rgb(0 0 0 / 0.1) 0 4px 12px;       color: rgb(0, 0, 0, 0.65);     }     &:hover {       transform: translateY(-1px);     }     &:active {       background-color: #f0f0f1;       border-color: rgb(0 0 0 / 0.15);       box-shadow: rgb(0 0 0 / 0.06) 0 2px 4px;       color: rgb(0 0 0 / 0.65);       transform: translateY(0);     }   } }
Front-end styles
/* Front-end block styles */ .wp-block-post-content .wp-block-football-rankings-league-table {   background: linear-gradient(to right, #8f94fb, #4e54c8);   max-width: unset;   display: grid;   place-items: center; }  #league-standings {   width: 900px;   margin: 60px 0;   max-width: unset;   font-size: 16px;   .header {     display: grid;     gap: 1em;     padding: 10px;     grid-template-columns: 1fr 1fr 3fr 4fr 3fr;     align-items: center;     color: white;     font-size: 16px;     font-weight: 600;     background-color: transparent;     background-repeat: no-repeat;     background-size: contain;     background-position: right;      .stats {       display: flex;       gap: 15px;       &amp; &gt; div {         width: 30px;       }     }   } } .league-table {   background: white;   box-shadow:     rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,     rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;   padding: 1em;   .position {     width: 20px;   }   .team {     display: grid;     gap: 1em;     padding: 10px 0;     grid-template-columns: 1fr 1fr 3fr 4fr 3fr;     align-items: center;   }   .team:not(:last-child) {     border-bottom: 1px solid lightgray;   }   .team-logo img {     width: 30px;     top: 3px;     position: relative;   }   .stats {     display: flex;     gap: 15px;     &amp; &gt; div {       width: 30px;       text-align: center;     }   }   .last-5-games {     display: flex;     gap: 5px;     &amp; &gt; div {       width: 25px;       height: 25px;       text-align: center;       border-radius: 3px;       font-size: 15px;     &amp; .result-W {       background: #347d39;       color: white;     }     &amp; .result-D {       background: gray;       color: white;     }     &amp; .result-L {       background: lightcoral;       color: white;     }   } } 

We add this to src/style.scss which takes care of the styling in both the editor and the frontend. I will not be able to share the demo URL since it would require editor access but I have a video recorded for you to see the demo:


Pretty neat, right? Now we have a fully functioning block that not only renders on the front end, but also fetches API data and renders right there in the Block Editor — with a refresh button to boot!

But if we want to take full advantage of the WordPress Block Editor, we ought to consider mapping some of the block’s UI elements to block controls for things like setting color, typography, and spacing. That’s a nice next step in the block development learning journey.


Rendering External API Data in WordPress Blocks on the Back End originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , ,

Rendering External API Data in WordPress Blocks on the Front End

There’ve been some new tutorials popping here on CSS-Tricks for working with WordPress blocks. One of them is an introduction to WordPress block development and it’s a good place to learn what blocks are and to register them in WordPress for use in pages and posts.

While the block basics are nicely covered in that post, I want to take it another step forward. You see, in that article, we learned the difference between rendering blocks in the back-end WordPress Block Editor and rendering them on the front-end theme. The example was a simple Pullquote Block that rendered different content and styles on each end.

Let’s go further and look at using dynamic content in a WordPress block. More specifically, let’s fetch data from an external API and render it on the front end when a particular block is dropped into the Block Editor.

We’re going to build a block that outputs data that shows soccer (er, football) rankings pulled from Api-Football.

An ordered set of football team rankings showing team logos, names, and game results.
This is what we’re working for together.

There’s more than one way to integrate an API with a WordPress block! Since the article on block basics has already walked through the process of making a block from scratch, we’re going to simplify things by using the @wordpress/create-block package to bootstrap our work and structure our project.

Initializing our block plugin

First things first: let’s spin up a new project from the command line:

npx @wordpress/create-block football-rankings

I normally would kick a project like this off by making the files from scratch, but kudos to the WordPress Core team for this handy utility!

Once the project folder has been created by the command, we technically have a fully-functional WordPress block registered as a plugin. So, let’s go ahead and drop the project folder into the wp-content/plugins directory where you have WordPress installed (probably best to be working in a local environment), then log into the WordPress admin and activate it from the Plugins screen.

Now that our block is initialized, installed, and activated, go ahead and open up the project folder from at /wp-content/plugins/football-rankings. You’re going to want to cd there from the command line as well to make sure we can continue development.

These are the only files we need to concentrate on at the moment:

  • edit.js
  • index.js
  • football-rankings.php

The other files in the project are important, of course, but are inessential at this point.

Reviewing the API source

We already know that we’re using Api-Football which comes to us courtesy of RapidAPI. Fortunately, RapidAPI has a dashboard that automatically generates the required scripts we need to fetch the API data for the 2021 Premier League Standings.

A dashboard interface with three columns showing code and data from an API source.
The RapidAPI dashboard

If you want to have a look on the JSON structure, you can generate visual representation with JSONCrack.

Fetching data from the edit.js file

I am going to wrap the RapidAPI code inside a React useEffect() hook with an empty dependency array so that it runs only once when the page is loaded. This way, we prevent WordPress from calling the API each time the Block Editor re-renders. You can check that using wp.data.subscribe() if you care to.

Here’s the code where I am importing useEffect(), then wrapping it around the fetch() code that RapidAPI provided:

/** * The edit function describes the structure of your block in the context of the * editor. This represents what the editor will render when the block is used. * * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit * * @return {WPElement} Element to render. */  import { useEffect } from "@wordpress/element";  export default function Edit(props) {   const { attributes, setAttributes } = props;    useEffect(() => {     const options = {       method: "GET",       headers: {         "X-RapidAPI-Key": "Your Rapid API key",         "X-RapidAPI-Host": "api-football-v1.p.rapidapi.com",       },     };      fetch("https://api-football-v1.p.rapidapi.com/v3/standings?season=2021&league=39", options)       .then( ( response ) => response.json() )       .then( ( response ) => {         let newData = { ...response };         setAttributes( { data: newData } );         console.log( "Attributes", attributes );       })       .catch((err) => console.error(err)); }, []);    return (     <p { ...useBlockProps() }>       { __( "Standings loaded on the front end", "external-api-gutenberg" ) }     </p>   ); }

Notice that I have left the return function pretty much intact, but have included a note that confirms the football standings are rendered on the front end. Again, we’re only going to focus on the front end in this article — we could render the data in the Block Editor as well, but we’ll leave that for another article to keep things focused.

Storing API data in WordPress

Now that we are fetching data, we need to store it somewhere in WordPress. This is where the attributes.data object comes in handy. We are defining the data.type as an object since the data is fetched and formatted as JSON. Make sure you don’t have any other type or else WordPress won’t save the data, nor does it throw any error for you to debug.

We define all this in our index.js file:

registerBlockType( metadata.name, {   edit: Edit,   attributes: {     data: {       type: "object",     },   },   save, } );

OK, so WordPress now knows that the RapidAPI data we’re fetching is an object. If we open a new post draft in the WordPress Block Editor and save the post, the data is now stored in the database. In fact, if we can see it in the wp_posts.post_content field if we open the site’s database in phpMyAdmin, Sequel Pro, Adminer, or whatever tool you use.

Showing a large string of JSON output in a database table.
API output stored in the WordPress database

Outputting JSON data in the front end

There are multiple ways to output the data on the front end. The way I’m going to show you takes the attributes that are stored in the database and passes them as a parameter through the render_callback function in our football-rankings.php file.

I like keeping a separation of concerns, so how I do this is to add two new files to the block plugin’s build folder: frontend.js and frontend.css (you can create a frontend.scss file in the src directory which compiled to CSS in the build directory). This way, the back-end and front-end codes are separate and the football-rankings.php file is a little easier to read.

/explanation Referring back to the introduction to WordPress block development, there are editor.css and style.css files for back-end and shared styles between the front and back end, respectively. By adding frontend.scss (which compiles to frontend.css, I can isolate styles that are only intended for the front end.

Before we worry about those new files, here’s how we call them in football-rankings.php:

/** * Registers the block using the metadata loaded from the `block.json` file. * Behind the scenes, it registers also all assets so they can be enqueued * through the block editor in the corresponding context. * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ function create_block_football_rankings_block_init() {   register_block_type( __DIR__ . '/build', array(     'render_callback' => 'render_frontend'   )); } add_action( 'init', 'create_block_football_rankings_block_init' );  function render_frontend($ attributes) {   if( !is_admin() ) {     wp_enqueue_script( 'football_rankings', plugin_dir_url( __FILE__ ) . '/build/frontend.js');     wp_enqueue_style( 'football_rankings', plugin_dir_url( __FILE__ ) . '/build/frontend.css' ); // HIGHLIGHT 15,16,17,18   }      ob_start(); ?>    <div class="football-rankings-frontend" id="league-standings">     <div class="data">       <pre>         <?php echo wp_json_encode( $ attributes ) ?>       </pre>     </div>     <div class="header">       <div class="position">Rank</div>       <div class="team-logo">Logo</div>       <div class="team-name">Team name</div>       <div class="stats">         <div class="games-played">GP</div>         <div class="games-won">GW</div>         <div class="games-drawn">GD</div>         <div class="games-lost">GL</div>         <div class="goals-for">GF</div>         <div class="goals-against">GA</div>         <div class="points">Pts</div>       </div>       <div class="form-history">Last 5 games</div>     </div>     <div class="league-table"></div>   </div>    <?php return ob_get_clean(); }

Since I am using the render_callback() method for the attributes, I am going to handle the enqueue manually just like the Block Editor Handbook suggests. That’s contained in the !is_admin() condition, and is enqueueing the two files so that we avoid enqueuing them while using the editor screen.

Now that we have two new files we’re calling, we’ve gotta make sure we are telling npm to compile them. So, do that in package.json, in the scripts section:

"scripts": {   "build": "wp-scripts build src/index.js src/frontend.js",   "format": "wp-scripts format",   "lint:css": "wp-scripts lint-style",   "lint:js": "wp-scripts lint-js",   "packages-update": "wp-scripts packages-update",   "plugin-zip": "wp-scripts plugin-zip",   "start": "wp-scripts start src/index.js src/frontend.js" },

Another way to include the files is to define them in the block metadata contained in our block.json file, as noted in the introduction to block development:

"viewScript": [ "file:./frontend.js", "example-shared-view-script" ], "style": [ "file:./frontend.css", "example-shared-style" ],

The only reason I’m going with the package.json method is because I am already making use of the render_callback() method.

Rendering the JSON data

In the rendering part, I am concentrating only on a single block. Generally speaking, you would want to target multiple blocks on the front end. In that case, you need to make use of document.querySelectorAll() with the block’s specific ID.

I’m basically going to wait for the window to load and grab data for a few key objects from JSON and apply them to some markup that renders them on the front end. I am also going to convert the attributes data to a JSON object so that it is easier to read through the JavaScript and set the details from JSON to HTML for things like the football league logo, team logos, and stats.

The “Last 5 games” column shows the result of a team’s last five matches. I have to manually alter the data for it since the API data is in a string format. Converting it to an array can help make use of it in HTML as a separate element for each of a team’s last five matches.

import "./frontend.scss";  // Wait for the window to load window.addEventListener( "load", () => {   // The code output   const dataEl = document.querySelector( ".data pre" ).innerHTML;   // The parent rankings element   const tableEl = document.querySelector( ".league-table" );   // The table headers   const tableHeaderEl = document.querySelector( "#league-standings .header" );   // Parse JSON for the code output   const dataJSON = JSON.parse( dataEl );   // Print a little note in the console   console.log( "Data from the front end", dataJSON );      // All the teams    let teams = dataJSON.data.response[ 0 ].league.standings[ 0 ];   // The league logo   let leagueLogoURL = dataJSON.data.response[ 0 ].league.logo;   // Apply the league logo as a background image inline style   tableHeaderEl.style.backgroundImage = `url( $ { leagueLogoURL } )`;      // Loop through the teams   teams.forEach( ( team, index ) => {     // Make a div for each team     const teamDiv = document.createElement( "div" );     // Set up the columns for match results     const { played, win, draw, lose, goals } = team.all;      // Add a class to the parent rankings element     teamDiv.classList.add( "team" );     // Insert the following markup and data in the parent element     teamDiv.innerHTML = `       <div class="position">         $ { index + 1 }       </div>       <div class="team-logo">         <img src="$ { team.team.logo }" />       </div>       <div class="team-name">$ { team.team.name }</div>       <div class="stats">         <div class="games-played">$ { played }</div>         <div class="games-won">$ { win }</div>         <div class="games-drawn">$ { draw }</div>         <div class="games-lost">$ { lose }</div>         <div class="goals-for">$ { goals.for }</div>         <div class="goals-against">$ { goals.against }</div>         <div class="points">$ { team.points }</div>       </div>       <div class="form-history"></div>     `;          // Stringify the last five match results for a team     const form = team.form.split( "" );          // Loop through the match results     form.forEach( ( result ) => {       // Make a div for each result       const resultEl = document.createElement( "div" );       // Add a class to the div       resultEl.classList.add( "result" );       // Evaluate the results       resultEl.innerText = result;       // If the result a win       if ( result === "W" ) {         resultEl.classList.add( "win" );       // If the result is a draw       } else if ( result === "D" ) {         resultEl.classList.add( "draw" );       // If the result is a loss       } else {         resultEl.classList.add( "lost" );       }       // Append the results to the column       teamDiv.querySelector( ".form-history" ).append( resultEl );     });      tableEl.append( teamDiv );   }); });

As far as styling goes, you’re free to do whatever you want! If you want something to work with, I have a full set of styles you can use as a starting point.

I styled things in SCSS since the @wordpress/create-block package supports it out of the box. Run npm run start in the command line to watch the SCSS files and compile them to CSS on save. Alternately, you can use npm run build on each save to compile the SCSS and build the rest of the plugin bundle.

View SCSS
body {   background: linear-gradient(to right, #8f94fb, #4e54c8); }  .data pre {   display: none; }  .header {   display: grid;   gap: 1em;   padding: 10px;   grid-template-columns: 1fr 1fr 3fr 4fr 3fr;   align-items: center;   color: white;   font-size: 16px;   font-weight: 600;   background-repeat: no-repeat;   background-size: contain;   background-position: right; }  .frontend#league-standings {   width: 900px;   margin: 60px 0;   max-width: unset;   font-size: 16px;    .header {     .stats {       display: flex;       gap: 15px;        &amp; &gt; div {         width: 30px;       }     }   } }  .league-table {   background: white;   box-shadow:     rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,     rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;   padding: 1em;    .position {     width: 20px;   }    .team {     display: grid;     gap: 1em;     padding: 10px 0;     grid-template-columns: 1fr 1fr 3fr 4fr 3fr;     align-items: center;   }    .team:not(:last-child) {     border-bottom: 1px solid lightgray;   }    .team-logo img {     width: 30px;   }    .stats {     display: flex;     gap: 15px;   }    .stats &gt; div {     width: 30px;     text-align: center;   }    .form-history {     display: flex;     gap: 5px;   }    .form-history &gt; div {     width: 25px;     height: 25px;     text-align: center;     border-radius: 3px;     font-size: 15px;   }    .form-history .win {     background: #347d39;     color: white;   }    .form-history .draw {     background: gray;     color: white;   }    .form-history .lost {     background: lightcoral;     color: white;   } }

Here’s the demo!

Check that out — we just made a block plugin that fetches data and renders it on the front end of a WordPress site.

We found an API, fetch()-ed data from it, saved it to the WordPress database, parsed it, and applied it to some HTML markup to display on the front end. Not bad for a single tutorial, right?

Again, we can do the same sort of thing so that the rankings render in the Block Editor in addition to the theme’s front end. But hopefully keeping this focused on the front end shows you how fetching data works in a WordPress block, and how the data can be structured and rendered for display.


Rendering External API Data in WordPress Blocks on the Front End originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]

TablesNG — Improvements to table rendering in Chromium

When I blogged “Making Tables With Sticky Header and Footers Got a Bit Easier” recently, I mentioned that the “stickiness” improvement was just one of the features that got better for <table>s in Chrome as part of the TablesNG upgrade. I ain’t the only one who’s stoked about it.

But Bramus took it the rest of the nine yards and looked at all of the table enhancements. Every one of these is great. The kind of thing that makes CSS ever-so-slightly less frustrating.

Just the writing-mode stuff is fantastic.

Direct Link to ArticlePermalink


The post TablesNG — Improvements to table rendering in Chromium appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Distributed Persistent Rendering (DPR)

Like Jamstack, Netlify is coining this term.

If your reaction is: great, a new thing I need to know about and learn, know that while Distributed Persistent Rendering (DPR) does involve some new things, this is actually a push toward simplification and leverages ideas as old as the web is, just like Jamstack.

It’s probably helpful to hear it right from Matt Biilmann, CEO of Netlify:

In that short video, he makes the point that React started out very simple and solved a lot of clear problems with JavaScript architecture and, as time goes on and it tries to solve more use-cases, it’s getting a lot more complicated and risks losing the appeal it once had in its simplicity.

Jamstack, too, faces this problem. The original simplicity of it was extremely appealing, but as it grows to accommodate more use-cases, things get complicated.

One of those complications are sites with many-thousands of pages. Sites like that can have really slow build times. It’s nice to see frameworks tackle that (Google “Incremental Builds {Your Favorite Framework}”), but heck, if you change one link in a site footer, you’re re-building the whole site based on that one change.

So instead of building many-thousands of pages during a build, say you just… didn’t. Until that page is requested once, anyway. That’s DPR.

Here’s Zach Leatherman doing that. He finds a spot on his site that generates some 400 pages on each build and tells Eleventy that instead of building it during the normal build process, defer it to the cloud (literally a lambda will run and build the page when needed).

Deferring those 400 pages saves seven seconds in the build. Say your site is more dramatic, like 16,000 pages. Scratch pad math says you are saving four minutes there. It’s not just time either, although that’s a biggie. I think of all the electricity and long-term storage you save building this way.

Here’s the Netlify blog post:

Just like coining the term “Jamstack” didn’t mean inventing an entirely new architecture from scratch, naming this concept of “Distributed Persistent Rendering” doesn’t mean we’re creating a brand new solution.

The term “DPR” is new to us, but in a lot of ways, we’re taking inspiration from solutions that have worked in the past. We’re simply reworking them to fit with modern Jamstack best practices.

I like that it’s not like this entirely new thing. I’m sure Netlify’s implementation of it is no joke, but for us, it’s very easy to think about:

  • Some pages are pre-built as usual
  • Some pages are not built (deferred)
  • When the non-built pages are requested for the first time, then they are built and cached so they don’t need to be built again.

That’s it, really.

It reminds me of how old WordPress caching plugins used to work. When a page was requested for the first time it would run the PHP and MySQL queries and all that, then save the result as an .html file to the disk to serve subsequent requests. Not new, but still efficient.

The trick to a DPR architecture on Netlify is using their (beta) On-Demand Builders, so here’s the blog post that explains everything and will get you to the docs and such.bui


The post Distributed Persistent Rendering (DPR) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Rendering the WordPress philosophy in GraphQL

WordPress is a CMS that’s coded in PHP. But, even though PHP is the foundation, WordPress also holds a philosophy where user needs are prioritized over developer convenience. That philosophy establishes an implicit contract between the developers building WordPress themes and plugins, and the user managing a WordPress site.

GraphQL is an interface that retrieves data from—and can submit data to—the server. A GraphQL server can have its own opinionatedness in how it implements the GraphQL spec, as to prioritize some certain behavior over another.

Can the WordPress philosophy that depends on server-side architecture co-exist with a JavaScript-based query language that passes data via an API?

Let’s pick that question apart, and explain how the GraphQL API WordPress plugin I authored establishes a bridge between the two architectures.

You may be aware of WPGraphQL. The plugin GraphQL API for WordPress (or “GraphQL API” from now on) is a different GraphQL server for WordPress, with different features.

Reconciling the WordPress philosophy within the GraphQL service

This table contains the expected behavior of a WordPress application or plugin, and how it can be interpreted by a GraphQL service running on WordPress:

Category WordPress app expected behavior Interpretation for GraphQL service running on WordPress
Accessing data Democratizing publishing: Any user (irrespective of having technical skills or not) must be able to use the software Democratizing data access and publishing: Any user (irrespective of having technical skills or not) must be able to visualize and modify the GraphQL schema, and execute a GraphQL query
Extensibility The application must be extensible through plugins The GraphQL schema must be extensible through plugins
Dynamic behavior The behavior of the application can be modified through hooks The results from resolving a query can be modified through directives
Localization The application must be localized, to be used by people from any region, speaking any language The GraphQL schema must be localized, to be used by people from any region, speaking any language
User interfaces Installing and operating functionality must be done through a user interface, resorting to code as little as possible Adding new entities (types, fields, directives) to the GraphQL schema, configuring them, executing queries, and defining permissions to access the service must be done through a user interface, resorting to code as little as possible
Access control Access to functionalities can be granted through user roles and permissions Access to the GraphQL schema can be granted through user roles and permissions
Preventing conflicts Developers do not know in advance who will use their plugins, or what configuration/environment those sites will run, meaning the plugin must be prepared for conflicts (such as having two plugins define the SMTP service), and attempt to prevent them, as much as possible Developers do not know in advance who will access and modify the GraphQL schema, or what configuration/environment those sites will run, meaning the plugin must be prepared for conflicts (such as having two plugins with the same name for a type in the GraphQL schema), and attempt to prevent them, as much as possible

Let’s see how the GraphQL API carries out these ideas.

Accessing data

Similar to REST, a GraphQL service must be coded through PHP functions. Who will do this, and how?

Altering the GraphQL schema through code

The GraphQL schema includes types, fields and directives. These are dealt with through resolvers, which are pieces of PHP code. Who should create these resolvers?

The best strategy is for the GraphQL API to already satisfy the basic GraphQL schema with all known entities in WordPress (including posts, users, comments, categories, and tags), and make it simple to introduce new resolvers, for instance for Custom Post Types (CPTs).

This is how the user entity is already provided by the plugin. The User type is provided through this code:

class UserTypeResolver extends AbstractTypeResolver {   public function getTypeName(): string   {     return 'User';   }    public function getSchemaTypeDescription(): ?string   {     return __('Representation of a user', 'users');   }    public function getID(object $ user)   {     return $ user-&gt;ID;   }    public function getTypeDataLoaderClass(): string   {     return UserTypeDataLoader::class;   } }

The type resolver does not directly load the objects from the database, but instead delegates this task to a TypeDataLoader object (in the example above, from UserTypeDataLoader. This decoupling is to follow the SOLID principles, providing different entities to tackle different responsibilities, as to make the code maintainable, extensible and understandable.

Adding username, email and url fields to the User type is done via a FieldResolver object:

class UserFieldResolver extends AbstractDBDataFieldResolver {   public static function getClassesToAttachTo(): array   {     return [       UserTypeResolver::class,     ];   }    public static function getFieldNamesToResolve(): array   {     return [       'username',       'email',       'url',     ];   }    public function getSchemaFieldDescription(     TypeResolverInterface $ typeResolver,     string $ fieldName   ): ?string {     $ descriptions = [       'username' => __("User's username handle", "graphql-api"),       'email' => __("User's email", "graphql-api"),       'url' => __("URL of the user's profile in the website", "graphql-api"),     ];     return $ descriptions[$ fieldName];   }    public function getSchemaFieldType(     TypeResolverInterface $ typeResolver,     string $ fieldName   ): ?string {     $ types = [       'username' => SchemaDefinition::TYPE_STRING,       'email' => SchemaDefinition::TYPE_EMAIL,       'url' => SchemaDefinition::TYPE_URL,     ];     return $ types[$ fieldName];   }    public function resolveValue(     TypeResolverInterface $ typeResolver,     object $ user,     string $ fieldName,     array $ fieldArgs = []   ) {     switch ($ fieldName) {       case 'username':         return $ user->user_login;        case 'email':         return $ user->user_email;        case 'url':         return get_author_posts_url($ user->ID);     }      return null;   } }

As it can be observed, the definition of a field for the GraphQL schema, and its resolution, has been split into a multitude of functions:

  • getSchemaFieldDescription
  • getSchemaFieldType
  • resolveValue

Other functions include:

This code is more legible than if all functionality is satisfied through a single function, or through a configuration array, thus making it easier to implement and maintain the resolvers.

Retrieving plugin or custom CPT data

What happens when a plugin has not integrated its data to the GraphQL schema by creating new type and field resolvers? Could the user then query data from this plugin through GraphQL? For instance, let’s say that WooCommerce has a CPT for products, but it does not introduce the corresponding Product type to the GraphQL schema. Is it possible to retrieve the product data?

Concerning CPT entities, their data can be fetched via type GenericCustomPost, which acts as a kind of wildcard, to encompass any custom post type installed in the site. The records are retrieved by querying Root.genericCustomPosts(customPostTypes: [cpt1, cpt2, ...]) (in this notation for fields, Root is the type, and genericCustomPosts is the field).

Then, to fetch the product data, corresponding to CPT with name "wc_product", we execute this query:

{   genericCustomPosts(customPostTypes: "[wc_product]") {     id     title     url     date   } }

However, all the available fields are only those ones present in every CPT entity: title, url, date, etc. If the CPT for a product has data for price, a corresponding field price is not available. wc_product refers to a CPT created by the WooCommerce plugin, so for that, either the WooCommerce or the website’s developers will have to implement the Product type, and define its own custom fields.

CPTs are often used to manage private data, which must not be exposed through the API. For this reason, the GraphQL API initially only exposes the Page type, and requires defining which other CPTs can have their data publicly queried:

The plugin provides an interface for whitelisting CPTs to be exposed in the API.

Transitioning from REST to GraphQL via persisted queries

While GraphQL is provided as a plugin, WordPress has built-in support for REST, through the WP REST API. In some circumstances, developers working with the WP REST API may find it problematic to transition to GraphQL. For instance, consider these differences:

  • A REST endpoint has its own URL, and can be queried via GET, while GraphQL, normally operates through a single endpoint, queried via POST only
  • The REST endpoint can be cached on the server-side (when queried via GET), while the GraphQL endpoint normally cannot

As a consequence, REST provides better out-of-the-box support for caching, making the application more performant and reducing the load on the server. GraphQL, instead, places more emphasis in caching on the client-side, as supported by the Apollo client.

After switching from REST to GraphQL, will the developer need to re-architect the application on the client-side, introducing the Apollo client just to introduce a layer of caching? That would be regrettable.

The “persisted queries” feature provides a solution for this situation. Persisted queries combine REST and GraphQL together, allowing us to:

  • create queries using GraphQL, and
  • publish the queries on their own URL, similar to REST endpoints.

The persisted query endpoint has the same behavior as a REST endpoint: it can be accessed via GET, and it can be cached server-side. But it was created using the GraphQL syntax, and the exposed data has no under/over fetching.

Extensibility

The architecture of the GraphQL API will define how easy it is to add our own extensions.

Decoupling type and field resolvers

The GraphQL API uses the Publish-subscribe pattern to have fields be “subscribed” to types.

Reappraising the field resolver from earlier on:

class UserFieldResolver extends AbstractDBDataFieldResolver {   public static function getClassesToAttachTo(): array   {     return [UserTypeResolver::class];   }    public static function getFieldNamesToResolve(): array   {     return [       'username',       'email',       'url',     ];   } }

The User type does not know in advance which fields it will satisfy, but these (username, email and url) are instead injected to the type by the field resolver.

This way, the GraphQL schema becomes easily extensible. By simply adding a field resolver, any plugin can add new fields to an existing type (such as WooCommerce adding a field for User.shippingAddress), or override how a field is resolved (such as redefining User.url to return the user’s website instead).

Code-first approach

Plugins must be able to extend the GraphQL schema. For instance, they could make available a new Product type, add an additional coauthors field on the Post type, provide a @sendEmail directive, or anything else.

To achieve this, the GraphQL API follows a code-first approach, in which the schema is generated from PHP code, on runtime.

The alternative approach, called SDL-first (Schema Definition Language), requires the schema be provided in advance, for instance, through some .gql file.

The main difference between these two approaches is that, in the code-first approach, the GraphQL schema is dynamic, adaptable to different users or applications. This suits WordPress, where a single site could power several applications (such as website and mobile app) and be customized for different clients. The GraphQL API makes this behavior explicit through the “custom endpoints” feature, which enables to create different endpoints, with access to different GraphQL schemas, for different users or applications.

To avoid performance hits, the schema is made static by caching it to disk or memory, and it is re-generated whenever a new plugin extending the schema is installed, or when the admin updates the settings.

Support for novel features

Another benefit of using the code-first approach is that it enables us to provide brand-new features that can be opted into, before these are supported by the GraphQL spec.

For instance, nested mutations have been requested for the spec but not yet approved. The GraphQL API complies with the spec, using types QueryRoot and MutationRoot to deal with queries and mutations respectively, as exposed in the standard schema. However, by enabling the opt-in “nested mutations” feature, the schema is transformed, and both queries and mutations will instead be handled by a single Root type, providing support for nested mutations.

Let’s see this novel feature in action. In this query, we first query the post through Root.post, then execute mutation Post.addComment on it and obtain the created comment object, and finally execute mutation Comment.reply on it and query some of its data (uncomment the first mutation to log the user in, as to be allowed to add comments):

# mutation { #   loginUser( #     usernameOrEmail:"test", #     password:"pass" #   ) { #     id #     name #   } # } mutation {   post(id:1459) {     id     title     addComment(comment:"That's really beautiful!") {       id       date       content       author {         id         name       }       reply(comment:"Yes, it is!") {         id         date         content       }     }   } }

Dynamic behavior

WordPress uses hooks (filters and actions) to modify behavior. Hooks are simple pieces of code that can override a value, or enable to execute a custom action, whenever triggered.

Is there an equivalent in GraphQL?

Directives to override functionality

Searching for a similar mechanism for GraphQL, I‘ve come to the conclusion that directives could be considered the equivalent to WordPress hooks to some extent: like a filter hook, a directive is a function that modifies the value of a field, thus augmenting some other functionality. For instance, let’s say we retrieve a list of post titles with this query:

query {   posts {     title   } }

…which produces this response:

{   "data": {     "posts": [       {         "title": "Scheduled by Leo"       },       {         "title": "COPE with WordPress: Post demo containing plenty of blocks"       },       {         "title": "A lovely tango, not with leo"       },       {       "title": "Hello world!"       },     ]   } }

These results are in English. How can we translate them to Spanish? With a directive @translate applied on field title (implemented through this directive resolver), which gets the value of the field as an input, calls the Google Translate API to translate it, and has its result override the original input, as in this query:

query {   posts {     title @translate(from:"en", to"es")   } }

…which produces this response:

{   "data": {     "posts": [       {         "title": "Programado por Leo"       },       {         "title": "COPE con WordPress: publica una demostración que contiene muchos bloques"       },       {         "title": "Un tango lindo, no con leo"       },       {         "title": "¡Hola Mundo!"       }     ]   } }

Please notice how directives are unconcerned with who the input is. In this case, it was a Post.title field, but it could’ve been Post.excerpt, Comment.content, or any other field of type String. Then, resolving fields and overriding their value is cleanly decoupled, and directives are always reusable.

Directives to connect to third parties

As WordPress keeps steadily becoming the OS of the web (currently powering 39% of all sites, more than any other software), it also progressively increases its interactions with external services (think of Stripe for payments, Slack for notifications, AWS S3 for hosting assets, and others).

As we‘ve seen above, directives can be used to override the response of a field. But where does the new value come from? It could come from some local function, but it could perfectly well also originate from some external service (as for directive @translate we’ve seen earlier on, which retrieves the new value from the Google Translate API).

For this reason, GraphQL API has decided to make it easy for directives to communicate with external APIs, enabling those services to transform the data from the WordPress site when executing a query, such as for:

  • translation,
  • image compression,
  • sourcing through a CDN, and
  • sending emails, SMS and Slack notifications.

As a matter of fact, GraphQL API has decided to make directives as powerful as possible, by making them low-level components in the server’s architecture, even having the query resolution itself be based on a directive pipeline. This grants directives the power to perform authorizations, validations, and modification of the response, among others.

Localization

GraphQL servers using the SDL-first approach find it difficult to localize the information in the schema (the corresponding issue for the spec was created more than four years ago, and still has no resolution).

Using the code-first approach, though, the GraphQL API can localize the descriptions in a straightforward manner, through the __('some text', 'domain') PHP function, and the localized strings will be retrieved from a POT file corresponding to the region and language selected in the WordPress admin.

For instance, as we saw earlier on, this code localizes the field descriptions:

class UserFieldResolver extends AbstractDBDataFieldResolver {   public function getSchemaFieldDescription(     TypeResolverInterface $ typeResolver,     string $ fieldName   ): ?string {     $ descriptions = [       'username' => __("User's username handle", "graphql-api"),       'email' => __("User's email", "graphql-api"),       'url' => __("URL of the user's profile in the website", "graphql-api"),     ];     return $ descriptions[$ fieldName];   } }

User interfaces

The GraphQL ecosystem is filled with open source tools to interact with the service, including many provide the same user-friendly experience expected in WordPress.

Visualizing the GraphQL schema is done with GraphQL Voyager:

GraphQL Voyager enables us to interact with the schema, as to get a good grasp of how all entities in the application’s data model relate to each other.

This can prove particularly useful when creating our own CPTs, and checking out how and from where they can be accessed, and what data is exposed for them:

Interacting with the schema

Executing the query against the GraphQL endpoint is done with GraphiQL:

GraphiQL for the admin

However, this tool is not simple enough for everyone, since the user must have knowledge of the GraphQL query syntax. So, in addition, the GraphiQL Explorer is installed on top of it, as to compose the GraphQL query by clicking on fields:

GraphiQL with Explorer for the admin

Access control

WordPress provides different user roles (admin, editor, author, contributor and subscriber) to manage user permissions, and users can be logged-in the wp-admin (eg: the staff), logged-in the public-facing site (eg: clients), or not logged-in or have an account (any visitor). The GraphQL API must account for these, allowing to grant granular access to different users.

Granting access to the tools

The GraphQL API allows to configure who has access to the GraphiQL and Voyager clients to visualize the schema and execute queries against it:

  • Only the admin?
  • The staff?
  • The clients?
  • Openly accessible to everyone?

For security reasons, the plugin, by default, only provides access to the admin, and does not openly expose the service on the Internet.

In the images from the previous section, the GraphiQL and Voyager clients are available in the wp-admin, available to the admin user only. The admin user can grant access to users with other roles (editor, author, contributor) through the settings:

The admin user can grant access to users with other roles (editor, author, contributor) through the settings.

As to grant access to our clients, or anyone on the open Internet, we don’t want to give them access to the WordPress admin. Then, the settings enable to expose the tools under a new, public-facing URL (such as mywebsite.com/graphiql and mywebsite.com/graphql-interactive). Exposing these public URLs is an opt-in choice, explicitly set by the admin.

Granting access to the GraphQL schema

The WP REST API does not make it easy to customize who has access to some endpoint or field within an endpoint, since no user interface is provided and it must be accomplished through code.

The GraphQL API, instead, makes use of the metadata already available in the GraphQL schema to enable configuration of the service through a user interface (powered by the WordPress editor). As a result, non-technical users can also manage their APIs without touching a line of code.

Managing access control to the different fields (and directives) from the schema is accomplished by clicking on them and selecting, from a dropdown, which users (like those logged in or with specific capabilities) can access them.

Preventing conflicts

Namespacing helps avoid conflicts whenever two plugins use the same name for their types. For instance, if both WooCommerce and Easy Digital Downloads implement a type named Product, it would become ambiguous to execute a query to fetch products. Then, namespacing would transform the type names to WooCommerceProduct and EDDProduct, resolving the conflict.

The likelihood of such conflict arising, though, is not very high. So the best strategy is to have it disabled by default (as to keep the schema as simple as possible), and enable it only if needed.

If enabled, the GraphQL server automatically namespaces types using the corresponding PHP package name (for which all packages follow the PHP Standard Recommendation PSR-4). For instance, for this regular GraphQL schema:

Regular GraphQL schema

…with namespacing enabled, Post becomes PoPSchema_Posts_Post, Comment becomes PoPSchema_Comments_Comment, and so on.

Namespaced GraphQL schema

That’s all, folks

Both WordPress and GraphQL are captivating topics on their own, so I find the integration of WordPress and GraphQL greatly endearing. Having been at it for a few years now, I can say that designing the optimal way to have an old CMS manage content, and a new interface access it, is a challenge worth pursuing.

I could continue describing how the WordPress philosophy can influence the implementation of a GraphQL service running on WordPress, talking about it even for several hours, using plenty of material that I have not included in this write-up. But I need to stop… So I’ll stop now.

I hope this article has managed to provide a good overview of the whys and hows for satisfying the WordPress philosophy in GraphQL, as done by plugin GraphQL API for WordPress.


The post Rendering the WordPress philosophy in GraphQL appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Rendering Spectrum

Here are the big categories of rendering websites:

  • Client: ship a <div id="root"></div> and let a JavaScript template render all of it.
  • Static: pre-render all the HTML.
  • Server: let a live server process requests and generate the HTML response.

They are not mutually exclusive.

  • A website could statically pre-render 75% of it’s pages (say, blog posts), but the other 25% have a server respond to (say, forums).
  • A website could statically pre-render all the pages, but have a couple of empty <div>s in there that have client-side rendered content in them (e.g. a dynamically generated menu based on the logged-in user).
  • A website could be primarily server-rendered, but has caching in front of it such that it behaves statically.
  • A website could render statically, but then “hydrate” itself into an entirely client-rendered site.
  • A website could be a mix of server and static rendering, but have dynamic parts similar to client-side rendering, but actually happen in an edge function, so it ends up more like server-side rendering.

Next.js is interesting in that it can do all three. Here’s Tim Neutkens in a recent interview:

Next.js allows you to pre-render pages. It creates HTML on a server at build time with static site generation or uses run-time rendering on the server side. Next allows you to do a hybrid of those. Unlike most other frameworks, you are not bound by, oh, I’m going to build my app completely statically generated. Instead, you’re allowed to have some pages be server-side rendered and some pages be statically generated.

In the new release we make it possible to update these statically generated pages without having to run a new build, rebuilding your whole app.

Cool. Love to see that happening at the framework level. Seems like having to go all-in on one rendering style isn’t practical for a lot of sites.

Client rendering is the most flexible, but comes with all these serious downsides like worse performance, worse reliability, more strain on devices, bad SEO, etc. Static pre-rendering is the most robust, speedy, and secure, but is the most limited. Edge functions on top of static is starting to open doors, but server-rendering is the classic mix of flexibility and speed that has dominated the web for good reason.

Client rendering also opens the door for that “SPA” (Single Page App) feel. I still like that, personally. I like the no-page-refresh feel. It’s makes a site feel snappy and opens the door for page transitions. Gatsby is famous for popularizing hydration, where you get the pre-rendered static bonus, but then the upgrade into SPA as the JavaScript downloads.

I’d love to see the web get to the point where we get all that “good feel” bonus of an SPA without actually having to build an SPA. It’s notable when frameworks provide SPA feels without having to manage much of that yourself, but still, something is managing it and that something is a bunch of JavaScript.

Tom MacWright wrote about that recently in his “If not SPAs, What?” post. Some of today’s alternatives:

Turbolinks … what is the bare minimum you need to do to get the SPA experience without any cooperation from your application?

Turbolinks is like… click link, click is intercepted, Ajax request for new page is made, JavaScript flops out the content on the page with the new content. Super easy to implement, but it’s still JavaScript, and not particularly intelligent about sending less data across the wire.

barba.js and instant.page are alternative approaches to the same sort of problem.

Barba is all about getting page transitions going (more detail on that concept). instant.page is all about pre-loading/rendering pages right before you click then, so even though you get a page refresh, it feels less intrusive (particularly with paint holding). Both are cool, but not quite one-to-one replacements for an SPA. (Even with paint holding, pre-rendering, and lightweight pages, I still don’t think the experience is quite a smooth as an SPA. For example, you still get the page loading spinner.)

So… is the anything else cooking? Kinda. There is <portal>. Possibly too simplified, but here goes: portals are like iframes. They can even be visually displayed just like an iframe. That means the rendering of the URL in the portal is already done. Then you can “promote” the portal to be the active page, and even animate the portal itself while doing so.

I don’t hate it. I can imagine someone building a turbolinks-like library on portals so they are “easy to use” and make a site more SPA-like.

Still, animating a rectangle into position isn’t often what is desired from animated page transitions. Just look at Sarah’s “Native-Like Animations for Page Transitions on the Web” article. That’s what the people want (at least the possibility of it). That’s why Jeremy said not portals the other day when he cheekily said that “[m]ost single page apps are just giant carousels.” He also points to Jake’s navigation-transitions proposal from a few years back.

I love this proposal. It focuses on user needs. It also asks why people reach for JavaScript frameworks instead of using what browsers provide. People reach for JavaScript frameworks because browsers don’t yet provide some functionality: components like tabs or accordions; DOM diffing; control over styling complex form elements; navigation transitions. The problems that JavaScript frameworks are solving today should be seen as the R&D departments for web standards of tomorrow. (And conversely, I strongly believe that the aim of any good JavaScript framework should be to make itself redundant.)

So what’s the best rendering method? Whatever works best for you, but perhaps a hierarchy like this makes some general sense:

  1. Static HTML as much as you can
  2. Edge functions over static HTML so you can do whatever dynamic things
  3. Server generated HTML what you have to after that
  4. Client-side render only what you absolutely have to

The post Rendering Spectrum appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

,
[Top]

content-visibility: the new CSS property that boosts your rendering performance

Una Kravets and Vladimir Levin:

[…] you can use another CSS property called content-visibility to apply the needed containment automatically. content-visibility ensures that you get the largest performance gains the browser can provide with minimal effort from you as a developer.

The content-visibility property accepts several values, but auto is the one that provides immediate performance improvements.

The perf benefits seems pretty big:

In our example, we see a boost from a 232ms rendering time to a 30ms rendering time. That’s a 7x performance boost.

It’s manual work though. You have to “section” large vertical chunks of the page yourself, apply content-visibility: auto; to them, then take a stab at about how tall they are, something like contain-intrinsic-size: 1000px;. That part seems super weird to me. Just guess at a height? What if I’m wrong? Can I hurt performance? Can (or should) I change that value at different viewports if the height difference between small and large screens is drastic?

Seems like you’d have to be a pretty skilled perf nerd to get this right, and know how to look at and compare rendering profiles in DevTools. All the more proof that web perf is its own vocation.

Direct Link to ArticlePermalink


The post content-visibility: the new CSS property that boosts your rendering performance appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Techniques for Rendering Text with WebGL

As is the rule in WebGL, anything that seems like it should be simple is actually quite complicated. Drawing lines, debugging shaders, text rendering… they are all damn hard to do well in WebGL.

Isn’t that weird? WebGL doesn’t have a built-in function for rendering text. Although text seems like the most basic of functionalities. When it comes down to actually rendering it, things get complicated. How do you account for the immense amount of glyphs for every language? How do you work with fixed-width, or proportional-width fonts? What do you do when text needs to be rendered top-to-bottom, left-to-right, or right-to-left? Mathematical equations, diagrams, sheet music?

Suddenly it starts to make sense why text rendering has no place in a low-level graphics API like WebGL. Text rendering is a complex problem with a lot of nuances. If we want to render text, we need to get creative. Fortunately, a lot of smart folks already came up with a wide range of techniques for all our WebGL text needs.

We’ll learn at some of those techniques in this article, including how to generate the assets they need and how to use them with ThreeJS, a JavaScript 3D library that includes a WebGL renderer. As a bonus, each technique is going to have a demo showcasing use cases.

Table of Contents


A quick note on text outside of WebGL

Although this article is all about text inside WebGL, the first thing you should consider is whether you can get away with using HMTL text or canvas overlayed on top of your WebGL canvas. The text can’t be occluded with the 3D geometry as an overlay, but you can get styling and accessibility out of the box. That’s all you need in a lot of cases.

Font geometries

One of the common ways to render text is to build the glyphs with a series of triangles, much like a regular model. After all, rendering points, lines and triangles are a strength of WebGL.

When creating a string, each glyph is made by reading the triangles from a font file of triangulated points. From there, you could extrude the glyph to make it 3D, or scale the glyphs via matrix operations.

Regular font representation (left) and font geometry (right)

Font geometry works best for a small amount of text. That’s because each glyph contains many triangles, which causes drawing to become problematic.

Rendering this exact paragraph you are reading right now with font geometry creates 185,084 triangles and 555,252 vertices. This is just 259 letters. Write the whole article using a font geometry and your computer fan might as well become an airplane turbine.

Although the number of triangles varies by the precision of the triangulation and the typeface in use, rendering lots of text will probably always be a bottleneck when working with font geometry.

How to create a font geometry from a font file

If it were as easy as choosing the font you want and rendering the text. I wouldn’t be writing this article. Regular font formats define glyphs with Bezier curves. On the flip side, drawing those in WebGL is extremely expensive on the CPU and is also complicated to do. If we want to render text, we need to create triangles (triangulation) out of Bezier curves.

I’ve found a few triangulation methods, but by no means are any of them perfect and they may not work with every font. But at least they’ll get you started for triangulating your own typefaces.

Method 1: Automatic and easy

If you are using ThreeJS, you pass your typeface through FaceType.js to read the parametric curves from your font file and put them into a .json file. The font geometry features in ThreeJS take care of triangulating the points for you:

const geometry = new THREE.FontGeometry("Hello There", {font: font, size: 80})

Alternatively, if you are not using ThreeJS and don’t need to have real-time triangulation. You could save yourself the pain of a manual process by using ThreeJS to triangulate the font for you. Then you can extract the vertices and indices from the geometry, and load them in your WebGL application of choice.

Method 2: Manual and painful

The manual option for triangulating a font file is extremely complicated and convoluted, at least initially. It would require a whole article just to explain it in detail. That said, we’ll quickly go over the steps of a basic implementation I grabbed from StackOverflow.

See the Pen
Triangulating Fonts
by Daniel Velasquez (@Anemolo)
on CodePen.

The implementation basically breaks down like this:

  1. Add OpenType.js and Earcut.js to your project.
  2. Get Bezier curves from your .tff font file using OpenType.js.
  3. Convert Bezier curves into closed shapes and sort them by descending area.
  4. Determine the indices for the holes by figuring out which shapes are inside other shapes.
  5. Send all of the points to Earcut with the hole indices as a second parameter.
  6. Use Earcut’s result as the indices for your geometry.
  7. Breath out.

Yeah, it’s a lot. And this implementation may not work for all typefaces. It’ll get you started nonetheless.

Using text geometries in ThreeJS

Thankfully, ThreeJS supports text geometries out of the box. Give it a .json of your favorite font’s Bezier curves and ThreeJS takes care of triangulating the vertices for you in runtime.

var loader = new THREE.FontLoader(); var font; var text = "Hello World" var loader = new THREE.FontLoader(); loader.load('fonts/helvetiker_regular.typeface.json', function (helvetiker) {   font = helvetiker;   var geometry = new THREE.TextGeometry(text, {     font: font,     size: 80,     height: 5,   }); }

Advantages

  • It’s easily extruded to create 3D strings.
  • Scaling is made easier with matrix operations.
  • It provides great quality depending on the amount of triangles used.

Disadvantages

  • This doesn’t scale well with large amounts of text due to the high triangle count. Since each character is defined by a lot of triangles, even rendering something as brief as “Hello World” results in 7,396 triangles and 22,188 vertices.
  • This doesn’t lend itself to common text effects.
  • Anti-aliasing depends on your post-processing aliasing or your browser default.
  • Scaling things too big might show the triangles.

Demo: Fading Letters

In the following demo, I took advantage of how easy it is to create 3D text using font geometries. Inside a vertex shader, the extrusion is increased and decreased as time goes on. Pair that with fog and standard material and you get these ghostly letters coming in and out of the void.

Notice how with a low amount of letters the amount of triangles is already in the tens of thousands!

Text (and canvas) textures

Making text textures is probably the simplest and oldest way to draw text in WebGL. Open up Photoshop or some other raster graphics editor, draw an image with some text on it, then render these textures onto a quad and you are done!

Alternatively, you could use the canvas to create the textures on demand at runtime. You’re able to render the canvas as a texture onto a quad as well.

Aside for being the least complicated technique of the bunch. Text textures and canvas textures have the benefit of only needed one quad per texture, or given piece of text. If you really wanted to, you could write the entire British Encyclopedia on a single texture. That way, you only have to render a single quad, six vertices and two faces. Of course, you would do it in a scale, but the idea still remains: You can batch multiple glyphs into same quad. Both text and canvas textures experience have issues with scaling, particularly when working with lots of text.

For text textures, the user has to download all the textures that make up the text, then keep them in memory. For canvas textures, the user doesn’t have to download anything — but now the user’s computer has to do all the rasterizing at runtime, and you need to keep track of where every word is located in the canvas. Plus, updating a big canvas can be really slow.

How to create and use a text texture

Text textures don’t have anything fancy going on for them. Open up your favorite raster graphics editor, draw some text on the canvas and export it as an image. Then you can load it as a texture, and map it on a plane:

// Load texture let texture = ; const geometry = new THREE.PlaneBufferGeometry(); const material new THREE.MeshBasicMaterial({map: texture}); this.scene.add(new Mesh(geometry,material));

If your WebGL app has a lot of text, downloading a huge sprite sheet of text might not be ideal, especially for users on slow connections. To avoid the download time, you can rasterize things on demand using an offscreen canvas then sample that canvas as a texture.

Let’s trade download time for performance since rasterizing lots of text takes more than a moment.

function createTextCanvas(string, parameters = {}){          const canvas = document.createElement("canvas");     const ctx = canvas.getContext("2d");          // Prepare the font to be able to measure     let fontSize = parameters.fontSize || 56;     ctx.font = `$ {fontSize}px monospace`;          const textMetrics = ctx.measureText(text);          let width = textMetrics.width;     let height = fontSize;          // Resize canvas to match text size      canvas.width = width;     canvas.height = height;     canvas.style.width = width + "px";     canvas.style.height = height + "px";          // Re-apply font since canvas is resized.     ctx.font = `$ {fontSize}px monospace`;     ctx.textAlign = parameters.align || "center" ;     ctx.textBaseline = parameters.baseline || "middle";          // Make the canvas transparent for simplicity     ctx.fillStyle = "transparent";     ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);          ctx.fillStyle = parameters.color || "white";     ctx.fillText(text, width / 2, height / 2);          return canvas; }  let texture = new THREE.Texture(createTextCanvas("This is text"));

Now you can use the texture on a plane, like the previous snippet. Or you could create a sprite instead.

As an alternative, you could use more efficient libraries to create texture or sprites, like three-text2d or three-spritetext. And if you want text multi-line text, you should check out this amazing tutorial.

Advantages

  • This provides great 1-to-1 quality with static text.
  • There’s a low vertex/face count. Each string can use as little as six vertices and two faces.
  • It’s easy to implement texture on a quad.
  • It’s fairly trivial to add effects, like borders and glows, using canvas or a graphics editor.
  • Canvas makes it easy to create multi-line text.

Disadvantages

  • Looks blurry if scaled, rotated or transformed after rasterizing.
  • On-non retina, text looks crunchy.
  • You have to rasterize all the strings used. A lot of strings means a lot of data to download.
  • On-demand rasterizing with canvas can be slow if you keep constantly updating the canvas.

Demo: Canvas texture

Canvas textures work well with a limited amount of text that doesn’t change often. So I built a simple wall of text with the quads re-using the same texture.

Bitmap fonts

Both font geometries and text textures experience the same problems handling lots of text. Having one million vertices per piece of text is super inefficient, and creating one texture per piece of text doesn’t really scale.

Bitmap fonts solve this issue by rasterizing all unique glyphs into a single texture, called a texture atlas. This means you can assemble any given string at runtime by creating a quad for each glyph and sampling the section of the texture atlas.

This means users only have to download and use a single texture for all of the text. It also means you only need to render as little as one quad per glyph:

A visual of bitmap font sampling

Rendering this whole article would be approximately 117,272 vertices and 58,636 triangles. That’s 3.1 times more efficient compared to a font geometry rendering just a single paragraph. That a huge improvement!

Because bitmap fonts rasterize the glyph into a texture, they suffer from the same problem as regular images. Zoom in or scale and you start seeing a pixelated and blurry mess. If you want text at a different size, you should send a secondary bitmap with the glyphs on that specific size. Or you could use a Signed Distance Field (SDF) which we’ll cover in the next section.

How to create bitmap fonts

There are a lot of tools to generate bitmaps. Here are some of the more relevant options out there:

  • Angelcode’s bmfont – This is by the creators of the bitmap format.
  • Hiero – This is a Java open-source tool. It’s very similar to Anglecode’s bmfont, but it allows you to add text effects.
  • Glyphs Designer – This is a paid MacOS app.
  • ShoeBox – This is an tool for dealing with sprites, including bitmap fonts.

We’ll use Anglecode’s bmfont for our example because I think it’s the easiest one to get started. At the bottom of this section, you can find other tools if you think it lacks the functionality you are looking for.

When you open the app, you’ll see a screen full of letters that you can select to use.The nice thing about this is that you are able to grab just the glyphs you need instead of sending Greek symbols.

The app’s sidebar allows you to choose and select groups of glyphs.

The BmFont application

Ready to export? Go to OptionsSave bitmap as. And done!

But we’re getting a little ahead of ourselves. Before exporting, there are a few important settings you should check.

Export and Font Option settings
  • Font settings: This let you choose the font and size you want to use. The most important item here is “Match char height.” By default, the app’s “size” option uses pixels instead of points. You’ll see a substantial difference between your graphics editor’s font size and the font size that is generated. Select the “Match char height” options if you want your glyphs to make sense.
  • Export settings: For the export, make sure the texture size is a power of two (e.g. 16×16, 32×32, 64×64, etc.). Then you are able to take advantage of “Linear Mipmap linear” filtering, if needed.

At the bottom of the settings, you’ll see the “file format” section. Choosing either option here is fine as long as you can read the file and create the glyphs.

If you are looking for the smallest file size. I ran a ultra non-scientific test where I created a bitmap of all lowecase and uppercase Latin characters and compared each option. For Font Descriptors, the most efficient format is Binary.

Font Descriptor Format File Size
Binary 3 KB
Raw Text 11 KB
XML 12 KB
Texture Format File Size
PNG 7 KB
Targa 64 KB
DirectDraw Surface 65 KB

PNG is the smallest file size for Text Texture.

Of course, it’s a little more complicated than just file sizes. To get a better idea of which option to use, you should look into parsing time and run-time performance. If you would like to know the pros and cons of each formats, check out this discussion.

How to use bitmap fonts

Creating bitmap font geometry is a bit more involved than just using a texture because we have to construct the string ourselves. Each glyph has its own height and width, and samples a different section of the texture. We have to create a quad for each glyph on our string so we can give them the correct UVs to sample it’s glyph.

You can use three-bmfont-text in ThreeJS to create strings using bitmaps, SDFs and MSDFs. It takes care of multi-line text, and batching all glyphs onto a single geometry. Note that it needs to be installed in a project from npm.

var createGeometry = require('three-bmfont-text') var loadFont = require('load-bmfont')  loadFont('fonts/Arial.fnt', function(err, font) {   // create a geometry of packed bitmap glyphs,    // word wrapped to 300px and right-aligned   var geometry = createGeometry({     font: font,     text: "My Text"   })        var textureLoader = new THREE.TextureLoader();   textureLoader.load('fonts/Arial.png', function (texture) {     // we can use a simple ThreeJS material     var material = new THREE.MeshBasicMaterial({       map: texture,       transparent: true,       color: 0xaaffff     })      // now do something with our mesh!     var mesh = new THREE.Mesh(geometry, material)   }) })

Depending whenever your text is drawn as as full black or full white, use the invert option.

Advantages

  • It’s fast and simple to render.
  • It’s a 1:1 ratio and resolution independent.
  • It can render any string, given the glyphs.
  • It’s good for lots of text that needs to change often.
  • It’s works extremely well with a limited number of glyphs.
  • It’s includes support for things like kerning, line height and word-wrapping at run-time.

Disadvantages

  • It only accepts a limited set of characters and styles.
  • It requires pre-rasterizing glyphs and extra bin packing for optimal usage.
  • It’s blurry and pixelated at large scales, and can also be rotated or transformed.
  • There’s only one quad per rendered glyph.

Interactive Demo: The Shining Credits

Raster bitmap fonts work great for movie credits because we only need a few sizes and styles. The drawback is that the text isn’t great with responsive designs because it’ll look blurry and pixelated at larger sizes.

For the mouse effect, I’m making calculations by mapping the mouse position to the size of the view then calculating the distance from the mouse to the text position. I’m also rotating the text when it hits specific points on the z-axis and y-axis.

Signed distance fields

Much like bitmap fonts, signed distance field (SDF) fonts are also a texture atlas. Unique glyphs are batch into a single “texture atlas” that can create any string at runtime.

But instead of storing the rasterized glyph on the texture the way bitmap fonts do, the glyph’s SDF is generated and stored instead which allows for a high resolution shape from a low resolution image.

Like polygonal meshes (font geometries), SDFs represent shapes. Each pixel on an SDF stores the distance to the closest surface. The sign indicates whenever the pixel is inside or outside the shape. If the sign is negative, then the pixel is inside; if it’s positive, then the pixel is outside. This video illustrates the concept nicely.

SDFs are also commonly used for raytracing and volumetric rendering.

Because an SDF stores distance in each pixel, the raw result looks like a blurry version of the original shape. To output the hard shape you’ll need to alpha test it at 0.5 which is the border of the glyph. Take a look at how the SDF of the letter “A” compares to it’s regular raster image:

Raster text beside of a raw and an alpha tested SDF

As I mentioned earlier, the big benefit of SDFs is being able to render high resolution shapes from low resolution SDF. This means you can create a 16pt font SDF and scale the text up to 100pt or more without losing much crispness.

SDFs are good at scaling because you can almost perfectly reconstruct the distance with bilinear interpolation, which is a fancy way of saying we can get values between two points. In this case, bilinear interpolating between two pixels on a regular bitmap font gives us the in-between color, resulting in a linear blur.

On an SDF, bilinear interpolating between two pixels provides the in-between distance to the nearest edge. Since these two pixel distances are similar to begin with, the resulting value doesn’t lose much information about the glyph. This also means the bigger the SDF, the more accurate and less information is lost.

However, this process comes with a caveat. If the rate change between pixels is not linear — like in the case of sharp corners — bilinear interpolation gives out an inaccurate value, resulting in chipped or rounded corners when scaling an SDF much higher than its original size.

SDF rounded corners

Aside from bumping the texture side, the only real solution is to use multi-channel SDFs, which is what we’ll cover in the next section.

If you want to take a deeper dive into the science behind SDFs, check out the Chris Green’s Master’s thesis (PDF) on the topic.

Advantages

  • They maintain crispness, even when rotated, scaled or transformed.
  • They are ideal for dynamic text.
  • They provide good quality to the size ratio. A single texture can be used to render tiny and huge font sizes without losing much quality.
  • They have a low vertex count of only four vertices per glyph.
  • Antialiasing is inexpensive as is making borders, drop shadows and all kinds of effects with alpha testing.
  • They’re smaller than MSDFs (which we’ll see in a bit).

Disadvantages

  • The can result in rounded or chipped corners when the texture is sampled beyond its resolution. (Again, we’ll see how MSDFs can prevent that.)
  • They’re ineffective at tiny font sizes.
  • They can only be used with monochrome glyphs.

Multi-channel signed distance fields

Multi-channel signed distance field (MSDF) fonts is a bit of a mouthful and a fairly recent variation on SDFs that is capable of producing near-perfect sharp corners by using all three color channels. They do look quite mind blowing at first but don’t let that put you off because they are easy to use than they appear.

A multi-channel signed distance field file can look a little spooky at first.

Using all three color channels does result in a heavier image, but that’s what gives MSDFs a far better quality-to-space ratio than regular SDFs. The following image shows the difference between an SDF and an MSDF for a font that has been scaled up to 50px.

The SDF font results in rounded corners, even at 1x zoom, while the MSDF font retains sharp edges, even at 5x zoom.

Like a regular SDF, an MSDF stores the distance to the nearest edge but changes the color channels whenever it finds a sharp corner. We get the shape by drawing where two color channels or more agree. Although there’s a bit more technique involved. Check out the README for this MSDF generator for a more thorough explanation.

Advantages

  • They support a higher quality and space ratio than SDFs. and are often the better choice.
  • They maintain sharp corners when scaled.

Disadvantages

  • They may contain small artifacts but those can be avoided by bumping up the texture size of the glyph.
  • They requires filtering the median of the three values at runtime which is a bit expensive.
  • They are only compatible with monochrome glyphs.

How to create MSDF fonts

The quickest way to create an MSDF font is to use the msdf-bmfont-web tool. It has most of the relevant customization options and it gets the job done in seconds, all in the browser. Alternatively, there are a number of Google Fonts that have already been converted into MSDF by the folks at A-Frame.

If you are also looking to generate SDFs or your typeface, it requires some special tweaking thanks to some problematic glyphs. The msdf-bmfont-xml CLI gives you a wide range of options, without making things overly confusing. Let’s take a look at how you would use it.

First, you’ll need to install globally it from npm:

npm install msdf-bmfont-xml -g

Next, give it a .ttf font file with your options:

msdf-bmfont ./Open-Sans-Black.ttf --output-type json --font-size 76 --texture-size 512,512

Those options are worth digging into a bit. While msdf-bmfont-xml provides a lot of options to fine-tune your font, there are really just a few options you’ll probably need to correctly generate an MSDF:

  • -t <type> or <code>--field-type <msdf or sdf>: msdf-bmfont-xml generates MSDFs glyph atlases by default. If you want to generate an SDF instead, you need to specify it by using -t sdf.
  • -f <xml or json> or --output-type <xml or json>: msdf-bmfont-xml generates an XML font file that you would have to parse to JSON at runtime. You can avoid this parsing step by exporting JSON straight away.
  • -s, --font-size <fontSize>: Some artifacts and imperfections might show up if the font size is super small. Bumping up the font size will get rid of them most of the time. This example shows a small imperfection in the letter “M.”
  • -m <w,h> or --texture-size <w,h>: If all your characters don’t fit in the same texture, a second texture is created to fit them in. Unless you are trying to take advantage of a multi-page glyph atlas, I recommend increasing the texture size so that it fits over all of the characters on one texture to avoid extra work.

There are other tools that help generate MSDF and SDF fonts:

  • msdf-bmfont-web: A web tool to create MSDFs (but not SDFs) quickly and easily
  • msdf-bmfont: A Node tool using Cairo and node-canvas
  • msdfgen: The original command line tool that all other MSDF tools are based from
  • Hiero: A tool for generating both bitmaps and SDF fonts

How to use SDF and MSDF fonts

Because SDF and MSDF fonts are also glyph atlases, we can use three-bmfont-text like we did for bitmap fonts. The only difference is that we have to get the glyph our of the distance fields with a fragment shader.

Here’s how that works for SDF fonts. Since our distance field has a value greater than .5 outside our glyph and less than 0.5 inside our glyph, we need to alpha test in a fragment shader on each pixel to make sure we only render pixels with a distance less than 0.5, rendering just the inside of the glyphs.

const fragmentShader = `    uniform vec3 color;   uniform sampler2D map;   varying vec2 vUv;      void main(){     vec4 texColor = texture2D(map, vUv);     // Only render the inside of the glyph.     float alpha = step(0.5, texColor.a);      gl_FragColor = vec4(color, alpha);     if (gl_FragColor.a < 0.0001) discard;   } `;  const vertexShader = `   varying vec2 vUv;      void main {     gl_Position = projectionMatrix * modelViewMatrix * position;     vUv = uv;   } `;  let material = new THREE.ShaderMaterial({   fragmentShader, vertexShader,   uniforms: {     map: new THREE.Uniform(glyphs),     color: new THREE.Uniform(new THREE.Color(0xff0000))   } })

Similarly, we can import the font from three-bmfont-text which comes with antialiasing out of the box. Then we can use it directly on a RawShaderMaterial:

let SDFShader = require('three-bmfont-text/shaders/sdf'); let material = new THREE.RawShaderMaterial(MSDFShader({   map: texture,   transparent: true,   color: 0x000000 }));

MSDF fonts are a little different. They recreate sharp corners by the intersections of two color channels. Two or more color channels have to agree on it. Before doing any alpha texting, we need to get the median of the three color channels to see where they agree:

const fragmentShader = `    uniform vec3 color;   uniform sampler2D map;   varying vec2 vUv;    float median(float r, float g, float b) {     return max(min(r, g), min(max(r, g), b));   }      void main(){     vec4 texColor = texture2D(map, vUv);     // Only render the inside of the glyph.     float sigDist = median(texColor.r, texColor.g, texColor.b) - 0.5;     float alpha = step(0.5, sigDist);     gl_FragColor = vec4(color, alpha);     if (gl_FragColor.a < 0.0001) discard;   } `; const vertexShader = `   varying vec2 vUv;      void main {     gl_Position = projectionMatrix * modelViewMatrix * position;     vUv = uv;   } `;  let material = new THREE.ShaderMaterial({   fragmentShader, vertexShader,   uniforms: {     map: new THREE.Uniform(glyphs),     color: new THREE.Uniform(new THREE.Color(0xff0000))   } })

Again, we can also import from three-bmfont-text using its MSDFShader which also comes with antialiasing out of the box. Then we can use it directly on a RawShaderMaterial:

let MSDFShader = require('three-bmfont-text/shaders/msdf'); let material = new THREE.RawShaderMaterial(MSDFShader({   map: texture,   transparent: true,   color: 0x000000 }));

Demo: Star Wars intro

The Star Wars drawl intro is a good example where MSDF and SDF fonts work well because the effect needs the text to come in multiple sizes. We can use a single MSDF and the text always looks sharp! Although, sadly, three-bm-font doesn’t support justified text yet. Applying left justification would make for a more balanced presentation.

For the light saber effect, I’m raycasting an invisible plane the size of the plane, drawing onto a canvas that’s the same size, and sampling that canvas by mapping the scene position to the texture coordinates.

Bonus tip: Generating 3D text with height maps

Aside from font geometries, all the techniques we’ve covered generate strings or glyphs on a single quad. If you want to build 3D geometries out of a flat texture, your best choice is to use a height map.

A height map is a technique where the geometry height is bumped up using a texture. This is normally used to generate mountains in games, but it turns out to be useful rendering text as well.

The only caveat is that you’ll need a lot of faces for the text to look smooth.

Further reading

Different situations call for different techniques. Nothing we saw here is a silver bullet and they all have their advantages and disadvantages.

There are a lot of tools and libraries out there to help make the most of WebGL text, most of which actually originate from outside WebGL. If you want to keep learning, I highly recommend you go beyond WebGL and check out some of these links:

The post Techniques for Rendering Text with WebGL appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Static First: Pre-Generated JAMstack Sites with Serverless Rendering as a Fallback

You might be seeing the term JAMstack popping up more and more frequently. I’ve been a fan of it as an approach for some time.

One of the principles of JAMstack is that of pre-rendering. In other words, it generates your site into a collection of static assets in advance, so that it can be served to your visitors with maximum speed and minimum overhead from a CDN or other optimized static hosting environment.

But if we are going to pre-generate our sites ahead of time, how do we make them feel dynamic? How do we build sites that need to change often? How do we work with things like user generated content?

As it happens, this can be a great use case for serverless functions. JAMstack and serverless are the best of friends. They complement each other wonderfully.

In this article, we’ll look at a pattern of using serverless functions as a fallback for pre-generated pages in a site that is comprised almost entirely of user generated content. We’ll use a technique of optimistic URL routing where the 404 page is a serverless function to add serverless rendering on the fly.

Buzzwordy? Perhaps. Effective? Most certainly!

You can go and have a play with the demo site to help you imagine this use case. But only if you promise to come back.

https://vlolly.net

Is that you? You came back? Great. Let’s dig in.

The idea behind this little example site is that it lets you create a nice, happy message and virtual pick-me-up to send to a friend. You can write a message, customize a lollipop (or a popsicle, for my American friends) and get a URL to share with your intended recipient. And just like that, you’ve brightened up their day. What’s not to love?

Traditionally, we’d build this site using some server-side scripting to handle the form submissions, add new lollies (our user generated content) to a database and generate a unique URL. Then we’d use some more server-side logic to parse requests for these pages, query the database to get the data needed to populate a page view, render it with a suitable template, and return it to the user.

That all seems logical.

But how much will it cost to scale?

Technical architects and tech leads often get this question when scoping a project. They need to plan, pay for, and provision enough horsepower in case of success.

This virtual lollipop site is no mere trinket. This thing is going to make me a gazillionaire due to all the positive messages we all want to send each other! Traffic levels are going to spike as the word gets out. I had better have a good strategy of ensuring that the servers can handle the hefty load. I might add some caching layers, some load balancers, and I’ll design my database and database servers to be able to share the load without groaning from the demand to make and serve all these lollies.

Except… I don’t know how to do that stuff.

And I don’t know how much it would cost to add that infrastructure and keep it all humming. It’s complicated.

This is why I love to simplify my hosting by pre-rendering as much as I can.

Serving static pages is significantly simpler and cheaper than serving pages dynamically from a web server which needs to perform some logic to generate views on demand for every visitor.

Since we are working with lots of user generated content, it still makes sense to use a database, but I’m not going to manage that myself. Instead, I’ll choose one of the many database options available as a service. And I’ll talk to it via its APIs.

I might choose Firebase, or MongoDB, or any number of others. Chris compiled a few of these on an excellent site about serverless resources which is well worth exploring.

In this case, I selected Fauna to use as my data store. Fauna has a nice API for stashing and querying data. It is a no-SQL flavored data store and gives me just what I need.

https://fauna.com

Critically, Fauna have made an entire business out of providing database services. They have the deep domain knowledge that I’ll never have. By using a database-as-a-service provider, I just inherited an expert data service team for my project, complete with high availability infrastructure, capacity and compliance peace of mind, skilled support engineers, and rich documentation.

Such are the advantages of using a third-party service like this rather than rolling your own.

Architecture TL;DR

I often find myself doodling the logical flow of things when I’m working on a proof of concept. Here’s my doodle for this site:

And a little explanation:

  1. A user creates a new lollipop by completing a regular old HTML form.
  2. The new content is saved in a database, and its submission triggers a new site generation and deployment.
  3. Once the site deployment is complete, the new lollipop will be available on a unique URL. It will be a static page served very rapidly from the CDN with no dependency on a database query or a server.
  4. Until the site generation is complete, any new lollipops will not be available as static pages. Unsuccessful requests for lollipop pages fall back to a page which dynamically generates the lollipop page by querying the database API on the fly.

This kind of approach, which first assumes static/pre-generated assets, only then falling back to a dynamic render when a static view is not available was usefully described by Markus Schork of Unilever as “Static First” which I rather like.

In a little more detail

You could just dive into the code for this site, which is open source and available for you to explore, or we could talk some more.

You want to dig in a little further, and explore the implementation of this example? OK, I’ll explain in some more details:

  • Getting data from the database to generate each page
  • Posting data to a database API with a serverless function
  • Triggering a full site re-generation
  • Rendering on demand when pages are yet to be generated

Generating pages from a database

In a moment, we’ll talk about how we post data into the database, but first, let’s assume that there are some entries in the database already. We are going to want to generate a site which includes a page for each and every one of those.

Static site generators are great at this. They chomp through data, apply it to templates, and output HTML files ready to be served. We could use any generator for this example. I chose Eleventy due to it’s relative simplicity and the speed of its site generation.

To feed Eleventy some data, we have a number of options. One is to give it some JavaScript which returns structured data. This is perfect for querying a database API.

Our Eleventy data file will look something like this:

// Set up a connection with the Fauna database. // Use an environment variable to authenticate // and get access to the database. const faunadb = require('faunadb'); const q = faunadb.query; const client = new faunadb.Client({   secret: process.env.FAUNADB_SERVER_SECRET });  module.exports = () => {   return new Promise((resolve, reject) => {     // get the most recent 100,000 entries (for the sake of our example)     client.query(       q.Paginate(q.Match(q.Ref("indexes/all_lollies")),{size:100000})     ).then((response) => {       // get all data for each entry       const lollies = response.data;       const getAllDataQuery = lollies.map((ref) => {         return q.Get(ref);       });       return client.query(getAllDataQuery).then((ret) => {         // send the data back to Eleventy for use in the site build         resolve(ret);       });     }).catch((error) => {       console.log("error", error);       reject(error);     });   }) }

I named this file lollies.js which will make all the data it returns available to Eleventy in a collection called lollies.

We can now use that data in our templates. If you’d like to see the code which takes that and generates a page for each item, you can see it in the code repository.

Submitting and storing data without a server

When we create a new lolly page we need to capture user content in the database so that it can be used to populate a page at a given URL in the future. For this, we are using a traditional HTML form which posts data to a suitable form handler.

The form looks something like this (or see the full code in the repo):

<form name="new-lolly" action="/new" method="POST">    <!-- Default "flavors": 3 bands of colors with color pickers -->   <input type="color" id="flavourTop" name="flavourTop" value="#d52358" />   <input type="color" id="flavourMiddle" name="flavourMiddle" value="#e95946" />   <input type="color" id="flavourBottom" name="flavourBottom" value="#deaa43" />    <!-- Message fields -->   <label for="recipientName">To</label>   <input type="text" id="recipientName" name="recipientName" />    <label for="message">Say something nice</label>   <textarea name="message" id="message" cols="30" rows="10"></textarea>    <label for="sendersName">From</label>   <input type="text" id="sendersName" name="sendersName" />    <!-- A descriptive submit button -->   <input type="submit" value="Freeze this lolly and get a link">  </form>

We have no web servers in our hosting scenario, so we will need to devise somewhere to handle the HTTP posts being submitted from this form. This is a perfect use case for a serverless function. I’m using Netlify Functions for this. You could use AWS Lambda, Google Cloud, or Azure Functions if you prefer, but I like the simplicity of the workflow with Netlify Functions, and the fact that this will keep my serverless API and my UI all together in one code repository.

It is good practice to avoid leaking back-end implementation details into your front-end. A clear separation helps to keep things more portable and tidy. Take a look at the action attribute of the form element above. It posts data to a path on my site called /new which doesn’t really hint at what service this will be talking to.

We can use redirects to route that to any service we like. I’ll send it to a serverless function which I’ll be provisioning as part of this project, but it could easily be customized to send the data elsewhere if we wished. Netlify gives us a simple and highly optimized redirects engine which directs our traffic out at the CDN level, so users are very quickly routed to the correct place.

The redirect rule below (which lives in my project’s netlify.toml file) will proxy requests to /new through to a serverless function hosted by Netlify Functions called newLolly.js.

# resolve the "new" URL to a function [[redirects]]   from = "/new"   to = "/.netlify/functions/newLolly"   status = 200

Let’s look at that serverless function which:

  • stores the new data in the database,
  • creates a new URL for the new page and
  • redirects the user to the newly created page so that they can see the result.

First, we’ll require the various utilities we’ll need to parse the form data, connect to the Fauna database and create readably short unique IDs for new lollies.

const faunadb = require('faunadb');          // For accessing FaunaDB const shortid = require('shortid');          // Generate short unique URLs const querystring = require('querystring');  // Help us parse the form data  // First we set up a new connection with our database. // An environment variable helps us connect securely // to the correct database. const q = faunadb.query const client = new faunadb.Client({   secret: process.env.FAUNADB_SERVER_SECRET })

Now we’ll add some code to the handle requests to the serverless function. The handler function will parse the request to get the data we need from the form submission, then generate a unique ID for the new lolly, and then create it as a new record in the database.

// Handle requests to our serverless function exports.handler = (event, context, callback) => {    // get the form data   const data = querystring.parse(event.body);   // add a unique path id. And make a note of it - we'll send the user to it later   const uniquePath = shortid.generate();   data.lollyPath = uniquePath;    // assemble the data ready to send to our database   const lolly = {     data: data   };    // Create the lolly entry in the fauna db   client.query(q.Create(q.Ref('classes/lollies'), lolly))     .then((response) => {       // Success! Redirect the user to the unique URL for this new lolly page       return callback(null, {         statusCode: 302,         headers: {           Location: `/lolly/$ {uniquePath}`,         }       });     }).catch((error) => {       console.log('error', error);       // Error! Return the error with statusCode 400       return callback(null, {         statusCode: 400,         body: JSON.stringify(error)       });     });  }

Let’s check our progress. We have a way to create new lolly pages in the database. And we’ve got an automated build which generates a page for every one of our lollies.

To ensure that there is a complete set of pre-generated pages for every lolly, we should trigger a rebuild whenever a new one is successfully added to the database. That is delightfully simple to do. Our build is already automated thanks to our static site generator. We just need a way to trigger it. With Netlify, we can define as many build hooks as we like. They are webhooks which will rebuild and deploy our site of they receive an HTTP POST request. Here’s the one I created in the site’s admin console in Netlify:

Netlify build hook

To regenerate the site, including a page for each lolly recorded in the database, we can make an HTTP POST request to this build hook as soon as we have saved our new data to the database.

This is the code to do that:

const axios = require('axios'); // Simplify making HTTP POST requests  // Trigger a new build to freeze this lolly forever axios.post('https://api.netlify.com/build_hooks/5d46fa20da4a1b70XXXXXXXXX') .then(function (response) {   // Report back in the serverless function's logs   console.log(response); }) .catch(function (error) {   // Describe any errors in the serverless function's logs   console.log(error); });

You can see it in context, added to the success handler for the database insertion in the full code.

This is all great if we are happy to wait for the build and deployment to complete before we share the URL of our new lolly with its intended recipient. But we are not a patient lot, and when we get that nice new URL for the lolly we just created, we’ll want to share it right away.

Sadly, if we hit that URL before the site has finished regenerating to include the new page, we’ll get a 404. But happily, we can use that 404 to our advantage.

Optimistic URL routing and serverless fallbacks

With custom 404 routing, we can choose to send every failed request for a lolly page to a page which will can look for the lolly data directly in the database. We could do that in with client-side JavaScript if we wanted, but even better would be to generate a ready-to-view page dynamically from a serverless function.

Here’s how:

Firstly, we need to tell all those hopeful requests for a lolly page that come back empty to go instead to our serverless function. We do that with another rule in our Netlify redirects configuration:

# unfound lollies should proxy to the API directly [[redirects]]   from = "/lolly/*"   to = "/.netlify/functions/showLolly?id=:splat"   status = 302

This rule will only be applied if the request for a lolly page did not find a static page ready to be served. It creates a temporary redirect (HTTP 302) to our serverless function, which looks something like this:

const faunadb = require('faunadb');                  // For accessing FaunaDB const pageTemplate = require('./lollyTemplate.js');  // A JS template litereal   // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({   secret: process.env.FAUNADB_SERVER_SECRET });  exports.handler = (event, context, callback) => {    // get the lolly ID from the request   const path = event.queryStringParameters.id.replace("/", "");    // find the lolly data in the DB   client.query(     q.Get(q.Match(q.Index("lolly_by_path"), path))   ).then((response) => {     // if found return a view     return callback(null, {       statusCode: 200,       body: pageTemplate(response.data)     });    }).catch((error) => {     // not found or an error, send the sad user to the generic error page     console.log('Error:', error);     return callback(null, {       body: JSON.stringify(error),       statusCode: 301,       headers: {         Location: `/melted/index.html`,       }     });   }); }

If a request for any other page (not within the /lolly/ path of the site) should 404, we won’t send that request to our serverless function to check for a lolly. We can just send the user directly to a 404 page. Our netlify.toml config lets us define as many level of 404 routing as we’d like, by adding fallback rules further down in the file. The first successful match in the file will be honored.

# unfound lollies should proxy to the API directly [[redirects]]   from = "/lolly/*"   to = "/.netlify/functions/showLolly?id=:splat"   status = 302  # Real 404s can just go directly here: [[redirects]]   from = "/*"   to = "/melted/index.html"   status = 404

And we’re done! We’ve now got a site which is static first, and which will try to render content on the fly with a serverless function if a URL has not yet been generated as a static file.

Pretty snappy!

Supporting larger scale

Our technique of triggering a build to regenerate the lollipop pages every single time a new entry is created might not be optimal forever. While it’s true that the automation of the build means it is trivial to redeploy the site, we might want to start throttling and optimizing things when we start to get very popular. (Which can only be a matter of time, right?)

That’s fine. Here are a couple of things to consider when we have very many pages to create, and more frequent additions to the database:

  • Instead of triggering a rebuild for each new entry, we could rebuild the site as a scheduled job. Perhaps this could happen once an hour or once a day.
  • If building once per day, we might decide to only generate the pages for new lollies submitted in the last day, and cache the pages generated each day for future use. This kind of logic in the build would help us support massive numbers of lolly pages without the build getting prohibitively long. But I’ll not go into intra-build caching here. If you are curious, you could ask about it over in the Netlify Community forum.

By combining both static, pre-generated assets, with serverless fallbacks which give dynamic rendering, we can satisfy a surprisingly broad set of use cases — all while avoiding the need to provision and maintain lots of dynamic infrastructure.

What other use cases might you be able to satisfy with this “static first” approach?

The post Static First: Pre-Generated JAMstack Sites with Serverless Rendering as a Fallback appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]

Revisiting the Rendering Tier

Have you ever created a well-intentioned, thoughtful design system only to watch it grow into an ever-increasing and scary codebase? I’ve been working in sort of the opposite direction, inheriting the scary codebase and trying to create a thoughtful system from it.

Here’s Alex Sanders on the topic, explaining how his team at The Guardian has tackled the task of creating design systems while combating complexity:

Systems that try to contain complexity over long periods of time by convention will inevitably tend toward entropy, because one significant characteristic of convention is that it is trivially simple to break one.

You do not even need to be malicious. A convention is not a line in the sand. You can have a very good case for breaking or stretching one, but once a convention is no longer fully observed, subsequent cases for breaking or stretching it are automatically stronger, because the convention is already weakened. The more this happens, the weaker it gets.

Complexity and entropy can be two outcomes in the same situation, but need not be mutually exclusive. Interesting to think that our best intentions to guard against complexity can be somewhat destructive.

I also love how Alex explains why it’s not possible for their team to use a Tachyons-esque approach to writing styles because of the way that their development environment is kinda slow. It would be painful for the team to make that switch, despite how it could solve some other problems. This reminded me that measuring problems in this way is why there can never be a single way to write CSS. We need that inherent flexibility, even at the expense of introducing inconsistencies. Hence, conventions being less of a line in the sand and more of a guide post.

On a separate note, I really like how Alex describes styles and attributes as the reasons why his team is writing those styles. It’s about aligning with business objectives:

…tens of thousands of rules that are intended to describe a maintainable set of responses to business and design problems.

That’s interesting since I don’t think we spend much time here talking specifically about the business side of CSS and the functional requirements that a styled user interface needs to accomplish.

And perhaps thinking about that can help us write better styles in the long term. Is this line of CSS solving a problem? Does this new class resolve an issue that will help our customers? These are good questions to keep in mind as we work, yet I know I don’t spend enough time thinking about them. I often see the design I’m turning into code as a problem to be solved instead.

Perhaps we should expand the way we styling a webpage because maybe, just maybe, it will help us write more maintainable code that’s built to solve a real business objective.

Direct Link to ArticlePermalink

The post Revisiting the Rendering Tier appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]