parse.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { SourceMapGenerator } from 'source-map'
  2. import { RawSourceMap, TemplateCompiler } from './types'
  3. import {
  4. parseComponent,
  5. VueTemplateCompilerParseOptions,
  6. SFCDescriptor,
  7. DEFAULT_FILENAME
  8. } from './parseComponent'
  9. import hash from 'hash-sum'
  10. import LRU from 'lru-cache'
  11. import { hmrShouldReload } from './compileScript'
  12. import { parseCssVars } from './cssVars'
  13. const cache = new LRU<string, SFCDescriptor>(100)
  14. const splitRE = /\r?\n/g
  15. const emptyRE = /^(?:\/\/)?\s*$/
  16. export interface SFCParseOptions {
  17. source: string
  18. filename?: string
  19. compiler?: TemplateCompiler
  20. compilerParseOptions?: VueTemplateCompilerParseOptions
  21. sourceRoot?: string
  22. sourceMap?: boolean
  23. /**
  24. * @deprecated use `sourceMap` instead.
  25. */
  26. needMap?: boolean
  27. }
  28. export function parse(options: SFCParseOptions): SFCDescriptor {
  29. const {
  30. source,
  31. filename = DEFAULT_FILENAME,
  32. compiler,
  33. compilerParseOptions = { pad: false } as VueTemplateCompilerParseOptions,
  34. sourceRoot = '',
  35. needMap = true,
  36. sourceMap = needMap
  37. } = options
  38. const cacheKey = hash(
  39. filename + source + JSON.stringify(compilerParseOptions)
  40. )
  41. let output = cache.get(cacheKey)
  42. if (output) {
  43. return output
  44. }
  45. if (compiler) {
  46. // user-provided compiler
  47. output = compiler.parseComponent(source, compilerParseOptions)
  48. } else {
  49. // use built-in compiler
  50. output = parseComponent(source, compilerParseOptions)
  51. }
  52. output.filename = filename
  53. // parse CSS vars
  54. output.cssVars = parseCssVars(output)
  55. output.shouldForceReload = prevImports =>
  56. hmrShouldReload(prevImports, output!)
  57. if (sourceMap) {
  58. if (output.script && !output.script.src) {
  59. output.script.map = generateSourceMap(
  60. filename,
  61. source,
  62. output.script.content,
  63. sourceRoot,
  64. compilerParseOptions.pad
  65. )
  66. }
  67. if (output.styles) {
  68. output.styles.forEach(style => {
  69. if (!style.src) {
  70. style.map = generateSourceMap(
  71. filename,
  72. source,
  73. style.content,
  74. sourceRoot,
  75. compilerParseOptions.pad
  76. )
  77. }
  78. })
  79. }
  80. }
  81. cache.set(cacheKey, output)
  82. return output
  83. }
  84. function generateSourceMap(
  85. filename: string,
  86. source: string,
  87. generated: string,
  88. sourceRoot: string,
  89. pad?: 'line' | 'space' | boolean
  90. ): RawSourceMap {
  91. const map = new SourceMapGenerator({
  92. file: filename.replace(/\\/g, '/'),
  93. sourceRoot: sourceRoot.replace(/\\/g, '/')
  94. })
  95. let offset = 0
  96. if (!pad) {
  97. offset = source.split(generated).shift()!.split(splitRE).length - 1
  98. }
  99. map.setSourceContent(filename, source)
  100. generated.split(splitRE).forEach((line, index) => {
  101. if (!emptyRE.test(line)) {
  102. map.addMapping({
  103. source: filename,
  104. original: {
  105. line: index + 1 + offset,
  106. column: 0
  107. },
  108. generated: {
  109. line: index + 1,
  110. column: 0
  111. }
  112. })
  113. }
  114. })
  115. return JSON.parse(map.toString())
  116. }