126 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			126 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { SourceMapGenerator } from 'source-map'
 | |
| import {
 | |
|   RawSourceMap,
 | |
|   VueTemplateCompiler,
 | |
|   VueTemplateCompilerParseOptions
 | |
| } from './types'
 | |
| 
 | |
| const hash = require('hash-sum')
 | |
| const cache = new (require('lru-cache'))(100)
 | |
| 
 | |
| const splitRE = /\r?\n/g
 | |
| const emptyRE = /^(?:\/\/)?\s*$/
 | |
| 
 | |
| export interface ParseOptions {
 | |
|   source: string
 | |
|   filename?: string
 | |
|   compiler: VueTemplateCompiler
 | |
|   compilerParseOptions?: VueTemplateCompilerParseOptions
 | |
|   sourceRoot?: string
 | |
|   needMap?: boolean
 | |
| }
 | |
| 
 | |
| export interface SFCCustomBlock {
 | |
|   type: string
 | |
|   content: string
 | |
|   attrs: { [key: string]: string | true }
 | |
|   start: number
 | |
|   end: number
 | |
|   map?: RawSourceMap
 | |
| }
 | |
| 
 | |
| export interface SFCBlock extends SFCCustomBlock {
 | |
|   lang?: string
 | |
|   src?: string
 | |
|   scoped?: boolean
 | |
|   module?: string | boolean
 | |
| }
 | |
| 
 | |
| export interface SFCDescriptor {
 | |
|   template: SFCBlock | null
 | |
|   script: SFCBlock | null
 | |
|   styles: SFCBlock[]
 | |
|   customBlocks: SFCCustomBlock[]
 | |
| }
 | |
| 
 | |
| export function parse(options: ParseOptions): SFCDescriptor {
 | |
|   const {
 | |
|     source,
 | |
|     filename = '',
 | |
|     compiler,
 | |
|     compilerParseOptions = { pad: 'line' } as VueTemplateCompilerParseOptions,
 | |
|     sourceRoot = '',
 | |
|     needMap = true
 | |
|   } = options
 | |
|   const cacheKey = hash(
 | |
|     filename + source + JSON.stringify(compilerParseOptions)
 | |
|   )
 | |
|   let output: SFCDescriptor = cache.get(cacheKey)
 | |
|   if (output) return output
 | |
|   output = compiler.parseComponent(source, compilerParseOptions)
 | |
|   if (needMap) {
 | |
|     if (output.script && !output.script.src) {
 | |
|       output.script.map = generateSourceMap(
 | |
|         filename,
 | |
|         source,
 | |
|         output.script.content,
 | |
|         sourceRoot,
 | |
|         compilerParseOptions.pad
 | |
|       )
 | |
|     }
 | |
|     if (output.styles) {
 | |
|       output.styles.forEach(style => {
 | |
|         if (!style.src) {
 | |
|           style.map = generateSourceMap(
 | |
|             filename,
 | |
|             source,
 | |
|             style.content,
 | |
|             sourceRoot,
 | |
|             compilerParseOptions.pad
 | |
|           )
 | |
|         }
 | |
|       })
 | |
|     }
 | |
|   }
 | |
|   cache.set(cacheKey, output)
 | |
|   return output
 | |
| }
 | |
| 
 | |
| function generateSourceMap(
 | |
|   filename: string,
 | |
|   source: string,
 | |
|   generated: string,
 | |
|   sourceRoot: string,
 | |
|   pad?: 'line' | 'space'
 | |
| ): RawSourceMap {
 | |
|   const map = new SourceMapGenerator({
 | |
|     file: filename.replace(/\\/g, '/'),
 | |
|     sourceRoot: sourceRoot.replace(/\\/g, '/')
 | |
|   })
 | |
|   let offset = 0
 | |
|   if (!pad) {
 | |
|     offset =
 | |
|       source
 | |
|         .split(generated)
 | |
|         .shift()!
 | |
|         .split(splitRE).length - 1
 | |
|   }
 | |
|   map.setSourceContent(filename, source)
 | |
|   generated.split(splitRE).forEach((line, index) => {
 | |
|     if (!emptyRE.test(line)) {
 | |
|       map.addMapping({
 | |
|         source: filename,
 | |
|         original: {
 | |
|           line: index + 1 + offset,
 | |
|           column: 0
 | |
|         },
 | |
|         generated: {
 | |
|           line: index + 1,
 | |
|           column: 0
 | |
|         }
 | |
|       })
 | |
|     }
 | |
|   })
 | |
|   return JSON.parse(map.toString())
 | |
| }
 |