Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is the global promise polyfill implemented in webpack v2?

I'd like to better understand the differences between how promises are implemented in webpack. Normally, blissful ignorance was enough to get by as I mostly develop apps, but I am definately a little confused in how to properly develop a plugin/tool/lib.


In creating apps the two following approaches never caused any issues; I guess mostly cause it didn't matter

webpack.config.js - using babel-polyfill as an entry point

module.exports = {
  entry: {
    foo: [
      'core-js/fn/promise',          <-- here
      './js/index.js'
    ]
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader'
      }
    ]
  }
}

Q: In this approach, since it's a polyfill it modifies the global Promise?

webpack config - shimming using webpacks provide plugin

module.exports = {
  entry: './js/index.js',

  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader'
      }
    ]
  },

  plugins: [
    new webpack.ProvidePlugin({
      Promise: 'es6-promise'          <-- here
    })
  ]
};

Q: Does this mean that the Promise is a module only specific to the webpack bundling process? Does the transpiled ES5 code have a local copy or es6-promise? Does it patch the global Promise?


In regards to creating a jquery plugin/tool/lib which is using babel for transpilation...

webpack.config.js - using babel-plugin-transform-runtime

module.exports = {
  entry: {
    foo: [
      './js/start.js'
    ]
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader'
      }
    ]
  }
}

.babelrc

{
  "presets": [ "es2015" ],
  "plugins": ["transform-runtime"]     <--here
}

start.js

require('babel-runtime/core-js/promise').default = require('es6-promise');  <--here
require('plugin');

Q: This aliases the es6-promise to the babel-runtime promise and is not global but only local to the tool?

like image 614
Jeff Avatar asked Dec 24 '22 18:12

Jeff


1 Answers

Polyfill in webpack entry

entry: ['core-js/fn/promise', './index.js']

This has the same effect as if you imported it at the top of your entry point.

In this approach, since it's a polyfill it modifies the global Promise?

Yes, this polyfill changes the global Promise. Calling it a polyfill usually means that it patches the global built-ins, although this is not strictly adhered to. If they don't change existing APIs but only provide the functionality, they are sometimes called Ponyfills.

Webpack shimming with ProvidePlugin

new webpack.ProvidePlugin({
  Promise: 'es6-promise'
})

The ProvidePlugin will import the configured module at the beginning of the module that uses it when the corresponding free variable is found. A free variable is an identifier that has not been declared in the current scope. Global variables are free variables in all local scopes.

Whenever a free Promise is encountered, webpack will add the following to the beginning of the module:

var Promise = require('es6-promise');

Does this mean that the Promise is a module only specific to the webpack bundling process?

That is correct, because ProvidePlugin is webpack specific and it's very unlikely that any other tool will respect any webpack settings.

Does the transpiled ES5 code have a local copy or es6-promise?

As with any other module, it is included once by webpack and all imports refer to that module.

Does it patch the global Promise?

It will only modify the global Promise if the imported module does it explicitly. The es6-promise you're using, does not patch the global by default as shown in Auto-polyfill.

Babel transform runtime

{
  "plugins": ["transform-runtime"]
}

The babel-plugin-transform-runtime uses core-js to provide missing functionalities like Promise. As you will recall, I said that core-js modifies the global Promise. This is not true for this case, because babel uses the version that doesn't pollute the global namespace, which is in core-js/library as mentioned in the core-js README. For example:

const Promise = require('core-js/library/fn/promise');

Babel will import the core-js Promise and replace Promise with the imported variable. See also the example in babel-plugin-transform-runtime - core-js aliasing. This is essentially the same thing as webpack's ProvidePlugin except that babel does not bundle up the modules, so it's just adding the import.

This aliases the es6-promise to the babel-runtime promise and is not global but only local to the tool?

It is not global because it's just a module. Babel takes your JavaScript and outputs some other JavaScript where the configured features are transpiled to ES5. You will run or bundle the resulting JavaScript and it's practically the same as if you had written ES5 in the first place.

require('babel-runtime/core-js/promise').default = require('es6-promise');

With that you modify the export and therefore the modules will use es6-promise instead. But overwriting an export is not a good idea, especially since the imports of ES modules are immutable in the spec. Babel is currently not spec-compliant in that regard. For more details see Making transpiled ES modules more spec-compliant.


Which one should you use?

It depends on what you're doing. Apart from the difference of whether they change globals or not, you can choose whichever you prefer. For instance using babel's transform runtime allows you to use it with any tool that uses babel, not just webpack.

For a library

None.

Leave the polyfill to the application developer. But you might mention that it depends on a certain feature and when the user wants to use the library in an environment that doesn't support the feature, they have to polyfill it. It's also fairly reasonable to assume that Promises are widely supported and if an application targets older environments, they will very likely have polyfilled it already. Keep in mind that this doesn't mean that you shouldn't transpile new features / syntax. This is specifically for new functionality like Promise or String.prototype.trimLeft.

For a tool

That also depends on your definition of a tool. Let's assume a tool is a piece of software that is used by developers (e.g. webpack, eslint, etc.). In that case it is exactly the same as for any app, at the end of the day it's just another app but only targeting developers. Specifically speaking about command line tools, you should decide what minimum Node version you want to support and include anything that is needed for that, you can specify that in your package.json in the engines field.

For a plugin

Plugin is a very broad term and can be anything between a library and an app. For example a webpack plugin or loader should work as is, whereas a jQuery plugin will be part of a web app and you should treat it as a library (they should probably be called library instead of plugin). Generally you want to match the guidelines of whatever you're extending. Have a look at it and see what they are targeting. For example webpack currently supports Node verions >=4.3.0, so your plugin should too.

like image 98
Michael Jungo Avatar answered Jan 02 '23 14:01

Michael Jungo