parser.js 2.8 KB

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