diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 9e58d751d..a481ddcf5 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -215,9 +215,10 @@ export function generate( } // binding optimizations - const optimizeSources = options.bindingMetadata - ? `, $props, $setup, $data, $options` - : `` + const optimizeSources = + options.bindingMetadata && !options.inline + ? `, $props, $setup, $data, $options` + : `` // enter render function if (!ssr) { if (isSetupInlined) { diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index f72bd16bb..0a6cc8ef0 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -113,17 +113,16 @@ export function processExpression( // it gets correct type return `__props.${raw}` } - } - - if (type === BindingTypes.CONST) { - // setup const binding in non-inline mode - return `$setup.${raw}` - } else if (type) { - return `$${type}.${raw}` } else { - // fallback to ctx - return `_ctx.${raw}` + if (type === BindingTypes.CONST) { + // setup const binding in non-inline mode + return `$setup.${raw}` + } else if (type) { + return `$${type}.${raw}` + } } + // fallback to ctx + return `_ctx.${raw}` } // fast path if expression is a simple identifier. diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index baac4d581..907a47c8d 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -1,62 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SFC compile \n` + - `` - ).content - ) - }) - - test('\n` + - `` - ).content - ) - }) - - test('\n` + - `` - ).content - ) - }) - - test('w/ \n` + - `` - ).content - ) - }) - }) - describe('async/await detection', () => { function assertAwaitDetection(code: string, shouldAsync = true) { const { content } = compile(``) - expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup()`) + expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`) } test('expression statement', () => { diff --git a/packages/compiler-sfc/__tests__/compileStyle.spec.ts b/packages/compiler-sfc/__tests__/compileStyle.spec.ts index ce8374703..a1b3b21ee 100644 --- a/packages/compiler-sfc/__tests__/compileStyle.spec.ts +++ b/packages/compiler-sfc/__tests__/compileStyle.spec.ts @@ -9,27 +9,27 @@ import { } from '../src/compileStyle' import path from 'path' -describe('SFC scoped CSS', () => { - function compileScoped( - source: string, - options?: Partial - ): string { - const res = compileStyle({ - source, - filename: 'test.css', - id: 'test', - scoped: true, - ...options +export function compileScoped( + source: string, + options?: Partial +): string { + const res = compileStyle({ + source, + filename: 'test.css', + id: 'test', + scoped: true, + ...options + }) + if (res.errors.length) { + res.errors.forEach(err => { + console.error(err) }) - if (res.errors.length) { - res.errors.forEach(err => { - console.error(err) - }) - expect(res.errors.length).toBe(0) - } - return res.code + expect(res.errors.length).toBe(0) } + return res.code +} +describe('SFC scoped CSS', () => { test('simple selectors', () => { expect(compileScoped(`h1 { color: red; }`)).toMatch( `h1[test] { color: red;` @@ -266,27 +266,6 @@ describe('SFC scoped CSS', () => { ).toHaveBeenWarned() }) }) - - describe('` + ).content + ) + }) + + test('\n` + + `` + ).content + ) + }) + + test('\n` + `` + ).content + ) + }) + + test('w/ \n` + + `` + ).content + ) + }) + }) + + test('generating correct code for nested paths', () => { + const { content } = compileSFCScript( + `\n` + + `` + ) + expect(content).toMatch(`_useCssVars(_ctx => ({ + color: (_ctx.color), + font_size: (_ctx.font.size) +})`) + assertCode(content) + }) + + test('w/ \n` + + `` + ) + // should handle: + // 1. local const bindings + // 2. local potential ref bindings + // 3. props bindings (analyzed) + expect(content).toMatch(`_useCssVars(_ctx => ({ + color: (color), + size: (_unref(size)), + foo: (__props.foo) +})`) + expect(content).toMatch( + `import { useCssVars as _useCssVars, unref as _unref } from 'vue'` + ) + assertCode(content) + }) + + test('should rewrite CSS vars in scoped mode', () => { + const { code } = compileStyle({ + source: `.foo { + color: var(--v-bind:color); + font-size: var(--:font.size); + }`, + filename: 'test.css', + id: 'data-v-test' + }) + expect(code).toMatchInlineSnapshot(` + ".foo { + color: var(--test-color); + font-size: var(--test-font_size); + }" + `) + }) +}) diff --git a/packages/compiler-sfc/__tests__/utils.ts b/packages/compiler-sfc/__tests__/utils.ts new file mode 100644 index 000000000..ae6861193 --- /dev/null +++ b/packages/compiler-sfc/__tests__/utils.ts @@ -0,0 +1,28 @@ +import { parse, SFCScriptCompileOptions, compileScript } from '../src' +import { parse as babelParse } from '@babel/parser' +import { babelParserDefaultPlugins } from '@vue/shared' + +export function compileSFCScript( + src: string, + options?: Partial +) { + const { descriptor } = parse(src) + return compileScript(descriptor, { + ...options, + id: 'xxxxxxxx' + }) +} + +export function assertCode(code: string) { + // parse the generated code to make sure it is valid + try { + babelParse(code, { + sourceType: 'module', + plugins: [...babelParserDefaultPlugins, 'typescript'] + }) + } catch (e) { + console.log(code) + throw e + } + expect(code).toMatchSnapshot() +} diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index a049d2059..522277db2 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1,5 +1,5 @@ import MagicString from 'magic-string' -import { BindingMetadata, BindingTypes } from '@vue/compiler-core' +import { BindingMetadata, BindingTypes, UNREF } from '@vue/compiler-core' import { SFCDescriptor, SFCScriptBlock } from './parse' import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser' import { babelParserDefaultPlugins, generateCodeFrame } from '@vue/shared' @@ -26,14 +26,20 @@ import { walk } from 'estree-walker' import { RawSourceMap } from 'source-map' import { CSS_VARS_HELPER, + parseCssVars, genCssVarsCode, injectCssVarsCalls -} from './genCssVars' +} from './cssVars' import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate' const DEFINE_OPTIONS = 'defineOptions' export interface SFCScriptCompileOptions { + /** + * Scope ID for prefixing injected CSS varialbes. + * This must be consistent with the `id` passed to `compileStyle`. + */ + id: string /** * https://babeljs.io/docs/en/babel-parser#plugins */ @@ -52,7 +58,7 @@ export interface SFCScriptCompileOptions { * from being hot-reloaded separately from component state. */ inlineTemplate?: boolean - templateOptions?: SFCTemplateCompileOptions + templateOptions?: Partial } const hasWarned: Record = {} @@ -71,19 +77,33 @@ function warnOnce(msg: string) { */ export function compileScript( sfc: SFCDescriptor, - options: SFCScriptCompileOptions = {} + options: SFCScriptCompileOptions ): SFCScriptBlock { - const { script, scriptSetup, styles, source, filename } = sfc + const { script, scriptSetup, source, filename } = sfc if (__DEV__ && !__TEST__ && scriptSetup) { warnOnce( `