diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 0b3181bca7d..00000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,62 +0,0 @@ -version: 2 - -defaults: &defaults - docker: - - image: vuejs/ci - -step_restore_cache: &restore_cache - restore_cache: - keys: - - v1-dependencies-{{ checksum "yarn.lock" }}-1 - - v1-dependencies- - -step_install_deps: &install_deps - run: - name: Install Dependencies - command: yarn --frozen-lockfile - -step_save_cache: &save_cache - save_cache: - paths: - - node_modules - - packages/compiler-core/node_modules - - packages/compiler-sfc/node_modules - - packages/vue/node_modules - - ~/.cache/yarn - key: v1-dependencies-{{ checksum "yarn.lock" }}-1 - -jobs: - test: - <<: *defaults - steps: - - checkout - - *restore_cache - - *install_deps - - *save_cache - - run: yarn test --ci --runInBand - - test-dts: - <<: *defaults - steps: - - checkout - - *restore_cache - - *install_deps - - *save_cache - - run: yarn test-dts - - check-size: - <<: *defaults - steps: - - checkout - - *restore_cache - - *install_deps - - *save_cache - - run: yarn size - -workflows: - version: 2 - ci: - jobs: - - test - - test-dts - - check-size diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..d06b03a3f89 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# update prettier & eslint config (#9162) +bfe6b459d3a0ce6168611ee1ac7e6e789709df9d diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..9288efdb9ac --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: yyx990803 +open_collective: vuejs diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000000..95e0ca79c07 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,74 @@ +name: "\U0001F41E Bug report" +description: Create a report to help us improve +body: + - type: markdown + attributes: + value: | + **Before You Start...** + + This form is only for submitting bug reports. If you have a usage question + or are unsure if this is really a bug, make sure to: + + - Read the [docs](https://vuejs.org/) + - Ask on [Discord Chat](https://chat.vuejs.org/) + - Ask on [GitHub Discussions](https://github.com/vuejs/core/discussions) + - Look for / ask questions on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=vue.js) + + Also try to search for your issue - it may have already been answered or even fixed in the development branch. + However, if you find that an old, closed issue still persists in the latest version, + you should open a new issue using the form below instead of commenting on the old issue. + - type: input + id: version + attributes: + label: Vue version + validations: + required: true + - type: input + id: reproduction-link + attributes: + label: Link to minimal reproduction + description: | + The easiest way to provide a reproduction is by showing the bug in [The SFC Playground](https://play.vuejs.org/). + If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue). + If neither of these are suitable, you can always provide a GitHub repository. + + The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed + to show the bug. See [Bug Reproduction Guidelines](https://github.com/vuejs/core/blob/main/.github/bug-repro-guidelines.md) for more details. + + Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided. + placeholder: Reproduction Link + validations: + required: true + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to reproduce + description: | + What do we need to do after opening your repro in order to make the bug happen? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code. + placeholder: Steps to reproduce + validations: + required: true + - type: textarea + id: expected + attributes: + label: What is expected? + validations: + required: true + - type: textarea + id: actually-happening + attributes: + label: What is actually happening? + validations: + required: true + - type: textarea + id: system-info + attributes: + label: System Info + description: Output of `npx envinfo --system --npmPackages vue --binaries --browsers` + render: shell + placeholder: System, Binaries, Browsers + - type: textarea + id: additional-comments + attributes: + label: Any additional comments? + description: e.g. some background/context of how you ran into this bug. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..02f99c6bfbb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,17 @@ +blank_issues_enabled: false +contact_links: + - name: Feature Request + url: https://github.com/vuejs/rfcs/discussions + about: Suggest new features for consideration + - name: Discord Chat + url: https://chat.vuejs.org + about: Ask questions and discuss with other Vue users in real time. + - name: Questions & Discussions + url: https://github.com/vuejs/core/discussions + about: Use GitHub discussions for message-board style questions and discussions. + - name: Patreon + url: https://www.patreon.com/evanyou + about: Love Vue.js? Please consider supporting us via Patreon. + - name: Open Collective + url: https://opencollective.com/vuejs/donate + about: Love Vue.js? Please consider supporting us via Open Collective. diff --git a/.github/bug-repro-guidelines.md b/.github/bug-repro-guidelines.md new file mode 100644 index 00000000000..90458b30741 --- /dev/null +++ b/.github/bug-repro-guidelines.md @@ -0,0 +1,29 @@ +## About Bug Reproductions + +A bug reproduction is a piece of code that can run and demonstrate how a bug can happen. + +### Text is not enough + +It's impossible to fix a bug from mere text descriptions. First, it's very difficult to precisely describe a technical problem while keeping it easy to follow; Second, the real cause may very well be something that you forgot to even mention. A reproduction is the only way that can reliably help us understand what is going on, so please provide one. + +### A repro must be runnable + +Screenshots or videos are NOT reproductions! They only show that the bug exists, but do not provide enough information on why it happens. Only runnable code provides the most complete context and allows us to properly debug the scenario. That said, in some cases videos/gifs can help explain interaction issues that are hard to describe in text. + +### A repro should be minimal + +Some users would give us a link to a real project and hope we can help them figure out what is wrong. We generally do not accept such requests because: + +You are already familiar with your codebase, but we are not. It is extremely time-consuming to hunt a bug in a big and unfamiliar codebase. + +The problematic behavior may very well be caused by your code rather than by a bug in Vue. + +A minimal reproduction means it demonstrates the bug, and the bug only. It should only contain the bare minimum amount of code that can reliably cause the bug. Try your best to get rid of anything that aren't directly related to the problem. + +### How to create a repro + +For Vue 3 core reproductions, try reproducing it in [The SFC Playground](https://play.vuejs.org/). + +If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue). + +If neither of these are suitable, you can always provide a GitHub repository. diff --git a/.github/commit-convention.md b/.github/commit-convention.md index a8522fa210a..11a64576a24 100644 --- a/.github/commit-convention.md +++ b/.github/commit-convention.md @@ -6,7 +6,7 @@ Messages must be matched by the following regex: -``` js +```regexp /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,50}/ ``` @@ -44,7 +44,7 @@ This reverts commit 667ecc1654a317a13331b17617d973392f415f02. ### Full Message Format -A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: +A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: ``` (): @@ -74,9 +74,9 @@ The scope could be anything specifying the place of the commit change. For examp The subject contains a succinct description of the change: -* use the imperative, present tense: "change" not "changed" nor "changes" -* don't capitalize the first letter -* no dot (.) at the end +- use the imperative, present tense: "change" not "changed" nor "changes" +- don't capitalize the first letter +- no dot (.) at the end ### Body diff --git a/.github/contributing.md b/.github/contributing.md index 62572bc435f..2554582b887 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -2,10 +2,11 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before submitting your contribution, please make sure to take a moment and read through the following guidelines: -- [Code of Conduct](https://github.com/vuejs/vue/blob/dev/.github/CODE_OF_CONDUCT.md) +- [Code of Conduct](https://vuejs.org/about/coc.html) - [Issue Reporting Guidelines](#issue-reporting-guidelines) - [Pull Request Guidelines](#pull-request-guidelines) - [Development Setup](#development-setup) +- [Scripts](#scripts) - [Project Structure](#project-structure) - [Contributing Tests](#contributing-tests) - [Financial Contribution](#financial-contribution) @@ -16,140 +17,227 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before ## Pull Request Guidelines -- Checkout a topic branch from a base branch, e.g. `master`, and merge back against that branch. +### What kinds of Pull Requests are accepted? + +- Bug fix that addresses a clearly identified bug. **"Clearly identified bug"** means the bug has a proper reproduction either from a related open issue, or is included in the PR itself. Avoid submitting PRs that claim to fix something but do not sufficiently explain what is being fixed. + +- New feature that addresses a clearly explained and widely applicable use case. **"Widely applicable"** means the new feature should provide non-trivial improvements to the majority of the user base. Vue already has a large API surface so we are quite cautious about adding new features - if the use case is niche and can be addressed via userland implementations, it likely isn't suitable to go into core. + + The feature implementation should also consider the trade-off between the added complexity vs. the benefits gained. For example, if a small feature requires significant changes that spreads across the codebase, it is likely not worth it, or the approach should be reconsidered. + + If the feature has a non-trivial API surface addition, or significantly affects the way a common use case is approached by the users, it should go through a discussion first in the [RFC repo](https://github.com/vuejs/rfcs/discussions). PRs of such features without prior discussion make it really difficult to steer / adjust the API design due to coupling with concrete implementations, and can lead to wasted work. + +- Chore: typos, comment improvements, build config, CI config, etc. For typos and comment changes, try to combine multiple of them into a single PR. + +- **It should be noted that we discourage contributors from submitting code refactors that are largely stylistic.** Code refactors are only accepted if it improves performance, or comes with sufficient explanations on why it objectively improves the code quality (e.g. makes a related feature implementation easier). + + The reason is that code readability is subjective. The maintainers of this project have chosen to write the code in its current style based on our preferences, and we do not want to spend time explaining our stylistic preferences. Contributors should just respect the established conventions when contributing code. + + Another aspect of it is that large scale stylistic changes result in massive diffs that touch multiple files, adding noise to the git history and makes tracing behavior changes across commits more cumbersome. + +### Pull Request Checklist + +- Vue core has two primary work branches: `main` and `minor`. + + - If your pull request is a feature that adds new API surface, it should be submitted against the `minor` branch. + + - Otherwise, it should be submitted against the `main` branch. + +- [Make sure to tick the "Allow edits from maintainers" box](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). This allows us to directly make minor edits / refactors and saves a lot of time. - If adding a new feature: + - Add accompanying test case. - Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it. -- If fixing bug: +- If fixing a bug: + - If you are resolving a special issue, add `(fix #xxxx[,#xxxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update entities encoding/decoding (fix #3899)`. - Provide a detailed description of the bug in the PR. Live demo preferred. - - Add appropriate test coverage if applicable. You can check the coverage of your code addition by running `yarn test --coverage`. + - Add appropriate test coverage if applicable. You can check the coverage of your code addition by running `nr test-coverage`. - It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging. - Make sure tests pass! -- Commit messages must follow the [commit message convention](./commit-convention.md) so that changelogs can be automatically generated. Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [yorkie](https://github.com/yyx990803/yorkie)). +- Commit messages must follow the [commit message convention](./commit-convention.md) so that changelogs can be automatically generated. Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)). + +- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)). + +### Advanced Pull Request Tips + +- The PR should fix the intended bug **only** and not introduce unrelated changes. This includes unnecessary refactors - a PR should focus on the fix and not code style, this makes it easier to trace changes in the future. + +- Consider the performance / size impact of the changes, and whether the bug being fixes justifies the cost. If the bug being fixed is a very niche edge case, we should try to minimize the size / perf cost to make it worthwhile. + + - Is the code perf-sensitive (e.g. in "hot paths" like component updates or the vdom patch function?) -- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [yorkie](https://github.com/yyx990803/yorkie)). + - If the branch is dev-only, performance is less of a concern. + + - Check how much extra bundle size the change introduces. + - Make sure to put dev-only code in `__DEV__` branches so they are tree-shakable. + - Runtime code is more sensitive to size increase than compiler code. + - Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in `@vue/shared` are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`. ## Development Setup -You will need [Node.js](http://nodejs.org) **version 10+**, and [Yarn](https://yarnpkg.com/en/docs/install). +You will need [Node.js](https://nodejs.org) with minimum version as specified in the [`.node-version`](https://github.com/vuejs/core/blob/main/.node-version) file, and [PNPM](https://pnpm.io) with minimum version as specified in the [`"packageManager"` field in `package.json`](https://github.com/vuejs/core/blob/main/package.json#L4). + +We also recommend installing [@antfu/ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier. After cloning the repo, run: -``` bash -$ yarn # install the dependencies of the project +```bash +$ pnpm i # install the dependencies of the project ``` A high level overview of tools used: - [TypeScript](https://www.typescriptlang.org/) as the development language -- [Rollup](https://rollupjs.org) for bundling -- [Jest](https://jestjs.io/) for unit testing +- [Vite](https://vitejs.dev/) and [ESBuild](https://esbuild.github.io/) for development bundling +- [Rollup](https://rollupjs.org) for production bundling +- [Vitest](https://vitest.dev/) for unit testing - [Prettier](https://prettier.io/) for code formatting +- [ESLint](https://eslint.org/) for static error prevention (outside of types) + +## Git Hooks + +The project uses [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks) to enforce the following on each commit: + +- Type check the entire project +- Automatically format changed files using Prettier +- Verify commit message format (logic in `scripts/verify-commit.js`) ## Scripts -### `yarn build` +**The examples below will be using the `nr` command from the [@antfu/ni](https://github.com/antfu/ni) package.** You can also use plain `npm run`, but you will need to pass all additional arguments after the command after an extra `--`. For example, `nr build runtime --all` is equivalent to `npm run build -- runtime --all`. + +The `run-s` and `run-p` commands found in some scripts are from [npm-run-all](https://github.com/mysticatea/npm-run-all) for orchestrating multiple scripts. `run-s` means "run in sequence" while `run-p` means "run in parallel". + +- [`nr build`](#nr-build) +- [`nr build-dts`](#nr-build-dts) +- [`nr check`](#nr-check) +- [`nr dev`](#nr-dev) +- [`nr dev-sfc`](#nr-dev-sfc) +- [`nr dev-esm`](#nr-dev-esm) +- [`nr dev-compiler`](#nr-dev-compiler) +- [`nr test`](#nr-test) +- [`nr test-dts`](#nr-test-dts) + +### `nr build` The `build` script builds all public packages (packages without `private: true` in their `package.json`). Packages to build can be specified with fuzzy matching: -``` bash +```bash # build runtime-core only -yarn build runtime-core +nr build runtime-core # build all packages matching "runtime" -yarn build runtime --all +nr build runtime --all ``` +Note that `nr build` uses `rollup-plugin-esbuild` for transpiling typescript and **does not perform type checking**. To run type check on the entire codebase, run `nr check`. Type checks are also automatically run on each commit. + #### Build Formats By default, each package will be built in multiple distribution formats as specified in the `buildOptions.formats` field in its `package.json`. These can be overwritten via the `-f` flag. The following formats are supported: -- **`global`**: - - For direct use via ` + + + +
+ + diff --git a/packages-private/sfc-playground/package.json b/packages-private/sfc-playground/package.json new file mode 100644 index 00000000000..55017ad57a6 --- /dev/null +++ b/packages-private/sfc-playground/package.json @@ -0,0 +1,21 @@ +{ + "name": "@vue/sfc-playground", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@vitejs/plugin-vue": "catalog:", + "vite": "catalog:" + }, + "dependencies": { + "@vue/repl": "^4.6.3", + "file-saver": "^2.0.5", + "jszip": "^3.10.1", + "vue": "workspace:*" + } +} diff --git a/packages-private/sfc-playground/public/logo.svg b/packages-private/sfc-playground/public/logo.svg new file mode 100644 index 00000000000..71c1cfb9afa --- /dev/null +++ b/packages-private/sfc-playground/public/logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages-private/sfc-playground/src/App.vue b/packages-private/sfc-playground/src/App.vue new file mode 100644 index 00000000000..c0246a7ccf4 --- /dev/null +++ b/packages-private/sfc-playground/src/App.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/packages-private/sfc-playground/src/Header.vue b/packages-private/sfc-playground/src/Header.vue new file mode 100644 index 00000000000..bf1c9bad6eb --- /dev/null +++ b/packages-private/sfc-playground/src/Header.vue @@ -0,0 +1,318 @@ + + + + + diff --git a/packages-private/sfc-playground/src/VersionSelect.vue b/packages-private/sfc-playground/src/VersionSelect.vue new file mode 100644 index 00000000000..3a30e497f97 --- /dev/null +++ b/packages-private/sfc-playground/src/VersionSelect.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/packages-private/sfc-playground/src/download/download.ts b/packages-private/sfc-playground/src/download/download.ts new file mode 100644 index 00000000000..6b051abae19 --- /dev/null +++ b/packages-private/sfc-playground/src/download/download.ts @@ -0,0 +1,42 @@ +import { saveAs } from 'file-saver' + +import index from './template/index.html?raw' +import main from './template/main.js?raw' +import pkg from './template/package.json?raw' +import config from './template/vite.config.js?raw' +import readme from './template/README.md?raw' +import type { ReplStore } from '@vue/repl' + +export async function downloadProject(store: ReplStore) { + if (!confirm('Download project files?')) { + return + } + + const { default: JSZip } = await import('jszip') + const zip = new JSZip() + + // basic structure + zip.file('index.html', index) + zip.file( + 'package.json', + pkg.replace(`"vue": "latest"`, `"vue": "${store.vueVersion || 'latest'}"`), + ) + zip.file('vite.config.js', config) + zip.file('README.md', readme) + + // project src + const src = zip.folder('src')! + src.file('main.js', main) + + const files = store.getFiles() + for (const file in files) { + if (file !== 'import-map.json' && file !== 'tsconfig.json') { + src.file(file, files[file]) + } else { + zip.file(file, files[file]) + } + } + + const blob = await zip.generateAsync({ type: 'blob' }) + saveAs(blob, 'vue-project.zip') +} diff --git a/packages-private/sfc-playground/src/download/template/README.md b/packages-private/sfc-playground/src/download/template/README.md new file mode 100644 index 00000000000..91b21489fd5 --- /dev/null +++ b/packages-private/sfc-playground/src/download/template/README.md @@ -0,0 +1,18 @@ +# Vite Vue Starter + +This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) version 18+ or 20+. + +To start: + +```sh +npm install +npm run dev + +# if using yarn: +yarn +yarn dev + +# if using pnpm: +pnpm install +pnpm run dev +``` diff --git a/packages-private/sfc-playground/src/download/template/index.html b/packages-private/sfc-playground/src/download/template/index.html new file mode 100644 index 00000000000..e631329c1b0 --- /dev/null +++ b/packages-private/sfc-playground/src/download/template/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/packages-private/sfc-playground/src/download/template/main.js b/packages-private/sfc-playground/src/download/template/main.js new file mode 100644 index 00000000000..01433bca2ac --- /dev/null +++ b/packages-private/sfc-playground/src/download/template/main.js @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/packages-private/sfc-playground/src/download/template/package.json b/packages-private/sfc-playground/src/download/template/package.json new file mode 100644 index 00000000000..8ae34af3f4b --- /dev/null +++ b/packages-private/sfc-playground/src/download/template/package.json @@ -0,0 +1,17 @@ +{ + "name": "vite-vue-starter", + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "dependencies": { + "vue": "latest" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.1", + "vite": "^7.1.3" + } +} diff --git a/packages-private/sfc-playground/src/download/template/vite.config.js b/packages-private/sfc-playground/src/download/template/vite.config.js new file mode 100644 index 00000000000..05c17402a4a --- /dev/null +++ b/packages-private/sfc-playground/src/download/template/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/packages-private/sfc-playground/src/icons/Copy.vue b/packages-private/sfc-playground/src/icons/Copy.vue new file mode 100644 index 00000000000..f3851da63cc --- /dev/null +++ b/packages-private/sfc-playground/src/icons/Copy.vue @@ -0,0 +1,14 @@ + diff --git a/packages-private/sfc-playground/src/icons/Download.vue b/packages-private/sfc-playground/src/icons/Download.vue new file mode 100644 index 00000000000..c5caec7098c --- /dev/null +++ b/packages-private/sfc-playground/src/icons/Download.vue @@ -0,0 +1,29 @@ + diff --git a/packages-private/sfc-playground/src/icons/GitHub.vue b/packages-private/sfc-playground/src/icons/GitHub.vue new file mode 100644 index 00000000000..2a6aaf62dd2 --- /dev/null +++ b/packages-private/sfc-playground/src/icons/GitHub.vue @@ -0,0 +1,7 @@ + diff --git a/packages-private/sfc-playground/src/icons/Moon.vue b/packages-private/sfc-playground/src/icons/Moon.vue new file mode 100644 index 00000000000..21f393d4d6e --- /dev/null +++ b/packages-private/sfc-playground/src/icons/Moon.vue @@ -0,0 +1,8 @@ + diff --git a/packages-private/sfc-playground/src/icons/Reload.vue b/packages-private/sfc-playground/src/icons/Reload.vue new file mode 100644 index 00000000000..5ec5be80889 --- /dev/null +++ b/packages-private/sfc-playground/src/icons/Reload.vue @@ -0,0 +1,14 @@ + diff --git a/packages-private/sfc-playground/src/icons/Share.vue b/packages-private/sfc-playground/src/icons/Share.vue new file mode 100644 index 00000000000..8dd9ccd84b3 --- /dev/null +++ b/packages-private/sfc-playground/src/icons/Share.vue @@ -0,0 +1,17 @@ + diff --git a/packages-private/sfc-playground/src/icons/Sun.vue b/packages-private/sfc-playground/src/icons/Sun.vue new file mode 100644 index 00000000000..4b73922421a --- /dev/null +++ b/packages-private/sfc-playground/src/icons/Sun.vue @@ -0,0 +1,40 @@ + diff --git a/packages-private/sfc-playground/src/main.ts b/packages-private/sfc-playground/src/main.ts new file mode 100644 index 00000000000..7be63408035 --- /dev/null +++ b/packages-private/sfc-playground/src/main.ts @@ -0,0 +1,9 @@ +import { createApp } from 'vue' +import App from './App.vue' + +// @ts-expect-error Custom window property +window.VUE_DEVTOOLS_CONFIG = { + defaultSelectedAppId: 'repl', +} + +createApp(App).mount('#app') diff --git a/packages-private/sfc-playground/src/vue-dev-proxy-prod.ts b/packages-private/sfc-playground/src/vue-dev-proxy-prod.ts new file mode 100644 index 00000000000..3b2faf19533 --- /dev/null +++ b/packages-private/sfc-playground/src/vue-dev-proxy-prod.ts @@ -0,0 +1,2 @@ +// serve vue to the iframe sandbox during dev. +export * from 'vue/dist/vue.runtime.esm-browser.prod.js' diff --git a/packages-private/sfc-playground/src/vue-dev-proxy.ts b/packages-private/sfc-playground/src/vue-dev-proxy.ts new file mode 100644 index 00000000000..f254416d4fd --- /dev/null +++ b/packages-private/sfc-playground/src/vue-dev-proxy.ts @@ -0,0 +1,2 @@ +// serve vue to the iframe sandbox during dev. +export * from 'vue' diff --git a/packages-private/sfc-playground/src/vue-server-renderer-dev-proxy.ts b/packages-private/sfc-playground/src/vue-server-renderer-dev-proxy.ts new file mode 100644 index 00000000000..f2ceb265609 --- /dev/null +++ b/packages-private/sfc-playground/src/vue-server-renderer-dev-proxy.ts @@ -0,0 +1,2 @@ +// serve vue/server-renderer to the iframe sandbox during dev. +export * from 'vue/server-renderer' diff --git a/packages-private/sfc-playground/vercel.json b/packages-private/sfc-playground/vercel.json new file mode 100644 index 00000000000..4511eb79d49 --- /dev/null +++ b/packages-private/sfc-playground/vercel.json @@ -0,0 +1,16 @@ +{ + "github": { + "silent": true + }, + "headers": [ + { + "source": "/assets/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "max-age=31536000, immutable" + } + ] + } + ] +} diff --git a/packages-private/sfc-playground/vite.config.ts b/packages-private/sfc-playground/vite.config.ts new file mode 100644 index 00000000000..2e77f1970a7 --- /dev/null +++ b/packages-private/sfc-playground/vite.config.ts @@ -0,0 +1,59 @@ +import fs from 'node:fs' +import path from 'node:path' +import { type Plugin, defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { spawnSync } from 'node:child_process' + +const commit = spawnSync('git', ['rev-parse', '--short=7', 'HEAD']) + .stdout.toString() + .trim() + +export default defineConfig({ + plugins: [ + vue({ + script: { + fs: { + fileExists: fs.existsSync, + readFile: file => fs.readFileSync(file, 'utf-8'), + }, + }, + }), + copyVuePlugin(), + ], + define: { + __COMMIT__: JSON.stringify(commit), + __VUE_PROD_DEVTOOLS__: JSON.stringify(true), + }, + optimizeDeps: { + exclude: ['@vue/repl'], + }, +}) + +function copyVuePlugin(): Plugin { + return { + name: 'copy-vue', + generateBundle() { + const copyFile = (file: string) => { + const filePath = path.resolve(__dirname, '../../packages', file) + const basename = path.basename(file) + if (!fs.existsSync(filePath)) { + throw new Error( + `${basename} not built. ` + + `Run "nr build vue -f esm-browser" first.`, + ) + } + this.emitFile({ + type: 'asset', + fileName: basename, + source: fs.readFileSync(filePath, 'utf-8'), + }) + } + + copyFile(`vue/dist/vue.esm-browser.js`) + copyFile(`vue/dist/vue.esm-browser.prod.js`) + copyFile(`vue/dist/vue.runtime.esm-browser.js`) + copyFile(`vue/dist/vue.runtime.esm-browser.prod.js`) + copyFile(`server-renderer/dist/server-renderer.esm-browser.js`) + }, + } +} diff --git a/packages-private/template-explorer/README.md b/packages-private/template-explorer/README.md new file mode 100644 index 00000000000..48d12571343 --- /dev/null +++ b/packages-private/template-explorer/README.md @@ -0,0 +1,10 @@ +## Template Explorer + +Live explorer for template compilation output. + +To run: + +- `npm run dev-compiler` in repo root +- When the compilation is done, in another terminal run `npm run open` + +Note: `index.html` uses CDN for dependencies and is continuously deployed at [https://template-explorer.vuejs.org](https://template-explorer.vuejs.org). For local development, use the scripts above. diff --git a/packages-private/template-explorer/_redirects b/packages-private/template-explorer/_redirects new file mode 100644 index 00000000000..9d570fb8259 --- /dev/null +++ b/packages-private/template-explorer/_redirects @@ -0,0 +1,2 @@ +https://vue-next-template-explorer.netlify.app https://template-explorer.vuejs.org 301! +https://vue-next-template-explorer.netlify.app/* https://template-explorer.vuejs.org/:splat 301! diff --git a/packages-private/template-explorer/index.html b/packages-private/template-explorer/index.html new file mode 100644 index 00000000000..d1db969f01b --- /dev/null +++ b/packages-private/template-explorer/index.html @@ -0,0 +1,24 @@ +Vue Template Explorer + + + + +
+
+ + + + + diff --git a/packages-private/template-explorer/local.html b/packages-private/template-explorer/local.html new file mode 100644 index 00000000000..c86cdb6b34c --- /dev/null +++ b/packages-private/template-explorer/local.html @@ -0,0 +1,24 @@ +Vue Template Explorer + + + + +
+
+ + + + + diff --git a/packages/template-explorer/package.json b/packages-private/template-explorer/package.json similarity index 66% rename from packages/template-explorer/package.json rename to packages-private/template-explorer/package.json index fb726f37c2a..08da34b173e 100644 --- a/packages/template-explorer/package.json +++ b/packages-private/template-explorer/package.json @@ -1,15 +1,17 @@ { "name": "@vue/template-explorer", - "version": "3.0.0-alpha.2", "private": true, + "version": "0.0.0", "buildOptions": { "formats": [ "global" ], + "compat": true, "env": "development", "enableNonBrowserBranches": true }, "dependencies": { - "monaco-editor": "^0.18.1" + "monaco-editor": "^0.52.2", + "source-map-js": "^1.2.1" } } diff --git a/packages/template-explorer/src/index.ts b/packages-private/template-explorer/src/index.ts similarity index 55% rename from packages/template-explorer/src/index.ts rename to packages-private/template-explorer/src/index.ts index 02b4d2fe80b..988712d623c 100644 --- a/packages/template-explorer/src/index.ts +++ b/packages-private/template-explorer/src/index.ts @@ -1,8 +1,19 @@ -import * as m from 'monaco-editor' -import { compile, CompilerError } from '@vue/compiler-dom' -import { compilerOptions, initOptions } from './options' -import { watch } from '@vue/runtime-dom' -import { SourceMapConsumer } from 'source-map' +import type * as m from 'monaco-editor' +import { + type CompilerError, + type CompilerOptions, + compile, +} from '@vue/compiler-dom' +import { compile as ssrCompile } from '@vue/compiler-ssr' +import { + compilerOptions, + defaultOptions, + initOptions, + ssrMode, +} from './options' +import { toRaw, watchEffect } from '@vue/runtime-dom' +import { SourceMapConsumer } from 'source-map-js' +import theme from './theme' declare global { interface Window { @@ -12,40 +23,83 @@ declare global { } } +interface PersistedState { + src: string + ssr: boolean + options: CompilerOptions +} + +const sharedEditorOptions: m.editor.IStandaloneEditorConstructionOptions = { + fontSize: 14, + scrollBeyondLastLine: false, + renderWhitespace: 'selection', + minimap: { + enabled: false, + }, +} + window.init = () => { const monaco = window.monaco - const persistedState = JSON.parse( - decodeURIComponent(window.location.hash.slice(1)) || - localStorage.getItem('state') || - `{}` - ) - Object.assign(compilerOptions, persistedState.options) + monaco.editor.defineTheme('my-theme', theme) + monaco.editor.setTheme('my-theme') + + let persistedState: PersistedState | undefined + + try { + let hash = window.location.hash.slice(1) + try { + hash = escape(atob(hash)) + } catch (e) {} + persistedState = JSON.parse( + decodeURIComponent(hash) || localStorage.getItem('state') || `{}`, + ) + } catch (e: any) { + // bad stored state, clear it + console.warn( + 'Persisted state in localStorage seems to be corrupted, please reload.\n' + + e.message, + ) + localStorage.clear() + } + + if (persistedState) { + // functions are not persistable, so delete it in case we sometimes need + // to debug with custom nodeTransforms + delete persistedState.options?.nodeTransforms + ssrMode.value = persistedState.ssr + Object.assign(compilerOptions, persistedState.options) + } - let lastSuccessfulCode: string = `/* See console for error */` + let lastSuccessfulCode: string let lastSuccessfulMap: SourceMapConsumer | undefined = undefined function compileCode(source: string): string { console.clear() try { const errors: CompilerError[] = [] - const { code, ast, map } = compile(source, { - filename: 'template.vue', + const compileFn = ssrMode.value ? ssrCompile : compile + const start = performance.now() + const { code, ast, map } = compileFn(source, { ...compilerOptions, + filename: 'ExampleTemplate.vue', sourceMap: true, onError: err => { errors.push(err) - } + }, }) + console.log(`Compiled in ${(performance.now() - start).toFixed(2)}ms.`) monaco.editor.setModelMarkers( editor.getModel()!, `@vue/compiler-dom`, - errors.filter(e => e.loc).map(formatError) + errors.filter(e => e.loc).map(formatError), ) console.log(`AST: `, ast) + console.log(`Options: `, toRaw(compilerOptions)) lastSuccessfulCode = code + `\n\n// Check the console for the AST` - lastSuccessfulMap = new window._deps['source-map'].SourceMapConsumer(map) + lastSuccessfulMap = new SourceMapConsumer(map!) lastSuccessfulMap!.computeColumnSpans() - } catch (e) { + } catch (e: any) { + lastSuccessfulCode = `/* ERROR: ${e.message} (see console for more info) */` console.error(e) } return lastSuccessfulCode @@ -60,55 +114,56 @@ window.init = () => { endLineNumber: loc.end.line, endColumn: loc.end.column, message: `Vue template compilation error: ${err.message}`, - code: String(err.code) + code: String(err.code), } } function reCompile() { const src = editor.getValue() // every time we re-compile, persist current state + + const optionsToSave = {} + let key: keyof CompilerOptions + for (key in compilerOptions) { + const val = compilerOptions[key] + if (typeof val !== 'object' && val !== defaultOptions[key]) { + // @ts-expect-error + optionsToSave[key] = val + } + } + const state = JSON.stringify({ src, - options: compilerOptions - }) + ssr: ssrMode.value, + options: optionsToSave, + } as PersistedState) localStorage.setItem('state', state) - window.location.hash = encodeURIComponent(state) + window.location.hash = btoa(unescape(encodeURIComponent(state))) const res = compileCode(src) if (res) { output.setValue(res) } } - const sharedEditorOptions: m.editor.IEditorConstructionOptions = { - theme: 'vs-dark', - fontSize: 14, - wordWrap: 'on', - scrollBeyondLastLine: false, - renderWhitespace: 'selection', - contextmenu: false, - minimap: { - enabled: false - } - } - const editor = monaco.editor.create(document.getElementById('source')!, { - value: persistedState.src || `
Hello World!
`, + value: persistedState?.src || `
Hello World
`, language: 'html', - ...sharedEditorOptions + ...sharedEditorOptions, + wordWrap: 'bounded', }) editor.getModel()!.updateOptions({ - tabSize: 2 + tabSize: 2, }) const output = monaco.editor.create(document.getElementById('output')!, { value: '', language: 'javascript', readOnly: true, - ...sharedEditorOptions + ...sharedEditorOptions, }) output.getModel()!.updateOptions({ - tabSize: 2 + tabSize: 2, }) // handle resize @@ -131,9 +186,9 @@ window.init = () => { clearEditorDecos() if (lastSuccessfulMap) { const pos = lastSuccessfulMap.generatedPositionFor({ - source: 'template.vue', + source: 'ExampleTemplate.vue', line: e.position.lineNumber, - column: e.position.column - 1 + column: e.position.column - 1, }) if (pos.line != null && pos.column != null) { prevOutputDecos = output.deltaDecorations(prevOutputDecos, [ @@ -142,22 +197,22 @@ window.init = () => { pos.line, pos.column + 1, pos.line, - pos.lastColumn ? pos.lastColumn + 2 : pos.column + 2 + pos.lastColumn ? pos.lastColumn + 2 : pos.column + 2, ), options: { - inlineClassName: `highlight` - } - } + inlineClassName: `highlight`, + }, + }, ]) output.revealPositionInCenter({ lineNumber: pos.line, - column: pos.column + 1 + column: pos.column + 1, }) } else { clearOutputDecos() } } - }, 100) + }, 100), ) let previousEditorDecos: string[] = [] @@ -171,17 +226,19 @@ window.init = () => { if (lastSuccessfulMap) { const pos = lastSuccessfulMap.originalPositionFor({ line: e.position.lineNumber, - column: e.position.column - 1 + column: e.position.column - 1, }) if ( pos.line != null && pos.column != null && - !// ignore mock location - (pos.line === 1 && pos.column === 0) + !( + // ignore mock location + (pos.line === 1 && pos.column === 0) + ) ) { const translatedPos = { column: pos.column + 1, - lineNumber: pos.line + lineNumber: pos.line, } previousEditorDecos = editor.deltaDecorations(previousEditorDecos, [ { @@ -189,29 +246,29 @@ window.init = () => { pos.line, pos.column + 1, pos.line, - pos.column + 1 + pos.column + 1, ), options: { isWholeLine: true, - className: `highlight` - } - } + className: `highlight`, + }, + }, ]) editor.revealPositionInCenter(translatedPos) } else { clearEditorDecos() } } - }, 100) + }, 100), ) initOptions() - watch(reCompile) + watchEffect(reCompile) } function debounce any>( fn: T, - delay: number = 300 + delay: number = 300, ): T { let prevTimer: number | null = null return ((...args: any[]) => { @@ -222,5 +279,5 @@ function debounce any>( fn(...args) prevTimer = null }, delay) - }) as any + }) as T } diff --git a/packages-private/template-explorer/src/options.ts b/packages-private/template-explorer/src/options.ts new file mode 100644 index 00000000000..e3cc6173a8a --- /dev/null +++ b/packages-private/template-explorer/src/options.ts @@ -0,0 +1,234 @@ +import { createApp, h, reactive, ref } from 'vue' +import type { CompilerOptions } from '@vue/compiler-dom' +import { BindingTypes } from '@vue/compiler-core' + +export const ssrMode = ref(false) + +export const defaultOptions: CompilerOptions = { + mode: 'module', + filename: 'Foo.vue', + prefixIdentifiers: false, + hoistStatic: false, + cacheHandlers: false, + scopeId: null, + inline: false, + ssrCssVars: `{ color }`, + compatConfig: { MODE: 3 }, + whitespace: 'condense', + bindingMetadata: { + TestComponent: BindingTypes.SETUP_CONST, + setupRef: BindingTypes.SETUP_REF, + setupConst: BindingTypes.SETUP_CONST, + setupLet: BindingTypes.SETUP_LET, + setupMaybeRef: BindingTypes.SETUP_MAYBE_REF, + setupProp: BindingTypes.PROPS, + vMySetupDir: BindingTypes.SETUP_CONST, + }, +} + +export const compilerOptions: CompilerOptions = reactive( + Object.assign({}, defaultOptions), +) + +const App = { + setup() { + return () => { + const isSSR = ssrMode.value + const isModule = compilerOptions.mode === 'module' + const usePrefix = + compilerOptions.prefixIdentifiers || compilerOptions.mode === 'module' + + return [ + h('h1', `Vue 3 Template Explorer`), + h( + 'a', + { + href: `https://github.com/vuejs/core/tree/${__COMMIT__}`, + target: `_blank`, + }, + `@${__COMMIT__}`, + ), + ' | ', + h( + 'a', + { + href: 'https://app.netlify.com/sites/vue-next-template-explorer/deploys', + target: `_blank`, + }, + 'History', + ), + + h('div', { id: 'options-wrapper' }, [ + h('div', { id: 'options-label' }, 'Options ↘'), + h('ul', { id: 'options' }, [ + // mode selection + h('li', { id: 'mode' }, [ + h('span', { class: 'label' }, 'Mode: '), + h('input', { + type: 'radio', + id: 'mode-module', + name: 'mode', + checked: isModule, + onChange() { + compilerOptions.mode = 'module' + }, + }), + h('label', { for: 'mode-module' }, 'module'), + ' ', + h('input', { + type: 'radio', + id: 'mode-function', + name: 'mode', + checked: !isModule, + onChange() { + compilerOptions.mode = 'function' + }, + }), + h('label', { for: 'mode-function' }, 'function'), + ]), + + // whitespace handling + h('li', { id: 'whitespace' }, [ + h('span', { class: 'label' }, 'whitespace: '), + h('input', { + type: 'radio', + id: 'whitespace-condense', + name: 'whitespace', + checked: compilerOptions.whitespace === 'condense', + onChange() { + compilerOptions.whitespace = 'condense' + }, + }), + h('label', { for: 'whitespace-condense' }, 'condense'), + ' ', + h('input', { + type: 'radio', + id: 'whitespace-preserve', + name: 'whitespace', + checked: compilerOptions.whitespace === 'preserve', + onChange() { + compilerOptions.whitespace = 'preserve' + }, + }), + h('label', { for: 'whitespace-preserve' }, 'preserve'), + ]), + + // SSR + h('li', [ + h('input', { + type: 'checkbox', + id: 'ssr', + name: 'ssr', + checked: ssrMode.value, + onChange(e: Event) { + ssrMode.value = (e.target as HTMLInputElement).checked + }, + }), + h('label', { for: 'ssr' }, 'SSR'), + ]), + + // toggle prefixIdentifiers + h('li', [ + h('input', { + type: 'checkbox', + id: 'prefix', + disabled: isModule || isSSR, + checked: usePrefix || isSSR, + onChange(e: Event) { + compilerOptions.prefixIdentifiers = + (e.target as HTMLInputElement).checked || isModule + }, + }), + h('label', { for: 'prefix' }, 'prefixIdentifiers'), + ]), + + // toggle hoistStatic + h('li', [ + h('input', { + type: 'checkbox', + id: 'hoist', + checked: compilerOptions.hoistStatic && !isSSR, + disabled: isSSR, + onChange(e: Event) { + compilerOptions.hoistStatic = ( + e.target as HTMLInputElement + ).checked + }, + }), + h('label', { for: 'hoist' }, 'hoistStatic'), + ]), + + // toggle cacheHandlers + h('li', [ + h('input', { + type: 'checkbox', + id: 'cache', + checked: usePrefix && compilerOptions.cacheHandlers && !isSSR, + disabled: !usePrefix || isSSR, + onChange(e: Event) { + compilerOptions.cacheHandlers = ( + e.target as HTMLInputElement + ).checked + }, + }), + h('label', { for: 'cache' }, 'cacheHandlers'), + ]), + + // toggle scopeId + h('li', [ + h('input', { + type: 'checkbox', + id: 'scope-id', + disabled: !isModule, + checked: isModule && compilerOptions.scopeId, + onChange(e: Event) { + compilerOptions.scopeId = + isModule && (e.target as HTMLInputElement).checked + ? 'scope-id' + : null + }, + }), + h('label', { for: 'scope-id' }, 'scopeId'), + ]), + + // inline mode + h('li', [ + h('input', { + type: 'checkbox', + id: 'inline', + checked: compilerOptions.inline, + onChange(e: Event) { + compilerOptions.inline = ( + e.target as HTMLInputElement + ).checked + }, + }), + h('label', { for: 'inline' }, 'inline'), + ]), + + // compat mode + h('li', [ + h('input', { + type: 'checkbox', + id: 'compat', + checked: compilerOptions.compatConfig!.MODE === 2, + onChange(e: Event) { + compilerOptions.compatConfig!.MODE = ( + e.target as HTMLInputElement + ).checked + ? 2 + : 3 + }, + }), + h('label', { for: 'compat' }, 'v2 compat mode'), + ]), + ]), + ]), + ] + } + }, +} + +export function initOptions() { + createApp(App).mount(document.getElementById('header')!) +} diff --git a/packages-private/template-explorer/src/theme.ts b/packages-private/template-explorer/src/theme.ts new file mode 100644 index 00000000000..9027cd0c011 --- /dev/null +++ b/packages-private/template-explorer/src/theme.ts @@ -0,0 +1,244 @@ +export default { + base: 'vs-dark' as const, + inherit: true, + rules: [ + { + foreground: 'de935f', + token: 'number', + }, + { + foreground: '969896', + token: 'comment', + }, + { + foreground: 'ced1cf', + token: 'keyword.operator.class', + }, + { + foreground: 'ced1cf', + token: 'constant.other', + }, + { + foreground: 'ced1cf', + token: 'source.php.embedded.line', + }, + { + foreground: 'cc6666', + token: 'variable', + }, + { + foreground: 'cc6666', + token: 'support.other.variable', + }, + { + foreground: 'cc6666', + token: 'string.other.link', + }, + { + foreground: 'cc6666', + token: 'string.regexp', + }, + { + foreground: 'cc6666', + token: 'entity.name.tag', + }, + { + foreground: 'cc6666', + token: 'entity.other.attribute-name', + }, + { + foreground: 'cc6666', + token: 'meta.tag', + }, + { + foreground: 'cc6666', + token: 'declaration.tag', + }, + { + foreground: 'cc6666', + token: 'markup.deleted.git_gutter', + }, + { + foreground: 'de935f', + token: 'constant.numeric', + }, + { + foreground: 'de935f', + token: 'constant.language', + }, + { + foreground: 'de935f', + token: 'support.constant', + }, + { + foreground: 'de935f', + token: 'constant.character', + }, + { + foreground: 'de935f', + token: 'variable.parameter', + }, + { + foreground: 'de935f', + token: 'punctuation.section.embedded', + }, + { + foreground: 'de935f', + token: 'keyword.other.unit', + }, + { + foreground: 'f0c674', + token: 'entity.name.class', + }, + { + foreground: 'f0c674', + token: 'entity.name.type.class', + }, + { + foreground: 'f0c674', + token: 'support.type', + }, + { + foreground: 'f0c674', + token: 'support.class', + }, + { + foreground: 'b5bd68', + token: 'string', + }, + { + foreground: 'b5bd68', + token: 'constant.other.symbol', + }, + { + foreground: 'b5bd68', + token: 'entity.other.inherited-class', + }, + { + foreground: 'b5bd68', + token: 'markup.heading', + }, + { + foreground: 'b5bd68', + token: 'markup.inserted.git_gutter', + }, + { + foreground: '8abeb7', + token: 'keyword.operator', + }, + { + foreground: '8abeb7', + token: 'constant.other.color', + }, + { + foreground: '81a2be', + token: 'entity.name.function', + }, + { + foreground: '81a2be', + token: 'meta.function-call', + }, + { + foreground: '81a2be', + token: 'support.function', + }, + { + foreground: '81a2be', + token: 'keyword.other.special-method', + }, + { + foreground: '81a2be', + token: 'meta.block-level', + }, + { + foreground: '81a2be', + token: 'markup.changed.git_gutter', + }, + { + foreground: 'b294bb', + token: 'keyword', + }, + { + foreground: 'b294bb', + token: 'storage', + }, + { + foreground: 'b294bb', + token: 'storage.type', + }, + { + foreground: 'b294bb', + token: 'entity.name.tag.css', + }, + { + foreground: 'ced2cf', + background: 'df5f5f', + token: 'invalid', + }, + { + foreground: 'ced2cf', + background: '82a3bf', + token: 'meta.separator', + }, + { + foreground: 'ced2cf', + background: 'b798bf', + token: 'invalid.deprecated', + }, + { + foreground: 'ffffff', + token: 'markup.inserted.diff', + }, + { + foreground: 'ffffff', + token: 'markup.deleted.diff', + }, + { + foreground: 'ffffff', + token: 'meta.diff.header.to-file', + }, + { + foreground: 'ffffff', + token: 'meta.diff.header.from-file', + }, + { + foreground: '718c00', + token: 'markup.inserted.diff', + }, + { + foreground: '718c00', + token: 'meta.diff.header.to-file', + }, + { + foreground: 'c82829', + token: 'markup.deleted.diff', + }, + { + foreground: 'c82829', + token: 'meta.diff.header.from-file', + }, + { + foreground: 'ffffff', + background: '4271ae', + token: 'meta.diff.header.from-file', + }, + { + foreground: 'ffffff', + background: '4271ae', + token: 'meta.diff.header.to-file', + }, + { + foreground: '3e999f', + fontStyle: 'italic', + token: 'meta.diff.range', + }, + ], + colors: { + 'editor.foreground': '#C5C8C6', + 'editor.background': '#1D1F21', + 'editor.selectionBackground': '#373B41', + 'editor.lineHighlightBackground': '#282A2E', + 'editorCursor.foreground': '#AEAFAD', + 'editorWhitespace.foreground': '#4B4E55', + }, +} diff --git a/packages-private/template-explorer/style.css b/packages-private/template-explorer/style.css new file mode 100644 index 00000000000..eed9e18a009 --- /dev/null +++ b/packages-private/template-explorer/style.css @@ -0,0 +1,97 @@ +body { + margin: 0; + overflow: hidden; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, + Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --bg: #1d1f21; + --border: #333; +} + +#header { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 60px; + box-sizing: border-box; + background-color: var(--bg); + border-bottom: 1px solid var(--border); + padding: 0.3em 1.6em; + color: #fff; + z-index: 1; +} + +h1 { + font-size: 18px; + display: inline-block; + margin-right: 15px; +} + +#options-wrapper { + position: absolute; + top: 20px; + right: 10px; +} + +#options-wrapper:hover #options { + display: block; +} + +#options-label { + cursor: pointer; + text-align: right; + padding-right: 10px; + font-weight: bold; +} + +#options { + display: none; + margin-top: 15px; + list-style-type: none; + background-color: var(--bg); + border: 1px solid var(--border); + padding: 15px 30px; +} + +#options li { + margin: 8px 0; +} + +#header a { + font-weight: 600; + color: rgb(101, 163, 221); +} + +#header .label { + font-weight: bold; +} + +#header input { + margin-right: 6px; +} + +#header label { + color: #999; +} + +.editor { + position: absolute; + top: 60px; + bottom: 0; + box-sizing: border-box; +} + +#source { + left: 0; + width: 45%; +} + +#output { + left: 45%; + width: 55%; +} + +.highlight { + background-color: rgba(46, 120, 190, 0.5); +} diff --git a/packages-private/tsconfig.json b/packages-private/tsconfig.json new file mode 100644 index 00000000000..1c287a7500c --- /dev/null +++ b/packages-private/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "isolatedDeclarations": false + }, + "include": ["."] +} diff --git a/packages-private/vite-debug/App.vue b/packages-private/vite-debug/App.vue new file mode 100644 index 00000000000..95b3be8eee5 --- /dev/null +++ b/packages-private/vite-debug/App.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/packages-private/vite-debug/README.md b/packages-private/vite-debug/README.md new file mode 100644 index 00000000000..4f035ae6f8d --- /dev/null +++ b/packages-private/vite-debug/README.md @@ -0,0 +1 @@ +This package is used for debugging issues that are related to `@vitejs/plugin-vue`, or can only be reproduced in a Vite-based setup. It aims to be as close to production as possible so Vue packages are resolved to the dist files instead of source. diff --git a/packages-private/vite-debug/index.html b/packages-private/vite-debug/index.html new file mode 100644 index 00000000000..79052a023ba --- /dev/null +++ b/packages-private/vite-debug/index.html @@ -0,0 +1,2 @@ + +
diff --git a/packages-private/vite-debug/main.ts b/packages-private/vite-debug/main.ts new file mode 100644 index 00000000000..52668a0a545 --- /dev/null +++ b/packages-private/vite-debug/main.ts @@ -0,0 +1,6 @@ +import { createApp } from 'vue' +import App from './App.vue' + +const app = createApp(App) + +app.mount('#app') diff --git a/packages-private/vite-debug/package.json b/packages-private/vite-debug/package.json new file mode 100644 index 00000000000..b0f2bad2b2d --- /dev/null +++ b/packages-private/vite-debug/package.json @@ -0,0 +1,15 @@ +{ + "name": "vite-debug", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@vitejs/plugin-vue": "catalog:", + "vite": "catalog:", + "vue": "workspace:*" + } +} diff --git a/packages-private/vite-debug/tsconfig.json b/packages-private/vite-debug/tsconfig.json new file mode 100644 index 00000000000..ceecb1cde14 --- /dev/null +++ b/packages-private/vite-debug/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler" + }, + "include": ["./*"] +} diff --git a/packages-private/vite-debug/vite.config.ts b/packages-private/vite-debug/vite.config.ts new file mode 100644 index 00000000000..c40aa3c361b --- /dev/null +++ b/packages-private/vite-debug/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index 785d3be37e2..db268af4f9b 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -1,9 +1,9 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`compiler: codegen ArrayExpression 1`] = ` +exports[`compiler: codegen > ArrayExpression 1`] = ` " -return function render() { - with (this) { +return function render(_ctx, _cache) { + with (_ctx) { return [ foo, bar(baz) @@ -12,33 +12,29 @@ return function render() { }" `; -exports[`compiler: codegen CacheExpression 1`] = ` +exports[`compiler: codegen > CacheExpression 1`] = ` " -export function render() { - const _ctx = this - const _cache = _ctx.$cache +export function render(_ctx, _cache) { return _cache[1] || (_cache[1] = foo) }" `; -exports[`compiler: codegen CacheExpression w/ isVNode: true 1`] = ` +exports[`compiler: codegen > CacheExpression w/ isVOnce: true 1`] = ` " -export function render() { - const _ctx = this - const _cache = _ctx.$cache +export function render(_ctx, _cache) { return _cache[1] || ( - setBlockTracking(-1), - _cache[1] = foo, - setBlockTracking(1), + _setBlockTracking(-1), + (_cache[1] = foo).cacheIndex = 1, + _setBlockTracking(1), _cache[1] ) }" `; -exports[`compiler: codegen ConditionalExpression 1`] = ` +exports[`compiler: codegen > ConditionalExpression 1`] = ` " -return function render() { - with (this) { +return function render(_ctx, _cache) { + with (_ctx) { return ok ? foo() : orNot @@ -48,144 +44,161 @@ return function render() { }" `; -exports[`compiler: codegen Element (callExpression + objectExpression + TemplateChildNode[]) 1`] = ` +exports[`compiler: codegen > Element (callExpression + objectExpression + TemplateChildNode[]) 1`] = ` " -return function render() { - with (this) { - return _createVNode(\\"div\\", { - id: \\"foo\\", +return function render(_ctx, _cache) { + with (_ctx) { + return _createElementVNode("div", { + id: "foo", [prop]: bar, [foo + bar]: bar }, [ - _createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" }) - ], 16) + _createElementVNode("p", { "some-key": "foo" }) + ], 16 /* FULL_PROPS */) } }" `; -exports[`compiler: codegen SequenceExpression 1`] = ` +exports[`compiler: codegen > assets + temps 1`] = ` " -return function render() { - with (this) { - return (foo, bar(baz)) +return function render(_ctx, _cache) { + with (_ctx) { + const _component_Foo = _resolveComponent("Foo") + const _component_bar_baz = _resolveComponent("bar-baz") + const _component_barbaz = _resolveComponent("barbaz") + const _component_Qux = _resolveComponent("Qux", true) + const _directive_my_dir_0 = _resolveDirective("my_dir_0") + const _directive_my_dir_1 = _resolveDirective("my_dir_1") + let _temp0, _temp1, _temp2 + + return null } }" `; -exports[`compiler: codegen assets 1`] = ` +exports[`compiler: codegen > comment 1`] = ` " -return function render() { - with (this) { - const _component_Foo = _resolveComponent(\\"Foo\\") - const _component_bar_baz = _resolveComponent(\\"bar-baz\\") - const _component_barbaz = _resolveComponent(\\"barbaz\\") - const _directive_my_dir = _resolveDirective(\\"my_dir\\") - - return null +return function render(_ctx, _cache) { + with (_ctx) { + return _createCommentVNode("foo") } }" `; -exports[`compiler: codegen comment 1`] = ` +exports[`compiler: codegen > compound expression 1`] = ` " -return function render() { - with (this) { - return _createCommentVNode(\\"foo\\") +return function render(_ctx, _cache) { + with (_ctx) { + return _ctx.foo + _toDisplayString(bar) + nested } }" `; -exports[`compiler: codegen compound expression 1`] = ` +exports[`compiler: codegen > forNode 1`] = ` " -return function render() { - with (this) { - return _ctx.foo + _toString(bar) +return function render(_ctx, _cache) { + with (_ctx) { + return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(), 1 /* TEXT */)) } }" `; -exports[`compiler: codegen forNode 1`] = ` +exports[`compiler: codegen > forNode with constant expression 1`] = ` " -return function render() { - with (this) { - return (foo, bar) +return function render(_ctx, _cache) { + with (_ctx) { + return (_openBlock(), _createElementBlock(_Fragment, null, _renderList(), 64 /* STABLE_FRAGMENT */)) } }" `; -exports[`compiler: codegen function mode preamble 1`] = ` +exports[`compiler: codegen > function mode preamble 1`] = ` "const _Vue = Vue -return function render() { - with (this) { +return function render(_ctx, _cache) { + with (_ctx) { const { createVNode: _createVNode, resolveDirective: _resolveDirective } = _Vue - + return null } }" `; -exports[`compiler: codegen function mode preamble w/ prefixIdentifiers: true 1`] = ` -"const { createVNode, resolveDirective } = Vue +exports[`compiler: codegen > function mode preamble w/ prefixIdentifiers: true 1`] = ` +"const { createVNode: _createVNode, resolveDirective: _resolveDirective } = Vue -return function render() { - const _ctx = this +return function render(_ctx, _cache) { return null }" `; -exports[`compiler: codegen hoists 1`] = ` +exports[`compiler: codegen > hoists 1`] = ` " const _hoisted_1 = hello -const _hoisted_2 = { id: \\"foo\\" } +const _hoisted_2 = { id: "foo" } -return function render() { - with (this) { +return function render(_ctx, _cache) { + with (_ctx) { return null } }" `; -exports[`compiler: codegen ifNode 1`] = ` +exports[`compiler: codegen > ifNode 1`] = ` " -return function render() { - with (this) { - return (foo, bar) +return function render(_ctx, _cache) { + with (_ctx) { + return foo + ? bar + : baz } }" `; -exports[`compiler: codegen interpolation 1`] = ` +exports[`compiler: codegen > interpolation 1`] = ` " -return function render() { - with (this) { - return _toString(hello) +return function render(_ctx, _cache) { + with (_ctx) { + return _toDisplayString(hello) } }" `; -exports[`compiler: codegen module mode preamble 1`] = ` -"import { createVNode, resolveDirective } from \\"vue\\" +exports[`compiler: codegen > module mode preamble 1`] = ` +"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from "vue" -export function render() { - const _ctx = this +export function render(_ctx, _cache) { return null }" `; -exports[`compiler: codegen prefixIdentifiers: true should inject _ctx statement 1`] = ` -" -return function render() { - const _ctx = this +exports[`compiler: codegen > module mode preamble w/ optimizeImports: true 1`] = ` +"import { createVNode, resolveDirective } from "vue" + +// Binding optimization for webpack code-split +const _createVNode = createVNode, _resolveDirective = resolveDirective + +export function render(_ctx, _cache) { return null }" `; -exports[`compiler: codegen static text 1`] = ` +exports[`compiler: codegen > static text 1`] = ` " -return function render() { - with (this) { - return \\"hello\\" +return function render(_ctx, _cache) { + with (_ctx) { + return "hello" + } +}" +`; + +exports[`compiler: codegen > temps 1`] = ` +" +return function render(_ctx, _cache) { + with (_ctx) { + let _temp0, _temp1, _temp2 + + return null } }" `; diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index 55bb9f92114..625485719cb 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -1,23 +1,25 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`compiler: integration tests function mode 1`] = ` +exports[`compiler: integration tests > function mode 1`] = ` "const _Vue = Vue -return function render() { - with (this) { - const { toString: _toString, openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment, renderList: _renderList, createTextVNode: _createTextVNode } = _Vue - - return (_openBlock(), _createBlock(\\"div\\", { - id: \\"foo\\", - class: bar.baz +return function render(_ctx, _cache) { + with (_ctx) { + const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList, createElementVNode: _createElementVNode, normalizeClass: _normalizeClass } = _Vue + + return (_openBlock(), _createElementBlock("div", { + id: "foo", + class: _normalizeClass(bar.baz) }, [ - _createTextVNode(_toString(world.burn()) + \\" \\", 1 /* TEXT */), - (_openBlock(), ok - ? _createBlock(\\"div\\", { key: 0 }, \\"yes\\") - : _createBlock(_Fragment, { key: 1 }, [\\"no\\"])), - (_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (value, index) => { - return (_openBlock(), _createBlock(\\"div\\", null, [ - _createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */) + _createTextVNode(_toDisplayString(world.burn()) + " ", 1 /* TEXT */), + ok + ? (_openBlock(), _createElementBlock("div", { key: 0 }, "yes")) + : (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ + _createTextVNode("no") + ], 64 /* STABLE_FRAGMENT */)), + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (value, index) => { + return (_openBlock(), _createElementBlock("div", null, [ + _createElementVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) ])) }), 256 /* UNKEYED_FRAGMENT */)) ], 2 /* CLASS */)) @@ -25,44 +27,46 @@ return function render() { }" `; -exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = ` -"const { toString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } = Vue +exports[`compiler: integration tests > function mode w/ prefixIdentifiers: true 1`] = ` +"const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList, createElementVNode: _createElementVNode, normalizeClass: _normalizeClass } = Vue -return function render() { - const _ctx = this - return (openBlock(), createBlock(\\"div\\", { - id: \\"foo\\", - class: _ctx.bar.baz +return function render(_ctx, _cache) { + return (_openBlock(), _createElementBlock("div", { + id: "foo", + class: _normalizeClass(_ctx.bar.baz) }, [ - createTextVNode(toString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */), - (openBlock(), (_ctx.ok) - ? createBlock(\\"div\\", { key: 0 }, \\"yes\\") - : createBlock(Fragment, { key: 1 }, [\\"no\\"])), - (openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => { - return (openBlock(), createBlock(\\"div\\", null, [ - createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */) + _createTextVNode(_toDisplayString(_ctx.world.burn()) + " ", 1 /* TEXT */), + (_ctx.ok) + ? (_openBlock(), _createElementBlock("div", { key: 0 }, "yes")) + : (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ + _createTextVNode("no") + ], 64 /* STABLE_FRAGMENT */)), + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => { + return (_openBlock(), _createElementBlock("div", null, [ + _createElementVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) ])) }), 256 /* UNKEYED_FRAGMENT */)) ], 2 /* CLASS */)) }" `; -exports[`compiler: integration tests module mode 1`] = ` -"import { toString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } from \\"vue\\" +exports[`compiler: integration tests > module mode 1`] = ` +"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createTextVNode as _createTextVNode, Fragment as _Fragment, renderList as _renderList, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass } from "vue" -export function render() { - const _ctx = this - return (openBlock(), createBlock(\\"div\\", { - id: \\"foo\\", - class: _ctx.bar.baz +export function render(_ctx, _cache) { + return (_openBlock(), _createElementBlock("div", { + id: "foo", + class: _normalizeClass(_ctx.bar.baz) }, [ - createTextVNode(toString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */), - (openBlock(), (_ctx.ok) - ? createBlock(\\"div\\", { key: 0 }, \\"yes\\") - : createBlock(Fragment, { key: 1 }, [\\"no\\"])), - (openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => { - return (openBlock(), createBlock(\\"div\\", null, [ - createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */) + _createTextVNode(_toDisplayString(_ctx.world.burn()) + " ", 1 /* TEXT */), + (_ctx.ok) + ? (_openBlock(), _createElementBlock("div", { key: 0 }, "yes")) + : (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ + _createTextVNode("no") + ], 64 /* STABLE_FRAGMENT */)), + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => { + return (_openBlock(), _createElementBlock("div", null, [ + _createElementVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) ])) }), 256 /* UNKEYED_FRAGMENT */)) ], 2 /* CLASS */)) diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap index d2ec37c611b..942eed4c4dc 100644 --- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap @@ -1,2477 +1,2345 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`compiler: parse Errors ABRUPT_CLOSING_OF_EMPTY_COMMENT 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "content": "", - "loc": Object { - "end": Object { - "column": 16, - "line": 1, - "offset": 15, - }, - "source": "", - "start": Object { - "column": 11, - "line": 1, - "offset": 10, - }, - }, - "type": 3, - }, - ], - "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 27, - "line": 1, - "offset": 26, - }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "ns": 0, - "props": Array [], - "tag": "template", - "tagType": 3, - "type": 1, - }, - ], - "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 27, - "line": 1, - "offset": 26, - }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "type": 0, -} -`; - -exports[`compiler: parse Errors ABRUPT_CLOSING_OF_EMPTY_COMMENT 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "content": "", - "loc": Object { - "end": Object { - "column": 17, - "line": 1, - "offset": 16, +exports[`compiler: parse > Edge Cases > invalid html 1`] = ` +{ + "cached": [], + "children": [ + { + "children": [ + { + "children": [], + "codegenNode": undefined, + "loc": { + "end": { + "column": 1, + "line": 3, + "offset": 13, }, - "source": "", - "start": Object { - "column": 11, - "line": 1, - "offset": 10, + "source": " +", + "start": { + "column": 1, + "line": 2, + "offset": 6, }, }, - "type": 3, + "ns": 0, + "props": [], + "tag": "span", + "tagType": 0, + "type": 1, }, ], "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 28, - "line": 1, - "offset": 27, + "loc": { + "end": { + "column": 7, + "line": 3, + "offset": 19, }, - "source": "", - "start": Object { + "source": "
+ +
", + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], - "tag": "template", - "tagType": 3, + "props": [], + "tag": "div", + "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 28, - "line": 1, + "components": [], + "directives": [], + "helpers": Set {}, + "hoists": [], + "imports": [], + "loc": { + "end": { + "column": 8, + "line": 4, "offset": 27, }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "type": 0, -} -`; - -exports[`compiler: parse Errors ABRUPT_CLOSING_OF_EMPTY_COMMENT 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "content": "", - "loc": Object { - "end": Object { - "column": 18, - "line": 1, - "offset": 17, - }, - "source": "", - "start": Object { - "column": 11, - "line": 1, - "offset": 10, - }, - }, - "type": 3, - }, - ], - "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 29, - "line": 1, - "offset": 28, - }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "ns": 0, - "props": Array [], - "tag": "template", - "tagType": 3, - "type": 1, - }, - ], - "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 29, - "line": 1, - "offset": 28, - }, - "source": "", - "start": Object { + "source": "
+ +
+
", + "start": { "column": 1, "line": 1, "offset": 0, }, }, + "source": "
+ +
+", + "temps": 0, "type": 0, } `; -exports[`compiler: parse Errors ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [], +exports[`compiler: parse > Edge Cases > self closing multiple tag 1`] = ` +{ + "cached": [], + "children": [ + { + "children": [], "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 35, + "isSelfClosing": true, + "loc": { + "end": { + "column": 37, "line": 1, - "offset": 34, + "offset": 36, }, - "source": "", - "start": Object { + "source": "
", + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [ - Object { - "loc": Object { - "end": Object { - "column": 23, - "line": 1, - "offset": 22, - }, - "source": "attr=\\"c\\"", - "start": Object { - "column": 11, - "line": 1, - "offset": 10, + "props": [ + { + "arg": { + "constType": 3, + "content": "class", + "isStatic": true, + "loc": { + "end": { + "column": 12, + "line": 1, + "offset": 11, + }, + "source": "class", + "start": { + "column": 7, + "line": 1, + "offset": 6, + }, }, + "type": 4, }, - "name": "attr", - "type": 6, - "value": Object { - "content": "c", - "loc": Object { - "end": Object { - "column": 23, + "exp": { + "constType": 0, + "content": "{ some: condition }", + "isStatic": false, + "loc": { + "end": { + "column": 33, "line": 1, - "offset": 22, + "offset": 32, }, - "source": "\\"c\\"", - "start": Object { - "column": 16, + "source": "{ some: condition }", + "start": { + "column": 14, "line": 1, - "offset": 15, + "offset": 13, }, }, - "type": 2, + "type": 4, + }, + "loc": { + "end": { + "column": 34, + "line": 1, + "offset": 33, + }, + "source": ":class="{ some: condition }"", + "start": { + "column": 6, + "line": 1, + "offset": 5, + }, }, + "modifiers": [], + "name": "bind", + "rawName": ":class", + "type": 7, }, ], - "tag": "template", - "tagType": 3, + "tag": "div", + "tagType": 0, "type": 1, }, - ], - "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 35, - "line": 1, - "offset": 34, - }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "type": 0, -} -`; - -exports[`compiler: parse Errors ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [], + { + "children": [], "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 34, - "line": 1, - "offset": 33, + "isSelfClosing": true, + "loc": { + "end": { + "column": 37, + "line": 2, + "offset": 73, }, - "source": "", - "start": Object { + "source": "

", + "start": { "column": 1, - "line": 1, - "offset": 0, + "line": 2, + "offset": 37, }, }, "ns": 0, - "props": Array [ - Object { - "loc": Object { - "end": Object { - "column": 22, - "line": 1, - "offset": 21, - }, - "source": "attr=\\"&#a;\\"", - "start": Object { - "column": 11, - "line": 1, - "offset": 10, + "props": [ + { + "arg": { + "constType": 3, + "content": "style", + "isStatic": true, + "loc": { + "end": { + "column": 16, + "line": 2, + "offset": 52, + }, + "source": "style", + "start": { + "column": 11, + "line": 2, + "offset": 47, + }, }, + "type": 4, }, - "name": "attr", - "type": 6, - "value": Object { - "content": "&#a;", - "loc": Object { - "end": Object { - "column": 22, - "line": 1, - "offset": 21, + "exp": { + "constType": 0, + "content": "{ color: 'red' }", + "isStatic": false, + "loc": { + "end": { + "column": 34, + "line": 2, + "offset": 70, }, - "source": "\\"&#a;\\"", - "start": Object { - "column": 16, - "line": 1, - "offset": 15, + "source": "{ color: 'red' }", + "start": { + "column": 18, + "line": 2, + "offset": 54, }, }, - "type": 2, + "type": 4, + }, + "loc": { + "end": { + "column": 35, + "line": 2, + "offset": 71, + }, + "source": "v-bind:style="{ color: 'red' }"", + "start": { + "column": 4, + "line": 2, + "offset": 40, + }, }, + "modifiers": [], + "name": "bind", + "rawName": "v-bind:style", + "type": 7, }, ], - "tag": "template", - "tagType": 3, + "tag": "p", + "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 34, - "line": 1, - "offset": 33, + "components": [], + "directives": [], + "helpers": Set {}, + "hoists": [], + "imports": [], + "loc": { + "end": { + "column": 37, + "line": 2, + "offset": 73, }, - "source": "", - "start": Object { + "source": "

+

", + "start": { "column": 1, "line": 1, "offset": 0, }, }, + "source": "

+

", + "temps": 0, "type": 0, } `; -exports[`compiler: parse Errors ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [], - "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 36, - "line": 1, - "offset": 35, - }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "ns": 0, - "props": Array [ - Object { - "loc": Object { - "end": Object { - "column": 24, - "line": 1, - "offset": 23, +exports[`compiler: parse > Edge Cases > valid html 1`] = ` +{ + "cached": [], + "children": [ + { + "children": [ + { + "children": [], + "codegenNode": undefined, + "isSelfClosing": true, + "loc": { + "end": { + "column": 39, + "line": 2, + "offset": 73, }, - "source": "attr=\\"ÿ\\"", - "start": Object { - "column": 11, - "line": 1, - "offset": 10, + "source": "

", + "start": { + "column": 3, + "line": 2, + "offset": 37, }, }, - "name": "attr", - "type": 6, - "value": Object { - "content": "ÿ", - "loc": Object { - "end": Object { - "column": 24, - "line": 1, - "offset": 23, + "ns": 0, + "props": [ + { + "arg": { + "constType": 3, + "content": "style", + "isStatic": true, + "loc": { + "end": { + "column": 18, + "line": 2, + "offset": 52, + }, + "source": "style", + "start": { + "column": 13, + "line": 2, + "offset": 47, + }, + }, + "type": 4, }, - "source": "\\"ÿ\\"", - "start": Object { - "column": 16, - "line": 1, - "offset": 15, + "exp": { + "constType": 0, + "content": "{ color: 'red' }", + "isStatic": false, + "loc": { + "end": { + "column": 36, + "line": 2, + "offset": 70, + }, + "source": "{ color: 'red' }", + "start": { + "column": 20, + "line": 2, + "offset": 54, + }, + }, + "type": 4, + }, + "loc": { + "end": { + "column": 37, + "line": 2, + "offset": 71, + }, + "source": "v-bind:style="{ color: 'red' }"", + "start": { + "column": 6, + "line": 2, + "offset": 40, + }, }, + "modifiers": [], + "name": "bind", + "rawName": "v-bind:style", + "type": 7, + }, + ], + "tag": "p", + "tagType": 0, + "type": 1, + }, + { + "content": " a comment with inside it ", + "loc": { + "end": { + "column": 43, + "line": 3, + "offset": 116, + }, + "source": "", + "start": { + "column": 3, + "line": 3, + "offset": 76, }, - "type": 2, }, + "type": 3, }, ], - "tag": "template", - "tagType": 3, - "type": 1, - }, - ], - "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 36, - "line": 1, - "offset": 35, - }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "type": 0, -} -`; - -exports[`compiler: parse Errors ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [], "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 35, - "line": 1, - "offset": 34, + "loc": { + "end": { + "column": 7, + "line": 4, + "offset": 123, }, - "source": "", - "start": Object { + "source": "

+

+ +

", + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [ - Object { - "loc": Object { - "end": Object { - "column": 23, - "line": 1, - "offset": 22, - }, - "source": "attr=\\"&#xg;\\"", - "start": Object { - "column": 11, - "line": 1, - "offset": 10, + "props": [ + { + "arg": { + "constType": 3, + "content": "class", + "isStatic": true, + "loc": { + "end": { + "column": 12, + "line": 1, + "offset": 11, + }, + "source": "class", + "start": { + "column": 7, + "line": 1, + "offset": 6, + }, }, + "type": 4, }, - "name": "attr", - "type": 6, - "value": Object { - "content": "&#xg;", - "loc": Object { - "end": Object { - "column": 23, + "exp": { + "constType": 0, + "content": "{ some: condition }", + "isStatic": false, + "loc": { + "end": { + "column": 33, "line": 1, - "offset": 22, + "offset": 32, }, - "source": "\\"&#xg;\\"", - "start": Object { - "column": 16, + "source": "{ some: condition }", + "start": { + "column": 14, "line": 1, - "offset": 15, + "offset": 13, }, }, - "type": 2, + "type": 4, }, - }, - ], - "tag": "template", - "tagType": 3, - "type": 1, - }, - ], - "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 35, - "line": 1, - "offset": 34, - }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "type": 0, -} -`; - -exports[`compiler: parse Errors ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "content": "c", - "loc": Object { - "end": Object { - "column": 16, + "loc": { + "end": { + "column": 34, "line": 1, - "offset": 15, + "offset": 33, }, - "source": "c", - "start": Object { - "column": 11, + "source": ":class="{ some: condition }"", + "start": { + "column": 6, "line": 1, - "offset": 10, + "offset": 5, }, }, - "type": 2, + "modifiers": [], + "name": "bind", + "rawName": ":class", + "type": 7, }, ], - "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 27, - "line": 1, - "offset": 26, - }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "ns": 0, - "props": Array [], - "tag": "template", - "tagType": 3, + "tag": "div", + "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 27, - "line": 1, - "offset": 26, + "components": [], + "directives": [], + "helpers": Set {}, + "hoists": [], + "imports": [], + "loc": { + "end": { + "column": 7, + "line": 4, + "offset": 123, }, - "source": "", - "start": Object { + "source": "
+

+ +

", + "start": { "column": 1, "line": 1, "offset": 0, }, }, + "source": "
+

+ +

", + "temps": 0, "type": 0, } `; -exports[`compiler: parse Errors ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "content": "&#a;", - "loc": Object { - "end": Object { - "column": 15, - "line": 1, - "offset": 14, - }, - "source": "&#a;", - "start": Object { - "column": 11, - "line": 1, - "offset": 10, - }, - }, - "type": 2, - }, - ], +exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > 1`] = ` +{ + "cached": [], + "children": [ + { + "children": [], "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 26, + "loc": { + "end": { + "column": 39, "line": 1, - "offset": 25, + "offset": 38, }, - "source": "", - "start": Object { + "source": "", + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", - "tagType": 3, + "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 26, + "components": [], + "directives": [], + "helpers": Set {}, + "hoists": [], + "imports": [], + "loc": { + "end": { + "column": 39, "line": 1, - "offset": 25, + "offset": 38, }, - "source": "", - "start": Object { + "source": "", + "start": { "column": 1, "line": 1, "offset": 0, }, }, + "source": "", + "temps": 0, "type": 0, } `; -exports[`compiler: parse Errors ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "content": "ÿ", - "loc": Object { - "end": Object { - "column": 17, +exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > 1`] = ` +{ + "cached": [], + "children": [ + { + "children": [ + { + "children": [ + { + "content": "cdata", + "loc": { + "end": { + "column": 30, + "line": 1, + "offset": 29, + }, + "source": "cdata", + "start": { + "column": 25, + "line": 1, + "offset": 24, + }, + }, + "type": 2, + }, + ], + "codegenNode": undefined, + "loc": { + "end": { + "column": 39, "line": 1, - "offset": 16, + "offset": 38, }, - "source": "ÿ", - "start": Object { + "source": "cdata", + "start": { "column": 11, "line": 1, "offset": 10, }, }, - "type": 2, + "ns": 1, + "props": [], + "tag": "svg", + "tagType": 0, + "type": 1, }, ], "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 28, + "loc": { + "end": { + "column": 50, "line": 1, - "offset": 27, + "offset": 49, }, - "source": "", - "start": Object { + "source": "", + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", - "tagType": 3, + "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 28, + "components": [], + "directives": [], + "helpers": Set {}, + "hoists": [], + "imports": [], + "loc": { + "end": { + "column": 50, "line": 1, - "offset": 27, + "offset": 49, }, - "source": "", - "start": Object { + "source": "", + "start": { "column": 1, "line": 1, "offset": 0, }, }, + "source": "", + "temps": 0, "type": 0, } `; -exports[`compiler: parse Errors ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "content": "&#xg;", - "loc": Object { - "end": Object { - "column": 16, +exports[`compiler: parse > Errors > DUPLICATE_ATTRIBUTE > 1`] = ` +{ + "cached": [], + "children": [ + { + "children": [ + { + "children": [], + "codegenNode": undefined, + "loc": { + "end": { + "column": 34, "line": 1, - "offset": 15, + "offset": 33, }, - "source": "&#xg;", - "start": Object { + "source": "
", + "start": { "column": 11, "line": 1, "offset": 10, }, }, - "type": 2, - }, - ], - "codegenNode": undefined, - "isSelfClosing": false, - "loc": Object { - "end": Object { - "column": 27, - "line": 1, - "offset": 26, - }, - "source": "", - "start": Object { - "column": 1, - "line": 1, - "offset": 0, + "ns": 0, + "props": [ + { + "loc": { + "end": { + "column": 21, + "line": 1, + "offset": 20, + }, + "source": "id=""", + "start": { + "column": 16, + "line": 1, + "offset": 15, + }, + }, + "name": "id", + "nameLoc": { + "end": { + "column": 18, + "line": 1, + "offset": 17, + }, + "source": "id", + "start": { + "column": 16, + "line": 1, + "offset": 15, + }, + }, + "type": 6, + "value": { + "content": "", + "loc": { + "end": { + "column": 21, + "line": 1, + "offset": 20, + }, + "source": """", + "start": { + "column": 19, + "line": 1, + "offset": 18, + }, + }, + "type": 2, + }, + }, + { + "loc": { + "end": { + "column": 27, + "line": 1, + "offset": 26, + }, + "source": "id=""", + "start": { + "column": 22, + "line": 1, + "offset": 21, + }, + }, + "name": "id", + "nameLoc": { + "end": { + "column": 24, + "line": 1, + "offset": 23, + }, + "source": "id", + "start": { + "column": 22, + "line": 1, + "offset": 21, + }, + }, + "type": 6, + "value": { + "content": "", + "loc": { + "end": { + "column": 27, + "line": 1, + "offset": 26, + }, + "source": """", + "start": { + "column": 25, + "line": 1, + "offset": 24, + }, + }, + "type": 2, + }, + }, + ], + "tag": "div", + "tagType": 0, + "type": 1, + }, + ], + "codegenNode": undefined, + "loc": { + "end": { + "column": 45, + "line": 1, + "offset": 44, + }, + "source": "", + "start": { + "column": 1, + "line": 1, + "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", - "tagType": 3, + "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], - "helpers": Array [], - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { - "column": 27, + "components": [], + "directives": [], + "helpers": Set {}, + "hoists": [], + "imports": [], + "loc": { + "end": { + "column": 45, "line": 1, - "offset": 26, + "offset": 44, }, - "source": "", - "start": Object { + "source": "", + "start": { "column": 1, "line": 1, "offset": 0, }, }, + "source": "", + "temps": 0, "type": 0, } `; -exports[`compiler: parse Errors CDATA_IN_HTML_CONTENT 1`] = ` -Object { - "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "content": "[CDATA[cdata]]", - "loc": Object { - "end": Object { - "column": 28, +exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME >