Reputation: 1632
I got the following TypeScript program transpiled to ES5:
File 1:
class BaseElement extends HTMLElement {
constructor() {
super();
}
}
File 2:
import {BaseElement} from './BaseElement';
class MyElement extends BaseElement {
constructor() {
super();
}
}
var el = new MyElement();
Putting everything manually within a file, the code works fine and executes in the browser, the HTMLElement is constructed without problems. However, as soon as I pack it via webpack, I get the following error message:
Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
Without webpack, the following JS code is constructed:
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var BaseElement = (function (_super) {
__extends(BaseElement, _super);
function BaseElement() {
_super.call(this);
}
return BaseElement;
}(HTMLElement));
var MyElement = (function (_super) {
__extends(MyElement, _super);
function MyElement() {
_super.call(this);
}
MyElement.prototype.createdCallback = function () {
this.innerHTML = "lol";
};
return MyElement;
}(BaseElement));
var el = new MyElement();
Using webpack, the following code is constructed:
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
__webpack_require__(1);
__webpack_require__(2);
/***/ },
/* 1 */
/***/ function(module, exports) {
"use strict";
var BaseElement = (function (_super) {
__extends(BaseElement, _super);
function BaseElement() {
_super.call(this);
}
return BaseElement;
}(HTMLElement));
exports.BaseElement = BaseElement;
/***/ },
/* 2 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var BaseElement_1 = __webpack_require__(1);
var MyElement = (function (_super) {
__extends(MyElement, _super);
function MyElement() {
_super.call(this);
}
MyElement.prototype.createdCallback = function () {
this.innerHTML = "lol";
};
return MyElement;
}(BaseElement_1.BaseElement));
exports.MyElement = MyElement;
// TODO: inject
var p = new MyElement();
/***/ }
/******/ ]);
Basically, webpack puts any module into a function and maintains an export variable between them, however the construction of HTMLElement fails. Without webpack (code above), it works fine.
Any ideas?
Upvotes: 29
Views: 23150
Reputation: 4290
It is transpiling issue. If you are transpiling or using ES5 then you need to bundle native-shim for browsers with native Web components support.(https://github.com/webcomponents/custom-elements/blob/master/src/native-shim.js)
If transpiling/using TS then target should be >=es2015
ES5-style classes don't work with native Custom Elements because the HTMLElement constructor uses the value of
new.target
to look up the custom element definition for the currently called constructor.new.target
is only set whennew
is called and is only propagated via super() calls. super() is not emulatable in ES5. The pattern ofSuperClass.call(this)`` only works when extending other ES5-style classes, and does not propagate
new.target`.
Check the issue discussion https://github.com/webcomponents/custom-elements/issues/29
Upvotes: 28
Reputation: 487
1) Babel 7.6.0
Personally, the error seems to be gone with these specific devDependencies :
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"babel-loader": "^8.0.6",
"webpack": "^4.39.3",
"webpack-cli": "^3.3.8"
}
And this webpack.config.js :
var glob = require('glob');
var path = require('path');
module.exports = {
entry: glob.sync('./app/scripts/**.js').reduce(function(obj, el){
obj[path.parse(el).name] = el;
return obj
},{}),
output: {
path: path.resolve(__dirname, './dist/scripts'),
filename: "[name].js"
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: [
path.resolve(__dirname, 'app/scripts')
],
options: {
presets: ['@babel/env']
}
}
]
}
};
So basically I'm telling webpack to transpile any .js file in my /app/scripts folder, and save them in /dist/scripts, using babel-loader, with the @babel/preset-env
package.
2) Babel 6.*.0
However, if you're using @babel/core
6.*.*
, you might want to check this https://medium.com/@allenhwkim/chrome-browser-custom-element-error-e86db5ae3b8c . It's quite simple and I already used it successfully before trying to update all my babel packages.
"All" you need to do is npm install babel-plugin-transform-es2015-classes babel-plugin-transform-custom-element-classes --save-dev
, then add them in your webpack.config.js (don't forget to npm install --save-dev babel-preset-es2015
as well) :
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: [
path.resolve(__dirname, 'app/scripts')
],
options: {
presets: ['es2015'],
plubins: ["transform-custom-element-classes", "transform-es2015-classes"]
}
}
]
}
Upvotes: 0
Reputation: 915
Babel 7 + Webpack 4 config for Web Components:
package.json:
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/plugin-proposal-class-properties": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"babel-loader": "^8.0.5",
"babel-plugin-transform-custom-element-classes": "^0.1.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.1"
}
webpack.config.js:
module: {
rules: [
{
test: /\.js$/,
use:{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
"transform-custom-element-classes",
"@babel/plugin-proposal-class-properties",
]
},
},
exclude: /node_modules/
}
]
}
Using transform-es2015-classes
plugin will break the build process when using Babel 7, since babel-preset-env
already contains it. @babel/plugin-proposal-class-properties
is necessary for the lifecycle callbacks. Using yearly presets such as es2015
is deprecated in Babel 7, use @babel/preset-env
instead.
Upvotes: 0
Reputation: 1571
I fixed this issue with help of this issue - https://github.com/facebook/create-react-app/issues/3225
Basically i installed these 2 plugins via npm and in my WebPack config added those 2 plugins for Babel:
use: [
{
loader: 'babel-loader',
options: {
presets: ['es2015'],
// https://github.com/facebook/create-react-app/issues/3225
plugins: ['transform-custom-element-classes', 'transform-es2015-classes']
},
}
],
Upvotes: 1
Reputation: 301
ES5-style classes don't work with native Custom Elements
To do a work around, just change the target in the tsconfig.json file to es6.
Upvotes: 20
Reputation: 422
Are you sure it's working without webpack? Running it through the playground gives the same error as you described (at runtime).
Anyway, you shouldn't be extending the HTMLElement
.
The HTMLElement
is actually an interface in typescript, so if anything you should be implementing it as such.
It exists as an object type in the browser, but it hasn't been declared as a typescript class, so typescript isn't able to properly extend it.
For ways around this issue, see this answer.
Upvotes: 0