parse.ts 2.8 KB

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