首页 > 解决方案 > Webpack如何像Angular一样在装饰器中导入html模板

问题描述

我正在为自定义元素编写一个装饰器,主要代码是:

interface CustomElementOptions {
  selector: string,
  template?: { default: string };
}

function CustomElement(options: CustomElementOptions) {
  return (target: CustomElementConstructor) => {
    if(options.template) {
      const template = document.createElement('template');
      template.innerHTML = options.template.default;
      Object.defineProperty(target.prototype, 'template', {
        value: template,
      });
    }
    window.customElements.define(options.selector, target as any);
  };
}

@CustomElement({
  selector: 'test-span',
  template: require('./template.html'),        // <----------------------   I want to change here with `templateUrl`
})
export class SpanElement extends HTMLElement {

  public template?: HTMLTemplateElement;

  constructor() {
    super();
    if(this.template) {
      this.appendChild(this.template.content.cloneNode(true));
    }
  }

}

我现在raw-loader用来加载 html 模板,但这还不够优雅,我想像 Angular:

@CustomElement({
  selector: 'test-span',
  templateUrl: './template.html',        // <----------------------  templateUrl
})
export class SpanElement extends HTMLElement { ... }

但我不能在装饰器中使用 require 或 import ,错误消息如下:

Uncaught Error: Cannot find module './template.html'
    at webpackEmptyContext (app|sync:2)
    at application.ts:10
    at __webpack_modules__../src/app/application.ts.__decorate (index.js:6)
    at Object../src/app/application.ts (application.ts:20)
    at __webpack_require__ (bootstrap:18)
    at Object../src/main.ts (main.ts:1)
    at __webpack_require__ (bootstrap:18)
    at startup:3
    at startup:5

angular-cli 里面有什么魔力?我尽力了,谢谢你的帮助!

标签: angularwebpackraw-loader

解决方案


终于,终于!我找到了解决方案。

简短的回答是更新打字稿 ast。

接下来是我的详细步骤。

一开始我不知道,我想实现自动化require('./template.html'),然后我想到了AST,我ts-loader用于编译打字稿,ts-loader提供getCustomTransformers自定义打字稿转换器,这是我的转换器,请忽略我乱码,这是我第一次工作使用 AST:

{
  loader: 'ts-loader',
  options: {
    configFile: 'tsconfig.json',
    getCustomTransformers: program => {
      return {
        before: [
          context => {
            const isIncludeTemplate = node => {
              return !!node.arguments[0].properties.find(property => property.name.escapedText === 'template');
            };
            const updateCustomElement = node => {
              if(isIncludeTemplate(node)) {
                return node;
              } else {
                const objectLiteralExpression = node.arguments[0];
                objectLiteralExpression.properties.push(
                  factory.createPropertyAssignment(
                    factory.createIdentifier('template'),
                    factory.createPropertyAccessExpression(
                      factory.createCallExpression(
                        factory.createIdentifier('require'),
                        undefined,
                        [objectLiteralExpression.properties.find(property => property.name.escapedText === 'templateUrl').initializer]
                      ),
                      factory.createIdentifier('default')
                    ),
                  ),
                );
                return factory.updateCallExpression(node, node.expression, undefined, [objectLiteralExpression]);
              }
            };
            const visit = node => {
              if(ts.isCallExpression(node) && node.expression.escapedText === 'CustomElement') {
                const newNode = updateCustomElement(node);
                return newNode;
              }
              return ts.visitEachChild(node, child => visit(child), context);
            };
            return node => ts.visitNode(node, visit);
          },
        ],
      };
    },
  },
}

推荐阅读