Evan You
9 years ago
14 changed files with 255 additions and 112 deletions
@ -0,0 +1,70 @@ |
|||
/* @flow */ |
|||
|
|||
import { parseHTML } from './html-parser' |
|||
import { makeMap } from 'shared/util' |
|||
import deindent from 'de-indent' |
|||
|
|||
const isSpecialTag = makeMap('script,style,template', true) |
|||
|
|||
/** |
|||
* Parse a single-file component (*.vue) file into an SFC Descriptor Object. |
|||
*/ |
|||
export function parseSFC (content: string): SFCDescriptor { |
|||
const sfc: SFCDescriptor = { |
|||
template: null, |
|||
script: null, |
|||
styles: [] |
|||
} |
|||
let depth = 0 |
|||
let currentBlock |
|||
|
|||
function start (tag, attrs) { |
|||
depth++ |
|||
if (depth > 1) { |
|||
return |
|||
} |
|||
if (isSpecialTag(tag)) { |
|||
const block: SFCBlock = currentBlock = { |
|||
type: tag, |
|||
content: '' |
|||
} |
|||
for (let i = 0; i < attrs.length; i++) { |
|||
const attr = attrs[i] |
|||
if (attr.name === 'lang') { |
|||
block.lang = attr.value |
|||
} |
|||
if (attr.name === 'scoped') { |
|||
block.scoped = true |
|||
} |
|||
if (attr.name === 'src') { |
|||
block.src = attr.value |
|||
} |
|||
} |
|||
if (tag === 'style') { |
|||
sfc.styles.push(block) |
|||
} else { |
|||
sfc[tag] = block |
|||
} |
|||
} |
|||
} |
|||
|
|||
function end () { |
|||
depth-- |
|||
currentBlock = null |
|||
} |
|||
|
|||
function chars (text) { |
|||
if (currentBlock) { |
|||
currentBlock.content = deindent(text) |
|||
} |
|||
} |
|||
|
|||
parseHTML(content, { |
|||
isSpecialTag, |
|||
start, |
|||
end, |
|||
chars |
|||
}) |
|||
|
|||
return sfc |
|||
} |
@ -1,104 +1,19 @@ |
|||
/* @flow */ |
|||
|
|||
import { extend, genStaticKeys, noop } from 'shared/util' |
|||
import { warn } from 'core/util/debug' |
|||
import { compile as baseCompile } from 'compiler/index' |
|||
import { compile as baseCompile } from 'web/compiler/index' |
|||
import { detectErrors } from 'compiler/error-detector' |
|||
import modules from 'web/compiler/modules/index' |
|||
import directives from 'web/compiler/directives/index' |
|||
import { isIE, isReservedTag, isUnaryTag, mustUseProp, getTagNamespace } from 'web/util/index' |
|||
|
|||
// detect possible CSP restriction
|
|||
/* istanbul ignore if */ |
|||
if (process.env.NODE_ENV !== 'production') { |
|||
try { |
|||
new Function('return 1') |
|||
} catch (e) { |
|||
if (e.toString().match(/unsafe-eval|CSP/)) { |
|||
warn( |
|||
'It seems you are using the standalone build of Vue.js in an ' + |
|||
'environment with Content Security Policy that prohibits unsafe-eval. ' + |
|||
'The template compiler cannot work in this environment. Consider ' + |
|||
'relaxing the policy to allow unsafe-eval or pre-compiling your ' + |
|||
'templates into render functions.' |
|||
) |
|||
} |
|||
} |
|||
} |
|||
|
|||
type CompiledFunctions = { |
|||
render: Function, |
|||
staticRenderFns: Array<Function> |
|||
} |
|||
|
|||
const cache1: { [key: string]: CompiledFunctions } = Object.create(null) |
|||
const cache2: { [key: string]: CompiledFunctions } = Object.create(null) |
|||
|
|||
export const baseOptions: CompilerOptions = { |
|||
isIE, |
|||
expectHTML: true, |
|||
preserveWhitespace: true, |
|||
modules, |
|||
staticKeys: genStaticKeys(modules), |
|||
directives, |
|||
isReservedTag, |
|||
isUnaryTag, |
|||
mustUseProp, |
|||
getTagNamespace |
|||
} |
|||
export { parseSFC as parseComponent } from 'compiler/parser/sfc-parser' |
|||
export { compileToFunctions } from 'web/compiler/index' |
|||
|
|||
export function compile ( |
|||
template: string, |
|||
options?: CompilerOptions |
|||
): { |
|||
ast: ?ASTElement, |
|||
render: string, |
|||
staticRenderFns: Array<string> |
|||
} { |
|||
options = options |
|||
? extend(extend({}, baseOptions), options) |
|||
: baseOptions |
|||
return baseCompile(template, options) |
|||
} |
|||
|
|||
export function compileToFunctions ( |
|||
template: string, |
|||
options?: CompilerOptions, |
|||
vm: Component |
|||
): CompiledFunctions { |
|||
const cache = options && options.preserveWhitespace === false ? cache1 : cache2 |
|||
const key = options && options.delimiters |
|||
? String(options.delimiters) + template |
|||
: template |
|||
if (cache[key]) { |
|||
return cache[key] |
|||
} |
|||
const res = {} |
|||
const compiled = compile(template, options) |
|||
res.render = makeFunction(compiled.render) |
|||
const l = compiled.staticRenderFns.length |
|||
res.staticRenderFns = new Array(l) |
|||
for (let i = 0; i < l; i++) { |
|||
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i]) |
|||
} |
|||
if (process.env.NODE_ENV !== 'production') { |
|||
if (res.render === noop || res.staticRenderFns.some(fn => fn === noop)) { |
|||
const errors = compiled.ast ? detectErrors(compiled.ast, warn) : [] |
|||
warn( |
|||
`failed to compile template:\n\n${template}\n\n` + |
|||
errors.join('\n') + |
|||
'\n\n', |
|||
vm |
|||
) |
|||
options?: Object |
|||
): CompiledResult { |
|||
const errors = [] |
|||
const compiled = baseCompile(template, { |
|||
warn: msg => { |
|||
errors.push(msg) |
|||
} |
|||
} |
|||
return (cache[key] = res) |
|||
} |
|||
|
|||
function makeFunction (code) { |
|||
try { |
|||
return new Function(code) |
|||
} catch (e) { |
|||
return noop |
|||
} |
|||
}) |
|||
compiled.errors = errors.concat(detectErrors(compiled.ast)) |
|||
return compiled |
|||
} |
|||
|
@ -0,0 +1,94 @@ |
|||
/* @flow */ |
|||
|
|||
import { extend, genStaticKeys, noop } from 'shared/util' |
|||
import { warn } from 'core/util/debug' |
|||
import { compile as baseCompile } from 'compiler/index' |
|||
import { detectErrors } from 'compiler/error-detector' |
|||
import modules from './modules/index' |
|||
import directives from './directives/index' |
|||
import { isIE, isReservedTag, isUnaryTag, mustUseProp, getTagNamespace } from '../util/index' |
|||
|
|||
const cache1: { [key: string]: CompiledFunctionResult } = Object.create(null) |
|||
const cache2: { [key: string]: CompiledFunctionResult } = Object.create(null) |
|||
|
|||
export const baseOptions: CompilerOptions = { |
|||
isIE, |
|||
expectHTML: true, |
|||
preserveWhitespace: true, |
|||
modules, |
|||
staticKeys: genStaticKeys(modules), |
|||
directives, |
|||
isReservedTag, |
|||
isUnaryTag, |
|||
mustUseProp, |
|||
getTagNamespace |
|||
} |
|||
|
|||
export function compile ( |
|||
template: string, |
|||
options?: CompilerOptions |
|||
): CompiledResult { |
|||
options = options |
|||
? extend(extend({}, baseOptions), options) |
|||
: baseOptions |
|||
return baseCompile(template, options) |
|||
} |
|||
|
|||
export function compileToFunctions ( |
|||
template: string, |
|||
options?: CompilerOptions, |
|||
vm: Component |
|||
): CompiledFunctionResult { |
|||
const _warn = (options && options.warn) || warn |
|||
// detect possible CSP restriction
|
|||
/* istanbul ignore if */ |
|||
if (process.env.NODE_ENV !== 'production') { |
|||
try { |
|||
new Function('return 1') |
|||
} catch (e) { |
|||
if (e.toString().match(/unsafe-eval|CSP/)) { |
|||
_warn( |
|||
'It seems you are using the standalone build of Vue.js in an ' + |
|||
'environment with Content Security Policy that prohibits unsafe-eval. ' + |
|||
'The template compiler cannot work in this environment. Consider ' + |
|||
'relaxing the policy to allow unsafe-eval or pre-compiling your ' + |
|||
'templates into render functions.' |
|||
) |
|||
} |
|||
} |
|||
} |
|||
const cache = options && options.preserveWhitespace === false ? cache1 : cache2 |
|||
const key = options && options.delimiters |
|||
? String(options.delimiters) + template |
|||
: template |
|||
if (cache[key]) { |
|||
return cache[key] |
|||
} |
|||
const res = {} |
|||
const compiled = compile(template, options) |
|||
res.render = makeFunction(compiled.render) |
|||
const l = compiled.staticRenderFns.length |
|||
res.staticRenderFns = new Array(l) |
|||
for (let i = 0; i < l; i++) { |
|||
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i]) |
|||
} |
|||
if (process.env.NODE_ENV !== 'production') { |
|||
if (res.render === noop || res.staticRenderFns.some(fn => fn === noop)) { |
|||
_warn( |
|||
`failed to compile template:\n\n${template}\n\n` + |
|||
detectErrors(compiled.ast).join('\n') + |
|||
'\n\n', |
|||
vm |
|||
) |
|||
} |
|||
} |
|||
return (cache[key] = res) |
|||
} |
|||
|
|||
function makeFunction (code) { |
|||
try { |
|||
return new Function(code) |
|||
} catch (e) { |
|||
return noop |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
import { parseSFC } from 'compiler/parser/sfc-parser' |
|||
|
|||
describe('SFC parser', () => { |
|||
it('should parse', () => { |
|||
const res = parseSFC(` |
|||
<template> |
|||
<div>hi</div> |
|||
</template> |
|||
<style src="./test.css"></style> |
|||
<style lang="stylus" scoped> |
|||
h1 |
|||
color red |
|||
h2 |
|||
color green |
|||
</style> |
|||
<script> |
|||
export default {} |
|||
</script> |
|||
`)
|
|||
expect(res.template.content.trim()).toBe('<div>hi</div>') |
|||
expect(res.styles.length).toBe(2) |
|||
expect(res.styles[0].src).toBe('./test.css') |
|||
expect(res.styles[1].lang).toBe('stylus') |
|||
expect(res.styles[1].scoped).toBe(true) |
|||
expect(res.styles[1].content.trim()).toBe('h1\n color red\nh2\n color green') |
|||
expect(res.script.content.trim()).toBe('export default {}') |
|||
}) |
|||
}) |
Loading…
Reference in new issue