> All in One 586

Ads

Wednesday, May 26, 2021

A Thorough Analysis of CSS-in-JS

Wondering what’s even more challenging than choosing a JavaScript framework? You guessed it: choosing a CSS-in-JS solution. Why? Because there are more than 50 libraries out there, each of them offering a unique set of features.

We tested 10 different libraries, which are listed here in no particular order: Styled JSX, styled-components, Emotion, Treat, TypeStyle, Fela, Stitches, JSS, Goober, and Compiled. We found that, although each library provides a diverse set of features, many of those features are actually commonly shared between most other libraries.

So instead of reviewing each library individually, we’ll analyse the features that stand out the most. This will help us to better understand which one fits best for a specific use case.

Note: We assume that if you’re here, you’re already familiar with CSS-in-JS. If you’re looking for a more elementary post, you can check out “An Introduction to CSS-in-JS.”

Common CSS-in-JS features

Most actively maintained libraries that tackle CSS-in-JS support all the following features, so we can consider them de-facto.

Scoped CSS

All CSS-in-JS libraries generate unique CSS class names, a technique pioneered by CSS modules. All styles are scoped to their respective component, providing encapsulation without affecting any styling defined outside the component.

With this feature built-in, we never have to worry about CSS class name collisions, specificity wars, or wasted time spent coming up with unique class names across the entire codebase.

This feature is invaluable for component-based development.

SSR (Server-Side Rendering)

When considering Single Page Apps (SPAs) — where the HTTP server only delivers an initial empty HTML page and all rendering is performed in the browser — Server-Side Rendering (SSR) might not be very useful. However, any website or application that needs to be parsed and indexed by search engines must have SSR pages and styles need to be generated on the server as well.

The same principle applies to Static Site Generators (SSG), where pages along with any CSS code are pre-generated as static HTML files at build time.

The good news is that all libraries we’ve tested support SSR, making them eligible for basically any type of project.

Automatic vendor prefixes

Because of the complex CSS standardization process, it might take years for any new CSS feature to become available in all popular browsers. One approach aimed at providing early access to experimental features is to ship non-standard CSS syntax under a vendor prefix:

/* WebKit browsers: Chrome, Safari, most iOS browsers, etc */
-webkit-transition: all 1s ease;

/* Firefox */
-moz-transition: all 1s ease;

/* Internet Explorer and Microsoft Edge */
-ms-transition: all 1s ease;

/* old pre-WebKit versions of Opera */
-o-transition: all 1s ease;

/* standard */
transition: all 1s ease; 

However, it turns out that vendor prefixes are problematic and the CSS Working Group intends to stop using them in the future. If we want to fully support older browsers that don’t implement the standard specification, we’ll need to know which features require a vendor prefix.

Fortunately, there are tools that allow us to use the standard syntax in our source code, generating the required vendor prefixed CSS properties automatically. All CSS-in-JS libraries also provide this feature out-of-the-box.

No inline styles

There are some CSS-in-JS libraries, like Radium or Glamor, that output all style definitions as inline styles. This technique has a huge limitation, because it’s impossible to define pseudo classes, pseudo-elements, or media queries using inline styles. So, these libraries had to hack these features by adding DOM event listeners and triggering style updates from JavaScript,  essentially reinventing native CSS features like :hover, :focus and many more.

It’s also generally accepted that inline styles are less performant than class names. It’s usually a discouraged practice to use them as a primary approach for styling components.

All current CSS-in-JS libraries have stepped away from using inline styles, adopting CSS class names to apply style definitions.

Full CSS support

A consequence of using CSS classes instead of inline styles is that there’s no limitation regarding what CSS properties we can and can’t use. During our analysis we were specifically interested in:

  • pseudo classes and elements;
  • media queries;
  • keyframe animations.

All the libraries we’ve analyzed offer full support for all CSS properties.

Differentiating features

This is where it gets even more interesting. Almost every library offers a unique set of features that can highly influence our decision when choosing the appropriate solution for a particular project. Some libraries pioneered a specific feature, while others chose to borrow or even improve certain features.

React-specific or framework-agnostic?

It’s not a secret that CSS-in-JS is more prevalent within the React ecosystem. That’s why some libraries are specifically built for React: Styled JSX, styled-components, and Stitches.

However, there are plenty of libraries that are framework-agnostic, making them applicable to any project: Emotion, Treat, TypeStyle, Fela, JSS or Goober.

If we need to support vanilla JavaScript code or frameworks other than React, the decision is simple: we should choose a framework-agnostic library. But when dealing with a React application, we have a much wider range of options which ultimately makes the decision more difficult. So let’s explore other criteria.

Styles/Component co-location

The ability to define styles along with their components is a very convenient feature, removing the need to switch back-and-forth between two different files: the .css or .less/.scss file containing the styles and the component file containing the markup and behavior.

React Native StyleSheets, Vue.js SFCs, or Angular Components support co-location of styles by default, which proves to be a real benefit during both the development and the maintenance phases. We always have the option to extract the styles into a separate file, in case we feel that they’re obscuring the rest of the code.

Almost all CSS-in-JS libraries support co-location of styles. The only exception we encountered was Treat, which requires us to define the styles in a separate .treat.ts file, similarly to how CSS Modules work.

Styles definition syntax

There are two different methods we can use to define our styles. Some libraries support only one method, while others are quite flexible and support both of them.

Tagged Templates syntax

The Tagged Templates syntax allows us to define styles as a string of plain CSS code inside a standard ES Template Literal:

// consider "css" being the API of a generic CSS-in-JS library
const heading = css`
  font-size: 2em;
  color: ${myTheme.color};
`;

We can see that:

  • CSS properties are written in kebab case just like regular CSS;
  • JavaScript values can be interpolated;
  • we can easily migrate existing CSS code without rewriting it.

Some things to keep in mind:

  • In order to get syntax highlight and code suggestions, an additional editor plugin is required; but this plugin is usually available for popular editors like VSCode, WebStorm, and others.
  • Since the final code must be eventually executed in JavaScript, the style definitions need to be parsed and converted to JavaScript code. This can be done either at runtime, or at build time, incurring a small overhead in bundle size, or computation.
Object Styles syntax

The Object Styles syntax allows us to define styles as regular JavaScript Objects:

// consider "css" being the API of a generic CSS-in-JS library
const heading = css({
  fontSize: "2em",
  color: myTheme.color,
});

We can see that:

  • CSS properties are written in camelCase and string values must be wrapped in quotes;
  • JavaScript values can be referenced as expected;
  • it doesn’t feel like writing CSS, as instead we define styles using a slightly different syntax but with the same property names and values available in CSS (don’t feel intimidated by this, you’ll get used to it in no time);
  • migrating existing CSS would require a rewrite in this new syntax.

Some things to keep in mind:

  • Syntax highlighting comes out-of-the-box, because we’re actually writing JavaScript code.
  • To get code completion, the library must ship CSS types definitions, most of them extending the popular CSSType package.
  • Since the styles are already written in JavaScript, there’s no additional parsing or conversion required.
Library Tagged template Object styles
styled-components
Emotion
Goober
Compiled
Fela 🟠
JSS 🟠
Treat
TypeStyle
Stitches
Styled JSX

✅  Full support         ðŸŸ   Requires plugin          ❌  Unsupported

Styles applying method

Now that we know what options are available for style definition, let’s have a look at how to apply them to our components and elements.

Using a class attribute / className prop

The easiest and most intuitive way to apply the styles is to simply attach them as classnames. Libraries that support this approach provide an API that returns a string which will output the generated unique classnames:

// consider "css" being the API of a generic CSS-in-JS library
const heading_style = css({
  color: "blue"
});

Next, we can take the heading_style, which contains a string of generated CSS class names, and apply it to our HTML element:

// Vanilla DOM usage
const heading = `<h1 class="${heading_style}">Title</h1>`;

// React-specific JSX usage
function Heading() {
  return <h1 className={heading_style}>Title</h1>;
}

As we can see, this method pretty much resembles the traditional styling: first we define the styles, then we attach the styles where we need them. The learning curve is low for anyone who has written CSS before.

Using a <Styled /> component

Another popular method, that was first introduced by the styled-components library (and named after it), takes a different approach.

// consider "styled" being the API for a generic CSS-in-JS library
const Heading = styled("h1")({
  color: "blue"
});

Instead of defining the styles separately and attaching them to existing components or HTML elements, we use a special API by specifying what type of element we want to create and the styles we want to attach to it.

The API will return a new component, having classname(s) already applied, that we can render like any other component in our application. This basically removes the mapping between the component and its styles.

Using the css prop

A newer method, introduced by Emotion, allows us to pass the styles to a special prop, usually named css. This API is available only for JSX-based syntax.

// React-specific JSX syntax
function Heading() {
  return <h1 css=>Title</h1>;
}

This approach has a certain ergonomic benefit, because we don’t have to import and use any special API from the library itself. We can simply pass the styles to this css prop, similarly to how we would use inline styles.

Note that this custom css prop is not a standard HTML attribute, and needs to be enabled and supported via a separate Babel plugin provided by the library.

Library className <Styled /> css prop
styled-components
Emotion
Goober 🟠 2
Compiled 🟠 1
Fela
JSS 🟠 2
Treat
TypeStyle
Stitches 🟠 1
Styled JSX

✅  Full support          ðŸŸ  1  Limited support          ðŸŸ  2  Requires plugin          ❌  Unsupported

Styles output

There are two mutually exclusive methods to generate and ship styles to the browser. Both methods have benefits and downsides, so let’s analyze them in detail.

<style>-injected DOM styles

Most CSS-in-JS libraries inject styles into the DOM at runtime, using either one or more <style> tags, or using the CSSStyleSheet API to manage styles directly within the CSSOM. During SSR, styles are always appended as a <style> tag inside the <head> of the rendered HTML page.

There are a few key advantages and preferred use cases for this approach:

  1. Inlining the styles during SSR provides an increase in page loading performance metrics such as FCP (First Contentful Paint), because rendering is not blocked by fetching a separate .css file from the server.
  2. It provides out-of-the-box critical CSS extraction during SSR by inlining only the styles required to render the initial HTML page. It also removes any dynamic styles, thus further improving loading time by downloading less code.
  3. Dynamic styling is usually easier to implement, as this approach appears to be more suited for highly interactive user interfaces and Single-Page Applications (SPA), where most components are client-side rendered.

The drawbacks are generally related to the total bundle size:

  • an additional runtime library is required for handling dynamic styling in the browser;
  • the inlined SSR styles won’t be cached out-of-the-box and they will need to be shipped to the browser upon each request since they’re part of the .html file rendered by the server;
  • the SSR styles that are inlined in the .html page will be sent to the browser again as JavaScript resources during the rehydration process.
Static .css file extraction

There’s a very small number of libraries that take a totally different approach. Instead of injecting the styles in the DOM, they generate static .css files. From a loading performance point of view, we get the same advantages and drawbacks that come with writing plain CSS files.

  1. The total amount of shipped code is much smaller, since there is no need for additional runtime code or rehydration overhead.
  2. Static .css files benefit from out-of-the-box caching inside the browser, so subsequent requests to the same page won’t fetch the styles again.
  3. This approach seems to be more appealing when dealing with SSR pages or Static Generated pages since they benefit from default caching mechanisms.

However, there are some important drawbacks we need to take note of:

  • The first visit to a page, with an empty cache, will usually have a longer FCP when using this method compared to the one mentioned previously; so deciding if we want to optimize for first-time users or returning visitors could play a crucial role when choosing this approach.
  • All dynamic styles that can be used on the page will be included in the pre-generated bundle, potentially leading to larger .css resources that need to be loaded up front.

Almost all the libraries that we tested implement the first method, injecting the styles into the DOM. The only tested library which supports static .css file extraction is Treat. There are other libraries that support this feature, like Astroturf, Linaria, and style9, which were not included in our final analysis.

Atomic CSS

Some libraries took optimizations one step further, implementing a technique called atomic CSS-in-JS, inspired by frameworks such as Tachyons or Tailwind.

Instead of generating CSS classes containing all the properties that were defined for a specific element, they generate a unique CSS class for each unique CSS property/value combination.

/* classic, non-atomic CSS class */
._wqdGktJ {
  color: blue;
  display: block;
  padding: 1em 2em;
}

/* atomic CSS classes */
._ktJqdG { color: blue; }
._garIHZ { display: block; }
/* short-hand properties are usually expanded */
._kZbibd { padding-right: 2em; }
._jcgYzk { padding-left: 2em; }
._ekAjen { padding-bottom: 1em; }
._ibmHGN { padding-top: 1em; }

This enables a high degree of reusability because each of these individual CSS classes can be reused anywhere in the code base.

In theory, this works really great in the case of large applications. Why? Because there’s a finite number of unique CSS properties that are needed for an entire application. Thus, the scaling factor is not linear, but rather logarithmic, resulting in less CSS output compared to non-atomic CSS.

But there is a catch: individual class names must be applied to each element that requires them, resulting in slightly larger HTML files:

<!-- with classic, non-atomic CSS classes -->
<h1 class="_wqdGktJ">...</h1>

<!-- with atomic CSS classes -->
<h1 class="_ktJqdG _garIHZ _kZbibd _jcgYzk _ekAjen _ibmHGN">...</h1>

So basically, we’re moving code from CSS to HTML. The resulting difference in size depends on too many aspects for us to draw a definite conclusion, but generally speaking, it should decrease the total amount of bytes shipped to the browser.

Conclusion

CSS-in-JS will dramatically change the way we author CSS, providing many benefits and improving our overall development experience.

However, choosing which library to adopt is not straightforward and all choices come with many technical compromises. In order to identify the library that is best suited for our needs, we have to understand the project requirements and its use cases:

  • Are we using React or not? React applications have a wider range of options, while non-React solutions have to use a framework agnostic library.
  • Are we dealing with a highly interactive application, with client-side rendering? In this case, we probably aren’t very concerned about the overhead of rehydration, or care that much about extracting static .css files.
  • Are we building a dynamic website with SSR pages? Then, extracting static .css files may probably be a better option, as it would allow us to benefit from caching.
  • Do we need to migrate existing CSS code? Using a library that supports Tagged Templates would make the migration easier and faster.
  • Do we want to optimize for first-time users or returning visitors? Static .css files offer the best experience for returning visitors by caching the resources, but the first visit requires an additional HTTP request that blocks page rendering.
  • Do we update our styles frequently? All cached .css files are worthless if we frequently update our styles, thus invalidating any cache.
  • Do we re-use a lot of styles and components? Atomic CSS shines if we reuse a lot of CSS properties in our codebase.

Answering the above questions will help us decide what features to look for when choosing a CSS-in-JS solution, allowing us to make more educated decisions.


The post A Thorough Analysis of CSS-in-JS appeared first on CSS-Tricks.

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



from CSS-Tricks https://ift.tt/3fn8YW7
via IFTTT

Amazon is buying MGM Studios for $8.45B

The big media consolidation continues — day after rumors swirled around Amazon’s acquisition of MGM, the online massive retailer confirmed today that it will be acquiring the nearly 100-year-old studio for a cool $8.45 billion.

The deal is another big step toward bolster Amazon’s fight in the streaming wars, with some 4,000 films. The list includes the James Bond and Rocky series and classics ranging from Fargo to Robocop to Silence of the Lambs. Also included are more than 17,000 TV shows. Once the deal closes, the short term impact will be unfettered access for Amazon’s Prime Video platform, giving the service a leg up against rivals like Netflix, Hulu and HBO Max.

As we’ve seen with the launch of studio streaming platforms like Disney+, the deal will also likely result in that content being pulled from competing services, once existing contracts end. “The real financial value behind this deal is the treasure trove of IP in the deep catalog that we plan to reimagine and develop together with MGM’s talented team,” Amazon Studios/Prime Video SVP Mike Hopkins said in a release. “It’s very exciting and provides so many opportunities for high-quality storytelling.”

Amazon also says it will be making efforts to preserve older films. The press material includes all of the standard language you would expect about marrying the old with the new. Here’s MGM Board Chairman, Kevin Ulrich, “I am very proud that MGM’s Lion, which has long evoked the Golden Age of Hollywood, will continue its storied history, and the idea born from the creation of United Artists lives on in a way the founders originally intended, driven by the talent and their vision. The opportunity to align MGM’s storied history with Amazon is an inspiring combination.”

Amazon has, of course, already been making an aggressive push into original content through its own production studio and distribution. On the film side, it has produced notable titles like Manchester By the Sea, which nabbed a screenwriting Oscar and its list of shows includes Transparent. The company is also embarking on a massive (and massively expensive) series based on Lord of Rings.

While Amazon has thrived with massive coffers, MGM’s had a more difficult 21st century. In 2010, the studio filed for a prepackaged Chapter 11 bankruptcy after switching hands several times. The studio was reorganized and its creditors took control.

The deal is just the latest in a flurry of media consolidation, including Disney/Fox, Viacom/CBS and AT&T/Time Warner. As ever with these massive deals, the acquisition is pending all sorts of regulator scrutiny.

 

 



from Amazon – TechCrunch https://ift.tt/3wz2ucj
via IFTTT

Glovo splurges $208M on three Delivery Hero brands in the Balkans

The high stakes game of chess (or, well, consolidation chicken) that is on-demand food delivery rolls on today with a little more territorial swapping in Europe: Barcelona-based Glovo has agreed to buy three of Berlin-based Delivery Hero’s food delivery brands in Central and Eastern Europe — with deals that it said are worth a total value of €170 million (~$208M).

Specifically, it’s picking up Delivery Hero’s foodpanda brand in Romania and Bulgaria; the Donesi brand in Serbia, Montenegro, Bosnia and Herzegovina; and Pauza in Croatia.

There’s some notable symmetry here: Last year Delivery Hero shelled out $272M for a bunch of Glovo’s LatAm brands, as the latter gave up on a region it had already started withdrawing from in its quest for profitability.

Glovo said then that it would be focusing on “key markets where we can build a long-term sustainable business and continue to provide our unique multi-category offering to our customers”.

Earlier this month the Barcelona-based ‘deliver anything’ app also announced it was picking up Ehrana, a local delivery company in Slovenia. So it’s been on quite the (local) shopping spree of late.

Its existing operational footprint covers markets in South West Europe, Eastern Europe and Sub-Saharan Africa. So its attention here, on the Balkans, suggests it sees a chance to eke out profitable potential in more of Central Europe too.

Glovo said the transactions in Bosnia Herzegovina, Bulgaria, Croatia, Montenegro and Serbia are expected to close “within the next few weeks”, subject to fulfilment of closing conditions and relevant regulatory approvals.

While it said Romania will be completed following approval from the competition authority — but gave no timeline for that.

Its splurge on Central and Eastern European rival food delivery brands follows a $528M Series F funding round in April — so it’s evidently not short of VC cash to burn spend.

Commenting in a statement, Oscar Pierre, CEO and co-founder, said: “It’s always been central to our long-term strategy to focus on markets where we see clear opportunities to lead and where we can build a sustainable business. Central and Eastern Europe is a very important part of that plan. The region has really embraced on-demand delivery platforms and we’re very excited to be strengthening our presence and increasing our footprint in countries that continue to show enormous potential for growth.” 

In another supporting statement Delivery Hero made it clear it has bigger fish to fry (than can be served up to hungry customers in the Balkans) right now.

“Delivery Hero has built a clear leading business in the Balkan region in the last couple of years. However, with a lot of operational priorities on our plate, we believe Glovo would be better positioned to continue building an amazing experience for our customers in this region,” said Niklas Östberg, its CEO and co-founder.

A relevant, recent development for Delivery Hero‘s business is the decision to re-enter its home market of Germany — Europe’s biggest economy — under its foodpanda brand, starting in its home city of Berlin this summer (but with a national expansion planned to follow).

This is notable because back in 2018 it sold its German operations to another on-demand food delivery rival, the Dutch giant Takeaway.com — in a $1.1BN deal which included the Lieferheld, Pizza.de and foodora brands — temporarily stepping out of the competitive fray. (Meanwhile Takeaway.com has since merged with the UK’s Just Eat to become… Just Eat Takeaway so, uh, keep up.)

Delivery Hero is returning to Germany now because it can, and because the market is huge. A two-year non-compete clause between it and Just Eat Takeaway recently expired — allowing for reheating (rehashing?) of the competitive food delivery mix in German cities.

Speaking to the FT back in May about this market return, Östberg suggested Delivery Hero has girded itself (and its investors) for a long fight.

“We don’t see necessarily that we are going to go in and win the market in the next year or so. This is a 10-year game,” he said. “Of course we will definitely make sure we put in enough money to be the clear number two, the clear challenger [to Just Eat Takeaway.com].”

Winning at food delivery is certainly a(n expensive) marathon, not a sprint.

There are also of course multiple races being run in markets around the world, depending on local conditions and competitive mix — with the chance that the winner of the biggest and most lucrative races will reach such a position of VC-sponsored glory that it can buy up the top competitors from the smaller races and consolidate everything — maximizing economies of scale and gaining the ability to squeeze out fresh competition to grab a juicy profit for themselves.

Or, well, that’s the theory. Competition regulators are likely to take increasing interest in this space, for one thing. Rising awareness of gig economy workers rights is also putting pressure on the model.

For now, the thin-margin food delivery business needs the right base conditions to survive. The model only functions in cities and ideally in highly dense urban environments. Most of the players in this space also do not employ the armies of riders that are needed to make deliveries — because doing so would make the model far more costly. And in Europe political attention on gig economy workers rights could force reforms that raise regional operational costs, putting further pressure on margins.

Spain has its own labor reforms in train that will affect Glovo in its home market, for example.

Achieving sustainability (i.e. profitability without the need for ongoing VC funding injections) remains a huge hurdle for delivery apps. It will likely require massive market consolidation and/or convincing users to switch from making the occasional order of a hot meal on a weekend to relying on app-based delivery for far more of their local shopping needs — not just lunch/dinner but groceries and toiletries, and other fast moving consumers goods and household items.

It’s notable that super fast grocery delivery is a major focus for Glovo, for example — which has recently been building out networks of inner city dark stores to service in-app convenience store shopping.

Lots of other on-demand app players are also ramping up on that front. Including Delivery Hero — which has been paying more attention to groceries (picking up InstaShop last year in a deal worth $360M).

Glovo building out in Central Europe while exiting markets further afield suggests it believes it can use a concentrated market footprint to drive operational efficiencies and strong order margins through a tightly integrated meal delivery and dark store play.

If it can do that — and offer at least the whiff of profitability — it could make its business an attractive future acquisition target for a larger global giant that’s looking to up the ‘consolidation chicken’ stakes by bolting on new regions.

A larger player like Delivery Hero may even be a potential future suitor — having shown it’s happy to return to markets it left earlier. After all, it surely knows Glovo’s business pretty well since they’ve done a number of market swaps. But, for now, that’s pure speculation.

Zooming out, what the on-demand model of app-based urban convenience means for the future of urban environments is a whole other question — and one which both competition and urban regulators will need to ponder very carefully.

If the rush to scale delivery platforms drives unstoppable consolidation that sees smaller players gobbled up by a few global giants — that can then use their size and scale to outcompete local shops — it may spell even more dark times for the traditional High Street and its family-run bodegas.

Local retail in many places has already been hammered by Internet giants like Amazon. Delivery apps are another high tech threat to bricks-and-mortar shopping. Touch of a button convenience does carry wider costs.

 



from Amazon – TechCrunch https://ift.tt/3fiBoAp
via IFTTT

Tuesday, May 25, 2021

Mostly Clear today!



With a high of F and a low of 53F. Currently, it's 57F and Clear outside.

Current wind speeds: 9 from the Southeast

Pollen: 5

Sunrise: May 25, 2021 at 05:30PM

Sunset: May 26, 2021 at 08:07AM

UV index: 0

Humidity: 66%

via https://ift.tt/2livfew

May 26, 2021 at 09:59AM

Creating Powerful Websites with Serverless-Driven CMS Development

Choosing the right tools to build a website for your organization is essential, but it can be tough to find the right fit. Simple site builders like Wix and Squarespace are easy for marketers to use, but severely limit developers when it comes to customizing site functionality. WordPress is a more robust content management system (CMS), but it requires clunky plugins with infrequent updates and potential security issues

Other site-building tools, such as Gatsby or Hexo, are developer-friendly, but make it very challenging for content creators without technical backgrounds to make even simple updates on their own. These tools often don’t meet the needs of large creative teams running corporate websites.

But there is an option that solves for both audiences. HubSpot’s CMS Hub is a content management system powered by a full CRM Platform that empowers developers to build advanced functionality and marketers to make updates and publish content on their own.

One of the features CMS Hub offers developers is the ability to create serverless functions. These functions make it possible to enhance a website’s backend functionality with integrated plugins that are much easier to develop, deploy, and maintain than third-party WordPress plugins. 

Throughout this article, you’ll get a hands-on look at how HubSpot serverless functions help you build custom websites that clients love. To provide a super-quick preview of what is possible, we’ll fetch news data using GET functions that could be used to populate a dynamic webpage. You’ll need to know some JavaScript to follow along. 

HubSpot Serverless Functions

Serverless architecture enables developers to build and run applications and services without managing the server’s infrastructure. You don’t need to provision, scale, and maintain servers or install and manage databases to host and serve your web applications. When your business grows, it is much easier to scale.

HubSpot’s serverless functions are as powerful as WordPress plugins. They are capable of interacting with HubSpot’s CRM platform as well as integrating with third-party services through APIs. You can use serverless functions to enhance your site, for example:

  • Getting data and storing it in HubDB or the HubSpot CRM
  • Integrating your website with third-party services like Google Forms
  • Creating event registration systems
  • Submitting forms that send data to other services

The functions’ code is stored in the developer file system and is accessed from the Design Manager user interface (UI) or the command-line interface (CLI). You can use the CLI to generate and edit the code locally using your preferred local development tools then upload these changes to HubSpot.

To try the example serverless function in the next section, you need to have access to a CMS Hub Enterprise account or sign up for a free developer testing account. We’ll deploy serverless functions into a site created based on the CMS Boilerplate.

(If you are not familiar with HubSpot development, you may want to check out the quick start guide before following along with the examples below.)

Fetching News Data Using GET Requests

Let’s start by implementing a serverless function that sends a GET request to a third-party REST API to fetch the latest news data using the Axios client. This API searches for online news articles that mention the keyword “HubSpot.”

Note: We’ll be using a third-party API available from NewsAPI.org to retrieve news data, so you first need to register for their API key.

APIs that require authentication or use API keys are not safe for a website’s frontend as they expose your credentials. Serverless functions are a good solution as an intermediary, keeping your credentials secret.

Head over to a CLI and run the following commands:

cd local-cms-dev
mkdir myfunctions
hs create function 

First, we navigate to our local CMS project, and we call the hs create function command to generate a simple boilerplate function.

You’ll be prompted for some information about your functions, such as:

  • Name of the folder where your function will be created. Enter myfunctions/getnews.
  • Name of the JavaScript file for your function. Enter getnews.
  • Select the HTTP method for the endpoint. Select GET.
  • Path portion of the URL created for the function. Enter getnews.

You should get a message saying that a function for the endpoint “/_hcms/API/getnews” has been created. This means, once uploaded, our function will be available from the /_hcms/API/getnews endpoint. 

Before uploading the function, let’s first implement our desired functionality.

Open the myfunctions\getnews.function\getnews.js file. You’ll find some boilerplate code for a serverless function that sends a GET request to the HubSpot search API. Remove the boilerplate code and leave only the following updated code:

const axios = require('axios');
const API_KEY = '<YOUR_API_KEY_HERE>';

exports.main = async (_, sendResponse) => {
   
};

Note that you should normally add your API key via the command-line interface hs secrets command, but adding it here is sufficient for the purpose of demonstrating the function.

We require the Axios library to send HTTP requests, and we export a main function that HubSpot executes when a request is made to the associated endpoint. We also define an API_KEY variable that holds the API key from the news API.

Next, inside the body of the main function, add the following code:

const response = await axios.get(`https://newsapi.org/v2/everything?q=HubSpot&sortBy=popularity&apiKey=${API_KEY}`);
sendResponse({ body: { response: response.data }, statusCode: 200 });

We call Axios to send a GET request to the API endpoint, then we call the sendResponse method to send the fetched data back to the client. We could call this API directly from the frontend code, but we would need to expose our API key, which should be secret. Thanks to the serverless function, fetching data happens on the server side, so we don’t have to expose the secret.

Finally, run the following command to upload your function:

hs upload myfunctions myfunctions

This command uploads files from the myfunctions local folder to a myfunctions folder (that will be created) in your account’s Design Manager.

Finally, run the method by visiting the /_hcms/API/getnews endpoint with your web browser. In our case, we need to visit this link. Here, you should see a list of news articles about HubSpot – albeit without any front-end design. 

While it is beyond the scope of this article, our next step would be to take the data from the NewsAPI and create a template that would allow us to output the news results onto a dynamic webpage. And with this, we’ll have a place where anyone can quickly catch up on all the latest news mentioning HubSpot or any other keyword you decide to include.  

Next Steps

When you need a small brochure-based website and won’t be making many updates, any CMS will do. However, when you are looking to create advanced digital experiences to grow your organization, HubSpot’s CMS Hub offers the functionality and flexibility you need. Plus, you can work with your preferred tools and modern workflows such as CLIs, integrated development environments (IDEs), and GitHub. 

Hopefully, this article has provided an initial glimpse of what is possible with HubSpot’s serverless functions. But don’t stop here, dive in and experiment with adding custom functionality to your own HubSpot-powered website. Your imagination is the limit. Sign up for a free developer test account to get started.

Further reading:


Ahmed Bouchefra is a developer and technical author with a BAC + 5 diploma in software development. Ahmed builds apps and authors technical content about JavaScript, Angular, Ionic, and more.


The post Creating Powerful Websites with Serverless-Driven CMS Development appeared first on CSS-Tricks.

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



from CSS-Tricks https://ift.tt/2SwHiVS
via IFTTT

DC attorney general files antitrust suit against Amazon over third-party seller agreements

Washington, DC Attorney General Karl Racine announced a new antitrust suit against Amazon Tuesday, accusing the company of stifling competition by exerting control over third-party sellers.

The lawsuit, filed in DC Superior Court, alleges that Amazon fixed prices on its massive online retail platform by blocking third-party sellers from selling their products for less elsewhere. Racine argues that this kind of arrangement means that sellers roll Amazon’s hefty fees into their prices, creating an “artificially high” price floor across the online retail market.

That practice may run afoul of the District of Columbia’s Antitrust Act. The AG’s office argues that the practice allows Amazon to exert monopoly power in online retail.

“Amazon has used its dominant position in the online retail market to win at all costs. It maximizes its profits at the expense of third-party sellers and consumers, while harming competition, stifling innovation, and illegally tilting the playing field in its favor,” Racine said.

“We filed this antitrust lawsuit to put an end to Amazon’s illegal control of prices across the online retail market. We need a fair online marketplace that expands options available to District residents and promotes competition, innovation, and choice.”

The DC AG lawsuit is the latest state-level effort to take tech’s most powerful companies down a notch. Facebook and Google are both facing multi-state lawsuits for alleged anticompetitive behavior, even as federal lawmakers creep toward major antitrust reform.



from Amazon – TechCrunch https://ift.tt/2TcG2qZ
via IFTTT

What I Learned Building a Word Game App With Nuxt on Google Play

I fell in love with coding the moment I created my first CSS :hover effect. Years later, that initial bite into interactivity on the web led me to a new goal: making a game.


Those early moments playing with :hover were nothing special, or even useful. I remember making a responsive grid of blue squares (made with float, if that gives you an idea of the timeline), each of which turned orange when the cursor moved over them. I spent what felt like hours mousing over the boxes, resizing the window to watch them change size and alignment, then doing it all over again. It felt like pure magic.

What I built on the web naturally became more complex than that grid of <div> elements over the years, but the thrill of bringing something truly interactive to life always stuck with me. And as I learned more and more about JavaScript, I especially loved making games.

Sometimes it was just a CodePen demo; sometimes it was a small side project deployed on Vercel or Netlify. I loved the challenge of recreating games like color flood, hangman, or Connect Four in a browser.

After a while, though, the goal got bigger: what if I made an actual game? Not just a web app; a real live, honest-to-goodness, download-from-an-app-store game. Last August, I started working on my most ambitious project to date, and four months later, I released it to the world (read: got tired of fiddling with it): a word game app that I call Quina.

What’s the game (and what’s that name)?

The easiest way to explain Quina is: it’s Mastermind, but with five-letter words. In fact, Mastermind is actually a version of a classic pen-and-paper game; Quina is simply another variation on that same original game.

The object of Quina is to guess a secret five-letter word. After each guess, you get a clue that tells you how close your guess is to the code word. You use that clue to refine your next guess, and so on, but you only get ten total guesses; run out and you lose.

Example Quina gameplay

The name “Quina” came about because it means “five at a time” in Latin (or so Google told me, anyway). The traditional game is usually played with four-letter words, or sometimes four digits (or in the case of Mastermind, four colors); Quina uses five-letter words with no repeated letters, so it felt fitting that the game should have a name that plays by its own rules. (I have no idea how the original Latin word was pronounced, but I say it “QUINN-ah,” which is probably wrong, but hey, it’s my game, right?)

I spent my evenings and weekends over the course of about four months building the app. I’d like to spend this article talking about the tech behind the game, the decisions involved, and lessons learned in case this is a road you’re interested in traveling down yourself.

Choosing Nuxt

I’m a huge fan of Vue, and wanted to use this project as a way to expand my knowledge of its ecosystem. I considered using another framework (I’ve also built projects in Svelte and React), but I felt Nuxt hit the sweet spot of familiarity, ease of use, and maturity. (By the way, if you didn’t know or hadn’t guessed: Nuxt could be fairly described as the Vue equivalent of Next.js.)

I hadn’t gone too deep with Nuxt previously; just a couple of very small apps. But I knew Nuxt can compile to a static app, which is just what I wanted — no (Node) servers to worry about. I also knew Nuxt could handle routing as easily as dropping Vue components into a /pages folder, which was very appealing.

Plus, though Vuex (the official state management in Vue) isn’t terribly complex on its own, I appreciated the way that Nuxt adds just a little bit of sugar to make it even simpler. (Nuxt makes things easy in a variety of ways, by the way, such as not requiring you to explicitly import your components before you can use them; you can just put them in the markup and Nuxt will figure it out and auto-import as needed.)

Finally, I knew ahead of time I was building a Progressive Web App (PWA), so the fact that there’s already a Nuxt PWA module to help build out all the features involved (such as a service worker for offline capability) already packaged up and ready to go was a big draw. In fact, there’s an impressive array of Nuxt modules available for any unseen hurdles. That made Nuxt the easiest, most obvious choice, and one I never regretted.

I ended up using more of the modules as I went, including the stellar Nuxt Content module, which allows you to write page content in Markdown, or even a mixture of Markdown and Vue components. I used that feature for the “FAQs” page and the “How to Play” page as well (since writing in Markdown is so much nicer than hard-coding HTML pages).

Achieving native app feel with the web

Quina would eventually find a home on the Google Play Store, but regardless of how or where it was played, I wanted it to feel like a full-fledged app from the get-go.

To start, that meant an optional dark mode, and a setting to reduce motion for optimal usability, like many native apps have (and in the case of reduced motion, like anything with animations should have).

Under the hood, both of the settings are ultimately booleans in the app’s Vuex data store. When true, the setting renders a specific class in the app’s default layout. Nuxt layouts are Vue templates that “wrap” all of your content, and render on all (or many) pages of your app (commonly used for things like shared headers and footers, but also useful for global settings):

<!-- layouts/default.vue -->
<template>
  <div
    :class="[
      {
        'dark-mode': darkMode,
        'reduce-motion': reduceMotion,
      },
      'dots',
    ]"
  >
    <Nuxt />
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['darkMode', 'reduceMotion']),
  },
  // Other layout component code here
}
</script>

Speaking of settings: though the web app is split into several different pages — menu, settings, about, play, etc. — the shared global Vuex data store helps to keep things in sync and feeling seamless between areas of the app (since the user will adjust their settings on one page, and see them apply to the game on another).

Every setting in the app is also synced to both localStorage and the Vuex store, which allows saving and loading values between sessions, on top of keeping track of settings as the user navigates between pages.

And speaking of navigation: moving between pages is another area where I felt there was a lot of opportunity to make Quina feel like a native app, by adding full-page transitions.

Nuxt page transitions in action

Vue transitions are fairly straightforward in general—you just write specifically-named CSS classes for your “to” and “from” transition states—but Nuxt goes a step further and allows you to set full page transitions with only a single line in a page’s Vue file:

<!-- A page component, e.g., pages/Options.vue -->
<script>
export default {
  transition: 'page-slide'
  // ... The rest of the component properties
}
</script>

That transition property is powerful; it lets Nuxt know we want the page-slide transition applied to this page whenever we navigate to or away from it. From there, all we need to do is define the classes that handle the animation, as you would with any Vue transition. Here’s my page-slide SCSS:

/* assets/css/_animations.scss */

.page-slide {
  &-enter-active {
    transition: all 0.35s cubic-bezier(0, 0.25, 0, 0.75);
  }

  &-leave-active {
    transition: all 0.35s cubic-bezier(0.75, 0, 1, 0.75);
  }

  &-enter,
  &-leave-to {
    opacity: 0;
    transform: translateY(1rem);

    .reduce-motion & {
      transform: none !important;
    }
  }

  &-leave-to {
    transform: translateY(-1rem);
  }
}

Notice the .reduce-motion class; that’s what we talked about in the layout file just above. It prevents visual movement when the user has indicated they prefer reduced motion (either via media query or manual setting), by disabling any transform properties (which seemed to warrant usage of the divisive !important flag). The opacity is still allowed to fade in and out, however, since this isn’t really movement.

Comparing default motion (left) with reduced motion (right)

Side note on transitions and handling 404s: The transitions and routing are, of course, handled by JavaScript under the hood (Vue Router, to be exact), but I ran into a frustrating issue where scripts would stop running on idle pages (for example, if the user left the app or tab open in the background for a while). When coming back to those idle pages and clicking a link, Vue Router would have stopped running, and so the link would be treated as relative and 404.

Example: the /faq page goes idle; the user comes back to it and clicks the link to visit the /options page. The app would attempt to go to /faq/options, which of course doesn’t exist.

My solution to this was a custom error.vue page (this is a Nuxt page that automatically handles all errors), where I’d run validation on the incoming path and redirect to the end of the path.

// layouts/error.vue
mounted() {
  const lastPage = '/' + this.$route.fullPath.split('/').pop()
  // Don't create a redirect loop
  if (lastPage !== this.$route.fullPath) {
    this.$router.push({
      path: lastPage,
    })
  }
}

This worked for my use case because a) I don’t have any nested routes; and b) at the end of it, if the path isn’t valid, it still hits a 404.

Vibration and sound

Transitions are nice, but I also knew Quina wouldn’t feel like a native app — especially on a smartphone — without both vibration and sound.

Vibration is relatively easy to achieve in browsers these days, thanks to the Navigator API. Most modern browsers simply allow you to call window.navigator.vibrate() to give the user a little buzz or series of buzzes — or, using a very short duration, a tiny bit of tactile feedback, like when you tap a key on a smartphone keyboard.

Obviously, you want to use vibration sparingly, for a few reasons. First, because too much can easily become a bad user experience; and second, because not all devices/browsers support it, so you need to be very careful about how and where you attempt to call the vibrate() function, lest you cause an error that shuts down the currently running script.

Personally, my solution was to set a Vuex getter to verify that the user is allowing vibration (it can be disabled from the settings page); that the current context is the client, not the server; and finally, that the function exists in the current browser. (ES2020 optional chaining would have worked here as well for that last part.)

// store/getters.js
vibration(state) {
  if (
    process.client &&
    state.options.vibration &&
    typeof window.navigator.vibrate !== 'undefined'
  ) {
    return true
  }
  return false
},

Side note: Checking for process.client is important in Nuxt — and many other frameworks with code that may run on Node — since window won’t always exist. This is true even if you’re using Nuxt in static mode, since the components are validated in Node during build time. process.client (and its opposite, process.server ) are Nuxt niceties that just validate the code’s current environment at runtime, so they’re perfect for isolating browser-only code.

Sound is another key part of the app’s user experience. Rather than make my own effects (which would’ve undoubtedly added dozens more hours to the project), I mixed samples from a few artists who know better what they’re doing in that realm, and who offered some free game sounds online. (See the app’s FAQs for full info.)

Users can set the volume they prefer, or shut the sound off entirely. This, and the vibration, are also set in localStorage on the user’s browser as well as synced to the Vuex store. This approach allows us to set a “permanent” setting saved in the browser, but without the need to retrieve it from the browser every time it’s referenced. (Sounds, for example, check the current volume level each time one is played, and the latency of waiting on a localStorage call every time that happens could be enough to kill the experience.)

An aside on sound

It turns out that for whatever reason, Safari is extremely laggy when it comes to sound. All the clicks, boops and dings would take a noticeable amount of time after the event that triggered them to actually play in Safari, especially on iOS. That was a deal-breaker, and a rabbit hole I spent a good amount of hours despairingly tunneling down.

Fortunately, I found a library called Howler.js that solves cross-platform sound issues quite easily (and that also has a fun little logo). Simply installing Howler as a dependency and running all of the app’s sounds through it — basically one or two lines of code — was enough to solve the issue.

The Howler.js logo, featuring a cute little howler monkey wearing headphones

If you’re building a JavaScript app with synchronous sound, I’d highly recommend using Howler, as I have no idea what Safari’s issue was or how Howler solves it. Nothing I tried worked, so I’m happy just having the issue resolved easily with very little overhead or code modification.

Gameplay, history, and awards

Quina can be a difficult game, especially at first, so there are a couple of ways to adjust the difficulty of the game to suit your personal preference:

  1. You can choose what kind of words you want to get as code words: Basic (common English words), Tricky (words that are either more obscure or harder to spell), or Random (a weighted mix of the two).
  2. You can choose whether to receive a hint at the start of each game, and if so, how much that hint reveals.
Quina offers several different ways to play, to accommodate players of all skill levels

These settings allow players of various skill, age, and/or English proficiency to play the game on their own level. (A Basic word set with strong hints would be the easiest; Tricky or Random with no hints would be the hardest.)

“Soft hints” reveal one letter in the code word (but not its position)

While simply playing a series of one-off games with adjustable difficulty might be enjoyable enough, that would feel more like a standard web app or demo than a real, full-fledged game. So, in keeping with the pursuit of that native app feel, Quina tracks your game history, shows your play statistics in a number of different ways, and offers several “awards” for various achievements.

Quina tracks the results of all games played, your longest win streaks, and many other stats

Under the hood, each game is saved as an object that looks something like this:

{
  guessesUsed: 3,
  difficulty: 'tricky',
  win: true,
  hint: 'none',
}

The app catalogues your games played (again, via Vuex state synced to localStorage) in the form of a gameHistory array of game objects, which the app then uses to display your stats — such as your win/loss ratio, how many games you’ve played, and your average guesses — as well as to show your progress towards the game’s “awards.”

This is all done easily enough with various Vuex getters, each of which utilizes JavaScript array methods, like .filter() and .reduce(), on the gameHistory array. For example, this is the getter that shows how many games the user has won while playing on the “tricky” setting:

// store/getters.js
trickyGamesWon(state) {
  return state.gameHistory.filter(
    (game) => game.win && game.difficulty === 'tricky'
  ).length
},

There are many other getters of varying complexity. (The one to determine the user’s longest win streak was particularly gnarly.)

Adding awards was a matter of creating an array of award objects, each tied to a specific Vuex getter, and each with a requirement.threshold property indicating when that award was unlocked (i.e., when the value returned by the getter was high enough). Here’s a sample:

// assets/js/awards.js
export default [
  {
    title: 'Onset',
    requirement: {
      getter: 'totalGamesPlayed',
      threshold: 1,
      text: 'Play your first game of Quina',
    }
  },
  {
    title: 'Sharp',
    requirement: {
      getter: 'trickyGamesWon',
      threshold: 10,
      text: 'Win ten total games on Tricky',
    },
  },
]

From there, it’s a pretty straightforward matter of looping over the achievements in a Vue template file to get the final output, using its requirement.text property (though there’s a good deal of math and animation added to fill the gauges to show the user’s progress towards achieving the award):

Awards are unlocked and displayed if a player’s progress is above the threshold; otherwise, the game displays the progress bar with an indication of how much is left until the award is unlocked.

There are 25 awards in all (that’s 5 × 5, in keeping with the theme) for various achievements like winning a certain number of games, trying out all the game modes, or even winning a game within your first three guesses. (That one is called “Lucky” — as an added little Easter egg, the name of each award is also a potential code word, i.e., five letters with no repeats.)

Unlocking awards doesn’t do anything except give you bragging rights, but some of them are pretty difficult to achieve. (It took me a few weeks after releasing to get them all!)

Pros and cons of this approach

There’s a lot to love about the “build once, deploy everywhere” strategy, but it also comes with some drawbacks:

Pros

  • You only need to deploy your store app once. After that, all updates can just be website deploys. (This is much quicker than waiting for an app store release.)
  • Build once. This is sorta true, but turned out to be not quite as straightforward as I thought due to Google’s payments policy (more on that later).
  • Everything is a browser. Your app is always running in the environment you’re used to, whether the user realizes it or not.

Cons

  • Event handlers can get really tricky. Since your code is running on all platforms simultaneously, you have to anticipate any and all types of user input at once. Some elements in the app can be tapped, clicked, long-pressed, and also respond differently to various keyboard keys; it can be tricky to handle all of those at once without any of the handlers stepping on each other’s toes.
  • You may have to split experiences. This will depend on what your app is doing, but there were some things I needed to show only for users on the Android app and others that were only for web. (I go into a little more detail on how I solved this in another section below.)
  • Everything is a browser. You’re not worried about what version of Android your users are on, but you are worried about what their default browser is (because the app will use their default browser behind the scenes). Typically on Android this will mean Chrome, but you do have to account for every possibility.

Logistics: turning a web app into a native app

There’s a lot of technology out there that makes the “build for the web, release everywhere” promise — React Native, Cordova, Ionic, Meteor, and NativeScript, just to name a few.

Generally, these boil down to two categories:

  1. You write your code the way a framework wants you to (not exactly the way you normally would), and the framework transforms it into a legitimate native app;
  2. You write your code the usual way, and the tech just wraps a native “shell” around your web tech and essentially disguises it as a native app.

The first approach may seem like the more desirable of the two (since at the end of it all you theoretically end up with a “real” native app), but I also found it comes with the biggest hurdles. Every platform or product requires you to learn its way of doing things, and that way is bound to be a whole ecosystem and framework unto itself. The promise of “just write what you know” is a pretty strong overstatement in my experience. I’d guess in a year or two a lot of those problems will be solved, but right now, you still feel a sizable gap between writing web code and shipping a native app.

On the other hand, the second approach is viable because of a thing called “TWA,” which is what makes it possible to make a website into an app in the first place.

What is a TWA app?

TWA stands for Trusted Web Activity — and since that answer is not likely to be helpful at all, let’s break that down a bit more, shall we?

A TWA app basically turns a website (or web app, if you want to split hairs) into a native app, with the help of a little UI trickery.

You could think of a TWA app as a browser in disguise. It’s an Android app without any internals, except for a web browser. The TWA app is pointed to a specific web URL, and whenever the app is booted, rather than doing normal native app stuff, it just loads that website instead  —  full-screen, with no browser controls, effectively making the website look and behave as though it were a full-fledged native app.

TWA requirements

It’s easy to see the appeal of wrapping up a website in a native app. However, not just any old site or URL qualifies; in order to launch your web site/app as a TWA native app, you’ll need to check the following boxes:

  • Your site/app must be a PWA. Google offers a validation check as part of Lighthouse, or you can check with Bubblewrap (more on that in a bit).
  • You must generate the app bundle/APK yourself; it’s not quite as easy as just submitting the URL of your progressive web app and having all the work done for you. (Don’t worry; we’ll cover a way to do this even if you know nothing about native app development.)
  • You must have a matching secure key, both in the Android app and uploaded to your web app at a specific URL.

That last point is where the “trusted” part comes in; a TWA app will check its own key, then verify that the key on your web app matches it, to ensure it’s loading the right site (presumably, to prevent malicious hijacking of app URLs). If the key doesn’t match or isn’t found, the app will still work, but the TWA functionality will be gone; it will just load the web site in a plain browser, chrome and all. So the key is extremely important to the experience of the app. (You could say it’s a key part. Sorry not sorry.)

Advantages and drawbacks of building a TWA app

The main advantage of a TWA app is that it doesn’t require you to change your code at all — no framework or platform to learn; you’re just building a website/web app like normal, and once you’ve got that done, you’ve basically got the app code done, too.

The main drawback, however, is that (despite helping to usher in the modern age of the web and JavaScript), Apple is not in favor of TWA apps; you can’t list them in the Apple App store. Only Google Play.

This may sound like a deal-breaker, but bear a few things in mind:

  • Remember, to list your app in the first place, it needs to be a PWA — which means it’s installable by default. Users on any platform can still add it to their device’s home screen from the browser. It doesn’t need to be in the Apple App Store to be installed on Apple devices (though it certainly misses out on the discoverability). So you could still build a marketing landing page into your app and prompt users to install it from there.
  • There’s also nothing to prevent you from developing a native iOS app using a completely different strategy. Even if you wanted both iOS and Android apps, as long as a web app is also part of the plan, having a TWA effectively cuts out half of that work.
  • Finally, while iOS has about a 50% market share in predominantly English-speaking countries and Japan, Android has well over 90% of the rest of the world. So, depending on your audience, missing out on the App Store may not be as impactful as you might think.

How to generate the Android App APK

At this point you might be saying, this TWA business sounds all well and good, but how do I actually take my site/app and shove it into an Android app?

The answer comes in the form of a lovely little CLI tool called Bubblewrap.

You can think of Bubblewrap as a tool that takes some input and options from you, and generates an Android app (specifically, an APK, one of the file formats allowed by the Google Play Store) out of the input.

Installing Bubblewrap is a little tricky, and while using it is not quite plug-and-play, it’s definitely far more within reach for an average front-end dev than any other comparable options that I found. The README file on Bubblewrap’s NPM page goes into the details, but as a brief overview:

Install Bubblewrap by running npm i -g @bubblewrap/cli (I’m assuming here you’re familiar with NPM and installing packages from it via the command line). That will allow you to use Bubblewrap anywhere.

Once it’s installed, you’ll run:

bubblewrap init --manifest https://your-webapp-domain/manifest.json

Note: the manifest.json file is required of all PWAs, and Bubblewrap needs the URL to that file, not just your app. Also be warned: depending on how your manifest file is generated, its name may be unique to each build. (Nuxt’s PWA module appends a unique UUID to the file name, for example.)

Also note that by default, Bubblewrap will validate that your web app is a valid PWA as part of this process. For some reason, when I was going through this process, the check kept coming back negative, despite Lighthouse confirming that it was in fact a fully functional progressive web app. Fortunately, Bubblewrap allows you to skip this check with the --skipPwaValidation flag.

If this is your first time using Bubblewrap, it will then ask if you want it to install the Java Development Kit (JDK) and Android Software Development Kit (SDK) for you. These two are the behind-the-scenes utilities required to generate an Android app. If you’re not sure, hit “Y” for yes.

Note: Bubblewrap expects these two development kits to exist in very specific locations, and won’t work properly if they’re not there. You can run bubblewrap doctor to verify, or see the full Bubblewrap CLI README.

After everything’s installed — assuming it finds your manifest.json file at the provided URL — Bubblewrap will ask some questions about your app.

Many of the questions are either preference (like your app’s main color) or just confirming basic details (like the domain and entry point for the app), and most will be pre-filled from your site’s manifest file.

Other questions that may already be pre-filled by your manifest include where to find your app’s various icons (to use as the home screen icon, status bar icon, etc.), what color the splash screen should be while the app is opening, and the app’s screen orientation, in case you want to force portrait or landscape. Bubblewrap will also ask if you want to request permission for your user’s geolocation, and whether you’re opting into Play Billing.

However, there are a few important questions that may be a little confusing, so let’s cover those here:

  • Application ID: This appears to be a Java convention, but each app needs a unique ID string that’s generally 2–3 dot-separated sections (e.g., collinsworth.quina.app). It doesn’t actually matter what this is; it’s not functional, it’s just convention. The only important thing is that you remember it, and that it’s unique. But do note that this will become part of your app’s unique Google Play Store URL. (For this reason, you cannot upload a new bundle with a previously used App ID, so make sure you’re happy with your ID.)
  • Starting version: This doesn’t matter at the moment, but the Play Store will require you to increment the version as you upload new bundles, and you cannot upload the same version twice. So I’d recommend starting at 0 or 1.
  • Display mode: There are actually a few ways that TWA apps can display your site. Here, you most likely want to choose either standalone (full-screen, but with the native status bar at the top), or fullscreen (no status bar). I personally chose the default standalone option, as I didn’t see any reason to hide the user’s status bar in-app, but you might choose differently depending on what your app does.

The signing key

The final piece of the puzzle is the signing key. This is the most important part. This key is what connects your progressive web app to this Android app. If the key the app is expecting doesn’t match what’s found in your PWA, again: your app will still work, but it will not look like a native app when the user opens it; it’ll just be a normal browser window.

There are two approaches here that are a little too complex to go into in detail, but I’ll try to give some pointers:

  1. Generate your own keystore. You can have Bubblewrap do this, or use a CLI tool called keytool (appropriately enough), but either way: be very careful. You need to explicitly track the exact name and passwords for your keystores, and since you’re creating both on the command line, you need to be extremely careful of special characters that could mess up the whole process. (Special characters may be interpreted differently on the command line, even when input as part of a password prompt.)
  2. Allow Google to handle your keys. This honestly isn’t dramatically simpler in my experience, but it saves some of the trouble of wrangling your own signing keys by allowing you to go into the Google Play Developer console, and download a pre-generated key for your app.

Whichever option you choose, there’s in-depth documentation on app signing here (written for Android apps, but most of it is still relevant).

The part where you get the key onto your personal site is covered in this guide to verifying Android app links. To crudely summarize: Google will look for a /.well-known/assetlinks.json file at that exact path on your site. The file needs to contain your unique key hash as well as a few other details:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target" : { "namespace": "android_app", "package_name": "your.app.id",
               "sha256_cert_fingerprints": ["your:unique:hash:here"] }
}]

What you should know about listing an app

Before you get started, there are also some hurdles to be aware of on the app store side of things:

  • First and foremost, you need to sign up before you can publish to the Google Play Store. This eligibility costs a one-time $25 USD fee.
  • Once approved, know that listing an app is neither quick nor easy. It’s more tedious than difficult or technical, but Google reviews every single app and update on the store, and requires you to fill out a lot of forms and info about both yourself and your app before you can even start the review process — which itself can take many days, even if your app isn’t even public yet. (Friendly heads-up: there’s been a “we’re experiencing longer than usual review times” warning banner in the Play console dashboard for at least six months now.)
    • Among the more tedious parts: you must upload several images of your app in action before your review can even begin. These will eventually become the images shown in the store listing — and bear in mind that changing them will also kick off a new review, so come to the table prepared if you want to minimize turnaround time.
    • You also need to provide links to your app’s terms of service and privacy policy (which is the only reason my app even has them, since they’re all but pointless).
    • There are lots of things you can’t undo. For example, you can never change a free app to paid, even if it hasn’t publicly launched yet and/or has zero downloads. You also have to be strict on versioning and naming with what you upload, because Google doesn’t let you overwrite or delete your apps or uploaded bundles, and doesn’t always let you revert other settings in the dashboard, either. If you have a “just jump in and work out the kinks later” approach (like me), you may find yourself starting over from scratch at least once or twice.
  • With a few exceptions, Google has extremely restrictive policies about collecting payments in an app. When I was building, it was charging a 30% fee on all transactions (they’ve since conditionally lowered that to 15% — better, but still five times more than most other payment providers would charge). Google also forces developers (with a few exceptions) to use its own native payment platform; no opting for Square, Stripe, PayPal, etc. in-app.
    • Fun fact: this policy had been announced but wasn’t in effect yet while I was trying to release Quina, and it still got flagged by the reviewer for being in violation. So they definitely take this policy very seriously.

Monetization, unlockables, and getting around Google

While my goal with Quina was mostly personal — challenge myself, prove I could, and learn more about the Vue ecosystem in a complex real-world app — I had also hoped as a secondary goal that my work might be able to make a little money on the side for me and my family.

Not a lot. I never had illusions of building the next Candy Crush (nor the ethical void required to engineer an addiction-fueled micro-transaction machine). But since I had poured hundreds of hours of my time and energy into the game, I had hoped that maybe I could make something in return, even if it was just a little beer money.

Initially, I didn’t love the idea of trying to sell the app or lock its content, so I decided to add a simple “would you care to support Quina if you like it?” prompt after every so many games, and make some of the content unlockable specifically for supporters. (Word sets are limited in size bu default, and some game settings are initially locked as well.) The prompt to support Quina can be permanently dismissed (I’m not a monster), and any donation unlocks everything; no tiered access or benefits.

This was all fairly straightforward to implement thanks to Stripe, even without a server; it’s all completely client-side. I just import a bit of JavaScript on the /support page, using Nuxt’s handy head function (which adds items to the <head> element specifically on the given page):

// pages/support.vue
head() {
  return {
    script: [
      {
        hid: 'stripe',
        src: 'https://js.stripe.com/v3',
        defer: true,
        callback: () => {
          // Adds all Stripe methods like redirectToCheckout to page component
          this.stripe = Stripe('your_stripe_id')
        },
      },
    ],
  }
},

With that bit in place (along with a sprinkle of templating and logic), users can choose their donation amount — set up as products on the Stripe side — and be redirected to Stripe to complete payment, then returned when finished. For each tier, the return redirect URL is slightly different via query parameters. Vue Router parses the URL to adjust the user’s stored donation history, and unlock features accordingly.

You might wonder why I’m revealing all of this, since it exposes the system as fairly easy to reverse-engineer. The answer is: I don’t care. In fact, I added a free tier myself, so you don’t even have to go to the trouble. I decided that if somebody really wanted the unlockables but couldn’t or wouldn’t pay for whatever reason, that’s fine. Maybe they live in a situation where $3 is a lot of money. Maybe they gave on one device already. Maybe they’ll do something else nice instead. But honestly, even if their intentions aren’t good: so what?

I appreciate support, but this isn’t my living, and I’m not trying to build a dopamine tollbooth. Besides, I’m not personally comfortable with the ethical implications of using a stack of totally open-source and/or free software (not to mention the accompanying mountain of documentation, blog posts, and Stack Overflow answers written about all of it) to build a closed garden for personal profit.

So, if you like Quina and can support it: sincerely, thank you. That means a ton to me. I love to see my work being enjoyed. But if not: that’s cool. If you want the “free” option, it’s there for you.

Anyway, this whole plan hit a snag when I learned about Google Play’s new monetization policy, effective this year. You can read it yourself, but to summarize: if you make money through a Google Play app and you’re not a nonprofit, you gotta go through Google Pay and pay a hefty fee — you are not allowed to use any other payment provider.

This meant I couldn’t even list the app; it would be blocked just for having a “support” page with payments that don’t go through Google. (I suppose I probably could have gotten around this by registering a nonprofit, but that seemed like the wrong way to go about it, on a number of levels.)

My eventual solution was to charge for the app itself on Google Play, by listing it for $2.99 (rather than my previously planned price of “free”), and simply altering the app experience for Android users accordingly.

Customizing the app experience for Google Play

Fortunately enough, Android apps send a custom header with the app’s unique ID when requesting a website. Using this header, it was easy enough to differentiate the app’s experience on the web and in the actual Android app.

For each request, the app checks for the Android ID; if present, the app sets a Vuex state boolean called isAndroid to true. This state cascades throughout the app, working to trigger various conditionals to do things like hide and show various FAQ questions, and (most importantly) to hide the support page in the nav menu. It also unlocks all content by default (since the user’s already “donated” on Android, by purchasing). I even went so far as to make simple <WebOnly> and <AndroidOnly> Vue wrapper components to wrap content only meant for one of the two. (Obviously, users on Android who can’t visit the support page shouldn’t see FAQs on the topic, as an example.)

<!-- /src/components/AndroidOnly.vue -->
<template>
  <div v-if="isAndroid">
    <slot />
  </div>
</template>

<script>
export default {
  computed: {
    isAndroid() {
      return this.$store.state.isAndroid
    },
  },
}
</script>

Accounting for accounts

For a time while building Quina, I had Firebase set up for logins and storing user data. I really liked the idea of allowing users to play on all their devices and track their stats everywhere, rather than have a separate history on each device/browser.

In the end, however, I scrapped that idea, for a few reasons. One was complexity; it’s not easy maintaining a secure accounts system and database, even with a nice system like Firebase, and that kind of overhead isn’t something I took lightly. But mainly: the decision boiled down to security and simplicity.

At the end of the day, I didn’t want to be responsible for users’ data. Their privacy and security is guaranteed by using localStorage, at the small cost of portability. I hope players don’t mind the possibility of losing their stats from time to time if it means they have no login or data to worry about. (And hey, it also gives them a chance to earn those awards all over again.)

Plus, it just feels nice. I get to honestly say there’s no way my app can possibly compromise your security or data because it knows literally nothing about you. And also, I don’t need to worry about compliance or cookie warnings or anything like that, either.

Wrapping up

Building Quina was my most ambitious project to date, and I had as much fun designing and engineering it as I have seeing players enjoy it.

I hope this journey has been helpful for you! While getting a web app listed in the Google Play Store has a lot of steps and potential pitfalls, it’s definitely within reach for a front-end developer. I hope you take this story as inspiration, and if you do, I’m excited to see what you build with your newfound knowledge.


The post What I Learned Building a Word Game App With Nuxt on Google Play appeared first on CSS-Tricks.

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



from CSS-Tricks https://ift.tt/3fPDXt0
via IFTTT

Scattered Strong Storms today!

With a high of F and a low of 62F. Currently, it's 66F and Showers in the Vicinity outside. Current wind speeds: 12 from the North ...