Compiler.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import { Pug } from "../typings/pug";
  2. import pug from "pug";
  3. import Plugin, { PugAST } from "./Plugin";
  4. import pugError from "pug-error";
  5. import lex from "pug-lexer";
  6. import parse from "pug-parser";
  7. import link from "pug-linker";
  8. import codeGen from "pug-code-gen";
  9. export enum CompilationType {
  10. TEMPLATE
  11. }
  12. export interface ICompilerOptions {
  13. /**
  14. * If set to true, the function source will be included in the compiled template
  15. * for better error messages. It is not enabled by default.
  16. */
  17. debug?: boolean;
  18. /**
  19. * The compilation file name.
  20. */
  21. fileName: string;
  22. /**
  23. * Any configurations to be passed to pug.
  24. * @internal Not meant to be used externally.
  25. */
  26. pug?: pug.Options;
  27. }
  28. export class PupperCompiler {
  29. /**
  30. * The contents of the current template being rendered.
  31. */
  32. public contents: string;
  33. public plugin = new Plugin(this);
  34. public compilationType: CompilationType;
  35. constructor(
  36. /**
  37. * Any options to be passed to the compiler.
  38. */
  39. public options: ICompilerOptions
  40. ) {
  41. }
  42. /**
  43. * Makes a compilation error.
  44. * @param code The error code.
  45. * @param message The error message.
  46. * @param data The error data.
  47. * @returns
  48. */
  49. public makeError(code: string, message: string, data: {
  50. line?: number;
  51. column?: number;
  52. } = {}) {
  53. return pugError(code, message, {
  54. ...data,
  55. filename: this.getFileName(),
  56. src: this.contents
  57. } as any);
  58. }
  59. /**
  60. * Makes an error with "PARSE_ERROR" code.
  61. * @param message The error message.
  62. * @param data The error data.
  63. * @returns
  64. */
  65. public makeParseError(message: string, data: {
  66. line?: number;
  67. column?: number;
  68. } = {}) {
  69. return this.makeError("PARSE_ERROR", message, data);
  70. }
  71. /**
  72. * Makes an error with "LEX_ERROR" code.
  73. * @param message The error message.
  74. * @param data The error data.
  75. * @returns
  76. */
  77. public makeLexError(message: string, data: {
  78. line?: number;
  79. column?: number;
  80. } = {}) {
  81. return this.makeError("LEX_ERROR", "Lexer error: " + message, data);
  82. }
  83. protected lexAndParseString(template: string) {
  84. let carrier: any;
  85. const options = this.makePugOptions();
  86. this.plugin.prepareHooks();
  87. try {
  88. this.contents = this.plugin.preLex(template);
  89. carrier = lex(this.contents, {
  90. ...options,
  91. plugins: [this.plugin.lex as any as lex.LexerFunction]
  92. });
  93. carrier = this.plugin.preParse(carrier);
  94. } catch(e) {
  95. throw this.makeLexError(e.message, e);
  96. }
  97. try {
  98. carrier = parse(carrier, this.makePugOptions() as any);
  99. carrier = link(carrier);
  100. carrier = this.plugin.postParse(carrier);
  101. } catch(e) {
  102. throw e instanceof pugError ? e : this.makeParseError(e.message, e);
  103. }
  104. return carrier as PugAST;
  105. }
  106. protected generateJavaScript(ast: Pug.PugAST): string {
  107. ast = this.plugin.preCodeGen(ast);
  108. let code = codeGen(ast, this.makePugOptions());
  109. code = this.plugin.postCodeGen(code);
  110. return code;
  111. }
  112. /**
  113. * Compiles a pupper component into a JavaScript component.
  114. * @param template The template to be compiled.
  115. * @returns
  116. */
  117. public compileComponent(template: string): string {
  118. this.contents = template;
  119. const ast = this.lexAndParseString(template);
  120. let rendered = this.generateJavaScript(ast);
  121. return rendered;
  122. }
  123. /**
  124. * Compiles a pupper template tag into HTML.
  125. * @param template The template to be compiled.
  126. * @returns
  127. */
  128. public compileTemplate(template: string): string {
  129. const pugOptions = this.makePugOptions();
  130. this.contents = template;
  131. this.compilationType = CompilationType.TEMPLATE;
  132. this.plugin.prepareHooks();
  133. const fn = pug.compile(template, pugOptions);
  134. const rendered = fn();
  135. return rendered;///*js*/`function $h(h) { return ${htmlToHs({ syntax: "h" })(rendered)}; }`;
  136. }
  137. public getFileName() {
  138. return this.options.fileName || this.options.pug.filename || "template.pupper";
  139. }
  140. /**
  141. * Sets the internal compiler file name.
  142. * @param fileName The new file name.
  143. * @returns
  144. */
  145. public setFileName(fileName: string) {
  146. this.options.fileName = fileName;
  147. return this;
  148. }
  149. /**
  150. * Make the options for the pug compiler.
  151. */
  152. protected makePugOptions() {
  153. const pugOptions: pug.Options & { filename: string } = {
  154. // We use "$render" as the internal function name.
  155. name: "$render",
  156. filename: this.getFileName(),
  157. compileDebug: this.options.debug || false,
  158. // Always use self to prevent conflicts with other compilers.
  159. self: true,
  160. plugins: []
  161. };
  162. pugOptions.plugins.push(this.plugin);
  163. return pugOptions;
  164. }
  165. }