Compiler.ts 6.1 KB

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