From 7edef7994be7b0bf6eba6e72f028a3662726bfad Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 24 Nov 2020 06:25:10 +0000 Subject: [PATCH 0001/3788] build(deps-dev): bump lint-staged from 10.5.1 to 10.5.2 (#2664) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index fa3f5fe7ee9..fcddf8a9d04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4654,9 +4654,9 @@ lines-and-columns@^1.1.6: integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= lint-staged@^10.2.10: - version "10.5.1" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.1.tgz#901e915c2360072dded0e7d752a0d9a49e079daa" - integrity sha512-fTkTGFtwFIJJzn/PbUO3RXyEBHIhbfYBE7+rJyLcOXabViaO/h6OslgeK6zpeUtzkDrzkgyAYDTLAwx6JzDTHw== + version "10.5.2" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.2.tgz#acfaa0093af3262aee3130b2e22438941530bdd1" + integrity sha512-e8AYR1TDlzwB8VVd38Xu2lXDZf6BcshVqKVuBQThDJRaJLobqKnpbm4dkwJ2puypQNbLr9KF/9mfA649mAGvjA== dependencies: chalk "^4.1.0" cli-truncate "^2.1.0" From 2f32e4a077cb465e41655c3463be4984f60d9295 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 24 Nov 2020 06:26:43 +0000 Subject: [PATCH 0002/3788] build(deps-dev): bump @typescript-eslint/parser from 4.8.1 to 4.8.2 (#2665) --- yarn.lock | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/yarn.lock b/yarn.lock index fcddf8a9d04..e2fad7683b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -949,35 +949,35 @@ "@types/yargs-parser" "*" "@typescript-eslint/parser@^4.1.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.8.1.tgz#4fe2fbdbb67485bafc4320b3ae91e34efe1219d1" - integrity sha512-QND8XSVetATHK9y2Ltc/XBl5Ro7Y62YuZKnPEwnNPB8E379fDsvzJ1dMJ46fg/VOmk0hXhatc+GXs5MaXuL5Uw== + version "4.8.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.8.2.tgz#78dccbe5124de2b8dea2d4c363dee9f769151ca8" + integrity sha512-u0leyJqmclYr3KcXOqd2fmx6SDGBO0MUNHHAjr0JS4Crbb3C3d8dwAdlazy133PLCcPn+aOUFiHn72wcuc5wYw== dependencies: - "@typescript-eslint/scope-manager" "4.8.1" - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/typescript-estree" "4.8.1" + "@typescript-eslint/scope-manager" "4.8.2" + "@typescript-eslint/types" "4.8.2" + "@typescript-eslint/typescript-estree" "4.8.2" debug "^4.1.1" -"@typescript-eslint/scope-manager@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.8.1.tgz#e343c475f8f1d15801b546cb17d7f309b768fdce" - integrity sha512-r0iUOc41KFFbZdPAdCS4K1mXivnSZqXS5D9oW+iykQsRlTbQRfuFRSW20xKDdYiaCoH+SkSLeIF484g3kWzwOQ== +"@typescript-eslint/scope-manager@4.8.2": + version "4.8.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.8.2.tgz#a18388c63ae9c17adde519384f539392f2c4f0d9" + integrity sha512-qHQ8ODi7mMin4Sq2eh/6eu03uVzsf5TX+J43xRmiq8ujng7ViQSHNPLOHGw/Wr5dFEoxq/ubKhzClIIdQy5q3g== dependencies: - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/visitor-keys" "4.8.1" + "@typescript-eslint/types" "4.8.2" + "@typescript-eslint/visitor-keys" "4.8.2" -"@typescript-eslint/types@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.8.1.tgz#23829c73c5fc6f4fcd5346a7780b274f72fee222" - integrity sha512-ave2a18x2Y25q5K05K/U3JQIe2Av4+TNi/2YuzyaXLAsDx6UZkz1boZ7nR/N6Wwae2PpudTZmHFXqu7faXfHmA== +"@typescript-eslint/types@4.8.2": + version "4.8.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.8.2.tgz#c862dd0e569d9478eb82d6aee662ea53f5661a36" + integrity sha512-z1/AVcVF8ju5ObaHe2fOpZYEQrwHyZ7PTOlmjd3EoFeX9sv7UekQhfrCmgUO7PruLNfSHrJGQvrW3Q7xQ8EoAw== -"@typescript-eslint/typescript-estree@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.1.tgz#7307e3f2c9e95df7daa8dc0a34b8c43b7ec0dd32" - integrity sha512-bJ6Fn/6tW2g7WIkCWh3QRlaSU7CdUUK52shx36/J7T5oTQzANvi6raoTsbwGM11+7eBbeem8hCCKbyvAc0X3sQ== +"@typescript-eslint/typescript-estree@4.8.2": + version "4.8.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.2.tgz#eeec34707d8577600fb21661b5287226cc8b3bed" + integrity sha512-HToGNwI6fekH0dOw3XEVESUm71Onfam0AKin6f26S2FtUmO7o3cLlWgrIaT1q3vjB3wCTdww3Dx2iGq5wtUOCg== dependencies: - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/visitor-keys" "4.8.1" + "@typescript-eslint/types" "4.8.2" + "@typescript-eslint/visitor-keys" "4.8.2" debug "^4.1.1" globby "^11.0.1" is-glob "^4.0.1" @@ -985,12 +985,12 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.1.tgz#794f68ee292d1b2e3aa9690ebedfcb3a8c90e3c3" - integrity sha512-3nrwXFdEYALQh/zW8rFwP4QltqsanCDz4CwWMPiIZmwlk9GlvBeueEIbq05SEq4ganqM0g9nh02xXgv5XI3PeQ== +"@typescript-eslint/visitor-keys@4.8.2": + version "4.8.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.2.tgz#62cd3fbbbf65f8eccfbe6f159eb1b84a243a3f77" + integrity sha512-Vg+/SJTMZJEKKGHW7YC21QxgKJrSbxoYYd3MEUGtW7zuytHuEcksewq0DUmo4eh/CTNrVJGSdIY9AtRb6riWFw== dependencies: - "@typescript-eslint/types" "4.8.1" + "@typescript-eslint/types" "4.8.2" eslint-visitor-keys "^2.0.0" "@zeit/schemas@2.6.0": From ae2caad7406e4114f7fd11ff670921cddc8dc514 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 23 Nov 2020 16:32:24 -0500 Subject: [PATCH 0003/3788] wip: support resolving directives from setup scope variables by naming convention v-my-dir can be resovled from setup scope variable named "vMyDir". --- .../src/transforms/transformElement.ts | 104 ++++++++++++------ .../__snapshots__/compileScript.spec.ts.snap | 26 +++++ .../__tests__/compileScript.spec.ts | 23 ++++ packages/template-explorer/src/options.ts | 3 +- 4 files changed, 121 insertions(+), 35 deletions(-) diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index f2f1459e05c..c9d1a3ee405 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -29,8 +29,7 @@ import { isObject, isReservedProp, capitalize, - camelize, - EMPTY_OBJ + camelize } from '@vue/shared' import { createCompilerError, ErrorCodes } from '../errors' import { @@ -255,34 +254,16 @@ export function resolveComponentType( } // 3. user component (from setup bindings) - const bindings = context.bindingMetadata - if (bindings !== EMPTY_OBJ) { - const checkType = (type: BindingTypes) => { - let resolvedTag = tag - if ( - bindings[resolvedTag] === type || - bindings[(resolvedTag = camelize(tag))] === type || - bindings[(resolvedTag = capitalize(camelize(tag)))] === type - ) { - return resolvedTag - } - } - const tagFromConst = checkType(BindingTypes.SETUP_CONST) - if (tagFromConst) { - return context.inline - ? // in inline mode, const setup bindings (e.g. imports) can be used as-is - tagFromConst - : `$setup[${JSON.stringify(tagFromConst)}]` - } - const tagFromSetup = - checkType(BindingTypes.SETUP_LET) || - checkType(BindingTypes.SETUP_REF) || - checkType(BindingTypes.SETUP_MAYBE_REF) - if (tagFromSetup) { - return context.inline - ? // setup scope bindings that may be refs need to be unrefed - `${context.helperString(UNREF)}(${tagFromSetup})` - : `$setup[${JSON.stringify(tagFromSetup)}]` + // this is skipped in browser build since browser builds do not perform + // binding analysis. + if (!__BROWSER__) { + const fromSetup = resolveSetupReference( + tag, + capitalize(camelize(tag)), + context + ) + if (fromSetup) { + return fromSetup } } @@ -292,6 +273,45 @@ export function resolveComponentType( return toValidAssetId(tag, `component`) } +function resolveSetupReference( + name: string, + interopName: string, + context: TransformContext +) { + const bindings = context.bindingMetadata + if (!bindings) { + return + } + + const checkType = (type: BindingTypes) => { + if (bindings[name] === type) { + return name + } + if (bindings[interopName] === type) { + return interopName + } + } + + const fromConst = checkType(BindingTypes.SETUP_CONST) + if (fromConst) { + return context.inline + ? // in inline mode, const setup bindings (e.g. imports) can be used as-is + fromConst + : `$setup[${JSON.stringify(fromConst)}]` + } + + const fromMaybeRef = + checkType(BindingTypes.SETUP_LET) || + checkType(BindingTypes.SETUP_REF) || + checkType(BindingTypes.SETUP_MAYBE_REF) + if (fromMaybeRef) { + return context.inline + ? // setup scope bindings that may be refs need to be unrefed + `${context.helperString(UNREF)}(${fromMaybeRef})` + : `$setup[${JSON.stringify(fromMaybeRef)}]` + } +} + export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode export function buildProps( @@ -590,12 +610,28 @@ function buildDirectiveArgs( const dirArgs: ArrayExpression['elements'] = [] const runtime = directiveImportMap.get(dir) if (runtime) { + // built-in directive with runtime dirArgs.push(context.helperString(runtime)) } else { - // inject statement for resolving directive - context.helper(RESOLVE_DIRECTIVE) - context.directives.add(dir.name) - dirArgs.push(toValidAssetId(dir.name, `directive`)) + // user directive. + // see if we have directives exposed via + + `, + { inlineTemplate: true } + ) + expect(content).toMatch('[_unref(vMyDir)]') + expect(content).toMatch('_createVNode(ChildComp)') + // kebab-case component support + expect(content).toMatch('_createVNode(SomeOtherComp)') + assertCode(content) + }) + test('avoid unref() when necessary', () => { // function, const, component import const { content } = compile( diff --git a/packages/template-explorer/src/options.ts b/packages/template-explorer/src/options.ts index cdf00ec874a..dab7f8adf8a 100644 --- a/packages/template-explorer/src/options.ts +++ b/packages/template-explorer/src/options.ts @@ -19,7 +19,8 @@ export const compilerOptions: CompilerOptions = reactive({ setupConst: BindingTypes.SETUP_CONST, setupLet: BindingTypes.SETUP_LET, setupMaybeRef: BindingTypes.SETUP_MAYBE_REF, - setupProp: BindingTypes.PROPS + setupProp: BindingTypes.PROPS, + vMySetupDir: BindingTypes.SETUP_CONST } }) From 47d73c23e18b3951d299497990081aca696814c4 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 24 Nov 2020 15:12:59 -0500 Subject: [PATCH 0004/3788] wip: defineOptions -> defineProps + defineEmit + useContext --- .../__snapshots__/compileScript.spec.ts.snap | 148 ++++--- .../__snapshots__/cssVars.spec.ts.snap | 4 +- .../__tests__/compileScript.spec.ts | 231 ++++++----- .../compiler-sfc/__tests__/cssVars.spec.ts | 8 +- packages/compiler-sfc/src/compileScript.ts | 366 ++++++++++-------- packages/runtime-core/src/apiDefineOptions.ts | 91 ----- packages/runtime-core/src/apiSetupHelpers.ts | 60 +++ packages/runtime-core/src/component.ts | 6 +- packages/runtime-core/src/componentOptions.ts | 2 +- .../runtime-core/src/componentRenderUtils.ts | 3 +- packages/runtime-core/src/index.ts | 2 +- test-dts/defineOptions.test-d.ts | 96 ----- test-dts/setupHelpers.test-d.ts | 89 +++++ 13 files changed, 588 insertions(+), 518 deletions(-) delete mode 100644 packages/runtime-core/src/apiDefineOptions.ts create mode 100644 packages/runtime-core/src/apiSetupHelpers.ts delete mode 100644 test-dts/defineOptions.test-d.ts create mode 100644 test-dts/setupHelpers.test-d.ts diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 9d05d643a83..11658523ece 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -33,38 +33,55 @@ return { x } export const n = 1" `; -exports[`SFC compile `) @@ -36,21 +32,42 @@ const bar = 1 expect(bindings).toStrictEqual({ foo: BindingTypes.PROPS, bar: BindingTypes.SETUP_CONST, - props: BindingTypes.SETUP_CONST, - emit: BindingTypes.SETUP_CONST + props: BindingTypes.SETUP_CONST }) // should remove defineOptions import and call - expect(content).not.toMatch('defineOptions') + expect(content).not.toMatch('defineProps') // should generate correct setup signature - expect(content).toMatch(`setup(__props, { props, emit }) {`) + expect(content).toMatch(`setup(__props) {`) + // should assign user identifier to it + expect(content).toMatch(`const props = __props`) // should include context options in default export expect(content).toMatch(`export default { expose: [], props: { - foo: String - }, - emit: ['a', 'b'],`) + foo: String +},`) + }) + + test('defineEmit()', () => { + const { content, bindings } = compile(` + + `) + assertCode(content) + expect(bindings).toStrictEqual({ + myEmit: BindingTypes.SETUP_CONST + }) + // should remove defineOptions import and call + expect(content).not.toMatch('defineEmit') + // should generate correct setup signature + expect(content).toMatch(`setup(__props, { emit: myEmit }) {`) + // should include context options in default export + expect(content).toMatch(`export default { + expose: [], + emits: ['foo', 'bar'],`) }) describe(' `) assertCode(content) @@ -375,42 +390,40 @@ const { props, emit } = defineOptions({ expose: [], props: { foo: String }, emits: ['a', 'b'], - setup(__props, { props, emit }) {`) + setup(__props, { emit }) {`) }) - test('defineOptions w/ type / extract props', () => { + test('defineProps w/ type', () => { const { content, bindings } = compile(` `) assertCode(content) @@ -466,33 +479,28 @@ const { props, emit } = defineOptions({ }) }) - test('defineOptions w/ type / extract emits', () => { + test('defineEmit w/ type', () => { const { content } = compile(` `) assertCode(content) - expect(content).toMatch(`props: {},\n emit: (e: 'foo' | 'bar') => void,`) + expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`) expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`) }) - test('defineOptions w/ type / extract emits (union)', () => { + test('defineEmit w/ type (union)', () => { + const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)` const { content } = compile(` `) assertCode(content) - expect(content).toMatch( - `props: {},\n emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void),` - ) + expect(content).toMatch(`emit: (${type}),`) expect(content).toMatch( `emits: ["foo", "bar", "baz"] as unknown as undefined` ) @@ -774,71 +782,96 @@ const { props, emit } = defineOptions({ ).toThrow(`ref: statements can only contain assignment expressions`) }) - test('defineOptions() w/ both type and non-type args', () => { + test('defineProps/Emit() w/ both type and non-type args', () => { + expect(() => { + compile(``) + }).toThrow(`cannot accept both type and non-type arguments`) + expect(() => { compile(``) }).toThrow(`cannot accept both type and non-type arguments`) }) - test('defineOptions() referencing local var', () => { + test('defineProps/Emit() referencing local var', () => { expect(() => compile(``) ).toThrow(`cannot reference locally declared variables`) + + expect(() => + compile(``) + ).toThrow(`cannot reference locally declared variables`) }) - test('defineOptions() referencing ref declarations', () => { + test('defineProps/Emit() referencing ref declarations', () => { + expect(() => + compile(``) + ).toThrow(`cannot reference locally declared variables`) + expect(() => compile(``) ).toThrow(`cannot reference locally declared variables`) }) - test('should allow defineOptions() referencing scope var', () => { + test('should allow defineProps/Emit() referencing scope var', () => { assertCode( compile(``).content ) }) - test('should allow defineOptions() referencing imported binding', () => { + test('should allow defineProps/Emit() referencing imported binding', () => { assertCode( compile(``).content ) }) @@ -1063,11 +1096,9 @@ describe('SFC analyze \n` + ``).descriptor + .slotted + ).toBe(false) + expect( + parse(``) + .descriptor.slotted + ).toBe(true) + expect( + parse(``) + .descriptor.slotted + ).toBe(true) + }) + test('error tolerance', () => { const { errors } = parse(` @@ -20,9 +20,9 @@ import CodeMirror from '../codemirror/CodeMirror.vue' import { store } from '../store' import { ref } from 'vue' -type Modes = 'preview' | 'executed' | 'js' | 'css' | 'template' +type Modes = 'preview' | 'js' | 'css' -const modes: Modes[] = ['preview', 'js', 'css', 'template', 'executed'] +const modes: Modes[] = ['preview', 'js', 'css'] const mode = ref('preview') @@ -35,14 +35,15 @@ const mode = ref('preview') .tab-buttons { box-sizing: border-box; border-bottom: 1px solid #ddd; + background-color: white; } .tab-buttons button { margin: 0; font-size: 13px; - font-family: 'Source Code Pro', monospace; + font-family: var(--font-code); border: none; outline: none; - background-color: #f8f8f8; + background-color: transparent; padding: 8px 16px 6px; text-transform: uppercase; cursor: pointer; @@ -51,7 +52,7 @@ const mode = ref('preview') } button.active { - color: #42b983; - border-bottom: 3px solid #42b983; + color: var(--color-branding-dark); + border-bottom: 3px solid var(--color-branding-dark); } diff --git a/packages/sfc-playground/src/output/Preview.vue b/packages/sfc-playground/src/output/Preview.vue index 8ef3b101441..ef76c23f311 100644 --- a/packages/sfc-playground/src/output/Preview.vue +++ b/packages/sfc-playground/src/output/Preview.vue @@ -11,12 +11,11 @@ - - `.trim() +export const MAIN_FILE = 'App.vue' +export const COMP_IDENTIFIER = `__sfc__` + // @ts-ignore -export const sandboxVueURL = import.meta.env.PROD +export const SANDBOX_VUE_URL = import.meta.env.PROD ? '/vue.runtime.esm-browser.js' // to be copied on build : '/src/vue-dev-proxy' -export const store = reactive({ - code: saved, - compiled: { - executed: '', +export class File { + filename: string + code: string + compiled = { js: '', - css: '', - template: '' + css: '' + } + + constructor(filename: string, code = '') { + this.filename = filename + this.code = code + } +} + +interface Store { + files: Record + activeFilename: string + readonly activeFile: File + errors: (string | Error)[] +} + +const savedFiles = localStorage.getItem(STORAGE_KEY) +const files = savedFiles + ? JSON.parse(savedFiles) + : { + 'App.vue': new File(MAIN_FILE, welcomeCode) + } + +export const store: Store = reactive({ + files, + activeFilename: MAIN_FILE, + get activeFile() { + return store.files[store.activeFilename] }, - errors: [] as (string | CompilerError | SyntaxError)[] + errors: [] +}) + +for (const file in store.files) { + if (file !== MAIN_FILE) { + compileFile(store.files[file]) + } +} + +watchEffect(() => compileFile(store.activeFile)) +watchEffect(() => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(store.files)) }) -const filename = 'Playground.vue' -const id = 'scope-id' -const compIdentifier = `__comp` +export function setActive(filename: string) { + store.activeFilename = filename +} + +export function addFile(filename: string) { + store.files[filename] = new File(filename) + setActive(filename) +} -watchEffect(async () => { - const { code, compiled } = store +export function deleteFile(filename: string) { + if (confirm(`Are you sure you want to delete ${filename}?`)) { + if (store.activeFilename === filename) { + store.activeFilename = MAIN_FILE + } + delete store.files[filename] + } +} + +async function compileFile({ filename, code, compiled }: File) { if (!code.trim()) { return } - localStorage.setItem(storeKey, code) + if (filename.endsWith('.js')) { + compiled.js = code + return + } + const id = await hashId(filename) const { errors, descriptor } = parse(code, { filename, sourceMap: true }) if (errors.length) { store.errors = errors @@ -84,20 +133,14 @@ watchEffect(async () => { refSugar: true, inlineTemplate: true }) - compiled.js = compiledScript.content.trim() finalCode += - `\n` + - rewriteDefault( - rewriteVueImports(compiledScript.content), - compIdentifier - ) + `\n` + rewriteDefault(compiledScript.content, COMP_IDENTIFIER) } catch (e) { store.errors = [e] return } } else { - compiled.js = '' - finalCode += `\nconst ${compIdentifier} = {}` + finalCode += `\nconst ${COMP_IDENTIFIER} = {}` } // template @@ -115,25 +158,25 @@ watchEffect(async () => { return } - compiled.template = templateResult.code.trim() finalCode += `\n` + - rewriteVueImports(templateResult.code).replace( + templateResult.code.replace( /\nexport (function|const) render/, '$1 render' ) - finalCode += `\n${compIdentifier}.render = render` - } else { - compiled.template = descriptor.scriptSetup - ? '/* inlined in JS (script setup) */' - : '/* no template present */' + finalCode += `\n${COMP_IDENTIFIER}.render = render` } if (hasScoped) { - finalCode += `\n${compIdentifier}.__scopeId = ${JSON.stringify( + finalCode += `\n${COMP_IDENTIFIER}.__scopeId = ${JSON.stringify( `data-v-${id}` )}` } + if (finalCode) { + finalCode += `\nexport default ${COMP_IDENTIFIER}` + compiled.js = finalCode.trimStart() + } + // styles let css = '' for (const style of descriptor.styles) { @@ -162,25 +205,18 @@ watchEffect(async () => { } if (css) { compiled.css = css.trim() - finalCode += `\ndocument.getElementById('__sfc-styles').innerHTML = ${JSON.stringify( - css - )}` } else { - compiled.css = '' + compiled.css = '/* No From aa8bf1b7a352095349e8799aa79247af5a29f837 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 28 Mar 2021 21:55:22 -0400 Subject: [PATCH 0331/3788] workflow(sfc-playground): improve module rewrite --- .../src/output/moduleCompiler.ts | 234 ++++++++++++------ .../sfc-playground/src/output/srcdoc.html | 14 ++ 2 files changed, 178 insertions(+), 70 deletions(-) diff --git a/packages/sfc-playground/src/output/moduleCompiler.ts b/packages/sfc-playground/src/output/moduleCompiler.ts index 82ffd223957..31f6bc06799 100644 --- a/packages/sfc-playground/src/output/moduleCompiler.ts +++ b/packages/sfc-playground/src/output/moduleCompiler.ts @@ -1,12 +1,23 @@ import { store, MAIN_FILE, SANDBOX_VUE_URL, File } from '../store' -import { babelParse, MagicString, walk } from '@vue/compiler-sfc' +import { + babelParse, + MagicString, + walk, + walkIdentifiers +} from '@vue/compiler-sfc' import { babelParserDefaultPlugins } from '@vue/shared' -import { Identifier, Node } from '@babel/types' +import { ExportSpecifier, Identifier, Node, ObjectProperty } from '@babel/types' export function compileModulesForPreview() { return processFile(store.files[MAIN_FILE]).reverse() } +const modulesKey = `__modules__` +const exportKey = `__export__` +const dynamicImportKey = `__dynamic_import__` +const moduleKey = `__module__` + +// similar logic with Vite's SSR transform, except this is targeting the browser function processFile(file: File, seen = new Set()) { if (seen.has(file)) { return [] @@ -14,133 +25,191 @@ function processFile(file: File, seen = new Set()) { seen.add(file) const { js, css } = file.compiled + + const s = new MagicString(js) + const ast = babelParse(js, { sourceFilename: file.filename, sourceType: 'module', plugins: [...babelParserDefaultPlugins] }).program.body + const idToImportMap = new Map() + const declaredConst = new Set() const importedFiles = new Set() const importToIdMap = new Map() - const s = new MagicString(js) - - function registerImport(source: string) { + function defineImport(node: Node, source: string) { const filename = source.replace(/^\.\/+/, '') if (!(filename in store.files)) { throw new Error(`File "${filename}" does not exist.`) } if (importedFiles.has(filename)) { - return importToIdMap.get(filename) + return importToIdMap.get(filename)! } importedFiles.add(filename) const id = `__import_${importedFiles.size}__` importToIdMap.set(filename, id) - s.prepend(`const ${id} = __modules__[${JSON.stringify(filename)}]\n`) + s.appendLeft( + node.start!, + `const ${id} = ${modulesKey}[${JSON.stringify(filename)}]\n` + ) return id } + function defineExport(name: string, local = name) { + s.append(`\n${exportKey}(${moduleKey}, "${name}", () => ${local})`) + } + + // 0. instantiate module s.prepend( - `const mod = __modules__[${JSON.stringify( + `const ${moduleKey} = __modules__[${JSON.stringify( file.filename - )}] = Object.create(null)\n\n` + )}] = { [Symbol.toStringTag]: "Module" }\n\n` ) + // 1. check all import statements and record id -> importName map for (const node of ast) { + // import foo from 'foo' --> foo -> __import_foo__.default + // import { baz } from 'foo' --> baz -> __import_foo__.baz + // import * as ok from 'foo' --> ok -> __import_foo__ if (node.type === 'ImportDeclaration') { const source = node.source.value - if (source === 'vue') { - // rewrite Vue imports - s.overwrite( - node.source.start!, - node.source.end!, - `"${SANDBOX_VUE_URL}"` - ) - } else if (source.startsWith('./')) { - // rewrite the import to retrieve the import from global registry - s.remove(node.start!, node.end!) - - const id = registerImport(source) - + if (source.startsWith('./')) { + const importId = defineImport(node, node.source.value) for (const spec of node.specifiers) { - if (spec.type === 'ImportDefaultSpecifier') { - s.prependRight( - node.start!, - `const ${spec.local.name} = ${id}.default\n` - ) - } else if (spec.type === 'ImportSpecifier') { - s.prependRight( - node.start!, - `const ${spec.local.name} = ${id}.${ - (spec.imported as Identifier).name - }\n` + if (spec.type === 'ImportSpecifier') { + idToImportMap.set( + spec.local.name, + `${importId}.${(spec.imported as Identifier).name}` ) + } else if (spec.type === 'ImportDefaultSpecifier') { + idToImportMap.set(spec.local.name, `${importId}.default`) } else { - // namespace import - s.prependRight(node.start!, `const ${spec.local.name} = ${id}`) + // namespace specifier + idToImportMap.set(spec.local.name, importId) } } + s.remove(node.start!, node.end!) + } else { + if (source === 'vue') { + // rewrite Vue imports + s.overwrite( + node.source.start!, + node.source.end!, + `"${SANDBOX_VUE_URL}"` + ) + } } } + } - if (node.type === 'ExportDefaultDeclaration') { - // export default -> mod.default = ... - s.overwrite(node.start!, node.declaration.start!, 'mod.default = ') - } - + // 2. check all export statements and define exports + for (const node of ast) { + // named exports if (node.type === 'ExportNamedDeclaration') { - if (node.source) { - // export { foo } from '...' -> mode.foo = __import_x__.foo - const id = registerImport(node.source.value) - let code = `` - for (const spec of node.specifiers) { - if (spec.type === 'ExportSpecifier') { - code += `mod.${(spec.exported as Identifier).name} = ${id}.${ - spec.local.name - }\n` - } - } - s.overwrite(node.start!, node.end!, code) - } else if (node.declaration) { + if (node.declaration) { if ( node.declaration.type === 'FunctionDeclaration' || node.declaration.type === 'ClassDeclaration' ) { // export function foo() {} - const name = node.declaration.id!.name - s.appendLeft(node.end!, `\nmod.${name} = ${name}\n`) + defineExport(node.declaration.id!.name) } else if (node.declaration.type === 'VariableDeclaration') { // export const foo = 1, bar = 2 for (const decl of node.declaration.declarations) { - for (const { name } of extractIdentifiers(decl.id)) { - s.appendLeft(node.end!, `\nmod.${name} = ${name}`) + const names = extractNames(decl.id as any) + for (const name of names) { + defineExport(name) } } } s.remove(node.start!, node.declaration.start!) + } else if (node.source) { + // export { foo, bar } from './foo' + const importId = defineImport(node, node.source.value) + for (const spec of node.specifiers) { + defineExport( + (spec.exported as Identifier).name, + `${importId}.${(spec as ExportSpecifier).local.name}` + ) + } + s.remove(node.start!, node.end!) } else { - let code = `` + // export { foo, bar } for (const spec of node.specifiers) { - if (spec.type === 'ExportSpecifier') { - code += `mod.${(spec.exported as Identifier).name} = ${ - spec.local.name - }\n` - } + const local = (spec as ExportSpecifier).local.name + const binding = idToImportMap.get(local) + defineExport((spec.exported as Identifier).name, binding || local) } - s.overwrite(node.start!, node.end!, code) + s.remove(node.start!, node.end!) } } + // default export + if (node.type === 'ExportDefaultDeclaration') { + s.overwrite(node.start!, node.start! + 14, `${moduleKey}.default =`) + } + + // export * from './foo' if (node.type === 'ExportAllDeclaration') { - const id = registerImport(node.source.value) - s.overwrite(node.start!, node.end!, `Object.assign(mod, ${id})`) + const importId = defineImport(node, node.source.value) + s.remove(node.start!, node.end!) + s.append(`\nfor (const key in ${importId}) { + if (key !== 'default') { + ${exportKey}(${moduleKey}, key, () => ${importId}[key]) + } + }`) } } - // dynamic import - walk(ast as any, { - enter(node) { - if (node.type === 'ImportExpression') { + // 3. convert references to import bindings + for (const node of ast) { + if (node.type === 'ImportDeclaration') continue + walkIdentifiers(node, (id, parent, parentStack) => { + const binding = idToImportMap.get(id.name) + if (!binding) { + return + } + if (isStaticProperty(parent) && parent.shorthand) { + // let binding used in a property shorthand + // { foo } -> { foo: __import_x__.foo } + // skip for destructure patterns + if ( + !(parent as any).inPattern || + isInDestructureAssignment(parent, parentStack) + ) { + s.appendLeft(id.end!, `: ${binding}`) + } + } else if ( + parent.type === 'ClassDeclaration' && + id === parent.superClass + ) { + if (!declaredConst.has(id.name)) { + declaredConst.add(id.name) + // locate the top-most node containing the class declaration + const topNode = parentStack[1] + s.prependRight(topNode.start!, `const ${id.name} = ${binding};\n`) + } + } else { + s.overwrite(id.start!, id.end!, binding) + } + }) + } + + // 4. convert dynamic imports + ;(walk as any)(ast, { + enter(node: Node, parent: Node) { + if (node.type === 'Import' && parent.type === 'CallExpression') { + const arg = parent.arguments[0] + if (arg.type === 'StringLiteral' && arg.value.startsWith('./')) { + s.overwrite(node.start!, node.start! + 6, dynamicImportKey) + s.overwrite( + arg.start!, + arg.end!, + JSON.stringify(arg.value.replace(/^\.\/+/, '')) + ) + } } } }) @@ -161,6 +230,13 @@ function processFile(file: File, seen = new Set()) { return processed } +const isStaticProperty = (node: Node): node is ObjectProperty => + node.type === 'ObjectProperty' && !node.computed + +function extractNames(param: Node): string[] { + return extractIdentifiers(param).map(id => id.name) +} + function extractIdentifiers( param: Node, nodes: Identifier[] = [] @@ -205,3 +281,21 @@ function extractIdentifiers( return nodes } + +function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean { + if ( + parent && + (parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern') + ) { + let i = parentStack.length + while (i--) { + const p = parentStack[i] + if (p.type === 'AssignmentExpression') { + return true + } else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) { + break + } + } + } + return false +} diff --git a/packages/sfc-playground/src/output/srcdoc.html b/packages/sfc-playground/src/output/srcdoc.html index 31e033542f2..063a522d456 100644 --- a/packages/sfc-playground/src/output/srcdoc.html +++ b/packages/sfc-playground/src/output/srcdoc.html @@ -11,6 +11,20 @@ + diff --git a/packages/sfc-playground/src/download/download.ts b/packages/sfc-playground/src/download/download.ts new file mode 100644 index 00000000000..91cdfd00f34 --- /dev/null +++ b/packages/sfc-playground/src/download/download.ts @@ -0,0 +1,31 @@ +import { exportFiles } from '../store' +import { saveAs } from 'file-saver' + +import index from './template/index.html?raw' +import main from './template/main.js?raw' +import pkg from './template/package.json?raw' +import config from './template/vite.config.js?raw' +import readme from './template/README.md?raw' + +export async function downloadProject() { + const { default: JSZip } = await import('jszip') + const zip = new JSZip() + + // basic structure + zip.file('index.html', index) + zip.file('package.json', pkg) + zip.file('vite.config.js', config) + zip.file('README.md', readme) + + // project src + const src = zip.folder('src')! + src.file('main.js', main) + + const files = exportFiles() + for (const file in files) { + src.file(file, files[file]) + } + + const blob = await zip.generateAsync({ type: 'blob' }) + saveAs(blob, 'vue-project.zip') +} diff --git a/packages/sfc-playground/src/download/template/README.md b/packages/sfc-playground/src/download/template/README.md new file mode 100644 index 00000000000..39c47d255ae --- /dev/null +++ b/packages/sfc-playground/src/download/template/README.md @@ -0,0 +1,14 @@ +# Vite Vue Starter + +This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) v12+. + +To start: + +```sh +npm install +npm run dev + +# if using yarn: +yarn +yarn dev +``` diff --git a/packages/sfc-playground/src/download/template/index.html b/packages/sfc-playground/src/download/template/index.html new file mode 100644 index 00000000000..030a6ff51bf --- /dev/null +++ b/packages/sfc-playground/src/download/template/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/packages/sfc-playground/src/download/template/main.js b/packages/sfc-playground/src/download/template/main.js new file mode 100644 index 00000000000..01433bca2ac --- /dev/null +++ b/packages/sfc-playground/src/download/template/main.js @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/packages/sfc-playground/src/download/template/package.json b/packages/sfc-playground/src/download/template/package.json new file mode 100644 index 00000000000..6bede6ce627 --- /dev/null +++ b/packages/sfc-playground/src/download/template/package.json @@ -0,0 +1,17 @@ +{ + "name": "vite-vue-starter", + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "dependencies": { + "vue": "^3.0.9" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^1.1.5", + "@vue/compiler-sfc": "^3.0.9", + "vite": "^2.1.3" + } +} \ No newline at end of file diff --git a/packages/sfc-playground/src/download/template/vite.config.js b/packages/sfc-playground/src/download/template/vite.config.js new file mode 100644 index 00000000000..315212d69a7 --- /dev/null +++ b/packages/sfc-playground/src/download/template/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()] +}) diff --git a/packages/sfc-playground/src/editor/FileSelector.vue b/packages/sfc-playground/src/editor/FileSelector.vue index 23c6d4f363e..639b0ec1860 100644 --- a/packages/sfc-playground/src/editor/FileSelector.vue +++ b/packages/sfc-playground/src/editor/FileSelector.vue @@ -101,14 +101,9 @@ function doneAddFile() { padding-left: 0; } .add { - margin: 0; font-size: 20px; font-family: var(--font-code); color: #999; - border: none; - outline: none; - background-color: transparent; - cursor: pointer; vertical-align: middle; margin-left: 6px; } diff --git a/packages/sfc-playground/src/output/Output.vue b/packages/sfc-playground/src/output/Output.vue index 0027cdaed04..87b189094f2 100644 --- a/packages/sfc-playground/src/output/Output.vue +++ b/packages/sfc-playground/src/output/Output.vue @@ -38,15 +38,10 @@ const mode = ref('preview') background-color: white; } .tab-buttons button { - margin: 0; font-size: 13px; font-family: var(--font-code); - border: none; - outline: none; - background-color: transparent; padding: 8px 16px 6px; text-transform: uppercase; - cursor: pointer; color: #999; box-sizing: border-box; } diff --git a/packages/sfc-playground/src/output/srcdoc.html b/packages/sfc-playground/src/output/srcdoc.html index 063a522d456..e389e39aa3f 100644 --- a/packages/sfc-playground/src/output/srcdoc.html +++ b/packages/sfc-playground/src/output/srcdoc.html @@ -97,7 +97,8 @@ ['clear', 'log', 'info', 'dir', 'warn', 'error', 'table'].forEach((level) => { const original = console[level]; console[level] = (...args) => { - if (String(args[0]).includes('You are running a development build of Vue')) { + const msg = String(args[0]) + if (msg.includes('You are running a development build of Vue')) { return } const stringifiedArgs = stringify(args); diff --git a/packages/sfc-playground/src/store.ts b/packages/sfc-playground/src/store.ts index 0eb121b109c..2328d52af62 100644 --- a/packages/sfc-playground/src/store.ts +++ b/packages/sfc-playground/src/store.ts @@ -7,8 +7,6 @@ import { rewriteDefault } from '@vue/compiler-sfc' -const STORAGE_KEY = 'vue-sfc-playground' - const welcomeCode = ` diff --git a/packages/sfc-playground/src/output/Preview.vue b/packages/sfc-playground/src/output/Preview.vue index ef76c23f311..0c3feac2fdc 100644 --- a/packages/sfc-playground/src/output/Preview.vue +++ b/packages/sfc-playground/src/output/Preview.vue @@ -30,26 +30,22 @@ async function updatePreview() { const modules = compileModulesForPreview() console.log(`successfully compiled ${modules.length} modules.`) // reset modules - await proxy.eval(` - window.__modules__ = {} - window.__css__ = '' - `) - // evaluate modules - for (const mod of modules) { - await proxy.eval(mod) - } - // reboot - await proxy.eval(` - import { createApp as _createApp } from "${SANDBOX_VUE_URL}" - if (window.__app__) { - window.__app__.unmount() - document.getElementById('app').innerHTML = '' - } - document.getElementById('__sfc-styles').innerHTML = window.__css__ - const app = window.__app__ = _createApp(__modules__["${MAIN_FILE}"].default) - app.config.errorHandler = e => console.error(e) - app.mount('#app') - `) + await proxy.eval([ + `window.__modules__ = {};window.__css__ = ''`, + ...modules, + ` +import { createApp as _createApp } from "${SANDBOX_VUE_URL}" + +if (window.__app__) { + window.__app__.unmount() + document.getElementById('app').innerHTML = '' +} + +document.getElementById('__sfc-styles').innerHTML = window.__css__ +const app = window.__app__ = _createApp(__modules__["${MAIN_FILE}"].default) +app.config.errorHandler = e => console.error(e) +app.mount('#app')`.trim() + ]) } catch (e) { runtimeError.value = e.stack } diff --git a/packages/sfc-playground/src/output/PreviewProxy.ts b/packages/sfc-playground/src/output/PreviewProxy.ts index 338da5ab868..8c115e0d2ab 100644 --- a/packages/sfc-playground/src/output/PreviewProxy.ts +++ b/packages/sfc-playground/src/output/PreviewProxy.ts @@ -86,7 +86,7 @@ export class PreviewProxy { } } - eval(script: string) { + eval(script: string | string[]) { return this.iframe_command('eval', { script }) } diff --git a/packages/sfc-playground/src/output/srcdoc.html b/packages/sfc-playground/src/output/srcdoc.html index e389e39aa3f..4446c493247 100644 --- a/packages/sfc-playground/src/output/srcdoc.html +++ b/packages/sfc-playground/src/output/srcdoc.html @@ -9,7 +9,7 @@ From 3736496006485e61614bef285ea89ea2a33134c4 Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Tue, 30 Mar 2021 03:15:08 +0800 Subject: [PATCH 0340/3788] fix(runtime-core): avoid unmount teleport's children multiple times (#3499) fix #3497 --- .../__tests__/components/Teleport.spec.ts | 42 ++++++++++++++++++- packages/runtime-core/src/renderer.ts | 22 +++++----- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/packages/runtime-core/__tests__/components/Teleport.spec.ts b/packages/runtime-core/__tests__/components/Teleport.spec.ts index f6babd9263b..8b234e2b1e6 100644 --- a/packages/runtime-core/__tests__/components/Teleport.spec.ts +++ b/packages/runtime-core/__tests__/components/Teleport.spec.ts @@ -8,7 +8,9 @@ import { ref, nextTick, markRaw, - defineComponent + defineComponent, + withDirectives, + createApp } from '@vue/runtime-test' import { createVNode, Fragment } from '../../src/vnode' import { compile, render as domRender } from 'vue' @@ -432,4 +434,42 @@ describe('renderer: teleport', () => { `"
teleported
false"` ) }) + + // #3497 + test(`the dir hooks of the Teleport's children should be called correctly`, async () => { + const target = nodeOps.createElement('div') + const root = nodeOps.createElement('div') + const toggle = ref(true) + const dir = { + mounted: jest.fn(), + unmounted: jest.fn() + } + + const app = createApp({ + setup() { + return () => { + return toggle.value + ? h(Teleport, { to: target }, [ + withDirectives(h('div', ['foo']), [[dir]]) + ]) + : null + } + } + }) + app.mount(root) + + expect(serializeInner(root)).toMatchInlineSnapshot( + `""` + ) + expect(serializeInner(target)).toMatchInlineSnapshot(`"
foo
"`) + expect(dir.mounted).toHaveBeenCalledTimes(1) + expect(dir.unmounted).toHaveBeenCalledTimes(0) + + toggle.value = false + await nextTick() + expect(serializeInner(root)).toMatchInlineSnapshot(`""`) + expect(serializeInner(target)).toMatchInlineSnapshot(`""`) + expect(dir.mounted).toHaveBeenCalledTimes(1) + expect(dir.unmounted).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 4375dcdbf10..5122e7a9131 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -2096,7 +2096,16 @@ function baseCreateRenderer( invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount') } - if ( + if (shapeFlag & ShapeFlags.TELEPORT) { + ;(vnode.type as typeof TeleportImpl).remove( + vnode, + parentComponent, + parentSuspense, + optimized, + internals, + doRemove + ) + } else if ( dynamicChildren && // #1153: fast path should not be taken for non-stable (v-for) fragments (type !== Fragment || @@ -2119,17 +2128,6 @@ function baseCreateRenderer( unmountChildren(children as VNode[], parentComponent, parentSuspense) } - if (shapeFlag & ShapeFlags.TELEPORT) { - ;(vnode.type as typeof TeleportImpl).remove( - vnode, - parentComponent, - parentSuspense, - optimized, - internals, - doRemove - ) - } - if (doRemove) { remove(vnode) } From 555b016dcb3b347a1d8b3d14df74c175115adb0b Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Tue, 30 Mar 2021 03:18:25 +0800 Subject: [PATCH 0341/3788] fix(compiler-core): allow PascalCase dynamic component tag usage (#3508) fix #3507 --- .../transforms/transformElement.spec.ts | 18 ++++++++++++++++++ .../src/transforms/transformElement.ts | 13 +++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index c07bb0e5533..bc2c9686a6c 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -836,6 +836,24 @@ describe('compiler: element transform', () => { }) }) + test('capitalized version w/ static binding', () => { + const { node, root } = parseWithBind(``) + expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT) + expect(node).toMatchObject({ + isBlock: true, + tag: { + callee: RESOLVE_DYNAMIC_COMPONENT, + arguments: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'foo', + isStatic: true + } + ] + } + }) + }) + test('dynamic binding', () => { const { node, root } = parseWithBind(``) expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT) diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 5c34c6d2eee..398758f4234 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -230,8 +230,9 @@ export function resolveComponentType( const { tag } = node // 1. dynamic component - const isProp = - node.tag === 'component' ? findProp(node, 'is') : findDir(node, 'is') + const isProp = isComponentTag(tag) + ? findProp(node, 'is') + : findDir(node, 'is') if (isProp) { const exp = isProp.type === NodeTypes.ATTRIBUTE @@ -413,7 +414,7 @@ export function buildProps( } } // skip :is on - if (name === 'is' && tag === 'component') { + if (name === 'is' && isComponentTag(tag)) { continue } properties.push( @@ -452,7 +453,7 @@ export function buildProps( // skip v-is and :is on if ( name === 'is' || - (isBind && tag === 'component' && isBindKey(arg, 'is')) + (isBind && isComponentTag(tag) && isBindKey(arg, 'is')) ) { continue } @@ -672,3 +673,7 @@ function stringifyDynamicPropNames(props: string[]): string { } return propsNamesString + `]` } + +function isComponentTag(tag: string) { + return tag[0].toLowerCase() + tag.slice(1) === 'component' +} From 4d2853ef57d28b5f0f7c395b1319076e0534cc2f Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 29 Mar 2021 15:26:54 -0400 Subject: [PATCH 0342/3788] workflow(sfc-playground): display analyzed bindings in js output --- packages/sfc-playground/src/store.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/sfc-playground/src/store.ts b/packages/sfc-playground/src/store.ts index 5a0a5d05027..7e5cdcb1bf7 100644 --- a/packages/sfc-playground/src/store.ts +++ b/packages/sfc-playground/src/store.ts @@ -147,6 +147,13 @@ async function compileFile({ filename, code, compiled }: File) { refSugar: true, inlineTemplate: true }) + if (compiledScript.bindings) { + finalCode += `\n/* Analyzed bindings: ${JSON.stringify( + compiledScript.bindings, + null, + 2 + )} */` + } finalCode += `\n` + rewriteDefault(compiledScript.content, COMP_IDENTIFIER) } catch (e) { From 7ab519cc96e939956c6ca67d05160e8de47b17e0 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 29 Mar 2021 15:34:57 -0400 Subject: [PATCH 0343/3788] workflow(sfc-playground): pass along analyzed bindings when compiling template --- packages/sfc-playground/src/store.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/sfc-playground/src/store.ts b/packages/sfc-playground/src/store.ts index 7e5cdcb1bf7..2f38287c5ed 100644 --- a/packages/sfc-playground/src/store.ts +++ b/packages/sfc-playground/src/store.ts @@ -140,9 +140,10 @@ async function compileFile({ filename, code, compiled }: File) { } // script + let compiledScript if (descriptor.script || descriptor.scriptSetup) { try { - const compiledScript = compileScript(descriptor, { + compiledScript = compileScript(descriptor, { id, refSugar: true, inlineTemplate: true @@ -172,7 +173,10 @@ async function compileFile({ filename, code, compiled }: File) { id, scoped: hasScoped, slotted: descriptor.slotted, - isProd: false + isProd: false, + compilerOptions: { + bindingMetadata: compiledScript && compiledScript.bindings + } }) if (templateResult.errors.length) { store.errors = templateResult.errors From 4d9f9fdf9d3194a32ca3d1719df809b90b56e2d3 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 29 Mar 2021 16:06:14 -0400 Subject: [PATCH 0344/3788] workflow(sfc-playground): make warnings dismissable --- packages/sfc-playground/src/Message.vue | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/sfc-playground/src/Message.vue b/packages/sfc-playground/src/Message.vue index 7cc23995554..828f6b2cd32 100644 --- a/packages/sfc-playground/src/Message.vue +++ b/packages/sfc-playground/src/Message.vue @@ -1,16 +1,23 @@ + `) + ).toThrow() + }) + + test('defineEmit w/ type (type literal w/ call signatures)', () => { + const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}` const { content } = compile(` diff --git a/packages/sfc-playground/src/output/Preview.vue b/packages/sfc-playground/src/output/Preview.vue index 0c3feac2fdc..230025a5f4d 100644 --- a/packages/sfc-playground/src/output/Preview.vue +++ b/packages/sfc-playground/src/output/Preview.vue @@ -14,7 +14,7 @@ import Message from '../Message.vue' import { ref, onMounted, onUnmounted, watchEffect } from 'vue' import srcdoc from './srcdoc.html?raw' import { PreviewProxy } from './PreviewProxy' -import { MAIN_FILE, SANDBOX_VUE_URL } from '../store' +import { MAIN_FILE, SANDBOX_VUE_URL } from '../sfcCompiler' import { compileModulesForPreview } from './moduleCompiler' const iframe = ref() diff --git a/packages/sfc-playground/src/output/moduleCompiler.ts b/packages/sfc-playground/src/output/moduleCompiler.ts index 31f6bc06799..ea22d6b462b 100644 --- a/packages/sfc-playground/src/output/moduleCompiler.ts +++ b/packages/sfc-playground/src/output/moduleCompiler.ts @@ -1,4 +1,5 @@ -import { store, MAIN_FILE, SANDBOX_VUE_URL, File } from '../store' +import { store, File } from '../store' +import { MAIN_FILE, SANDBOX_VUE_URL } from '../sfcCompiler' import { babelParse, MagicString, diff --git a/packages/sfc-playground/src/sfcCompiler.ts b/packages/sfc-playground/src/sfcCompiler.ts new file mode 100644 index 00000000000..e0ab8d2f9cf --- /dev/null +++ b/packages/sfc-playground/src/sfcCompiler.ts @@ -0,0 +1,225 @@ +import { store, File } from './store' +import { + parse, + compileTemplate, + compileStyleAsync, + compileScript, + rewriteDefault, + SFCDescriptor, + BindingMetadata +} from '@vue/compiler-sfc' + +export const MAIN_FILE = 'App.vue' +export const COMP_IDENTIFIER = `__sfc__` + +// @ts-ignore +export const SANDBOX_VUE_URL = import.meta.env.PROD + ? '/vue.runtime.esm-browser.js' // to be copied on build + : '/src/vue-dev-proxy' + +export async function compileFile({ filename, code, compiled }: File) { + if (!code.trim()) { + return + } + + if (filename.endsWith('.js')) { + compiled.js = compiled.ssr = code + return + } + + const id = await hashId(filename) + const { errors, descriptor } = parse(code, { filename, sourceMap: true }) + if (errors.length) { + store.errors = errors + return + } + + if ( + (descriptor.script && descriptor.script.lang) || + (descriptor.scriptSetup && descriptor.scriptSetup.lang) || + descriptor.styles.some(s => s.lang) || + (descriptor.template && descriptor.template.lang) + ) { + store.errors = [ + 'lang="x" pre-processors are not supported in the in-browser playground.' + ] + return + } + + const hasScoped = descriptor.styles.some(s => s.scoped) + let clientCode = '' + let ssrCode = '' + + const appendSharedCode = (code: string) => { + clientCode += code + ssrCode += code + } + + const clientScriptResult = doCompileScript(descriptor, id, false) + if (!clientScriptResult) { + return + } + const [clientScript, bindings] = clientScriptResult + clientCode += clientScript + + // script ssr only needs to be performed if using `.trim() -export const MAIN_FILE = 'App.vue' -export const COMP_IDENTIFIER = `__sfc__` - -// @ts-ignore -export const SANDBOX_VUE_URL = import.meta.env.PROD - ? '/vue.runtime.esm-browser.js' // to be copied on build - : '/src/vue-dev-proxy' - export class File { filename: string code: string compiled = { js: '', - css: '' + css: '', + ssr: '' } constructor(filename: string, code = '') { @@ -106,143 +93,3 @@ export function deleteFile(filename: string) { delete store.files[filename] } } - -async function compileFile({ filename, code, compiled }: File) { - if (!code.trim()) { - return - } - - if (filename.endsWith('.js')) { - compiled.js = code - return - } - - const id = await hashId(filename) - const { errors, descriptor } = parse(code, { filename, sourceMap: true }) - if (errors.length) { - store.errors = errors - return - } - - const hasScoped = descriptor.styles.some(s => s.scoped) - let finalCode = '' - - if ( - (descriptor.script && descriptor.script.lang) || - (descriptor.scriptSetup && descriptor.scriptSetup.lang) || - descriptor.styles.some(s => s.lang) || - (descriptor.template && descriptor.template.lang) - ) { - store.errors = [ - 'lang="x" pre-processors are not supported in the in-browser playground.' - ] - return - } - - // script - let compiledScript - if (descriptor.script || descriptor.scriptSetup) { - try { - compiledScript = compileScript(descriptor, { - id, - refSugar: true, - inlineTemplate: true - }) - if (compiledScript.bindings) { - finalCode += `\n/* Analyzed bindings: ${JSON.stringify( - compiledScript.bindings, - null, - 2 - )} */` - } - finalCode += - `\n` + rewriteDefault(compiledScript.content, COMP_IDENTIFIER) - } catch (e) { - store.errors = [e] - return - } - } else { - finalCode += `\nconst ${COMP_IDENTIFIER} = {}` - } - - // template - if (descriptor.template && !descriptor.scriptSetup) { - const templateResult = compileTemplate({ - source: descriptor.template.content, - filename, - id, - scoped: hasScoped, - slotted: descriptor.slotted, - isProd: false, - compilerOptions: { - bindingMetadata: compiledScript && compiledScript.bindings - } - }) - if (templateResult.errors.length) { - store.errors = templateResult.errors - return - } - - finalCode += - `\n` + - templateResult.code.replace( - /\nexport (function|const) render/, - '$1 render' - ) - finalCode += `\n${COMP_IDENTIFIER}.render = render` - } - if (hasScoped) { - finalCode += `\n${COMP_IDENTIFIER}.__scopeId = ${JSON.stringify( - `data-v-${id}` - )}` - } - - if (finalCode) { - finalCode += `\n${COMP_IDENTIFIER}.__file = ${JSON.stringify(filename)}` - finalCode += `\nexport default ${COMP_IDENTIFIER}` - compiled.js = finalCode.trimStart() - } - - // styles - let css = '' - for (const style of descriptor.styles) { - if (style.module) { - // TODO error - continue - } - - const styleResult = await compileStyleAsync({ - source: style.content, - filename, - id, - scoped: style.scoped, - modules: !!style.module - }) - if (styleResult.errors.length) { - // postcss uses pathToFileURL which isn't polyfilled in the browser - // ignore these errors for now - if (!styleResult.errors[0].message.includes('pathToFileURL')) { - store.errors = styleResult.errors - } - // proceed even if css compile errors - } else { - css += styleResult.code + '\n' - } - } - if (css) { - compiled.css = css.trim() - } else { - compiled.css = '/* No diff --git a/packages/sfc-playground/src/sfcCompiler.ts b/packages/sfc-playground/src/sfcCompiler.ts index 15b24e4c02b..dc586632870 100644 --- a/packages/sfc-playground/src/sfcCompiler.ts +++ b/packages/sfc-playground/src/sfcCompiler.ts @@ -1,22 +1,40 @@ import { store, File } from './store' -import { - parse, - compileTemplate, - compileStyleAsync, - compileScript, - rewriteDefault, - SFCDescriptor, - BindingMetadata -} from '@vue/compiler-sfc' +import { SFCDescriptor, BindingMetadata } from '@vue/compiler-sfc' +import * as defaultCompiler from '@vue/compiler-sfc' export const MAIN_FILE = 'App.vue' export const COMP_IDENTIFIER = `__sfc__` +/** + * The default SFC compiler we are using is built from each commit + * but we may swap it out with a version fetched from CDNs + */ +let SFCCompiler: typeof defaultCompiler = defaultCompiler + // @ts-ignore -export const SANDBOX_VUE_URL = import.meta.env.PROD +const defaultVueUrl = import.meta.env.PROD ? '/vue.runtime.esm-browser.js' // to be copied on build : '/src/vue-dev-proxy' +export let SANDBOX_VUE_URL = defaultVueUrl + +export async function setVersion(version: string) { + const compilerUrl = `https://unpkg.com/@vue/compiler-sfc@${version}/dist/compiler-sfc.esm-browser.js` + const runtimeUrl = `https://cdn.skypack.dev/@vue/runtime-dom@${version}` + const [compiler] = await Promise.all([ + import(/* @vite-ignore */ compilerUrl), + import(/* @vite-ignore */ runtimeUrl) + ]) + SFCCompiler = compiler + SANDBOX_VUE_URL = runtimeUrl + console.info(`Now using Vue version: ${version}`) +} + +export function resetVersion() { + SFCCompiler = defaultCompiler + SANDBOX_VUE_URL = defaultVueUrl +} + export async function compileFile({ filename, code, compiled }: File) { if (!code.trim()) { store.errors = [] @@ -30,7 +48,10 @@ export async function compileFile({ filename, code, compiled }: File) { } const id = await hashId(filename) - const { errors, descriptor } = parse(code, { filename, sourceMap: true }) + const { errors, descriptor } = SFCCompiler.parse(code, { + filename, + sourceMap: true + }) if (errors.length) { store.errors = errors return @@ -121,7 +142,7 @@ export async function compileFile({ filename, code, compiled }: File) { return } - const styleResult = await compileStyleAsync({ + const styleResult = await SFCCompiler.compileStyleAsync({ source: style.content, filename, id, @@ -156,7 +177,7 @@ function doCompileScript( ): [string, BindingMetadata | undefined] | undefined { if (descriptor.script || descriptor.scriptSetup) { try { - const compiledScript = compileScript(descriptor, { + const compiledScript = SFCCompiler.compileScript(descriptor, { id, refSugar: true, inlineTemplate: true, @@ -173,7 +194,9 @@ function doCompileScript( 2 )} */` } - code += `\n` + rewriteDefault(compiledScript.content, COMP_IDENTIFIER) + code += + `\n` + + SFCCompiler.rewriteDefault(compiledScript.content, COMP_IDENTIFIER) return [code, compiledScript.bindings] } catch (e) { store.errors = [e] @@ -190,7 +213,7 @@ function doCompileTemplate( bindingMetadata: BindingMetadata | undefined, ssr: boolean ) { - const templateResult = compileTemplate({ + const templateResult = SFCCompiler.compileTemplate({ source: descriptor.template!.content, filename: descriptor.filename, id, diff --git a/yarn.lock b/yarn.lock index 2eb346a0814..285bf02127d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7223,10 +7223,10 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vite@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.1.4.tgz#66396823701e54cf3bfb9f73dbd386c9b4329c86" - integrity sha512-j/p0RZQvNY/auSPfazsDfo1PHtFp8ktwXPbzI6NqplzHcR3Cn/dfQWiMxL6zp8j9IWdcJP1Zfms7mxruBhStJw== +vite@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.1.5.tgz#4857da441c62f7982c83cbd5f42a00330f20c9c1" + integrity sha512-tYU5iaYeUgQYvK/CNNz3tiJ8vYqPWfCE9IQ7K0iuzYovWw7lzty7KRYGWwV3CQPh0NKxWjOczAqiJsCL0Xb+Og== dependencies: esbuild "^0.9.3" postcss "^8.2.1" From ebf396251cb0181dc968657e9e060a3307429d5e Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 30 Mar 2021 22:10:18 -0400 Subject: [PATCH 0367/3788] chore(sfc-playground): responsive --- packages/sfc-playground/src/Header.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sfc-playground/src/Header.vue b/packages/sfc-playground/src/Header.vue index 9c408e16a7c..48821d49515 100644 --- a/packages/sfc-playground/src/Header.vue +++ b/packages/sfc-playground/src/Header.vue @@ -129,7 +129,7 @@ h1 img { top: -2px; } -@media (max-width: 400px) { +@media (max-width: 480px) { h1 span { display: none; } From 7bc5fd9a04db4f4a335ca945ece9ed43271b1b52 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 31 Mar 2021 12:22:08 -0400 Subject: [PATCH 0368/3788] chore(sfc-playground): teardown preview update watcher --- packages/sfc-playground/src/output/Preview.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/sfc-playground/src/output/Preview.vue b/packages/sfc-playground/src/output/Preview.vue index 230025a5f4d..637b618eb9f 100644 --- a/packages/sfc-playground/src/output/Preview.vue +++ b/packages/sfc-playground/src/output/Preview.vue @@ -12,6 +12,7 @@ From e097bd4dd5497223bd1110c9616b88f148624b13 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 31 Mar 2021 12:36:45 -0400 Subject: [PATCH 0369/3788] chore(sfc-playground): use svg logo --- packages/sfc-playground/index.html | 2 +- packages/sfc-playground/public/icon.png | Bin 3395 -> 0 bytes packages/sfc-playground/public/logo.svg | 4 ++++ packages/sfc-playground/src/Header.vue | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) delete mode 100644 packages/sfc-playground/public/icon.png create mode 100644 packages/sfc-playground/public/logo.svg diff --git a/packages/sfc-playground/index.html b/packages/sfc-playground/index.html index a5e35213436..f58afbc9295 100644 --- a/packages/sfc-playground/index.html +++ b/packages/sfc-playground/index.html @@ -4,7 +4,7 @@ - + Vue SFC Playground diff --git a/packages/sfc-playground/public/icon.png b/packages/sfc-playground/public/icon.png deleted file mode 100644 index 4388bfdca3d4d778d2f25a5861599f8f19a1177c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3395 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 diff --git a/packages/sfc-playground/public/logo.svg b/packages/sfc-playground/public/logo.svg new file mode 100644 index 00000000000..71c1cfb9afa --- /dev/null +++ b/packages/sfc-playground/public/logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/sfc-playground/src/Header.vue b/packages/sfc-playground/src/Header.vue index 48821d49515..a2a9f88439e 100644 --- a/packages/sfc-playground/src/Header.vue +++ b/packages/sfc-playground/src/Header.vue @@ -1,7 +1,7 @@