parser.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import deindent from 'de-indent'
  2. import { parseHTML } from 'compiler/parser/html-parser'
  3. import { makeMap } from 'shared/util'
  4. const splitRE = /\r?\n/g
  5. const replaceRE = /./g
  6. const isSpecialTag = makeMap('script,style,template', true)
  7. /**
  8. * Parse a single-file component (*.vue) file into an SFC Descriptor Object.
  9. */
  10. export function parseComponent(
  11. content: string,
  12. options: Record<string, any> = {}
  13. ): SFCDescriptor {
  14. const sfc: SFCDescriptor = {
  15. template: null,
  16. script: null,
  17. styles: [],
  18. customBlocks: [],
  19. errors: []
  20. }
  21. let depth = 0
  22. let currentBlock: SFCBlock | null = null
  23. let warn: any = msg => {
  24. sfc.errors.push(msg)
  25. }
  26. if (__DEV__ && options.outputSourceRange) {
  27. warn = (msg, range) => {
  28. const data: WarningMessage = { msg }
  29. if (range.start != null) {
  30. data.start = range.start
  31. }
  32. if (range.end != null) {
  33. data.end = range.end
  34. }
  35. sfc.errors.push(data)
  36. }
  37. }
  38. function start(
  39. tag: string,
  40. attrs: Array<ASTAttr>,
  41. unary: boolean,
  42. start: number,
  43. end: number
  44. ) {
  45. if (depth === 0) {
  46. currentBlock = {
  47. type: tag,
  48. content: '',
  49. start: end,
  50. attrs: attrs.reduce((cumulated, { name, value }) => {
  51. cumulated[name] = value || true
  52. return cumulated
  53. }, {})
  54. }
  55. if (isSpecialTag(tag)) {
  56. checkAttrs(currentBlock, attrs)
  57. if (tag === 'style') {
  58. sfc.styles.push(currentBlock)
  59. } else {
  60. sfc[tag] = currentBlock
  61. }
  62. } else {
  63. // custom blocks
  64. sfc.customBlocks.push(currentBlock)
  65. }
  66. }
  67. if (!unary) {
  68. depth++
  69. }
  70. }
  71. function checkAttrs(block: SFCBlock, attrs: Array<ASTAttr>) {
  72. for (let i = 0; i < attrs.length; i++) {
  73. const attr = attrs[i]
  74. if (attr.name === 'lang') {
  75. block.lang = attr.value
  76. }
  77. if (attr.name === 'scoped') {
  78. block.scoped = true
  79. }
  80. if (attr.name === 'module') {
  81. block.module = attr.value || true
  82. }
  83. if (attr.name === 'src') {
  84. block.src = attr.value
  85. }
  86. }
  87. }
  88. function end(tag: string, start: number) {
  89. if (depth === 1 && currentBlock) {
  90. currentBlock.end = start
  91. let text = content.slice(currentBlock.start, currentBlock.end)
  92. if (options.deindent !== false) {
  93. text = deindent(text)
  94. }
  95. // pad content so that linters and pre-processors can output correct
  96. // line numbers in errors and warnings
  97. if (currentBlock.type !== 'template' && options.pad) {
  98. text = padContent(currentBlock, options.pad) + text
  99. }
  100. currentBlock.content = text
  101. currentBlock = null
  102. }
  103. depth--
  104. }
  105. function padContent(block: SFCBlock, pad: true | 'line' | 'space') {
  106. if (pad === 'space') {
  107. return content.slice(0, block.start).replace(replaceRE, ' ')
  108. } else {
  109. const offset = content.slice(0, block.start).split(splitRE).length
  110. const padChar = block.type === 'script' && !block.lang ? '//\n' : '\n'
  111. return Array(offset).join(padChar)
  112. }
  113. }
  114. parseHTML(content, {
  115. warn,
  116. start,
  117. end,
  118. outputSourceRange: options.outputSourceRange
  119. })
  120. return sfc
  121. }