a153c4566ac6f962e5a07b64a32b2b7e0b22738d4bcdc7db43bc99a1a01118f54650aef0a45d0a7dbd76b47059d36a03853ad0422ad97bd8e6db3a20218838 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /* @flow */
  2. import deindent from 'de-indent'
  3. import { parseHTML } from 'compiler/parser/html-parser'
  4. import { makeMap } from 'shared/util'
  5. const splitRE = /\r?\n/g
  6. const replaceRE = /./g
  7. const isSpecialTag = makeMap('script,style,template', true)
  8. /**
  9. * Parse a single-file component (*.vue) file into an SFC Descriptor Object.
  10. */
  11. export function parseComponent (
  12. content: string,
  13. options?: Object = {}
  14. ): SFCDescriptor {
  15. const sfc: SFCDescriptor = {
  16. template: null,
  17. script: null,
  18. styles: [],
  19. customBlocks: [],
  20. errors: []
  21. }
  22. let depth = 0
  23. let currentBlock: ?SFCBlock = null
  24. let warn = msg => {
  25. sfc.errors.push(msg)
  26. }
  27. if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
  28. warn = (msg, range) => {
  29. const data: WarningMessage = { msg }
  30. if (range.start != null) {
  31. data.start = range.start
  32. }
  33. if (range.end != null) {
  34. data.end = range.end
  35. }
  36. sfc.errors.push(data)
  37. }
  38. }
  39. function start (
  40. tag: string,
  41. attrs: Array<ASTAttr>,
  42. unary: boolean,
  43. start: number,
  44. end: number
  45. ) {
  46. if (depth === 0) {
  47. currentBlock = {
  48. type: tag,
  49. content: '',
  50. start: end,
  51. attrs: attrs.reduce((cumulated, { name, value }) => {
  52. cumulated[name] = value || true
  53. return cumulated
  54. }, {})
  55. }
  56. if (isSpecialTag(tag)) {
  57. checkAttrs(currentBlock, attrs)
  58. if (tag === 'style') {
  59. sfc.styles.push(currentBlock)
  60. } else {
  61. sfc[tag] = currentBlock
  62. }
  63. } else { // 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
  111. ? '//\n'
  112. : '\n'
  113. return Array(offset).join(padChar)
  114. }
  115. }
  116. parseHTML(content, {
  117. warn,
  118. start,
  119. end,
  120. outputSourceRange: options.outputSourceRange
  121. })
  122. return sfc
  123. }