Coder Social home page Coder Social logo

webreflection / babel-plugin-transform-builtin-classes Goto Github PK

View Code? Open in Web Editor NEW
43.0 5.0 2.0 39 KB

A fix for the infamous Babel #4480 bug.

Home Page: https://github.com/babel/babel/issues/4480

License: ISC License

JavaScript 97.53% HTML 2.47%

babel-plugin-transform-builtin-classes's Introduction

Babel Builtin Classes Fix

Build Status donate

About Babel 7

Babel 7 included this plugin in core so you don't need this plugin anymore.


Inspired by Logan Smyth transformer, but actually very different.

This transformer aim is to fix once forever problems with native extends and Custom Elements.

// finally fixed
class List extends Array {}
console.assert(new List instanceof List);

// finally fixed too
class MyElement extends HTMLElement {
  connectedCallback() {
    this.textContent = 'hello';
  }
}

customElements.define('my-element', MyElement);

const node = new MyElement;
console.assert(node.constructor === MyElement);
console.assert(node instanceof MyElement);
console.assert(node instanceof HTMLElement);

document.body.appendChild(node);

Usage

In your Babel 6 configuration, for example in a .babelrc you might have the following, which would enable the plugin and configure it to look for any class extending HTMLElement, Error or Array globals.

{
  "plugins": [
    // either the preset es2015 or at least the following
    "babel-plugin-transform-es2015-classes",
    ["babel-plugin-transform-builtin-classes", {
      "globals": ["Array", "Error", "HTMLElement"]
    }]
  ]
}

However, you can find all known Chrome browser classes already listed in the file lib/.babelrc too.

Rollup

In this case you might need to invert the plugins order:

{
  plugins: [
    ['transform-builtin-classes', {
      globals: ['HTMLElement']
    }],
    'transform-es2015-classes'
    // ... others ...
  ]
}

Compatibility

This transformer works on IE11 and every other browser with Object.setPrototypeOf or __proto__ as fallback.

There is NO IE <= 10 support. If you need IE <= 10 don't use this plugin and/or don't extend natives (recommended).

About logIfPatched option

If you'd like to have a visual feedback when patched classes are encountered, use the logIfPatched: true option.

{
  "plugins": [
    ['transform-builtin-classes', {
      globals: ['Array'],
      logIfPatched: true
    }]
  ]
}

This will output (as console.warn) โœ” builtin extends patched whenever a class is found.

babel-plugin-transform-builtin-classes's People

Contributors

chocolateboy avatar webreflection avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

vincentriemer

babel-plugin-transform-builtin-classes's Issues

_fixBabelExtend only injected upon first compile

Hey!

The plugin only injects the necessary _fixBabelExtend function on "first" compile of the source. Meaning that if the node thread is kept alive, using a 'watch'-command or other, the result is not as expected when the code is recompiled upon save. Assuming some fixIsInjected-boolean kept in memory that never resets?

I have this happening when using gulp-watch to trigger the task, and when just using the --watch flag with babel-cli.

Example package.json:

{
  "dependencies": {
    "babel-plugin-transform-builtin-classes": "^0.3.2",
    "babel-preset-es2015": "^6.24.1"
  },
  "babel": {
    "presets": [
      "es2015"
    ],
    "plugins": [
      ["transform-builtin-classes", {
        "globals": [
          "HTMLElement"
        ]
      }]
    ]
  }
}

Example test.js:

class TestComponent extends HTMLElement {
  connectedCallback () {
    // Hi
  }
}

Example cli command:

babel test.js -o out.js --watch

To get around this issue, I'm having to spawn new threads for each compile.

path.inShadow not found (babel 7 support?)

Hey, thanks for this plugin!

I think now that Babel 7 is out, this plugin might need to be changed to support it? Currently the error I'm seeing is:

ERROR in ./test.js
Module build failed (from ../node_modules/babel-loader/lib/index.js):
TypeError: path.inShadow is not a function
    at ReplaceSupers.ReturnStatement (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/babel-helper-replace-supers/lib/index.js:55:15)
    at NodePath._call (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/@babel/traverse/lib/path/context.js:53:20)
    at NodePath.call (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/@babel/traverse/lib/path/context.js:40:17)
    at NodePath.visit (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/@babel/traverse/lib/path/context.js:88:12)
    at TraversalContext.visitQueue (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/@babel/traverse/lib/context.js:118:16)
    at TraversalContext.visitMultiple (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/@babel/traverse/lib/context.js:85:17)
    at TraversalContext.visit (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/@babel/traverse/lib/context.js:144:19)
    at Function.traverse.node (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/@babel/traverse/lib/index.js:94:17)
    at NodePath.visit (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/@babel/traverse/lib/path/context.js:95:18)
    at TraversalContext.visitQueue (/Users/owenbuckley/Workspace/project-evergreen/repos/todo-app/node_modules/@babel/traverse/lib/context.js:118:16)
 @ multi ../node_modules/webpack-dev-server/client?http://localhost:1981 ./test test[1]

Using .babelrc in conjunection with webpack + babel-loader

{
  "presets": ["@babel/preset-env"],
  "plugins": [
    "babel-plugin-transform-es2015-classes",
    ["babel-plugin-transform-builtin-classes", {
      "globals": ["Array", "Error", "HTMLElement"]
    }]
  ]
}

Thanks and let me know if you need anymore info!

Understanding when to use what.

I think I see: I need this transform in case document-register-element isn't loaded, because in that case the transpiled code won't work with native CE v1 even if we had followed the caveat workarounds. Is this right?

It may be helpful for people to have the following details written somewhere:

  • In which cases using only document-register-element is fine
    • for libraries
    • for apps
    • How does this limit people's abilities to extend classes in this case
  • In which cases using only transform-builtin-classes is fine
    • for libraries
    • for apps
    • How does this limit people's abilities to extend classes in this case
  • In which cases to use both transform-builtin-classes and document-register-element together
    • for libraries
    • for apps
    • How does this limit people's abilities to extend classes in this case

It's been hard to piece everything together by trial and error over the course of a few days and lots of fiddling, so basically I may have forgotten some details, and you probably know it all best.

Not working with Node.js 8.9.0

Hello, I just try this with Node.js 8.9.0, but it doesn't worked.

This is the code am I tried:

.babelrc

{
    "presets": ["env", "flow"],
    "plugins":  [
        "transform-flow-strip-types",
        "babel-plugin-transform-es2015-classes",
        ["babel-plugin-transform-builtin-classes", {
            "globals": ["Error"],
            "logIfPatched": true
        }]
    ]
}

exceptions.js

/* @flow */
class BadRequestError extends Error {
	constructor(message:string) {
		super(message);
	}
}

class InvalidRequestBodyError extends Error {
	constructor(message:string) {
		super(message);
	}
}

class UnauthorizedError extends Error {
	constructor(message:string) {
		super(message);
	}
}

class ExpiredResourceError extends Error {
	constructor(message:string) {
		super(message);
	}
}

class InternalServerError extends Error {
	constructor(message:string) {
		super(message);
	}
}

class ResourceNotFoundError extends Error {
	constructor(message:string) {
		super(message);
	}
}

module.exports = {
	BadRequestError,
	InvalidRequestBodyError,
	UnauthorizedError,
	ExpiredResourceError,
	InternalServerError,
	ResourceNotFoundError
};

index.js

/**
 * Return proper status code
 * @param {Error} error 
 */
function getStatusCode(error:Error):number {
	console.log(error instanceof ResourceNotFoundError);    // Returns false here!!

	if(error instanceof InvalidRequestBodyError) 
		return 400;
	else if(error instanceof BadRequestError)
		return 400;
	else if(error instanceof UnauthorizedError)
		return 401;
	else if(error instanceof ResourceNotFoundError)
		return 404; 
	else if(error instanceof ExpiredResourceError)
		return 410;
	
	return 500;
}

Using [email protected] and [email protected]

IE11 "The custom element being constructed was not registered with 'customElements'"

Hello! This plugin looks like what I've always wanted.
I'm having some issues however.

Custom elements fail to initiate in IE11 with this error:
screenshot 2017-08-01 21 37 21

Working fine in Chrome 59, Firefox 54, Edge 38, and Safari 10

The JS was compiled using gulp-babel with settings:

const gulp = require('gulp')
const babel = require('gulp-babel')
const concat = require('gulp-concat')

gulp.task('default', () => {
  return gulp.src('source/js/**/*.js')
    .pipe(concat('app.js'))
    .pipe(babel({
      presets: [
        'es2015'
      ],
      plugins: [
        ['transform-builtin-classes', {
          'globals': ['Array', 'Error', 'HTMLElement']
        }]
      ]
    }))
    .pipe(gulp.dest('./build/js'))
})

Bare bones HTML and JS for testing:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <script src="https://cdn.rawgit.com/webcomponents/custom-elements/f27a9390/custom-elements.min.js" charset="utf-8"></script>
    <script src="app.js" charset="utf-8"></script>

    <cool-element></cool-element>
  </body>
</html>
class CoolElement extends HTMLElement {
  connectedCallback () {
    this.innerText = 'Hello World'
  }
}
customElements.define('cool-element', CoolElement)

Am I missing anything?

I'm using class factory mixins.

export default
function WebComponent(base) {
  base = base || HTMLElement
  return class WebComponent extends base { ... }
}

A user might pass anything in: HTMLImageElement, HTMLButtonElement, or some class extending from a native one, etc.

Will this still work?

Doesn't seem to work in Chrome v67

.babelrc

{
	"presets": [
		"es2015"
	],
	"plugins": [
		"babel-plugin-transform-es2015-classes",
		[
			"babel-plugin-transform-builtin-classes",
			{
				"globals": [
					"Array",
					"Error",
					"HTMLElement",
					"HTMLButtonElement"
				]
			}
		]
	]
}

packages

"babel-cli": "^6.26.0",
    "babel-core": "^6.26.3",
    "babel-plugin-transform-builtin-classes": "^0.6.1",
    "babel-plugin-transform-es2015-classes": "^6.24.1",
    "babel-preset-env": "^1.7.0",
    "babel-preset-es2015": "^6.24.1",`

input

// finally fixed
class List extends Array {}
console.assert(new List instanceof List);

// finally fixed too
class MyElement extends HTMLElement {
  connectedCallback() {
    this.textContent = 'hello';
  }
}
customElements.define('my-element', MyElement);

const node = new MyElement;
console.assert(node.constructor === MyElement);
console.assert(node instanceof MyElement);
console.assert(node instanceof HTMLElement);

document.body.appendChild(node);`

Output

'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

// finally fixed
var List = function (_Array) {
  _inherits(List, _Array);

  function List() {
    _classCallCheck(this, List);

    return _possibleConstructorReturn(this, (List.__proto__ || Object.getPrototypeOf(List)).apply(this, arguments));
  }

  return List;
}(Array);

console.assert(new List() instanceof List);

// finally fixed too

var MyElement = function (_HTMLElement) {
  _inherits(MyElement, _HTMLElement);

  function MyElement() {
    _classCallCheck(this, MyElement);

    return _possibleConstructorReturn(this, (MyElement.__proto__ || Object.getPrototypeOf(MyElement)).apply(this, arguments));
  }

  _createClass(MyElement, [{
    key: 'connectedCallback',
    value: function connectedCallback() {
      this.textContent = 'hello';
    }
  }]);

  return MyElement;
}(HTMLElement);

customElements.define('my-element', MyElement);

var node = new MyElement();
console.assert(node.constructor === MyElement);
console.assert(node instanceof MyElement);
console.assert(node instanceof HTMLElement);

document.body.appendChild(node);

Assertion failed: console.assert
(anonymous) @ customElementES5.js:24
customElementES5.js:34 Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
at new MyElement (customElementES5.js:34)
at customElementES5.js:49
MyElement @ customElementES5.js:34
(anonymous) @ customElementES5.js:49

Not working in Meteor.

I'm trying to use this by adding a .babelrc file to my Meteor project:

{
    "plugins": [
        ["transform-builtin-classes", {
            "globals": ["HTMLElement"]
        }]
    ]
}

Meteor's Babel setup uses "@babel/plugin-transform-classes": "^7.0.0-beta.39",.

But, nothing happens. My Custom Elements still have the error I'm trying to avoid when HTMLElement is not called as new HTMLElement.

Maybe babel-plugin-transform-builtin-classes isn't working with the new @babel/plugin-transform-classes plugin, which is no longer named babel-plugin-transform-es2015-classes?

It doesn't work in IE11 on windows 7 in combination with babel-polyfill

We use this plugin in production at our company.
But there seems to be a problem.
In Internet explorer, I can not get this to work.
If I do this:

    class List extends Array {}
    console.log(new List());
    console.log(new List(1));
    console.log(new List(1) instanceof List);
    console.log(new List(1) instanceof Array);

I get the following:

image

To reproduce, I installed this virtual box on https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/:

image

Not compatible with document-register-element for IE11

In the line return sPO(new Constructor, Class.prototype);

Constructor doesn't have "this" context as Class.prototype and that why document-register-element can't get constructor from "this" statement.

The different with Reflect in the context of call new Constructor

Using babel-cli

My HTMLElement subclasses are not being wrapped with your helper function, so I tore my project down to the basics. One thing I do not understand is why the plugin doesn't seem to work from the command line.

For example, if I run the following from the root of this project, I do not generate a wrapped HTMLElement.

babel test/cool-element.js 

[feature request] support mixins

Just opening this as a note, but I understand you might not have interest in implementing this. It'd probably take some complicated static analysis.

Another option could be to allow users to explicitly tell the plugin by writing a comment:

function SomeMixin(base) {
  // @transform-builtin-classes extends HTMLElement
  return class Foo extends base { ... }
}

then the plugin could pick up on this cue, and it is better (IMO) than specifying a list in babelrc.

This would solve #5 and #6.

Does not work with mixins

A very common pattern emerging when developing web components is the use of mixins. Whether using functional mixins or class based mixins, one does not typically code this way:

class MyElement extends HTMLElement

Instead, the mixin looks something like this:

import Composable from './composable'
class MyElement extends Composable(HTMLElement).compose( Mixin1, Mixin 2)

<composable.js>
export default ( Base ) => {

    class Composable extends Base { ... }

    return Composable
}

or

var MyElement = Mixin1(Mixin2(HTMLElement))

I'm not sure if the plugin can be written to work with these runtime semantics.

I have tried to create a subclass of HTMLElement called FixedHTMLElement which is passed in to Composable.

class FixedHTMLElement extends HTMLElement {}

class MyElement extends Composable(FixedHTMLElement).compose( Mixin1, Mixin 2)

But this didn't work. Are there any approaches that may work with your plugin for these cases?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.