扩展 HTMLElement:使用 webpack 时构造函数失败
Posted
技术标签:
【中文标题】扩展 HTMLElement:使用 webpack 时构造函数失败【英文标题】:extending HTMLElement: Constructor fails when webpack was used 【发布时间】:2016-12-26 12:33:22 【问题描述】:我将以下 TypeScript 程序转换为 ES5:
文件 1:
class BaseElement extends htmlElement
constructor()
super();
文件 2:
import BaseElement from './BaseElement';
class MyElement extends BaseElement
constructor()
super();
var el = new MyElement();
将所有内容手动放入文件中,代码可以正常工作并在浏览器中执行,HTMLElement 的构建没有问题。但是,一旦我通过 webpack 打包它,我就会收到以下错误消息:
Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
不使用webpack,构造如下JS代码:
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();
使用webpack,构造如下代码:
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();
/***/
/******/ ]);
基本上,webpack 将任何模块放入一个函数中,并在它们之间维护一个导出变量,但是 HTMLElement 的构造失败。没有 webpack(上面的代码),它可以正常工作。
有什么想法吗?
【问题讨论】:
【参考方案1】:你确定它在没有 webpack 的情况下工作吗?通过playground 运行它会出现与您描述的相同的错误(在运行时)。
无论如何,您不应该扩展HTMLElement
。HTMLElement
实际上是打字稿中的一个接口,所以如果有的话,你应该这样实现它。
它在浏览器中作为对象类型存在,但尚未声明为 typescript 类,因此 typescript 无法正确扩展它。
有关解决此问题的方法,请参阅answer。
【讨论】:
看看developers.google.com/web/fundamentals/primers/customelements,我想这是在 ES2015 中扩展 HTMLElement 以使用 Web 组件的首选方式。问题似乎在于 Typescript 如何转译这样的结构。 playground 的 target 选项被自动设置为 ES5,所以它在运行时会抛出同样的错误。【参考方案2】:这是转译问题。如果您正在转译或使用 ES5,则需要为支持原生 Web 组件的浏览器捆绑 native-shim。(https://github.com/webcomponents/custom-elements/blob/master/src/native-shim.js)
ES5 风格的类不适用于原生自定义元素,因为 HTMLElement 构造函数使用
new.target
的值来查找当前调用的构造函数的自定义元素定义。new.target
仅在调用new
时设置,并且仅通过 super() 调用传播。 super() 在 ES5 中不可模拟。SuperClass.call(this)`` only works when extending other ES5-style classes, and does not propagate
new.target`的模式。
查看问题讨论https://github.com/webcomponents/custom-elements/issues/29
【讨论】:
始终欢迎提供潜在解决方案的链接,但请add context around the link,以便您的其他用户知道它是什么以及为什么存在。始终引用重要链接中最相关的部分,以防目标站点无法访问或永久离线。考虑到仅仅是指向外部站点的链接是Why and how are some answers deleted? 的一个可能原因。 在我的项目中添加了native-shim.js
,它解决了这个问题。谢谢【参考方案3】:
ES5 风格的类不适用于原生自定义元素
要解决此问题,只需将 tsconfig.json 文件中的目标更改为 es6。
【讨论】:
谢谢,这行得通。如果您不需要担心 IE,那么这是最好的选择。【参考方案4】:我在这个问题的帮助下解决了这个问题 - https://github.com/facebook/create-react-app/issues/3225
基本上我通过 npm 安装了这两个插件,并在我的 WebPack 配置中为 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']
,
],
【讨论】:
【参考方案5】:用于 Web 组件的 Babel 7 + Webpack 4 配置:
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/
]
使用transform-es2015-classes
插件会在使用 Babel 7 时中断构建过程,因为babel-preset-env
已经包含它。 @babel/plugin-proposal-class-properties
是生命周期回调所必需的。在 Babel 7 中不推荐使用诸如 es2015
之类的年度预设,而是使用 @babel/preset-env
。
【讨论】:
试过这个,但仍然得到“非法构造函数”错误。【参考方案6】:1) Babel 7.6.0
就个人而言,这些特定的 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"
还有这个 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']
]
;
所以基本上我是在告诉 webpack 转译我 /app/scripts 文件夹中的任何 .js 文件,并使用 babel-loader 和 @babel/preset-env
包将它们保存在 /dist/scripts 中。
2) Babel 6.*.0
但是,如果您使用的是 @babel/core
6.*.*
,则可能需要检查此 https://medium.com/@allenhwkim/chrome-browser-custom-element-error-e86db5ae3b8c 。这很简单,在尝试更新我所有的 babel 包之前,我已经成功使用了它。
“所有”你需要做的是npm install babel-plugin-transform-es2015-classes babel-plugin-transform-custom-element-classes --save-dev
,然后将它们添加到你的webpack.config.js(不要忘记npm install --save-dev babel-preset-es2015
):
module:
rules: [
test: /\.js$/,
loader: 'babel-loader',
include: [
path.resolve(__dirname, 'app/scripts')
],
options:
presets: ['es2015'],
plubins: ["transform-custom-element-classes", "transform-es2015-classes"]
]
【讨论】:
以上是关于扩展 HTMLElement:使用 webpack 时构造函数失败的主要内容,如果未能解决你的问题,请参考以下文章
TypeScript——不能将类型“HTMLElement | null”分配给类型“HTMLElement”
TypeScript——不能将类型“HTMLElement | null”分配给类型“HTMLElement”
如何使用 window.setInterval 动态创建 HTMLElement 和更新 innerHTML