import { type RawSourceMap, SourceMapConsumer } from 'source-map-js' import { parse as babelParse } from '@babel/parser' import { type SFCTemplateCompileOptions, compileTemplate, } from '../src/compileTemplate' import { type SFCTemplateBlock, parse } from '../src/parse' import { compileScript } from '../src' import { getPositionInCode } from './utils' function compile(opts: Omit) { return compileTemplate({ ...opts, id: '', }) } test('should work', () => { const source = `

{{ render }}

` const result = compile({ filename: 'example.vue', source }) expect(result.errors.length).toBe(0) expect(result.source).toBe(source) // should expose render fn expect(result.code).toMatch(`export function render(`) }) // #6807 test('should work with style comment', () => { const source = `
{{ render }}
` const result = compile({ filename: 'example.vue', source }) expect(result.errors.length).toBe(0) expect(result.source).toBe(source) expect(result.code).toMatch(`{"width":"300px","height":"100px"}`) }) test('preprocess pug', () => { const template = parse( ` `, { filename: 'example.vue', sourceMap: true }, ).descriptor.template as SFCTemplateBlock const result = compile({ filename: 'example.vue', source: template.content, preprocessLang: template.lang, }) expect(result.errors.length).toBe(0) }) test('preprocess pug with indents and blank lines', () => { const template = parse( ` `, { filename: 'example.vue', sourceMap: true }, ).descriptor.template as SFCTemplateBlock const result = compile({ filename: 'example.vue', source: template.content, preprocessLang: template.lang, }) expect(result.errors.length).toBe(0) expect(result.source).toBe( '

The next line contains four spaces.

The next line is empty.

This is the last line.

', ) }) test('warn missing preprocessor', () => { const template = parse(`\n`, { filename: 'example.vue', sourceMap: true, }).descriptor.template as SFCTemplateBlock const result = compile({ filename: 'example.vue', source: template.content, preprocessLang: template.lang, }) expect(result.errors.length).toBe(1) }) test('transform asset url options', () => { const input = { source: ``, filename: 'example.vue' } // Object option const { code: code1 } = compile({ ...input, transformAssetUrls: { tags: { foo: ['bar'] }, }, }) expect(code1).toMatch(`import _imports_0 from 'baz'\n`) // legacy object option (direct tags config) const { code: code2 } = compile({ ...input, transformAssetUrls: { foo: ['bar'], }, }) expect(code2).toMatch(`import _imports_0 from 'baz'\n`) // false option const { code: code3 } = compile({ ...input, transformAssetUrls: false, }) expect(code3).not.toMatch(`import _imports_0 from 'baz'\n`) }) test('source map', () => { const template = parse( ` `, { filename: 'example.vue', sourceMap: true }, ).descriptor.template! const { code, map } = compile({ filename: 'example.vue', source: template.content, }) expect(map!.sources).toEqual([`example.vue`]) expect(map!.sourcesContent).toEqual([template.content]) const consumer = new SourceMapConsumer(map as RawSourceMap) expect( consumer.originalPositionFor(getPositionInCode(code, 'foobar')), ).toMatchObject(getPositionInCode(template.content, `foobar`)) }) test('source map: v-if generated comment should not have original position', () => { const template = parse( ` `, { filename: 'example.vue', sourceMap: true }, ).descriptor.template! const { code, map } = compile({ filename: 'example.vue', source: template.content, }) expect(map!.sources).toEqual([`example.vue`]) expect(map!.sourcesContent).toEqual([template.content]) const consumer = new SourceMapConsumer(map as RawSourceMap) const commentNode = code.match(/_createCommentVNode\("v-if", true\)/) expect(commentNode).not.toBeNull() const commentPosition = getPositionInCode(code, commentNode![0]) const originalPosition = consumer.originalPositionFor(commentPosition) // the comment node should not be mapped to the original source expect(originalPosition.column).toBeNull() expect(originalPosition.line).toBeNull() expect(originalPosition.source).toBeNull() }) test('should work w/ AST from descriptor', () => { const source = ` ` const template = parse(source, { filename: 'example.vue', sourceMap: true, }).descriptor.template! expect(template.ast!.source).toBe(source) const { code, map } = compile({ filename: 'example.vue', source: template.content, ast: template.ast, }) expect(map!.sources).toEqual([`example.vue`]) // when reusing AST from SFC parse for template compile, // the source corresponds to the entire SFC expect(map!.sourcesContent).toEqual([source]) const consumer = new SourceMapConsumer(map as RawSourceMap) expect( consumer.originalPositionFor(getPositionInCode(code, 'foobar')), ).toMatchObject(getPositionInCode(source, `foobar`)) expect(code).toBe( compile({ filename: 'example.vue', source: template.content, }).code, ) }) test('should work w/ AST from descriptor in SSR mode', () => { const source = ` ` const template = parse(source, { filename: 'example.vue', sourceMap: true, }).descriptor.template! expect(template.ast!.source).toBe(source) const { code, map } = compile({ filename: 'example.vue', source: '', // make sure it's actually using the AST instead of source ast: template.ast, ssr: true, }) expect(map!.sources).toEqual([`example.vue`]) // when reusing AST from SFC parse for template compile, // the source corresponds to the entire SFC expect(map!.sourcesContent).toEqual([source]) const consumer = new SourceMapConsumer(map as RawSourceMap) expect( consumer.originalPositionFor(getPositionInCode(code, 'foobar')), ).toMatchObject(getPositionInCode(source, `foobar`)) expect(code).toBe( compile({ filename: 'example.vue', source: template.content, ssr: true, }).code, ) }) test('should not reuse AST if using custom compiler', () => { const source = ` ` const template = parse(source, { filename: 'example.vue', sourceMap: true, }).descriptor.template! const { code } = compile({ filename: 'example.vue', source: template.content, ast: template.ast, compiler: { parse: () => null as any, // @ts-expect-error compile: input => ({ code: input }), }, }) // what we really want to assert is that the `input` received by the custom // compiler is the source string, not the AST. expect(code).toBe(template.content) }) test('should force re-parse on already transformed AST', () => { const source = ` ` const template = parse(source, { filename: 'example.vue', sourceMap: true, }).descriptor.template! // force set to empty, if this is reused then it won't generate proper code template.ast!.children = [] template.ast!.transformed = true const { code } = compile({ filename: 'example.vue', source: '', ast: template.ast, }) expect(code).toBe( compile({ filename: 'example.vue', source: template.content, }).code, ) }) test('should force re-parse with correct compiler in SSR mode', () => { const source = ` ` const template = parse(source, { filename: 'example.vue', sourceMap: true, }).descriptor.template! // force set to empty, if this is reused then it won't generate proper code template.ast!.children = [] template.ast!.transformed = true const { code } = compile({ filename: 'example.vue', source: '', ast: template.ast, ssr: true, }) expect(code).toBe( compile({ filename: 'example.vue', source: template.content, ssr: true, }).code, ) }) test('template errors', () => { const result = compile({ filename: 'example.vue', source: `
`, }) expect(result.errors).toMatchSnapshot() }) test('preprocessor errors', () => { const template = parse( ` `, { filename: 'example.vue', sourceMap: true }, ).descriptor.template as SFCTemplateBlock const result = compile({ filename: 'example.vue', source: template.content, preprocessLang: template.lang, }) expect(result.errors.length).toBe(1) const message = result.errors[0].toString() expect(message).toMatch(`Error: example.vue:3:1`) expect(message).toMatch( `The end of the string reached with no closing bracket ) found.`, ) }) // #3447 test('should generate the correct imports expression', () => { const { code } = compile({ filename: 'example.vue', source: ` `, ssr: true, }) expect(code).toMatch(`_ssrRenderAttr(\"src\", _imports_1)`) expect(code).toMatch(`_createVNode(\"img\", { src: _imports_1 })`) }) // #3874 test('should not hoist srcset URLs in SSR mode', () => { const { code } = compile({ filename: 'example.vue', source: ` `, ssr: true, }) expect(code).toMatchSnapshot() }) // #6742 test('dynamic v-on + static v-on should merged', () => { const source = `` const result = compile({ filename: 'example.vue', source }) expect(result.code).toMatchSnapshot() }) // #9853 regression found in Nuxt tests // walkIdentifiers can get called multiple times on the same node // due to #9729 calling it during SFC template usage check. // conditions needed: // 1. ` ` const { descriptor } = parse(src) // compileScript triggers importUsageCheck compileScript(descriptor, { id: 'xxx' }) const { code } = compileTemplate({ id: 'xxx', filename: 'test.vue', ast: descriptor.template!.ast, source: descriptor.template!.content, }) expect(code).not.toMatch(`_ctx.t`) }) test('prefixing edge case for reused AST ssr mode', () => { const src = ` ` const { descriptor } = parse(src) // compileScript triggers importUsageCheck compileScript(descriptor, { id: 'xxx' }) expect(() => compileTemplate({ id: 'xxx', filename: 'test.vue', ast: descriptor.template!.ast, source: descriptor.template!.content, ssr: true, }), ).not.toThrowError() }) // #10852 test('non-identifier expression in legacy filter syntax', () => { const src = ` ` const { descriptor } = parse(src) const compilationResult = compileTemplate({ id: 'xxx', filename: 'test.vue', ast: descriptor.template!.ast, source: descriptor.template!.content, ssr: false, compilerOptions: { compatConfig: { MODE: 2, }, }, }) expect(() => { babelParse(compilationResult.code, { sourceType: 'module' }) }).not.toThrow() })