Polyfill ? CoreJs?

CNHur HyeonBin (Max)
Reponses  01month ago( Both Korean and English Version )

Introduction

It is very common for JavaScript execution environments to not support the latest version of JavaScript, or to only support some or older versions. Polyfill is what solves the problem of JavaScript source code features not working in specific environments.
 
 In other words, a Polyfill is code that is implemented to ensure that code works properly by using JavaScript features that support JavaScript functions or APIs that are not supported in the current JavaScript execution environment. The process of attaching such code to existing code, the process of attaching polyfills, is called polyfilling. Since Babel commonly performs polyfilling together, it's easy to find articles that confuse transpiling with polyfilling and learn by comparing the two together. 
 
Babel is the most famous and popular transpiler for JS, and I understand it as a tool for performing transpiling, but it is said to perform polyfilling. The purpose of transpiling and the purpose of polyfilling seem to often be confusing because they both involve changing code to make non-working code work in specific environments. I too received such questions in interviews at famous large IT companies, and rather than brushing it off lightly, it seems like a good topic to think deeply about and answer for myself at least once.
Can the process of attaching polyfill scripts to source code be called transpiling??
Students studying JS often don't know why they need to understand the difference between ES6+ and ES5, don't understand well what features were added in ES6, and don't know why they need to understand them. In fact, for students who only have experience with toy projects with an extremely small number of users, most users are likely to be their acquaintances or school professors, meaning they have a high probability of only using the latest browsers like Chrome. In other words, because they don't feel the need to perform cross-browsing for older browsers, they write the latest JS syntax without much consideration, and since no problems occurred, they don't feel the necessity to study and understand older JS versions. However, from a company's perspective, the user base is diverse and the browsers they access are varied, so they need to understand polyfills to properly solve cross-browsing issues, and I think they require developers to understand various JS versions to understand this.
 

How to Apply Polyfill Scripts

An actual example of a polyfill script looks like this. 
The latest version of the JS Array wrapper object methods includes flat(). It's simply a method that extracts elements from a 2D array to create a 1D array, for example

const arr = [[1],[2],[3]] 
console.log(arr.flat()) // [1,2,3]

It works like this. However, flat is ES10 JS syntax and doesn't work in older browsers.

What if it's a project developed with React and uses the flat method internally, but an older browser is used? Transpiling and bundling would be successful, the browser would successfully download and execute the JS bundle, but problems would occur during execution, throwing a Runtime error. To solve this, if we attach a polyfill, the code could evolve as follows.

if (!Array.prototype.flat) {
  Array.prototype.flat = function(depth = 1) {
    if (this == null) {
      throw new TypeError('Array.prototype.flat called on null or undefined');
    }
   
    const O = Object(this);

    const len = parseInt(O.length) || 0;
 
    let depthNum = depth === undefined ? 1 : Number(depth);
    if (depthNum < 0) depthNum = 0;
    if (depthNum === Infinity) depthNum = Number.MAX_SAFE_INTEGER;
    

    function flattenDeep(arr, currentDepth) {
      const result = [];
      
      for (let i = 0; i < arr.length; i++) {
        const element = arr[i];
     
        if (Array.isArray(element) && currentDepth > 0) {
          result.push(...flattenDeep(element, currentDepth - 1));
        } else {
          result.push(element);
        }
      }
      
      return result;
    }
    
    return flattenDeep(O, depthNum);
  };
}

const arr = [[1],[2],[3]];
console.log(arr.flat());

Reading the code, polyfill is just a fancy word, it's simply defining a new flat method directly in the wrapper object's prototype. However, during development, you use numerous methods and features in the source code, and newly released JS versions include many new features. It's practically impossible for developers to understand all of these, implement all features through hard coding, and write polyfill scripts directly every time they use new features.

 

What solves this effectively is the Core-Js library.

 

How to Efficiently Load Polyfill Scripts

Core-Js is a JS library that is a collection of polyfill scripts for various JS features. Simply put, instead of developers implementing polyfills directly when using features, they can use already written polyfill scripts from Core-Js.
 
Using the Core-Js library, it would be written as follows:
import 'core-js/features/array/flat';
const arr = [[1],[2],[3]]; 
console.log(arr.flat());
 
It's simpler than expected. You just need to import the desired polyfill script part from Core-js, and the bundler will include the polyfill script in the JS script to be bundled. If the browser cannot use methods like flat(), it will use the loaded polyfill script.
 
There are roughly two ways to load polyfill scripts using Core-Js.
 
1. Loading the entire large branch of Core-Js and using only the necessary parts from the entire branch = 'import 'core-js''
 
2. Loading only the desired parts from the large branches of Core-Js = import 'core-js/features/array/flat'
 
Method 1 imports the entire large branch, so most or all features the developer uses are already loaded, meaning there's not much to worry about regarding polyfills, but it also means loading all unnecessary parts.
 
Method 2 loads only the really desired scripts from within Core-Js, so you can load the minimum polyfill scripts. 
 
In other words, if we understand which features need polyfills added, we can import everything from Core-Js using method 2. This way, we can load only the absolutely necessary polyfill scripts to reduce source code size and effectively respond to cross-browsing issues.
 
- Examples of code loading Polyfill Scripts from Core-js for promise, set, flat, etc
 
However, as projects grow, source code becomes vast, and the methods and features used increase, the amount of core-js that needs to be imported can exceed 100 or 200 lines, and since developers are human, there can be features that are missed. As this repeats, developers inevitably end up importing core-js all at once instead of importing only necessary features, and core-js contains a vast amount of polyfill scripts, and browsers download vast amounts of scripts... The scenario becomes too complex to continue describing. 
 
How can we effectively use polyfill scripts by harmoniously combining methods 1 and 2?
 

How to Efficiently Load Core-Js

Effective Management of Polyfill Scripts through Babel

Among the content mentioned in the introduction earlier, there was content about 'Babel performing polyfilling.' Therefore, if we write in the latest JS syntax as usual and only have the transpiler manage polyfilling, we don't need to memorize all features perfectly, and problems caused by developer mistakes can also be significantly reduced.
 
Taking the most popular IE legacy browser as an example, it can be described like this: Developers write JS source code as usual, tell Babel which browsers it should be compatible with, and command it to respond appropriately.
 
The important part here is that you need to tell Babel the **environment where it should operate**, not a specific JS version. So far, I've explained focusing on JS versions and specific methods or features, but suddenly saying you need to tell Babel about the environment might be confusing. The reason you need to tell Babel about the environment rather than a specific version is that JS browsers very commonly support only specific parts of a specific version, not the entire version. For example, IE11 doesn't support all of ES6, but only specific features within ES6. Therefore, telling Babel 'change it to a certain version' rather than 'change it to work properly in a certain environment' might be more realistic and correct.
 
-> In summary, since browser JS execution environments are concerned with specific features of each JS version rather than JS versions themselves, managing polyfill scripts based on operating environments rather than by version is more realistic and correct.
 
Anyway!
 
You can manage Core-Js polyfill scripts through Babel configuration.
 
module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { ie: 11 } }],
    useBuiltIns: 'usage',
    corejs: 3
  ],
};

 

By telling Babel that it's code that needs to run on IE11 like this, and importing all of core-js in the actual source code, Babel will proceed with the work of leaving only the necessary parts from the already vastly loaded polyfill scripts and removing everything else.
 
In other words, it loads all polyfills in method 1, then proceeds with the work of leaving only method 2 and removing the rest.
 
The advantage of this approach is that it enables very fit source code management for specific browsers. Since polyfill scripts are already loaded this way during build time, when accessing with IE11, there's no need for additional changes to the script, and the already built script can be applied immediately for fast response. However, the disadvantage is that it's 'fit only for specific browsers.' In fact, developers cannot be certain which browser users will access with. Various browsers exist such as Firefox, Chrome, Whale, IE, etc., and various versions exist within various browsers, so storing all of these is not easy. 
 
Specifying a target that fits all versions is like setting media queries in responsive web pages to fit all screens worldwide, and we don't actually configure responsive web this way. If you write perfect media queries for all screen sizes, the CSS length becomes vast, developer mistakes exist, and users have to load unnecessary code they don't use, leading to an inefficient path.
 

Using Professional Polyfill Services

polyfill.io is a free web service created by Financial Times that analyzes User-Agent to detect the browser the user accessed and applies polyfill scripts (loads and applies all polyfills generally needed for that browser) to fit the source code to that browser.

Scenario: 

  1. Developers develop source code without worrying much about polyfills.
  2. Users use specific browsers to run that source code. 
  3. polyfill.io detects the user's browser and changes the source code to fit that browser. 
  4. Users download the source code changed to fit their browser, enabling the web page to work properly.

However, in February 2024, the polyfill.io domain was transferred to China, and since then, serious malware has been spread through polyfill.io, so the service is currently suspended.

As an alternative, the currently available service is polyfill-fastly.io.

The usage is simpler than expected. If you import `https://polyfill-fastly.io/v3/polyfill.min.js` with a script tag in the head or body of the HTML of the project where you want to apply polyfill scripts, you can apply polyfill scripts to the scripts loaded in that HTML. 

Taking a light look at the internal operation: 
  1. Browser Detection Stage: When a user accesses a website, the browser sends a request to the `polyfill-fastly.io` server while including its information in the User-Agent header. For example, IE11 would send information like `Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko`.
  2. Polyfill Calculation Stage: The polyfill-fastly.io server analyzes the received User-Agent information to identify JavaScript features not supported by that browser. For IE11, it would determine that polyfills for `Promise`, `Array.includes()`, `Symbol`, etc. are needed, while for the latest Chrome, it would calculate that almost no polyfills are needed since it supports almost all features.
  3. Script Generation and Response: Based on the calculated results, it combines only the necessary polyfills from the core-js library into a single JavaScript file and responds to the browser. IE11 users would receive hundreds of lines of polyfill code, while Chrome users would receive an almost empty file.
By providing customized polyfills for each browser this way, users of modern browsers don't download unnecessary code, while users of older browsers can use all necessary features.

 


 

Conclusion 

In the case of certain large corporations or specialized IT companies, they directly implement and use polyfill services.
They detect user browsers using libraries that detect JavaScript user browsers, and load polyfill scripts needed for that browser or polyfill scripts needed to make source code suitable for target browsers using core-js.
Through this, they can manage polyfill scripts more suitable for their projects, and proper polyfill scripts are essential work to reduce source code size and prevent unexpected runtime errors. Therefore, I think it's important to have experience actually using them even for small web services. 

CNHur HyeonBin (Max)
Reponses  0