ComponentHook.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { IPluginNode } from "../../Plugin";
  2. import { Hook } from "../Hook";
  3. import { TagNode } from "../nodes/TagNode";
  4. import { ScriptParser } from "./component/ScriptParser";
  5. import { ConditionalHook } from "./ConditionalHook";
  6. import { StyleAndScriptHook } from "./StyleAndScriptHook";
  7. const DefaultExportSymbol = Symbol("ExportedComponent");
  8. export interface IComponent {
  9. name: string | symbol;
  10. template: string;
  11. script?: string;
  12. setupScript?: string;
  13. style?: string;
  14. exported?: boolean;
  15. }
  16. export class ComponentHook extends Hook {
  17. public $after = [ConditionalHook, StyleAndScriptHook];
  18. /**
  19. * The imports that will later be putted into the template header
  20. */
  21. protected components: Record<string | symbol, IComponent> = {};
  22. public parseComponentNode(node: TagNode) {
  23. const name = node.getAttribute("name")?.replace(/"/g, "");
  24. const template = node.findFirstChildByTagName("template") as TagNode;
  25. const script = node.findFirstChildByTagName("script") as TagNode;
  26. const style = node.findFirstChildByTagName("style") as TagNode;
  27. // If no script tag was found
  28. if (!script) {
  29. throw this.makeError("COMPONENT_HAS_NO_SCRIPT_TAG", "Components must have a a script tag.", {
  30. line: node.getLine(),
  31. column: node.getColumn()
  32. });
  33. }
  34. /**
  35. * Create the component
  36. */
  37. const component: IComponent = {
  38. name,
  39. template: null,
  40. script: null,
  41. style: null,
  42. exported: node.hasAttribute("export")
  43. };
  44. // If the component is not exported and has no name
  45. if (!component.exported && (!name || !name.length)) {
  46. throw new Error("Scoped components must have a name.");
  47. }
  48. // If the component has no name
  49. if (!name || !name.length) {
  50. // Assume it's the default export
  51. component.name = DefaultExportSymbol;
  52. }
  53. // If has a template
  54. if (template) {
  55. this.plugin.parseChildren(template);
  56. let lines = this.plugin.options.contents.split("\n");
  57. const nextNodeAfterTemplate = template.getNextNode();
  58. lines = lines.slice(
  59. template.getLine(),
  60. nextNodeAfterTemplate ? nextNodeAfterTemplate.getLine() - 1 : (node.hasNext() ? node.getNextNode().getLine() - 1 : lines.length)
  61. );
  62. // Detect identation
  63. const identation = /^([\t\n]*) */.exec(lines[0]);
  64. const contents = lines
  65. // Replace the first identation
  66. .map((line) => line.replace(identation[0], ""))
  67. .join("\n");
  68. const templateAsString = this.plugin.compiler.compileTemplate(contents);
  69. component.template = templateAsString;
  70. }
  71. // If has a script
  72. if (script) {
  73. this.plugin.parseChildren(script);
  74. const scriptContent = script.getChildren().map((node) => node.getProp("val")).join("");
  75. component.script = scriptContent;
  76. }
  77. // If has a style
  78. if (style) {
  79. console.log(style);
  80. }
  81. return component;
  82. }
  83. public parse(nodes: IPluginNode[]) {
  84. for(let node of nodes) {
  85. // Check if it's a tag "component" node
  86. if (node.isType("Tag") && node.isName("component")) {
  87. // Parse the component
  88. const component = this.parseComponentNode(node as TagNode);
  89. // Save the component
  90. this.components[component.name] = component;
  91. // Remove the node from the template
  92. node.delete();
  93. continue;
  94. }
  95. }
  96. return nodes;
  97. }
  98. public afterCompile(code: string) {
  99. const exportedComponent = this.components[DefaultExportSymbol];
  100. // Check if has any exported components
  101. if (exportedComponent) {
  102. // Parse the script
  103. const parsedScript = new ScriptParser(
  104. exportedComponent,
  105. this.plugin.getCompilerOptions().filename,
  106. this.components,
  107. this.plugin
  108. ).parse();
  109. code = `${parsedScript}\n`;
  110. if (exportedComponent.style) {
  111. code += `\n${exportedComponent.style}\n`;
  112. }
  113. }
  114. return code;
  115. }
  116. };