扩展 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 运行它会出现与您描述的相同的错误(在运行时)。

无论如何,您不应该扩展HTMLElementHTMLElement 实际上是打字稿中的一个接口,所以如果有的话,你应该这样实现它。 它在浏览器中作为对象类型存在,但尚未声明为 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 propagatenew.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

TypeScript 抱怨 HTMLElement 没有 value 属性

如何使用 HTMLElement 以编程方式单击链接元素?

属性 'checked' 在类型 'HTMLElement' 角度 4 上不存在