diff --git a/packages/compiler-sfc/__tests__/parse.spec.ts b/packages/compiler-sfc/__tests__/parse.spec.ts
new file mode 100644
index 00000000..14cb9dcc
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/parse.spec.ts
@@ -0,0 +1,21 @@
+import { parse } from '../src'
+import { mockWarn } from '@vue/runtime-test'
+
+describe('compiler:sfc', () => {
+ mockWarn()
+ describe('error', () => {
+ test('should only allow single template element', () => {
+ parse(``)
+ expect(
+ `Single file component can contain only one template element`
+ ).toHaveBeenWarned()
+ })
+
+ test('should only allow single script element', () => {
+ parse(``)
+ expect(
+ `Single file component can contain only one script element`
+ ).toHaveBeenWarned()
+ })
+ })
+})
diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts
index 9cfded62..de8945dd 100644
--- a/packages/compiler-sfc/src/parse.ts
+++ b/packages/compiler-sfc/src/parse.ts
@@ -7,6 +7,7 @@ import {
SourceLocation
} from '@vue/compiler-core'
import { RawSourceMap } from 'source-map'
+import { generateCodeFrame } from '@vue/shared'
export interface SFCParseOptions {
needMap?: boolean
@@ -78,14 +79,14 @@ export function parse(
if (!sfc.template) {
sfc.template = createBlock(node) as SFCTemplateBlock
} else {
- // TODO warn duplicate template
+ warnDuplicateBlock(source, filename, node)
}
break
case 'script':
if (!sfc.script) {
sfc.script = createBlock(node) as SFCScriptBlock
} else {
- // TODO warn duplicate script
+ warnDuplicateBlock(source, filename, node)
}
break
case 'style':
@@ -105,6 +106,24 @@ export function parse(
return sfc
}
+function warnDuplicateBlock(
+ source: string,
+ filename: string,
+ node: ElementNode
+) {
+ const codeFrame = generateCodeFrame(
+ source,
+ node.loc.start.offset,
+ node.loc.end.offset
+ )
+ const location = `${filename}:${node.loc.start.line}:${node.loc.start.column}`
+ console.warn(
+ `Single file component can contain only one ${
+ node.tag
+ } element (${location}):\n\n${codeFrame}`
+ )
+}
+
function createBlock(node: ElementNode): SFCBlock {
const type = node.tag
const text = node.children[0] as TextNode