templateLoader.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. const qs = require('querystring')
  2. const loaderUtils = require('loader-utils')
  3. const { resolveCompiler } = require('../compiler')
  4. const { getDescriptor } = require('../descriptorCache')
  5. const { resolveScript } = require('../resolveScript')
  6. // Loader that compiles raw template into JavaScript functions.
  7. // This is injected by the global pitcher (../pitch) for template
  8. // selection requests initiated from vue files.
  9. module.exports = function (source) {
  10. const loaderContext = this
  11. const filename = this.resourcePath
  12. const ctx = this.rootContext
  13. const query = qs.parse(this.resourceQuery.slice(1))
  14. // although this is not the main vue-loader, we can get access to the same
  15. // vue-loader options because we've set an ident in the plugin and used that
  16. // ident to create the request for this loader in the pitcher.
  17. const options = loaderUtils.getOptions(loaderContext) || {}
  18. const { id } = query
  19. const isServer = loaderContext.target === 'node'
  20. const isProduction =
  21. options.productionMode ||
  22. loaderContext.minimize ||
  23. process.env.NODE_ENV === 'production'
  24. const isFunctional = query.functional
  25. const compilerOptions = Object.assign(
  26. {
  27. outputSourceRange: true
  28. },
  29. options.compilerOptions,
  30. {
  31. scopeId: query.scoped ? `data-v-${id}` : null,
  32. comments: query.comments
  33. }
  34. )
  35. const { compiler, templateCompiler } = resolveCompiler(ctx, loaderContext)
  36. const descriptor = getDescriptor(filename, options, loaderContext)
  37. const script = resolveScript(descriptor, id, options, loaderContext)
  38. // Prettier 3 APIs are all async
  39. // but `vue/compiler-sfc` and `@vue/component-compiler-utils` can only use sync function to format code
  40. let hasCompatiblePrettier = false
  41. try {
  42. const prettier = require('prettier/package.json')
  43. const major = parseInt(prettier.version.split('.')[0], 10)
  44. if (major === 1 || major === 2) {
  45. hasCompatiblePrettier = true
  46. }
  47. } catch (e) {}
  48. // for vue/compiler-sfc OR @vue/component-compiler-utils
  49. const finalOptions = {
  50. source,
  51. filename: this.resourcePath,
  52. compiler: options.compiler || templateCompiler,
  53. compilerOptions,
  54. // allow customizing behavior of vue-template-es2015-compiler
  55. transpileOptions: options.transpileOptions,
  56. transformAssetUrls: options.transformAssetUrls || true,
  57. isProduction,
  58. isFunctional,
  59. optimizeSSR: isServer && options.optimizeSSR !== false,
  60. prettify: options.prettify === undefined ? hasCompatiblePrettier : options.prettify,
  61. bindings: script ? script.bindings : undefined
  62. }
  63. const compiled = compiler.compileTemplate(finalOptions)
  64. // tips
  65. if (compiled.tips && compiled.tips.length) {
  66. compiled.tips.forEach((tip) => {
  67. loaderContext.emitWarning(typeof tip === 'object' ? tip.msg : tip)
  68. })
  69. }
  70. // errors
  71. if (compiled.errors && compiled.errors.length) {
  72. const generateCodeFrame =
  73. (templateCompiler && templateCompiler.generateCodeFrame) ||
  74. compiler.generateCodeFrame
  75. // 2.6 compiler outputs errors as objects with range
  76. if (generateCodeFrame && finalOptions.compilerOptions.outputSourceRange) {
  77. // TODO account for line offset in case template isn't placed at top
  78. // of the file
  79. loaderContext.emitError(
  80. `\n\n Errors compiling template:\n\n` +
  81. compiled.errors
  82. .map(({ msg, start, end }) => {
  83. const frame = generateCodeFrame(source, start, end)
  84. return ` ${msg}\n\n${pad(frame)}`
  85. })
  86. .join(`\n\n`) +
  87. '\n'
  88. )
  89. } else {
  90. loaderContext.emitError(
  91. `\n Error compiling template:\n${pad(compiled.source)}\n` +
  92. compiled.errors.map((e) => ` - ${e}`).join('\n') +
  93. '\n'
  94. )
  95. }
  96. }
  97. const { code } = compiled
  98. // finish with ESM exports
  99. return code + `\nexport { render, staticRenderFns }`
  100. }
  101. function pad(source) {
  102. return source
  103. .split(/\r?\n/)
  104. .map((line) => ` ${line}`)
  105. .join('\n')
  106. }