Views.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. const logger = require('@overleaf/logger')
  2. const pug = require('pug')
  3. const globby = require('globby')
  4. const Settings = require('@overleaf/settings')
  5. const fs = require('fs')
  6. const Path = require('path')
  7. // Generate list of view names from app/views
  8. function buildViewList() {
  9. return globby
  10. .sync('app/views/**/*.pug', {
  11. onlyFiles: true,
  12. concurrency: 1,
  13. ignore: [
  14. // Ignore includes
  15. '**/_*.pug',
  16. '**/_*/**',
  17. // Ignore shared layout files
  18. 'app/views/layout*',
  19. 'app/views/layout/*',
  20. ],
  21. })
  22. .concat(
  23. globby.sync('modules/*/app/views/**/*.pug', {
  24. onlyFiles: true,
  25. concurrency: 1,
  26. // Ignore includes
  27. ignore: ['**/_*.pug', '**/_*/**'],
  28. })
  29. )
  30. .concat(Object.values(Settings.viewIncludes).flat())
  31. .map(x => Path.resolve(x))
  32. }
  33. const PUG_COMPILE_ARGUMENTS = {
  34. doctype: 'html',
  35. cache: true,
  36. compileDebug: Settings.debugPugTemplates,
  37. inlineRuntimeFunctions: false,
  38. module: true,
  39. }
  40. function precompileViewsAndCacheToDisk() {
  41. const startTime = Date.now()
  42. let success = 0
  43. let precompiled = 0
  44. for (const filename of buildViewList()) {
  45. const precompiledFilename = filename.replace(/\.pug$/, '.js')
  46. try {
  47. const src = pug.compileFileClient(filename, PUG_COMPILE_ARGUMENTS)
  48. try {
  49. if (fs.readFileSync(precompiledFilename, 'utf-8') === src) {
  50. precompiled++
  51. continue
  52. }
  53. } catch {}
  54. fs.writeFileSync(precompiledFilename, src, {
  55. encoding: 'utf-8',
  56. mode: 0o644,
  57. })
  58. success++
  59. } catch (err) {
  60. logger.err({ err, filename }, 'failed to precompile pug template')
  61. throw err
  62. }
  63. }
  64. logger.info(
  65. { timeTaken: Date.now() - startTime, success, precompiled },
  66. 'compiled pug templates'
  67. )
  68. }
  69. module.exports = {
  70. compileViewIncludes(app) {
  71. const viewIncludes = {}
  72. for (const [view, paths] of Object.entries(Settings.viewIncludes)) {
  73. viewIncludes[view] = []
  74. for (const filePath of paths) {
  75. viewIncludes[view].push(
  76. pug.compileFile(filePath, {
  77. ...PUG_COMPILE_ARGUMENTS,
  78. cache: app.enabled('view cache'),
  79. })
  80. )
  81. }
  82. }
  83. return viewIncludes
  84. },
  85. precompileViews(app) {
  86. const startTime = Date.now()
  87. let success = 0
  88. let precompiled = 0
  89. let failures = 0
  90. for (const filename of buildViewList()) {
  91. const precompiledFilename = filename.replace(/\.pug$/, '.js')
  92. if (fs.existsSync(precompiledFilename)) {
  93. logger.debug({ filename }, 'loading precompiled pug template')
  94. try {
  95. pug.cache[filename] = require(precompiledFilename)
  96. precompiled++
  97. continue
  98. } catch (err) {
  99. logger.error(
  100. { filename, err },
  101. 'error loading precompiled pug template'
  102. )
  103. failures++
  104. }
  105. }
  106. try {
  107. logger.warn({ filename }, 'compiling pug template at boot time')
  108. pug.compileFile(filename, PUG_COMPILE_ARGUMENTS)
  109. success++
  110. } catch (err) {
  111. logger.error({ filename, err }, 'error compiling pug template')
  112. failures++
  113. }
  114. }
  115. logger.debug(
  116. { timeTaken: Date.now() - startTime, failures, success, precompiled },
  117. 'compiled pug templates'
  118. )
  119. },
  120. }
  121. if (require.main === module) {
  122. precompileViewsAndCacheToDisk()
  123. process.exit(0)
  124. }