├── .editorconfig ├── .git-blame-ignore-revs ├── .github ├── CODE_OF_CONDUCT.md ├── COMMIT_CONVENTION.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── release-tag.yml ├── .gitignore ├── .prettierrc ├── BACKERS.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── api-extractor.json ├── api-extractor.tsconfig.json ├── benchmarks ├── big-table │ ├── demo.css │ ├── index.html │ └── style.css ├── dbmon │ ├── ENV.js │ ├── app.js │ ├── index.html │ └── lib │ │ ├── memory-stats.js │ │ ├── monitor.js │ │ └── styles.css ├── reorder-list │ └── index.html ├── ssr │ ├── README.md │ ├── common.js │ ├── renderToStream.js │ └── renderToString.js ├── svg │ └── index.html └── uptime │ └── index.html ├── compiler-sfc ├── index.d.ts ├── index.js ├── index.mjs └── package.json ├── dist ├── vue.common.js ├── vue.runtime.common.js └── vue.runtime.mjs ├── examples ├── classic │ ├── commits │ │ ├── app.js │ │ └── index.html │ ├── elastic-header │ │ ├── index.html │ │ └── style.css │ ├── firebase │ │ ├── app.js │ │ ├── index.html │ │ └── style.css │ ├── grid │ │ ├── grid.js │ │ ├── index.html │ │ └── style.css │ ├── markdown │ │ ├── index.html │ │ └── style.css │ ├── modal │ │ ├── index.html │ │ └── style.css │ ├── move-animations │ │ └── index.html │ ├── select2 │ │ └── index.html │ ├── svg │ │ ├── index.html │ │ ├── style.css │ │ └── svg.js │ ├── todomvc │ │ ├── app.js │ │ ├── index.html │ │ └── readme.md │ └── tree │ │ ├── index.html │ │ └── tree.js └── composition │ ├── commits.html │ ├── grid.html │ ├── markdown.html │ ├── svg.html │ ├── todomvc.html │ └── tree.html ├── package.json ├── packages ├── compiler-sfc │ ├── api-extractor.json │ ├── package.json │ ├── src │ │ ├── babelUtils.ts │ │ ├── compileScript.ts │ │ ├── compileStyle.ts │ │ ├── compileTemplate.ts │ │ ├── cssVars.ts │ │ ├── index.ts │ │ ├── parse.ts │ │ ├── parseComponent.ts │ │ ├── prefixIdentifiers.ts │ │ ├── rewriteDefault.ts │ │ ├── stylePlugins │ │ │ ├── scoped.ts │ │ │ └── trim.ts │ │ ├── stylePreprocessors.ts │ │ ├── templateCompilerModules │ │ │ ├── assetUrl.ts │ │ │ ├── srcset.ts │ │ │ └── utils.ts │ │ ├── types.ts │ │ └── warn.ts │ └── test │ │ ├── __snapshots__ │ │ ├── compileScript.spec.ts.snap │ │ └── cssVars.spec.ts.snap │ │ ├── compileScript.spec.ts │ │ ├── compileStyle.spec.ts │ │ ├── compileTemplate.spec.ts │ │ ├── cssVars.spec.ts │ │ ├── parseComponent.spec.ts │ │ ├── prefixIdentifiers.spec.ts │ │ ├── rewriteDefault.spec.ts │ │ ├── stylePluginScoped.spec.ts │ │ ├── tsconfig.json │ │ └── util.ts ├── server-renderer │ ├── README.md │ ├── client-plugin.d.ts │ ├── index.js │ ├── package.json │ ├── server-plugin.d.ts │ ├── src │ │ ├── bundle-renderer │ │ │ ├── create-bundle-renderer.ts │ │ │ ├── create-bundle-runner.ts │ │ │ └── source-map-support.ts │ │ ├── compiler.ts │ │ ├── create-basic-renderer.ts │ │ ├── create-renderer.ts │ │ ├── directives │ │ │ ├── index.ts │ │ │ ├── model.ts │ │ │ └── show.ts │ │ ├── index-basic.ts │ │ ├── index.ts │ │ ├── modules │ │ │ ├── attrs.ts │ │ │ ├── class.ts │ │ │ ├── dom-props.ts │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── optimizing-compiler │ │ │ ├── codegen.ts │ │ │ ├── index.ts │ │ │ ├── modules.ts │ │ │ ├── optimizer.ts │ │ │ └── runtime-helpers.ts │ │ ├── render-context.ts │ │ ├── render-stream.ts │ │ ├── render.ts │ │ ├── template-renderer │ │ │ ├── create-async-file-mapper.ts │ │ │ ├── index.ts │ │ │ ├── parse-template.ts │ │ │ └── template-stream.ts │ │ ├── util.ts │ │ ├── webpack-plugin │ │ │ ├── client.ts │ │ │ ├── server.ts │ │ │ └── util.ts │ │ └── write.ts │ ├── test │ │ ├── async-loader.js │ │ ├── compile-with-webpack.ts │ │ ├── fixtures │ │ │ ├── app.js │ │ │ ├── async-bar.js │ │ │ ├── async-foo.js │ │ │ ├── cache-opt-out.js │ │ │ ├── cache.js │ │ │ ├── error.js │ │ │ ├── nested-cache.js │ │ │ ├── promise-rejection.js │ │ │ ├── split.js │ │ │ ├── test.css │ │ │ ├── test.png │ │ │ └── test.woff2 │ │ ├── ssr-basic-renderer.spec.ts │ │ ├── ssr-bundle-render.spec.ts │ │ ├── ssr-reactivity.spec.ts │ │ ├── ssr-stream.spec.ts │ │ ├── ssr-string.spec.ts │ │ ├── ssr-template.spec.ts │ │ ├── tsconfig.json │ │ └── utils.ts │ └── types │ │ ├── index.d.ts │ │ ├── plugin.d.ts │ │ ├── test.ts │ │ └── tsconfig.json └── template-compiler │ ├── README.md │ ├── index.js │ ├── package.json │ └── types │ ├── index.d.ts │ ├── test.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── alias.js ├── build.js ├── config.js ├── feature-flags.js ├── gen-release-note.js ├── git-hooks │ ├── commit-msg │ └── pre-commit ├── release.js └── verify-commit-msg.js ├── src ├── compiler │ ├── codeframe.ts │ ├── codegen │ │ ├── events.ts │ │ └── index.ts │ ├── create-compiler.ts │ ├── directives │ │ ├── bind.ts │ │ ├── index.ts │ │ ├── model.ts │ │ └── on.ts │ ├── error-detector.ts │ ├── helpers.ts │ ├── index.ts │ ├── optimizer.ts │ ├── parser │ │ ├── entity-decoder.ts │ │ ├── filter-parser.ts │ │ ├── html-parser.ts │ │ ├── index.ts │ │ └── text-parser.ts │ └── to-function.ts ├── core │ ├── components │ │ ├── index.ts │ │ └── keep-alive.ts │ ├── config.ts │ ├── global-api │ │ ├── assets.ts │ │ ├── extend.ts │ │ ├── index.ts │ │ ├── mixin.ts │ │ └── use.ts │ ├── index.ts │ ├── instance │ │ ├── events.ts │ │ ├── index.ts │ │ ├── init.ts │ │ ├── inject.ts │ │ ├── lifecycle.ts │ │ ├── proxy.ts │ │ ├── render-helpers │ │ │ ├── bind-dynamic-keys.ts │ │ │ ├── bind-object-listeners.ts │ │ │ ├── bind-object-props.ts │ │ │ ├── check-keycodes.ts │ │ │ ├── index.ts │ │ │ ├── render-list.ts │ │ │ ├── render-slot.ts │ │ │ ├── render-static.ts │ │ │ ├── resolve-filter.ts │ │ │ ├── resolve-scoped-slots.ts │ │ │ └── resolve-slots.ts │ │ ├── render.ts │ │ └── state.ts │ ├── observer │ │ ├── array.ts │ │ ├── dep.ts │ │ ├── index.ts │ │ ├── scheduler.ts │ │ ├── traverse.ts │ │ └── watcher.ts │ ├── util │ │ ├── debug.ts │ │ ├── env.ts │ │ ├── error.ts │ │ ├── index.ts │ │ ├── lang.ts │ │ ├── next-tick.ts │ │ ├── options.ts │ │ ├── perf.ts │ │ └── props.ts │ └── vdom │ │ ├── create-component.ts │ │ ├── create-element.ts │ │ ├── create-functional-component.ts │ │ ├── helpers │ │ ├── extract-props.ts │ │ ├── get-first-component-child.ts │ │ ├── index.ts │ │ ├── is-async-placeholder.ts │ │ ├── merge-hook.ts │ │ ├── normalize-children.ts │ │ ├── normalize-scoped-slots.ts │ │ ├── resolve-async-component.ts │ │ └── update-listeners.ts │ │ ├── modules │ │ ├── directives.ts │ │ ├── index.ts │ │ └── template-ref.ts │ │ ├── patch.ts │ │ └── vnode.ts ├── global.d.ts ├── platforms │ └── web │ │ ├── compiler │ │ ├── directives │ │ │ ├── html.ts │ │ │ ├── index.ts │ │ │ ├── model.ts │ │ │ └── text.ts │ │ ├── index.ts │ │ ├── modules │ │ │ ├── class.ts │ │ │ ├── index.ts │ │ │ ├── model.ts │ │ │ └── style.ts │ │ ├── options.ts │ │ └── util.ts │ │ ├── entry-compiler.ts │ │ ├── entry-runtime-esm.ts │ │ ├── entry-runtime-with-compiler-esm.ts │ │ ├── entry-runtime-with-compiler.ts │ │ ├── entry-runtime.ts │ │ ├── runtime-with-compiler.ts │ │ ├── runtime │ │ ├── class-util.ts │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── transition-group.ts │ │ │ └── transition.ts │ │ ├── directives │ │ │ ├── index.ts │ │ │ ├── model.ts │ │ │ └── show.ts │ │ ├── index.ts │ │ ├── modules │ │ │ ├── attrs.ts │ │ │ ├── class.ts │ │ │ ├── dom-props.ts │ │ │ ├── events.ts │ │ │ ├── index.ts │ │ │ ├── style.ts │ │ │ └── transition.ts │ │ ├── node-ops.ts │ │ ├── patch.ts │ │ └── transition-util.ts │ │ └── util │ │ ├── attrs.ts │ │ ├── class.ts │ │ ├── compat.ts │ │ ├── element.ts │ │ ├── index.ts │ │ └── style.ts ├── shared │ ├── constants.ts │ └── util.ts ├── types │ ├── compiler.ts │ ├── component.ts │ ├── global-api.ts │ ├── modules.d.ts │ ├── options.ts │ ├── ssr.ts │ ├── utils.ts │ └── vnode.ts └── v3 │ ├── apiAsyncComponent.ts │ ├── apiInject.ts │ ├── apiLifecycle.ts │ ├── apiSetup.ts │ ├── apiWatch.ts │ ├── currentInstance.ts │ ├── debug.ts │ ├── h.ts │ ├── index.ts │ ├── reactivity │ ├── computed.ts │ ├── effect.ts │ ├── effectScope.ts │ ├── operations.ts │ ├── reactive.ts │ ├── readonly.ts │ └── ref.ts │ └── sfc-helpers │ ├── useCssModule.ts │ └── useCssVars.ts ├── test ├── e2e │ ├── async-edge-cases.html │ ├── async-edge-cases.spec.ts │ ├── basic-ssr.html │ ├── basic-ssr.spec.ts │ ├── commits.mock.ts │ ├── commits.spec.ts │ ├── e2eUtils.ts │ ├── grid.spec.ts │ ├── markdown.spec.ts │ ├── svg.spec.ts │ ├── todomvc.spec.ts │ └── tree.spec.ts ├── helpers │ ├── classlist.ts │ ├── shim-done.ts │ ├── test-object-option.ts │ ├── to-have-warned.ts │ ├── trigger-event.ts │ ├── vdom.ts │ └── wait-for-update.ts ├── test-env.d.ts ├── transition │ ├── helpers.ts │ ├── karma.conf.js │ ├── package.json │ ├── transition-group.spec.ts │ ├── transition-mode.spec.ts │ ├── transition-with-keep-alive.spec.ts │ └── transition.spec.ts ├── tsconfig.json ├── unit │ ├── features │ │ ├── component │ │ │ ├── component-async.spec.ts │ │ │ ├── component-keep-alive.spec.ts │ │ │ ├── component-scoped-slot.spec.ts │ │ │ ├── component-slot.spec.ts │ │ │ └── component.spec.ts │ │ ├── debug.spec.ts │ │ ├── directives │ │ │ ├── bind.spec.ts │ │ │ ├── class.spec.ts │ │ │ ├── cloak.spec.ts │ │ │ ├── for.spec.ts │ │ │ ├── html.spec.ts │ │ │ ├── if.spec.ts │ │ │ ├── model-checkbox.spec.ts │ │ │ ├── model-component.spec.ts │ │ │ ├── model-dynamic.spec.ts │ │ │ ├── model-file.spec.ts │ │ │ ├── model-parse.spec.ts │ │ │ ├── model-radio.spec.ts │ │ │ ├── model-select.spec.ts │ │ │ ├── model-text.spec.ts │ │ │ ├── on.spec.ts │ │ │ ├── once.spec.ts │ │ │ ├── pre.spec.ts │ │ │ ├── show.spec.ts │ │ │ ├── static-style-parser.spec.ts │ │ │ ├── style.spec.ts │ │ │ └── text.spec.ts │ │ ├── error-handling.spec.ts │ │ ├── filter │ │ │ └── filter.spec.ts │ │ ├── global-api │ │ │ ├── assets.spec.ts │ │ │ ├── compile.spec.ts │ │ │ ├── config.spec.ts │ │ │ ├── extend.spec.ts │ │ │ ├── mixin.spec.ts │ │ │ ├── observable.spec.ts │ │ │ ├── set-delete.spec.ts │ │ │ └── use.spec.ts │ │ ├── instance │ │ │ ├── init.spec.ts │ │ │ ├── methods-data.spec.ts │ │ │ ├── methods-events.spec.ts │ │ │ ├── methods-lifecycle.spec.ts │ │ │ ├── properties.spec.ts │ │ │ └── render-proxy.spec.ts │ │ ├── options │ │ │ ├── _scopeId.spec.ts │ │ │ ├── comments.spec.ts │ │ │ ├── components.spec.ts │ │ │ ├── computed.spec.ts │ │ │ ├── data.spec.ts │ │ │ ├── delimiters.spec.ts │ │ │ ├── directives.spec.ts │ │ │ ├── el.spec.ts │ │ │ ├── errorCaptured.spec.ts │ │ │ ├── extends.spec.ts │ │ │ ├── functional.spec.ts │ │ │ ├── inheritAttrs.spec.ts │ │ │ ├── inject.spec.ts │ │ │ ├── lifecycle.spec.ts │ │ │ ├── methods.spec.ts │ │ │ ├── mixins.spec.ts │ │ │ ├── name.spec.ts │ │ │ ├── parent.spec.ts │ │ │ ├── props.spec.ts │ │ │ ├── propsData.spec.ts │ │ │ ├── render.spec.ts │ │ │ ├── renderError.spec.ts │ │ │ ├── template.spec.ts │ │ │ └── watch.spec.ts │ │ ├── template-ref.spec.ts │ │ └── v3 │ │ │ ├── apiAsyncComponent.spec.ts │ │ │ ├── apiInject.spec.ts │ │ │ ├── apiLifecycle.spec.ts │ │ │ ├── apiSetup.spec.ts │ │ │ ├── apiWatch.spec.ts │ │ │ ├── reactivity │ │ │ ├── computed.spec.ts │ │ │ ├── effectScope.spec.ts │ │ │ ├── reactive.spec.ts │ │ │ ├── readonly.spec.ts │ │ │ ├── ref.spec.ts │ │ │ ├── shallowReactive.spec.ts │ │ │ └── shallowReadonly.spec.ts │ │ │ ├── setupTemplateRef.spec.ts │ │ │ └── useCssVars.spec.ts │ └── modules │ │ ├── compiler │ │ ├── codeframe.spec.ts │ │ ├── codegen.spec.ts │ │ ├── compiler-options.spec.ts │ │ ├── optimizer.spec.ts │ │ └── parser.spec.ts │ │ ├── observer │ │ ├── dep.spec.ts │ │ ├── observer.spec.ts │ │ ├── scheduler.spec.ts │ │ └── watcher.spec.ts │ │ ├── server-compiler │ │ └── compiler-options.spec.ts │ │ ├── util │ │ ├── error.spec.ts │ │ ├── next-tick.spec.ts │ │ └── toString.spec.ts │ │ └── vdom │ │ ├── create-component.spec.ts │ │ ├── create-element.spec.ts │ │ ├── modules │ │ ├── attrs.spec.ts │ │ ├── class.spec.ts │ │ ├── directive.spec.ts │ │ ├── dom-props.spec.ts │ │ ├── events.spec.ts │ │ └── style.spec.ts │ │ └── patch │ │ ├── children.spec.ts │ │ ├── edge-cases.spec.ts │ │ ├── element.spec.ts │ │ ├── hooks.spec.ts │ │ └── hydration.spec.ts └── vitest.setup.ts ├── tsconfig.json ├── types ├── built-in-components.d.ts ├── common.d.ts ├── index.d.ts ├── jsx.d.ts ├── options.d.ts ├── plugin.d.ts ├── test │ ├── async-component-test.ts │ ├── augmentation-test.ts │ ├── es-module.ts │ ├── options-test.ts │ ├── plugin-test.ts │ ├── setup-helpers-test.ts │ ├── umd-test.ts │ ├── utils.ts │ ├── v3 │ │ ├── define-async-component-test.tsx │ │ ├── define-component-test.tsx │ │ ├── inject-test.ts │ │ ├── reactivity-test.ts │ │ ├── setup-test.ts │ │ ├── tsx-test.tsx │ │ └── watch-test.ts │ └── vue-test.ts ├── tsconfig.json ├── typings.json ├── umd.d.ts ├── v3-component-options.d.ts ├── v3-component-props.d.ts ├── v3-component-public-instance.d.ts ├── v3-define-async-component.d.ts ├── v3-define-component.d.ts ├── v3-directive.d.ts ├── v3-manual-apis.d.ts ├── v3-setup-context.d.ts ├── v3-setup-helpers.d.ts ├── vnode.d.ts └── vue.d.ts └── vitest.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # chore: move to typescript 2 | af9fc2bcff31d5baa413039818a9b3e011deccaf 3 | # workflow: remove eslint, apply prettier 4 | 72aed6a149b94b5b929fb47370a7a6d4cb7491c5 5 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [yyx990803, posva] 4 | patreon: evanyou 5 | open_collective: vuejs 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: npm/vue 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Vue 2 has reached EOL! 4 | url: https://v2.vuejs.org/eol/ 5 | about: Vue 2 has reached EOL and is no longer actively maintained. Click the link on the right for more details. 6 | - name: Vue 2 NES by HeroDevs 7 | url: https://www.herodevs.com/support/nes-vue?utm_source=vuejs-github&utm_medium=issue-form 8 | about: Learn more about Vue 2 NES if you have security or compliance requirements for continued Vue 2 usage. 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | **What kind of change does this PR introduce?** (check at least one) 10 | 11 | - [ ] Bugfix 12 | - [ ] Feature 13 | - [ ] Code style update 14 | - [ ] Refactor 15 | - [ ] Build-related changes 16 | - [ ] Other, please describe: 17 | 18 | **Does this PR introduce a breaking change?** (check one) 19 | 20 | - [ ] Yes 21 | - [ ] No 22 | 23 | If yes, please describe the impact and migration path for existing applications: 24 | 25 | **The PR fulfills these requirements:** 26 | 27 | - [ ] It's submitted to the `main` branch for v2.x (or to a previous version branch) 28 | - [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number) 29 | - [ ] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup 30 | - [ ] New/updated tests are included 31 | 32 | If adding a **new feature**, the PR's description includes: 33 | - [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it) 34 | 35 | **Other information:** 36 | -------------------------------------------------------------------------------- /.github/workflows/release-tag.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Create Release for Tag 16 | id: release_tag 17 | uses: yyx990803/release-tag@master 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | tag_name: ${{ github.ref }} 22 | body: | 23 | Please refer to [CHANGELOG.md](https://github.com/vuejs/vue/blob/main/CHANGELOG.md) for details. 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.log 4 | explorations 5 | TODOs.md 6 | RELEASE_NOTE*.md 7 | packages/server-renderer/basic.js 8 | packages/server-renderer/build.dev.js 9 | packages/server-renderer/build.prod.js 10 | packages/server-renderer/server-plugin.js 11 | packages/server-renderer/client-plugin.js 12 | packages/template-compiler/build.js 13 | packages/template-compiler/browser.js 14 | .vscode 15 | dist 16 | temp 17 | types/v3-generated.d.ts 18 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | printWidth: 80 4 | trailingComma: 'none' 5 | arrowParens: 'avoid' 6 | -------------------------------------------------------------------------------- /BACKERS.md: -------------------------------------------------------------------------------- 1 |

Sponsors & Backers

2 | 3 | Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of the awesome sponsors and backers listed in this file. If you'd like to join them, please consider [ sponsor Vue's development](https://vuejs.org/sponsor/). 4 | 5 |

6 | 7 | sponsors 8 | 9 |

10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-present, Yuxi (Evan) You 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | 4 | "projectFolder": ".", 5 | 6 | "compiler": { 7 | "tsconfigFilePath": "api-extractor.tsconfig.json" 8 | }, 9 | 10 | "mainEntryPointFilePath": "./temp/src/v3/index.d.ts", 11 | 12 | "dtsRollup": { 13 | "enabled": true, 14 | "untrimmedFilePath": "", 15 | "publicTrimmedFilePath": "./types/v3-generated.d.ts" 16 | }, 17 | 18 | "apiReport": { 19 | "enabled": false 20 | }, 21 | 22 | "docModel": { 23 | "enabled": false 24 | }, 25 | 26 | "tsdocMetadata": { 27 | "enabled": false 28 | }, 29 | 30 | "messages": { 31 | "compilerMessageReporting": { 32 | "default": { 33 | "logLevel": "warning" 34 | } 35 | }, 36 | 37 | "extractorMessageReporting": { 38 | "default": { 39 | "logLevel": "warning", 40 | "addToApiReportFile": true 41 | }, 42 | 43 | "ae-missing-release-tag": { 44 | "logLevel": "none" 45 | }, 46 | "ae-internal-missing-underscore": { 47 | "logLevel": "none" 48 | }, 49 | "ae-forgotten-export": { 50 | "logLevel": "none" 51 | } 52 | }, 53 | 54 | "tsdocMessageReporting": { 55 | "default": { 56 | "logLevel": "warning" 57 | }, 58 | 59 | "tsdoc-undefined-tag": { 60 | "logLevel": "none" 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /api-extractor.tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./temp", 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /benchmarks/big-table/demo.css: -------------------------------------------------------------------------------- 1 | form { 2 | margin-bottom: 15px; 3 | } 4 | 5 | td.hidden { 6 | color: #ccc; 7 | } 8 | 9 | table.filtered td.item { 10 | background-color: #FFFFBF; 11 | } 12 | 13 | table.filtered td.item.hidden { 14 | background-color: transparent; 15 | } 16 | -------------------------------------------------------------------------------- /benchmarks/dbmon/app.js: -------------------------------------------------------------------------------- 1 | var app = new Vue({ 2 | el: '#app', 3 | data: { 4 | databases: [] 5 | } 6 | }) 7 | 8 | function loadSamples() { 9 | app.databases = Object.freeze(ENV.generateData().toArray()); 10 | Monitoring.renderRate.ping(); 11 | setTimeout(loadSamples, ENV.timeout); 12 | } 13 | 14 | loadSamples() 15 | -------------------------------------------------------------------------------- /benchmarks/dbmon/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | dbmon (Vue) 7 | 8 | 9 |

10 | Reference: js-repaint-perfs 11 |

12 |
13 | 14 | 15 | 16 | 17 | 20 | 27 | 28 | 29 |
{{db.dbname}} 18 | {{db.lastSample.nbQueries}} 19 | 21 | {{q.formatElapsed}} 22 |
23 |
{{q.query}}
24 |
25 |
26 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /benchmarks/dbmon/lib/styles.css: -------------------------------------------------------------------------------- 1 | body {color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;margin:0;} 2 | label {display:inline-block;font-weight:700;margin-bottom:5px;} 3 | input[type=range] {display:block;width:100%;} 4 | table {border-collapse:collapse;border-spacing:0;} 5 | :before,:after {box-sizing: border-box;} 6 | 7 | .table > thead > tr > th,.table > tbody > tr > th,.table > tfoot > tr > th,.table > thead > tr > td,.table > tbody > tr > td,.table > tfoot > tr > td {border-top:1px solid #ddd;line-height:1.42857143;padding:8px;vertical-align:top;} 8 | .table {width:100%;} 9 | .table-striped > tbody > tr:nth-child(odd) > td,.table-striped > tbody > tr:nth-child(odd) > th {background:#f9f9f9;} 10 | 11 | .label {border-radius:.25em;color:#fff;display:inline;font-size:75%;font-weight:700;line-height:1;padding:.2em .6em .3em;text-align:center;vertical-align:baseline;white-space:nowrap;} 12 | .label-success {background-color:#5cb85c;} 13 | .label-warning {background-color:#f0ad4e;} 14 | 15 | .popover {background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;box-shadow:0 5px 10px rgba(0,0,0,.2);display:none;left:0;max-width:276px;padding:1px;position:absolute;text-align:left;top:0;white-space:normal;z-index:1010;} 16 | .popover>.arrow:after {border-width:10px;content:"";} 17 | .popover.left {margin-left:-10px;} 18 | .popover.left > .arrow {border-right-width:0;border-left-color:rgba(0,0,0,.25);margin-top:-11px;right:-11px;top:50%;} 19 | .popover.left > .arrow:after {border-left-color:#fff;border-right-width:0;bottom:-10px;content:" ";right:1px;} 20 | .popover > .arrow {border-width:11px;} 21 | .popover > .arrow,.popover>.arrow:after {border-color:transparent;border-style:solid;display:block;height:0;position:absolute;width:0;} 22 | 23 | .popover-content {padding:9px 14px;} 24 | 25 | .Query {position:relative;} 26 | .Query:hover .popover {display:block;left:-100%;width:100%;} 27 | -------------------------------------------------------------------------------- /benchmarks/ssr/README.md: -------------------------------------------------------------------------------- 1 | # Vue.js SSR benchmark 2 | 3 | This benchmark renders a table of 1000 rows with 10 columns (10k components), with around 30k normal elements on the page. Note this is not something likely to be seen in a typical app. This benchmark is mostly for stress/regression testing and comparing between `renderToString` and `renderToStream`. 4 | 5 | To view the results follow the run section. Note that the overall completion time for the results is variable, this is due to other system related variants at run time (available memory, processing power, etc). In ideal circumstances, both should finish within similar results. 6 | 7 | `renderToStream` pipes the content through a stream which provides considerable performance benefits (faster time-to-first-byte and non-event-loop-blocking) over `renderToString`. This can be observed through the benchmark. 8 | 9 | ### run 10 | 11 | ``` bash 12 | npm run bench:ssr 13 | ``` 14 | -------------------------------------------------------------------------------- /benchmarks/ssr/common.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const self = (global || root) // eslint-disable-line 4 | 5 | self.performance = { 6 | now: function () { 7 | var hrtime = process.hrtime() 8 | return ((hrtime[0] * 1000000 + hrtime[1] / 1000) / 1000) 9 | } 10 | } 11 | 12 | function generateGrid (rowCount, columnCount) { 13 | var grid = [] 14 | 15 | for (var r = 0; r < rowCount; r++) { 16 | var row = { id: r, items: [] } 17 | for (var c = 0; c < columnCount; c++) { 18 | row.items.push({ id: (r + '-' + c) }) 19 | } 20 | grid.push(row) 21 | } 22 | 23 | return grid 24 | } 25 | 26 | const gridData = generateGrid(1000, 10) 27 | 28 | module.exports = { 29 | template: '

{{ Math.random() }}

', 30 | components: { 31 | myTable: { 32 | data: function () { 33 | return { 34 | grid: gridData 35 | } 36 | }, 37 | // template: '
123{{ item.id }}
', 38 | template: '
', 39 | components: { 40 | row: { 41 | props: ['row'], 42 | template: '{{ Math.random() }}', 43 | components: { 44 | column: { 45 | template: '' + 46 | // 25 plain elements for each cell 47 | '' + 52 | '' 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /benchmarks/ssr/renderToStream.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | 'use strict' 4 | 5 | process.env.NODE_ENV = 'production' 6 | 7 | const Vue = require('../../dist/vue.runtime.common.js') 8 | const createRenderer = require('../../packages/server-renderer').createRenderer 9 | const renderToStream = createRenderer().renderToStream 10 | const gridComponent = require('./common.js') 11 | 12 | console.log('--- renderToStream --- ') 13 | const self = (global || root) 14 | const s = self.performance.now() 15 | 16 | const stream = renderToStream(new Vue(gridComponent)) 17 | let str = '' 18 | let first 19 | let complete 20 | stream.once('data', () => { 21 | first = self.performance.now() - s 22 | }) 23 | stream.on('data', chunk => { 24 | str += chunk 25 | }) 26 | stream.on('end', () => { 27 | complete = self.performance.now() - s 28 | console.log(`first chunk: ${first.toFixed(2)}ms`) 29 | console.log(`complete: ${complete.toFixed(2)}ms`) 30 | console.log() 31 | }) 32 | -------------------------------------------------------------------------------- /benchmarks/ssr/renderToString.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | const Vue = require('../../dist/vue.runtime.common.js') 6 | const createRenderer = require('../../packages/server-renderer').createRenderer 7 | const renderToString = createRenderer().renderToString 8 | const gridComponent = require('./common.js') 9 | 10 | console.log('--- renderToString --- ') 11 | const self = (global || root) 12 | self.s = self.performance.now() 13 | 14 | renderToString(new Vue(gridComponent), (err, res) => { 15 | if (err) throw err 16 | // console.log(res) 17 | console.log('Complete time: ' + (self.performance.now() - self.s).toFixed(2) + 'ms') 18 | console.log() 19 | }) 20 | -------------------------------------------------------------------------------- /compiler-sfc/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from '@vue/compiler-sfc' 2 | -------------------------------------------------------------------------------- /compiler-sfc/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@vue/compiler-sfc') 2 | -------------------------------------------------------------------------------- /compiler-sfc/index.mjs: -------------------------------------------------------------------------------- 1 | export * from '@vue/compiler-sfc' 2 | -------------------------------------------------------------------------------- /compiler-sfc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.js", 3 | "module": "index.mjs", 4 | "types": "index.d.ts" 5 | } 6 | -------------------------------------------------------------------------------- /dist/vue.common.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./vue.common.prod.js') 3 | } else { 4 | module.exports = require('./vue.common.dev.js') 5 | } 6 | -------------------------------------------------------------------------------- /dist/vue.runtime.common.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./vue.runtime.common.prod.js') 3 | } else { 4 | module.exports = require('./vue.runtime.common.dev.js') 5 | } 6 | -------------------------------------------------------------------------------- /dist/vue.runtime.mjs: -------------------------------------------------------------------------------- 1 | import Vue from './vue.runtime.common.js' 2 | export default Vue 3 | 4 | // this should be kept in sync with src/v3/index.ts 5 | export const { 6 | version, 7 | 8 | // refs 9 | ref, 10 | shallowRef, 11 | isRef, 12 | toRef, 13 | toRefs, 14 | unref, 15 | proxyRefs, 16 | customRef, 17 | triggerRef, 18 | computed, 19 | 20 | // reactive 21 | reactive, 22 | isReactive, 23 | isReadonly, 24 | isShallow, 25 | isProxy, 26 | shallowReactive, 27 | markRaw, 28 | toRaw, 29 | readonly, 30 | shallowReadonly, 31 | 32 | // watch 33 | watch, 34 | watchEffect, 35 | watchPostEffect, 36 | watchSyncEffect, 37 | 38 | // effectScope 39 | effectScope, 40 | onScopeDispose, 41 | getCurrentScope, 42 | 43 | // provide / inject 44 | provide, 45 | inject, 46 | 47 | // lifecycle 48 | onBeforeMount, 49 | onMounted, 50 | onBeforeUpdate, 51 | onUpdated, 52 | onBeforeUnmount, 53 | onUnmounted, 54 | onErrorCaptured, 55 | onActivated, 56 | onDeactivated, 57 | onServerPrefetch, 58 | onRenderTracked, 59 | onRenderTriggered, 60 | 61 | // v2 only 62 | set, 63 | del, 64 | 65 | // v3 compat 66 | h, 67 | getCurrentInstance, 68 | useSlots, 69 | useAttrs, 70 | mergeDefaults, 71 | nextTick, 72 | useCssModule, 73 | useCssVars, 74 | defineComponent, 75 | defineAsyncComponent 76 | } = Vue 77 | -------------------------------------------------------------------------------- /examples/classic/commits/app.js: -------------------------------------------------------------------------------- 1 | var apiURL = 'https://api.github.com/repos/vuejs/vue/commits?per_page=3&sha=' 2 | 3 | /** 4 | * Actual demo 5 | */ 6 | 7 | new Vue({ 8 | el: '#demo', 9 | 10 | data: { 11 | branches: ['main', 'dev'], 12 | currentBranch: 'main', 13 | commits: null 14 | }, 15 | 16 | created: function () { 17 | this.fetchData() 18 | }, 19 | 20 | watch: { 21 | currentBranch: 'fetchData' 22 | }, 23 | 24 | filters: { 25 | truncate: function (v) { 26 | var newline = v.indexOf('\n') 27 | return newline > 0 ? v.slice(0, newline) : v 28 | }, 29 | formatDate: function (v) { 30 | return v.replace(/T|Z/g, ' ') 31 | } 32 | }, 33 | 34 | methods: { 35 | fetchData: function () { 36 | var self = this 37 | var xhr = new XMLHttpRequest() 38 | xhr.open('GET', apiURL + self.currentBranch) 39 | xhr.onload = function () { 40 | self.commits = JSON.parse(xhr.responseText) 41 | console.log(self.commits[0].html_url) 42 | } 43 | xhr.send() 44 | } 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /examples/classic/commits/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vue.js github commits example 5 | 21 | 22 | 23 | 24 | 25 |
26 |

Latest Vue.js Commits

27 | 35 |

vuejs/vue@{{ currentBranch }}

36 | 44 |
45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/classic/elastic-header/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-weight: 300; 3 | font-size: 1.8em; 4 | margin-top: 0; 5 | } 6 | a { 7 | color: #fff; 8 | } 9 | .draggable-header-view { 10 | background-color: #fff; 11 | box-shadow: 0 4px 16px rgba(0,0,0,.15); 12 | width: 320px; 13 | height: 560px; 14 | overflow: hidden; 15 | margin: 30px auto; 16 | position: relative; 17 | font-family: 'Roboto', Helvetica, Arial, sans-serif; 18 | color: #fff; 19 | font-size: 14px; 20 | font-weight: 300; 21 | -webkit-user-select: none; 22 | -moz-user-select: none; 23 | -ms-user-select: none; 24 | user-select: none; 25 | } 26 | .draggable-header-view .bg { 27 | position: absolute; 28 | top: 0; 29 | left: 0; 30 | z-index: 0; 31 | } 32 | .draggable-header-view .header, .draggable-header-view .content { 33 | position: relative; 34 | z-index: 1; 35 | padding: 30px; 36 | box-sizing: border-box; 37 | } 38 | .draggable-header-view .header { 39 | height: 160px; 40 | } 41 | .draggable-header-view .content { 42 | color: #333; 43 | line-height: 1.5em; 44 | } 45 | -------------------------------------------------------------------------------- /examples/classic/firebase/app.js: -------------------------------------------------------------------------------- 1 | var emailRE = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 2 | 3 | // Setup Firebase 4 | var config = { 5 | apiKey: "AIzaSyAi_yuJciPXLFr_PYPeU3eTvtXf8jbJ8zw", 6 | authDomain: "vue-demo-537e6.firebaseapp.com", 7 | databaseURL: "https://vue-demo-537e6.firebaseio.com" 8 | } 9 | firebase.initializeApp(config) 10 | 11 | var usersRef = firebase.database().ref('users') 12 | 13 | // create Vue app 14 | var app = new Vue({ 15 | // element to mount to 16 | el: '#app', 17 | // initial data 18 | data: { 19 | newUser: { 20 | name: '', 21 | email: '' 22 | } 23 | }, 24 | // firebase binding 25 | // https://github.com/vuejs/vuefire 26 | firebase: { 27 | users: usersRef 28 | }, 29 | // computed property for form validation state 30 | computed: { 31 | validation: function () { 32 | return { 33 | name: !!this.newUser.name.trim(), 34 | email: emailRE.test(this.newUser.email) 35 | } 36 | }, 37 | isValid: function () { 38 | var validation = this.validation 39 | return Object.keys(validation).every(function (key) { 40 | return validation[key] 41 | }) 42 | } 43 | }, 44 | // methods 45 | methods: { 46 | addUser: function () { 47 | if (this.isValid) { 48 | usersRef.push(this.newUser) 49 | this.newUser.name = '' 50 | this.newUser.email = '' 51 | } 52 | }, 53 | removeUser: function (user) { 54 | usersRef.child(user['.key']).remove() 55 | } 56 | } 57 | }) 58 | -------------------------------------------------------------------------------- /examples/classic/firebase/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vue.js firebase + validation example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 23 |
24 | 25 | 26 | 27 |
28 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/classic/firebase/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica, Arial, sans-serif; 3 | } 4 | 5 | ul { 6 | padding: 0; 7 | } 8 | 9 | .user { 10 | height: 30px; 11 | line-height: 30px; 12 | padding: 10px; 13 | border-top: 1px solid #eee; 14 | overflow: hidden; 15 | transition: all .25s ease; 16 | } 17 | 18 | .user:last-child { 19 | border-bottom: 1px solid #eee; 20 | } 21 | 22 | .v-enter, .v-leave-to { 23 | height: 0; 24 | padding-top: 0; 25 | padding-bottom: 0; 26 | border-top-width: 0; 27 | border-bottom-width: 0; 28 | } 29 | 30 | .errors { 31 | color: #f00; 32 | } 33 | -------------------------------------------------------------------------------- /examples/classic/grid/grid.js: -------------------------------------------------------------------------------- 1 | // register the grid component 2 | Vue.component('demo-grid', { 3 | template: '#grid-template', 4 | replace: true, 5 | props: { 6 | data: Array, 7 | columns: Array, 8 | filterKey: String 9 | }, 10 | data: function () { 11 | var sortOrders = {} 12 | this.columns.forEach(function (key) { 13 | sortOrders[key] = 1 14 | }) 15 | return { 16 | sortKey: '', 17 | sortOrders: sortOrders 18 | } 19 | }, 20 | computed: { 21 | filteredData: function () { 22 | var sortKey = this.sortKey 23 | var filterKey = this.filterKey && this.filterKey.toLowerCase() 24 | var order = this.sortOrders[sortKey] || 1 25 | var data = this.data 26 | if (filterKey) { 27 | data = data.filter(function (row) { 28 | return Object.keys(row).some(function (key) { 29 | return String(row[key]).toLowerCase().indexOf(filterKey) > -1 30 | }) 31 | }) 32 | } 33 | if (sortKey) { 34 | data = data.slice().sort(function (a, b) { 35 | a = a[sortKey] 36 | b = b[sortKey] 37 | return (a === b ? 0 : a > b ? 1 : -1) * order 38 | }) 39 | } 40 | return data 41 | } 42 | }, 43 | filters: { 44 | capitalize: function (str) { 45 | return str.charAt(0).toUpperCase() + str.slice(1) 46 | } 47 | }, 48 | methods: { 49 | sortBy: function (key) { 50 | this.sortKey = key 51 | this.sortOrders[key] = this.sortOrders[key] * -1 52 | } 53 | } 54 | }) 55 | 56 | // bootstrap the demo 57 | var demo = new Vue({ 58 | el: '#demo', 59 | data: { 60 | searchQuery: '', 61 | gridColumns: ['name', 'power'], 62 | gridData: [ 63 | { name: 'Chuck Norris', power: Infinity }, 64 | { name: 'Bruce Lee', power: 9000 }, 65 | { name: 'Jackie Chan', power: 7000 }, 66 | { name: 'Jet Li', power: 8000 } 67 | ] 68 | } 69 | }) 70 | -------------------------------------------------------------------------------- /examples/classic/grid/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js grid component example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 36 | 37 | 38 |
39 | 42 | 46 | 47 |
48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /examples/classic/grid/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica Neue, Arial, sans-serif; 3 | font-size: 14px; 4 | color: #444; 5 | } 6 | 7 | table { 8 | border: 2px solid #42b983; 9 | border-radius: 3px; 10 | background-color: #fff; 11 | } 12 | 13 | th { 14 | background-color: #42b983; 15 | color: rgba(255,255,255,0.66); 16 | cursor: pointer; 17 | -webkit-user-select: none; 18 | -moz-user-select: none; 19 | -ms-user-select: none; 20 | user-select: none; 21 | } 22 | 23 | td { 24 | background-color: #f9f9f9; 25 | } 26 | 27 | th, td { 28 | min-width: 120px; 29 | padding: 10px 20px; 30 | } 31 | 32 | th.active { 33 | color: #fff; 34 | } 35 | 36 | th.active .arrow { 37 | opacity: 1; 38 | } 39 | 40 | .arrow { 41 | display: inline-block; 42 | vertical-align: middle; 43 | width: 0; 44 | height: 0; 45 | margin-left: 5px; 46 | opacity: 0.66; 47 | } 48 | 49 | .arrow.asc { 50 | border-left: 4px solid transparent; 51 | border-right: 4px solid transparent; 52 | border-bottom: 4px solid #fff; 53 | } 54 | 55 | .arrow.dsc { 56 | border-left: 4px solid transparent; 57 | border-right: 4px solid transparent; 58 | border-top: 4px solid #fff; 59 | } 60 | -------------------------------------------------------------------------------- /examples/classic/markdown/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js markdown editor example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 |
18 | 19 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/classic/markdown/style.css: -------------------------------------------------------------------------------- 1 | html, body, #editor { 2 | margin: 0; 3 | height: 100%; 4 | font-family: 'Helvetica Neue', Arial, sans-serif; 5 | color: #333; 6 | } 7 | 8 | textarea, #editor div { 9 | display: inline-block; 10 | width: 49%; 11 | height: 100%; 12 | vertical-align: top; 13 | -webkit-box-sizing: border-box; 14 | -moz-box-sizing: border-box; 15 | box-sizing: border-box; 16 | padding: 0 20px; 17 | } 18 | 19 | textarea { 20 | border: none; 21 | border-right: 1px solid #ccc; 22 | resize: none; 23 | outline: none; 24 | background-color: #f6f6f6; 25 | font-size: 14px; 26 | font-family: 'Monaco', courier, monospace; 27 | padding: 20px; 28 | } 29 | 30 | code { 31 | color: #f66; 32 | } -------------------------------------------------------------------------------- /examples/classic/modal/style.css: -------------------------------------------------------------------------------- 1 | .modal-mask { 2 | position: fixed; 3 | z-index: 9998; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | background-color: rgba(0, 0, 0, .5); 9 | display: table; 10 | transition: opacity .3s ease; 11 | } 12 | 13 | .modal-wrapper { 14 | display: table-cell; 15 | vertical-align: middle; 16 | } 17 | 18 | .modal-container { 19 | width: 300px; 20 | margin: 0px auto; 21 | padding: 20px 30px; 22 | background-color: #fff; 23 | border-radius: 2px; 24 | box-shadow: 0 2px 8px rgba(0, 0, 0, .33); 25 | transition: all .3s ease; 26 | font-family: Helvetica, Arial, sans-serif; 27 | } 28 | 29 | .modal-header h3 { 30 | margin-top: 0; 31 | color: #42b983; 32 | } 33 | 34 | .modal-body { 35 | margin: 20px 0; 36 | } 37 | 38 | .modal-default-button { 39 | float: right; 40 | } 41 | 42 | /* 43 | * The following styles are auto-applied to elements with 44 | * transition="modal" when their visibility is toggled 45 | * by Vue.js. 46 | * 47 | * You can easily play with the modal transition by editing 48 | * these styles. 49 | */ 50 | 51 | .modal-enter { 52 | opacity: 0; 53 | } 54 | 55 | .modal-leave-to { 56 | opacity: 0; 57 | } 58 | 59 | .modal-enter .modal-container, 60 | .modal-leave-to .modal-container { 61 | -webkit-transform: scale(1.1); 62 | transform: scale(1.1); 63 | } 64 | -------------------------------------------------------------------------------- /examples/classic/svg/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js SVG graph example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 25 | 26 | 27 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | {{stat.value}} 42 | 43 |
44 |
45 | 46 | 47 |
48 |
{{ stats }}
49 |
50 | 51 |

* input[type="range"] requires IE10 or above.

52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /examples/classic/svg/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica Neue, Arial, sans-serif; 3 | } 4 | 5 | polygon { 6 | fill: #42b983; 7 | opacity: .75; 8 | } 9 | 10 | circle { 11 | fill: transparent; 12 | stroke: #999; 13 | } 14 | 15 | text { 16 | font-family: Helvetica Neue, Arial, sans-serif; 17 | font-size: 10px; 18 | fill: #666; 19 | } 20 | 21 | label { 22 | display: inline-block; 23 | margin-left: 10px; 24 | width: 20px; 25 | } 26 | 27 | #raw { 28 | position: absolute; 29 | top: 0; 30 | left: 300px; 31 | } -------------------------------------------------------------------------------- /examples/classic/todomvc/readme.md: -------------------------------------------------------------------------------- 1 | # Vue.js TodoMVC Example 2 | 3 | > Vue.js is a library for building interactive web interfaces. 4 | It provides data-driven, nestable view components with a simple and flexible API. 5 | 6 | > _[Vue.js - v2.vuejs.org](https://v2.vuejs.org)_ 7 | 8 | ## Learning Vue.js 9 | The [Vue.js website](https://v2.vuejs.org/) is a great resource to get started. 10 | 11 | Here are some links you may find helpful: 12 | 13 | * [Official Guide](https://v2.vuejs.org/guide/) 14 | * [API Reference](https://v2.vuejs.org/api/) 15 | * [Examples](https://v2.vuejs.org/examples/) 16 | 17 | Get help from other Vue.js users: 18 | 19 | * [Vue.js official forum](https://forum.vuejs.org) 20 | * [Vue.js on Twitter](https://twitter.com/vuejs) 21 | * [Vue.js on Gitter](https://gitter.im/vuejs/vue) 22 | 23 | _If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ 24 | 25 | ## Credit 26 | 27 | This TodoMVC application was created by [Evan You](https://evanyou.me). 28 | -------------------------------------------------------------------------------- /examples/classic/tree/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js tree view example 6 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 48 | 49 |

(You can double click on an item to turn it into a folder.)

50 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/classic/tree/tree.js: -------------------------------------------------------------------------------- 1 | // demo data 2 | var data = { 3 | name: 'My Tree', 4 | children: [ 5 | { name: 'hello' }, 6 | { name: 'wat' }, 7 | { 8 | name: 'child folder', 9 | children: [ 10 | { 11 | name: 'child folder', 12 | children: [ 13 | { name: 'hello' }, 14 | { name: 'wat' } 15 | ] 16 | }, 17 | { name: 'hello' }, 18 | { name: 'wat' }, 19 | { 20 | name: 'child folder', 21 | children: [ 22 | { name: 'hello' }, 23 | { name: 'wat' } 24 | ] 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | 31 | // define the item component 32 | Vue.component('item', { 33 | template: '#item-template', 34 | props: { 35 | model: Object 36 | }, 37 | data: function () { 38 | return { 39 | open: false 40 | } 41 | }, 42 | computed: { 43 | isFolder: function () { 44 | return this.model.children && 45 | this.model.children.length 46 | } 47 | }, 48 | methods: { 49 | toggle: function () { 50 | if (this.isFolder) { 51 | this.open = !this.open 52 | } 53 | }, 54 | changeType: function () { 55 | if (!this.isFolder) { 56 | Vue.set(this.model, 'children', []) 57 | this.addChild() 58 | this.open = true 59 | } 60 | }, 61 | addChild: function () { 62 | this.model.children.push({ 63 | name: 'new stuff' 64 | }) 65 | } 66 | } 67 | }) 68 | 69 | // boot up the demo 70 | var demo = new Vue({ 71 | el: '#demo', 72 | data: { 73 | treeData: data 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /examples/composition/markdown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 |
8 |
9 | 10 | 29 | 30 | 67 | -------------------------------------------------------------------------------- /packages/compiler-sfc/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | 4 | "projectFolder": ".", 5 | 6 | "mainEntryPointFilePath": "../../temp/packages/compiler-sfc/src/index.d.ts", 7 | 8 | "compiler": { 9 | "tsconfigFilePath": "../../api-extractor.tsconfig.json" 10 | }, 11 | 12 | "dtsRollup": { 13 | "enabled": true, 14 | "untrimmedFilePath": "", 15 | "publicTrimmedFilePath": "./dist/compiler-sfc.d.ts" 16 | }, 17 | 18 | "apiReport": { 19 | "enabled": false 20 | }, 21 | 22 | "docModel": { 23 | "enabled": false 24 | }, 25 | 26 | "tsdocMetadata": { 27 | "enabled": false 28 | }, 29 | 30 | "messages": { 31 | "compilerMessageReporting": { 32 | "default": { 33 | "logLevel": "warning" 34 | } 35 | }, 36 | 37 | "extractorMessageReporting": { 38 | "default": { 39 | "logLevel": "warning", 40 | "addToApiReportFile": true 41 | }, 42 | 43 | "ae-missing-release-tag": { 44 | "logLevel": "none" 45 | }, 46 | "ae-internal-missing-underscore": { 47 | "logLevel": "none" 48 | }, 49 | "ae-forgotten-export": { 50 | "logLevel": "none" 51 | } 52 | }, 53 | 54 | "tsdocMessageReporting": { 55 | "default": { 56 | "logLevel": "warning" 57 | }, 58 | 59 | "tsdoc-undefined-tag": { 60 | "logLevel": "none" 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/compiler-sfc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue/compiler-sfc", 3 | "version": "2.7.16", 4 | "description": "compiler-sfc for Vue 2", 5 | "main": "dist/compiler-sfc.js", 6 | "types": "dist/compiler-sfc.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "dependencies": { 11 | "@babel/parser": "^7.23.5", 12 | "postcss": "^8.4.14", 13 | "source-map": "^0.6.1" 14 | }, 15 | "devDependencies": { 16 | "@babel/types": "^7.23.5", 17 | "@types/estree": "^0.0.48", 18 | "@types/hash-sum": "^1.0.0", 19 | "@types/lru-cache": "^5.1.1", 20 | "@vue/consolidate": "^0.17.3", 21 | "de-indent": "^1.0.2", 22 | "estree-walker": "^2.0.2", 23 | "hash-sum": "^2.0.0", 24 | "less": "^4.1.3", 25 | "lru-cache": "^5.1.1", 26 | "magic-string": "^0.25.9", 27 | "merge-source-map": "^1.1.0", 28 | "postcss-modules": "^4.3.1", 29 | "postcss-selector-parser": "^6.0.10", 30 | "pug": "^3.0.2", 31 | "sass": "^1.52.3", 32 | "stylus": "^0.58.1" 33 | }, 34 | "optionalDependencies": { 35 | "prettier": "^1.18.2 || ^2.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/compiler-sfc/src/index.ts: -------------------------------------------------------------------------------- 1 | // API 2 | export { parse } from './parse' 3 | export { compileTemplate } from './compileTemplate' 4 | export { compileStyle, compileStyleAsync } from './compileStyle' 5 | export { compileScript } from './compileScript' 6 | export { generateCodeFrame } from 'compiler/codeframe' 7 | export { rewriteDefault } from './rewriteDefault' 8 | 9 | // For backwards compat only. Some existing tools like 10 | // fork-ts-checker-webpack-plugin relies on its presence for differentiating 11 | // between Vue 2 and Vue 3. 12 | // ref #12719 13 | // ref https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/issues/765 14 | export { parseComponent } from './parseComponent' 15 | 16 | // types 17 | export { SFCParseOptions } from './parse' 18 | export { CompilerOptions, WarningMessage } from 'types/compiler' 19 | export { TemplateCompiler } from './types' 20 | export { 21 | SFCBlock, 22 | SFCCustomBlock, 23 | SFCScriptBlock, 24 | SFCDescriptor 25 | } from './parseComponent' 26 | export { 27 | SFCTemplateCompileOptions, 28 | SFCTemplateCompileResults 29 | } from './compileTemplate' 30 | export { SFCStyleCompileOptions, SFCStyleCompileResults } from './compileStyle' 31 | export { SFCScriptCompileOptions } from './compileScript' 32 | -------------------------------------------------------------------------------- /packages/compiler-sfc/src/stylePlugins/trim.ts: -------------------------------------------------------------------------------- 1 | import { PluginCreator } from 'postcss' 2 | 3 | const trimPlugin: PluginCreator<{}> = () => { 4 | return { 5 | postcssPlugin: 'vue-sfc-trim', 6 | Once(root) { 7 | root.walk(({ type, raws }) => { 8 | if (type === 'rule' || type === 'atrule') { 9 | if (raws.before) raws.before = '\n' 10 | if ('after' in raws && raws.after) raws.after = '\n' 11 | } 12 | }) 13 | } 14 | } 15 | } 16 | 17 | trimPlugin.postcss = true 18 | export default trimPlugin 19 | -------------------------------------------------------------------------------- /packages/compiler-sfc/src/types.ts: -------------------------------------------------------------------------------- 1 | import { CompilerOptions, CompiledResult } from 'types/compiler' 2 | import { SFCDescriptor } from './parseComponent' 3 | 4 | export interface StartOfSourceMap { 5 | file?: string 6 | sourceRoot?: string 7 | } 8 | 9 | export interface RawSourceMap extends StartOfSourceMap { 10 | version: string 11 | sources: string[] 12 | names: string[] 13 | sourcesContent?: string[] 14 | mappings: string 15 | } 16 | 17 | export interface TemplateCompiler { 18 | parseComponent(source: string, options?: any): SFCDescriptor 19 | compile(template: string, options: CompilerOptions): CompiledResult 20 | ssrCompile(template: string, options: CompilerOptions): CompiledResult 21 | } 22 | 23 | export const enum BindingTypes { 24 | /** 25 | * returned from data() 26 | */ 27 | DATA = 'data', 28 | /** 29 | * declared as a prop 30 | */ 31 | PROPS = 'props', 32 | /** 33 | * a local alias of a ` 7 | 8 | 9 | 10 |
11 |
12 | {{ num }} 13 | 14 |
15 |
16 | 25 | 26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | countA: {{countA}} 36 |
37 |
38 | countB: {{countB}} 39 |
40 |
41 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /test/e2e/async-edge-cases.spec.ts: -------------------------------------------------------------------------------- 1 | // @vitest-environment node 2 | import path from 'path' 3 | import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils' 4 | 5 | describe('basic-ssr', () => { 6 | const { page, text, click, isChecked } = setupPuppeteer() 7 | 8 | test( 9 | 'should work', 10 | async () => { 11 | await page().goto( 12 | `file://${path.resolve(__dirname, `async-edge-cases.html`)}` 13 | ) 14 | 15 | // #4510 16 | expect(await text('#case-1')).toContain('1') 17 | expect(await isChecked('#case-1 input')).toBe(false) 18 | 19 | await click('#case-1 input') 20 | expect(await text('#case-1')).toContain('2') 21 | expect(await isChecked('#case-1 input')).toBe(true) 22 | 23 | await click('#case-1 input') 24 | expect(await text('#case-1')).toContain('3') 25 | expect(await isChecked('#case-1 input')).toBe(false) 26 | 27 | // #6566 28 | expect(await text('#case-2 button')).toContain('Expand is True') 29 | expect(await text('.count-a')).toContain('countA: 0') 30 | expect(await text('.count-b')).toContain('countB: 0') 31 | 32 | await click('#case-2 button') 33 | expect(await text('#case-2 button')).toContain('Expand is False') 34 | expect(await text('.count-a')).toContain('countA: 1') 35 | expect(await text('.count-b')).toContain('countB: 0') 36 | 37 | await click('#case-2 button') 38 | expect(await text('#case-2 button')).toContain('Expand is True') 39 | expect(await text('.count-a')).toContain('countA: 1') 40 | expect(await text('.count-b')).toContain('countB: 1') 41 | }, 42 | E2E_TIMEOUT 43 | ) 44 | }) 45 | -------------------------------------------------------------------------------- /test/e2e/basic-ssr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
wtf
12 | 13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/e2e/basic-ssr.spec.ts: -------------------------------------------------------------------------------- 1 | // @vitest-environment node 2 | import path from 'path' 3 | import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils' 4 | 5 | describe('basic-ssr', () => { 6 | const { page, text } = setupPuppeteer() 7 | 8 | test( 9 | 'should work', 10 | async () => { 11 | await page().goto(`file://${path.resolve(__dirname, `basic-ssr.html`)}`) 12 | expect(await text('#result')).toContain( 13 | `
foo
` 14 | ) 15 | }, 16 | E2E_TIMEOUT 17 | ) 18 | }) 19 | -------------------------------------------------------------------------------- /test/e2e/markdown.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | setupPuppeteer, 3 | expectByPolling, 4 | getExampleUrl, 5 | E2E_TIMEOUT 6 | } from './e2eUtils' 7 | 8 | describe('e2e: markdown', () => { 9 | const { page, isVisible, value, html } = setupPuppeteer() 10 | 11 | async function testMarkdown(apiType: 'classic' | 'composition') { 12 | await page().goto(getExampleUrl('markdown', apiType)) 13 | expect(await isVisible('#editor')).toBe(true) 14 | expect(await value('textarea')).toBe('# hello') 15 | expect(await html('#editor div')).toBe('

hello

\n') 16 | 17 | await page().type('textarea', '\n## foo\n\n- bar\n- baz') 18 | 19 | // assert the output is not updated yet because of debounce 20 | // debounce has become unstable on CI so this assertion is disabled 21 | // expect(await html('#editor div')).toBe('

hello

\n') 22 | 23 | await expectByPolling( 24 | () => html('#editor div'), 25 | '

hello

\n' + 26 | '

foo

\n' + 27 | '\n' 28 | ) 29 | } 30 | 31 | test( 32 | 'classic', 33 | async () => { 34 | await testMarkdown('classic') 35 | }, 36 | E2E_TIMEOUT 37 | ) 38 | 39 | test( 40 | 'composition', 41 | async () => { 42 | await testMarkdown('composition') 43 | }, 44 | E2E_TIMEOUT 45 | ) 46 | }) 47 | -------------------------------------------------------------------------------- /test/helpers/classlist.ts: -------------------------------------------------------------------------------- 1 | expect.extend({ 2 | toHaveClass(el: Element, cls: string) { 3 | const pass = el.classList 4 | ? el.classList.contains(cls) 5 | : (el.getAttribute('class') || '').split(/\s+/g).indexOf(cls) > -1 6 | return { 7 | pass, 8 | message: () => 9 | `Expected element${pass ? ' ' : ' not '}to have class ${cls}` 10 | } 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /test/helpers/shim-done.ts: -------------------------------------------------------------------------------- 1 | // wrap tests to support test('foo', done => {...}) interface 2 | const _test = test 3 | 4 | const wait = (): [() => void, Promise] => { 5 | let done 6 | const p = new Promise((resolve, reject) => { 7 | done = resolve 8 | done.fail = reject 9 | }) 10 | return [done, p] 11 | } 12 | 13 | const shimmed = 14 | ((global as any).it = 15 | (global as any).test = 16 | (desc: string, fn?: any, timeout?: number) => { 17 | if (fn && fn.length > 0) { 18 | _test( 19 | desc, 20 | () => { 21 | const [done, p] = wait() 22 | fn(done) 23 | return p 24 | }, 25 | timeout 26 | ) 27 | } else { 28 | _test(desc, fn, timeout) 29 | } 30 | }) 31 | 32 | ;['skip', 'only', 'todo', 'concurrent'].forEach(key => { 33 | shimmed[key] = _test[key] 34 | }) 35 | 36 | export {} 37 | -------------------------------------------------------------------------------- /test/helpers/test-object-option.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default function testObjectOption(name) { 4 | it(`Options ${name}: should warn non object value`, () => { 5 | const options = {} 6 | options[name] = () => {} 7 | new Vue(options) 8 | expect(`Invalid value for option "${name}"`).toHaveBeenWarned() 9 | }) 10 | 11 | it(`Options ${name}: should not warn valid object value`, () => { 12 | const options = {} 13 | options[name] = {} 14 | new Vue(options) 15 | expect(`Invalid value for option "${name}"`).not.toHaveBeenWarned() 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/helpers/trigger-event.ts: -------------------------------------------------------------------------------- 1 | export function triggerEvent(target, event, process) { 2 | const e = document.createEvent('HTMLEvents') 3 | e.initEvent(event, true, true) 4 | if (event === 'click') { 5 | // @ts-expect-error Button is readonly 6 | ;(e as MouseEvent).button = 0 7 | } 8 | if (process) process(e) 9 | target.dispatchEvent(e) 10 | } 11 | -------------------------------------------------------------------------------- /test/helpers/vdom.ts: -------------------------------------------------------------------------------- 1 | import VNode from 'core/vdom/vnode' 2 | 3 | export function createTextVNode(text) { 4 | return new VNode(undefined, undefined, undefined, text) 5 | } 6 | -------------------------------------------------------------------------------- /test/helpers/wait-for-update.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | // helper for async assertions. 4 | // Use like this: 5 | // 6 | // vm.a = 123 7 | // waitForUpdate(() => { 8 | // expect(vm.$el.textContent).toBe('123') 9 | // vm.a = 234 10 | // }) 11 | // .then(() => { 12 | // // more assertions... 13 | // }) 14 | // .then(done) 15 | 16 | interface Job extends Function { 17 | wait?: boolean 18 | fail?: (e: any) => void 19 | } 20 | 21 | const waitForUpdate = (initialCb: Job) => { 22 | let end 23 | const queue: Job[] = initialCb ? [initialCb] : [] 24 | 25 | function shift() { 26 | const job = queue.shift() 27 | if (queue.length) { 28 | let hasError = false 29 | try { 30 | job!.wait ? job!(shift) : job!() 31 | } catch (e) { 32 | hasError = true 33 | const done = queue[queue.length - 1] 34 | if (done && done.fail) { 35 | done.fail(e) 36 | } 37 | } 38 | if (!hasError && !job!.wait) { 39 | if (queue.length) { 40 | Vue.nextTick(shift) 41 | } 42 | } 43 | } else if (job && (job.fail || job === end)) { 44 | job() // done 45 | } 46 | } 47 | 48 | Vue.nextTick(() => { 49 | if (!queue.length || (!end && !queue[queue.length - 1]!.fail)) { 50 | throw new Error('waitForUpdate chain is missing .then(done)') 51 | } 52 | shift() 53 | }) 54 | 55 | const chainer = { 56 | then: nextCb => { 57 | queue.push(nextCb) 58 | return chainer 59 | }, 60 | thenWaitFor: wait => { 61 | if (typeof wait === 'number') { 62 | wait = timeout(wait) 63 | } 64 | wait.wait = true 65 | queue.push(wait) 66 | return chainer 67 | }, 68 | end: endFn => { 69 | queue.push(endFn) 70 | end = endFn 71 | } 72 | } 73 | 74 | return chainer 75 | } 76 | 77 | function timeout(n) { 78 | return next => setTimeout(next, n) 79 | } 80 | 81 | export { waitForUpdate } 82 | -------------------------------------------------------------------------------- /test/test-env.d.ts: -------------------------------------------------------------------------------- 1 | interface Chainer { 2 | then(next: Function): this 3 | thenWaitFor(n: number | Function): this 4 | end(endFn: Function): void 5 | } 6 | 7 | declare function waitForUpdate(cb: Function): Chainer 8 | 9 | declare function createTextVNode(arg?: string): any 10 | 11 | declare function triggerEvent( 12 | target: Element, 13 | event: string, 14 | process?: (e: any) => void 15 | ): void 16 | 17 | // vitest extends jest namespace so we can just extend jest.Matchers 18 | declare namespace jest { 19 | interface Matchers { 20 | toHaveBeenWarned(): R 21 | toHaveBeenWarnedLast(): R 22 | toHaveBeenWarnedTimes(n: number): R 23 | toHaveBeenTipped(): R 24 | toHaveClass(cls: string): R 25 | } 26 | } 27 | 28 | declare const jasmine: { 29 | createSpy: (id?: string) => { 30 | (...args: any[]): any 31 | calls: { 32 | count(): number 33 | } 34 | } 35 | addMatchers(matchers: any): void 36 | } 37 | -------------------------------------------------------------------------------- /test/transition/karma.conf.js: -------------------------------------------------------------------------------- 1 | const featureFlags = require('../../scripts/feature-flags') 2 | process.env.CHROME_BIN = require('puppeteer').executablePath() 3 | 4 | const define = { 5 | __DEV__: `true`, 6 | 'process.env.CI': String(!!process.env.CI) 7 | } 8 | 9 | for (const key in featureFlags) { 10 | define[`process.env.${key}`] = String(featureFlags[key]) 11 | } 12 | 13 | module.exports = function (config) { 14 | config.set({ 15 | basePath: '.', 16 | frameworks: ['jasmine'], 17 | files: ['*.spec.ts'], 18 | preprocessors: { 19 | '*.spec.ts': ['esbuild'] 20 | }, 21 | esbuild: { 22 | define 23 | }, 24 | browsers: ['ChromeHeadless'], 25 | plugins: ['karma-jasmine', 'karma-esbuild', 'karma-chrome-launcher'], 26 | singleRun: true 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /test/transition/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "vitest/globals"] 5 | }, 6 | "include": ["../src", "."] 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/features/directives/cloak.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Directive v-cloak', () => { 4 | it('should be removed after compile', () => { 5 | const el = document.createElement('div') 6 | el.setAttribute('v-cloak', '') 7 | const vm = new Vue({ el }) 8 | expect(vm.$el.hasAttribute('v-cloak')).toBe(false) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/unit/features/directives/model-file.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Directive v-model file', () => { 4 | it('warn to use @change instead', () => { 5 | new Vue({ 6 | data: { 7 | file: '' 8 | }, 9 | template: '' 10 | }).$mount() 11 | expect('Use a v-on:change listener instead').toHaveBeenWarned() 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /test/unit/features/directives/model-parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { parseModel } from 'compiler/directives/model' 2 | 3 | describe('model expression parser', () => { 4 | it('parse single path', () => { 5 | const res = parseModel('foo') 6 | expect(res.exp).toBe('foo') 7 | expect(res.key).toBe(null) 8 | }) 9 | 10 | it('parse object dot notation', () => { 11 | const res = parseModel('a.b.c') 12 | expect(res.exp).toBe('a.b') 13 | expect(res.key).toBe('"c"') 14 | }) 15 | 16 | it('parse string in brackets', () => { 17 | const res = parseModel('a["b"][c]') 18 | expect(res.exp).toBe('a["b"]') 19 | expect(res.key).toBe('c') 20 | }) 21 | 22 | it('parse brackets with object dot notation', () => { 23 | const res = parseModel('a["b"][c].xxx') 24 | expect(res.exp).toBe('a["b"][c]') 25 | expect(res.key).toBe('"xxx"') 26 | }) 27 | 28 | it('parse nested brackets', () => { 29 | const res = parseModel('a[i[c]]') 30 | expect(res.exp).toBe('a') 31 | expect(res.key).toBe('i[c]') 32 | }) 33 | 34 | it('combined', () => { 35 | const res = parseModel('test.xxx.a["asa"][test1[key]]') 36 | expect(res.exp).toBe('test.xxx.a["asa"]') 37 | expect(res.key).toBe('test1[key]') 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/unit/features/directives/pre.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Directive v-pre', function () { 4 | it('should not compile inner content', function () { 5 | const vm = new Vue({ 6 | template: `
7 |
{{ a }}
8 |
{{ a }}
9 |
10 | 11 |
12 |
`, 13 | data: { 14 | a: 123 15 | } 16 | }) 17 | vm.$mount() 18 | expect(vm.$el.firstChild.textContent).toBe('{{ a }}') 19 | expect(vm.$el.children[1].textContent).toBe('123') 20 | expect(vm.$el.lastChild.innerHTML).toBe('') 21 | }) 22 | 23 | it('should not compile on root node', function () { 24 | const vm = new Vue({ 25 | template: '
{{ a }}
', 26 | replace: true, 27 | data: { 28 | a: 123 29 | } 30 | }) 31 | vm.$mount() 32 | expect(vm.$el.firstChild.textContent).toBe('{{ a }}') 33 | }) 34 | 35 | // #8286 36 | it('should not compile custom component tags', function () { 37 | Vue.component('vtest', { template: `
Hello World
` }) 38 | const vm = new Vue({ 39 | template: '
', 40 | replace: true 41 | }) 42 | vm.$mount() 43 | expect(vm.$el.firstChild.tagName).toBe('VTEST') 44 | }) 45 | 46 | // #10087 47 | it('should not compile attributes', function () { 48 | const vm = new Vue({ 49 | template: '

A Test

' 50 | }) 51 | vm.$mount() 52 | expect(vm.$el.firstChild.getAttribute('open')).toBe('hello') 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/unit/features/directives/static-style-parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { parseStyleText } from 'web/util/style' 2 | const base64ImgUrl = 3 | 'url("data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==")' 4 | const logoUrl = 'url(https://vuejs.org/images/logo.png)' 5 | 6 | it('should parse normal static style', () => { 7 | const staticStyle = `font-size: 12px;background: ${logoUrl};color:red` 8 | const res = parseStyleText(staticStyle) 9 | expect(res.background).toBe(logoUrl) 10 | expect(res.color).toBe('red') 11 | expect(res['font-size']).toBe('12px') 12 | }) 13 | 14 | it('should parse base64 background', () => { 15 | const staticStyle = `background: ${base64ImgUrl}` 16 | const res = parseStyleText(staticStyle) 17 | expect(res.background).toBe(base64ImgUrl) 18 | }) 19 | 20 | it('should parse multiple background images ', () => { 21 | let staticStyle = `background: ${logoUrl}, ${logoUrl};` 22 | let res = parseStyleText(staticStyle) 23 | expect(res.background).toBe(`${logoUrl}, ${logoUrl}`) 24 | 25 | staticStyle = `background: ${base64ImgUrl}, ${base64ImgUrl}` 26 | res = parseStyleText(staticStyle) 27 | expect(res.background).toBe(`${base64ImgUrl}, ${base64ImgUrl}`) 28 | }) 29 | 30 | it('should parse other images ', () => { 31 | let staticStyle = `shape-outside: ${logoUrl}` 32 | let res = parseStyleText(staticStyle) 33 | expect(res['shape-outside']).toBe(logoUrl) 34 | 35 | staticStyle = `list-style-image: ${logoUrl}` 36 | res = parseStyleText(staticStyle) 37 | expect(res['list-style-image']).toBe(logoUrl) 38 | 39 | staticStyle = `border-image: ${logoUrl} 30 30 repeat` 40 | res = parseStyleText(staticStyle) 41 | expect(res['border-image']).toBe(`${logoUrl} 30 30 repeat`) 42 | }) 43 | -------------------------------------------------------------------------------- /test/unit/features/global-api/compile.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Global API: compile', () => { 4 | it('should compile render functions', () => { 5 | const res = Vue.compile('
{{ msg }}
') 6 | const vm = new Vue({ 7 | data: { 8 | msg: 'hello' 9 | }, 10 | render: res.render, 11 | staticRenderFns: res.staticRenderFns 12 | }).$mount() 13 | expect(vm.$el.innerHTML).toContain('hello') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/unit/features/global-api/observable.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Global API: observable', () => { 4 | it('should work', done => { 5 | const state = Vue.observable({ 6 | count: 0 7 | }) 8 | 9 | const app = new Vue({ 10 | render(h) { 11 | return h('div', [ 12 | h('span', state.count), 13 | h( 14 | 'button', 15 | { 16 | on: { 17 | click: () => { 18 | state.count++ 19 | } 20 | } 21 | }, 22 | '+' 23 | ) 24 | ]) 25 | } 26 | }).$mount() 27 | 28 | expect(app.$el.querySelector('span').textContent).toBe('0') 29 | app.$el.querySelector('button').click() 30 | waitForUpdate(() => { 31 | expect(app.$el.querySelector('span').textContent).toBe('1') 32 | }).then(done) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/unit/features/global-api/use.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Global API: use', () => { 4 | const def = {} 5 | const options = {} 6 | const pluginStub = { 7 | install: (Vue, opts) => { 8 | Vue.directive('plugin-test', def) 9 | expect(opts).toBe(options) 10 | } 11 | } 12 | 13 | it('should apply Object plugin', () => { 14 | Vue.use(pluginStub, options) 15 | expect(Vue.options.directives['plugin-test']).toBe(def) 16 | delete Vue.options.directives['plugin-test'] 17 | expect(Vue.options.directives['plugin-test']).toBeUndefined() 18 | 19 | // should not double apply 20 | Vue.use(pluginStub, options) 21 | expect(Vue.options.directives['plugin-test']).toBeUndefined() 22 | }) 23 | 24 | it('should apply Function plugin', () => { 25 | Vue.use(pluginStub.install, options) 26 | expect(Vue.options.directives['plugin-test']).toBe(def) 27 | delete Vue.options.directives['plugin-test'] 28 | }) 29 | 30 | it('should work on extended constructors without polluting the base', () => { 31 | const Ctor = Vue.extend({}) 32 | Ctor.use(pluginStub, options) 33 | expect(Vue.options.directives['plugin-test']).toBeUndefined() 34 | expect(Ctor.options.directives['plugin-test']).toBe(def) 35 | }) 36 | 37 | // GitHub issue #5970 38 | it('should work on multi version', () => { 39 | const Ctor1 = Vue.extend({}) 40 | const Ctor2 = Vue.extend({}) 41 | 42 | Ctor1.use(pluginStub, options) 43 | expect(Vue.options.directives['plugin-test']).toBeUndefined() 44 | expect(Ctor1.options.directives['plugin-test']).toBe(def) 45 | 46 | // multi version Vue Ctor with the same cid 47 | Ctor2.cid = Ctor1.cid 48 | Ctor2.use(pluginStub, options) 49 | expect(Vue.options.directives['plugin-test']).toBeUndefined() 50 | expect(Ctor2.options.directives['plugin-test']).toBe(def) 51 | }) 52 | 53 | // #8595 54 | it('chain call', () => { 55 | expect(Vue.use(() => {})).toBe(Vue) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /test/unit/features/instance/init.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Initialization', () => { 4 | it('without new', () => { 5 | try { 6 | Vue() 7 | } catch (e) {} 8 | expect( 9 | 'Vue is a constructor and should be called with the `new` keyword' 10 | ).toHaveBeenWarned() 11 | }) 12 | 13 | it('with new', () => { 14 | expect(new Vue() instanceof Vue).toBe(true) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /test/unit/features/options/comments.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Comments', () => { 4 | it('comments should be kept', () => { 5 | const vm = new Vue({ 6 | comments: true, 7 | data() { 8 | return { 9 | foo: 1 10 | } 11 | }, 12 | template: 13 | '
node1{{foo}}
' 14 | }).$mount() 15 | expect(vm.$el.innerHTML).toEqual( 16 | 'node11' 17 | ) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /test/unit/features/options/extends.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { nativeWatch } from 'core/util/env' 3 | 4 | describe('Options extends', () => { 5 | it('should work on objects', () => { 6 | const A = { 7 | data() { 8 | return { a: 1 } 9 | } 10 | } 11 | const B = { 12 | extends: A, 13 | data() { 14 | return { b: 2 } 15 | } 16 | } 17 | const vm = new Vue({ 18 | extends: B, 19 | data: { 20 | c: 3 21 | } 22 | }) 23 | expect(vm.a).toBe(1) 24 | expect(vm.b).toBe(2) 25 | expect(vm.c).toBe(3) 26 | }) 27 | 28 | it('should work on extended constructors', () => { 29 | const A = Vue.extend({ 30 | data() { 31 | return { a: 1 } 32 | } 33 | }) 34 | const B = Vue.extend({ 35 | extends: A, 36 | data() { 37 | return { b: 2 } 38 | } 39 | }) 40 | const vm = new Vue({ 41 | extends: B, 42 | data: { 43 | c: 3 44 | } 45 | }) 46 | expect(vm.a).toBe(1) 47 | expect(vm.b).toBe(2) 48 | expect(vm.c).toBe(3) 49 | }) 50 | 51 | if (nativeWatch) { 52 | it('should work with global mixins + Object.prototype.watch', done => { 53 | Vue.mixin({}) 54 | 55 | const spy = vi.fn() 56 | const A = Vue.extend({ 57 | data: function () { 58 | return { a: 1 } 59 | }, 60 | watch: { 61 | a: spy 62 | }, 63 | created: function () { 64 | this.a = 2 65 | } 66 | }) 67 | new Vue({ 68 | extends: A 69 | }) 70 | waitForUpdate(() => { 71 | expect(spy).toHaveBeenCalledWith(2, 1) 72 | }).then(done) 73 | }) 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /test/unit/features/options/inheritAttrs.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Options inheritAttrs', () => { 4 | it('should work', done => { 5 | const vm = new Vue({ 6 | template: ``, 7 | data: { foo: 'foo' }, 8 | components: { 9 | foo: { 10 | inheritAttrs: false, 11 | template: `
foo
` 12 | } 13 | } 14 | }).$mount() 15 | expect(vm.$el.id).toBe('') 16 | vm.foo = 'bar' 17 | waitForUpdate(() => { 18 | expect(vm.$el.id).toBe('') 19 | }).then(done) 20 | }) 21 | 22 | it('with inner v-bind', done => { 23 | const vm = new Vue({ 24 | template: ``, 25 | data: { foo: 'foo' }, 26 | components: { 27 | foo: { 28 | inheritAttrs: false, 29 | template: `
` 30 | } 31 | } 32 | }).$mount() 33 | expect(vm.$el.children[0].id).toBe('foo') 34 | vm.foo = 'bar' 35 | waitForUpdate(() => { 36 | expect(vm.$el.children[0].id).toBe('bar') 37 | }).then(done) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/unit/features/options/methods.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import testObjectOption from '../../../helpers/test-object-option' 3 | 4 | describe('Options methods', () => { 5 | testObjectOption('methods') 6 | 7 | it('should have correct context', () => { 8 | const vm = new Vue({ 9 | data: { 10 | a: 1 11 | }, 12 | methods: { 13 | plus() { 14 | this.a++ 15 | } 16 | } 17 | }) 18 | vm.plus() 19 | expect(vm.a).toBe(2) 20 | }) 21 | 22 | it('should warn methods of not function type', () => { 23 | new Vue({ 24 | methods: { 25 | hello: {} 26 | } 27 | }) 28 | expect( 29 | 'Method "hello" has type "object" in the component definition' 30 | ).toHaveBeenWarned() 31 | }) 32 | 33 | it('should warn methods conflicting with data', () => { 34 | new Vue({ 35 | data: { 36 | foo: 1 37 | }, 38 | methods: { 39 | foo() {} 40 | } 41 | }) 42 | expect( 43 | `Method "foo" has already been defined as a data property` 44 | ).toHaveBeenWarned() 45 | }) 46 | 47 | it('should warn methods conflicting with internal methods', () => { 48 | new Vue({ 49 | methods: { 50 | _update() {} 51 | } 52 | }) 53 | expect( 54 | `Method "_update" conflicts with an existing Vue instance method` 55 | ).toHaveBeenWarned() 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /test/unit/features/options/name.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Options name', () => { 4 | it('should contain itself in self components', () => { 5 | const vm = Vue.extend({ 6 | name: 'SuperVue' 7 | }) 8 | 9 | expect(vm.options.components['SuperVue']).toEqual(vm) 10 | }) 11 | 12 | it('should warn when incorrect name given', () => { 13 | Vue.extend({ 14 | name: 'Hyper*Vue' 15 | }) 16 | 17 | /* eslint-disable */ 18 | expect(`Invalid component name: "Hyper*Vue".`).toHaveBeenWarned() 19 | /* eslint-enable */ 20 | 21 | Vue.extend({ 22 | name: '2Cool2BValid' 23 | }) 24 | 25 | /* eslint-disable */ 26 | expect(`Invalid component name: "2Cool2BValid".`).toHaveBeenWarned() 27 | /* eslint-enable */ 28 | }) 29 | 30 | it('id should not override given name when using Vue.component', () => { 31 | const SuperComponent = Vue.component('super-component', { 32 | name: 'SuperVue' 33 | })! 34 | 35 | expect(SuperComponent.options.components['SuperVue']).toEqual( 36 | SuperComponent 37 | ) 38 | expect(SuperComponent.options.components['super-component']).toEqual( 39 | SuperComponent 40 | ) 41 | }) 42 | 43 | it('should allow all potential custom element name for component name including non-alphanumeric characters', () => { 44 | Vue.extend({ 45 | name: 'my-컴포넌트' 46 | }) 47 | 48 | expect(`Invalid component name`).not.toHaveBeenWarned() 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/unit/features/options/parent.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Options parent', () => { 4 | it('should work', () => { 5 | const parent = new Vue({ 6 | render() {} 7 | }).$mount() 8 | 9 | const child = new Vue({ 10 | parent: parent, 11 | render() {} 12 | }).$mount() 13 | 14 | // this option is straight-forward 15 | // it should register 'parent' as a $parent for 'child' 16 | // and push 'child' to $children array on 'parent' 17 | expect(child.$options.parent).toBeDefined() 18 | expect(child.$options.parent).toEqual(parent) 19 | expect(child.$parent).toBeDefined() 20 | expect(child.$parent).toEqual(parent) 21 | expect(parent.$children).toContain(child) 22 | 23 | // destroy 'child' and check if it was removed from 'parent' $children 24 | child.$destroy() 25 | expect(parent.$children.length).toEqual(0) 26 | parent.$destroy() 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/unit/features/options/propsData.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Options propsData', () => { 4 | it('should work', done => { 5 | const A = Vue.extend({ 6 | props: ['a'], 7 | template: '
{{ a }}
' 8 | }) 9 | const vm = new A({ 10 | propsData: { 11 | a: 123 12 | } 13 | }).$mount() 14 | expect(vm.a).toBe(123) 15 | expect(vm.$el.textContent).toBe('123') 16 | vm.a = 234 17 | waitForUpdate(() => { 18 | expect(vm.$el.textContent).toBe('234') 19 | }).then(done) 20 | }) 21 | 22 | it('warn non instantiation usage', () => { 23 | Vue.extend({ 24 | propsData: { 25 | a: 123 26 | } 27 | }) 28 | expect( 29 | 'option "propsData" can only be used during instance creation' 30 | ).toHaveBeenWarned() 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /test/unit/features/options/render.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Options render', () => { 4 | it('basic usage', () => { 5 | const vm = new Vue({ 6 | render(h) { 7 | const children: any[] = [] 8 | for (let i = 0; i < this.items.length; i++) { 9 | children.push(h('li', { staticClass: 'task' }, [this.items[i].name])) 10 | } 11 | return h('ul', { staticClass: 'tasks' }, children) 12 | }, 13 | data: { 14 | items: [ 15 | { id: 1, name: 'task1' }, 16 | { id: 2, name: 'task2' } 17 | ] 18 | } 19 | }).$mount() 20 | expect(vm.$el.tagName).toBe('UL') 21 | for (let i = 0; i < vm.$el.children.length; i++) { 22 | const li = vm.$el.children[i] 23 | expect(li.tagName).toBe('LI') 24 | expect(li.textContent).toBe(vm.items[i].name) 25 | } 26 | }) 27 | 28 | it('allow null data', () => { 29 | const vm = new Vue({ 30 | render(h) { 31 | return h('div', null, 'hello' /* string as children*/) 32 | } 33 | }).$mount() 34 | expect(vm.$el.tagName).toBe('DIV') 35 | expect(vm.$el.textContent).toBe('hello') 36 | }) 37 | 38 | it('should warn non `render` option and non `template` option', () => { 39 | new Vue().$mount() 40 | expect( 41 | 'Failed to mount component: template or render function not defined.' 42 | ).toHaveBeenWarned() 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/unit/features/options/renderError.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | describe('Options renderError', () => { 4 | it('should be used on render errors', done => { 5 | Vue.config.errorHandler = () => {} 6 | const vm = new Vue({ 7 | data: { 8 | ok: true 9 | }, 10 | render(h) { 11 | if (this.ok) { 12 | return h('div', 'ok') 13 | } else { 14 | throw new Error('no') 15 | } 16 | }, 17 | renderError(h, err) { 18 | return h('div', err.toString()) 19 | } 20 | }).$mount() 21 | expect(vm.$el.textContent).toBe('ok') 22 | vm.ok = false 23 | waitForUpdate(() => { 24 | expect(vm.$el.textContent).toBe('Error: no') 25 | Vue.config.errorHandler = undefined 26 | }).then(done) 27 | }) 28 | 29 | it('should pass on errors in renderError to global handler', () => { 30 | const spy = (Vue.config.errorHandler = vi.fn()) 31 | const err = new Error('renderError') 32 | const vm = new Vue({ 33 | render() { 34 | throw new Error('render') 35 | }, 36 | renderError() { 37 | throw err 38 | } 39 | }).$mount() 40 | expect(spy).toHaveBeenCalledWith(err, vm, 'renderError') 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/unit/features/v3/useCssVars.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { useCssVars, h, reactive, nextTick } from 'v3' 3 | 4 | describe('useCssVars', () => { 5 | async function assertCssVars(getApp: (state: any) => any) { 6 | const state = reactive({ color: 'red' }) 7 | const App = getApp(state) 8 | const vm = new Vue(App).$mount() 9 | await nextTick() 10 | expect((vm.$el as HTMLElement).style.getPropertyValue(`--color`)).toBe( 11 | `red` 12 | ) 13 | 14 | state.color = 'green' 15 | await nextTick() 16 | expect((vm.$el as HTMLElement).style.getPropertyValue(`--color`)).toBe( 17 | `green` 18 | ) 19 | } 20 | 21 | test('basic', async () => { 22 | await assertCssVars(state => ({ 23 | setup() { 24 | // test receiving render context 25 | useCssVars(vm => ({ 26 | color: vm.color 27 | })) 28 | return state 29 | }, 30 | render() { 31 | return h('div') 32 | } 33 | })) 34 | }) 35 | 36 | test('on HOCs', async () => { 37 | const Child = { 38 | render: () => h('div') 39 | } 40 | 41 | await assertCssVars(state => ({ 42 | setup() { 43 | useCssVars(() => state) 44 | return () => h(Child) 45 | } 46 | })) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/unit/modules/observer/dep.spec.ts: -------------------------------------------------------------------------------- 1 | import Dep, { cleanupDeps } from 'core/observer/dep' 2 | 3 | describe('Dep', () => { 4 | let dep 5 | 6 | beforeEach(() => { 7 | dep = new Dep() 8 | }) 9 | 10 | describe('instance', () => { 11 | it('should be created with correct properties', () => { 12 | expect(dep.subs.length).toBe(0) 13 | expect(new Dep().id).toBe(dep.id + 1) 14 | }) 15 | }) 16 | 17 | describe('addSub()', () => { 18 | it('should add sub', () => { 19 | dep.addSub(null) 20 | expect(dep.subs.length).toBe(1) 21 | expect(dep.subs[0]).toBe(null) 22 | }) 23 | }) 24 | 25 | describe('removeSub()', () => { 26 | it('should remove sub', () => { 27 | const sub = {} 28 | dep.subs.push(sub) 29 | dep.removeSub(sub) 30 | expect(dep.subs.includes(sub)).toBe(false) 31 | 32 | // nulled subs are cleared on next flush 33 | cleanupDeps() 34 | expect(dep.subs.length).toBe(0) 35 | }) 36 | }) 37 | 38 | describe('depend()', () => { 39 | let _target 40 | 41 | beforeAll(() => { 42 | _target = Dep.target 43 | }) 44 | 45 | afterAll(() => { 46 | Dep.target = _target 47 | }) 48 | 49 | it('should do nothing if no target', () => { 50 | Dep.target = null 51 | dep.depend() 52 | }) 53 | 54 | it('should add itself to target', () => { 55 | Dep.target = { addDep: vi.fn() } as any 56 | dep.depend() 57 | expect(Dep.target!.addDep).toHaveBeenCalledWith(dep) 58 | }) 59 | }) 60 | 61 | describe('notify()', () => { 62 | it('should notify subs', () => { 63 | dep.subs.push({ update: vi.fn() }) 64 | dep.notify() 65 | expect(dep.subs[0].update).toHaveBeenCalled() 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/unit/modules/server-compiler/compiler-options.spec.ts: -------------------------------------------------------------------------------- 1 | import { ssrCompile } from 'server/compiler' 2 | 3 | describe('ssrCompile options', () => { 4 | it('comments', () => { 5 | const compiled = ssrCompile( 6 | ` 7 |
8 | 9 |
10 | `, 11 | { comments: true } 12 | ) 13 | 14 | expect(compiled.render).toContain('') 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /test/unit/modules/util/error.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { invokeWithErrorHandling } from 'core/util/error' 3 | 4 | describe('invokeWithErrorHandling', () => { 5 | if (typeof Promise !== 'undefined') { 6 | it('should errorHandler call once when nested calls return rejected promise', done => { 7 | const originalHandler = Vue.config.errorHandler 8 | const handler = (Vue.config.errorHandler = vi.fn()) 9 | const userCatch = vi.fn() 10 | const err = new Error('fake error') 11 | 12 | invokeWithErrorHandling(() => { 13 | return invokeWithErrorHandling(() => { 14 | return Promise.reject(err) 15 | }) 16 | }) 17 | .catch(userCatch) 18 | .then(() => { 19 | Vue.config.errorHandler = originalHandler 20 | expect(handler.mock.calls.length).toBe(1) 21 | expect(userCatch).toHaveBeenCalledWith(err) 22 | done() 23 | }) 24 | }) 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /test/unit/modules/util/next-tick.spec.ts: -------------------------------------------------------------------------------- 1 | import { nextTick } from 'core/util/next-tick' 2 | 3 | describe('nextTick', () => { 4 | it('accepts a callback', done => { 5 | nextTick(done) 6 | }) 7 | 8 | it('returns undefined when passed a callback', () => { 9 | expect(nextTick(() => {})).toBeUndefined() 10 | }) 11 | 12 | if (typeof Promise !== 'undefined') { 13 | it('returns a Promise when provided no callback', done => { 14 | nextTick().then(done) 15 | }) 16 | 17 | it('returns a Promise with a context argument when provided a falsy callback and an object', done => { 18 | const obj = {} 19 | nextTick(undefined, obj).then(ctx => { 20 | expect(ctx).toBe(obj) 21 | done() 22 | }) 23 | }) 24 | 25 | it('returned Promise should resolve correctly vs callback', done => { 26 | const spy = vi.fn() 27 | nextTick(spy) 28 | nextTick().then(() => { 29 | expect(spy).toHaveBeenCalled() 30 | done() 31 | }) 32 | }) 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /test/unit/modules/util/toString.spec.ts: -------------------------------------------------------------------------------- 1 | import { toString } from 'core/util/index' 2 | import { ref } from 'v3' 3 | 4 | test('should unwrap refs', () => { 5 | expect( 6 | toString({ 7 | a: ref(0), 8 | b: { c: ref(1) } 9 | }) 10 | ).toBe(JSON.stringify({ a: 0, b: { c: 1 } }, null, 2)) 11 | }) 12 | -------------------------------------------------------------------------------- /test/unit/modules/vdom/modules/directive.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { patch } from 'web/runtime/patch' 3 | import VNode from 'core/vdom/vnode' 4 | 5 | describe('vdom directive module', () => { 6 | it('should work', () => { 7 | const directive1 = { 8 | bind: vi.fn(), 9 | update: vi.fn(), 10 | unbind: vi.fn() 11 | } 12 | const vm = new Vue({ directives: { directive1 } }) 13 | // create 14 | const vnode1 = new VNode('div', {}, [ 15 | new VNode( 16 | 'p', 17 | { 18 | directives: [ 19 | { 20 | name: 'directive1', 21 | value: 'hello', 22 | arg: 'arg1', 23 | modifiers: { modifier1: true } 24 | } 25 | ] 26 | }, 27 | undefined, 28 | 'hello world', 29 | undefined, 30 | vm 31 | ) 32 | ]) 33 | patch(null, vnode1) 34 | expect(directive1.bind).toHaveBeenCalled() 35 | // update 36 | const vnode2 = new VNode('div', {}, [ 37 | new VNode( 38 | 'p', 39 | { 40 | directives: [ 41 | { 42 | name: 'directive1', 43 | value: 'world', 44 | arg: 'arg1', 45 | modifiers: { modifier1: true } 46 | } 47 | ] 48 | }, 49 | undefined, 50 | 'hello world', 51 | undefined, 52 | vm 53 | ) 54 | ]) 55 | patch(vnode1, vnode2) 56 | expect(directive1.update).toHaveBeenCalled() 57 | // destroy 58 | const vnode3 = new VNode('div') 59 | patch(vnode2, vnode3) 60 | expect(directive1.unbind).toHaveBeenCalled() 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /test/unit/modules/vdom/modules/style.spec.ts: -------------------------------------------------------------------------------- 1 | import { patch } from 'web/runtime/patch' 2 | import VNode from 'core/vdom/vnode' 3 | 4 | describe('vdom style module', () => { 5 | it('should create an element with style', () => { 6 | const vnode = new VNode('p', { style: { fontSize: '12px' } }) 7 | const elm = patch(null, vnode) 8 | expect(elm.style.fontSize).toBe('12px') 9 | }) 10 | 11 | it('should create an element with array style', () => { 12 | const vnode = new VNode('p', { 13 | style: [{ fontSize: '12px' }, { color: 'red' }] 14 | }) 15 | const elm = patch(null, vnode) 16 | expect(elm.style.fontSize).toBe('12px') 17 | expect(elm.style.color).toBe('red') 18 | }) 19 | 20 | it('should change elements style', () => { 21 | const vnode1 = new VNode('p', { style: { fontSize: '12px' } }) 22 | const vnode2 = new VNode('p', { 23 | style: { fontSize: '10px', display: 'block' } 24 | }) 25 | patch(null, vnode1) 26 | const elm = patch(vnode1, vnode2) 27 | expect(elm.style.fontSize).toBe('10px') 28 | expect(elm.style.display).toBe('block') 29 | }) 30 | 31 | it('should remove elements attrs', () => { 32 | const vnode1 = new VNode('p', { style: { fontSize: '12px' } }) 33 | const vnode2 = new VNode('p', { style: { display: 'block' } }) 34 | patch(null, vnode1) 35 | const elm = patch(vnode1, vnode2) 36 | expect(elm.style.fontSize).toBe('') 37 | expect(elm.style.display).toBe('block') 38 | }) 39 | 40 | it('border related style should update correctly', () => { 41 | const vnode1 = new VNode('p', { 42 | style: { border: '10px solid red', 'border-bottom': '10px solid blue' } 43 | }) 44 | const vnode2 = new VNode('p', { 45 | style: { 46 | 'border-right': '10px solid red', 47 | 'border-bottom': '10px solid blue' 48 | } 49 | }) 50 | patch(null, vnode1) 51 | const elm = patch(vnode1, vnode2) 52 | expect(elm.style.borderBottom).toBe('10px solid blue') 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | process.env.NEW_SLOT_SYNTAX = 'true' 2 | 3 | import './helpers/shim-done' 4 | import './helpers/to-have-warned' 5 | import './helpers/classlist' 6 | 7 | import { waitForUpdate } from './helpers/wait-for-update' 8 | import { triggerEvent } from './helpers/trigger-event' 9 | import { createTextVNode } from './helpers/vdom' 10 | 11 | global.waitForUpdate = waitForUpdate 12 | global.triggerEvent = triggerEvent 13 | global.createTextVNode = createTextVNode 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "dist", 5 | "sourceMap": true, 6 | "target": "esnext", 7 | "module": "ESNext", 8 | "moduleResolution": "node", 9 | "newLine": "LF", 10 | "strict": true, 11 | 12 | "allowJs": true, 13 | "noImplicitAny": false, 14 | "noImplicitThis": false, 15 | 16 | "noUnusedLocals": true, 17 | "experimentalDecorators": true, 18 | "resolveJsonModule": true, 19 | "esModuleInterop": true, 20 | "removeComments": false, 21 | "jsx": "preserve", 22 | "lib": ["esnext", "dom"], 23 | "types": ["node"], 24 | "paths": { 25 | "compiler/*": ["src/compiler/*"], 26 | "core/*": ["src/core/*"], 27 | "server/*": ["packages/server-renderer/src/*"], 28 | "sfc/*": ["packages/compiler-sfc/src/*"], 29 | "shared/*": ["src/shared/*"], 30 | "web/*": ["src/platforms/web/*"], 31 | "v3": ["src/v3/index"], 32 | "v3/*": ["src/v3/*"], 33 | "types/*": ["src/types/*"], 34 | "vue": ["src/platforms/web/entry-runtime-with-compiler"] 35 | } 36 | }, 37 | "include": ["src", "packages/*/src"] 38 | } 39 | -------------------------------------------------------------------------------- /types/built-in-components.d.ts: -------------------------------------------------------------------------------- 1 | import { DefineComponent } from './v3-define-component' 2 | 3 | type Hook void> = T | T[] 4 | 5 | export interface TransitionProps { 6 | name?: string 7 | appear?: boolean 8 | css?: boolean 9 | mode?: 'in-out' | 'out-in' | 'default' 10 | type?: 'transition' | 'animation' 11 | 12 | duration?: 13 | | number 14 | | string 15 | | { 16 | enter: number 17 | leave: number 18 | } 19 | 20 | // classes 21 | enterClass?: string 22 | enterActiveClass?: string 23 | enterToClass?: string 24 | appearClass?: string 25 | appearActiveClass?: string 26 | appearToClass?: string 27 | leaveClass?: string 28 | leaveActiveClass?: string 29 | leaveToClass?: string 30 | 31 | // event hooks 32 | onBeforeEnter?: Hook<(el: Element) => void> 33 | onEnter?: Hook<(el: Element, done: () => void) => void> 34 | onAfterEnter?: Hook<(el: Element) => void> 35 | onEnterCancelled?: Hook<(el: Element) => void> 36 | onBeforeLeave?: Hook<(el: Element) => void> 37 | onLeave?: Hook<(el: Element, done: () => void) => void> 38 | onAfterLeave?: Hook<(el: Element) => void> 39 | onLeaveCancelled?: Hook<(el: Element) => void> 40 | onBeforeAppear?: Hook<(el: Element) => void> 41 | onAppear?: Hook<(el: Element, done: () => void) => void> 42 | onAfterAppear?: Hook<(el: Element) => void> 43 | onAppearCancelled?: Hook<(el: Element) => void> 44 | } 45 | 46 | export declare const Transition: DefineComponent 47 | 48 | export type TransitionGroupProps = Omit & { 49 | tag?: string 50 | moveClass?: string 51 | } 52 | 53 | export declare const TransitionGroup: DefineComponent 54 | 55 | type MatchPattern = string | RegExp | (string | RegExp)[] 56 | 57 | export interface KeepAliveProps { 58 | include?: MatchPattern 59 | exclude?: MatchPattern 60 | max?: number | string 61 | } 62 | 63 | export declare const KeepAlive: DefineComponent 64 | -------------------------------------------------------------------------------- /types/common.d.ts: -------------------------------------------------------------------------------- 1 | export type Data = { [key: string]: unknown } 2 | 3 | export type UnionToIntersection = ( 4 | U extends any ? (k: U) => void : never 5 | ) extends (k: infer I) => void 6 | ? I 7 | : never 8 | 9 | // Conditional returns can enforce identical types. 10 | // See here: https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650 11 | // prettier-ignore 12 | type Equal = 13 | (() => U extends Left ? 1 : 0) extends (() => U extends Right ? 1 : 0) ? true : false; 14 | 15 | export type HasDefined = Equal extends true ? false : true 16 | 17 | // If the type T accepts type "any", output type Y, otherwise output type N. 18 | // https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360 19 | export type IfAny = 0 extends 1 & T ? Y : N 20 | 21 | export type LooseRequired = { [P in string & keyof T]: T[P] } 22 | -------------------------------------------------------------------------------- /types/plugin.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue as _Vue } from './vue' 2 | 3 | export type PluginFunction = (Vue: typeof _Vue, options?: T) => void 4 | 5 | export interface PluginObject { 6 | install: PluginFunction 7 | [key: string]: any 8 | } 9 | -------------------------------------------------------------------------------- /types/test/async-component-test.ts: -------------------------------------------------------------------------------- 1 | import Vue, { AsyncComponent, Component } from '../index' 2 | 3 | const a: AsyncComponent = () => ({ 4 | component: new Promise((res, rej) => { 5 | res({ template: '' }) 6 | }) 7 | }) 8 | 9 | const b: AsyncComponent = () => ({ 10 | // @ts-expect-error component has to be a Promise that resolves to a component 11 | component: () => 12 | new Promise((res, rej) => { 13 | res({ template: '' }) 14 | }) 15 | }) 16 | 17 | const c: AsyncComponent = () => 18 | new Promise((res, rej) => { 19 | res({ 20 | template: '' 21 | }) 22 | }) 23 | 24 | const d: AsyncComponent = () => 25 | new Promise<{ default: Component }>((res, rej) => { 26 | res({ 27 | default: { 28 | template: '' 29 | } 30 | }) 31 | }) 32 | 33 | const e: AsyncComponent = () => ({ 34 | component: new Promise<{ default: Component }>((res, rej) => { 35 | res({ 36 | default: { 37 | template: '' 38 | } 39 | }) 40 | }) 41 | }) 42 | 43 | // Test that Vue.component accepts any AsyncComponent 44 | Vue.component('async-component1', a) 45 | -------------------------------------------------------------------------------- /types/test/augmentation-test.ts: -------------------------------------------------------------------------------- 1 | import Vue, { defineComponent } from '../index' 2 | 3 | declare module '../vue' { 4 | // add instance property and method 5 | interface Vue { 6 | $instanceProperty: string 7 | $instanceMethod(): void 8 | } 9 | 10 | // add static property and method 11 | interface VueConstructor { 12 | staticProperty: string 13 | staticMethod(): void 14 | } 15 | } 16 | 17 | // augment ComponentOptions 18 | declare module '../options' { 19 | interface ComponentOptions { 20 | foo?: string 21 | } 22 | } 23 | 24 | const vm = new Vue({ 25 | props: ['bar'], 26 | data: { 27 | a: true 28 | }, 29 | foo: 'foo', 30 | methods: { 31 | foo() { 32 | this.a = false 33 | } 34 | }, 35 | computed: { 36 | BAR(): string { 37 | return this.bar.toUpperCase() 38 | } 39 | } 40 | }) 41 | 42 | vm.$instanceProperty 43 | vm.$instanceMethod() 44 | 45 | Vue.staticProperty 46 | Vue.staticMethod() 47 | 48 | defineComponent({ 49 | mounted() { 50 | this.$instanceMethod 51 | this.$instanceProperty 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /types/test/es-module.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /types/test/plugin-test.ts: -------------------------------------------------------------------------------- 1 | import Vue from '../index' 2 | import { PluginFunction, PluginObject } from '../index' 3 | 4 | class Option { 5 | prefix: string = '' 6 | suffix: string = '' 7 | } 8 | 9 | const plugin: PluginObject