diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index dd8d8311..8331ad7d 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -154,12 +154,11 @@ return function render() { `; exports[`compiler: codegen module mode preamble 1`] = ` -"import { helperOne, helperTwo } from 'vue' +"import { helperOne, helperTwo } from \\"vue\\" export default function render() { - with (this) { - return null - } + const _ctx = this + return null }" `; diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index f7508225..db2bfb17 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`function mode 1`] = ` +exports[`compiler: integration tests function mode 1`] = ` "const _Vue = Vue return function render() { with (this) { @@ -13,10 +13,50 @@ return function render() { ok ? _createVNode(\\"div\\", 0, \\"yes\\") : \\"no\\", - _renderList(list, (i, j) => { - return _createVNode(\\"div\\", 0, [_createVNode(\\"span\\", 0, _toString(i + j))]) + _renderList(list, (value, index) => { + return _createVNode(\\"div\\", 0, [_createVNode(\\"span\\", 0, _toString(value + index))]) }) ]) } }" `; + +exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = ` +"const { createVNode, toString, renderList } = Vue + +return function render() { + const _ctx = this + return createVNode(\\"div\\", { + id: \\"foo\\", + class: _ctx.bar + }, [ + toString(_ctx.world), + (_ctx.ok) + ? createVNode(\\"div\\", 0, \\"yes\\") + : \\"no\\", + renderList(_ctx.list, (value, index) => { + return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, toString(value + index))]) + }) + ]) +}" +`; + +exports[`compiler: integration tests module mode 1`] = ` +"import { createVNode, toString, renderList } from \\"vue\\" + +export default function render() { + const _ctx = this + return createVNode(\\"div\\", { + id: \\"foo\\", + class: _ctx.bar + }, [ + _toString(_ctx.world), + (_ctx.ok) + ? createVNode(\\"div\\", 0, \\"yes\\") + : \\"no\\", + _renderList(_ctx.list, (value, index) => { + return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, _toString(value + index))]) + }) + ]) +}" +`; diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index 88416f06..edc579f6 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -51,7 +51,7 @@ describe('compiler: codegen', () => { imports: [`helperOne`, `helperTwo`] }) const { code } = generate(root, { mode: 'module' }) - expect(code).toMatch(`import { helperOne, helperTwo } from 'vue'`) + expect(code).toMatch(`import { helperOne, helperTwo } from "vue"`) expect(code).toMatchSnapshot() }) diff --git a/packages/compiler-core/__tests__/compile.spec.ts b/packages/compiler-core/__tests__/compile.spec.ts index ceb69aea..1d56e92a 100644 --- a/packages/compiler-core/__tests__/compile.spec.ts +++ b/packages/compiler-core/__tests__/compile.spec.ts @@ -1,139 +1,227 @@ import { compile } from '../src' import { SourceMapConsumer, RawSourceMap } from 'source-map' -// Integration tests for parser + transform + codegen -test('function mode', async () => { +describe('compiler: integration tests', () => { const source = `
{{ world }}
yes
-
{{ i + j }}
+
{{ value + index }}
`.trim() - const { code, map } = compile(source, { - sourceMap: true, - filename: `foo.vue` + + function getPositionInCode(code: string, token: string) { + const generatedOffset = code.indexOf(token) + let line = 1 + let lastNewLinePos = -1 + for (let i = 0; i < generatedOffset; i++) { + if (code.charCodeAt(i) === 10 /* newline char code */) { + line++ + lastNewLinePos = i + } + } + return { + line, + column: + lastNewLinePos === -1 + ? generatedOffset + : generatedOffset - lastNewLinePos - 1 + } + } + + test('function mode', async () => { + const { code, map } = compile(source, { + sourceMap: true, + filename: `foo.vue` + }) + + expect(code).toMatch( + `const { createVNode: _createVNode, toString: _toString, renderList: _renderList } = _Vue` + ) + + expect(code).toMatchSnapshot() + expect(map!.sources).toEqual([`foo.vue`]) + expect(map!.sourcesContent).toEqual([source]) + + const consumer = await new SourceMapConsumer(map as RawSourceMap) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `id`)) + ).toMatchObject(getPositionInCode(source, `id`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `"foo"`)) + ).toMatchObject(getPositionInCode(source, `"foo"`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `class:`)) + ).toMatchObject(getPositionInCode(source, `class=`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `bar`)) + ).toMatchObject(getPositionInCode(source, `bar`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `world`)) + ).toMatchObject(getPositionInCode(source, `{{ world }}`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `ok`)) + ).toMatchObject(getPositionInCode(source, `ok`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `list`)) + ).toMatchObject(getPositionInCode(source, `list`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `value`)) + ).toMatchObject(getPositionInCode(source, `value`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `index`)) + ).toMatchObject(getPositionInCode(source, `index`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `value + index`)) + ).toMatchObject(getPositionInCode(source, `{{ value + index }}`)) }) - expect(code).toMatchSnapshot() - expect(map!.sources).toEqual([`foo.vue`]) - expect(map!.sourcesContent).toEqual([source]) - - const consumer = await new SourceMapConsumer(map as RawSourceMap) - - // id= - expect( - consumer.originalPositionFor({ - line: 6, - column: 6 + test('function mode w/ prefixIdentifiers: true', async () => { + const { code, map } = compile(source, { + sourceMap: true, + filename: `foo.vue`, + prefixIdentifiers: true }) - ).toMatchObject({ - line: 1, - column: 5 + + expect(code).toMatch(`const { createVNode, toString, renderList } = Vue`) + + expect(code).toMatchSnapshot() + expect(map!.sources).toEqual([`foo.vue`]) + expect(map!.sourcesContent).toEqual([source]) + + const consumer = await new SourceMapConsumer(map as RawSourceMap) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `id`)) + ).toMatchObject(getPositionInCode(source, `id`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `"foo"`)) + ).toMatchObject(getPositionInCode(source, `"foo"`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `class:`)) + ).toMatchObject(getPositionInCode(source, `class=`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `bar`)) + ).toMatchObject(getPositionInCode(source, `bar`)) + expect( + consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`)) + ).toMatchObject(getPositionInCode(source, `bar`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `world`)) + ).toMatchObject(getPositionInCode(source, `{{ world }}`)) + expect( + consumer.originalPositionFor(getPositionInCode(code, `_ctx.world`)) + ).toMatchObject(getPositionInCode(source, `{{ world }}`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `ok`)) + ).toMatchObject(getPositionInCode(source, `ok`)) + expect( + consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`)) + ).toMatchObject(getPositionInCode(source, `ok`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `list`)) + ).toMatchObject(getPositionInCode(source, `list`)) + expect( + consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`)) + ).toMatchObject(getPositionInCode(source, `list`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `value`)) + ).toMatchObject(getPositionInCode(source, `value`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `index`)) + ).toMatchObject(getPositionInCode(source, `index`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `value + index`)) + ).toMatchObject(getPositionInCode(source, `value + index`)) }) - // "foo" - expect( - consumer.originalPositionFor({ - line: 6, - column: 10 + test('module mode', async () => { + const { code, map } = compile(source, { + mode: 'module', + sourceMap: true, + filename: `foo.vue` }) - ).toMatchObject({ - line: 1, - column: 8 - }) - // :class= - expect( - consumer.originalPositionFor({ - line: 7, - column: 6 - }) - ).toMatchObject({ - line: 1, - column: 15 - }) - // bar - expect( - consumer.originalPositionFor({ - line: 7, - column: 13 - }) - ).toMatchObject({ - line: 1, - column: 22 - }) + expect(code).toMatch( + `import { createVNode, toString, renderList } from "vue"` + ) - // {{ world }} - expect( - consumer.originalPositionFor({ - line: 9, - column: 16 - }) - ).toMatchObject({ - line: 2, - column: 2 - }) + expect(code).toMatchSnapshot() + expect(map!.sources).toEqual([`foo.vue`]) + expect(map!.sourcesContent).toEqual([source]) - // ok - expect( - consumer.originalPositionFor({ - line: 10, - column: 6 - }) - ).toMatchObject({ - line: 3, - column: 13 - }) + const consumer = await new SourceMapConsumer(map as RawSourceMap) - // i - expect( - consumer.originalPositionFor({ - line: 13, - column: 25 - }) - ).toMatchObject({ - line: 5, - column: 15 - }) + expect( + consumer.originalPositionFor(getPositionInCode(code, `id`)) + ).toMatchObject(getPositionInCode(source, `id`)) - // j - expect( - consumer.originalPositionFor({ - line: 13, - column: 28 - }) - ).toMatchObject({ - line: 5, - column: 18 - }) + expect( + consumer.originalPositionFor(getPositionInCode(code, `"foo"`)) + ).toMatchObject(getPositionInCode(source, `"foo"`)) - // list - expect( - consumer.originalPositionFor({ - line: 13, - column: 18 - }) - ).toMatchObject({ - line: 5, - column: 24 - }) + expect( + consumer.originalPositionFor(getPositionInCode(code, `class:`)) + ).toMatchObject(getPositionInCode(source, `class=`)) - // i + j - expect( - consumer.originalPositionFor({ - line: 14, - column: 81 - }) - ).toMatchObject({ - line: 5, - column: 36 + expect( + consumer.originalPositionFor(getPositionInCode(code, `bar`)) + ).toMatchObject(getPositionInCode(source, `bar`)) + expect( + consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`)) + ).toMatchObject(getPositionInCode(source, `bar`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `world`)) + ).toMatchObject(getPositionInCode(source, `{{ world }}`)) + expect( + consumer.originalPositionFor(getPositionInCode(code, `_ctx.world`)) + ).toMatchObject(getPositionInCode(source, `{{ world }}`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `ok`)) + ).toMatchObject(getPositionInCode(source, `ok`)) + expect( + consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`)) + ).toMatchObject(getPositionInCode(source, `ok`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `list`)) + ).toMatchObject(getPositionInCode(source, `list`)) + expect( + consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`)) + ).toMatchObject(getPositionInCode(source, `list`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `value`)) + ).toMatchObject(getPositionInCode(source, `value`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `index`)) + ).toMatchObject(getPositionInCode(source, `index`)) + + expect( + consumer.originalPositionFor(getPositionInCode(code, `value + index`)) + ).toMatchObject(getPositionInCode(source, `value + index`)) }) }) - -test.todo('function mode w/ prefixIdentifiers: true') - -test.todo('module mode') - -test.todo('module mode w/ prefixIdentifiers: true') diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 088edecf..6129cfdd 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -29,26 +29,17 @@ function parseWithExpressionTransform( describe('compiler: expression transform', () => { test('interpolation (root)', () => { const node = parseWithExpressionTransform(`{{ foo }}`) as ExpressionNode - expect(node.children).toMatchObject([ - `_ctx.`, - { - content: `foo`, - loc: node.loc - } - ]) + expect(node.children).toBeUndefined() + expect(node.content).toBe(`_ctx.foo`) }) test('interpolation (children)', () => { - const node = parseWithExpressionTransform( + const el = parseWithExpressionTransform( `
{{ foo }}
` ) as ElementNode - expect((node.children[0] as ExpressionNode).children).toMatchObject([ - `_ctx.`, - { - content: `foo`, - loc: node.children[0].loc - } - ]) + const node = el.children[0] as ExpressionNode + expect(node.children).toBeUndefined() + expect(node.content).toBe(`_ctx.foo`) }) test('directive value', () => { @@ -57,13 +48,8 @@ describe('compiler: expression transform', () => { ) as ElementNode expect((node.props[0] as DirectiveNode).arg!.children).toBeUndefined() const exp = (node.props[0] as DirectiveNode).exp! - expect(exp.children).toMatchObject([ - `_ctx.`, - { - content: `baz`, - loc: exp.loc - } - ]) + expect(exp.children).toBeUndefined() + expect(exp.content).toBe(`_ctx.baz`) }) test('dynamic directive arg', () => { @@ -72,20 +58,10 @@ describe('compiler: expression transform', () => { ) as ElementNode const arg = (node.props[0] as DirectiveNode).arg! const exp = (node.props[0] as DirectiveNode).exp! - expect(arg.children).toMatchObject([ - `_ctx.`, - { - content: `arg`, - loc: arg.loc - } - ]) - expect(exp.children).toMatchObject([ - `_ctx.`, - { - content: `baz`, - loc: exp.loc - } - ]) + expect(arg.children).toBeUndefined() + expect(arg.content).toBe(`_ctx.arg`) + expect(exp.children).toBeUndefined() + expect(exp.content).toBe(`_ctx.baz`) }) test('should prefix complex expressions', () => { @@ -94,9 +70,8 @@ describe('compiler: expression transform', () => { ) as ExpressionNode // should parse into compound expression expect(node.children).toMatchObject([ - `_ctx.`, { - content: `foo`, + content: `_ctx.foo`, loc: { source: `foo`, start: { @@ -111,9 +86,9 @@ describe('compiler: expression transform', () => { } } }, - `(_ctx.`, + `(`, { - content: `baz`, + content: `_ctx.baz`, loc: { source: `baz`, start: { @@ -128,9 +103,9 @@ describe('compiler: expression transform', () => { } } }, - ` + 1, { key: _ctx.`, + ` + 1, { key: `, { - content: `kuz`, + content: `_ctx.kuz`, loc: { source: `kuz`, start: { @@ -151,17 +126,16 @@ describe('compiler: expression transform', () => { test('should prefix v-if condition', () => { const node = parseWithExpressionTransform(`
`) as IfNode - expect(node.branches[0].condition!.children).toMatchObject([ - `_ctx.`, - { content: `ok` } - ]) + expect(node.branches[0].condition!.children).toBeUndefined() + expect(node.branches[0].condition!.content).toBe(`_ctx.ok`) }) test('should prefix v-for source', () => { const node = parseWithExpressionTransform( `
` ) as ForNode - expect(node.source.children).toMatchObject([`_ctx.`, { content: `list` }]) + expect(node.source.children).toBeUndefined() + expect(node.source.content).toBe(`_ctx.list`) }) test('should not prefix v-for alias', () => { @@ -177,7 +151,8 @@ describe('compiler: expression transform', () => { const j = div.children[1] as ExpressionNode expect(j.type).toBe(NodeTypes.EXPRESSION) - expect(j.children).toMatchObject([`_ctx.`, { content: `j` }]) + expect(j.children).toBeUndefined() + expect(j.content).toBe(`_ctx.j`) }) test('should not prefix v-for aliases (multiple)', () => { @@ -188,12 +163,19 @@ describe('compiler: expression transform', () => { const exp = div.children[0] as ExpressionNode expect(exp.type).toBe(NodeTypes.EXPRESSION) - expect(exp.content).toBe(`i + j + k`) - expect(exp.children).toBeUndefined() + // parsed for better source-map support + expect(exp.children).toMatchObject([ + { content: `i` }, + ` + `, + { content: `j` }, + ` + `, + { content: `k` } + ]) const l = div.children[1] as ExpressionNode expect(l.type).toBe(NodeTypes.EXPRESSION) - expect(l.children).toMatchObject([`_ctx.`, { content: `l` }]) + expect(l.children).toBeUndefined() + expect(l.content).toBe(`_ctx.l`) }) test('should prefix id outside of v-for', () => { @@ -202,8 +184,8 @@ describe('compiler: expression transform', () => { ) as ElementNode const exp = node.children[1] as ExpressionNode expect(exp.type).toBe(NodeTypes.EXPRESSION) - expect(exp.content).toBe(`i`) - expect(exp.children).toMatchObject([`_ctx.`, { content: `i` }]) + expect(exp.children).toBeUndefined() + expect(exp.content).toBe(`_ctx.i`) }) test('nested v-for', () => { @@ -217,7 +199,11 @@ describe('compiler: expression transform', () => { const innerExp = (innerFor.children[0] as ElementNode) .children[0] as ExpressionNode expect(innerExp.type).toBe(NodeTypes.EXPRESSION) - expect(innerExp.children).toMatchObject([`i + _ctx.`, { content: `j` }]) + expect(innerExp.children).toMatchObject([ + { content: 'i' }, + ` + `, + { content: `_ctx.j` } + ]) // when an inner v-for shadows a variable of an outer v-for and exit, // it should not cause the outer v-for's alias to be removed from known ids @@ -232,8 +218,12 @@ describe('compiler: expression transform', () => { `{{ Math.max(1, 2) }}` ) as ExpressionNode expect(node.type).toBe(NodeTypes.EXPRESSION) - expect(node.content).toBe(`Math.max(1, 2)`) - expect(node.children).toBeUndefined() + expect(node.children).toMatchObject([ + { content: `Math` }, + `.`, + { content: `max` }, + `(1, 2)` + ]) }) test('should not prefix id of a function declaration', () => { @@ -242,8 +232,10 @@ describe('compiler: expression transform', () => { ) as ExpressionNode expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.children).toMatchObject([ - `function foo() { return _ctx.`, - { content: `bar` }, + `function `, + { content: `foo` }, + `() { return `, + { content: `_ctx.bar` }, ` }` ]) }) @@ -254,8 +246,11 @@ describe('compiler: expression transform', () => { ) as ExpressionNode expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.children).toMatchObject([ - `foo => foo + _ctx.`, - { content: `bar` } + { content: `foo` }, + ` => `, + { content: `foo` }, + ` + `, + { content: `_ctx.bar` } ]) }) @@ -265,8 +260,8 @@ describe('compiler: expression transform', () => { ) as ExpressionNode expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.children).toMatchObject([ - `{ foo: _ctx.`, - { content: `bar` }, + `{ foo: `, + { content: `_ctx.bar` }, ` }` ]) }) @@ -277,10 +272,10 @@ describe('compiler: expression transform', () => { ) as ExpressionNode expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.children).toMatchObject([ - `{ [_ctx.`, - { content: `foo` }, - `]: _ctx.`, - { content: `bar` }, + `{ [`, + { content: `_ctx.foo` }, + `]: `, + { content: `_ctx.bar` }, ` }` ]) }) @@ -288,8 +283,8 @@ describe('compiler: expression transform', () => { test('should prefix object property shorthand value', () => { const node = parseWithExpressionTransform(`{{ { foo } }}`) as ExpressionNode expect(node.children).toMatchObject([ - `{ foo: _ctx.`, - { content: `foo` }, + `{ foo: `, + { content: `_ctx.foo` }, ` }` ]) }) @@ -299,9 +294,11 @@ describe('compiler: expression transform', () => { `{{ foo.bar.baz }}` ) as ExpressionNode expect(node.children).toMatchObject([ - `_ctx.`, - { content: `foo` }, - `.bar.baz` + { content: `_ctx.foo` }, + `.`, + { content: `bar` }, + `.`, + { content: `baz` } ]) }) @@ -310,12 +307,11 @@ describe('compiler: expression transform', () => { `{{ foo[bar][baz] }}` ) as ExpressionNode expect(node.children).toMatchObject([ - `_ctx.`, - { content: `foo` }, - `[_ctx.`, - { content: `bar` }, - `][_ctx.`, - { content: 'baz' }, + { content: `_ctx.foo` }, + `[`, + { content: `_ctx.bar` }, + `][`, + { content: '_ctx.baz' }, `]` ]) }) diff --git a/packages/compiler-core/__tests__/transforms/vOn.spec.ts b/packages/compiler-core/__tests__/transforms/vOn.spec.ts index 2f2f131e..b2b44e7b 100644 --- a/packages/compiler-core/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vOn.spec.ts @@ -25,7 +25,7 @@ function parseWithVOn( return ast.children[0] as ElementNode } -describe('compiler: transform v-bind', () => { +describe('compiler: transform v-on', () => { test('basic', () => { const node = parseWithVOn(`
`) const props = node.codegenNode!.arguments[1] as ObjectExpression @@ -76,8 +76,8 @@ describe('compiler: transform v-bind', () => { const props = node.codegenNode!.arguments[1] as ObjectExpression expect(props.properties[0]).toMatchObject({ key: { - content: `"on" + event`, - isStatic: false + isStatic: false, + children: [`"on" + `, { content: `event` }] }, value: { content: `handler`, @@ -94,10 +94,10 @@ describe('compiler: transform v-bind', () => { expect(props.properties[0]).toMatchObject({ key: { isStatic: false, - children: [`"on" + `, `_ctx.`, { content: `event` }] + children: [`"on" + `, { content: `_ctx.event` }] }, value: { - content: `handler`, + content: `_ctx.handler`, isStatic: false } }) diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index c851d956..9e69c62e 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -150,6 +150,7 @@ export function generate( const context = createCodegenContext(ast, options) const { mode, push, prefixIdentifiers, indent, deindent, newline } = context const hasImports = ast.imports.length + const useWithBlock = !prefixIdentifiers && mode !== 'module' // preambles if (mode === 'function') { @@ -170,7 +171,7 @@ export function generate( } else { // generate import statements for helpers if (hasImports) { - push(`import { ${ast.imports.join(', ')} } from 'vue'\n`) + push(`import { ${ast.imports.join(', ')} } from "vue"\n`) } genHoists(ast.hoists, context) push(`export default `) @@ -180,12 +181,12 @@ export function generate( push(`function render() {`) indent() - if (!prefixIdentifiers) { + if (useWithBlock) { push(`with (this) {`) indent() // function mode const declarations should be inside with block // also they should be renamed to avoid collision with user properties - if (mode === 'function' && hasImports) { + if (hasImports) { push(`const { ${ast.imports.map(n => `${n}: _${n}`).join(', ')} } = _Vue`) newline() } @@ -206,10 +207,12 @@ export function generate( // generate the VNode tree expression push(`return `) genChildren(ast.children, context, true) - if (!prefixIdentifiers) { + + if (useWithBlock) { deindent() push(`}`) } + deindent() push(`}`) return { diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index 08424542..c077c1a8 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -70,7 +70,8 @@ export const enum ErrorCodes { X_V_ON_NO_EXPRESSION, // generic errors - X_PREFIX_ID_NOT_SUPPORTED + X_PREFIX_ID_NOT_SUPPORTED, + X_MODULE_MODE_NOT_SUPPORTED } export const errorMessages: { [code: number]: string } = { @@ -138,5 +139,6 @@ export const errorMessages: { [code: number]: string } = { [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression`, // generic errors - [ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler because it is optimized for payload size.` + [ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`, + [ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.` } diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 572abead..c747c8fe 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -18,15 +18,21 @@ export function compile( template: string | RootNode, options: CompilerOptions = {} ): CodegenResult { - if (__BROWSER__ && options.prefixIdentifiers === false) { - ;(options.onError || defaultOnError)( - createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED) - ) + if (__BROWSER__) { + const onError = options.onError || defaultOnError + if (options.prefixIdentifiers === true) { + onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED)) + } else if (options.mode === 'module') { + onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED)) + } } const ast = isString(template) ? parse(template, options) : template - const prefixIdentifiers = !__BROWSER__ && options.prefixIdentifiers === true + const prefixIdentifiers = + !__BROWSER__ && + (options.prefixIdentifiers === true || options.mode === 'module') + transform(ast, { ...options, prefixIdentifiers, diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index be09c690..533108ac 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -12,7 +12,7 @@ import { parseScript } from 'meriyah' import { walk } from 'estree-walker' import { NodeTransform, TransformContext } from '../transform' import { NodeTypes, createExpression, ExpressionNode } from '../ast' -import { Node, Function, Identifier } from 'estree' +import { Node, Function, Identifier, Property } from 'estree' import { advancePositionWithClone } from '../utils' export const transformExpression: NodeTransform = (node, context) => { if (node.type === NodeTypes.EXPRESSION && !node.isStatic) { @@ -43,12 +43,15 @@ const simpleIdRE = /^[a-zA-Z$_][\w$]*$/ const isFunction = (node: Node): node is Function => /Function(Expression|Declaration)$/.test(node.type) +const isPropertyKey = (node: Node, parent: Node) => + parent.type === 'Property' && parent.key === node && !parent.computed + // cache node requires let _parseScript: typeof parseScript let _walk: typeof walk interface PrefixMeta { - prefix: string + prefix?: string start: number end: number } @@ -72,7 +75,7 @@ export function processExpression( // fast path if expression is a simple identifier. if (simpleIdRE.test(node.content)) { if (!context.identifiers[node.content]) { - node.children = [`_ctx.`, createExpression(node.content, false, node.loc)] + node.content = `_ctx.${node.content}` } return } @@ -92,23 +95,23 @@ export function processExpression( walk(ast, { enter(node: Node & PrefixMeta, parent) { if (node.type === 'Identifier') { - if ( - !ids.includes(node) && - !knownIds[node.name] && - shouldPrefix(node, parent) - ) { - if ( - parent.type === 'Property' && - parent.value === node && - parent.key === node - ) { - // property shorthand like { foo }, we need to add the key since we - // rewrite the value - node.prefix = `${node.name}: _ctx.` - } else { - node.prefix = `_ctx.` + if (!ids.includes(node)) { + if (!knownIds[node.name] && shouldPrefix(node, parent)) { + if ( + isPropertyKey(node, parent) && + (parent as Property).value === node + ) { + // property shorthand like { foo }, we need to add the key since we + // rewrite the value + node.prefix = `${node.name}: ` + } + node.name = `_ctx.${node.name}` + ids.push(node) + } else if (!isPropertyKey(node, parent)) { + // also generate sub-expressioms for other identifiers for better + // source map support. (except for property keys which are static) + ids.push(node) } - ids.push(node) } } else if (isFunction(node)) { // walk function expressions and add its arguments to known identifiers @@ -147,7 +150,9 @@ export function processExpression( ids.forEach((id, i) => { const last = ids[i - 1] as any const leadingText = full.slice(last ? last.end - 1 : 0, id.start - 1) - children.push(leadingText + id.prefix) + if (leadingText.length || id.prefix) { + children.push(leadingText + (id.prefix || ``)) + } const source = full.slice(id.start - 1, id.end - 1) children.push( createExpression(id.name, false, { @@ -191,12 +196,9 @@ function shouldPrefix(identifier: Identifier, parent: Node) { ) && // not a key of Property !( - parent.type === 'Property' && - parent.key === identifier && - // computed keys should be prefixed - !parent.computed && + isPropertyKey(identifier, parent) && // shorthand keys should be prefixed - !(parent.value === identifier) + !((parent as Property).value === identifier) ) && // not a property of a MemberExpression !( diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index 7d88aa7c..019bd6ed 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -2,7 +2,6 @@ import { DirectiveTransform } from '../transform' import { createObjectProperty, createExpression, ExpressionNode } from '../ast' import { capitalize } from '@vue/shared' import { createCompilerError, ErrorCodes } from '../errors' -import { isSimpleIdentifier } from '../utils' // v-on without arg is handled directly in ./element.ts due to it affecting // codegen for the entire props object. This transform here is only for v-on @@ -14,22 +13,18 @@ export const transformOn: DirectiveTransform = ( if (!exp && !modifiers.length) { context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc)) } - const { content, children, isStatic, loc: argLoc } = arg! + const { content, isStatic, loc: argLoc } = arg! let eventName: ExpressionNode if (isStatic) { // static arg eventName = createExpression(`on${capitalize(content)}`, true, argLoc) - } else if (!children) { - // dynamic arg with no rewrite - eventName = createExpression( - `"on" + ${isSimpleIdentifier(content) ? content : `(${content})`}`, - false, - argLoc - ) } else { - // dynamic arg with ctx prefixing + // dynamic arg. turn it into a compound expression. eventName = arg! - children.unshift(`"on" + `) + ;( + eventName.children || + (eventName.children = [{ ...eventName, children: undefined }]) + ).unshift(`"on" + `) } // TODO .once modifier handling since it is platform agnostic // other modifiers are handled in compiler-dom