Tutorials on Web Apps

Learn about Web Apps from fellow newline community members!

  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL
  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL

Which Module Formats Should Your JavaScript Library Support?

As a web application grows and more features are added, modularizing the code improves readability and maintainability. In a basic web application, the application fetches and loads JavaScript files by having multiple <script /> tags in an HTML document. Often, these <script /> tags reference libraries from CDNs, such as cdnjs or unpkg , before the bundled application code. This approach involves manually ordering the <script /> tags correctly. For example, if you wanted to use a library like React in the browser (without any additional tooling, just plain HTML, CSS and JavaScript), then you would need to... Continually adding more JavaScript files becomes more tricky because not only do you need to ensure that their dependencies precede them, but also that there are no naming collisions as a result of variables sharing the same global scope and overriding each other. With no native, built-in features for namespacing and modules in early versions of JavaScript language, different module formats have been introduced over the years to fill this void until the ES6 specification, which includes official syntax for writing modules via ECMAScript (ES6) modules. Below, I'm going to provide a brief overview of the following module systems: For developers, understanding each module system allows them to better determine which one best suits their applications' needs. For library authors, choosing the module systems their library should be compatible with depends on the environments where their intended users' applications run. One of the earliest ways of exposing libraries in the web browser, immediately invoked function expression (IIFE) are anonymous functions that run right after being defined. Variables declared within the IIFE are scoped to the anonymous function, not to the global scope. This keeps variables inaccessible from outside the IIFE (restricts access only to the code within the IIFE) and prevents the pollution of the global scope. A common design pattern that leverages IIFEs is the Singleton pattern , which creates a single object instance and namespaces code. This object serves as a single point of access to a specific group of functionality. For real-world examples, look no further than the Math object or jQuery . For example... Writing modules this way is convenient and supported by older browsers. In fact, you can safely concatenate and bundle together multiple files that contain IIFEs without worrying about naming and scoping collisions. For modules with dependencies, you can pass those dependencies as arguments to the IIFE's anonymous function. If the dependency is of a primitive data type, then any changes made to the dependency in the global scope will not affect the IIFE module. If the dependency is an object, then any changes made to the dependency's properties in the global scope will affect the IIFE module, unless you destructure out the properties used in the IIFE module. For the previous example, if we override the Math object's pow method after running the IIFE, then subsequent calls to the module's calculateMethod method will return a different value. If we modify the IIFE module to accept the Math object as a dependency argument, then the module "captures a snapshot" of the object's properties via destructuring. Overriding these properties' values in later statements will have no affect on the module's methods. Nevertheless, IIFE modules load synchronously, which means correctly ordering the module files is critical. Otherwise, the application will break. For large projects, IIFE modules can be difficult to manage, especially if you have many overlapping and nested dependencies. The default module system of Node.js , CommonJS (CJS) uses the require syntax for importing modules and the module.exports and exports syntax for default and named exports respectively. Each file represents a module, and all variables local to the module are private since Node.js wraps the module within a function wrapper. For example, this module... Becomes... The module not only has its variables scoped privately to it, but it still has access to the globals exports , require and module . __filename and __dirname are module scoped and hold the file name and directory name of the module respectively. The require syntax lets you import built-in Node.js or third-party modules locally installed and referenced in package.json (if supplied a module name) or custom modules (if supplied a relative path). ( ./circle.js ) ( Some File in the Same Directory as ./circle.js ) CommonJS require statements are synchronous, which means CommonJS modules are loaded synchronously. Provided the application's single entry point, CommonJS automatically knows how to order the modules and dependencies and handle circular dependencies. If you decide to use CommonJS modules for your client-side applications, then you need additional tooling, such as Browserify , RollupJS or Webpack , to bundle and transpile the modules into a single file that can be understood and ran by a browser. Remember, CommonJS is not part of the official JavaScript specification. Much like IIFEs, CommonJS was not designed for generating small bundle sizes. Bundle size was not factored into CommonJS's design since CommonJS is mostly used for developing server-side applications. For client-side applications, the code must be downloaded first before running it. As a result, larger bundle sizes serve as performance bottlenecks that slow down an application. Plus, without tree-shaking, this makes CommonJS a non-optimal module system for client-side applications. Unlike IIFEs and CommonJS, Asynchronous Module Definition (AMD) asynchronously loads modules and their dependencies. Originating from the Dojo Toolkit, AMD is designed for client-side applications and doesn't require any additional tooling. In fact, all you need to run applications following the AMD module format is the RequireJS library, an in-browser module loader. That's it. Here's a simple example that runs a simple React application, structured with AMD, in the browser. ( index.html ) ( main.js ) Calling the requirejs or define methods registers the factory function (the anonymous function passed as the second argument to these methods). AMD executes this function only after all the dependencies have been loaded and executed. Each module references dependencies by name, not by their global variable. Each dependency name is mapped to the location of the dependency's source code. AMD allows multiple modules to be defined within a single file, and it supports older browsers. However, it is not as popular as more modern module formats, such as ECMAScript modules and Universal Module Definition. For libraries that support both client-side and server-side environments, the Universal Module Definition (UMD) offers a unified solution for making modules compatible with many different module formats, such as CommonJS and AMD. Regardless of whether an application consumes your library as a CommonJS, AMD or IIFE module, UMD conditionally checks for the module format being used at runtime and executes code specific to the detected module format. The UMD template code is an intimidating-looking IIFE, but it is not difficult to understand. Here's UMD in action from React 's development library. Here, the factory function contains the actual library code. ECMAScript modules (ESM), the most recently introduced and recommended module format, is the standard and official way of handling modules in JavaScript. Module bundlers support ESM and optimize the code using techniques like tree-shaking (removes unused code from the final output), which are not supported by other module formats. Loading and parsing modules is asynchronous, but executing them is synchronous. This module format is commonly used in TypeScript applications. Similar to CommonJS, ESM provides multiple ways for exporting code: default exports or named exports. A file can only have one default export ( default export syntax), but it can have any number of named exports ( export function , export const , etc. syntax). ( ./circle.js ) Importing these named exports separately tells the module bundler which portions of the imported module should be included in the outputted code. Any named exports not imported are omitted. This decreases the size of the library, which is useful if your library relies on a few methods from a large utility library like lodash . ( Some File in the Same Directory as ./circle.js ) Ask yourself the following questions while building your library: Once you answer these questions, you can tell the module bundler to output the code in specific module formats for users to consume it within their own libraries/projects. You can also check out our new course, The newline Guide to Creating React Libraries from Scratch , where we teach you everything you need to know to succeed in creating your own library. 

Thumbnail Image of Tutorial Which Module Formats Should Your JavaScript Library Support?

Encapsulated CSS

Photo by  Mika Baumeister  on  Unsplash Most modern frameworks, like React, use components as their foundation. They do this for a few reasons, but a crucial one is that components allow you to break your app into simple single-purpose parts that can then be composed together to solve more complex needs. Unfortunately, CSS was invented to solve problems from the top down,  starting with more general rules to more specific rules . Components encourage you to start from the bottom up, breaking your pages down into the more specific parts first, often in isolation to the whole, and then composing them together.  Many tools and naming conventions have been created to help us maintain our style sheets like BEM, SASS, Less, CSS-Modules, and CSS-in-JS, but they all fall short in the one problem that tooling can never solve:  Which component should be in charge of which styles? The answer to this question is key to making composition work, especially where web layout is concerned. That answer is Encapsulated CSS. Encapsulated CSS is a term that summarizes the rules of how to apply CSS styles in a composable way. It’s based on the programming principle of encapsulation. If we were to define encapsulation in a language-agnostic way, encapsulation involves grouping related things and restricting access to those things except through appropriate channels. For example, many program languages utilize a module system, which follows the principles of encapsulation. When you import a module like React , you get a group of functions that help you build a React application, but you don’t have access to the actual internals that make a React applications work.  Encapsulated CSS is based on that same principle. It’s a methodology that helps group-related styles at the correct component level and only applies styles through appropriate channels. There are two essential principles of Encapsulated CSS: The first principle is:  “Elements do not lay themselves out.”  When I say that elements don’t lay themselves out, I am specifically speaking to an element's position, size, and margin. The second principle is:  “Elements style themselves and layout only their immediate children.”  Properties that involve the border-box and inward are considered part of the component and therefore they should be applied at the element level. This also includes the layout environment of the component’s immediate children. Therefore, it is the parent component’s responsibility to set its direct children's position, size, and margin. If a component shouldn’t set its own layout properties, how do you set them? I mentioned earlier that encapsulation allows you to access properties through appropriate channels. These appropriate channels in components are props and the direct child combinator. In React, we use props as inputs to our component, much like functions use arguments. Our components use these props in our components. Just like we can expose a label or an onClick prop, we can expose layout properties like margin-top or min-width: Using props in this manner works well with one or two properties, but it becomes unwieldy quickly as you expose more and more properties. The props of your component should also be a reflection of what your component does. Having an arbitrary marginLeft prop doesn’t make sense on a calendar component. The other channel for adding styles is using the direct child combinator in the parent component. This tool allows us to select any or all of our parent containers’ immediate children and apply the layout styling we need.  Let’s take the following Blog Post component: Applying styles that follow the principles of Encapsulated CSS, we could do something like this: In the above stylesheet, the .blog-post and .blog-title classes set their own non-layout-related styles. However, when we needed to set layout properties on any of the elements, we used the direct child combinator to select the appropriate children of the .blog-post to set those properties. For example, we use  .blog-post > h2 to select the h2 tags that were direct children of the .blog-post class to set the layout properties we needed. At this point, one might start to ask why bother to go through this ceremony of adding a separate rule for the direct children just to add a layout property? One could argue that you could write less CSS by just applying the layout properties to the corresponding element.  First of all, because we didn’t add any layout properties to the .blog-post class, this blog post component can safely be placed in any context and not interfere with the layout environment that it is being put. The children can also now be refactored into components, if needed, and won’t bring along the baggage of the layout environment it was initially created in. It also helps with debugging. One of the most significant difficulties of debugging CSS is determining where your styles are coming from. It is easier to track down styles if you have rules in place where those style properties originate. If the style you are looking for is layout-related, you know that the style must be on the parent element. Encapsulated CSS is about creating restrictions that make sense and encourages you to write code that is easier to maintain and refactor. By following these rules, your components will become much easier to refactor and compose anywhere in your app.

Thumbnail Image of Tutorial Encapsulated CSS

I got a job offer, thanks in a big part to your teaching. They sent a test as part of the interview process, and this was a huge help to implement my own Node server.

This has been a really good investment!

Advance your career with newline Pro.

Only $30 per month for unlimited access to over 60+ books, guides and courses!

Learn More